diff options
author | jloup <jeanloup.jamet@gmail.com> | 2018-05-11 13:57:29 +0200 |
---|---|---|
committer | jloup <jeanloup.jamet@gmail.com> | 2018-05-11 13:57:29 +0200 |
commit | 2da5b12c31074591eaf16929b760322b98f189e8 (patch) | |
tree | 407b699688edddca381ca80e5ecea9458d748cc1 /api | |
parent | 299b6b6d9fb879c06e675ef240f361348629ff6c (diff) | |
download | Front-2da5b12c31074591eaf16929b760322b98f189e8.tar.gz Front-2da5b12c31074591eaf16929b760322b98f189e8.tar.zst Front-2da5b12c31074591eaf16929b760322b98f189e8.zip |
Mails.
Diffstat (limited to 'api')
-rw-r--r-- | api/api.go | 19 | ||||
-rw-r--r-- | api/mail.go | 103 | ||||
-rw-r--r-- | api/password_reset.go | 7 | ||||
-rw-r--r-- | api/routes.go | 9 | ||||
-rw-r--r-- | api/user.go | 71 |
5 files changed, 209 insertions, 0 deletions
@@ -8,8 +8,18 @@ import ( | |||
8 | ) | 8 | ) |
9 | 9 | ||
10 | var CONFIG Config | 10 | var CONFIG Config |
11 | var MAIL_CONFIG MailConfig | ||
12 | |||
13 | type MailConfig struct { | ||
14 | IsEnabled bool | ||
15 | SmtpAddress string `toml:"smtp_address"` | ||
16 | AddressFrom string `toml:"address_from"` | ||
17 | Login string `toml:"login"` | ||
18 | Password string `toml:"password"` | ||
19 | } | ||
11 | 20 | ||
12 | type Config struct { | 21 | type Config struct { |
22 | Domain string `toml:"domain"` | ||
13 | JwtSecret string `toml:"jwt_secret"` | 23 | JwtSecret string `toml:"jwt_secret"` |
14 | PasswordResetSecret string `toml:"password_reset_secret"` | 24 | PasswordResetSecret string `toml:"password_reset_secret"` |
15 | FreeSMSUser string `toml:"free_sms_user"` | 25 | FreeSMSUser string `toml:"free_sms_user"` |
@@ -23,6 +33,15 @@ func SetConfig(config Config) { | |||
23 | PASSWORD_RESET_SECRET = []byte(config.PasswordResetSecret) | 33 | PASSWORD_RESET_SECRET = []byte(config.PasswordResetSecret) |
24 | } | 34 | } |
25 | 35 | ||
36 | func SetMailConfig(config MailConfig) { | ||
37 | MAIL_CONFIG = config | ||
38 | |||
39 | if config.Login != "" && config.AddressFrom != "" && config.Password != "" && config.SmtpAddress != "" { | ||
40 | MAIL_CONFIG.IsEnabled = true | ||
41 | ConfigureMailTemplateEngine() | ||
42 | } | ||
43 | } | ||
44 | |||
26 | type Error struct { | 45 | type Error struct { |
27 | Code ErrorCode | 46 | Code ErrorCode |
28 | UserMessage string | 47 | UserMessage string |
diff --git a/api/mail.go b/api/mail.go new file mode 100644 index 0000000..e0f6ccb --- /dev/null +++ b/api/mail.go | |||
@@ -0,0 +1,103 @@ | |||
1 | package api | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strings" | ||
6 | |||
7 | "github.com/matcornic/hermes" | ||
8 | "gopkg.in/gomail.v2" | ||
9 | ) | ||
10 | |||
11 | var MailTemplateEngine hermes.Hermes | ||
12 | |||
13 | func ConfigureMailTemplateEngine() { | ||
14 | var link string | ||
15 | if strings.Contains(CONFIG.Domain, "localhost") { | ||
16 | link = fmt.Sprintf("http://%s", CONFIG.Domain) | ||
17 | } else { | ||
18 | link = fmt.Sprintf("https://%s", CONFIG.Domain) | ||
19 | } | ||
20 | |||
21 | MailTemplateEngine = hermes.Hermes{ | ||
22 | Product: hermes.Product{ | ||
23 | Name: "CryptoPF", | ||
24 | Link: link, | ||
25 | Copyright: "Copyright © 2017 CryptoPF. All rights reserved.", | ||
26 | }, | ||
27 | } | ||
28 | } | ||
29 | |||
30 | func SendResetPasswordMail(to, token string) error { | ||
31 | mail := hermes.Email{ | ||
32 | Body: hermes.Body{ | ||
33 | Name: to, | ||
34 | Intros: []string{ | ||
35 | "You have received this email because a password reset request for your CryptoPF account was received.", | ||
36 | }, | ||
37 | Actions: []hermes.Action{ | ||
38 | { | ||
39 | Instructions: "Click the button below to reset your password:", | ||
40 | Button: hermes.Button{ | ||
41 | Color: "#DC4D2F", | ||
42 | Text: "Reset your password", | ||
43 | Link: fmt.Sprintf("%s/change-password?token=%s", MailTemplateEngine.Product.Link, token), | ||
44 | }, | ||
45 | }, | ||
46 | }, | ||
47 | Outros: []string{ | ||
48 | "If you did not request a password reset, no further action is required on your part.", | ||
49 | }, | ||
50 | Signature: "Thanks", | ||
51 | }, | ||
52 | } | ||
53 | |||
54 | body, err := MailTemplateEngine.GenerateHTML(mail) | ||
55 | if err != nil { | ||
56 | return err | ||
57 | } | ||
58 | |||
59 | return SendEmail(to, "Password reset", body) | ||
60 | } | ||
61 | |||
62 | func SendConfirmationMail(to, token string) error { | ||
63 | mail := hermes.Email{ | ||
64 | Body: hermes.Body{ | ||
65 | Name: to, | ||
66 | Intros: []string{ | ||
67 | "Welcome to CryptoPF! We're very excited to have you on board.", | ||
68 | }, | ||
69 | Actions: []hermes.Action{ | ||
70 | { | ||
71 | Instructions: "To get started with CryptoPF, please click here:", | ||
72 | Button: hermes.Button{ | ||
73 | Text: "Confirm your account", | ||
74 | Link: fmt.Sprintf("%s/confirm?token=%s", MailTemplateEngine.Product.Link, token), | ||
75 | }, | ||
76 | }, | ||
77 | }, | ||
78 | Outros: []string{ | ||
79 | "Need help, or have questions? Just reply to this email, we'd love to help.", | ||
80 | }, | ||
81 | Signature: "Thanks", | ||
82 | }, | ||
83 | } | ||
84 | |||
85 | body, err := MailTemplateEngine.GenerateHTML(mail) | ||
86 | if err != nil { | ||
87 | return err | ||
88 | } | ||
89 | |||
90 | return SendEmail(to, "Confirm your email", body) | ||
91 | } | ||
92 | |||
93 | func SendEmail(to, subject, body string) error { | ||
94 | m := gomail.NewMessage() | ||
95 | m.SetAddressHeader("From", MAIL_CONFIG.AddressFrom, "CryptoPF") | ||
96 | m.SetAddressHeader("To", to, to) | ||
97 | m.SetHeader("Subject", subject) | ||
98 | m.SetBody("text/html", body) | ||
99 | |||
100 | d := gomail.NewPlainDialer(MAIL_CONFIG.SmtpAddress, 587, MAIL_CONFIG.Login, MAIL_CONFIG.Password) | ||
101 | |||
102 | return d.DialAndSend(m) | ||
103 | } | ||
diff --git a/api/password_reset.go b/api/password_reset.go index 4b002cd..c7931d4 100644 --- a/api/password_reset.go +++ b/api/password_reset.go | |||
@@ -42,6 +42,13 @@ func (q PasswordResetQuery) Run() (interface{}, *Error) { | |||
42 | } | 42 | } |
43 | } | 43 | } |
44 | 44 | ||
45 | if MAIL_CONFIG.IsEnabled { | ||
46 | err = SendResetPasswordMail(q.In.Email, token) | ||
47 | if err != nil { | ||
48 | return nil, NewInternalError(err) | ||
49 | } | ||
50 | } | ||
51 | |||
45 | return nil, nil | 52 | return nil, nil |
46 | } | 53 | } |
47 | 54 | ||
diff --git a/api/routes.go b/api/routes.go index d7b316d..d0e8cec 100644 --- a/api/routes.go +++ b/api/routes.go | |||
@@ -27,6 +27,7 @@ var Groups = []Group{ | |||
27 | {"POST", []gin.HandlerFunc{Signin}, "/signin"}, | 27 | {"POST", []gin.HandlerFunc{Signin}, "/signin"}, |
28 | {"POST", []gin.HandlerFunc{PasswordReset}, "/passwordreset"}, | 28 | {"POST", []gin.HandlerFunc{PasswordReset}, "/passwordreset"}, |
29 | {"POST", []gin.HandlerFunc{ChangePassword}, "/changepassword"}, | 29 | {"POST", []gin.HandlerFunc{ChangePassword}, "/changepassword"}, |
30 | {"POST", []gin.HandlerFunc{ConfirmEmail}, "/confirmemail"}, | ||
30 | }, | 31 | }, |
31 | }, | 32 | }, |
32 | { | 33 | { |
@@ -160,3 +161,11 @@ func ChangePassword(c *gin.Context) { | |||
160 | 161 | ||
161 | RunQuery(query, c) | 162 | RunQuery(query, c) |
162 | } | 163 | } |
164 | |||
165 | func ConfirmEmail(c *gin.Context) { | ||
166 | query := &ConfirmEmailQuery{} | ||
167 | |||
168 | query.In.Token = c.PostForm("token") | ||
169 | |||
170 | RunQuery(query, c) | ||
171 | } | ||
diff --git a/api/user.go b/api/user.go index c1d9d6c..2848696 100644 --- a/api/user.go +++ b/api/user.go | |||
@@ -3,7 +3,10 @@ package api | |||
3 | import ( | 3 | import ( |
4 | "fmt" | 4 | "fmt" |
5 | "regexp" | 5 | "regexp" |
6 | "strconv" | ||
7 | "time" | ||
6 | 8 | ||
9 | "github.com/dchest/passwordreset" | ||
7 | "github.com/gin-gonic/gin" | 10 | "github.com/gin-gonic/gin" |
8 | 11 | ||
9 | "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db" | 12 | "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db" |
@@ -90,6 +93,21 @@ func (q SignupQuery) Run() (interface{}, *Error) { | |||
90 | return nil, NewInternalError(err) | 93 | return nil, NewInternalError(err) |
91 | } | 94 | } |
92 | 95 | ||
96 | if MAIL_CONFIG.IsEnabled { | ||
97 | mailConfirmationToken := passwordreset.NewToken(q.In.Email, time.Hour*24*1, []byte(strconv.FormatUint(uint64(newUser.Status), 10)), PASSWORD_RESET_SECRET) | ||
98 | err = SendConfirmationMail(q.In.Email, mailConfirmationToken) | ||
99 | if err != nil { | ||
100 | return nil, NewInternalError(err) | ||
101 | } | ||
102 | } | ||
103 | |||
104 | if CONFIG.FreeSMSUser != "" { | ||
105 | err := SendSMS(CONFIG.FreeSMSUser, CONFIG.FreeSMSPass, fmt.Sprintf("'%v' request a password reset. Token '/change-password?token=%v'", q.In.Email, token)) | ||
106 | if err != nil { | ||
107 | return nil, NewInternalError(err) | ||
108 | } | ||
109 | } | ||
110 | |||
93 | return SignResult{token}, nil | 111 | return SignResult{token}, nil |
94 | } | 112 | } |
95 | 113 | ||
@@ -143,3 +161,56 @@ func GetUser(c *gin.Context) db.User { | |||
143 | 161 | ||
144 | return user.(db.User) | 162 | return user.(db.User) |
145 | } | 163 | } |
164 | |||
165 | type ConfirmEmailQuery struct { | ||
166 | In struct { | ||
167 | Token string | ||
168 | } | ||
169 | } | ||
170 | |||
171 | func (q ConfirmEmailQuery) ValidateParams() *Error { | ||
172 | |||
173 | if q.In.Token == "" { | ||
174 | return &Error{BadRequest, "invalid token", fmt.Errorf("invalid token")} | ||
175 | } | ||
176 | |||
177 | return nil | ||
178 | } | ||
179 | |||
180 | func (q ConfirmEmailQuery) Run() (interface{}, *Error) { | ||
181 | var user *db.User | ||
182 | |||
183 | email, err := passwordreset.VerifyToken(q.In.Token, func(email string) ([]byte, error) { | ||
184 | var err error | ||
185 | user, err = db.GetUserByEmail(email) | ||
186 | if err != nil { | ||
187 | return nil, err | ||
188 | } | ||
189 | |||
190 | if user == nil { | ||
191 | return nil, fmt.Errorf("'%v' is not registered", email) | ||
192 | } | ||
193 | |||
194 | return []byte(strconv.FormatUint(uint64(user.Status), 10)), nil | ||
195 | |||
196 | }, PASSWORD_RESET_SECRET) | ||
197 | |||
198 | if err != nil && (err == passwordreset.ErrExpiredToken) { | ||
199 | return nil, &Error{BadRequest, "expired token", fmt.Errorf("expired token")} | ||
200 | } else if err != nil && (err == passwordreset.ErrMalformedToken || err == passwordreset.ErrWrongSignature) { | ||
201 | return nil, &Error{BadRequest, "wrong token", fmt.Errorf("wrong token")} | ||
202 | } else if err != nil { | ||
203 | return nil, NewInternalError(err) | ||
204 | } | ||
205 | |||
206 | if user == nil { | ||
207 | return nil, &Error{BadRequest, "bad request", fmt.Errorf("no user found for email '%v'", email)} | ||
208 | } | ||
209 | |||
210 | err = db.SetUserStatus(user, db.Confirmed) | ||
211 | if err != nil { | ||
212 | return nil, NewInternalError(err) | ||
213 | } | ||
214 | |||
215 | return nil, nil | ||
216 | } | ||