1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-01-08 04:03:53 +02:00

Merge pull request #83 from buu700/master

Additional -Maker properties on Config for GAE support
This commit is contained in:
Aaron L 2016-05-09 12:24:05 -07:00
commit 3e77b1fd27
16 changed files with 131 additions and 60 deletions

View File

@ -32,7 +32,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 +94,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,12 +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) {
ctx := a.NewContext()
ctx.SessionStorer = clientStoreWrapper{a.SessionStoreMaker(w, r)}
ctx.CookieStorer = clientStoreWrapper{a.CookieStoreMaker(w, r)}
return a.currentUser(ctx, 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) {
@ -175,8 +170,5 @@ func (a *Authboss) UpdatePassword(w http.ResponseWriter, r *http.Request,
return nil
}
ctx := a.NewContext()
ctx.SessionStorer = clientStoreWrapper{a.SessionStoreMaker(w, r)}
ctx.CookieStorer = clientStoreWrapper{a.CookieStoreMaker(w, r)}
return a.Callbacks.FireAfter(EventPasswordReset, ctx)
return a.Callbacks.FireAfter(EventPasswordReset, a.InitContext(w, r))
}

View File

@ -100,8 +100,24 @@ type Config struct {
// Storer is the interface through which Authboss accesses the web apps database.
Storer Storer
// StoreMaker is an alternative to defining Storer directly, which facilitates creating
// a Storer on demand from the current http request. Unless you have an exceedingly unusual
// special requirement, defining Storer 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 datastore API methods. To avoid passing StoreMaker
// an expired request object, where relevant, calls to this function will never be spun off as
// goroutines.
StoreMaker StoreMaker
// OAuth2Storer is a different kind of storer only meant for OAuth2.
OAuth2Storer OAuth2Storer
// OAuth2StoreMaker is an alternative to defining OAuth2Storer directly, which facilitates creating
// a OAuth2Storer on demand from the current http request. Unless you have an exceedingly unusual
// special requirement, defining OAuth2Storer 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 datastore API methods. To avoid passing OAuth2StoreMaker
// an expired request object, where relevant, calls to this function will never be spun off as
// goroutines.
OAuth2StoreMaker OAuth2StoreMaker
// CookieStoreMaker must be defined to provide an interface capapable of storing cookies
// for the given response, and reading them from the request.
CookieStoreMaker CookieStoreMaker
@ -111,9 +127,25 @@ 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. To avoid passing LogWriteMaker
// an expired request object, where relevant, calls to this function will never be spun off as
// goroutines.
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
// MailMaker is an alternative to defining Mailer directly, which facilitates creating
// a Mailer on demand from the current http request. Unless you have an exceedingly unusual
// special requirement, defining Mailer 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 mail API methods. To avoid passing MailMaker
// an expired request object, where relevant, calls to this function will never be spun off as
// goroutines.
MailMaker MailMaker
// ContextProvider provides a context for a given request
ContextProvider func(*http.Request) context.Context
}

View File

@ -57,7 +57,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 +130,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.MailMaker != nil {
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 +154,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 +176,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

@ -31,7 +31,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"
@ -82,6 +83,9 @@ func (s smtpMailer) Send(data Email) error {
return smtp.SendMail(s.Server, s.Auth, data.From, data.To, toSend)
}
// MailMaker is used to create a mailer from an http request.
type MailMaker func(http.ResponseWriter, *http.Request) Mailer
func namedAddress(name, address string) string {
if len(name) == 0 {
return address

View File

@ -33,7 +33,7 @@ func init() {
// 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 +192,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

@ -71,14 +71,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 +173,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 +194,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.MailMaker != nil {
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 +213,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
}
@ -268,7 +268,7 @@ func TestRecover_sendRecoverMail_FailToSend(t *testing.T) {
mailer.SendErr = "failed to send"
r.Mailer = mailer
r.sendRecoverEmail("", "")
r.sendRecoverEmail(r.NewContext(), "", "")
if !strings.Contains(logger.String(), "failed to send") {
t.Error("Expected logged to have msg:", "failed to send")
@ -285,7 +285,7 @@ func TestRecover_sendRecoverEmail(t *testing.T) {
r.RootURL = "bar"
r.Mailer = mailer
r.sendRecoverEmail("a@b.c", "abc=")
r.sendRecoverEmail(r.NewContext(), "a@b.c", "abc=")
if len(mailer.Last.To) != 1 {
t.Error("Expected 1 to email")
}

View File

@ -37,14 +37,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 +124,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

@ -51,14 +51,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 +158,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 +230,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

@ -310,6 +310,12 @@ func (a Attributes) Bind(strct interface{}, ignoreMissing bool) error {
return nil
}
// StoreMaker is used to create a storer from an http request.
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) OAuth2Storer
// Unbind is the opposite of Bind, taking a struct in and producing a list of attributes.
func Unbind(intf interface{}) Attributes {
structValue := reflect.ValueOf(intf)