diff options
Diffstat (limited to 'api')
-rw-r--r-- | api/api.go | 16 | ||||
-rw-r--r-- | api/auth_jwt.go | 4 | ||||
-rw-r--r-- | api/free_sms.go | 26 | ||||
-rw-r--r-- | api/password_reset.go | 103 | ||||
-rw-r--r-- | api/routes.go | 19 | ||||
-rw-r--r-- | api/user.go | 7 |
6 files changed, 171 insertions, 4 deletions
@@ -7,6 +7,22 @@ import ( | |||
7 | "github.com/gin-gonic/gin" | 7 | "github.com/gin-gonic/gin" |
8 | ) | 8 | ) |
9 | 9 | ||
10 | var CONFIG Config | ||
11 | |||
12 | type Config struct { | ||
13 | JwtSecret string `toml:"jwt_secret"` | ||
14 | PasswordResetSecret string `toml:"password_reset_secret"` | ||
15 | FreeSMSUser string `toml:"free_sms_user"` | ||
16 | FreeSMSPass string `toml:"free_sms_pass"` | ||
17 | } | ||
18 | |||
19 | func SetConfig(config Config) { | ||
20 | CONFIG = config | ||
21 | |||
22 | JWT_SECRET = []byte(config.JwtSecret) | ||
23 | PASSWORD_RESET_SECRET = []byte(config.PasswordResetSecret) | ||
24 | } | ||
25 | |||
10 | type Error struct { | 26 | type Error struct { |
11 | Code ErrorCode | 27 | Code ErrorCode |
12 | UserMessage string | 28 | UserMessage string |
diff --git a/api/auth_jwt.go b/api/auth_jwt.go index 5ce1593..db7e3f4 100644 --- a/api/auth_jwt.go +++ b/api/auth_jwt.go | |||
@@ -20,10 +20,6 @@ type JwtClaims struct { | |||
20 | jwt.StandardClaims | 20 | jwt.StandardClaims |
21 | } | 21 | } |
22 | 22 | ||
23 | func SetJwtSecretKey(secret string) { | ||
24 | JWT_SECRET = []byte(secret) | ||
25 | } | ||
26 | |||
27 | func VerifyJwtToken(token string) (JwtClaims, error) { | 23 | func VerifyJwtToken(token string) (JwtClaims, error) { |
28 | if len(JWT_SECRET) == 0 { | 24 | if len(JWT_SECRET) == 0 { |
29 | return JwtClaims{}, fmt.Errorf("not initialized jwt secret") | 25 | return JwtClaims{}, fmt.Errorf("not initialized jwt secret") |
diff --git a/api/free_sms.go b/api/free_sms.go new file mode 100644 index 0000000..f09a1d1 --- /dev/null +++ b/api/free_sms.go | |||
@@ -0,0 +1,26 @@ | |||
1 | package api | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "net/http" | ||
6 | "net/url" | ||
7 | ) | ||
8 | |||
9 | func SendSMS(user, pass, msg string) error { | ||
10 | form := url.Values{ | ||
11 | "user": []string{user}, | ||
12 | "pass": []string{pass}, | ||
13 | "msg": []string{msg}, | ||
14 | } | ||
15 | |||
16 | response, err := http.Get(fmt.Sprintf("https://smsapi.free-mobile.fr/sendmsg?%s", form.Encode())) | ||
17 | if err != nil { | ||
18 | return err | ||
19 | } | ||
20 | |||
21 | if response.StatusCode != 200 { | ||
22 | return fmt.Errorf("Cannot send sms: status code %v", response.StatusCode) | ||
23 | } | ||
24 | |||
25 | return nil | ||
26 | } | ||
diff --git a/api/password_reset.go b/api/password_reset.go new file mode 100644 index 0000000..82aaaef --- /dev/null +++ b/api/password_reset.go | |||
@@ -0,0 +1,103 @@ | |||
1 | package api | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "time" | ||
6 | |||
7 | "github.com/dchest/passwordreset" | ||
8 | "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db" | ||
9 | ) | ||
10 | |||
11 | var PASSWORD_RESET_SECRET []byte | ||
12 | |||
13 | type PasswordResetQuery struct { | ||
14 | In struct { | ||
15 | Email string | ||
16 | } | ||
17 | } | ||
18 | |||
19 | func (q PasswordResetQuery) ValidateParams() *Error { | ||
20 | if q.In.Email == "" { | ||
21 | return &Error{InvalidEmail, "invalid email", fmt.Errorf("invalid email")} | ||
22 | } | ||
23 | |||
24 | return nil | ||
25 | } | ||
26 | |||
27 | func (q PasswordResetQuery) Run() (interface{}, *Error) { | ||
28 | user, err := db.GetUserByEmail(q.In.Email) | ||
29 | if err != nil { | ||
30 | return nil, NewInternalError(err) | ||
31 | } | ||
32 | |||
33 | if user == nil { | ||
34 | return nil, &Error{NotFound, "account not found", fmt.Errorf("'%v' is not registered", q.In.Email)} | ||
35 | } | ||
36 | |||
37 | token := passwordreset.NewToken(q.In.Email, time.Hour*24*1, []byte(user.PasswordHash), PASSWORD_RESET_SECRET) | ||
38 | if CONFIG.FreeSMSUser != "" { | ||
39 | err := SendSMS(CONFIG.FreeSMSUser, CONFIG.FreeSMSPass, fmt.Sprintf("'%v' request a password reset. Token '/change-password?token=%v'", q.In.Email, token)) | ||
40 | if err != nil { | ||
41 | return nil, NewInternalError(err) | ||
42 | } | ||
43 | } | ||
44 | |||
45 | return "OK", nil | ||
46 | } | ||
47 | |||
48 | type ChangePasswordQuery struct { | ||
49 | In struct { | ||
50 | Token string | ||
51 | Password string | ||
52 | } | ||
53 | } | ||
54 | |||
55 | func (q ChangePasswordQuery) ValidateParams() *Error { | ||
56 | if q.In.Password == "" { | ||
57 | return &Error{InvalidPassword, "invalid password", fmt.Errorf("invalid password")} | ||
58 | } | ||
59 | |||
60 | if q.In.Token == "" { | ||
61 | return &Error{BadRequest, "invalid token", fmt.Errorf("invalid token")} | ||
62 | } | ||
63 | |||
64 | return nil | ||
65 | } | ||
66 | |||
67 | func (q ChangePasswordQuery) Run() (interface{}, *Error) { | ||
68 | var user *db.User | ||
69 | |||
70 | email, err := passwordreset.VerifyToken(q.In.Token, func(email string) ([]byte, error) { | ||
71 | var err error | ||
72 | user, err = db.GetUserByEmail(email) | ||
73 | if err != nil { | ||
74 | return nil, err | ||
75 | } | ||
76 | |||
77 | if user == nil { | ||
78 | return nil, fmt.Errorf("'%v' is not registered", email) | ||
79 | } | ||
80 | |||
81 | return []byte(user.PasswordHash), nil | ||
82 | |||
83 | }, PASSWORD_RESET_SECRET) | ||
84 | |||
85 | if err != nil && (err == passwordreset.ErrExpiredToken) { | ||
86 | return nil, &Error{BadRequest, "expired token", fmt.Errorf("expired token")} | ||
87 | } else if err != nil && (err == passwordreset.ErrMalformedToken || err == passwordreset.ErrWrongSignature) { | ||
88 | return nil, &Error{BadRequest, "wrong token", fmt.Errorf("wrong token")} | ||
89 | } else if err != nil { | ||
90 | return nil, NewInternalError(err) | ||
91 | } | ||
92 | |||
93 | if user == nil { | ||
94 | return nil, &Error{BadRequest, "bad request", fmt.Errorf("no user found for email '%v'", email)} | ||
95 | } | ||
96 | |||
97 | err = db.SetPassword(user, q.In.Password) | ||
98 | if err != nil { | ||
99 | return nil, NewInternalError(err) | ||
100 | } | ||
101 | |||
102 | return "OK", nil | ||
103 | } | ||
diff --git a/api/routes.go b/api/routes.go index cdf3dd9..22af0e7 100644 --- a/api/routes.go +++ b/api/routes.go | |||
@@ -25,6 +25,8 @@ var Groups = []Group{ | |||
25 | []Route{ | 25 | []Route{ |
26 | {"POST", []gin.HandlerFunc{Signup}, "/signup"}, | 26 | {"POST", []gin.HandlerFunc{Signup}, "/signup"}, |
27 | {"POST", []gin.HandlerFunc{Signin}, "/signin"}, | 27 | {"POST", []gin.HandlerFunc{Signin}, "/signin"}, |
28 | {"POST", []gin.HandlerFunc{PasswordReset}, "/passwordreset"}, | ||
29 | {"POST", []gin.HandlerFunc{ChangePassword}, "/changepassword"}, | ||
28 | }, | 30 | }, |
29 | }, | 31 | }, |
30 | { | 32 | { |
@@ -132,3 +134,20 @@ func UpdateMarketConfig(c *gin.Context) { | |||
132 | 134 | ||
133 | RunQuery(query, c) | 135 | RunQuery(query, c) |
134 | } | 136 | } |
137 | |||
138 | func PasswordReset(c *gin.Context) { | ||
139 | query := &PasswordResetQuery{} | ||
140 | |||
141 | query.In.Email = c.PostForm("email") | ||
142 | |||
143 | RunQuery(query, c) | ||
144 | } | ||
145 | |||
146 | func ChangePassword(c *gin.Context) { | ||
147 | query := &ChangePasswordQuery{} | ||
148 | |||
149 | query.In.Token = c.PostForm("token") | ||
150 | query.In.Password = c.PostForm("password") | ||
151 | |||
152 | RunQuery(query, c) | ||
153 | } | ||
diff --git a/api/user.go b/api/user.go index 1dc69e4..9fd9479 100644 --- a/api/user.go +++ b/api/user.go | |||
@@ -74,6 +74,13 @@ func (q SignupQuery) Run() (interface{}, *Error) { | |||
74 | return nil, NewInternalError(fmt.Errorf("cannot create jwt token %v", err)) | 74 | return nil, NewInternalError(fmt.Errorf("cannot create jwt token %v", err)) |
75 | } | 75 | } |
76 | 76 | ||
77 | if CONFIG.FreeSMSUser != "" { | ||
78 | err := SendSMS(CONFIG.FreeSMSUser, CONFIG.FreeSMSPass, fmt.Sprintf("New user signup '%v'", q.In.Email)) | ||
79 | if err != nil { | ||
80 | return nil, NewInternalError(err) | ||
81 | } | ||
82 | } | ||
83 | |||
77 | return SignResult{token}, nil | 84 | return SignResult{token}, nil |
78 | } | 85 | } |
79 | 86 | ||