mirror of
				https://github.com/volatiletech/authboss.git
				synced 2025-10-30 23:47:59 +02:00 
			
		
		
		
	Add some more infrastructure.
- Add module system. - Add storer interface.
This commit is contained in:
		
							
								
								
									
										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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user