2015-03-15 17:06:08 +02:00
|
|
|
// Package confirm implements confirmation of user registration via e-mail
|
2015-02-07 14:27:12 +02:00
|
|
|
package confirm
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/md5"
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2015-02-10 10:43:45 +02:00
|
|
|
"net/url"
|
2015-02-27 08:01:53 +02:00
|
|
|
"path"
|
2015-02-07 14:27:12 +02:00
|
|
|
|
|
|
|
"gopkg.in/authboss.v0"
|
2015-03-28 18:08:05 +02:00
|
|
|
"gopkg.in/authboss.v0/internal/response"
|
2015-02-07 14:27:12 +02:00
|
|
|
)
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Storer and FormValue constants
|
2015-02-07 14:27:12 +02:00
|
|
|
const (
|
2015-02-16 23:27:29 +02:00
|
|
|
StoreConfirmToken = "confirm_token"
|
|
|
|
StoreConfirmed = "confirmed"
|
2015-02-07 14:27:12 +02:00
|
|
|
|
|
|
|
FormValueConfirm = "cnf"
|
|
|
|
|
|
|
|
tplConfirmHTML = "confirm_email.html.tpl"
|
2015-02-11 02:29:52 +02:00
|
|
|
tplConfirmText = "confirm_email.txt.tpl"
|
2015-02-07 14:27:12 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2015-02-22 10:09:52 +02:00
|
|
|
errUserMissing = errors.New("confirm: After registration user must be loaded")
|
2015-02-07 14:27:12 +02:00
|
|
|
)
|
|
|
|
|
2015-02-24 21:04:27 +02:00
|
|
|
// ConfirmStorer must be implemented in order to satisfy the confirm module's
|
|
|
|
// storage requirements.
|
|
|
|
type ConfirmStorer interface {
|
|
|
|
authboss.Storer
|
|
|
|
// ConfirmUser looks up a user by a confirm token. See confirm module for
|
|
|
|
// attribute names. If the token is not found in the data store,
|
|
|
|
// simply return nil, ErrUserNotFound.
|
|
|
|
ConfirmUser(confirmToken string) (interface{}, error)
|
|
|
|
}
|
|
|
|
|
2015-02-07 14:27:12 +02:00
|
|
|
func init() {
|
2015-02-27 09:09:37 +02:00
|
|
|
authboss.RegisterModule("confirm", &Confirm{})
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Confirm module
|
2015-02-07 14:27:12 +02:00
|
|
|
type Confirm struct {
|
2015-04-01 00:27:47 +02:00
|
|
|
*authboss.Authboss
|
2015-03-28 18:08:05 +02:00
|
|
|
emailHTMLTemplates response.Templates
|
|
|
|
emailTextTemplates response.Templates
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Initialize the module
|
2015-04-01 00:27:47 +02:00
|
|
|
func (c *Confirm) Initialize(ab *authboss.Authboss) (err error) {
|
|
|
|
c.Authboss = ab
|
|
|
|
|
2015-02-10 10:43:45 +02:00
|
|
|
var ok bool
|
2015-04-01 00:27:47 +02:00
|
|
|
storer, ok := c.Storer.(ConfirmStorer)
|
2015-02-16 06:35:32 +02:00
|
|
|
if storer == nil || !ok {
|
2015-03-16 23:42:45 +02:00
|
|
|
return errors.New("confirm: Need a ConfirmStorer")
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
c.emailHTMLTemplates, err = response.LoadTemplates(ab, c.LayoutHTMLEmail, c.ViewsPath, tplConfirmHTML)
|
2015-03-03 21:23:49 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-01 00:27:47 +02:00
|
|
|
c.emailTextTemplates, err = response.LoadTemplates(ab, c.LayoutTextEmail, c.ViewsPath, tplConfirmText)
|
2015-02-07 14:27:12 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-22 05:53:51 +02:00
|
|
|
c.Callbacks.After(authboss.EventGetUser, func(ctx *authboss.Context) error {
|
|
|
|
_, err := c.beforeGet(ctx)
|
|
|
|
return err
|
|
|
|
})
|
2015-04-01 00:27:47 +02:00
|
|
|
c.Callbacks.Before(authboss.EventAuth, c.beforeGet)
|
|
|
|
c.Callbacks.After(authboss.EventRegister, c.afterRegister)
|
2015-02-07 14:27:12 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Routes for the module
|
2015-02-07 14:27:12 +02:00
|
|
|
func (c *Confirm) Routes() authboss.RouteTable {
|
|
|
|
return authboss.RouteTable{
|
|
|
|
"/confirm": c.confirmHandler,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Storage requirements
|
2015-02-07 14:27:12 +02:00
|
|
|
func (c *Confirm) Storage() authboss.StorageOptions {
|
|
|
|
return authboss.StorageOptions{
|
2015-04-01 00:27:47 +02:00
|
|
|
c.PrimaryID: authboss.String,
|
|
|
|
authboss.StoreEmail: authboss.String,
|
|
|
|
StoreConfirmToken: authboss.String,
|
|
|
|
StoreConfirmed: authboss.Bool,
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
func (c *Confirm) beforeGet(ctx *authboss.Context) (authboss.Interrupt, error) {
|
2015-02-22 10:09:52 +02:00
|
|
|
if confirmed, err := ctx.User.BoolErr(StoreConfirmed); err != nil {
|
|
|
|
return authboss.InterruptNone, err
|
|
|
|
} else if !confirmed {
|
|
|
|
return authboss.InterruptAccountNotConfirmed, nil
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
2015-02-10 10:43:45 +02:00
|
|
|
|
2015-02-22 10:09:52 +02:00
|
|
|
return authboss.InterruptNone, nil
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// AfterRegister ensures the account is not activated.
|
2015-03-16 23:42:45 +02:00
|
|
|
func (c *Confirm) afterRegister(ctx *authboss.Context) error {
|
2015-02-07 14:27:12 +02:00
|
|
|
if ctx.User == nil {
|
2015-02-22 10:09:52 +02:00
|
|
|
return errUserMissing
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
token := make([]byte, 32)
|
|
|
|
if _, err := rand.Read(token); err != nil {
|
2015-02-22 10:09:52 +02:00
|
|
|
return err
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
sum := md5.Sum(token)
|
|
|
|
|
2015-02-16 23:27:29 +02:00
|
|
|
ctx.User[StoreConfirmToken] = base64.StdEncoding.EncodeToString(sum[:])
|
2015-02-07 14:27:12 +02:00
|
|
|
|
2015-02-18 18:57:50 +02:00
|
|
|
if err := ctx.SaveUser(); err != nil {
|
2015-02-22 10:09:52 +02:00
|
|
|
return err
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
2015-02-22 10:09:52 +02:00
|
|
|
email, err := ctx.User.StringErr(authboss.StoreEmail)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
2015-02-22 10:09:52 +02:00
|
|
|
|
2015-02-27 08:01:53 +02:00
|
|
|
goConfirmEmail(c, email, base64.URLEncoding.EncodeToString(token))
|
2015-02-22 10:09:52 +02:00
|
|
|
|
|
|
|
return nil
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
2015-02-10 10:43:45 +02:00
|
|
|
var goConfirmEmail = func(c *Confirm, to, token string) {
|
|
|
|
go c.confirmEmail(to, token)
|
|
|
|
}
|
|
|
|
|
2015-02-07 14:27:12 +02:00
|
|
|
// confirmEmail sends a confirmation e-mail.
|
|
|
|
func (c *Confirm) confirmEmail(to, token string) {
|
2015-04-01 00:27:47 +02:00
|
|
|
p := path.Join(c.MountPath, "confirm")
|
|
|
|
url := fmt.Sprintf("%s%s?%s=%s", c.RootURL, p, url.QueryEscape(FormValueConfirm), url.QueryEscape(token))
|
2015-02-07 14:27:12 +02:00
|
|
|
|
2015-02-22 10:09:52 +02:00
|
|
|
email := authboss.Email{
|
|
|
|
To: []string{to},
|
2015-04-01 00:27:47 +02:00
|
|
|
From: c.EmailFrom,
|
|
|
|
Subject: c.EmailSubjectPrefix + "Confirm New Account",
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
err := response.Email(c.Mailer, email, c.emailHTMLTemplates, tplConfirmHTML, c.emailTextTemplates, tplConfirmText, url)
|
2015-02-22 10:09:52 +02:00
|
|
|
if err != nil {
|
2015-04-01 00:27:47 +02:00
|
|
|
fmt.Fprintf(c.LogWriter, "confirm: Failed to send e-mail: %v", err)
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-22 10:09:52 +02:00
|
|
|
func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
|
2015-08-02 20:55:39 +02:00
|
|
|
token := r.FormValue(FormValueConfirm)
|
|
|
|
if len(token) == 0 {
|
|
|
|
return authboss.ClientDataErr{FormValueConfirm}
|
2015-02-10 10:43:45 +02:00
|
|
|
}
|
2015-02-07 14:27:12 +02:00
|
|
|
|
2015-02-16 23:27:29 +02:00
|
|
|
toHash, err := base64.URLEncoding.DecodeString(token)
|
2015-02-07 14:27:12 +02:00
|
|
|
if err != nil {
|
2015-02-22 10:09:52 +02:00
|
|
|
return authboss.ErrAndRedirect{
|
2015-02-25 00:45:37 +02:00
|
|
|
Location: "/", Err: fmt.Errorf("confirm: token failed to decode %q => %v\n", token, err),
|
2015-02-22 10:09:52 +02:00
|
|
|
}
|
2015-02-10 10:43:45 +02:00
|
|
|
}
|
|
|
|
|
2015-02-16 23:27:29 +02:00
|
|
|
sum := md5.Sum(toHash)
|
|
|
|
|
|
|
|
dbTok := base64.StdEncoding.EncodeToString(sum[:])
|
2015-04-01 00:27:47 +02:00
|
|
|
user, err := c.Storer.(ConfirmStorer).ConfirmUser(dbTok)
|
2015-02-10 10:43:45 +02:00
|
|
|
if err == authboss.ErrUserNotFound {
|
2015-02-25 00:45:37 +02:00
|
|
|
return authboss.ErrAndRedirect{Location: "/", Err: errors.New("confirm: token not found")}
|
2015-02-10 10:43:45 +02:00
|
|
|
} else if err != nil {
|
2015-02-22 10:09:52 +02:00
|
|
|
return err
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|
|
|
|
|
2015-02-10 10:43:45 +02:00
|
|
|
ctx.User = authboss.Unbind(user)
|
2015-02-07 14:27:12 +02:00
|
|
|
|
2015-02-16 23:27:29 +02:00
|
|
|
ctx.User[StoreConfirmToken] = ""
|
|
|
|
ctx.User[StoreConfirmed] = true
|
2015-02-07 14:27:12 +02:00
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
key, err := ctx.User.StringErr(c.PrimaryID)
|
2015-02-22 10:09:52 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-02-07 14:27:12 +02:00
|
|
|
|
2015-02-18 18:57:50 +02:00
|
|
|
if err := ctx.SaveUser(); err != nil {
|
2015-02-22 10:09:52 +02:00
|
|
|
return err
|
2015-02-10 10:43:45 +02:00
|
|
|
}
|
2015-02-07 14:27:12 +02:00
|
|
|
|
2015-02-22 10:09:52 +02:00
|
|
|
ctx.SessionStorer.Put(authboss.SessionKey, key)
|
2015-04-01 00:27:47 +02:00
|
|
|
response.Redirect(ctx, w, r, c.RegisterOKPath, "You have successfully confirmed your account.", "", true)
|
2015-02-22 10:09:52 +02:00
|
|
|
|
|
|
|
return nil
|
2015-02-07 14:27:12 +02:00
|
|
|
}
|