1
0
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:
Aaron 2015-01-10 22:52:39 -08:00
parent ec00be1052
commit a2ffe4f7c4
13 changed files with 282 additions and 53 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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