1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-02-03 13:21:22 +02:00
authboss/confirm/confirm.go

196 lines
4.8 KiB
Go
Raw Normal View History

// Package confirm implements confirmation of user registration via e-mail
package confirm
import (
"context"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
2015-02-10 00:43:45 -08:00
"net/url"
"path"
"github.com/pkg/errors"
2017-07-30 19:39:33 -07:00
"github.com/volatiletech/authboss"
"github.com/volatiletech/authboss/internal/response"
)
2015-03-16 14:42:45 -07:00
// Storer and FormValue constants
const (
StoreConfirmToken = "confirm_token"
StoreConfirmed = "confirmed"
FormValueConfirm = "cnf"
tplConfirmHTML = "confirm_email.html.tpl"
tplConfirmText = "confirm_email.txt.tpl"
)
var (
errUserMissing = errors.New("after registration user must be loaded")
)
// ConfirmStoreLoader allows lookup of users by different parameters.
type ConfirmStoreLoader interface {
authboss.StoreLoader
2015-02-24 11:04:27 -08:00
// 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.
LoadByConfirmToken(confirmToken string) (ConfirmStorer, error)
}
// ConfirmStorer defines attributes for the confirm module.
type ConfirmStorer interface {
authboss.Storer
PutConfirmed(ctx context.Context, confirmed bool) error
PutConfirmToken(ctx context.Context, token string) error
GetConfirmed(ctx context.Context) (confirmed bool, err error)
GetConfirmToken(ctx context.Context) (token string, err error)
2015-02-24 11:04:27 -08:00
}
func init() {
authboss.RegisterModule("confirm", &Confirm{})
}
2015-03-16 14:42:45 -07:00
// Confirm module
type Confirm struct {
2015-03-31 15:27:47 -07:00
*authboss.Authboss
}
2015-03-16 14:42:45 -07:00
// Initialize the module
2015-03-31 15:27:47 -07:00
func (c *Confirm) Initialize(ab *authboss.Authboss) (err error) {
c.Authboss = ab
c.Events.After(authboss.EventGetUser, func(ctx context.Context) error {
_, err := c.beforeGet(ctx)
return err
})
c.Events.Before(authboss.EventAuth, c.beforeGet)
c.Events.After(authboss.EventRegister, c.afterRegister)
return nil
}
2015-03-16 14:42:45 -07:00
// Routes for the module
func (c *Confirm) Routes() authboss.RouteTable {
return authboss.RouteTable{
"/confirm": c.confirmHandler,
}
}
// Templates returns the list of templates required by this module
func (c *Confirm) Templates() []string {
return []string{tplConfirmHTML, tplConfirmText}
}
func (c *Confirm) beforeGet(ctx context.Context) (authboss.Interrupt, error) {
2015-02-22 00:09:52 -08:00
if confirmed, err := ctx.User.BoolErr(StoreConfirmed); err != nil {
return authboss.InterruptNone, err
} else if !confirmed {
return authboss.InterruptAccountNotConfirmed, nil
}
2015-02-10 00:43:45 -08:00
2015-02-22 00:09:52 -08:00
return authboss.InterruptNone, nil
}
// AfterRegister ensures the account is not activated.
func (c *Confirm) afterRegister(ctx context.Context) error {
if ctx.User == nil {
2015-02-22 00:09:52 -08:00
return errUserMissing
}
token := make([]byte, 32)
if _, err := rand.Read(token); err != nil {
2015-02-22 00:09:52 -08:00
return err
}
sum := md5.Sum(token)
ctx.User[StoreConfirmToken] = base64.StdEncoding.EncodeToString(sum[:])
if err := ctx.SaveUser(); err != nil {
2015-02-22 00:09:52 -08:00
return err
}
2015-02-22 00:09:52 -08:00
email, err := ctx.User.StringErr(authboss.StoreEmail)
if err != nil {
return err
}
2015-02-22 00:09:52 -08:00
2016-05-07 02:12:20 -04:00
goConfirmEmail(c, ctx, email, base64.URLEncoding.EncodeToString(token))
2015-02-22 00:09:52 -08:00
return nil
}
var goConfirmEmail = func(c *Confirm, ctx context.Context, to, token string) {
if ctx.MailMaker != nil {
2016-05-07 02:12:20 -04:00
c.confirmEmail(ctx, to, token)
} else {
go c.confirmEmail(ctx, to, token)
}
2015-02-10 00:43:45 -08:00
}
// confirmEmail sends a confirmation e-mail.
func (c *Confirm) confirmEmail(ctx context.Context, to, token string) {
2015-03-31 15:27:47 -07: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-22 00:09:52 -08:00
email := authboss.Email{
To: []string{to},
2015-03-31 15:27:47 -07:00
From: c.EmailFrom,
Subject: c.EmailSubjectPrefix + "Confirm New Account",
}
2016-05-07 02:12:20 -04:00
err := response.Email(ctx.Mailer, email, c.emailHTMLTemplates, tplConfirmHTML, c.emailTextTemplates, tplConfirmText, url)
2015-02-22 00:09:52 -08:00
if err != nil {
2016-05-07 02:12:20 -04:00
fmt.Fprintf(ctx.LogWriter, "confirm: Failed to send e-mail: %v", err)
}
}
func (c *Confirm) confirmHandler(w http.ResponseWriter, r *http.Request) error {
token := r.FormValue(FormValueConfirm)
if len(token) == 0 {
2016-05-08 12:37:02 -07:00
return authboss.ClientDataErr{Name: FormValueConfirm}
2015-02-10 00:43:45 -08:00
}
toHash, err := base64.URLEncoding.DecodeString(token)
if err != nil {
2015-02-22 00:09:52 -08:00
return authboss.ErrAndRedirect{
Location: "/", Err: errors.Wrapf(err, "token failed to decode %q", token),
2015-02-22 00:09:52 -08:00
}
2015-02-10 00:43:45 -08:00
}
sum := md5.Sum(toHash)
dbTok := base64.StdEncoding.EncodeToString(sum[:])
2016-05-07 02:12:20 -04:00
user, err := ctx.Storer.(ConfirmStorer).ConfirmUser(dbTok)
2015-02-10 00:43:45 -08:00
if err == authboss.ErrUserNotFound {
return authboss.ErrAndRedirect{Location: "/", Err: errors.New("token not found")}
2015-02-10 00:43:45 -08:00
} else if err != nil {
2015-02-22 00:09:52 -08:00
return err
}
2015-02-10 00:43:45 -08:00
ctx.User = authboss.Unbind(user)
ctx.User[StoreConfirmToken] = ""
ctx.User[StoreConfirmed] = true
2015-03-31 15:27:47 -07:00
key, err := ctx.User.StringErr(c.PrimaryID)
2015-02-22 00:09:52 -08:00
if err != nil {
return err
}
if err := ctx.SaveUser(); err != nil {
2015-02-22 00:09:52 -08:00
return err
2015-02-10 00:43:45 -08:00
}
2015-02-22 00:09:52 -08:00
ctx.SessionStorer.Put(authboss.SessionKey, key)
2015-03-31 15:27:47 -07:00
response.Redirect(ctx, w, r, c.RegisterOKPath, "You have successfully confirmed your account.", "", true)
2015-02-22 00:09:52 -08:00
return nil
}