aboutsummaryrefslogtreecommitdiff
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/api.go19
-rw-r--r--api/mail.go103
-rw-r--r--api/password_reset.go7
-rw-r--r--api/routes.go9
-rw-r--r--api/user.go71
5 files changed, 209 insertions, 0 deletions
diff --git a/api/api.go b/api/api.go
index 79a13a5..e011811 100644
--- a/api/api.go
+++ b/api/api.go
@@ -8,8 +8,18 @@ import (
8) 8)
9 9
10var CONFIG Config 10var CONFIG Config
11var MAIL_CONFIG MailConfig
12
13type 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
12type Config struct { 21type 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
36func 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
26type Error struct { 45type 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 @@
1package api
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/matcornic/hermes"
8 "gopkg.in/gomail.v2"
9)
10
11var MailTemplateEngine hermes.Hermes
12
13func 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
30func 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
62func 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
93func 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
165func 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
3import ( 3import (
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
165type ConfirmEmailQuery struct {
166 In struct {
167 Token string
168 }
169}
170
171func (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
180func (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}