1
0
mirror of https://github.com/volatiletech/authboss.git synced 2024-11-24 08:42:17 +02:00

final App-Engine-related fixes

This commit is contained in:
Ryan Lester 2016-05-07 02:12:20 -04:00
parent 2357e0679d
commit 8ed6deac08
16 changed files with 120 additions and 79 deletions

View File

@ -12,6 +12,8 @@ import (
)
const (
ModuleName = "auth"
methodGET = "GET"
methodPOST = "POST"
@ -19,7 +21,7 @@ const (
)
func init() {
authboss.RegisterModule("auth", &Auth{})
authboss.RegisterModule(ModuleName, &Auth{})
}
// Auth module
@ -32,7 +34,7 @@ type Auth struct {
func (a *Auth) Initialize(ab *authboss.Authboss) (err error) {
a.Authboss = ab
if a.Storer == nil {
if a.Storer == nil && a.StoreMaker == nil {
return errors.New("auth: Need a Storer")
}
@ -94,11 +96,11 @@ func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
if valid, err := validateCredentials(ctx, key, password); err != nil {
errData["error"] = "Internal server error"
fmt.Fprintf(a.LogWriter, "auth: validate credentials failed: %v\n", err)
fmt.Fprintf(ctx.LogWriter, "auth: validate credentials failed: %v\n", err)
return a.templates.Render(ctx, w, r, tplLogin, errData)
} else if !valid {
if err := a.Callbacks.FireAfter(authboss.EventAuthFail, ctx); err != nil {
fmt.Fprintf(a.LogWriter, "EventAuthFail callback error'd out: %v\n", err)
fmt.Fprintf(ctx.LogWriter, "EventAuthFail callback error'd out: %v\n", err)
}
return a.templates.Render(ctx, w, r, tplLogin, errData)
}

View File

@ -64,7 +64,7 @@ func (a *Authboss) Init(modulesToLoad ...string) error {
// CurrentUser retrieves the current user from the session and the database.
func (a *Authboss) CurrentUser(w http.ResponseWriter, r *http.Request) (interface{}, error) {
return a.currentUser(a.initContext(w, r), w, r)
return a.currentUser(a.InitContext(w, r), w, r)
}
func (a *Authboss) currentUser(ctx *Context, w http.ResponseWriter, r *http.Request) (interface{}, error) {
@ -105,27 +105,6 @@ func (a *Authboss) currentUser(ctx *Context, w http.ResponseWriter, r *http.Requ
return user, err
}
func (a *Authboss) initContext(w http.ResponseWriter, r *http.Request) *Context {
ctx := a.NewContext()
if ctx.StoreMaker != nil {
ctx.Storer = a.StoreMaker(w, r)
}
if ctx.OAuth2StoreMaker != nil {
ctx.OAuth2Storer = a.OAuth2StoreMaker(w, r)
}
if ctx.MailMaker != nil {
ctx.Mailer = a.MailMaker(w, r)
}
ctx.SessionStorer = clientStoreWrapper{a.SessionStoreMaker(w, r)}
ctx.CookieStorer = clientStoreWrapper{a.CookieStoreMaker(w, r)}
return ctx
}
// CurrentUserP retrieves the current user but panics if it's not available for
// any reason.
func (a *Authboss) CurrentUserP(w http.ResponseWriter, r *http.Request) interface{} {
@ -191,5 +170,5 @@ func (a *Authboss) UpdatePassword(w http.ResponseWriter, r *http.Request,
return nil
}
return a.Callbacks.FireAfter(EventPasswordReset, a.initContext(w, r))
return a.Callbacks.FireAfter(EventPasswordReset, a.InitContext(w, r))
}

View File

@ -21,6 +21,8 @@ type Config struct {
RootURL string
// BCryptCost is the cost of the bcrypt password hashing function.
BCryptCost int
// If true, authboss won't use any goroutines. Dependencies of authboss may or may not use goroutines.
DisableGoroutines bool
// PrimaryID is the primary identifier of the user. Set to one of:
// authboss.StoreEmail, authboss.StoreUsername (StoreEmail is default)
@ -123,6 +125,12 @@ type Config struct {
// LogWriter is written to when errors occur, as well as on startup to show which modules are loaded
// and which routes they registered. By default writes to io.Discard.
LogWriter io.Writer
// LogWriteMaker is an alternative to defining LogWriter directly, which facilitates creating
// a LogWriter on demand from the current http request. Unless you have an exceedingly unusual
// special requirement, defining LogWriter directly is the preferred pattern; literally the only
// known use case at the time of this property being added is Google App Engine, which requires
// the current context as an argument to its logging API methods.
LogWriteMaker LogWriteMaker
// Mailer is the mailer being used to send e-mails out. Authboss defines two loggers for use
// LogMailer and SMTPMailer, the default is a LogMailer to io.Discard.
Mailer Mailer

View File

@ -17,6 +17,8 @@ import (
// Storer and FormValue constants
const (
ModuleName = "confirm"
StoreConfirmToken = "confirm_token"
StoreConfirmed = "confirmed"
@ -41,7 +43,7 @@ type ConfirmStorer interface {
}
func init() {
authboss.RegisterModule("confirm", &Confirm{})
authboss.RegisterModule(ModuleName, &Confirm{})
}
// Confirm module
@ -57,7 +59,7 @@ func (c *Confirm) Initialize(ab *authboss.Authboss) (err error) {
var ok bool
storer, ok := c.Storer.(ConfirmStorer)
if storer == nil || !ok {
if c.StoreMaker == nil && (storer == nil || !ok) {
return errors.New("confirm: Need a ConfirmStorer")
}
@ -130,17 +132,21 @@ func (c *Confirm) afterRegister(ctx *authboss.Context) error {
return err
}
goConfirmEmail(c, email, base64.URLEncoding.EncodeToString(token))
goConfirmEmail(c, ctx, email, base64.URLEncoding.EncodeToString(token))
return nil
}
var goConfirmEmail = func(c *Confirm, to, token string) {
go c.confirmEmail(to, token)
var goConfirmEmail = func(c *Confirm, ctx *authboss.Context, to, token string) {
if ctx.DisableGoroutines {
c.confirmEmail(ctx, to, token)
} else {
go c.confirmEmail(ctx, to, token)
}
}
// confirmEmail sends a confirmation e-mail.
func (c *Confirm) confirmEmail(to, token string) {
func (c *Confirm) confirmEmail(ctx *authboss.Context, to, token string) {
p := path.Join(c.MountPath, "confirm")
url := fmt.Sprintf("%s%s?%s=%s", c.RootURL, p, url.QueryEscape(FormValueConfirm), url.QueryEscape(token))
@ -150,9 +156,9 @@ func (c *Confirm) confirmEmail(to, token string) {
Subject: c.EmailSubjectPrefix + "Confirm New Account",
}
err := response.Email(c.Mailer, email, c.emailHTMLTemplates, tplConfirmHTML, c.emailTextTemplates, tplConfirmText, url)
err := response.Email(ctx.Mailer, email, c.emailHTMLTemplates, tplConfirmHTML, c.emailTextTemplates, tplConfirmText, url)
if err != nil {
fmt.Fprintf(c.LogWriter, "confirm: Failed to send e-mail: %v", err)
fmt.Fprintf(ctx.LogWriter, "confirm: Failed to send e-mail: %v", err)
}
}
@ -172,7 +178,7 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r
sum := md5.Sum(toHash)
dbTok := base64.StdEncoding.EncodeToString(sum[:])
user, err := c.Storer.(ConfirmStorer).ConfirmUser(dbTok)
user, err := ctx.Storer.(ConfirmStorer).ConfirmUser(dbTok)
if err == authboss.ErrUserNotFound {
return authboss.ErrAndRedirect{Location: "/", Err: errors.New("confirm: token not found")}
} else if err != nil {

View File

@ -112,8 +112,8 @@ func TestConfirm_AfterRegister(t *testing.T) {
sentEmail := false
goConfirmEmail = func(c *Confirm, to, token string) {
c.confirmEmail(to, token)
goConfirmEmail = func(c *Confirm, ctx *authboss.Context, to, token string) {
c.confirmEmail(ctx, to, token)
sentEmail = true
}

View File

@ -2,6 +2,7 @@ package authboss
import (
"errors"
"net/http"
"strings"
)
@ -32,6 +33,31 @@ func (a *Authboss) NewContext() *Context {
}
}
func (a *Authboss) InitContext(w http.ResponseWriter, r *http.Request) *Context {
ctx := a.NewContext()
if ctx.StoreMaker != nil {
ctx.Storer = ctx.StoreMaker(w, r)
}
if ctx.OAuth2StoreMaker != nil {
ctx.OAuth2Storer = ctx.OAuth2StoreMaker(w, r)
}
if ctx.LogWriteMaker != nil {
ctx.LogWriter = ctx.LogWriteMaker(w, r)
}
if ctx.MailMaker != nil {
ctx.Mailer = ctx.MailMaker(w, r)
}
ctx.SessionStorer = clientStoreWrapper{a.SessionStoreMaker(w, r)}
ctx.CookieStorer = clientStoreWrapper{a.CookieStoreMaker(w, r)}
return ctx
}
// LoadUser loads the user Attributes if they haven't already been loaded.
func (c *Context) LoadUser(key string) error {
if c.User != nil {

View File

@ -10,6 +10,8 @@ import (
// Storage key constants
const (
ModuleName = "Lock"
StoreAttemptNumber = "attempt_number"
StoreAttemptTime = "attempt_time"
StoreLocked = "locked"
@ -20,7 +22,7 @@ var (
)
func init() {
authboss.RegisterModule("lock", &Lock{})
authboss.RegisterModule(ModuleName, &Lock{})
}
// Lock module
@ -31,7 +33,7 @@ type Lock struct {
// Initialize the module
func (l *Lock) Initialize(ab *authboss.Authboss) error {
l.Authboss = ab
if l.Storer == nil {
if l.Storer == nil && l.StoreMaker == nil {
return errors.New("lock: Need a Storer")
}

View File

@ -1,13 +1,18 @@
package authboss
import (
"io"
"log"
"net/http"
"os"
)
// DefaultLogger is a basic logger.
type DefaultLogger log.Logger
// LogWriteMaker is used to create a logger from an http request.
type LogWriteMaker func(http.ResponseWriter, *http.Request) io.Writer
// NewDefaultLogger creates a logger to stdout.
func NewDefaultLogger() *DefaultLogger {
return ((*DefaultLogger)(log.New(os.Stdout, "", log.LstdFlags)))

View File

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"io"
"net/http"
"net/smtp"
"strings"
"text/template"

View File

@ -17,6 +17,10 @@ import (
"gopkg.in/authboss.v0/internal/response"
)
const (
ModuleName = "oauth2"
)
var (
errOAuthStateValidation = errors.New("Could not validate oauth2 state param")
)
@ -27,13 +31,13 @@ type OAuth2 struct {
}
func init() {
authboss.RegisterModule("oauth2", &OAuth2{})
authboss.RegisterModule(ModuleName, &OAuth2{})
}
// Initialize module
func (o *OAuth2) Initialize(ab *authboss.Authboss) error {
o.Authboss = ab
if o.OAuth2Storer == nil {
if o.OAuth2Storer == nil && o.OAuth2StoreMaker == nil {
return errors.New("oauth2: need an OAuth2Storer")
}
return nil
@ -192,7 +196,7 @@ func (o *OAuth2) oauthCallback(ctx *authboss.Context, w http.ResponseWriter, r *
user[authboss.StoreOAuth2Refresh] = token.RefreshToken
}
if err = o.OAuth2Storer.PutOAuth(uid, provider, user); err != nil {
if err = ctx.OAuth2Storer.PutOAuth(uid, provider, user); err != nil {
return err
}

View File

@ -28,6 +28,8 @@ const (
)
const (
ModuleName = "recover"
methodGET = "GET"
methodPOST = "POST"
@ -56,7 +58,7 @@ type RecoverStorer interface {
func init() {
m := &Recover{}
authboss.RegisterModule("recover", m)
authboss.RegisterModule(ModuleName, m)
}
// Recover module
@ -71,14 +73,14 @@ type Recover struct {
func (r *Recover) Initialize(ab *authboss.Authboss) (err error) {
r.Authboss = ab
if r.Storer == nil {
if r.Storer != nil {
if _, ok := r.Storer.(RecoverStorer); !ok {
return errors.New("recover: RecoverStorer required for recover functionality")
}
} else if r.StoreMaker == nil {
return errors.New("recover: Need a RecoverStorer")
}
if _, ok := r.Storer.(RecoverStorer); !ok {
return errors.New("recover: RecoverStorer required for recover functionality")
}
if len(r.XSRFName) == 0 {
return errors.New("auth: XSRFName must be set")
}
@ -173,7 +175,7 @@ func (rec *Recover) startHandlerFunc(ctx *authboss.Context, w http.ResponseWrite
return err
}
goRecoverEmail(rec, email, encodedToken)
goRecoverEmail(rec, ctx, email, encodedToken)
ctx.SessionStorer.Put(authboss.FlashSuccessKey, recoverInitiateSuccessFlash)
response.Redirect(ctx, w, r, rec.RecoverOKPath, "", "", true)
@ -194,11 +196,15 @@ func newToken() (encodedToken, encodedChecksum string, err error) {
return base64.URLEncoding.EncodeToString(token), base64.StdEncoding.EncodeToString(sum[:]), nil
}
var goRecoverEmail = func(r *Recover, to, encodedToken string) {
go r.sendRecoverEmail(to, encodedToken)
var goRecoverEmail = func(r *Recover, ctx *authboss.Context, to, encodedToken string) {
if ctx.DisableGoroutines {
r.sendRecoverEmail(ctx, to, encodedToken)
} else {
go r.sendRecoverEmail(ctx, to, encodedToken)
}
}
func (r *Recover) sendRecoverEmail(to, encodedToken string) {
func (r *Recover) sendRecoverEmail(ctx *authboss.Context, to, encodedToken string) {
p := path.Join(r.MountPath, "recover/complete")
query := url.Values{formValueToken: []string{encodedToken}}
url := fmt.Sprintf("%s%s?%s", r.RootURL, p, query.Encode())
@ -209,8 +215,8 @@ func (r *Recover) sendRecoverEmail(to, encodedToken string) {
Subject: r.EmailSubjectPrefix + "Password Reset",
}
if err := response.Email(r.Mailer, email, r.emailHTMLTemplates, tplInitHTMLEmail, r.emailTextTemplates, tplInitTextEmail, url); err != nil {
fmt.Fprintln(r.LogWriter, "recover: failed to send recover email:", err)
if err := response.Email(ctx.Mailer, email, r.emailHTMLTemplates, tplInitHTMLEmail, r.emailTextTemplates, tplInitTextEmail, url); err != nil {
fmt.Fprintln(ctx.LogWriter, "recover: failed to send recover email:", err)
}
}

View File

@ -179,7 +179,7 @@ func TestRecover_startHandlerFunc_POST(t *testing.T) {
storer.Users["john"] = authboss.Attributes{authboss.StoreUsername: "john", authboss.StoreEmail: "a@b.c"}
sentEmail := false
goRecoverEmail = func(_ *Recover, _, _ string) {
goRecoverEmail = func(_ *Recover, _ *authboss.Context, _, _ string) {
sentEmail = true
}

View File

@ -11,6 +11,8 @@ import (
)
const (
ModuleName = "register"
tplRegister = "register.html.tpl"
)
@ -24,7 +26,7 @@ type RegisterStorer interface {
}
func init() {
authboss.RegisterModule("register", &Register{})
authboss.RegisterModule(ModuleName, &Register{})
}
// Register module.
@ -37,14 +39,14 @@ type Register struct {
func (r *Register) Initialize(ab *authboss.Authboss) (err error) {
r.Authboss = ab
if r.Storer == nil {
if r.Storer != nil {
if _, ok := r.Storer.(RegisterStorer); !ok {
return errors.New("register: RegisterStorer required for register functionality")
}
} else if r.StoreMaker == nil {
return errors.New("register: Need a RegisterStorer")
}
if _, ok := r.Storer.(RegisterStorer); !ok {
return errors.New("register: RegisterStorer required for register functionality")
}
if r.templates, err = response.LoadTemplates(r.Authboss, r.Layout, r.ViewsPath, tplRegister); err != nil {
return err
}
@ -124,7 +126,7 @@ func (reg *Register) registerPostHandler(ctx *authboss.Context, w http.ResponseW
attr[authboss.StorePassword] = string(pass)
ctx.User = attr
if err := reg.Storer.(RegisterStorer).Create(key, attr); err == authboss.ErrUserFound {
if err := ctx.Storer.(RegisterStorer).Create(key, attr); err == authboss.ErrUserFound {
data := authboss.HTMLData{
"primaryID": reg.PrimaryID,
"primaryIDValue": key,

View File

@ -14,6 +14,8 @@ import (
)
const (
ModuleName = "remember"
nRandBytes = 32
)
@ -39,7 +41,7 @@ type RememberStorer interface {
}
func init() {
authboss.RegisterModule("remember", &Remember{})
authboss.RegisterModule(ModuleName, &Remember{})
}
// Remember module
@ -51,14 +53,14 @@ type Remember struct {
func (r *Remember) Initialize(ab *authboss.Authboss) error {
r.Authboss = ab
if r.Storer == nil && r.OAuth2Storer == nil {
return errors.New("remember: Need a RememberStorer")
}
if _, ok := r.Storer.(RememberStorer); !ok {
if _, ok := r.OAuth2Storer.(RememberStorer); !ok {
return errors.New("remember: RememberStorer required for remember functionality")
if r.Storer != nil || r.OAuth2Storer != nil {
if _, ok := r.Storer.(RememberStorer); !ok {
if _, ok := r.OAuth2Storer.(RememberStorer); !ok {
return errors.New("remember: RememberStorer required for remember functionality")
}
}
} else if r.StoreMaker == nil && r.OAuth2StoreMaker == nil {
return errors.New("remember: Need a RememberStorer")
}
r.Callbacks.Before(authboss.EventGetUserSession, r.auth)
@ -158,8 +160,8 @@ func (r *Remember) afterPassword(ctx *authboss.Context) error {
ctx.CookieStorer.Del(authboss.CookieRemember)
var storer RememberStorer
if storer, ok = r.Storer.(RememberStorer); !ok {
if storer, ok = r.OAuth2Storer.(RememberStorer); !ok {
if storer, ok = ctx.Storer.(RememberStorer); !ok {
if storer, ok = ctx.OAuth2Storer.(RememberStorer); !ok {
return nil
}
}
@ -230,8 +232,8 @@ func (r *Remember) auth(ctx *authboss.Context) (authboss.Interrupt, error) {
sum := md5.Sum(token)
var storer RememberStorer
if storer, ok = r.Storer.(RememberStorer); !ok {
storer, ok = r.OAuth2Storer.(RememberStorer)
if storer, ok = ctx.Storer.(RememberStorer); !ok {
storer, ok = ctx.OAuth2Storer.(RememberStorer)
}
err = storer.UseToken(givenKey, base64.StdEncoding.EncodeToString(sum[:]))

View File

@ -47,9 +47,7 @@ type contextRoute struct {
func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Instantiate the context
ctx := c.Authboss.NewContext()
ctx.CookieStorer = clientStoreWrapper{c.CookieStoreMaker(w, r)}
ctx.SessionStorer = clientStoreWrapper{c.SessionStoreMaker(w, r)}
ctx := c.Authboss.InitContext(w, r)
// Check to make sure we actually need to visit this route
if redirectIfLoggedIn(ctx, w, r) {

View File

@ -314,7 +314,7 @@ func (a Attributes) Bind(strct interface{}, ignoreMissing bool) error {
type StoreMaker func(http.ResponseWriter, *http.Request) Storer
// OAuth2StoreMaker is used to create an oauth2 storer from an http request.
type OAuth2StoreMaker func(http.ResponseWriter, *http.Request) Storer
type OAuth2StoreMaker func(http.ResponseWriter, *http.Request) OAuth2Storer
// Unbind is the opposite of Bind, taking a struct in and producing a list of attributes.
func Unbind(intf interface{}) Attributes {