mirror of
https://github.com/volatiletech/authboss.git
synced 2024-11-28 08:58:38 +02:00
More changes
This commit is contained in:
parent
f7db80e4e2
commit
02e91bc0ad
18
authboss.go
18
authboss.go
@ -22,18 +22,16 @@ type Authboss struct {
|
||||
Config
|
||||
Callbacks *Callbacks
|
||||
|
||||
loadedModules map[string]Modularizer
|
||||
ModuleAttributes AttributeMeta
|
||||
mux *http.ServeMux
|
||||
loadedModules map[string]Modularizer
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
// New makes a new instance of authboss with a default
|
||||
// configuration.
|
||||
func New() *Authboss {
|
||||
ab := &Authboss{
|
||||
Callbacks: NewCallbacks(),
|
||||
loadedModules: make(map[string]Modularizer),
|
||||
ModuleAttributes: make(AttributeMeta),
|
||||
Callbacks: NewCallbacks(),
|
||||
loadedModules: make(map[string]Modularizer),
|
||||
}
|
||||
ab.Config.Defaults()
|
||||
return ab
|
||||
@ -53,12 +51,6 @@ func (a *Authboss) Init(modulesToLoad ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, mod := range a.loadedModules {
|
||||
for k, v := range mod.Storage() {
|
||||
a.ModuleAttributes[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -67,7 +59,7 @@ func (a *Authboss) CurrentUser(w http.ResponseWriter, r *http.Request) (interfac
|
||||
return a.currentUser(a.InitContext(w, r), w, r)
|
||||
}
|
||||
|
||||
func (a *Authboss) currentUser(ctx *Context, w http.ResponseWriter, r *http.Request) (interface{}, error) {
|
||||
func (a *Authboss) currentUser(w http.ResponseWriter, r *http.Request) (interface{}, error) {
|
||||
_, err := a.Callbacks.FireBefore(EventGetUserSession, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1,6 +1,7 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@ -48,10 +49,10 @@ const (
|
||||
// the callback chain and the original handler from continuing execution.
|
||||
// The execution should also stopped if there is an error (and therefore if error is set
|
||||
// the bool is automatically considered set).
|
||||
type Before func(*Context) (Interrupt, error)
|
||||
type Before func(context.Context) (Interrupt, error)
|
||||
|
||||
// After is a request callback that happens after the event.
|
||||
type After func(*Context) error
|
||||
type After func(context.Context) error
|
||||
|
||||
// Callbacks is a collection of callbacks that fire before and after certain
|
||||
// methods.
|
||||
@ -89,7 +90,7 @@ func (c *Callbacks) After(e Event, f After) {
|
||||
// check the value of interrupted. If error is nil then the interrupt
|
||||
// value should be checked. If it is not InterruptNone then there is a reason
|
||||
// the current process should stop it's course of action.
|
||||
func (c *Callbacks) FireBefore(e Event, ctx *Context) (interrupt Interrupt, err error) {
|
||||
func (c *Callbacks) FireBefore(e Event, ctx context.Context) (interrupt Interrupt, err error) {
|
||||
callbacks := c.before[e]
|
||||
for _, fn := range callbacks {
|
||||
interrupt, err = fn(ctx)
|
||||
@ -107,7 +108,7 @@ func (c *Callbacks) FireBefore(e Event, ctx *Context) (interrupt Interrupt, err
|
||||
|
||||
// FireAfter event to all the callbacks with a context. The error can safely be
|
||||
// ignored as it is logged.
|
||||
func (c *Callbacks) FireAfter(e Event, ctx *Context) (err error) {
|
||||
func (c *Callbacks) FireAfter(e Event, ctx context.Context) (err error) {
|
||||
callbacks := c.after[e]
|
||||
for _, fn := range callbacks {
|
||||
if err = fn(ctx); err != nil {
|
||||
|
@ -53,15 +53,12 @@ func (c clientStoreWrapper) GetErr(key string) (string, error) {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// CookieStoreMaker is used to create a cookie storer from an http request. Keep in mind
|
||||
// ClientStoreMaker is used to create a cookie storer from an http request. Keep in mind
|
||||
// security considerations for your implementation, Secure, HTTP-Only, etc flags.
|
||||
type CookieStoreMaker func(http.ResponseWriter, *http.Request) ClientStorer
|
||||
|
||||
// SessionStoreMaker is used to create a session storer from an http request.
|
||||
// It must be implemented to satisfy certain modules (auth, remember primarily).
|
||||
// It should be a secure storage of the session. This means if it represents a cookie-based session
|
||||
// storage these cookies should be signed in order to prevent tampering, or they should be encrypted.
|
||||
type SessionStoreMaker func(http.ResponseWriter, *http.Request) ClientStorer
|
||||
//
|
||||
// There's two major uses for this. To create session based client storers
|
||||
// and session storers.
|
||||
type ClientStoreMaker func(http.ResponseWriter, *http.Request) ClientStorer
|
||||
|
||||
// FlashSuccess returns FlashSuccessKey from the session and removes it.
|
||||
func (a *Authboss) FlashSuccess(w http.ResponseWriter, r *http.Request) string {
|
||||
|
15
config.go
15
config.go
@ -1,6 +1,7 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -8,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Config holds all the configuration for both authboss and it's modules.
|
||||
@ -26,14 +26,7 @@ type Config struct {
|
||||
// authboss.StoreEmail, authboss.StoreUsername (StoreEmail is default)
|
||||
PrimaryID string
|
||||
|
||||
// Layout that all authboss views will be inserted into.
|
||||
Layout *template.Template
|
||||
// LayoutHTMLEmail is for emails going out in HTML form, authbosses e-mail templates
|
||||
// will be inserted into this layout.
|
||||
LayoutHTMLEmail *template.Template
|
||||
// LayoutTextEmail is for emails going out in text form, authbosses e-mail templates
|
||||
// will be inserted into this layout.
|
||||
LayoutTextEmail *template.Template
|
||||
ViewLoader RenderLoader
|
||||
// LayoutDataMaker is a function that can provide authboss with the layout's
|
||||
// template data. It will be merged with the data being provided for the current
|
||||
// view in order to render the templates.
|
||||
@ -103,10 +96,10 @@ type Config struct {
|
||||
|
||||
// CookieStoreMaker must be defined to provide an interface capapable of storing cookies
|
||||
// for the given response, and reading them from the request.
|
||||
CookieStoreMaker CookieStoreMaker
|
||||
CookieStoreMaker ClientStoreMaker
|
||||
// SessionStoreMaker must be defined to provide an interface capable of storing session-only
|
||||
// values for the given response, and reading them from the request.
|
||||
SessionStoreMaker SessionStoreMaker
|
||||
SessionStoreMaker ClientStoreMaker
|
||||
// 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
|
||||
|
110
context.go
110
context.go
@ -1,110 +0,0 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FormValue constants
|
||||
var (
|
||||
FormValueRedirect = "redir"
|
||||
FormValueOAuth2State = "state"
|
||||
)
|
||||
|
||||
// Context provides context for module operations and callbacks. One obvious
|
||||
// need for context is a request's session store. It is not safe for use by
|
||||
// multiple goroutines.
|
||||
type Context struct {
|
||||
*Authboss
|
||||
|
||||
SessionStorer ClientStorerErr
|
||||
CookieStorer ClientStorerErr
|
||||
User Attributes
|
||||
|
||||
// Values is a free-form key-value store to pass data to callbacks
|
||||
Values map[string]string
|
||||
}
|
||||
|
||||
// NewContext is exported for testing modules.
|
||||
func (a *Authboss) NewContext() *Context {
|
||||
return &Context{
|
||||
Authboss: a,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var user interface{}
|
||||
var err error
|
||||
|
||||
if index := strings.IndexByte(key, ';'); index > 0 {
|
||||
user, err = c.OAuth2Storer.GetOAuth(key[:index], key[index+1:])
|
||||
} else {
|
||||
user, err = c.Storer.Get(key)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.User = Unbind(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadSessionUser loads the user from the session if the user has not already been
|
||||
// loaded.
|
||||
func (c *Context) LoadSessionUser() error {
|
||||
if c.User != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
key, ok := c.SessionStorer.Get(SessionKey)
|
||||
if !ok {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
return c.LoadUser(key)
|
||||
}
|
||||
|
||||
// SaveUser saves the user Attributes.
|
||||
func (c *Context) SaveUser() error {
|
||||
if c.User == nil {
|
||||
return errors.New("User not initialized.")
|
||||
}
|
||||
|
||||
key, ok := c.User.String(c.PrimaryID)
|
||||
if !ok {
|
||||
return errors.New("User improperly initialized, primary ID missing")
|
||||
}
|
||||
|
||||
return c.Storer.Put(key, c.User)
|
||||
}
|
109
context_test.go
109
context_test.go
@ -1,109 +0,0 @@
|
||||
package authboss
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestContext_SaveUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ab := New()
|
||||
ctx := ab.NewContext()
|
||||
storer := mockStorer{}
|
||||
ab.Storer = storer
|
||||
ctx.User = Attributes{StoreUsername: "joe", StoreEmail: "hello@joe.com", StorePassword: "mysticalhash"}
|
||||
|
||||
err := ctx.SaveUser()
|
||||
if err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
|
||||
attr, ok := storer["hello@joe.com"]
|
||||
if !ok {
|
||||
t.Error("Could not find joe!")
|
||||
}
|
||||
|
||||
for k, v := range ctx.User {
|
||||
if v != attr[k] {
|
||||
t.Error(v, "not equal to", ctx.User[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_LoadUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ab := New()
|
||||
ctx := ab.NewContext()
|
||||
|
||||
attr := Attributes{
|
||||
"email": "hello@joe.com",
|
||||
"password": "mysticalhash",
|
||||
"uid": "what",
|
||||
"provider": "google",
|
||||
}
|
||||
|
||||
storer := mockStorer{
|
||||
"joe": attr,
|
||||
"whatgoogle": attr,
|
||||
}
|
||||
ab.Storer = storer
|
||||
ab.OAuth2Storer = storer
|
||||
|
||||
ctx.User = nil
|
||||
if err := ctx.LoadUser("joe"); err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
|
||||
if email, err := ctx.User.StringErr("email"); err != nil {
|
||||
t.Error(err)
|
||||
} else if email != attr["email"] {
|
||||
t.Error("Email wrong:", email)
|
||||
}
|
||||
if password, err := ctx.User.StringErr("password"); err != nil {
|
||||
t.Error(err)
|
||||
} else if password != attr["password"] {
|
||||
t.Error("Password wrong:", password)
|
||||
}
|
||||
|
||||
ctx.User = nil
|
||||
if err := ctx.LoadUser("what;google"); err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
|
||||
if email, err := ctx.User.StringErr("email"); err != nil {
|
||||
t.Error(err)
|
||||
} else if email != attr["email"] {
|
||||
t.Error("Email wrong:", email)
|
||||
}
|
||||
if password, err := ctx.User.StringErr("password"); err != nil {
|
||||
t.Error(err)
|
||||
} else if password != attr["password"] {
|
||||
t.Error("Password wrong:", password)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_LoadSessionUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ab := New()
|
||||
ctx := ab.NewContext()
|
||||
storer := mockStorer{
|
||||
"joe": Attributes{"email": "hello@joe.com", "password": "mysticalhash"},
|
||||
}
|
||||
ab.Storer = storer
|
||||
ctx.SessionStorer = mockClientStore{
|
||||
SessionKey: "joe",
|
||||
}
|
||||
|
||||
err := ctx.LoadSessionUser()
|
||||
if err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
|
||||
attr := storer["joe"]
|
||||
|
||||
for k, v := range attr {
|
||||
if v != ctx.User[k] {
|
||||
t.Error(v, "not equal to", ctx.User[k])
|
||||
}
|
||||
}
|
||||
}
|
77
mailer.go
77
mailer.go
@ -2,22 +2,24 @@ package authboss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"math/rand"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SendMail uses the currently configured mailer to deliver e-mails.
|
||||
func (a *Authboss) SendMail(data Email) error {
|
||||
return a.Mailer.Send(data)
|
||||
func (a *Authboss) SendMail(ctx context.Context, data Email) error {
|
||||
return a.Mailer.Send(ctx, data)
|
||||
}
|
||||
|
||||
// Mailer is a type that is capable of sending an e-mail.
|
||||
type Mailer interface {
|
||||
Send(Email) error
|
||||
Send(context.Context, Email) error
|
||||
}
|
||||
|
||||
// LogMailer creates a mailer that doesn't deliver e-mails but
|
||||
@ -31,7 +33,8 @@ func SMTPMailer(server string, auth smtp.Auth) Mailer {
|
||||
if len(server) == 0 {
|
||||
panic("SMTP Mailer must be created with a server string.")
|
||||
}
|
||||
return smtpMailer{server, auth}
|
||||
random := rand.New(rand.NewSource(time.Now()))
|
||||
return smtpMailer{server, auth, random}
|
||||
}
|
||||
|
||||
// Email all the things. The ToNames and friends are parallel arrays and must
|
||||
@ -53,8 +56,17 @@ type logMailer struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (l logMailer) Send(data Email) error {
|
||||
func (l logMailer) Send(mail Email) error {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
data := struct {
|
||||
Boundary string
|
||||
Mail Email
|
||||
}{
|
||||
Boundary: "284fad24nao8f4na284f2n4",
|
||||
Mail: mail,
|
||||
}
|
||||
|
||||
err := emailTmpl.Execute(buf, data)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -69,10 +81,20 @@ func (l logMailer) Send(data Email) error {
|
||||
type smtpMailer struct {
|
||||
Server string
|
||||
Auth smtp.Auth
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
func (s smtpMailer) Send(data Email) error {
|
||||
func (s smtpMailer) Send(mail Email) error {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
data := struct {
|
||||
Boundary string
|
||||
Mail Email
|
||||
}{
|
||||
Boundary: boundary(),
|
||||
Mail: mail,
|
||||
}
|
||||
|
||||
err := emailTmpl.Execute(buf, data)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -83,8 +105,21 @@ 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
|
||||
// boundary makes mime boundaries, these are largely useless strings that just
|
||||
// need to be the same in the mime structure. We choose from the alphabet below
|
||||
// and create a random string of length 23
|
||||
// Example:
|
||||
// 284fad24nao8f4na284f2n4
|
||||
func (s smtpMailer) boundary() string {
|
||||
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for i := 0; i < 23; i++ {
|
||||
buf.WriteByte(alphabet[s.rand.Int()%len(alphabet)])
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func namedAddress(name, address string) string {
|
||||
if len(name) == 0 {
|
||||
@ -119,25 +154,25 @@ var emailTmpl = template.Must(template.New("email").Funcs(template.FuncMap{
|
||||
"join": strings.Join,
|
||||
"namedAddress": namedAddress,
|
||||
"namedAddresses": namedAddresses,
|
||||
}).Parse(`To: {{namedAddresses .ToNames .To}}{{if .Cc}}
|
||||
Cc: {{namedAddresses .CcNames .Cc}}{{end}}{{if .Bcc}}
|
||||
Bcc: {{namedAddresses .BccNames .Bcc}}{{end}}
|
||||
From: {{namedAddress .FromName .From}}
|
||||
Subject: {{.Subject}}{{if .ReplyTo}}
|
||||
Reply-To: {{namedAddress .ReplyToName .ReplyTo}}{{end}}
|
||||
}).Parse(`To: {{namedAddresses .Mail.ToNames .Mail.To}}{{if .Mail.Cc}}
|
||||
Cc: {{namedAddresses .Mail.CcNames .Mail.Cc}}{{end}}{{if .Mail.Bcc}}
|
||||
Bcc: {{namedAddresses .Mail.BccNames .Mail.Bcc}}{{end}}
|
||||
From: {{namedAddress .Mail.FromName .Mail.From}}
|
||||
Subject: {{.Mail.Subject}}{{if .Mail.ReplyTo}}
|
||||
Reply-To: {{namedAddress .Mail.ReplyToName .Mail.ReplyTo}}{{end}}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative; boundary="===============284fad24nao8f4na284f2n4=="
|
||||
Content-Type: multipart/alternative; boundary="==============={{.Boundary}}=="
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
--===============284fad24nao8f4na284f2n4==
|
||||
--==============={{.Boundary}}==
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
{{.TextBody}}
|
||||
--===============284fad24nao8f4na284f2n4==
|
||||
{{.Mail.TextBody}}
|
||||
--==============={{.Boundary}}==
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
{{.HTMLBody}}
|
||||
--===============284fad24nao8f4na284f2n4==--
|
||||
{{.Mail.HTMLBody}}
|
||||
--==============={{.Boundary}}==--
|
||||
`))
|
||||
|
@ -3,6 +3,7 @@ package authboss
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@ -69,3 +70,12 @@ func TestSMTPMailer(t *testing.T) {
|
||||
t.Error("Should have panicd.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoundary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mailer := smtpMailer{nil, nil, rand.New(rand.NewSource(3))}
|
||||
if got := mailer.boundary(); got != "ntadoe" {
|
||||
t.Error("boundary was wrong", got)
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,12 @@ type mockUser struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
type mockStorer map[string]Attributes
|
||||
type mockStoredUser struct {
|
||||
mockUser
|
||||
mockStoreLoader
|
||||
}
|
||||
|
||||
type mockStoreLoader map[string]mockUser
|
||||
|
||||
func (m mockStorer) Create(key string, attr Attributes) error {
|
||||
m[key] = attr
|
||||
|
@ -8,7 +8,6 @@ var registeredModules = make(map[string]Modularizer)
|
||||
type Modularizer interface {
|
||||
Initialize(*Authboss) error
|
||||
Routes() RouteTable
|
||||
Storage() StorageOptions
|
||||
}
|
||||
|
||||
// RegisterModule with the core providing all the necessary information to
|
||||
|
16
oauth2.go
16
oauth2.go
@ -1,12 +1,17 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// FormValue constants
|
||||
const (
|
||||
FormValueOAuth2State = "state"
|
||||
)
|
||||
|
||||
/*
|
||||
OAuth2Provider is the entire configuration
|
||||
required to authenticate with this provider.
|
||||
@ -25,16 +30,15 @@ specifically: UID (the ID according to the provider) and the Email address.
|
||||
The UID must be passed back or there will be an error as it is the means of identifying the
|
||||
user in the system, e-mail is optional but should be returned in systems using
|
||||
emailing. The keys authboss.StoreOAuth2UID and authboss.StoreEmail can be used to set
|
||||
these values in the authboss.Attributes map returned by the callback.
|
||||
these values in the map returned by the callback.
|
||||
|
||||
In addition to the required values mentioned above any additional
|
||||
values that you wish to have in your user struct can be included here, such as the
|
||||
Name of the user at the endpoint. Keep in mind that only types that are valid for the
|
||||
Attributes type should be used: string, bool, time.Time, int64, or any type that implements
|
||||
database/driver.Valuer.
|
||||
Name of the user at the endpoint. This will be passed back in the Arbitrary()
|
||||
function if it exists.
|
||||
*/
|
||||
type OAuth2Provider struct {
|
||||
OAuth2Config *oauth2.Config
|
||||
AdditionalParams url.Values
|
||||
Callback func(context.Context, oauth2.Config, *oauth2.Token) (Attributes, error)
|
||||
Callback func(context.Context, oauth2.Config, *oauth2.Token) (map[string]string, error)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -10,12 +11,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"github.com/go-authboss/authboss"
|
||||
"github.com/go-authboss/authboss/internal/mocks"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/facebook"
|
||||
"golang.org/x/oauth2/google"
|
||||
"github.com/go-authboss/authboss"
|
||||
"github.com/go-authboss/authboss/internal/mocks"
|
||||
)
|
||||
|
||||
var testProviders = map[string]authboss.OAuth2Provider{
|
||||
|
@ -1,12 +1,12 @@
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"github.com/go-authboss/authboss"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -23,7 +23,7 @@ type googleMeResponse struct {
|
||||
var clientGet = (*http.Client).Get
|
||||
|
||||
// Google is a callback appropriate for use with Google's OAuth2 configuration.
|
||||
func Google(ctx context.Context, cfg oauth2.Config, token *oauth2.Token) (authboss.Attributes, error) {
|
||||
func Google(ctx context.Context, cfg oauth2.Config, token *oauth2.Token) (map[string]string, error) {
|
||||
client := cfg.Client(ctx, token)
|
||||
resp, err := clientGet(client, googleInfoEndpoint)
|
||||
if err != nil {
|
||||
@ -37,7 +37,7 @@ func Google(ctx context.Context, cfg oauth2.Config, token *oauth2.Token) (authbo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authboss.Attributes{
|
||||
return map[string]string{
|
||||
authboss.StoreOAuth2UID: jsonResp.ID,
|
||||
authboss.StoreEmail: jsonResp.Email,
|
||||
}, nil
|
||||
@ -50,7 +50,7 @@ type facebookMeResponse struct {
|
||||
}
|
||||
|
||||
// Facebook is a callback appropriate for use with Facebook's OAuth2 configuration.
|
||||
func Facebook(ctx context.Context, cfg oauth2.Config, token *oauth2.Token) (authboss.Attributes, error) {
|
||||
func Facebook(ctx context.Context, cfg oauth2.Config, token *oauth2.Token) (map[string]string, error) {
|
||||
client := cfg.Client(ctx, token)
|
||||
resp, err := clientGet(client, facebookInfoEndpoint)
|
||||
if err != nil {
|
||||
@ -64,7 +64,7 @@ func Facebook(ctx context.Context, cfg oauth2.Config, token *oauth2.Token) (auth
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authboss.Attributes{
|
||||
return map[string]string{
|
||||
"name": jsonResp.Name,
|
||||
authboss.StoreOAuth2UID: jsonResp.ID,
|
||||
authboss.StoreEmail: jsonResp.Email,
|
||||
|
@ -1,15 +1,15 @@
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"github.com/go-authboss/authboss"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func TestGoogle(t *testing.T) {
|
||||
|
185
router.go
185
router.go
@ -5,11 +5,18 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FormValue constants
|
||||
const (
|
||||
FormValueRedirect = "redir"
|
||||
)
|
||||
|
||||
// HandlerFunc augments http.HandlerFunc with a context and error handling.
|
||||
type HandlerFunc func(http.ResponseWriter, *http.Request) error
|
||||
|
||||
// RouteTable is a routing table from a path to a handlerfunc.
|
||||
type RouteTable map[string]http.HandlerFunc
|
||||
type RouteTable map[string]HandlerFunc
|
||||
|
||||
// NewRouter returns a router to be mounted at some mountpoint.
|
||||
func (a *Authboss) NewRouter() http.Handler {
|
||||
@ -43,98 +50,100 @@ type contextRoute struct {
|
||||
}
|
||||
|
||||
func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Instantiate the context
|
||||
ctx := c.Authboss.InitContext(w, r)
|
||||
/*
|
||||
// Instantiate the context
|
||||
ctx := c.Authboss.InitContext(w, r)
|
||||
|
||||
// Check to make sure we actually need to visit this route
|
||||
if redirectIfLoggedIn(ctx, w, r) {
|
||||
return
|
||||
}
|
||||
// Check to make sure we actually need to visit this route
|
||||
if redirectIfLoggedIn(ctx, w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
// Call the handler
|
||||
err := c.fn(ctx, w, r)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
// Call the handler
|
||||
err := c.fn(ctx, w, r)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Log the error
|
||||
fmt.Fprintf(c.LogWriter, "Error Occurred at %s: %v", r.URL.Path, err)
|
||||
// Log the error
|
||||
fmt.Fprintf(c.LogWriter, "Error Occurred at %s: %v", r.URL.Path, err)
|
||||
|
||||
// Do specific error handling for special kinds of errors.
|
||||
switch e := err.(type) {
|
||||
case ErrAndRedirect:
|
||||
if len(e.FlashSuccess) > 0 {
|
||||
ctx.SessionStorer.Put(FlashSuccessKey, e.FlashSuccess)
|
||||
// Do specific error handling for special kinds of errors.
|
||||
switch e := err.(type) {
|
||||
case ErrAndRedirect:
|
||||
if len(e.FlashSuccess) > 0 {
|
||||
ctx.SessionStorer.Put(FlashSuccessKey, e.FlashSuccess)
|
||||
}
|
||||
if len(e.FlashError) > 0 {
|
||||
ctx.SessionStorer.Put(FlashErrorKey, e.FlashError)
|
||||
}
|
||||
http.Redirect(w, r, e.Location, http.StatusFound)
|
||||
case ClientDataErr:
|
||||
if c.BadRequestHandler != nil {
|
||||
c.BadRequestHandler.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
io.WriteString(w, "400 Bad request")
|
||||
}
|
||||
default:
|
||||
if c.ErrorHandler != nil {
|
||||
c.ErrorHandler.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
io.WriteString(w, "500 An error has occurred")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(e.FlashError) > 0 {
|
||||
ctx.SessionStorer.Put(FlashErrorKey, e.FlashError)
|
||||
}
|
||||
http.Redirect(w, r, e.Location, http.StatusFound)
|
||||
case ClientDataErr:
|
||||
if c.BadRequestHandler != nil {
|
||||
c.BadRequestHandler.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
io.WriteString(w, "400 Bad request")
|
||||
}
|
||||
default:
|
||||
if c.ErrorHandler != nil {
|
||||
c.ErrorHandler.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
io.WriteString(w, "500 An error has occurred")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// redirectIfLoggedIn checks a user's existence by using currentUser. This is done instead of
|
||||
// a simple Session cookie check so that the remember module has a chance to log the user in
|
||||
// before they are determined to "not be logged in".
|
||||
//
|
||||
// The exceptional routes are sort of hardcoded in a terrible way in here, later on this could move to some
|
||||
// configuration or something more interesting.
|
||||
func redirectIfLoggedIn(ctx *Context, w http.ResponseWriter, r *http.Request) (handled bool) {
|
||||
// If it's a log out url, always let it pass through.
|
||||
if strings.HasSuffix(r.URL.Path, "/logout") {
|
||||
return false
|
||||
}
|
||||
// redirectIfLoggedIn checks a user's existence by using currentUser. This is done instead of
|
||||
// a simple Session cookie check so that the remember module has a chance to log the user in
|
||||
// before they are determined to "not be logged in".
|
||||
//
|
||||
// The exceptional routes are sort of hardcoded in a terrible way in here, later on this could move to some
|
||||
// configuration or something more interesting.
|
||||
func redirectIfLoggedIn(ctx *Context, w http.ResponseWriter, r *http.Request) (handled bool) {
|
||||
// If it's a log out url, always let it pass through.
|
||||
if strings.HasSuffix(r.URL.Path, "/logout") {
|
||||
return false
|
||||
}
|
||||
|
||||
// If it's an auth url, allow them through if they're half-authed.
|
||||
if strings.HasSuffix(r.URL.Path, "/auth") || strings.Contains(r.URL.Path, "/oauth2/") {
|
||||
if halfAuthed, ok := ctx.SessionStorer.Get(SessionHalfAuthKey); ok && halfAuthed == "true" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, check if they're logged in, this uses hooks to allow remember
|
||||
// to set the session cookie
|
||||
cu, err := ctx.currentUser(ctx, w, r)
|
||||
|
||||
// if the user was not found, that means the user was deleted from the underlying
|
||||
// storer and we should just remove this session cookie and allow them through.
|
||||
// if it's a generic error, 500
|
||||
// if the user is found, redirect them away from this page, because they don't need
|
||||
// to see it.
|
||||
if err == ErrUserNotFound {
|
||||
uname, _ := ctx.SessionStorer.Get(SessionKey)
|
||||
fmt.Fprintf(ctx.LogWriter, "user (%s) has session cookie but user not found, removing cookie", uname)
|
||||
ctx.SessionStorer.Del(SessionKey)
|
||||
return false
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(ctx.LogWriter, "error occurred reading current user at %s: %v", r.URL.Path, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
io.WriteString(w, "500 An error has occurred")
|
||||
return true
|
||||
}
|
||||
|
||||
if cu != nil {
|
||||
if redir := r.FormValue(FormValueRedirect); len(redir) > 0 {
|
||||
http.Redirect(w, r, redir, http.StatusFound)
|
||||
} else {
|
||||
http.Redirect(w, r, ctx.AuthLoginOKPath, http.StatusFound)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// If it's an auth url, allow them through if they're half-authed.
|
||||
if strings.HasSuffix(r.URL.Path, "/auth") || strings.Contains(r.URL.Path, "/oauth2/") {
|
||||
if halfAuthed, ok := ctx.SessionStorer.Get(SessionHalfAuthKey); ok && halfAuthed == "true" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, check if they're logged in, this uses hooks to allow remember
|
||||
// to set the session cookie
|
||||
cu, err := ctx.currentUser(ctx, w, r)
|
||||
|
||||
// if the user was not found, that means the user was deleted from the underlying
|
||||
// storer and we should just remove this session cookie and allow them through.
|
||||
// if it's a generic error, 500
|
||||
// if the user is found, redirect them away from this page, because they don't need
|
||||
// to see it.
|
||||
if err == ErrUserNotFound {
|
||||
uname, _ := ctx.SessionStorer.Get(SessionKey)
|
||||
fmt.Fprintf(ctx.LogWriter, "user (%s) has session cookie but user not found, removing cookie", uname)
|
||||
ctx.SessionStorer.Del(SessionKey)
|
||||
return false
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(ctx.LogWriter, "error occurred reading current user at %s: %v", r.URL.Path, err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
io.WriteString(w, "500 An error has occurred")
|
||||
return true
|
||||
}
|
||||
|
||||
if cu != nil {
|
||||
if redir := r.FormValue(FormValueRedirect); len(redir) > 0 {
|
||||
http.Redirect(w, r, redir, http.StatusFound)
|
||||
} else {
|
||||
http.Redirect(w, r, ctx.AuthLoginOKPath, http.StatusFound)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
*/
|
||||
}
|
||||
|
@ -53,6 +53,10 @@ type Storer interface {
|
||||
GetUsername(ctx context.Context) (username string, err error)
|
||||
GetPassword(ctx context.Context) (password string, err error)
|
||||
|
||||
// Create the user with the state, this should throw errors if
|
||||
// it's created already.
|
||||
Create(ctx context.Context) error
|
||||
|
||||
// Save the state
|
||||
Save(ctx context.Context) error
|
||||
|
||||
@ -66,7 +70,7 @@ type ArbitraryStorer interface {
|
||||
|
||||
// PutArbitrary allows arbitrary fields defined by the authboss library
|
||||
// consumer to add fields to the user registration piece.
|
||||
PutArbitrary(ctx context.Context) error
|
||||
PutArbitrary(ctx context.Context, arbitrary map[string]string) error
|
||||
// GetArbitrary is used only to display the arbitrary data back to the user
|
||||
// when the form is reset.
|
||||
GetArbitrary(ctx context.Context) (arbitrary map[string]string, err error)
|
||||
|
Loading…
Reference in New Issue
Block a user