mirror of
https://github.com/volatiletech/authboss.git
synced 2025-03-27 22:01:29 +02:00
Add some more infrastructure.
- Add module system. - Add storer interface.
This commit is contained in:
parent
caf42a992e
commit
8ac986b99b
8
authboss.go
Normal file
8
authboss.go
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Package authboss is a modular authentication system for the web. It tries to
|
||||
remove as much boilerplate and "hard things" as possible so that each time you
|
||||
start a new web project in Go, you can plug it in, configure and be off to the
|
||||
races without having to think about the hard questions like how to store
|
||||
Remember Me tokens, or passwords.
|
||||
*/
|
||||
package authboss // import "gopkg.in/authboss.v0"
|
8
config.go
Normal file
8
config.go
Normal file
@ -0,0 +1,8 @@
|
||||
package authboss
|
||||
|
||||
// Config is a map to provide configuration key-values.
|
||||
//
|
||||
// Config{
|
||||
// "logger": myIOReader,
|
||||
// }
|
||||
type Config map[string]interface{}
|
50
core/core.go
Normal file
50
core/core.go
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Package core is essentially just a namespacing for the module system. This
|
||||
allows the main package for user consumption to remain free of cruft.
|
||||
*/
|
||||
package core // import "gopkg.in/authboss.v0/core
|
||||
|
||||
// dataType represents the various types that clients must be able to store.
|
||||
// This type is duplicated from storer.go that we might avoid having users
|
||||
// importing the core package.
|
||||
type dataType int
|
||||
|
||||
const (
|
||||
Integer dataType = iota
|
||||
String
|
||||
DateTime
|
||||
)
|
||||
|
||||
var modules = make(map[string]module)
|
||||
|
||||
type module struct {
|
||||
Name string
|
||||
Storage StorageOptions
|
||||
RequiredConfig []string
|
||||
}
|
||||
|
||||
// StorageOptions is a map depicting the things a module must be able to store.
|
||||
type StorageOptions map[string]DataType
|
||||
|
||||
// Register a module with the core providing all the necessary information to
|
||||
// integrate into authboss.
|
||||
func Register(name string, storage StorageOptions, requiredConfig ...string) {
|
||||
}
|
||||
|
||||
// LoadedModules returns a list of modules that are currently loaded.
|
||||
func LoadedModules() []string {
|
||||
mods := make([]string, len(modules))
|
||||
i := 0
|
||||
for k, _ := range modules {
|
||||
mods[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
// IsLoaded checks if a specific module is loaded.
|
||||
func IsLoaded(mod string) bool {
|
||||
_, ok := modules[mod]
|
||||
return ok
|
||||
}
|
181
storer.go
Normal file
181
storer.go
Normal file
@ -0,0 +1,181 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DataType represents the various types that clients must be able to store.
|
||||
type DataType int
|
||||
|
||||
const (
|
||||
Integer DataType = iota
|
||||
String
|
||||
DateTime
|
||||
)
|
||||
|
||||
func (d DataType) String() string {
|
||||
switch d {
|
||||
case Integer:
|
||||
return "Integer"
|
||||
case String:
|
||||
return "String"
|
||||
case DateTime:
|
||||
return "DateTime"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// AttributeMeta stores type information for attributes.
|
||||
type AttributeMeta map[string]DataType
|
||||
|
||||
// Names returns the names of all the attributes.
|
||||
func (a AttributeMeta) Names() []string {
|
||||
names := make([]string, len(a))
|
||||
i := 0
|
||||
for n, _ := range a {
|
||||
names[i] = n
|
||||
i++
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// Attribute is data along with type information.
|
||||
type Attribute struct {
|
||||
Type DataType
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Attributes is just a key-value mapping of data.
|
||||
type Attributes map[string]Attribute
|
||||
|
||||
// Names returns the names of all the attributes.
|
||||
func (a Attributes) Names() []string {
|
||||
names := make([]string, len(a))
|
||||
i := 0
|
||||
for n, _ := range a {
|
||||
names[i] = n
|
||||
i++
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// Bind the data in the attributes to the given struct. This means the
|
||||
// struct creator must have read the documentation and decided what fields
|
||||
// will be needed ahead of time.
|
||||
func (a Attributes) Bind(strct interface{}) error {
|
||||
structType := reflect.TypeOf(strct)
|
||||
if structType.Kind() != reflect.Ptr {
|
||||
return errors.New("Bind: Must pass in a struct pointer.")
|
||||
}
|
||||
|
||||
structVal := reflect.ValueOf(strct).Elem()
|
||||
structType = structVal.Type()
|
||||
for k, v := range a {
|
||||
if _, has := structType.FieldByName(k); !has {
|
||||
return fmt.Errorf("Bind: Struct was missing %s field, type: %v", k, v.Type)
|
||||
}
|
||||
|
||||
field := structVal.FieldByName(k)
|
||||
if !field.CanSet() {
|
||||
return fmt.Errorf("Bind: Found field %s, but was not writeable.", k)
|
||||
}
|
||||
|
||||
fieldKind := field.Kind()
|
||||
fieldType := field.Type()
|
||||
switch v.Type {
|
||||
case Integer:
|
||||
if fieldKind != reflect.Int {
|
||||
return fmt.Errorf("Bind: Field %s's type should be %s but was %s", k, reflect.Int.String(), fieldType)
|
||||
}
|
||||
field.SetInt(int64(v.Value.(int)))
|
||||
case String:
|
||||
if fieldKind != reflect.String {
|
||||
return fmt.Errorf("Bind: Field %s's type should be %s but was %s", k, reflect.String.String(), fieldType)
|
||||
}
|
||||
field.SetString(v.Value.(string))
|
||||
case DateTime:
|
||||
timeType := reflect.TypeOf(time.Time{})
|
||||
if fieldType != timeType {
|
||||
return fmt.Errorf("Bind: Field %s's type should be %s but was %s", k, timeType.String(), fieldType)
|
||||
}
|
||||
field.Set(reflect.ValueOf(v.Value))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type postgresUser struct {
|
||||
// Anything Else
|
||||
CustomAttribute string
|
||||
|
||||
// AuthBoss attributes.
|
||||
Email string
|
||||
}
|
||||
|
||||
func (p *postgresStorer) Put(key string, attr Attributes) error {
|
||||
u := postgresUser{}
|
||||
if err := attr.Bind(&u); err != nil {
|
||||
panic("I should have written my user struct better!")
|
||||
}
|
||||
|
||||
_, err := p.Exec("update users set email = $1 where id = $2", u.Email, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postgresStorer) Create(key string, attr Attributes) error {
|
||||
u := postgresUser{
|
||||
CustomAttribute: "DefaultValue",
|
||||
}
|
||||
|
||||
if err := attr.Bind(&u); err != nil {
|
||||
panic("I should have written my user struct better!")
|
||||
}
|
||||
|
||||
_, err := p.Exec("insert into users (custom_attribute, email) values ($1)", u.CustomAttribute, u.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postgresStorer) Get(key string, attrMeta AttributeMeta) (interface{}, error) {
|
||||
row := p.QueryRow(`select * from users where key = $1`, key)
|
||||
u := postgresUser{}
|
||||
if err := row.Scan(&u.CustomAttribute, &u.Email); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}*/
|
100
storer_test.go
Normal file
100
storer_test.go
Normal file
@ -0,0 +1,100 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAttributes_Bind(t *testing.T) {
|
||||
anInteger := 5
|
||||
aString := "string"
|
||||
aTime := time.Now()
|
||||
|
||||
data := Attributes{
|
||||
"Integer": Attribute{Integer, anInteger},
|
||||
"String": Attribute{String, aString},
|
||||
"Date": Attribute{DateTime, aTime},
|
||||
}
|
||||
|
||||
s := struct {
|
||||
Integer int
|
||||
String string
|
||||
Date time.Time
|
||||
}{}
|
||||
|
||||
if err := data.Bind(&s); err != nil {
|
||||
t.Error("Unexpected Error:", err)
|
||||
}
|
||||
|
||||
if s.Integer != anInteger {
|
||||
t.Error("Integer was not set.")
|
||||
}
|
||||
if s.String != aString {
|
||||
t.Error("String was not set.")
|
||||
}
|
||||
if s.Date != aTime {
|
||||
t.Error("Time was not set.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttributes_BindNoPtr(t *testing.T) {
|
||||
data := Attributes{}
|
||||
s := struct{}{}
|
||||
|
||||
if err := data.Bind(s); err == nil {
|
||||
t.Error("Expected an error.")
|
||||
} else if !strings.Contains(err.Error(), "struct pointer") {
|
||||
t.Error("Expected an error about pointers got:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttributes_BindMissingField(t *testing.T) {
|
||||
data := Attributes{"Integer": Attribute{Integer, 5}}
|
||||
s := struct{}{}
|
||||
|
||||
if err := data.Bind(&s); err == nil {
|
||||
t.Error("Expected an error.")
|
||||
} else if !strings.Contains(err.Error(), "missing") {
|
||||
t.Error("Expected an error about missing fields, got:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttributes_BindTypeFail(t *testing.T) {
|
||||
tests := []struct {
|
||||
Attr Attributes
|
||||
Err string
|
||||
ToBind interface{}
|
||||
}{
|
||||
{
|
||||
Attr: Attributes{"Integer": Attribute{Integer, 5}},
|
||||
Err: "should be int",
|
||||
ToBind: &struct {
|
||||
Integer string
|
||||
}{},
|
||||
},
|
||||
{
|
||||
Attr: Attributes{"String": Attribute{String, ""}},
|
||||
Err: "should be string",
|
||||
ToBind: &struct {
|
||||
String int
|
||||
}{},
|
||||
},
|
||||
{
|
||||
Attr: Attributes{"Date": Attribute{DateTime, time.Time{}}},
|
||||
Err: "should be time.Time",
|
||||
ToBind: &struct {
|
||||
Date int
|
||||
}{},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if err := test.Attr.Bind(test.ToBind); err == nil {
|
||||
t.Errorf("%d> Expected an error.", i)
|
||||
} else if !strings.Contains(err.Error(), test.Err) {
|
||||
t.Errorf("%d> Expected an error about %q got: %q", i, test.Err, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user