mirror of
https://github.com/volatiletech/authboss.git
synced 2024-11-24 08:42:17 +02:00
Add many new files and types.
- Add context. - Add handler type. - Add new storers for client storage and sessions. - Add start of remember module.
This commit is contained in:
parent
ec00be1052
commit
a2ffe4f7c4
16
authboss.go
16
authboss.go
@ -12,8 +12,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
var logger io.Writer = ioutil.Discard
|
||||
@ -35,17 +33,3 @@ func Init(config *Config) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Router returns a router to be mounted at some mountpoint.
|
||||
func Router(config *Config) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
for name, mod := range modules {
|
||||
for route, handler := range mod.Routes() {
|
||||
fmt.Fprintf(logger, "[%-10s] Register Route: %s\n", name, route)
|
||||
mux.HandleFunc(path.Join(config.MountPath, route), handler)
|
||||
}
|
||||
}
|
||||
|
||||
return mux
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@ -11,6 +10,7 @@ import (
|
||||
|
||||
func TestMain(main *testing.M) {
|
||||
RegisterModule("testmodule", testMod)
|
||||
Init(NewConfig())
|
||||
code := main.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
@ -40,16 +40,14 @@ func TestAuthBossRouter(t *testing.T) {
|
||||
c.MountPath = "/candycanes"
|
||||
c.LogWriter = os.Stdout
|
||||
|
||||
router := Router(c)
|
||||
router := NewRouter(c)
|
||||
|
||||
r, _ := http.NewRequest("GET", "/candycanes/testroute", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(response, r)
|
||||
|
||||
log.Println(response.HeaderMap)
|
||||
|
||||
if response.Header().Get("testmodule") != "test" {
|
||||
if response.Header().Get("testhandler") != "test" {
|
||||
t.Error("Expected a header to have been set.")
|
||||
}
|
||||
}
|
||||
|
47
callbacks.go
Normal file
47
callbacks.go
Normal file
@ -0,0 +1,47 @@
|
||||
package authboss
|
||||
|
||||
// Before callbacks can interrupt the flow by returning an error. This is used to stop
|
||||
// the callback chain and the original handler from executing.
|
||||
type Before func(Context) error
|
||||
|
||||
// After is a request callback that happens after the event.
|
||||
type After func(Context)
|
||||
|
||||
// Callbacks is a collection of callbacks that fire before and after certain
|
||||
// methods.
|
||||
type Callbacks struct {
|
||||
beforeAuth []Before
|
||||
afterAuth []After
|
||||
}
|
||||
|
||||
func NewCallbacks() *Callbacks {
|
||||
return &Callbacks{
|
||||
make([]Before, 0),
|
||||
make([]After, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Callbacks) AddBeforeAuth(f Before) {
|
||||
c.beforeAuth = append(c.beforeAuth, f)
|
||||
}
|
||||
|
||||
func (c *Callbacks) AddAfterAuth(f After) {
|
||||
c.afterAuth = append(c.afterAuth, f)
|
||||
}
|
||||
|
||||
func (c *Callbacks) BeforeAuth(ctx Context) error {
|
||||
for _, fn := range c.beforeAuth {
|
||||
err := fn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Callbacks) AfterAuth(ctx Context) {
|
||||
for _, fn := range c.afterAuth {
|
||||
fn(ctx)
|
||||
}
|
||||
}
|
8
client_storer.go
Normal file
8
client_storer.go
Normal file
@ -0,0 +1,8 @@
|
||||
package authboss
|
||||
|
||||
// ClientStorer should be able to store values on the clients machine. This is
|
||||
// usually going to be a cookie store.
|
||||
type ClientStorer interface {
|
||||
Put(key, value string)
|
||||
Get(key string) string
|
||||
}
|
@ -12,8 +12,10 @@ type Config struct {
|
||||
|
||||
AuthLogoutRoute string `json:"authLogoutRoute" xml:"authLogoutRoute"`
|
||||
|
||||
Storer Storer `json:"-" xml:"-"`
|
||||
LogWriter io.Writer `json:"-" xml:"-"`
|
||||
Storer Storer `json:"-" xml:"-"`
|
||||
ClientStorer ClientStorer `json:"-" xml:"-"`
|
||||
SessionStorer SessionStorer `json:"-" xml:"-"`
|
||||
LogWriter io.Writer `json:"-" xml:"-"`
|
||||
}
|
||||
|
||||
// NewConfig creates a new config full of default values ready to override.
|
||||
|
26
context.go
Normal file
26
context.go
Normal file
@ -0,0 +1,26 @@
|
||||
package authboss
|
||||
|
||||
// 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 {
|
||||
ClientStorer ClientStorer
|
||||
User Attributes
|
||||
|
||||
keyValues map[string]interface{}
|
||||
}
|
||||
|
||||
func NewContext() *Context {
|
||||
return &Context{
|
||||
keyValues: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) Put(key string, thing interface{}) {
|
||||
c.keyValues[key] = thing
|
||||
}
|
||||
|
||||
func (c *Context) Get(key string) (thing interface{}, ok bool) {
|
||||
thing, ok = c.keyValues[key]
|
||||
return thing, ok
|
||||
}
|
11
module.go
11
module.go
@ -1,17 +1,8 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var modules = make(map[string]Modularizer)
|
||||
|
||||
// RouteTable is a routing table from a path to a handlerfunc.
|
||||
type RouteTable map[string]http.HandlerFunc
|
||||
|
||||
// StorageOptions is a map depicting the things a module must be able to store.
|
||||
type StorageOptions map[string]DataType
|
||||
|
||||
// Modularizer should be implemented by all the authboss modules.
|
||||
type Modularizer interface {
|
||||
Initialize(*Config) error
|
||||
Routes() RouteTable
|
||||
|
@ -19,7 +19,7 @@ var testMod = &testModule{
|
||||
},
|
||||
}
|
||||
|
||||
func testHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func testHandler(ctx *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("testhandler", "test")
|
||||
}
|
||||
|
||||
|
91
remember/remember.go
Normal file
91
remember/remember.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Package remember implements persistent logins through (typically) cookie session
|
||||
// storages. The SessionStorer implementation must be fully secure either over https
|
||||
// or using signed cookies or it is easily exploitable.
|
||||
package remember
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"gopkg.in/authboss.v0"
|
||||
)
|
||||
|
||||
const nRandBytes = 32
|
||||
|
||||
// R is the singleton instance of the remember module which will have been
|
||||
// configured and ready to use after authboss.Init()
|
||||
var R *Remember
|
||||
|
||||
func init() {
|
||||
R = &Remember{}
|
||||
authboss.RegisterModule("remember", R)
|
||||
}
|
||||
|
||||
type Remember struct {
|
||||
storer authboss.TokenStorer
|
||||
}
|
||||
|
||||
func (r *Remember) Initialize(c *authboss.Config) error {
|
||||
if storer, ok := c.Storer.(authboss.TokenStorer); !ok {
|
||||
return errors.New("Remember module requires a TokenStorer interface be satisfied.")
|
||||
} else {
|
||||
r.storer = storer
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remember) Routes() authboss.RouteTable {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remember) Storage() authboss.StorageOptions {
|
||||
return nil
|
||||
}
|
||||
|
||||
// New generates a new remember token and stores it in the configured TokenStorer.
|
||||
// The return value is a token that should only be given to a user if the delivery
|
||||
// method is secure which means at least signed if not encrypted.
|
||||
func (r *Remember) New(ctx *authboss.Context, storageKey string, keys ...string) (string, error) {
|
||||
token := make([]byte, nRandBytes)
|
||||
if _, err := rand.Read(token); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
token = append(token, []byte(k)...)
|
||||
}
|
||||
|
||||
sum := md5.Sum(token)
|
||||
finalToken := base64.URLEncoding.EncodeToString(token)
|
||||
storageToken := base64.StdEncoding.EncodeToString(sum[:])
|
||||
|
||||
if err := r.storer.AddToken(storageKey, storageToken); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return finalToken, nil
|
||||
}
|
||||
|
||||
// Auth takes a token that was given to a user and checks to see if something
|
||||
// is matching in the database. If something is found the old token is deleted
|
||||
// and a new one should be generated. The return value is the key of the
|
||||
// record who owned this token.
|
||||
func (r *Remember) Auth(ctx *authboss.Context, finalToken string) (string, error) {
|
||||
token, err := base64.URLEncoding.DecodeString(finalToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sum := md5.Sum(token)
|
||||
key, err := r.storer.UseToken(base64.StdEncoding.EncodeToString(sum[:]))
|
||||
if err == authboss.TokenNotFound {
|
||||
return "", nil
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
16
remember/remember_test.go
Normal file
16
remember/remember_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package remember
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gopkg.in/authboss.v0"
|
||||
)
|
||||
|
||||
func TestMakeToken(t *testing.T) {
|
||||
tok, err := R.New(authboss.NewContext(), "storage", "hello", "world", "5")
|
||||
if err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
} else if len(tok) == 0 {
|
||||
t.Error("It should have made a token.")
|
||||
}
|
||||
}
|
37
router.go
Normal file
37
router.go
Normal file
@ -0,0 +1,37 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Handler augments http.HandlerFunc with a context.
|
||||
type HandlerFunc func(*Context, http.ResponseWriter, *http.Request)
|
||||
|
||||
// RouteTable is a routing table from a path to a handlerfunc.
|
||||
type RouteTable map[string]HandlerFunc
|
||||
|
||||
// NewRouter returns a router to be mounted at some mountpoint.
|
||||
func NewRouter(config *Config) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
for name, mod := range modules {
|
||||
for route, handler := range mod.Routes() {
|
||||
fmt.Fprintf(logger, "[%-10s] Register Route: %s\n", name, route)
|
||||
mux.Handle(path.Join(config.MountPath, route), contextRoute{handler})
|
||||
}
|
||||
}
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
type contextRoute struct {
|
||||
fn HandlerFunc
|
||||
}
|
||||
|
||||
func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := NewContext()
|
||||
|
||||
c.fn(ctx, w, r)
|
||||
}
|
9
session_storer.go
Normal file
9
session_storer.go
Normal file
@ -0,0 +1,9 @@
|
||||
package authboss
|
||||
|
||||
// SessionStorer 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 storage
|
||||
// these cookies should be signed in order to prevent tampering, or they should be encrypted.
|
||||
type SessionStorer interface {
|
||||
Put(key string, value interface{})
|
||||
Get(key string) interface{}
|
||||
}
|
58
storer.go
58
storer.go
@ -8,6 +8,45 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// UserNotFound should be returned from Get when the record is not found.
|
||||
var UserNotFound = errors.New("User not found")
|
||||
|
||||
// TokenNotFound should be returned from UseToken when the record is not found.
|
||||
var TokenNotFound = errors.New("Token not found")
|
||||
|
||||
// StorageOptions is a map depicting the things a module must be able to store.
|
||||
type StorageOptions map[string]DataType
|
||||
|
||||
// Storer must be implemented in order to store the user's attributes somewhere.
|
||||
// The type of store is up to the developer implementing it, and all it has to
|
||||
// do is be able to store several simple types.
|
||||
type Storer interface {
|
||||
// Create is the same as put, except it refers to a non-existent key.
|
||||
Create(key string, attr Attributes) error
|
||||
// Put is for storing the attributes passed in. The type information can
|
||||
// help serialization without using type assertions.
|
||||
Put(key string, attr Attributes) error
|
||||
// Get is for retrieving attributes for a given key. The return value
|
||||
// must be a struct thot contains a field with the correct type as shown
|
||||
// by attrMeta. If the key is not found in the data store simply
|
||||
// return nil, UserNotFound.
|
||||
Get(key string, attrMeta AttributeMeta) (interface{}, error)
|
||||
}
|
||||
|
||||
// TokenStorer must be implemented in order to satisfy the remember module's
|
||||
// storage requirements.
|
||||
type TokenStorer interface {
|
||||
Storer
|
||||
// AddToken saves a new token for the key.
|
||||
AddToken(key, token string) error
|
||||
// DelTokens removes all tokens for a given key.
|
||||
DelTokens(key string) error
|
||||
// UseToken finds the token, removes the key/token entry in the store
|
||||
// and returns the key that was found. If the token could not be found
|
||||
// return "", TokenNotFound
|
||||
UseToken(token string) (key string, err error)
|
||||
}
|
||||
|
||||
// DataType represents the various types that clients must be able to store.
|
||||
type DataType int
|
||||
|
||||
@ -152,25 +191,6 @@ func Unbind(intf interface{}) Attributes {
|
||||
return attr
|
||||
}
|
||||
|
||||
// UserNotFound should be returned from Get when the record is not found.
|
||||
var UserNotFound = errors.New("User Not Found")
|
||||
|
||||
// Storer must be implemented in order to store the user's attributes somewhere.
|
||||
// The type of store is up to the developer implementing it, and all it has to
|
||||
// do is be able to store several simple types.
|
||||
type Storer interface {
|
||||
// Create is the same as put, except it refers to a non-existent key.
|
||||
Create(key string, attr Attributes) error
|
||||
// Put is for storing the attributes passed in. The type information can
|
||||
// help serialization without using type assertions.
|
||||
Put(key string, attr Attributes) error
|
||||
// Get is for retrieving attributes for a given key. The return value
|
||||
// must be a struct thot contains a field with the correct type as shown
|
||||
// by attrMeta. If the key is not found in the data store simply
|
||||
// return nil, UserNotFound.
|
||||
Get(key string, attrMeta AttributeMeta) (interface{}, error)
|
||||
}
|
||||
|
||||
/*type postgresStorer struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user