Documentation ¶
Overview ¶
Package goauth provides convinient functions to authenticate users, encrypt their passwords and create and administer login sessions on top of gorilla sessions.
What is the problem with gorilla sessions? Nothing, they're good and do what they should do. But there is no direct way to connect these sessions with user information. Using them directly for user login sessions is not good because:
The client may not cope with the MaxAge and edit the cookie. In this case it will send the cookie again to the server and gorialla will accept it. At least that's what I found out, maybe I did something wrong but I just edited the lifespan of the cookie and gorilla still accepted it.
There is no connection between a session and a user you might have in your database. For example if you have a "log out everywhere" function four your user or the user changes his password there is no direct way to connect the gorilla session with that user and invalidate all sessions keys. Or if you delete a user: You don't want the user be able to still login with already delivered keys.
This package provides a lot of interfaces to connect gorilla sessions with user information. It also provides implementations for these interface for MySQL, postgres and sqlite databases.
See the github page for more details: https://github.com/FabianWe/goauth and the wiki for some more explanation and small examples: https://github.com/FabianWe/goauth/wiki
Index ¶
- Constants
- Variables
- func CurrentTime() time.Time
- func DefaultTimeFromScanType(val interface{}) (time.Time, error)
- func GenRandomBase64(n int) (string, error)
- func KeyInvalid(now, validUntil time.Time) bool
- func KeyValid(now, validUntil time.Time) bool
- type BaseUserInformation
- type BcryptHandler
- type InMemoryHandler
- func (h *InMemoryHandler) CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error)
- func (h *InMemoryHandler) DeleteEntriesForUser(user UserKeyType) (int64, error)
- func (h *InMemoryHandler) DeleteInvalidKeys() (int64, error)
- func (h *InMemoryHandler) DeleteKey(key string) error
- func (h *InMemoryHandler) GetData(key string) (*SessionKeyData, error)
- func (h *InMemoryHandler) Init() error
- type MemcachedSessionHandler
- func (handler *MemcachedSessionHandler) CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error)
- func (handler *MemcachedSessionHandler) DeleteEntriesForUser(user UserKeyType) (int64, error)
- func (handler *MemcachedSessionHandler) DeleteInvalidKeys() (int64, error)
- func (handler *MemcachedSessionHandler) DeleteKey(key string) error
- func (handler *MemcachedSessionHandler) GetData(key string) (*SessionKeyData, error)
- func (handler *MemcachedSessionHandler) Init() error
- type MySQLSessionTemplate
- func (t MySQLSessionTemplate) CreateQ() string
- func (t MySQLSessionTemplate) DeleteForUserQ() string
- func (t MySQLSessionTemplate) DeleteInvalidQ() string
- func (t MySQLSessionTemplate) DeleteKeyQ() string
- func (t MySQLSessionTemplate) GetQ() string
- func (t MySQLSessionTemplate) InitQ() string
- func (t MySQLSessionTemplate) TimeFromScanType(val interface{}) (time.Time, error)
- type PasswordHandler
- type PostgresSessionTemplate
- func (t PostgresSessionTemplate) CreateQ() string
- func (t PostgresSessionTemplate) DeleteForUserQ() string
- func (t PostgresSessionTemplate) DeleteInvalidQ() string
- func (t PostgresSessionTemplate) DeleteKeyQ() string
- func (t PostgresSessionTemplate) GetQ() string
- func (t PostgresSessionTemplate) InitQ() string
- func (t PostgresSessionTemplate) TimeFromScanType(val interface{}) (time.Time, error)
- type RedisSessionHandler
- func (handler *RedisSessionHandler) CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error)
- func (handler *RedisSessionHandler) DeleteEntriesForUser(user UserKeyType) (int64, error)
- func (handler *RedisSessionHandler) DeleteInvalidKeys() (int64, error)
- func (handler *RedisSessionHandler) DeleteKey(key string) error
- func (handler *RedisSessionHandler) GetData(key string) (*SessionKeyData, error)
- func (handler *RedisSessionHandler) Init() error
- type RedisUserHandler
- func (handler *RedisUserHandler) DeleteUser(userName string) error
- func (handler *RedisUserHandler) GetUserBaseInfo(userName string) (*BaseUserInformation, error)
- func (handler *RedisUserHandler) GetUserID(userName string) (uint64, error)
- func (handler *RedisUserHandler) GetUserName(id uint64) (string, error)
- func (handler *RedisUserHandler) Init() error
- func (handler *RedisUserHandler) Insert(userName, firstName, lastName, email string, plainPW []byte) (uint64, error)
- func (handler *RedisUserHandler) ListUsers() (map[uint64]string, error)
- func (handler *RedisUserHandler) UpdatePassword(userName string, plainPW []byte) error
- func (handler *RedisUserHandler) Validate(userName string, cleartextPwCheck []byte) (uint64, error)
- type SQLSessionHandler
- func NewMySQLSessionHandler(db *sql.DB, tableName, userIDType string) *SQLSessionHandler
- func NewPostgresSessionHandler(db *sql.DB, tableName, userIDType string) *SQLSessionHandler
- func NewSQLSessionHandler(db *sql.DB, t SQLSessionTemplate, tableName, userIDType string, lockDB bool) *SQLSessionHandler
- func NewSQLite3SessionHandler(db *sql.DB, tableName, userIDType string) *SQLSessionHandler
- func (c *SQLSessionHandler) CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error)
- func (c *SQLSessionHandler) DeleteEntriesForUser(user UserKeyType) (int64, error)
- func (c *SQLSessionHandler) DeleteInvalidKeys() (int64, error)
- func (c *SQLSessionHandler) DeleteKey(key string) error
- func (c *SQLSessionHandler) GetData(key string) (*SessionKeyData, error)
- func (c *SQLSessionHandler) Init() error
- type SQLSessionTemplate
- type SQLUserHandler
- func NewMySQLUserHandler(db *sql.DB, pwHandler PasswordHandler) *SQLUserHandler
- func NewPostgresUserHandler(db *sql.DB, pwHandler PasswordHandler) *SQLUserHandler
- func NewSQLUserHandler(queries *SQLUserQueries, db *sql.DB, pwHandler PasswordHandler, blockDB bool) *SQLUserHandler
- func NewSQLite3UserHandler(db *sql.DB, pwHandler PasswordHandler) *SQLUserHandler
- func (handler *SQLUserHandler) DeleteUser(username string) error
- func (handler *SQLUserHandler) GetUserBaseInfo(userName string) (*BaseUserInformation, error)
- func (handler *SQLUserHandler) GetUserID(userName string) (uint64, error)
- func (handler *SQLUserHandler) GetUserName(id uint64) (string, error)
- func (handler *SQLUserHandler) Init() error
- func (handler *SQLUserHandler) Insert(userName, firstName, lastName, email string, plainPW []byte) (uint64, error)
- func (handler *SQLUserHandler) ListUsers() (map[uint64]string, error)
- func (handler *SQLUserHandler) UpdatePassword(username string, plainPW []byte) error
- func (handler *SQLUserHandler) Validate(userName string, cleartextPwCheck []byte) (uint64, error)
- type SQLUserQueries
- type SQLite3SessionTemplate
- type ScryptHandler
- type SessionController
- func NewInMemoryController() *SessionController
- func NewMySQLSessionController(db *sql.DB, tableName, userIDType string) *SessionController
- func NewPostgresSessionController(db *sql.DB, tableName, userIDType string) *SessionController
- func NewSQLite3SessionController(db *sql.DB, tableName, userIDType string) *SessionController
- func NewSessionController(h SessionHandler) *SessionController
- func (c *SessionController) AddKey(user UserKeyType, validDuration time.Duration) (*SessionKeyData, string, error)
- func (c *SessionController) CreateAuthSession(r *http.Request, store sessions.Store, user UserKeyType, ...) (*SessionKeyData, string, *sessions.Session, error)
- func (c *SessionController) DeleteEntriesDaemon(sleep time.Duration, ctx context.Context, reportErr bool)
- func (c *SessionController) EndSession(r *http.Request, store sessions.Store) error
- func (c SessionController) GetKey(session *sessions.Session) (string, error)
- func (c SessionController) GetSession(r *http.Request, store sessions.Store) (*sessions.Session, error)
- func (c *SessionController) ValidateSession(r *http.Request, store sessions.Store) (*SessionKeyData, *sessions.Session, error)
- type SessionHandler
- type SessionKeyData
- type UserHandler
- type UserKeyType
Constants ¶
const ( // DefaultRandomByteLength is the default length for random bytes array, this // creates random base64 strings of length 64. DefaultRandomByteLength = 48 // DefaultKeyLength is the length of the random base 64 strings, it should // be set in accordance with DefaultRandomByteLength. DefaultKeyLength = 64 )
const ( // DefaultCost is the default cost parameter for bcrypt. DefaultCost = 13 // DefaultPWLength is he default length of encrypted passwords. // This is 60 for bcrypt. DefaultPWLength = 60 // NoUserID is an user id that is returned if the user was // not found or some error occurred. NoUserID = math.MaxUint64 )
const ( // RedisDateFormat is the format string that is used to format / parse // date strings in redis. RedisDateFormat = "2006-01-02 15:04:05" )
const ( // SessionKey is the key to store the auth-key in a gorilla session. // So we store the create create for the auth session in // session.Values[SessionKey]. SessionKey = "key" )
Variables ¶
var DefaultPWHandler = NewBcryptHandler(-1)
DefaultPWHandler is the default handler for password encryption / decription.
var ErrInvalidKey = errors.New("The key is not valid any more.")
ErrInvalidKey is the error that will be returned if a key was found in the storage but the key is not valid anymore.
var ErrKeyNotFound = errors.New("No entry for key was found")
ErrKeyNotFound is the error that is returned whenever you try to lookup the information stored for a certain key but that key does not exist.
var ErrNotAuthSession = errors.New("The session is not a valid auth session.")
ErrNotAuthSession is the error that will be returned if a gorialla session does not have the SessionKey in session.Values. This usually means that the user does not have a session yet and needs to login.
var ErrUserNotFound = errors.New("User not found.")
ErrUserNotFound is an error that is used in the Validate function to signal that the user with the given username was not found.
Functions ¶
func CurrentTime ¶
CurrentTime returns the current type. For consistent behaviour you should always use this method to get the current time. It returns time.Now().UTC()
func DefaultTimeFromScanType ¶
DefaultTimeFromScanType is the default function to return database entries to a time.Time.
func GenRandomBase64 ¶
GenRandomBase64 returns a random base64 encoded string based on a random byte sequence of size n. Note that the returned string does not have length n, n is the size of the byte array! To represent n bytes in base64 you need 4*(n/3) rounded up to the next multiple of 4. So this will be the length of the returned string. For example use n = 24 for keys of length 32, n = 48 for keys of length 64 and n = 96 for keys of length 128. Use n = -1 to use the default value which is 48, so a random string of length 64.
func KeyInvalid ¶
KeyInvalid checks if a key is invalid. A key is considered invalid if now is after validUntil. The parameter now exists s.t. you can use the same now in all queries, so usually you create now once at the beginning of your function.
Types ¶
type BaseUserInformation ¶
type BaseUserInformation struct { ID uint64 UserName, FirstName, LastName, Email string LastLogin time.Time IsActive bool }
DefaultUserInformation is used to wrap the the information for a user in the default scheme.
New in version v0.5
type BcryptHandler ¶
type BcryptHandler struct {
// contains filtered or unexported fields
}
BcryptHandler is a PasswordHandler that uses bcrypt.
func NewBcryptHandler ¶
func NewBcryptHandler(cost int) *BcryptHandler
NewBcryptHandler creates a new PasswordHandler that uses bcrypt. cost is the cost parameter for the algorithm, use -1 for the default value (which should be fine in most cases). The default value is 13. Note that bcrypt has some further restrictions on the cost parameter: Currently it must be between 4 and 31.
func (*BcryptHandler) CheckPassword ¶
func (handler *BcryptHandler) CheckPassword(hashedPW, password []byte) (bool, error)
CheckPassword checks if the plaintext password was used to create the hashedPW.
func (*BcryptHandler) GenerateHash ¶
func (handler *BcryptHandler) GenerateHash(password []byte) ([]byte, error)
GenerateHash generates the password hash using bcrypt.
func (*BcryptHandler) PasswordHashLength ¶
func (handler *BcryptHandler) PasswordHashLength() int
PasswordHashLength returns the default length for bcrypt, that is 60.
type InMemoryHandler ¶
type InMemoryHandler struct {
// contains filtered or unexported fields
}
This type implements the SessionHandler interface using an in memory map. This map will be lost after you stop your application.
func NewInMemoryHandler ¶
func NewInMemoryHandler() *InMemoryHandler
func (*InMemoryHandler) CreateEntry ¶
func (h *InMemoryHandler) CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error)
func (*InMemoryHandler) DeleteEntriesForUser ¶
func (h *InMemoryHandler) DeleteEntriesForUser(user UserKeyType) (int64, error)
func (*InMemoryHandler) DeleteInvalidKeys ¶
func (h *InMemoryHandler) DeleteInvalidKeys() (int64, error)
func (*InMemoryHandler) DeleteKey ¶
func (h *InMemoryHandler) DeleteKey(key string) error
func (*InMemoryHandler) GetData ¶
func (h *InMemoryHandler) GetData(key string) (*SessionKeyData, error)
func (*InMemoryHandler) Init ¶
func (h *InMemoryHandler) Init() error
type MemcachedSessionHandler ¶
type MemcachedSessionHandler struct { // Parent is the handler wrapped by memcached. Parent SessionHandler // Client is the memcached client to connect to memcached. Client *memcache.Client // SessionPrefix is the prefix for keys stored in memcached, default is "skey". SessionPrefix string // ConvertUser is the function used to transform the string representation // of the user identification back to its original type. // The default assumes uint64. ConvertUser func(val string) (interface{}, error) // Expiration value defines how long an entry in memcached is considered // valid. // From the memcached docs (https://github.com/memcached/memcached/wiki/Programming#expiration): // "Expiration times are specified in unsigned integer seconds. They can be // set from 0, meaning "never expire", to 30 days (60 * 60 * 24 * 30). // Any time higher than 30 days is interpreted as a unix timestamp date. // If you want to expire an object on january 1st of next year, // this is how you do that." // Defauts to 3600 (1 hour). Expiration int32 // contains filtered or unexported fields }
MemcachedSessionHandler is a SessionHandler that wraps another handler and queries memcached first and only performs a query on the wrapper handler when the memcached lookup failed.
A key k gets stored as "skey<SOME-RANDOM-INT>:k" in memcached. Note that the max length for keys in memcached is 250, so don't set the session key length to something too big. The data associated with the key is stored as a json string.
The function ConvertUser is used to transform a value stored in the json string back to its original type, so you probably have to implement your own variant: The user information is of type interface{} s.t. you can use whatever type you want. When storing the user information the user key is transformed to a string with the String() method. You want to make sure that when retrieving the data your key gets transformed to the correct data type. The default implementation assumes that the key type is uint64.
Memcached errors are not returned in the functions but printed to the log.
For more examples read the wiki: https://github.com/FabianWe/goauth/wiki/Using-Memcached-for-Session-Lookups
func NewMemcachedSessionHandler ¶
func NewMemcachedSessionHandler(parent SessionHandler, client *memcache.Client) *MemcachedSessionHandler
NewMemcachedSessionHandler returns a new MemcachedSessionHandler that uses parent as the main handler to query when a memcached lookup fails. It sets Expiration to 3600 (which means 1 hour) and SessionPrefix to "skey".
func (*MemcachedSessionHandler) CreateEntry ¶
func (handler *MemcachedSessionHandler) CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error)
CreateEntry creates an entry in the parent, if that succeeds it also adds an entry in memcached.
func (*MemcachedSessionHandler) DeleteEntriesForUser ¶
func (handler *MemcachedSessionHandler) DeleteEntriesForUser(user UserKeyType) (int64, error)
DeleteEntriesForUser invalidates ALL entries in memcached by creating a new random number. After that it calls DeleteEntriesForUser on the parent.
func (*MemcachedSessionHandler) DeleteInvalidKeys ¶
func (handler *MemcachedSessionHandler) DeleteInvalidKeys() (int64, error)
DeleteInvalidKeys only calls DeleteInvalidKeys on the parent.
func (*MemcachedSessionHandler) DeleteKey ¶
func (handler *MemcachedSessionHandler) DeleteKey(key string) error
DeleteKey first deletes the entry from memcached and then from the parent.
func (*MemcachedSessionHandler) GetData ¶
func (handler *MemcachedSessionHandler) GetData(key string) (*SessionKeyData, error)
GetData works the following way: First lookup the entry in memcached, if this worked return the value. Otherwise we ask the parent. If lookup on the parent succeeds we add the entry in memcached as well.
func (*MemcachedSessionHandler) Init ¶
func (handler *MemcachedSessionHandler) Init() error
Init simply calls Parent.Init()
type MySQLSessionTemplate ¶
type MySQLSessionTemplate struct { }
MySQLSessionTemplate implements SQLSessionTemplate with MySQL queries.
func NewMySQLSessionTemplate ¶
func NewMySQLSessionTemplate() MySQLSessionTemplate
NewMySQLSessionTemplate returns a new MySQLSessionTemplate.
func (MySQLSessionTemplate) CreateQ ¶
func (t MySQLSessionTemplate) CreateQ() string
func (MySQLSessionTemplate) DeleteForUserQ ¶
func (t MySQLSessionTemplate) DeleteForUserQ() string
func (MySQLSessionTemplate) DeleteInvalidQ ¶
func (t MySQLSessionTemplate) DeleteInvalidQ() string
func (MySQLSessionTemplate) DeleteKeyQ ¶
func (t MySQLSessionTemplate) DeleteKeyQ() string
func (MySQLSessionTemplate) GetQ ¶
func (t MySQLSessionTemplate) GetQ() string
func (MySQLSessionTemplate) InitQ ¶
func (t MySQLSessionTemplate) InitQ() string
func (MySQLSessionTemplate) TimeFromScanType ¶
func (t MySQLSessionTemplate) TimeFromScanType(val interface{}) (time.Time, error)
TimeFromScanType for MySQL first checks if the value is already a time.Time (the driver has an option to enable this). If not it pasres the datetime in the format "2006-01-02 15:04:05".
type PasswordHandler ¶
type PasswordHandler interface { // GenerateHash generates a hash from the given password. GenerateHash(password []byte) ([]byte, error) // CheckPassword tests if the passwords are equal, can return an error // (something is wrong with the data, random engine...). This should still // be handled as failure but you may wish to preceed differently. CheckPassword(hashedPW, password []byte) (bool, error) // PasswordHashLength returns the length of the password hash. // The hashes must be of the same length, so this method // must return the length of the elements created with // GenerateHash. // For bcrypt the length is 60. PasswordHashLength() int }
PasswordHandler is an interface that knows three methods: Create a hash from a given (plaintext) password Compare a previously by this method generated hash and compare it to plaintext password. A function PasswordHashLength that returns the length of the password hashes. There is an implementation BcryptHandler, so you don't have to write one on your own, but you could!
type PostgresSessionTemplate ¶
type PostgresSessionTemplate struct{}
PostgresSessionTemplate ist an implementation of SQLSessionTemplate for psotgres.
func NewPostgresSessionTemplate ¶
func NewPostgresSessionTemplate() PostgresSessionTemplate
NewPostgresSessionTemplate returns a new PostgresSessionTemplate.
func (PostgresSessionTemplate) CreateQ ¶
func (t PostgresSessionTemplate) CreateQ() string
func (PostgresSessionTemplate) DeleteForUserQ ¶
func (t PostgresSessionTemplate) DeleteForUserQ() string
func (PostgresSessionTemplate) DeleteInvalidQ ¶
func (t PostgresSessionTemplate) DeleteInvalidQ() string
func (PostgresSessionTemplate) DeleteKeyQ ¶
func (t PostgresSessionTemplate) DeleteKeyQ() string
func (PostgresSessionTemplate) GetQ ¶
func (t PostgresSessionTemplate) GetQ() string
func (PostgresSessionTemplate) InitQ ¶
func (t PostgresSessionTemplate) InitQ() string
func (PostgresSessionTemplate) TimeFromScanType ¶
func (t PostgresSessionTemplate) TimeFromScanType(val interface{}) (time.Time, error)
type RedisSessionHandler ¶
type RedisSessionHandler struct { // Client is the client to connect to redis. Client *redis.Client // SessionPrefix is the prefix that gets appended to all entries in redis // that contain session keys. // Defaults to "skey:" in NewRedisSessionHandler. // UserPrefix is the prefix that gets appended to all entries in redis // that contain user sets. // Defaults to "usessions:" in NewRedisSessionHandler. SessionPrefix, UserPrefix string // ConvertUser is the function used to transform the string representation // of the user identification back to its original type. // The default assumes uint64. ConvertUser func(val string) (interface{}, error) }
RedisSessionHandler is a session Handler using redis. This works the following way: All session keys are added to redis in the form "skey:<key>" Users are stored as strings so the same as for memcached applies: To retrieve the user correctly for your type you have to define a different ConvertUser method. The default one assumes uint64. Also for each user we store a set of the keys associated with the user. These entries are stored in the form "usessions:<user>". Every time a new entry is created we add the new key to this set and delete keys that were already deleted from this set. This way we take care that not all sessions, even those that were deleted by redis, will be stored forever. So old sessions get deleted either when the user set entry expires always set to the maximum of all stored sessions or the user logs in again. So at some point those entries will be deleted. This is some additional overhead in CreateEntry but should be absolutely fine.
All expiration stuff is handled by redis, so the DeleteInvalidKeys does actually nothing.
func NewRedisSessionHandler ¶
func NewRedisSessionHandler(client *redis.Client) *RedisSessionHandler
NewRedisSessionHandler creates a new RedisSessionHandler.
func (*RedisSessionHandler) CreateEntry ¶
func (handler *RedisSessionHandler) CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error)
CreateEntry adds a new entry. Note that in redis this also includes to add a new key to the user sessions set and removing keys from that set that no longer exist. So this may take some time longer than other methods (especially if a user has multiple sessions). But this is still fine if you don't add thousands of keys within seconds ;).
func (*RedisSessionHandler) DeleteEntriesForUser ¶
func (handler *RedisSessionHandler) DeleteEntriesForUser(user UserKeyType) (int64, error)
func (*RedisSessionHandler) DeleteInvalidKeys ¶
func (handler *RedisSessionHandler) DeleteInvalidKeys() (int64, error)
func (*RedisSessionHandler) DeleteKey ¶
func (handler *RedisSessionHandler) DeleteKey(key string) error
func (*RedisSessionHandler) GetData ¶
func (handler *RedisSessionHandler) GetData(key string) (*SessionKeyData, error)
func (*RedisSessionHandler) Init ¶
func (handler *RedisSessionHandler) Init() error
Init is a NOOP for for redis.
type RedisUserHandler ¶
type RedisUserHandler struct { // Client is the client used to connect to redis. Client *redis.Client // PwHandler is used for password encryption / decryption PwHandler PasswordHandler // UserPrefix gets appended before the username in the redis key. // Defaults to "user:" in NewRedisUserHandler. // NextIDKey is the ID that was last used to create a user. // This is the key that stores the value in redis. UserPrefix, NextIDKey string // The prefix used to store the mapping id -> user name UserIDPrefix string }
RedisUserHandler is a UserHandler that uses redis.
func NewRedisUserHandler ¶
func NewRedisUserHandler(client *redis.Client, pwHandler PasswordHandler) *RedisUserHandler
NewRedisUserHandler returns a new RedisUserHandler.
func (*RedisUserHandler) DeleteUser ¶
func (handler *RedisUserHandler) DeleteUser(userName string) error
func (*RedisUserHandler) GetUserBaseInfo ¶
func (handler *RedisUserHandler) GetUserBaseInfo(userName string) (*BaseUserInformation, error)
func (*RedisUserHandler) GetUserID ¶
func (handler *RedisUserHandler) GetUserID(userName string) (uint64, error)
func (*RedisUserHandler) GetUserName ¶
func (handler *RedisUserHandler) GetUserName(id uint64) (string, error)
func (*RedisUserHandler) Init ¶
func (handler *RedisUserHandler) Init() error
func (*RedisUserHandler) Insert ¶
func (handler *RedisUserHandler) Insert(userName, firstName, lastName, email string, plainPW []byte) (uint64, error)
func (*RedisUserHandler) ListUsers ¶
func (handler *RedisUserHandler) ListUsers() (map[uint64]string, error)
func (*RedisUserHandler) UpdatePassword ¶
func (handler *RedisUserHandler) UpdatePassword(userName string, plainPW []byte) error
type SQLSessionHandler ¶
type SQLSessionHandler struct { // DB is the database to operate on. DB *sql.DB // The queries required by this handler. InitQ, GetQ, CreateQ, DeleteForUserQ, DeleteInvalidQ, DeleteKeyQ string // TableName is the name of the session table, by default user_sessions. TableName string // UserIDType is the sql type that is used to store the user identifiaction. UserIDType string // KeySize is the length of the key strings. KeySize int // TimeFromScanType: See TimeFromScanType in the documentation of SQLSessionTemplate. TimeFromScanType func(val interface{}) (time.Time, error) // ForceUIDuint forces the user id to be of type uint64. // This field exists because most drivers stoer big ints simply as int, which // would mean we could never have more than 2^32 users. I Mean must people don't // have that but I thought it just to be thorough to enforce unsinged ints. ForceUIDuint bool // contains filtered or unexported fields }
SQLSessionHandler is an implementation of SessionHandler that uses a predinfed set of SQL queries. These queries are generated in NewSQLSessionHandler and stored in strings here. The reason we do that is that SQLSessionTemplate uses placeholders and we want to avoid calling fmt.Sprintf on every query. So now given all the details we simply generate the queries from the template once in NewSQLSessionHandler and replace the table name and key size once. The handler also requires the sql.DB database.
func NewMySQLSessionHandler ¶
func NewMySQLSessionHandler(db *sql.DB, tableName, userIDType string) *SQLSessionHandler
NewMYSQLSessionHandler returns a new SQLSessionHandler that uses MySQL.
func NewPostgresSessionHandler ¶
func NewPostgresSessionHandler(db *sql.DB, tableName, userIDType string) *SQLSessionHandler
NewPostgresSessionHandler returns a new SQLSessionHandler using postgres. It changes the default value of userIDType (the NewSQLSessionHandler uses BIGINT UNSIGNED NOT NULL). In postgres there is no unsigned keyword, so we use "BIGINT NOT NULL" as default.
func NewSQLSessionHandler ¶
func NewSQLSessionHandler(db *sql.DB, t SQLSessionTemplate, tableName, userIDType string, lockDB bool) *SQLSessionHandler
NewSQLSessionHandler compiles the query template with the given information. tableName is the name of the SQL table, if set to "" it defaults to "user_sessions". userIDType is the SQL user identifiaction type, if set to "" it defaults to "BIGINT UNSIGNED NOT NULL".
The lockDB argument is used for sqlite3 (and maybe other drivers): sqlite3 does not support writing from multiple goroutines and thus the database has to be locked. If set to true a mutex will be used to synchronize access to the database.
See documentation of SQLSessionHandler for more details.
func NewSQLite3SessionHandler ¶
func NewSQLite3SessionHandler(db *sql.DB, tableName, userIDType string) *SQLSessionHandler
NewSQLite3SessionHandler returns a new SQLSessionHandler that uses sqlite3.
func (*SQLSessionHandler) CreateEntry ¶
func (c *SQLSessionHandler) CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error)
func (*SQLSessionHandler) DeleteEntriesForUser ¶
func (c *SQLSessionHandler) DeleteEntriesForUser(user UserKeyType) (int64, error)
func (*SQLSessionHandler) DeleteInvalidKeys ¶
func (c *SQLSessionHandler) DeleteInvalidKeys() (int64, error)
func (*SQLSessionHandler) DeleteKey ¶
func (c *SQLSessionHandler) DeleteKey(key string) error
func (*SQLSessionHandler) GetData ¶
func (c *SQLSessionHandler) GetData(key string) (*SessionKeyData, error)
func (*SQLSessionHandler) Init ¶
func (c *SQLSessionHandler) Init() error
type SQLSessionTemplate ¶
type SQLSessionTemplate interface { // InitQ returns a query to initialize the session database. InitQ() string // GetQ is a query to select the user identifiaction, the time the key was // created and the time the until the key is valid from the database given // the session key. GetQ() string // CreateQ inserts the user identifiaction, the key, the time the key was // created and the time until the key is valid (in that order) in the database. CreateQ() string // DeleteForUserQ deletes all entries for a given user identifiaction from the // database. DeleteForUserQ() string // DeleteInvalidQ must delete all invalid keys from the database. DeleteInvalidQ() string // DeleteKeyQ deletes the entry for a given key from the database. DeleteKeyQ() string // TimeFromScanType is a rather odd function, but time fields are handled // differently in different handlers and some handlers even have options to change // that behaviour. Therefor when we get a time field (via the Row.Scan or some // other scan function) we pass a pointer to an interface{} to it. // Whatever that type is depends on the handler. // Example: The MySQL driver by default gets the datetime field as string and has // an option to parse it as time.Time (though this is disabled by default). // So the MySQL implementation first checks if val is already of time.Time and // then returns this value. If it is not why try to read it as a string and parse // this string to a time.Time. Postgres uses time.Time already. TimeFromScanType(val interface{}) (time.Time, error) }
SQLSessionTemplate to generate queries for different SQL flavours such as MySQL or postgres. It must use certain placeholders for example for the table name or key length. See the MySQL implementation, it would be really cumbersome to document all the details.
type SQLUserHandler ¶
type SQLUserHandler struct { // SQLUserQueries are the queries used to access the database. *SQLUserQueries // DB is the database to execute the queries on. DB *sql.DB // PwHandler is used to encrypt / validate passwords. PwHandler PasswordHandler // contains filtered or unexported fields }
SQLUserHandler implements the UserHandler by executing queries as defined in an instance of SQLUserQueries.
func NewMySQLUserHandler ¶
func NewMySQLUserHandler(db *sql.DB, pwHandler PasswordHandler) *SQLUserHandler
NewMySQLUserHandler returns a new handler that uses MySQL.
func NewPostgresUserHandler ¶
func NewPostgresUserHandler(db *sql.DB, pwHandler PasswordHandler) *SQLUserHandler
NewPostgresUserHandler returns a new handler that uses postgres.
func NewSQLUserHandler ¶
func NewSQLUserHandler(queries *SQLUserQueries, db *sql.DB, pwHandler PasswordHandler, blockDB bool) *SQLUserHandler
NewSQLUserHandler returns a new SQLUserHandler given the queries and all the other information required. queries are the queries used to access the database.
db is the database to execute the queries on.
pwHandler is used to encrypt / validate passwords. Set this to nil if you want to use the default handler (bcrypt with cost 13).
blockDB should be set to true if your database does not support access to the database by different goroutines. This is for example an issue with sqlite3. I'm not very happy to have it here since I think that's the job of the database driver, but we need it until there's a safe implementation of sqlite3. If it is set to true access to the database will be controlled with a mutex. For MySQL and postgres there is no need for this, the drivers handle this.
func NewSQLite3UserHandler ¶
func NewSQLite3UserHandler(db *sql.DB, pwHandler PasswordHandler) *SQLUserHandler
NewSQLite3UserHandler returns a new handler that uses sqlite3. Note that sqlite3 is really slow with this stuff!
func (*SQLUserHandler) DeleteUser ¶
func (handler *SQLUserHandler) DeleteUser(username string) error
func (*SQLUserHandler) GetUserBaseInfo ¶
func (handler *SQLUserHandler) GetUserBaseInfo(userName string) (*BaseUserInformation, error)
getUserInfoQ := "SELECT id, first_name, last_name, email, is_active, last_login FROM users WHERE id=?"
func (*SQLUserHandler) GetUserID ¶
func (handler *SQLUserHandler) GetUserID(userName string) (uint64, error)
func (*SQLUserHandler) GetUserName ¶
func (handler *SQLUserHandler) GetUserName(id uint64) (string, error)
func (*SQLUserHandler) Init ¶
func (handler *SQLUserHandler) Init() error
func (*SQLUserHandler) Insert ¶
func (handler *SQLUserHandler) Insert(userName, firstName, lastName, email string, plainPW []byte) (uint64, error)
func (*SQLUserHandler) ListUsers ¶
func (handler *SQLUserHandler) ListUsers() (map[uint64]string, error)
func (*SQLUserHandler) UpdatePassword ¶
func (handler *SQLUserHandler) UpdatePassword(username string, plainPW []byte) error
type SQLUserQueries ¶
type SQLUserQueries struct { // PwLength is the length of the database hashes stored in // the database, needed to initialize the database with the // correct length. PwLength int // InitQuery is the query to generate the "users" table. // It should take care take when executing this command // no error is returned if the table already exists. // It should take care to adjust the length of the password // field to the PwLength. InitQuery string // InsertQuery is a query to insert a user to the default // scheme. // It must use placeholders (? in MySQL, $i in postgre) // for the information that will be stored. // The values are passed in the following order: // username, first_name, last_name, email, password, is_active, last_login // username, first_name, last_name, email are of type string, // password is of type []byte, is_active of type bool // and last_login of type time.Time. InsertQuery string // ValidateQuery must be a query that selects exactly // two values: the id and the password column given // the username. // You must use one placeholder that gets replaced by the // username. Example in MySQL: // "SELECT id, password FROM users WHERE username = ?" ValidateQuery string // UpdatePasswordQuery is the query to update the password for a given username. UpdatePasswordQuery string // ListUsersQuery is the query to select all available users. // // New in version v0.4 ListUsersQuery string // GetUsernameQ is the query used to get the user name given an id. // // New in version v0.4 GetUsernameQ string // DeleteUserQ is used to delete the user give the user name. // // New in version v0.4 DeleteUserQ string // GetUserInfoQuery is the query to get the information for a given username // from the default scheme. // // New in version v0.5 GetUserInfoQuery string // GetIDQuery is the query used to get the id given a username. // // New in version v0.6 GetIDQuery string // TimeFromScanType is used to transform database time entries to // gos time. See SQLSessionHandler for details. // Defaults to a function that first checks if the value is already a time.Time // and otherwise parses the value as a string in NewSQLUserQueries. // // New in version v0.5 TimeFromScanType func(val interface{}) (time.Time, error) }
SQLUserQueries stores several queries for working with users on SQL databases. These queries can be different for different SQL flavours and thus there different methods that create such an object, for example MySQLUserQueries. In general there is one database for all user information called "users". The default scheme that should be implemented looks as follows (in MySQL syntax):
CREATE TABLE IF NOT EXISTS users ( id SERIAL, username VARCHAR(150) NOT NULL, first_name VARCHAR(30) NOT NULL, last_name VARCHAR(30) NOT NULL, email VARCHAR(254), password CHAR(<PWLENGTH>), is_active BOOL, last_login DATETIME, PRIMARY KEY(id), UNIQUE(username) );
On the wiki there are more notes on how to alter this scheme: https://github.com/FabianWe/goauth/wiki/Manage-Users#the-default-user-scheme
func MySQLUserQueries ¶
func MySQLUserQueries(pwLength int) *SQLUserQueries
MySQLUserQueries provides queries to use with MySQL.
func PostgresUserQueries ¶
func PostgresUserQueries(pwLength int) *SQLUserQueries
PostgresUserQueries provides queries to use with postgres.
func SQLite3UserQueries ¶
func SQLite3UserQueries(pwLength int) *SQLUserQueries
SQLite3UserQueries provides queries to use with sqlite3.
type SQLite3SessionTemplate ¶
type SQLite3SessionTemplate struct { // most of the stuff is the same as for SQL, so we can actually simply // delegate it to this template and just define the new queries MySQLSessionTemplate }
SQLite3SessionTemplate is an implementation of SQLSessionTemplate using sqlite3 queries. Nearly all MySQL queries work, so we simply delegate it to a MySQLSessionTemplate and implement the different queries again.
func NewSQLite3SessionTemplate ¶
func NewSQLite3SessionTemplate() *SQLite3SessionTemplate
NewSQLite3SessionTemplate returns a new SQLite3SessionTemplate.
func (*SQLite3SessionTemplate) InitQ ¶
func (*SQLite3SessionTemplate) InitQ() string
type ScryptHandler ¶
ScryptHandler is a PasswordHandler that uses scrypt.
func NewScryptHandler ¶
func NewScryptHandler(params *scrypt.Params) *ScryptHandler
NewScryptHandler returns a new ScryptHandler that uses the defined parameters. Set to nil to use scryp.DefaultParams. You should know what you do however!
func (*ScryptHandler) CheckPassword ¶
func (handler *ScryptHandler) CheckPassword(hashedPW, password []byte) (bool, error)
CheckPassword checks if the plaintext password was used to create the hashedPW.
func (*ScryptHandler) GenerateHash ¶
func (handler *ScryptHandler) GenerateHash(password []byte) ([]byte, error)
GenerateHash generates the password hash using scrypt.
func (*ScryptHandler) PasswordHashLength ¶
func (handler *ScryptHandler) PasswordHashLength() int
PasswordHashLength returns the password length for scrypt.
type SessionController ¶
type SessionController struct { SessionHandler NumBytes int SessionName string }
SessionController uses a SessionHandler to query the storage and add additional functionality. It is used as the main anchorpoint for user authentication. It creates sessions called SessionName (the field in this struct) and stores in that session the key in session.Values["key"]. NumBytes is the length of the random byte slice, see GenRandomBase64 for details about this parameter.
func NewInMemoryController ¶
func NewInMemoryController() *SessionController
func NewMySQLSessionController ¶
func NewMySQLSessionController(db *sql.DB, tableName, userIDType string) *SessionController
NewMySQLSessionController returns a new SessionController that uses a MySQL database.
func NewPostgresSessionController ¶
func NewPostgresSessionController(db *sql.DB, tableName, userIDType string) *SessionController
NewPostgresSessionController returns a new SessionController using postgres. It changes the default value of userIDType (the NewSQLSessionHandler uses BIGINT UNSIGNED NOT NULL). In postgres there is no unsigned keyword, so we use "BIGINT NOT NULL" as default.
func NewSQLite3SessionController ¶
func NewSQLite3SessionController(db *sql.DB, tableName, userIDType string) *SessionController
NewSQLite3SessionController returns a SessionController that uses sqlite3.
func NewSessionController ¶
func NewSessionController(h SessionHandler) *SessionController
NewSessionController creates a new session controller given a SessionHandler, the size of the random byte slice If you use another key length or session name set the values after calling NewSessionController, i.e. controller.NumBytes = ... and controller.SessionName = ...
func (*SessionController) AddKey ¶
func (c *SessionController) AddKey(user UserKeyType, validDuration time.Duration) (*SessionKeyData, string, error)
AddKey adds a new entry to the storage. This function returns either nil, "" and some error if something went wrong or the SessionKeyData instance, the key that was used to identify this session and nil.
func (*SessionController) CreateAuthSession ¶
func (c *SessionController) CreateAuthSession(r *http.Request, store sessions.Store, user UserKeyType, validDuration time.Duration) (*SessionKeyData, string, *sessions.Session, error)
CreateAuthSession will create a new session and add it to the underlying storage. It returns the data that was stored for the key, the generated key the goriall session the value was stored in and any error. If err != nil you should always consider it as a failure and assume that something went wrong on your server (internal server error). It will return the session even if err != nil and we didn't store the key, but that is not really important since you should always handle it as an error.
It will set the session.MaxAge to the correct value, but again will not call session.Save!
func (*SessionController) DeleteEntriesDaemon ¶
func (c *SessionController) DeleteEntriesDaemon(sleep time.Duration, ctx context.Context, reportErr bool)
DeleteEntriesDaemon starts a goroutine that runs forever and deletes invalid keys from the underlying storage.
The sleep parameter specifies how often entries should be deleted. Something reasonable would be for example to do this every day.
When the daemon gets started it immediately deletes invalid keys, so make sure you start it after calling init.
The context parameter can be set to nil and the daemon runs forever. If it is set to a context however it will listen on the context.Done channel and stop once it receives a stop signal. See the wiki for an example.
func (*SessionController) EndSession ¶
EndSession deletes the key stored in session.Values from the underlying storage. If the session does not contain an auth key it will not return an, i.e. if the session is not an auth session we can't look up the key. It will then return nil as an error. So an error is only returned if something really went wrong.
The session.MaxAge will be set to -1.
func (SessionController) GetKey ¶
func (c SessionController) GetKey(session *sessions.Session) (string, error)
GetKey retrieves the key from the session != nil. It returns the key in the session and nil if everything is ok, "" and ErrNotAuthSession if the session does not contain any auth information and "" and some err != nil if something else is wrong.
New in version v0.3
func (SessionController) GetSession ¶
func (c SessionController) GetSession(r *http.Request, store sessions.Store) (*sessions.Session, error)
GetSession tries to extract the auth session from the store, returns the session and nil if everything is ok and nil and an error if some occurred.
New in version v0.3
func (*SessionController) ValidateSession ¶
func (c *SessionController) ValidateSession(r *http.Request, store sessions.Store) (*SessionKeyData, *sessions.Session, error)
ValidateSession validates the key that is stored in the session. This function will try to get a session that is called SessionName (so usually the session "user-auth"). If an error occurred while trying to get the session it returns nil, nil and the error. If the found session does not have the required SessionKey value (the one we store the key that connects the gorilla session to our storage) this function returns nil, the session, and ErrNotAuthSession. If there is a user auth key stored in the session it will lookup the key in the underlying storage. It then returns nil, the session and KeyNotFoundErr if the key was not found in the storage (for example the key was deleted because it was not valid any more). If the key is still present in the storage but not valid any more InvalidKeyErr will be returned. Otherwise it returns the data found in the storage, the auth session object (for possible further processing) and nil as error. Otherwise it returns any error that may have happend while asking the underlying storage, such as database errors. So summarize:
If it returns a *SessionKeyData != nil everything is ok, you can get the user information from the SessionKeyData element.
If it returns nil as for the SessionKeyData something went wrong: (1) Something was wrong with the store (2) err == NotAuthSessionErr no authentication information was found, so probably the user has to log in and create a new sessionn (3) err == KeyNotFoundErr auth information was provided, but the key was not found, so either someone tried a random key or the session of the user simply expired and was therefore deleted from storage (4) err == InvalidKeyErr the key was still found in the database but is not valid any more, so probably the user hast to login again.
This method will automatically update the session.MaxAge to the time the key is still considered valid. If the key is invalid it will set the MaxAge to -1.
This method will not call session.Save!
See examples for how to use this method.
type SessionHandler ¶
type SessionHandler interface { // Init initializes the storage s.t. it is ready for use. This could be for // example a create table statement. You should however not overwrite // any existing data (if you have any). // For example only create a table if it does not already exist. // This method should be called each time you start your program. Init() error // GetData is a function to get the user data for a given key. // It should return nil and KeyNotFoundErr if the key was not found // and nil and some other error in case something went wrong during lookup. // If an err != nil is returned it must always return *SessionKeyData != nil. GetData(key string) (*SessionKeyData, error) // CreateEntry creates a new entry for the user with the given key. // It should call CurrentTimeKeyData and add this value. // Return an error if one occurred, maybe you should check if the // key already exists, however this is very unlikely. // Return error != nil only if the insertion really failed. // It returns the inserted data if everything went ok. CreateEntry(user UserKeyType, key string, validDuration time.Duration) (*SessionKeyData, error) // DeleteEntriesForUser removes all keys for the given user. // It returns the number of removed entries and returns an error if something // went wrong. DeleteEntriesForUser(user UserKeyType) (int64, error) // DeleteInvalidKeys removes all invalid keys from the storage. // Returns the number of removed keys and an error if something went wrong. DeleteInvalidKeys() (int64, error) // DeleteKey removes the key from the storage, return an error if one occurred. // It doesn't return an error if the key is invalid / not found! DeleteKey(key string) error }
SessionHandler is the interface to store and retrieve session keys and the associated SessionKeyData objects.
type SessionKeyData ¶
type SessionKeyData struct { // User is the user connected with a key. User UserKeyType // CreationTime is the time the key was created. CreationTime time.Time // ValidUntil is the time until the key is considered valid. ValidUntil time.Time }
SessionKeyData type is used as a result in a key lookup. It contains the user that corresponds to the session key and the time it was created and the time when the key becomes invalid. All methods that accept a *SessionKeyData should assume that the lookup failed if it is nil. The time should *always* be in UTC so the behaviour is consistent (and UTC is usually the easiest option). A key is considered valid if currentTime <= ValidUntil. You can use the helper functions KeyValid(now, ValidUntil) or KeyInvalid(now, ValidUntil), or directly use these constraints directly in your database queries.
func CurrentTimeKeyData ¶
func CurrentTimeKeyData(user UserKeyType, validDuration time.Duration) *SessionKeyData
CurrentTimeKeyData creates a new SessionKeyData object with the current time. It should be use by all handlers s.t. the behaviour is consistent. it creates the time object in UTC.
func NewSessionKeyData ¶
func NewSessionKeyData(user UserKeyType, creationTime, validUntil time.Time) *SessionKeyData
NewSessionKeyData creates a new SessionKeyData instance with the given values. If you want to create a new SessionKeyData object to insert it somewhere you should use CurrentTimeKeyData for consistent behaviour.
type UserHandler ¶
type UserHandler interface { // Init initializes the underlying storage. // Use this function every time you start your app, this // function must take sure that no error is produced if // invoked several times. // In SQL for example "CREATE TABLE IF NOT EXISTS" Init() error // Insert inserts a new user into the default scheme. // This function must return NoUserID and an error != nil // if any error occurred. // If the insert took place it always returns an error == nil. // However it can return nil as an error and NoUserID, in this case the // database doesn't support an immediate lookup for the newly inserted id // (sqlite3 and MySQL seem to support this though, postgres not). // Note that an error is also raised if the username is already in use // (must be unique). Insert(userName, firstName, lastName, email string, plainPW []byte) (uint64, error) // Validate validates the given plaintext password with the hashed password // of the user in the storage. // If err != nil you should always consider the lookup as a failure. // The function returns NoUserID and ErrUserNotFound if the user was not // found. // If no error occurred you can check if the login was successful by checking // the returned user id: // On failure it returns NoUserID and on success the id of the user with // username. Validate(userName string, CleartextPwCheck []byte) (uint64, error) // UpdatePassword updates the password for a user. UpdatePassword(username string, plainPW []byte) error // ListUsers returns all users currently present in the storage (by id). // // New in version v0.4 ListUsers() (map[uint64]string, error) // GetUserName returns the username for a given id. // Returns "" and ErrUserNotFound if the id is not valid. // // New in version v0.4 GetUserName(id uint64) (string, error) // GetUserID returns the id for a given username. // If the user does not exist return ErrUserNotFound. // // New version v0.6 GetUserID(userName string) (uint64, error) // DeleteUser deletes the user with the given username. // If the user doesn't exist it will do nothing. // // New in version v0.4 DeleteUser(username string) error // GetUserBaseInfo returns the information for a given user in the default // scheme. // If you have a different scheme to manage your users this will probably // not work. // Returns ErrUserNotFound if the user doesn't exist. // // New in version v0.5 GetUserBaseInfo(userName string) (*BaseUserInformation, error) }
UserHandler is an interface to deal with the management of users. It should use a PasswordHandler for generating passwords to store.
type UserKeyType ¶
type UserKeyType interface{}
UserKeyType is a special type that is used for user keys. User keys can be for example strings (username) or ints (uid). It is used to indentify a user for example in database. So it should be something that can be stored in a database or in dicts. Note that if it's something more complex you must register it with gob.Register s.t. it can be stored in the session. See http://www.gorillatoolkit.org/pkg/sessions for example. "Basic" types such as int, string, ... work fine.