goauth

package module
v0.0.0-...-cd6c9c4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 11, 2017 License: MIT Imports: 20 Imported by: 0

README

goauth

Package goauth provides convinient functions to authenticate users, encrypt their passwords and create and check login sessions (via tokens).

The documentation of this package can be found on GoDoc.

License: MIT License

What is this package for

This package may help you if:

  • You wish to manage user sessions: A user logs in and stays logged in until his login session expires. This package creates a database and stores session keys in it. It helps you with the generation and validation of such keys. You can store them in a secure cookie for example. Currently supported for user sessions: MySQL, postgres, sqlite3 and redis.
  • Manage user accounts in a database: There are different ways to accomplish this, either by using a default scheme that is defined in this package our with your own scheme. This package takes care that user passwords are stored in a secure way using bcrypt or with onre more line of code scrypt. Supported storages: MySQL, postgres, sqlite3 and redis.

I wanted to develop some small Go web applications without a big framework or something like that but with user authentication and couldn't find a suitable and small library. So I've written this one by myself.

Current Version and Safety

User authentication and session management is very important and needs to be absolutely safe. I've written this package by myself and until now no one revised the code. If someone would do that I would be very happy!

The current release is version v0.5, I haven't really tested it in any project yet, but I'm going to do so. I will develop in a new branch v0.6 from now on and the master branch stays at version v0.5 for now. But I think there may be some changes I'll have to make once I really use this project, so I will merge v0.6 in the master pretty soon.

Please not that this package comes without any warranty: If you use it do it on your own risk.

Quickstart

Installation

Installation with go get github.com/FabianWe/goauth/ should do the trick. One important notice though: The bcrypt package still uses the old "golang.org/x/net/context" package. I tried to install it inside a docker container (so a "fresh" installation of Go) and it didn't work because the import can't be resolved in newer versions of Go. I tried even go tool fix -force context /go/src/golang.org/x/crypto/ (here is the problem). But the acme package uses a package from context that go fix can't fix... So in my docker installation I ended up simply removing the file (see Dockerfile). Of course this is not a suitable option. So you may have to install the old context package by checking out the project our something. I hope this issue resolves itself once the old imports are gone.

If you're planning to you sqlite (support for that should be out very soon) take care of the installation notice, I quote: "This package can be installed with the go get command

go get github.com/mattn/go-sqlite3

go-sqlite3 is cgo package. If you want to build your app using go-sqlite3, you need gcc. However, if you install go-sqlite3 with go install github.com/mattn/go-sqlite3, you don't need gcc to build your app anymore."

Where Do I Start?

The wiki of this project is a good starting point. It explains most of the basics. Also you should read the entation on GoDoc.

In order to work properly you need a good backend for your storage. There is an in memory implementation for user sessions, but this is not very efficient and also you loose all your data once you stop your program.

You should really use a database, such as MariadDB (or any other MySQL) or postgres. We also support sqlite3, but this is very slow for this stuff and so not a good choice. There is also a cached version with memcached with another backend (from v0.2 on). Since version v0.3 there is also a session handler using redis.

One important note: Since we use gorilla sessions you should take care of the advice in their docs: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!

For example use

http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))

Please find the copyright information on the wiki. goauth is distributed under the MIT License.

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

View Source
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
)
View Source
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
)
View Source
const (
	// RedisDateFormat is the format string that is used to format / parse
	// date strings in redis.
	RedisDateFormat = "2006-01-02 15:04:05"
)
View Source
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

View Source
var DefaultPWHandler = NewBcryptHandler(-1)

DefaultPWHandler is the default handler for password encryption / decription.

View Source
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.

View Source
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.

View Source
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.

View Source
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

func CurrentTime() time.Time

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

func DefaultTimeFromScanType(val interface{}) (time.Time, error)

DefaultTimeFromScanType is the default function to return database entries to a time.Time.

func GenRandomBase64

func GenRandomBase64(n int) (string, error)

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

func KeyInvalid(now, validUntil time.Time) bool

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.

func KeyValid

func KeyValid(now, validUntil time.Time) bool

KeyValid checks if a key is still valid. This is the case if validUntil <= now. 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 (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

func (*RedisUserHandler) Validate

func (handler *RedisUserHandler) Validate(userName string, cleartextPwCheck []byte) (uint64, 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

func (*SQLUserHandler) Validate

func (handler *SQLUserHandler) Validate(userName string, cleartextPwCheck []byte) (uint64, 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

type ScryptHandler struct {
	// Stores the parameters for scrypt.
	Params scrypt.Params
}

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

func (c *SessionController) EndSession(r *http.Request, store sessions.Store) error

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL