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:
commit
3e77b1fd27
@ -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)
|
||||
}
|
||||
|
12
authboss.go
12
authboss.go
@ -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))
|
||||
}
|
||||
|
32
config.go
32
config.go
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
26
context.go
26
context.go
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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[:]))
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user