revision = "b26d9c308763d68093482582cea63d69be07a0f0"
version = "v0.3.0"
+[[projects]]
+ name = "github.com/Masterminds/semver"
+ packages = ["."]
+ revision = "c7af12943936e8c39859482e61f0574c2fd7fc75"
+ version = "v1.4.2"
+
+[[projects]]
+ name = "github.com/Masterminds/sprig"
+ packages = ["."]
+ revision = "6b2a58267f6a8b1dc8e2eb5519b984008fa85e8c"
+ version = "v2.15.0"
+
[[projects]]
name = "github.com/Sirupsen/logrus"
packages = ["."]
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
+[[projects]]
+ name = "github.com/aokoli/goutils"
+ packages = ["."]
+ revision = "3391d3790d23d03408670993e957e8f408993c34"
+ version = "v1.0.1"
+
[[projects]]
name = "github.com/boombuler/barcode"
packages = [
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
+[[projects]]
+ name = "github.com/google/uuid"
+ packages = ["."]
+ revision = "064e2069ce9c359c118179501254f67d7d37ba24"
+ version = "0.2"
+
+[[projects]]
+ name = "github.com/huandu/xstrings"
+ packages = ["."]
+ revision = "2bf18b218c51864a87384c06996e40ff9dcff8e1"
+ version = "v1.0.0"
+
+[[projects]]
+ name = "github.com/imdario/mergo"
+ packages = ["."]
+ revision = "7fe0c75c13abdee74b09fcacef5ea1c6bba6a874"
+ version = "0.2.4"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/jaytaylor/html2text"
+ packages = ["."]
+ revision = "64a82a6d140778896f13303121a49d8cb8007034"
+
[[projects]]
branch = "master"
name = "github.com/jinzhu/inflection"
packages = ["."]
revision = "3e7b2ea67e9637d153f53ef5ff148f23ee5274d4"
+[[projects]]
+ name = "github.com/matcornic/hermes"
+ packages = ["."]
+ revision = "23ab47deb5a321481be0c4936b810c1420da2262"
+ version = "1.1.1"
+
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
+[[projects]]
+ name = "github.com/mattn/go-runewidth"
+ packages = ["."]
+ revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
+ version = "v0.0.2"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/olekukonko/tablewriter"
+ packages = ["."]
+ revision = "d4647c9c7a84d847478d890b816b7d8b62b0b279"
+
[[projects]]
name = "github.com/pquerna/otp"
packages = [
packages = ["."]
revision = "2df3e6ddaf6e9531dd02d7b6337f2d310f5e4f22"
+[[projects]]
+ branch = "master"
+ name = "github.com/shurcooL/sanitized_anchor_name"
+ packages = ["."]
+ revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/ssor/bom"
+ packages = ["."]
+ revision = "6386211fdfcf24c0bfbdaceafd02849ed9a8a509"
+
[[projects]]
name = "github.com/ugorji/go"
packages = ["codec"]
packages = [
"bcrypt",
"blowfish",
+ "pbkdf2",
+ "scrypt",
"ssh/terminal"
]
revision = "8b1d31080a7692e075c4681cb2458454a1fe0706"
[[projects]]
branch = "master"
name = "golang.org/x/net"
- packages = ["websocket"]
+ packages = [
+ "html",
+ "html/atom",
+ "websocket"
+ ]
revision = "640f4622ab692b87c2f3a94265e6f579fe38263d"
[[projects]]
]
revision = "78d5f264b493f125018180c204871ecf58a2dce1"
+[[projects]]
+ branch = "v3"
+ name = "gopkg.in/alexcesaro/quotedprintable.v3"
+ packages = ["."]
+ revision = "2caba252f4dc53eaf6b553000885530023f54623"
+
[[projects]]
name = "gopkg.in/go-playground/validator.v8"
packages = ["."]
revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf"
version = "v8.18.2"
+[[projects]]
+ name = "gopkg.in/gomail.v2"
+ packages = ["."]
+ revision = "41f3572897373c5538c50a2402db15db079fa4fd"
+ version = "2.0.0"
+
+[[projects]]
+ name = "gopkg.in/russross/blackfriday.v2"
+ packages = ["."]
+ revision = "cadec560ec52d93835bf2f15bd794700d3a2473b"
+ version = "v2.0.0"
+
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "5c987f56ef837352173d0a50f12d6c58ac72831b5e90d34ca0a283bee71fb1a2"
+ inputs-digest = "9df92ae3bbf81638f86228e1daacd75bd4f6d0afbfc449742e7f28fdefffdc46"
solver-name = "gps-cdcl"
solver-version = 1
[[override]]
name = "github.com/dchest/authcookie"
branch = "master"
+
+[[constraint]]
+ name = "github.com/matcornic/hermes"
+ version = "1.1.1"
)
var CONFIG Config
+var MAIL_CONFIG MailConfig
+
+type MailConfig struct {
+ IsEnabled bool
+ SmtpAddress string `toml:"smtp_address"`
+ AddressFrom string `toml:"address_from"`
+ Login string `toml:"login"`
+ Password string `toml:"password"`
+}
type Config struct {
+ Domain string `toml:"domain"`
JwtSecret string `toml:"jwt_secret"`
PasswordResetSecret string `toml:"password_reset_secret"`
FreeSMSUser string `toml:"free_sms_user"`
PASSWORD_RESET_SECRET = []byte(config.PasswordResetSecret)
}
+func SetMailConfig(config MailConfig) {
+ MAIL_CONFIG = config
+
+ if config.Login != "" && config.AddressFrom != "" && config.Password != "" && config.SmtpAddress != "" {
+ MAIL_CONFIG.IsEnabled = true
+ ConfigureMailTemplateEngine()
+ }
+}
+
type Error struct {
Code ErrorCode
UserMessage string
--- /dev/null
+package api
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/matcornic/hermes"
+ "gopkg.in/gomail.v2"
+)
+
+var MailTemplateEngine hermes.Hermes
+
+func ConfigureMailTemplateEngine() {
+ var link string
+ if strings.Contains(CONFIG.Domain, "localhost") {
+ link = fmt.Sprintf("http://%s", CONFIG.Domain)
+ } else {
+ link = fmt.Sprintf("https://%s", CONFIG.Domain)
+ }
+
+ MailTemplateEngine = hermes.Hermes{
+ Product: hermes.Product{
+ Name: "CryptoPF",
+ Link: link,
+ Copyright: "Copyright © 2017 CryptoPF. All rights reserved.",
+ },
+ }
+}
+
+func SendResetPasswordMail(to, token string) error {
+ mail := hermes.Email{
+ Body: hermes.Body{
+ Name: to,
+ Intros: []string{
+ "You have received this email because a password reset request for your CryptoPF account was received.",
+ },
+ Actions: []hermes.Action{
+ {
+ Instructions: "Click the button below to reset your password:",
+ Button: hermes.Button{
+ Color: "#DC4D2F",
+ Text: "Reset your password",
+ Link: fmt.Sprintf("%s/change-password?token=%s", MailTemplateEngine.Product.Link, token),
+ },
+ },
+ },
+ Outros: []string{
+ "If you did not request a password reset, no further action is required on your part.",
+ },
+ Signature: "Thanks",
+ },
+ }
+
+ body, err := MailTemplateEngine.GenerateHTML(mail)
+ if err != nil {
+ return err
+ }
+
+ return SendEmail(to, "Password reset", body)
+}
+
+func SendConfirmationMail(to, token string) error {
+ mail := hermes.Email{
+ Body: hermes.Body{
+ Name: to,
+ Intros: []string{
+ "Welcome to CryptoPF! We're very excited to have you on board.",
+ },
+ Actions: []hermes.Action{
+ {
+ Instructions: "To get started with CryptoPF, please click here:",
+ Button: hermes.Button{
+ Text: "Confirm your account",
+ Link: fmt.Sprintf("%s/confirm?token=%s", MailTemplateEngine.Product.Link, token),
+ },
+ },
+ },
+ Outros: []string{
+ "Need help, or have questions? Just reply to this email, we'd love to help.",
+ },
+ Signature: "Thanks",
+ },
+ }
+
+ body, err := MailTemplateEngine.GenerateHTML(mail)
+ if err != nil {
+ return err
+ }
+
+ return SendEmail(to, "Confirm your email", body)
+}
+
+func SendEmail(to, subject, body string) error {
+ m := gomail.NewMessage()
+ m.SetAddressHeader("From", MAIL_CONFIG.AddressFrom, "CryptoPF")
+ m.SetAddressHeader("To", to, to)
+ m.SetHeader("Subject", subject)
+ m.SetBody("text/html", body)
+
+ d := gomail.NewPlainDialer(MAIL_CONFIG.SmtpAddress, 587, MAIL_CONFIG.Login, MAIL_CONFIG.Password)
+
+ return d.DialAndSend(m)
+}
}
}
+ if MAIL_CONFIG.IsEnabled {
+ err = SendResetPasswordMail(q.In.Email, token)
+ if err != nil {
+ return nil, NewInternalError(err)
+ }
+ }
+
return nil, nil
}
{"POST", []gin.HandlerFunc{Signin}, "/signin"},
{"POST", []gin.HandlerFunc{PasswordReset}, "/passwordreset"},
{"POST", []gin.HandlerFunc{ChangePassword}, "/changepassword"},
+ {"POST", []gin.HandlerFunc{ConfirmEmail}, "/confirmemail"},
},
},
{
RunQuery(query, c)
}
+
+func ConfirmEmail(c *gin.Context) {
+ query := &ConfirmEmailQuery{}
+
+ query.In.Token = c.PostForm("token")
+
+ RunQuery(query, c)
+}
import (
"fmt"
"regexp"
+ "strconv"
+ "time"
+ "github.com/dchest/passwordreset"
"github.com/gin-gonic/gin"
"immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db"
return nil, NewInternalError(err)
}
+ if MAIL_CONFIG.IsEnabled {
+ mailConfirmationToken := passwordreset.NewToken(q.In.Email, time.Hour*24*1, []byte(strconv.FormatUint(uint64(newUser.Status), 10)), PASSWORD_RESET_SECRET)
+ err = SendConfirmationMail(q.In.Email, mailConfirmationToken)
+ if err != nil {
+ return nil, NewInternalError(err)
+ }
+ }
+
+ if CONFIG.FreeSMSUser != "" {
+ err := SendSMS(CONFIG.FreeSMSUser, CONFIG.FreeSMSPass, fmt.Sprintf("'%v' request a password reset. Token '/change-password?token=%v'", q.In.Email, token))
+ if err != nil {
+ return nil, NewInternalError(err)
+ }
+ }
+
return SignResult{token}, nil
}
return user.(db.User)
}
+
+type ConfirmEmailQuery struct {
+ In struct {
+ Token string
+ }
+}
+
+func (q ConfirmEmailQuery) ValidateParams() *Error {
+
+ if q.In.Token == "" {
+ return &Error{BadRequest, "invalid token", fmt.Errorf("invalid token")}
+ }
+
+ return nil
+}
+
+func (q ConfirmEmailQuery) Run() (interface{}, *Error) {
+ var user *db.User
+
+ email, err := passwordreset.VerifyToken(q.In.Token, func(email string) ([]byte, error) {
+ var err error
+ user, err = db.GetUserByEmail(email)
+ if err != nil {
+ return nil, err
+ }
+
+ if user == nil {
+ return nil, fmt.Errorf("'%v' is not registered", email)
+ }
+
+ return []byte(strconv.FormatUint(uint64(user.Status), 10)), nil
+
+ }, PASSWORD_RESET_SECRET)
+
+ if err != nil && (err == passwordreset.ErrExpiredToken) {
+ return nil, &Error{BadRequest, "expired token", fmt.Errorf("expired token")}
+ } else if err != nil && (err == passwordreset.ErrMalformedToken || err == passwordreset.ErrWrongSignature) {
+ return nil, &Error{BadRequest, "wrong token", fmt.Errorf("wrong token")}
+ } else if err != nil {
+ return nil, NewInternalError(err)
+ }
+
+ if user == nil {
+ return nil, &Error{BadRequest, "bad request", fmt.Errorf("no user found for email '%v'", email)}
+ }
+
+ err = db.SetUserStatus(user, db.Confirmed)
+ if err != nil {
+ return nil, NewInternalError(err)
+ }
+
+ return nil, nil
+}
type ApiConfig struct {
api.Config
- Domain string `toml:"domain"`
}
type Config struct {
App AppConfig
Api ApiConfig
+ Mail api.MailConfig
Db db.DBConfig
Redis db.RedisConfig
}
api.SetConfig(C.Api.Config)
+ api.SetMailConfig(C.Mail)
db.Init(C.Db, C.Redis)
"/",
"/signup",
"/signin",
+ "/confirm",
"/reset-password",
"/change-password",
"/signout",
return '/changepassword';
}
},
+ 'CONFIRM_EMAIL': {
+ 'type': 'POST',
+ 'auth': false,
+ 'parameters': [
+ {'name': 'token', 'mandatory': true, 'inquery': true},
+ ],
+ 'buildUrl': function() {
+ return '/confirmemail';
+ }
+ },
'MARKET': {
'type': 'GET',
'auth': true,
</div>);
});
+App.page('/confirm', false, function(context) {
+ var token = qs.parse(context.querystring).token;
+
+ if (token === undefined) {
+ App.go('/');
+ return;
+ }
+
+ Api.Call(
+ 'CONFIRM_EMAIL',
+ {'token': token},
+ function(err, status, data) {
+ if (err) {
+ console.error(err, data);
+ }
+
+ App.go('/me');
+ }
+ );
+});
+
App.page('/signout', true, function(context) {
cookies.removeItem('jwt');
App.mount(<div>
<div className="row">
<div className="box offset-3 col-6 text-center">
- <p>Please be patient, you account is being confirmed...</p>
- <p><a href="/me"><u>Refresh</u></a></p>
+ <p>An email has now been sent to your email address.</p>
+ <p>Please click the "Confirm your account" button to validate your email.</p>
</div>
</div>
</div>);
return DB.Update(user)
}
+
+func SetUserStatus(user *User, status UserStatus) error {
+ user.Status = status
+
+ return DB.Update(user)
+}