1
0
mirror of https://github.com/volatiletech/authboss.git synced 2024-11-28 08:58:38 +02:00

More changes

This commit is contained in:
Aaron L 2017-02-20 15:56:26 -08:00
parent f7db80e4e2
commit 02e91bc0ad
16 changed files with 214 additions and 384 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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)
}

View File

@ -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])
}
}
}

View File

@ -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}}==--
`))

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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{

View File

@ -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,

View File

@ -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
View File

@ -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
*/
}

View File

@ -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)