aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjloup <jeanloup.jamet@gmail.com>2018-05-11 13:57:29 +0200
committerjloup <jeanloup.jamet@gmail.com>2018-05-11 13:57:29 +0200
commit2da5b12c31074591eaf16929b760322b98f189e8 (patch)
tree407b699688edddca381ca80e5ecea9458d748cc1
parent299b6b6d9fb879c06e675ef240f361348629ff6c (diff)
downloadFront-2da5b12c31074591eaf16929b760322b98f189e8.tar.gz
Front-2da5b12c31074591eaf16929b760322b98f189e8.tar.zst
Front-2da5b12c31074591eaf16929b760322b98f189e8.zip
Mails.
-rw-r--r--Gopkg.lock100
-rw-r--r--Gopkg.toml4
-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
-rw-r--r--cmd/app/main.go4
-rw-r--r--cmd/web/js/api.js10
-rw-r--r--cmd/web/js/main.jsx25
-rw-r--r--db/user.go6
11 files changed, 353 insertions, 5 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 91f891c..2a6ae4f 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -8,12 +8,30 @@
8 version = "v0.3.0" 8 version = "v0.3.0"
9 9
10[[projects]] 10[[projects]]
11 name = "github.com/Masterminds/semver"
12 packages = ["."]
13 revision = "c7af12943936e8c39859482e61f0574c2fd7fc75"
14 version = "v1.4.2"
15
16[[projects]]
17 name = "github.com/Masterminds/sprig"
18 packages = ["."]
19 revision = "6b2a58267f6a8b1dc8e2eb5519b984008fa85e8c"
20 version = "v2.15.0"
21
22[[projects]]
11 name = "github.com/Sirupsen/logrus" 23 name = "github.com/Sirupsen/logrus"
12 packages = ["."] 24 packages = ["."]
13 revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" 25 revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
14 version = "v1.0.5" 26 version = "v1.0.5"
15 27
16[[projects]] 28[[projects]]
29 name = "github.com/aokoli/goutils"
30 packages = ["."]
31 revision = "3391d3790d23d03408670993e957e8f408993c34"
32 version = "v1.0.1"
33
34[[projects]]
17 name = "github.com/boombuler/barcode" 35 name = "github.com/boombuler/barcode"
18 packages = [ 36 packages = [
19 ".", 37 ".",
@@ -104,6 +122,30 @@
104 version = "v1.1.0" 122 version = "v1.1.0"
105 123
106[[projects]] 124[[projects]]
125 name = "github.com/google/uuid"
126 packages = ["."]
127 revision = "064e2069ce9c359c118179501254f67d7d37ba24"
128 version = "0.2"
129
130[[projects]]
131 name = "github.com/huandu/xstrings"
132 packages = ["."]
133 revision = "2bf18b218c51864a87384c06996e40ff9dcff8e1"
134 version = "v1.0.0"
135
136[[projects]]
137 name = "github.com/imdario/mergo"
138 packages = ["."]
139 revision = "7fe0c75c13abdee74b09fcacef5ea1c6bba6a874"
140 version = "0.2.4"
141
142[[projects]]
143 branch = "master"
144 name = "github.com/jaytaylor/html2text"
145 packages = ["."]
146 revision = "64a82a6d140778896f13303121a49d8cb8007034"
147
148[[projects]]
107 branch = "master" 149 branch = "master"
108 name = "github.com/jinzhu/inflection" 150 name = "github.com/jinzhu/inflection"
109 packages = ["."] 151 packages = ["."]
@@ -122,12 +164,30 @@
122 revision = "3e7b2ea67e9637d153f53ef5ff148f23ee5274d4" 164 revision = "3e7b2ea67e9637d153f53ef5ff148f23ee5274d4"
123 165
124[[projects]] 166[[projects]]
167 name = "github.com/matcornic/hermes"
168 packages = ["."]
169 revision = "23ab47deb5a321481be0c4936b810c1420da2262"
170 version = "1.1.1"
171
172[[projects]]
125 name = "github.com/mattn/go-isatty" 173 name = "github.com/mattn/go-isatty"
126 packages = ["."] 174 packages = ["."]
127 revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" 175 revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
128 version = "v0.0.3" 176 version = "v0.0.3"
129 177
130[[projects]] 178[[projects]]
179 name = "github.com/mattn/go-runewidth"
180 packages = ["."]
181 revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
182 version = "v0.0.2"
183
184[[projects]]
185 branch = "master"
186 name = "github.com/olekukonko/tablewriter"
187 packages = ["."]
188 revision = "d4647c9c7a84d847478d890b816b7d8b62b0b279"
189
190[[projects]]
131 name = "github.com/pquerna/otp" 191 name = "github.com/pquerna/otp"
132 packages = [ 192 packages = [
133 ".", 193 ".",
@@ -144,6 +204,18 @@
144 revision = "2df3e6ddaf6e9531dd02d7b6337f2d310f5e4f22" 204 revision = "2df3e6ddaf6e9531dd02d7b6337f2d310f5e4f22"
145 205
146[[projects]] 206[[projects]]
207 branch = "master"
208 name = "github.com/shurcooL/sanitized_anchor_name"
209 packages = ["."]
210 revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
211
212[[projects]]
213 branch = "master"
214 name = "github.com/ssor/bom"
215 packages = ["."]
216 revision = "6386211fdfcf24c0bfbdaceafd02849ed9a8a509"
217
218[[projects]]
147 name = "github.com/ugorji/go" 219 name = "github.com/ugorji/go"
148 packages = ["codec"] 220 packages = ["codec"]
149 revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab" 221 revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
@@ -155,6 +227,8 @@
155 packages = [ 227 packages = [
156 "bcrypt", 228 "bcrypt",
157 "blowfish", 229 "blowfish",
230 "pbkdf2",
231 "scrypt",
158 "ssh/terminal" 232 "ssh/terminal"
159 ] 233 ]
160 revision = "8b1d31080a7692e075c4681cb2458454a1fe0706" 234 revision = "8b1d31080a7692e075c4681cb2458454a1fe0706"
@@ -162,7 +236,11 @@
162[[projects]] 236[[projects]]
163 branch = "master" 237 branch = "master"
164 name = "golang.org/x/net" 238 name = "golang.org/x/net"
165 packages = ["websocket"] 239 packages = [
240 "html",
241 "html/atom",
242 "websocket"
243 ]
166 revision = "640f4622ab692b87c2f3a94265e6f579fe38263d" 244 revision = "640f4622ab692b87c2f3a94265e6f579fe38263d"
167 245
168[[projects]] 246[[projects]]
@@ -175,12 +253,30 @@
175 revision = "78d5f264b493f125018180c204871ecf58a2dce1" 253 revision = "78d5f264b493f125018180c204871ecf58a2dce1"
176 254
177[[projects]] 255[[projects]]
256 branch = "v3"
257 name = "gopkg.in/alexcesaro/quotedprintable.v3"
258 packages = ["."]
259 revision = "2caba252f4dc53eaf6b553000885530023f54623"
260
261[[projects]]
178 name = "gopkg.in/go-playground/validator.v8" 262 name = "gopkg.in/go-playground/validator.v8"
179 packages = ["."] 263 packages = ["."]
180 revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf" 264 revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf"
181 version = "v8.18.2" 265 version = "v8.18.2"
182 266
183[[projects]] 267[[projects]]
268 name = "gopkg.in/gomail.v2"
269 packages = ["."]
270 revision = "41f3572897373c5538c50a2402db15db079fa4fd"
271 version = "2.0.0"
272
273[[projects]]
274 name = "gopkg.in/russross/blackfriday.v2"
275 packages = ["."]
276 revision = "cadec560ec52d93835bf2f15bd794700d3a2473b"
277 version = "v2.0.0"
278
279[[projects]]
184 name = "gopkg.in/yaml.v2" 280 name = "gopkg.in/yaml.v2"
185 packages = ["."] 281 packages = ["."]
186 revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" 282 revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
@@ -189,6 +285,6 @@
189[solve-meta] 285[solve-meta]
190 analyzer-name = "dep" 286 analyzer-name = "dep"
191 analyzer-version = 1 287 analyzer-version = 1
192 inputs-digest = "5c987f56ef837352173d0a50f12d6c58ac72831b5e90d34ca0a283bee71fb1a2" 288 inputs-digest = "9df92ae3bbf81638f86228e1daacd75bd4f6d0afbfc449742e7f28fdefffdc46"
193 solver-name = "gps-cdcl" 289 solver-name = "gps-cdcl"
194 solver-version = 1 290 solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index 5926748..31e55bd 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -76,3 +76,7 @@
76[[override]] 76[[override]]
77 name = "github.com/dchest/authcookie" 77 name = "github.com/dchest/authcookie"
78 branch = "master" 78 branch = "master"
79
80[[constraint]]
81 name = "github.com/matcornic/hermes"
82 version = "1.1.1"
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}
diff --git a/cmd/app/main.go b/cmd/app/main.go
index 28eb775..e414bf2 100644
--- a/cmd/app/main.go
+++ b/cmd/app/main.go
@@ -22,12 +22,12 @@ type AppConfig struct {
22 22
23type ApiConfig struct { 23type ApiConfig struct {
24 api.Config 24 api.Config
25 Domain string `toml:"domain"`
26} 25}
27 26
28type Config struct { 27type Config struct {
29 App AppConfig 28 App AppConfig
30 Api ApiConfig 29 Api ApiConfig
30 Mail api.MailConfig
31 Db db.DBConfig 31 Db db.DBConfig
32 Redis db.RedisConfig 32 Redis db.RedisConfig
33 33
@@ -61,6 +61,7 @@ func init() {
61 } 61 }
62 62
63 api.SetConfig(C.Api.Config) 63 api.SetConfig(C.Api.Config)
64 api.SetMailConfig(C.Mail)
64 65
65 db.Init(C.Db, C.Redis) 66 db.Init(C.Db, C.Redis)
66 67
@@ -139,6 +140,7 @@ func main() {
139 "/", 140 "/",
140 "/signup", 141 "/signup",
141 "/signin", 142 "/signin",
143 "/confirm",
142 "/reset-password", 144 "/reset-password",
143 "/change-password", 145 "/change-password",
144 "/signout", 146 "/signout",
diff --git a/cmd/web/js/api.js b/cmd/web/js/api.js
index f892a6b..62530ba 100644
--- a/cmd/web/js/api.js
+++ b/cmd/web/js/api.js
@@ -64,6 +64,16 @@ var ApiEndpoints = {
64 return '/changepassword'; 64 return '/changepassword';
65 } 65 }
66 }, 66 },
67 'CONFIRM_EMAIL': {
68 'type': 'POST',
69 'auth': false,
70 'parameters': [
71 {'name': 'token', 'mandatory': true, 'inquery': true},
72 ],
73 'buildUrl': function() {
74 return '/confirmemail';
75 }
76 },
67 'MARKET': { 77 'MARKET': {
68 'type': 'GET', 78 'type': 'GET',
69 'auth': true, 79 'auth': true,
diff --git a/cmd/web/js/main.jsx b/cmd/web/js/main.jsx
index 84b5848..5dc45eb 100644
--- a/cmd/web/js/main.jsx
+++ b/cmd/web/js/main.jsx
@@ -62,6 +62,27 @@ App.page('/change-password', false, function(context) {
62 </div>); 62 </div>);
63}); 63});
64 64
65App.page('/confirm', false, function(context) {
66 var token = qs.parse(context.querystring).token;
67
68 if (token === undefined) {
69 App.go('/');
70 return;
71 }
72
73 Api.Call(
74 'CONFIRM_EMAIL',
75 {'token': token},
76 function(err, status, data) {
77 if (err) {
78 console.error(err, data);
79 }
80
81 App.go('/me');
82 }
83 );
84});
85
65App.page('/signout', true, function(context) { 86App.page('/signout', true, function(context) {
66 cookies.removeItem('jwt'); 87 cookies.removeItem('jwt');
67 88
@@ -84,8 +105,8 @@ App.page('/not_confirmed', true, function(context) {
84 App.mount(<div> 105 App.mount(<div>
85 <div className="row"> 106 <div className="row">
86 <div className="box offset-3 col-6 text-center"> 107 <div className="box offset-3 col-6 text-center">
87 <p>Please be patient, you account is being confirmed...</p> 108 <p>An email has now been sent to your email address.</p>
88 <p><a href="/me"><u>Refresh</u></a></p> 109 <p>Please click the "Confirm your account" button to validate your email.</p>
89 </div> 110 </div>
90 </div> 111 </div>
91 </div>); 112 </div>);
diff --git a/db/user.go b/db/user.go
index 7a0a32b..64ca6a6 100644
--- a/db/user.go
+++ b/db/user.go
@@ -80,3 +80,9 @@ func SetPassword(user *User, password string) error {
80 80
81 return DB.Update(user) 81 return DB.Update(user)
82} 82}
83
84func SetUserStatus(user *User, status UserStatus) error {
85 user.Status = status
86
87 return DB.Update(user)
88}