aboutsummaryrefslogtreecommitdiff
path: root/api
diff options
context:
space:
mode:
authorjloup <jean-loup.jamet@trainline.com>2018-02-14 14:19:09 +0100
committerjloup <jean-loup.jamet@trainline.com>2018-02-14 14:19:09 +0100
commit7a9e5112eaaea58d55f181d3e5296e4ff839921c (patch)
tree968ed193f42a1fad759cc89ad2f8ad5b0091291e /api
downloadFront-7a9e5112eaaea58d55f181d3e5296e4ff839921c.tar.gz
Front-7a9e5112eaaea58d55f181d3e5296e4ff839921c.tar.zst
Front-7a9e5112eaaea58d55f181d3e5296e4ff839921c.zip
initial commit
Diffstat (limited to 'api')
-rw-r--r--api/api.go130
-rw-r--r--api/auth_jwt.go107
-rw-r--r--api/auth_otp.go122
-rw-r--r--api/const.go51
-rw-r--r--api/const_string.go28
-rw-r--r--api/logger.go27
-rw-r--r--api/market_config.go77
-rw-r--r--api/routes.go123
-rw-r--r--api/user.go133
9 files changed, 798 insertions, 0 deletions
diff --git a/api/api.go b/api/api.go
new file mode 100644
index 0000000..7b7be49
--- /dev/null
+++ b/api/api.go
@@ -0,0 +1,130 @@
1package api
2
3import (
4 "net/http"
5 "unicode"
6
7 "github.com/gin-gonic/gin"
8)
9
10type Error struct {
11 Code ErrorCode
12 UserMessage string
13 err error
14}
15
16func (e Error) Err() error {
17 if e.err != nil {
18 return e
19 }
20
21 return nil
22}
23
24func (e Error) Error() string {
25 if e.err != nil {
26 return e.err.Error()
27 }
28
29 return ""
30}
31
32func NewInternalError(err error) *Error {
33 return &Error{InternalError, "internal error", err}
34}
35
36func ToSnake(in string) string {
37 runes := []rune(in)
38 length := len(runes)
39
40 var out []rune
41 for i := 0; i < length; i++ {
42 if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
43 out = append(out, '_')
44 }
45 out = append(out, unicode.ToLower(runes[i]))
46 }
47
48 return string(out)
49}
50
51type Response struct {
52 StatusCode Status `json:"-"`
53 ErrorCode ErrorCode `json:"-"`
54
55 Status string `json:"status"`
56 Code string `json:"code,omitempty"`
57 Response interface{} `json:"response,omitempty"`
58 Message string `json:"message,omitempty"`
59}
60
61func (r Response) populateStatus() Response {
62 r.Status = ToSnake(r.StatusCode.String())
63
64 if r.ErrorCode != 0 {
65 r.Code = ToSnake(r.ErrorCode.String())
66 }
67
68 return r
69}
70
71func ErrorResponse(code ErrorCode, message string) Response {
72 return Response{
73 StatusCode: NOK,
74 ErrorCode: code,
75 Message: message,
76 }
77}
78
79func SuccessResponse(data interface{}) Response {
80 return Response{
81 StatusCode: OK,
82 Response: data,
83 }
84}
85
86func WriteJsonResponse(response Response, c *gin.Context) {
87 response = response.populateStatus()
88
89 c.JSON(StatusToHttpCode(response.StatusCode, response.ErrorCode), response)
90}
91
92func WriteBinary(contentType string, b []byte, c *gin.Context) {
93 c.Data(http.StatusOK, contentType, b)
94}
95
96type Middleware func(*gin.Context) *Error
97
98func M(handler Middleware) gin.HandlerFunc {
99 return func(c *gin.Context) {
100 err := handler(c)
101
102 if err != nil {
103 WriteJsonResponse(ErrorResponse(err.Code, err.UserMessage), c)
104 c.Error(err)
105 c.Abort()
106 } else {
107 c.Next()
108 }
109 }
110}
111
112type Query interface {
113 ValidateParams() *Error
114 Run() (interface{}, *Error)
115}
116
117func RunQuery(query Query, c *gin.Context) {
118 if err := query.ValidateParams(); err != nil {
119 WriteJsonResponse(ErrorResponse(err.Code, err.UserMessage), c)
120 c.Error(err)
121 return
122 }
123
124 if out, err := query.Run(); err != nil {
125 WriteJsonResponse(ErrorResponse(err.Code, err.UserMessage), c)
126 c.Error(err)
127 } else {
128 WriteJsonResponse(SuccessResponse(out), c)
129 }
130}
diff --git a/api/auth_jwt.go b/api/auth_jwt.go
new file mode 100644
index 0000000..f713f4e
--- /dev/null
+++ b/api/auth_jwt.go
@@ -0,0 +1,107 @@
1package api
2
3import (
4 "fmt"
5 "strings"
6 "time"
7
8 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db"
9
10 "github.com/dgrijalva/jwt-go"
11 "github.com/gin-gonic/gin"
12)
13
14// Static secret.
15var JWT_SECRET []byte
16
17type JwtClaims struct {
18 Authorized bool `json:"authorized"`
19 Subject int64 `json:"sub,omitempty"`
20 jwt.StandardClaims
21}
22
23func SetJwtSecretKey(secret string) {
24 JWT_SECRET = []byte(secret)
25}
26
27func VerifyJwtToken(token string) (JwtClaims, error) {
28 if len(JWT_SECRET) == 0 {
29 return JwtClaims{}, fmt.Errorf("not initialized jwt secret")
30 }
31
32 t, err := jwt.ParseWithClaims(token, &JwtClaims{}, func(t *jwt.Token) (interface{}, error) {
33 if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
34 return nil, fmt.Errorf("Unexpected signing method: %v", t.Header["alg"])
35 }
36
37 return JWT_SECRET, nil
38 })
39
40 claims, ok := t.Claims.(*JwtClaims)
41
42 if !ok || !t.Valid || err != nil {
43 return JwtClaims{}, fmt.Errorf("invalid token (err: %v, claimsok: %v)", err, ok)
44 }
45
46 return *claims, nil
47}
48
49func SignJwt(claims JwtClaims) (string, error) {
50 if len(JWT_SECRET) == 0 {
51 return "", fmt.Errorf("not initialized jwt secret")
52 }
53
54 token := jwt.NewWithClaims(jwt.SigningMethodHS256, &claims)
55
56 return token.SignedString(JWT_SECRET)
57}
58
59func CreateJwtToken(userId int64) (string, error) {
60 claims := JwtClaims{
61 false,
62 userId,
63 jwt.StandardClaims{
64 ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
65 },
66 }
67
68 return SignJwt(claims)
69}
70
71func GetBearerToken(header string) (string, error) {
72 const prefix = "Bearer "
73
74 if !strings.HasPrefix(header, prefix) {
75 return "", fmt.Errorf("invalid authorization token")
76 }
77
78 return header[len(prefix):], nil
79}
80
81func JwtAuth(c *gin.Context) *Error {
82 token, err := GetBearerToken(c.GetHeader("Authorization"))
83 if err != nil {
84 return &Error{NotAuthorized, "not authorized", err}
85 }
86
87 claims, err := VerifyJwtToken(token)
88 if err != nil {
89 return &Error{NotAuthorized, "not authorized", err}
90 }
91
92 user, err := db.GetUserById(claims.Subject)
93 if err != nil {
94 return &Error{NotAuthorized, "not authorized", err}
95 }
96
97 c.Set("user", *user)
98 c.Set("claims", claims)
99
100 return nil
101}
102
103func GetClaims(c *gin.Context) JwtClaims {
104 claims, _ := c.Get("claims")
105
106 return claims.(JwtClaims)
107}
diff --git a/api/auth_otp.go b/api/auth_otp.go
new file mode 100644
index 0000000..de1cf24
--- /dev/null
+++ b/api/auth_otp.go
@@ -0,0 +1,122 @@
1package api
2
3import (
4 "bytes"
5 "fmt"
6 "image/png"
7
8 "github.com/gin-gonic/gin"
9 "github.com/pquerna/otp/totp"
10 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db"
11)
12
13func GenerateSecret(user db.User) (bytes.Buffer, string, error) {
14 var buf bytes.Buffer
15 key, err := totp.Generate(totp.GenerateOpts{
16 Issuer: "cryptoportfolio",
17 AccountName: user.Email,
18 Period: 30,
19 })
20
21 if err != nil {
22 return buf, "", err
23 }
24
25 qrImage, err := key.Image(200, 200)
26 if err != nil {
27 return buf, "", err
28 }
29
30 png.Encode(&buf, qrImage)
31 if err != nil {
32 return buf, "", err
33 }
34
35 return buf, key.Secret(), nil
36}
37
38type OtpEnrollmentQuery struct {
39 In struct {
40 User db.User
41 }
42}
43
44func (q OtpEnrollmentQuery) ValidateParams() *Error {
45 return nil
46}
47
48func (q OtpEnrollmentQuery) Run() (*bytes.Buffer, string, *Error) {
49 if q.In.User.OtpSecret != "" && q.In.User.IsOtpSetup == true {
50 return nil, "", &Error{OtpAlreadySetup, "otp is already setup", fmt.Errorf("otp already setup")}
51 }
52
53 buf, secret, err := GenerateSecret(q.In.User)
54 if err != nil {
55 return nil, "", NewInternalError(err)
56 }
57
58 err = db.SetOtpSecret(&q.In.User, secret, true)
59 if err != nil {
60 return nil, "", NewInternalError(err)
61 }
62
63 return &buf, secret, nil
64}
65
66type OtpValidateQuery struct {
67 In struct {
68 Pass string
69 User db.User
70 Claims JwtClaims
71 }
72
73 Out struct {
74 Token string `json:"token"`
75 }
76}
77
78func (q OtpValidateQuery) ValidateParams() *Error {
79 if q.In.Pass == "" {
80 return &Error{InvalidOtp, "invalid otp", fmt.Errorf("invalid otp '%v'", q.In.Pass)}
81 }
82
83 return nil
84}
85
86func (q OtpValidateQuery) Run() (interface{}, *Error) {
87 var err error
88
89 if q.In.User.OtpSecret == "" {
90 return nil, &Error{OtpNotSetup, "otp is not setup", fmt.Errorf("otp is not setup")}
91 }
92
93 if !totp.Validate(q.In.Pass, q.In.User.OtpSecret) {
94 return nil, &Error{InvalidOtp, "invalid otp", fmt.Errorf("invalid otp '%v'", q.In.Pass)}
95
96 } else if err := db.SetOtpSecret(&q.In.User, q.In.User.OtpSecret, false); err != nil {
97 return nil, NewInternalError(err)
98 }
99
100 q.In.Claims.Authorized = true
101 q.Out.Token, err = SignJwt(q.In.Claims)
102 if err != nil {
103 return nil, NewInternalError(err)
104 }
105
106 return q.Out, nil
107}
108
109func OtpAuth(c *gin.Context) *Error {
110 claims := GetClaims(c)
111 user := GetUser(c)
112
113 if user.IsOtpSetup == false || user.OtpSecret == "" {
114 return &Error{OtpNotSetup, "otp is not setup", fmt.Errorf("otp is not setup")}
115 }
116
117 if !claims.Authorized {
118 return &Error{NeedOtpValidation, "not authorized", fmt.Errorf("otp not authorized")}
119 }
120
121 return nil
122}
diff --git a/api/const.go b/api/const.go
new file mode 100644
index 0000000..2edd6f4
--- /dev/null
+++ b/api/const.go
@@ -0,0 +1,51 @@
1package api
2
3import "net/http"
4
5//go:generate stringer -type=Status,ErrorCode -output const_string.go
6type Status uint32
7type ErrorCode uint32
8
9const (
10 OK Status = iota
11 NOK
12
13 BadRequest ErrorCode = iota + 1
14 EmailExists
15 InternalError
16 InvalidCredentials
17 InvalidEmail
18 InvalidOtp
19 InvalidPassword
20 NeedOtpValidation
21 NotAuthorized
22 NotFound
23 OtpAlreadySetup
24 OtpNotSetup
25 UserNotConfirmed
26)
27
28func StatusToHttpCode(status Status, code ErrorCode) int {
29 if status == OK {
30 return http.StatusOK
31 }
32
33 switch code {
34 case BadRequest, InvalidPassword, InvalidEmail:
35 return http.StatusBadRequest
36
37 case InvalidCredentials, InvalidOtp:
38 return http.StatusUnauthorized
39
40 case UserNotConfirmed, NotAuthorized, OtpAlreadySetup, OtpNotSetup, NeedOtpValidation:
41 return http.StatusForbidden
42
43 case EmailExists:
44 return http.StatusConflict
45
46 case NotFound:
47 return http.StatusNotFound
48 }
49
50 return http.StatusInternalServerError
51}
diff --git a/api/const_string.go b/api/const_string.go
new file mode 100644
index 0000000..611db40
--- /dev/null
+++ b/api/const_string.go
@@ -0,0 +1,28 @@
1// Code generated by "stringer -type=Status,ErrorCode -output const_string.go"; DO NOT EDIT
2
3package api
4
5import "fmt"
6
7const _Status_name = "OKNOK"
8
9var _Status_index = [...]uint8{0, 2, 5}
10
11func (i Status) String() string {
12 if i >= Status(len(_Status_index)-1) {
13 return fmt.Sprintf("Status(%d)", i)
14 }
15 return _Status_name[_Status_index[i]:_Status_index[i+1]]
16}
17
18const _ErrorCode_name = "BadRequestEmailExistsInternalErrorInvalidCredentialsInvalidEmailInvalidOtpInvalidPasswordNeedOtpValidationNotAuthorizedNotFoundOtpAlreadySetupOtpNotSetupUserNotConfirmed"
19
20var _ErrorCode_index = [...]uint8{0, 10, 21, 34, 52, 64, 74, 89, 106, 119, 127, 142, 153, 169}
21
22func (i ErrorCode) String() string {
23 i -= 3
24 if i >= ErrorCode(len(_ErrorCode_index)-1) {
25 return fmt.Sprintf("ErrorCode(%d)", i+3)
26 }
27 return _ErrorCode_name[_ErrorCode_index[i]:_ErrorCode_index[i+1]]
28}
diff --git a/api/logger.go b/api/logger.go
new file mode 100644
index 0000000..7057a30
--- /dev/null
+++ b/api/logger.go
@@ -0,0 +1,27 @@
1package api
2
3import (
4 "github.com/gin-gonic/gin"
5 "github.com/jloup/utils"
6)
7
8var log = utils.StandardL().WithField("module", "api")
9
10func Logger() gin.HandlerFunc {
11 return func(c *gin.Context) {
12 path := c.Request.URL.Path
13 rawQuery := c.Request.URL.RawQuery
14
15 c.Next()
16
17 for _, err := range c.Errors {
18 l := log.WithField("path", path)
19
20 if rawQuery != "" {
21 l = l.WithField("query", rawQuery)
22 }
23
24 l.Errorf("%s", err.Err)
25 }
26 }
27}
diff --git a/api/market_config.go b/api/market_config.go
new file mode 100644
index 0000000..3fd10ae
--- /dev/null
+++ b/api/market_config.go
@@ -0,0 +1,77 @@
1package api
2
3import (
4 "fmt"
5
6 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db"
7)
8
9type MarketConfigQuery struct {
10 In struct {
11 User db.User
12 Market string
13 }
14}
15
16func (q MarketConfigQuery) ValidateParams() *Error {
17 if q.In.Market != "poloniex" {
18 return &Error{BadRequest, "invalid market name", fmt.Errorf("'%v' is not a valid market name", q.In.Market)}
19 }
20
21 return nil
22}
23
24func (q MarketConfigQuery) Run() (interface{}, *Error) {
25 config, err := db.GetUserMarketConfig(q.In.User.Id, q.In.Market)
26 if err != nil {
27 return nil, NewInternalError(err)
28 }
29
30 if config == nil {
31 configMap := make(map[string]string)
32 configMap["key"] = ""
33 configMap["secret"] = ""
34
35 config, err = db.SetUserMarketConfig(q.In.User.Id, q.In.Market, configMap)
36 if err != nil {
37 return nil, NewInternalError(err)
38 }
39
40 }
41
42 return config.Config, nil
43}
44
45type UpdateMarketConfigQuery struct {
46 In struct {
47 User db.User
48 Market string
49 Key string
50 Secret string
51 }
52}
53
54func (q UpdateMarketConfigQuery) ValidateParams() *Error {
55 if q.In.Market == "" {
56 return &Error{BadRequest, "invalid market name", fmt.Errorf("'%v' is not a valid market name", q.In.Market)}
57 }
58
59 return nil
60}
61
62func (q UpdateMarketConfigQuery) Run() (interface{}, *Error) {
63 configMap := make(map[string]string)
64 if q.In.Key != "" {
65 configMap["key"] = q.In.Key
66 }
67 if q.In.Secret != "" {
68 configMap["secret"] = q.In.Secret
69 }
70
71 _, err := db.SetUserMarketConfig(q.In.User.Id, q.In.Market, configMap)
72 if err != nil {
73 return nil, NewInternalError(err)
74 }
75
76 return nil, nil
77}
diff --git a/api/routes.go b/api/routes.go
new file mode 100644
index 0000000..d7e712c
--- /dev/null
+++ b/api/routes.go
@@ -0,0 +1,123 @@
1package api
2
3import (
4 "encoding/base64"
5
6 "github.com/gin-gonic/gin"
7)
8
9type Route struct {
10 Method string
11 Handlers []gin.HandlerFunc
12 Path string
13}
14
15type Group struct {
16 Root string
17 Middlewares []Middleware
18 Routes []Route
19}
20
21var Groups = []Group{
22 {
23 "",
24 nil,
25 []Route{
26 {"POST", []gin.HandlerFunc{Signup}, "/signup"},
27 {"POST", []gin.HandlerFunc{Signin}, "/signin"},
28 },
29 },
30 {
31 "/otp",
32 []Middleware{JwtAuth, UserConfirmed},
33 []Route{
34 {"GET", []gin.HandlerFunc{OtpEnrollment}, "/enroll"},
35 {"POST", []gin.HandlerFunc{OtpValidate}, "/validate"},
36 },
37 },
38 {
39 "/market",
40 []Middleware{JwtAuth, UserConfirmed, OtpAuth},
41 []Route{
42 {"GET", []gin.HandlerFunc{GetMarketConfig}, "/:name"},
43 {"POST", []gin.HandlerFunc{UpdateMarketConfig}, "/:name/update"},
44 },
45 },
46}
47
48func Signup(c *gin.Context) {
49 query := &SignupQuery{}
50
51 query.In.Email = c.PostForm("email")
52 query.In.Password = c.PostForm("password")
53
54 RunQuery(query, c)
55}
56
57func Signin(c *gin.Context) {
58 query := &SigninQuery{}
59
60 query.In.Email = c.PostForm("email")
61 query.In.Password = c.PostForm("password")
62
63 RunQuery(query, c)
64}
65
66func OtpValidate(c *gin.Context) {
67 query := &OtpValidateQuery{}
68
69 query.In.Pass = c.PostForm("pass")
70 query.In.User = GetUser(c)
71 query.In.Claims = GetClaims(c)
72
73 RunQuery(query, c)
74}
75
76func OtpEnrollment(c *gin.Context) {
77 query := &OtpEnrollmentQuery{}
78
79 query.In.User = GetUser(c)
80
81 qrPng, secret, err := query.Run()
82 if err != nil {
83 WriteJsonResponse(ErrorResponse(err.Code, err.UserMessage), c)
84 c.Error(err)
85 return
86 }
87
88 if c.Query("format") == "png" {
89 c.Header("X-OTP-Secret", secret)
90 WriteBinary("image/png", qrPng.Bytes(), c)
91 } else {
92 response := struct {
93 Base64img string `json:"base64img"`
94 OtpSecret string `json:"secret"`
95 }{
96 base64.StdEncoding.EncodeToString(qrPng.Bytes()),
97 secret,
98 }
99
100 WriteJsonResponse(SuccessResponse(response), c)
101 }
102
103}
104
105func GetMarketConfig(c *gin.Context) {
106 query := &MarketConfigQuery{}
107
108 query.In.User = GetUser(c)
109 query.In.Market = c.Param("name")
110
111 RunQuery(query, c)
112}
113
114func UpdateMarketConfig(c *gin.Context) {
115 query := &UpdateMarketConfigQuery{}
116
117 query.In.User = GetUser(c)
118 query.In.Market = c.Param("name")
119 query.In.Key = c.PostForm("key")
120 query.In.Secret = c.PostForm("secret")
121
122 RunQuery(query, c)
123}
diff --git a/api/user.go b/api/user.go
new file mode 100644
index 0000000..4d4edba
--- /dev/null
+++ b/api/user.go
@@ -0,0 +1,133 @@
1package api
2
3import (
4 "fmt"
5 "regexp"
6
7 "github.com/gin-gonic/gin"
8
9 "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db"
10)
11
12const (
13 VALID_EMAIL_REGEX = `(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$`
14)
15
16func IsValidEmailAddress(email string) bool {
17 r := regexp.MustCompile(VALID_EMAIL_REGEX)
18
19 return r.MatchString(email)
20}
21
22type SignParams struct {
23 Email string
24 Password string
25}
26
27type SignResult struct {
28 Token string `json:"token"`
29}
30
31func (s SignParams) Validate() *Error {
32 if !IsValidEmailAddress(s.Email) {
33 return &Error{InvalidEmail, "invalid email", fmt.Errorf("'%v' is not a valid email", s.Email)}
34 }
35
36 if s.Password == "" {
37 return &Error{InvalidPassword, "invalid password", fmt.Errorf("invalid password")}
38 }
39
40 return nil
41}
42
43type SignupQuery struct {
44 In SignParams
45}
46
47func (q SignupQuery) ValidateParams() *Error {
48 return q.In.Validate()
49}
50
51func (q SignupQuery) Run() (interface{}, *Error) {
52 user, err := db.GetUserByEmail(q.In.Email)
53 if err != nil {
54 return nil, NewInternalError(err)
55 }
56
57 if user != nil {
58 return nil, &Error{EmailExists, "email already taken", fmt.Errorf("'%v' is already registered '%v'", q.In.Email, user)}
59 }
60
61 newUser := db.User{Email: q.In.Email, Status: db.AwaitingConfirmation}
62 newUser.PasswordHash, err = db.HashPassword(q.In.Password)
63 if err != nil {
64 return nil, NewInternalError(err)
65 }
66
67 err = db.InsertUser(&newUser)
68 if err != nil {
69 return nil, NewInternalError(err)
70 }
71
72 token, err := CreateJwtToken(newUser.Id)
73 if err != nil {
74 return nil, NewInternalError(fmt.Errorf("cannot create jwt token %v", err))
75 }
76
77 return SignResult{token}, nil
78}
79
80type SigninQuery struct {
81 In SignParams
82}
83
84func (q SigninQuery) ValidateParams() *Error {
85 return q.In.Validate()
86}
87
88func (q SigninQuery) Run() (interface{}, *Error) {
89 user, err := db.GetUserByEmail(q.In.Email)
90 if err != nil {
91 return nil, NewInternalError(err)
92 }
93
94 if user == nil {
95 return nil, &Error{InvalidCredentials, "invalid credentials", fmt.Errorf("no email '%v' found", q.In.Email)}
96 }
97
98 err = db.ValidatePassword(q.In.Password, user.PasswordHash)
99 if err != nil {
100 return nil, &Error{InvalidCredentials, "invalid credentials", err}
101 }
102
103 if user.Status != db.Confirmed {
104 return nil, &Error{UserNotConfirmed, "user awaiting admin validation", fmt.Errorf("user '%v' not confirmed", user)}
105 }
106
107 token, err := CreateJwtToken(user.Id)
108 if err != nil {
109 return nil, NewInternalError(err)
110 }
111
112 return SignResult{token}, nil
113}
114
115func UserConfirmed(c *gin.Context) *Error {
116 user, exists := c.Get("user")
117
118 if !exists {
119 return &Error{NotAuthorized, "not authorized", fmt.Errorf("no user key in context")}
120 }
121
122 if user.(db.User).Status != db.Confirmed {
123 return &Error{UserNotConfirmed, "user awaiting admin validation", fmt.Errorf("user '%v' not confirmed", user)}
124 }
125
126 return nil
127}
128
129func GetUser(c *gin.Context) db.User {
130 user, _ := c.Get("user")
131
132 return user.(db.User)
133}