gopherbouncedb

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

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

Go to latest
Published: Sep 16, 2019 License: Apache-2.0 Imports: 13 Imported by: 1

README

gopherbouncedb

Data base support for the gopherbounce package

Documentation

Overview

Package gopherbouncedb provides a database interface for gopherbounce. It defines an interface for accessing users and provides a generic SQL implementation (without importing any database drivers).

Several implementations for different databases are available. Note that the API / interface description may change in the future if I feel that more functionality is required. So if you use your own implementation of the interfaces make sure to use a fixed version and upgrade if the interface changes.

It uses the same approach as the SQL package: Every implementation registers with a unique name (using Register) and then a new handler is created with with a config string that is implementation depended.

Index

Constants

View Source
const (
	// InvalidUserID is used when a user id is required but no user with the
	// given credentials was found.
	InvalidUserID = UserID(-1)
)
View Source
const (
	// SessionKeyBytes is the length of session random keys in bytes.
	// Session keys are encoded in base64 which results in strings of length 39.
	SessionKeyBytes = 29
)
View Source
const (
	// SpecialChars defines the special characters that must be included in a password
	// to pass the SpecialCharacterClass.
	SpecialChars = "~!@#$%^&*()+=_-{}[]\\|:;?/<>,"
)

Variables

View Source
var (
	// DefaultUserRowNames maps the fields from UserModel (as strings)
	// to the default name of a sql row.
	DefaultUserRowNames = map[string]string{
		"ID":          "id",
		"FirstName":   "first_name",
		"LastName":    "last_name",
		"Username":    "username",
		"EMail":       "email",
		"Password":    "password",
		"IsActive":    "is_active",
		"IsSuperUser": "is_superuser",
		"IsStaff":     "is_staff",
		"DateJoined":  "date_joined",
		"LastLogin":   "last_login",
	}

	// DefaultSessionRowNames maps the fields from SessionEntry (as strings)
	// to the default name of a sql row.
	DefaultSessionRowNames = map[string]string{
		"User":       "user",
		"Key":        "session_key",
		"ExpireDate": "expire_date",
	}
)
View Source
var (
	ErrEmptyUsername          = errors.New("no username given")
	ErrEmptyEmail             = errors.New("no EMail given")
	ErrEmptyPassword          = errors.New("no password set")
	ErrInvalidEmailSyntax     = errors.New("invalid syntax in email")
	ErrUsernameTooLong        = errors.New("username is longer than 150 characters")
	ErrPasswordTooLong        = errors.New("password is longer than 270 characters")
	ErrEmailTooLong           = errors.New("email is longer than 254 characters")
	ErrFirstNameTooLong       = errors.New("first name is longer than 50 characters")
	ErrLastNameTooLong        = errors.New("last name is longer than 150 characters")
	ErrInvalidUsernameSyntax  = errors.New("invalid username syntax")
	ErrInvalidFirstNameSyntax = errors.New("invalid first name")
	ErrInvalidLastNameSyntax  = errors.New("invalid last name")
)

These variables define errors returned by some of the validators.

View Source
var (
	// EmailRegexp is used to verify that an email is valid.
	// It is the python version taken from https://emailregex.com/
	EmailRx = regexp.MustCompile(`(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)`)
)
View Source
var (
	// UsernameSyntaxRx tries to implement the following syntax for user names:
	// First a alphabetic symbol (a-zA-Z), followed by a sequence of chars, dots
	// points and numbers.
	// But it is not allowed to end with an underscore or dot.
	// Also after a dot or underscore no dot or underscore is allowed.
	// It does however not check the length limits.
	UsernameRx = regexp.MustCompile(`^[a-zA-Z]([a-zA-Z0-9]|[_.][a-zA-Z0-9])*?$`)
)

Functions

func CheckEmailMaxLen

func CheckEmailMaxLen(email string) error

CheckEmailMaxLen tests if the email length is not longer than the allowed length (254 chars).

func CheckFirstNameMaxLen

func CheckFirstNameMaxLen(name string) error

CheckFirstNameMaxLen tests if the name is not longer than the allowed length (50 chars).

func CheckFirstNameSyntax

func CheckFirstNameSyntax(name string) error

CheckFirstNameSyntax tests if all chars of the name are a unicode letter (class L).

func CheckLastNameMaxLen

func CheckLastNameMaxLen(name string) error

CheckLastNameMaxLen tests if the name is not longer than the allowed length (150 chars).

func CheckLastNameSyntax

func CheckLastNameSyntax(name string) error

CheckLastNameSyntax tests if all chars of the name are a unicode letter (class L).

func CheckPasswordHashMaxLen

func CheckPasswordHashMaxLen(password string) error

CheckPasswordHashMaxLen tests if the password hash length is not longer than the allowed length (270 chars).

func CheckUsernameMaxLen

func CheckUsernameMaxLen(username string) error

CheckUsernameMaxLen tests if the username is not longer than the allowed length (150 chars).

func CheckUsernameSyntax

func CheckUsernameSyntax(username string) error

CheckUsernameSyntax tests if the username matches the following syntax: First a alphabetic symbol (a-zA-Z), followed by a sequence of chars, dots points and numbers. But it is not allowed to end with an underscore or dot. Also after a dot or underscore no dot or underscore is allowed. It does however not check the length limits.

func ClassCounter

func ClassCounter(classes []RuneClass, s string) int

ClassCounter counts how many classes are are contained in the input string. That is if a class matches a single rune in s it is considered contained in the input.

func DigitClass

func DigitClass(r rune) bool

DigitClass tests if the rune is a number (0 - 9).

func GenSessionKey

func GenSessionKey() (string, error)

GenSessionKey returns a new cryptographically secure random key. It uses 29 random bytes (as defined in SessionKeyBytes). The random bytes are base64 encoded which results in strings of length 39.

func IsEmailSyntaxValid

func IsEmailSyntaxValid(email string) error

IsEmailSyntaxValid tests if the email is syntactically correct. It does however not check the length of the email.

func LowerLetterClass

func LowerLetterClass(r rune) bool

LowerLetterClass tests if 'a' ≤ r ≤ 'z'.

func RetrySessionInsert

func RetrySessionInsert(storage SessionStorage, session *SessionEntry, numTries int) error

RetrySessionInsert tries to insert a session key multiple times.

If a key insertion failed because the key already exists we can use this method to create new keys and try the insert again. A key collision should not usually fail, thus this is function only exists as a precaution.

This method will return all other errors (database connection failed etc.) directly without retrying. If the insertion failed numTries times an error of type RetryInsertErr is returned which contains all insertion errors.

func SpecialCharacterClass

func SpecialCharacterClass(r rune) bool

SpecialCharacterClass tests if the rune is a special char from SpecialChars.

func UpperLetterClass

func UpperLetterClass(r rune) bool

UpperLetterClass tests if 'A' ≤ r ≤ 'Z'.

func VerifiyNameExists

func VerifiyNameExists(u *UserModel) error

VerifiyNameExists tests if the user has a username. It is a UserVerifier.

func VerifyEmailExists

func VerifyEmailExists(u *UserModel) error

VerifyEmailExists tests if the user has an email. It is a UserVerifier.

func VerifyEmailSyntax

func VerifyEmailSyntax(u *UserModel) error

VerifyEmailSyntax tests if the user email is syntactically. It does however not check the length of the email.

func VerifyPasswordExists

func VerifyPasswordExists(u *UserModel) error

VerifyPasswordExists tests if the password is not empty. It is a UserVerifier.

func VerifyStandardUserMaxLens

func VerifyStandardUserMaxLens(u *UserModel) error

VerifyStandardUserMaxLens tests the username, password hash, email, first name and last name for their max lengths and returns nil only iff all tests passed.

Types

type AmbiguousCredentials

type AmbiguousCredentials string

AmbiguousCredentials is an error returned when the update of an user would lead to an inconsistent database state, such as email already in use.

func NewAmbiguousCredentials

func NewAmbiguousCredentials(message string) AmbiguousCredentials

NewAmbiguousCredentials returns a new AmbiguousCredentials given the cause.

func (AmbiguousCredentials) Error

func (e AmbiguousCredentials) Error() string

Error returns the error string.

type MemdummySessionStorage

type MemdummySessionStorage struct {
	// contains filtered or unexported fields
}

func NewMemdummySessionStorage

func NewMemdummySessionStorage() *MemdummySessionStorage

func (*MemdummySessionStorage) CleanUp

func (s *MemdummySessionStorage) CleanUp(referenceDate time.Time) (int64, error)

func (*MemdummySessionStorage) Clear

func (s *MemdummySessionStorage) Clear()

func (*MemdummySessionStorage) DeleteForUser

func (s *MemdummySessionStorage) DeleteForUser(user UserID) (int64, error)

func (*MemdummySessionStorage) DeleteSession

func (s *MemdummySessionStorage) DeleteSession(key string) error

func (*MemdummySessionStorage) GetSession

func (s *MemdummySessionStorage) GetSession(key string) (*SessionEntry, error)

func (*MemdummySessionStorage) InitSessions

func (s *MemdummySessionStorage) InitSessions() error

func (*MemdummySessionStorage) InsertSession

func (s *MemdummySessionStorage) InsertSession(session *SessionEntry) error

type MemdummyUserStorage

type MemdummyUserStorage struct {
	// contains filtered or unexported fields
}

MemdummyUserStorage is an implementation of UserStorage using an in-memory storage. It should never be used in production code, instead it serves as a reference implementation and can be used for test cases.

func NewMemdummyUserStorage

func NewMemdummyUserStorage() *MemdummyUserStorage

NewMemdummyUserStorage returns a new storage without any data.

func (*MemdummyUserStorage) Clear

func (s *MemdummyUserStorage) Clear()

func (*MemdummyUserStorage) DeleteUser

func (s *MemdummyUserStorage) DeleteUser(id UserID) error

func (*MemdummyUserStorage) GetUser

func (s *MemdummyUserStorage) GetUser(id UserID) (*UserModel, error)

func (*MemdummyUserStorage) GetUserByEmail

func (s *MemdummyUserStorage) GetUserByEmail(email string) (*UserModel, error)

func (*MemdummyUserStorage) GetUserByName

func (s *MemdummyUserStorage) GetUserByName(username string) (*UserModel, error)

func (*MemdummyUserStorage) InitUsers

func (s *MemdummyUserStorage) InitUsers() error

func (*MemdummyUserStorage) InsertUser

func (s *MemdummyUserStorage) InsertUser(user *UserModel) (UserID, error)

func (*MemdummyUserStorage) ListUsers

func (s *MemdummyUserStorage) ListUsers() (UserIterator, error)

func (*MemdummyUserStorage) UpdateUser

func (s *MemdummyUserStorage) UpdateUser(id UserID, newCredentials *UserModel, fields []string) error

type NoSuchSession

type NoSuchSession string

NoSuchSession is the error returned if the lookup of a session failed because such a session does not exist.

func NewNoSuchSession

func NewNoSuchSession(message string) NoSuchSession

NewNoSuchSession returns a new NoSuchSession error given the message.

func NewNoSuchSessionKey

func NewNoSuchSessionKey(key string) NoSuchSession

NewNoSuchSessionKey returns a new NoSuchSession error given the key that doesn't exist.

func (NoSuchSession) Error

func (e NoSuchSession) Error() string

Error returns the error message.

type NoSuchUser

type NoSuchUser string

NoSuchUser is an error returned when the lookup of a user failed because no entry for that user exists.

func NewNoSuchUser

func NewNoSuchUser(message string) NoSuchUser

NewNoSuchUser returns a new NoSuchUser given the cause.

func NewNoSuchUserID

func NewNoSuchUserID(id UserID) NoSuchUser

NewNoSuchUserID returns a new NoSuchUser error with a given id.

func NewNoSuchUserMail

func NewNoSuchUserMail(email string) NoSuchUser

NewNoSuchUserMail returns a new NoSuchUser error with a given email.

func NewNoSuchUserUsername

func NewNoSuchUserUsername(username string) NoSuchUser

NewNoSuchUserUsername returns a new NoSuchUser error with a given user name.

func (NoSuchUser) Error

func (e NoSuchUser) Error() string

Error returns the error string.

type NotSupported

type NotSupported struct {
	// contains filtered or unexported fields
}

NotSupported is the error returned when inserting / updating a user and getting LastInsertID or RowsAffected is not supported by the driver.

func NewNotSupported

func NewNotSupported(initial error) NotSupported

NewNoInsertID returns a new NoInsertID.

func (NotSupported) Error

func (e NotSupported) Error() string

Error returns the error string.

type PasswordVerifier

type PasswordVerifier func(pw string) bool

PasswordVerifier is any function that checks if a given password meets certain criteria, for example min length or contains at least one character from a certain range.

func PWContainsAll

func PWContainsAll(classes []RuneClass) PasswordVerifier

PWContainsAll is a generator that returns a PasswordVerifier.

The returned verifier tests if the password contains at least one char of each class.

func PWContainsAtLeast

func PWContainsAtLeast(classes []RuneClass, k int) PasswordVerifier

PWContainsAtLeast is a generator that returns a PasswordVerifier.

The returned verifier tests if the password contains at least one char from k different classes.

func PWLenVerifier

func PWLenVerifier(minLen, maxLen int) PasswordVerifier

PWLenVerifier is a generator for a PasswordVerifier that checks the length of the password.

The password must have at least length minLen and at most length maxLen. To disable any of the checks pass -1.

type RetryInsertErr

type RetryInsertErr []error

RetryInsertErr is returned if several inserts failed (usually with RetrySessionInsert) and all generated keys were invalid. This should never happen in general.

func NewRetryInsertErr

func NewRetryInsertErr(errs []error) RetryInsertErr

NewRetryInsertErr returns a new RetryInsertErr given the accumulated insert errors.

func (RetryInsertErr) Error

func (e RetryInsertErr) Error() string

Error returns the error message.

type RollbackErr

type RollbackErr struct {
	// contains filtered or unexported fields
}

RollbackErr is returned when the rollback operation of a transaction failed. This is usually not a good sign: There was an error before and we tried to rollback the operations already performed. But this rollback resulted in yet another error.

func NewRollbackErr

func NewRollbackErr(initialErr, rollbackErr error) RollbackErr

NewRollbackErr returns a new RollbackErr given the cause that lead to calling rollback (initialErr) and the rollback error (rollbackErr) itself.

func (RollbackErr) Error

func (e RollbackErr) Error() string

Error returns the error string.

type RuneClass

type RuneClass func(r rune) bool

RuneClass is any set of runes identified by a function.

type SQLBridge

type SQLBridge interface {
	// TimeScanType should return the type that is used to retrieve
	// time.Time objects from the database.
	// When we retrieve for example a user a variable is created with this function
	// and then passed to the scan method to retrieve a time from the database.
	// Thus it should return a pointer s.t. the database Scan method can
	// assign it the actual value.
	//
	// After the retrieving with Scan is done this object is converted to a time.Time
	// with ConvertTimeScanType.
	//
	// The easiest implementation is to just return a *time.Time.
	TimeScanType() interface{}
	// ConvertTimeScanType is used to transform the values that were processed with
	// a variable from TimeScanType, thus this function can assume that val is of
	// the type returned by TimeScanType.
	// However, type checking should be done and an error returned if this is not the case.
	// Thus the workflow for retrieving time.Time elements from the database is as followś:
	// Call database Scan method with the value retrieved from TimeScanType.
	// This value is then converted to an actual time.Time with this function.
	//
	// For example if TimeScanType returns x = *time.Time this method can just return
	// *x.
	ConvertTimeScanType(val interface{}) (time.Time, error)
	// IsDuplicateInsert checks if the error is an error that was caused by inserting
	// a duplicate entry.
	//
	// Various database drivers have their own way of defining such a key error, for
	// example by an error code or a specific error type.
	IsDuplicateInsert(err error) bool
	// IsDuplicateUpdate is used the same way as IsDuplicateInsert, but is used in
	// update operations.
	// Usually database drivers don't distinguish between different key errors
	// on insert/update and thus in most cases it works the same way as IsDuplicateInsert.
	IsDuplicateUpdate(err error) bool
	// ConvertTime has the same idea as TimeScanType: Transform an entry of time.Time
	// to a driver specific time that can be used for this driver.
	// Whereas TimeScanType is used for Scan the value returned by ConvertTime
	// is used on inserts and updates.
	// For example a driver may not be able to insert a time.Time value into the database
	// directly. Instead it may have to be converted to a string instead.
	//
	// In contrast to TimeScanType it should however (in general) not return a pointer.
	// A driver that can insert time.Time directly should simply returned the supplied
	// argument of time.Time.
	ConvertTime(t time.Time) interface{}
}

SQLBridge is a type that is used to abstract away certain driver specific implementation problems.

This might not be the "best" approach, but it is one that works. Some of the things in database/sql are not very generic. For example various drivers handle time.Time differently. Also for certain errors (such as duplicate key errors) there are no generic error types. To have more detailed control this bridge is used to deal with these problems.

type SQLSessionStorage

type SQLSessionStorage struct {
	SessionDB      *sql.DB
	SessionQueries SessionSQL
	SessionBridge  SQLBridge
}

func NewSQLSessionStorage

func NewSQLSessionStorage(db *sql.DB, queries SessionSQL, bridge SQLBridge) *SQLSessionStorage

func (*SQLSessionStorage) CleanUp

func (s *SQLSessionStorage) CleanUp(referenceDate time.Time) (int64, error)

func (*SQLSessionStorage) DeleteForUser

func (s *SQLSessionStorage) DeleteForUser(user UserID) (int64, error)

func (*SQLSessionStorage) DeleteSession

func (s *SQLSessionStorage) DeleteSession(key string) error

func (*SQLSessionStorage) GetSession

func (s *SQLSessionStorage) GetSession(key string) (*SessionEntry, error)

func (*SQLSessionStorage) InitSessions

func (s *SQLSessionStorage) InitSessions() error

func (*SQLSessionStorage) InsertSession

func (s *SQLSessionStorage) InsertSession(session *SessionEntry) error

type SQLTemplateReplacer

type SQLTemplateReplacer struct {
	// contains filtered or unexported fields
}

SQLTemplateReplacer is used to replace the meta variables in the queries of a UserSQL implementation. It basically maps these meta variable to their actual content and offers a method to apply the replacement.

The Apply method is safe to be called concurrently, all functions that in some way change the content are not safe to be called concurrently.

That is: First set the content and then use Apply as you see fit.

func DefaultSQLReplacer

func DefaultSQLReplacer() *SQLTemplateReplacer

DefaultSQLReplacer returns a new SQLTemplateReplacer that takes care that all variables mentioned in the documentation of UserSQL are mapped to their default values.

func NewSQLTemplateReplacer

func NewSQLTemplateReplacer() *SQLTemplateReplacer

NewSQLTemplateReplacer returns a new SQLTemplateReplacer with no replacements taking place. DefaultSQLReplacer should be used to generate a replacer with the default replacements taking place.

func (*SQLTemplateReplacer) Apply

func (t *SQLTemplateReplacer) Apply(templateStr string) string

Apply replaces all meta variables that are a key in the template string with their respective values.

func (*SQLTemplateReplacer) Delete

func (t *SQLTemplateReplacer) Delete(key string)

Delete removes an entry from the mapping. If the key is not present nothing happens.

func (*SQLTemplateReplacer) DeleteMany

func (t *SQLTemplateReplacer) DeleteMany(keys ...string)

DeleteMany deletes multiple keys from the mapping, it is more efficient than to call Delete for each entry. If a key is not present nothing happens.

func (*SQLTemplateReplacer) HasKey

func (t *SQLTemplateReplacer) HasKey(key string) bool

HasKey returns true if there exists an entry for the given key.

func (*SQLTemplateReplacer) Set

func (t *SQLTemplateReplacer) Set(key, value string)

Set sets the meta variable to a new value.

func (*SQLTemplateReplacer) SetMany

func (t *SQLTemplateReplacer) SetMany(oldnew ...string)

SetMany sets many key / value pairs. It should be a bit more efficient than calling Set for each entry. oldnew must be a sequence with entries of the form [KEY_ONE, VALUE_ONE, KEY_TWO, VALUE_TWO, ...].

All entries not mentioned in oldnew are not changed and not deleted.

It panics if given an odd number of arguments

func (*SQLTemplateReplacer) Update

func (t *SQLTemplateReplacer) Update(other *SQLTemplateReplacer)

Update updates the entries by updating the fields from another replacer. It works the same way as UpdateDict.

func (*SQLTemplateReplacer) UpdateDict

func (t *SQLTemplateReplacer) UpdateDict(mapping map[string]string)

UpdateDict is another way to update the key / value mapping. All entries contained in mapping are updated, all other entries are not changed and not deleted.

type SQLUserIterator

type SQLUserIterator struct {
	Rows   *sql.Rows
	Bridge SQLBridge
}

func NewSQLUserIterator

func NewSQLUserIterator(rows *sql.Rows, bridge SQLBridge) *SQLUserIterator

func (*SQLUserIterator) Close

func (it *SQLUserIterator) Close() error

func (*SQLUserIterator) Err

func (it *SQLUserIterator) Err() error

func (*SQLUserIterator) HasNext

func (it *SQLUserIterator) HasNext() bool

func (*SQLUserIterator) Next

func (it *SQLUserIterator) Next() (*UserModel, error)

type SQLUserStorage

type SQLUserStorage struct {
	UserDB      *sql.DB
	UserQueries UserSQL
	UserBridge  SQLBridge
}

SQLUserStorage implements UserStorage by working with database/sql.

It does not rely on a specific driver and no driver is imported; it only uses methods like db.Scan or db.Execute.

In order to use your own implementation for these generic sql methods two things must be implemented: The queries to be used of type UserSQL and the database bridge of type SQLBridge.

func NewSQLUserStorage

func NewSQLUserStorage(db *sql.DB, queries UserSQL, bridge SQLBridge) *SQLUserStorage

NewSQLUserStorage returns a new SQLUserStorage.

func (*SQLUserStorage) DeleteUser

func (s *SQLUserStorage) DeleteUser(id UserID) error

func (*SQLUserStorage) GetUser

func (s *SQLUserStorage) GetUser(id UserID) (*UserModel, error)

func (*SQLUserStorage) GetUserByEmail

func (s *SQLUserStorage) GetUserByEmail(email string) (*UserModel, error)

func (*SQLUserStorage) GetUserByName

func (s *SQLUserStorage) GetUserByName(username string) (*UserModel, error)

func (*SQLUserStorage) InitUsers

func (s *SQLUserStorage) InitUsers() error

InitUsers executes all init queries in a single transaction.

func (*SQLUserStorage) InsertUser

func (s *SQLUserStorage) InsertUser(user *UserModel) (UserID, error)

func (*SQLUserStorage) ListUsers

func (s *SQLUserStorage) ListUsers() (UserIterator, error)

func (*SQLUserStorage) UpdateUser

func (s *SQLUserStorage) UpdateUser(id UserID, newCredentials *UserModel, fields []string) error

UpdateUser is a bit more complicated then the other functions. Its behavior is defined in the UserSQL documentation, but here's a small summary of what happens: If SupportsUserFields returns false the query call from UpdateUser(nil) is used and the arguments are given in the default order, that is username, email, ... and the id as the last element.

If SupportsUserFields returns true only the arguments as defined in fields are given (in the order as tehy're mentioned) and UpdateUser is called with these fields and must return a query that updates these fields. Again the user id is given as the last argument.

type SessionEntry

type SessionEntry struct {
	Key        string
	User       UserID
	ExpireDate time.Time
}

SessionEntry is an entry for a session to be stored in a database. It describes the user this session belongs to (by id) and a unique cryptographically secure random key. The ExpireDate describes how long the session is considered valid.

func NewSessionWithKey

func NewSessionWithKey(user UserID, expireDate time.Time) (*SessionEntry, error)

NewSessionWithKey returns a new SessionEntry and creates automatically a new session key. If an error is returned the session should not be used.

func (*SessionEntry) Copy

func (s *SessionEntry) Copy() *SessionEntry

SessionEntry returns a copy of another session entry.

func (*SessionEntry) IsValid

func (s *SessionEntry) IsValid(referenceDate time.Time) bool

IsValid returns true iff the session is considered valid. This function should be used to check if a session is valid. One databases it's generally a good idea not to iterate over all session and test whether it's still valid. So the definition of a valid session is that the reference date is before the ExpireDate of the session. Note: Thus if expireDate == referenceDate the sessionis considered invalid. But I think that should be okay, if we're in this range were it makes a difference it should not matter.

type SessionExists

type SessionExists string

SessionExists is the error returned if the insertion of a session failed because the key already exists (should rarely happen).

func NewSessionExists

func NewSessionExists(message string) SessionExists

NewSessionExists returns a new SessionExists error given the message.

func NewSessionExistsKey

func NewSessionExistsKey(key string) SessionExists

NewSessionExistsKey returns a new SessionExists error given the key that already existed in the datastore.

func (SessionExists) Error

func (e SessionExists) Error() string

Error returns the error message.

type SessionSQL

type SessionSQL interface {
	InitSessions() []string
	GetSession() string
	InsertSession() string
	DeleteSession() string
	CleanUpSession() string
	DeleteForUserSession() string
}

type SessionStorage

type SessionStorage interface {
	// InitSessions is called once to make sure all tables and indexes exist in the database.
	InitSessions() error
	// InsertSession inserts a new session to the datastore.
	// If the session key already exists it should an error of type SessionExists.
	InsertSession(session *SessionEntry) error
	// GetSession returns the session with the given key.
	// If no such session exists it should return an error of type NoSuchSession
	GetSession(key string) (*SessionEntry, error)
	// DeleteSession deletes the session with the given key.
	// If no such session exists this will not be considered an error.
	DeleteSession(key string) error
	// CleanUp should remove all entries that are not valid any more given the
	// reference date.
	// If a session is valid should be checked with SessionEntry.IsValid.
	// It returns the number of deletes entries.
	// If the cleanup worked successfully but the driver doesn't support the number of
	// affected entries it should return an error of type NotSupported.
	CleanUp(referenceDate time.Time) (int64, error)
	// DeleteForUser deletes all session for the given user id.
	// It returns the number of deleted entries.
	// If the delete worked successfully but the driver doesn't support the number of
	// affected entries it should return an error of type NotSupported.
	DeleteForUser(user UserID) (int64, error)
}

SessionStorage provides methods that are used to store and deal with auth session.

In general if a user gets deleted all the users' sessions should be deleted as well. Since we have to different interfaces there is no direct way of adapting this. However both storages interfaces are usually combined in a Storage, this way you might be able to adept to this. But it shouldn't be a big problem if a session for a non-existent user remains in the store.

type Storage

type Storage interface {
	UserStorage
	SessionStorage
}

Storage combines a user storage and a session storage.

type UserExists

type UserExists string

UserExists is an error returned when the creation of a user object failed because a user with the given credentials already exists.

func NewUserExists

func NewUserExists(message string) UserExists

NewUserExists returns a new NewUserExists given the cause.

func (UserExists) Error

func (e UserExists) Error() string

Error returns the error string.

type UserID

type UserID int64

UserID is the id of a user stored in a database.

type UserIterator

type UserIterator interface {
	HasNext() bool
	Next() (*UserModel, error)
	Err() error
	Close() error
}

UserIterator is a type used to iterate over user entries.

This might not be the best go-ish way, but it should do. The contract for using a user iterator is as follows:

If the iterator is retrieved without an error the Close() method of the iterator must be called (usually as a deferred function call). Before accessing an element with Next(), HasNext() must be called. Finally, a call to Err must be made in order to test if the iteration ended regularly or if there was an error.

type UserModel

type UserModel struct {
	ID          UserID
	FirstName   string
	LastName    string
	Username    string
	EMail       string
	Password    string
	IsActive    bool
	IsSuperUser bool
	IsStaff     bool
	DateJoined  time.Time
	LastLogin   time.Time
}

UserModel stores general information about a user, all these fields should be stored in the database.

FirstName, LastName, Username, EMail should be self-explaining. Password is the hash of the password (string). IsActive is used as an alternative to destroying an account. Because this could have some undesired effects it is preferred to just set the user to inactive instead of deleting the user object. IsSuperUser and IsStaff should be true if the user is an "admin" user / part of the staff. This was inspired by the Django user model. DateJoined and LastLogin should also be self-explaining. Note that LastLogin can be zero, meaning if the user never logged in LastLogin.IsZero() == true.

In general UserID, Username and EMail should be unique.

Because this model is usually stored in a database here is a summary of some conventions for the fields: The strings are usually varchars with the following maximum lengths: Username (150), password (270), EMail (254), FirstName (50), LastName(150). These properties can also be verified before inserting the user to a database with VerifyStandardUserMaxLens. The database implementations don't check that automatically, but the convenient wrappers I'm trying to implement will.

func AsUsersSlice

func AsUsersSlice(it UserIterator) ([]*UserModel, error)

AsUsersSlice takes a not-closed iterator an returns all elements as a slice. If some error happens it returns nil and the error. Errors from closing the iterator are not returned (ignored).

func (*UserModel) Copy

func (u *UserModel) Copy() *UserModel

Copy creates a copy of the user model and returns a new one with the same contens.

func (*UserModel) GetFieldByName

func (u *UserModel) GetFieldByName(name string) (val interface{}, err error)

GetFieldByName returns the value of the field given by its string name.

This helps with methods that for example only update certain fields. The key must be the name of one of the fields of the user model. If the key is invalid an error is returned.

type UserSQL

type UserSQL interface {
	// InitUsers returns a sequence of init actions.
	// They're all run on one transaction and rolled-back if one fails.
	// In this query als initialization should happen, usually something like
	// "create table if not exists" (or creating an index).
	InitUsers() []string
	// GetUser is the query to return a user with a given id.
	// It must select all fields from the user table in the following order:
	// id, user name, password, email, first name, last name, is superuser,
	// is staff, is active, date joined, last login.
	//
	// Exactly one element is passed to the query and that is the user id to look for.
	GetUser() string
	// GetUserByName does the same as GetUser but instead of an id gets a user name to
	// look for.
	GetUserByName() string
	// GetUserByEmail does the same as GetUser but instead of an id gets an email to
	// look for.
	// If the email is not unique this might lead to errors.
	GetUserByEmail() string
	// InsertUser inserts a new user into the database.
	//
	// The arguments parsed into Execute are the same once (and in the same order)
	// as in GetUser, except the id field (that is automatically generated).
	InsertUser() string
	// UpdateUser is used to update a user.
	// The query might depend on the fields which we want to update.
	// You don't have to support update by fields and just update all fields, even
	// those not given in fields. Just make sure to implement this in the
	// SupportsUserFields function.
	//
	// The sql implementation works as follows: If SupportsUserFields returns false
	// UpdateUser is always called with nil, meaning all fields must be updated.
	// If SupportsUserFields returns true then this function is called with the
	// actual fields and the returned statement updates should only those fields.
	//
	// Concerning in the arguments: In case len(fields) == 0 the same order as in GetUser,
	// except the id (this one can't be updated).
	// If fields is given the order of the arguments are in the same order as the fields.
	// The contents of fields are discussed in more detail in the documentation of the
	// UserStorage interface.
	// In all cases the id is passed as the last element.
	// This is the argument used usually in the WHERE clause and defines the user to
	// update by its id.
	//
	// A small example: If fields is nil: ... SET username=?, ... WHERE id=?.
	// The arguments are given in the order username, ..., id.
	//
	// If fields is given for example as ["LastName", "EMail"]:
	// ... SET last_name=?, email=? WHERE id=?.
	// The arguments are given in the order last_name, email, id.
	UpdateUser(fields []string) string
	// SupportsUserFields returns true if UpdateUser has the additional fields update
	// ability.
	// It's totally okay to return false and always update all values.
	// In this case UpdateUser always gets called with nil.
	SupportsUserFields() bool
	// DeleteUser removes a user from the database.
	// It is given a single argument, the user id.
	DeleteUser() string
	// ListUsers returns all users with a SELECT statement.
	ListUsers() string
}

UserSQL defines an interface for working with user queries in a sql database.

It is used in the generic SQLStorage to retrieve database specific queries. The queries may (and should) contain placeholders (not the queries returned by your implementation, see the note below). For example the table name might be changed, the default name for the user table is "auth_user". To be more flexible this, the table name can be changed. Thus the queries can contain a variable that gets replaced with the actual table name. This meta variable has the form $SOME_NAME$. The following variables are enabled by default: "$USERS_TABLE_NAME$": Name of the users table. Defaults to "auth_user". "$EMAIL_UNIQUE$": Specifies if the E-Mail should be unique. By default it is set to the string "UNIQUE". But it can be replaced by an empty string as well. This should be fine with most sql implementations. If not you might write your own implementation that does something different and does not use "$EMAIL_UNIQUE$".

The replacement of the meta variables should only done once during the initialization. A SQLTemplateReplacer is used to achieve this.

Important note: The queries returned by this implementation are not allowed to contain meta variables. A replacer is not run by default! Instead you have to create the queries with placeholders once (for example as constants) and then apply a replacer by yourself once to get rid of the placeholders. Of course you don't need to use this feature, but it keeps your tables more dynamic and allows more configuration. As an example you might look at one of the implementations, for example the driver. All queries exist as a const string with placeholders. Then a replacer is run once and the implementation only returns those strings. They also use other placeholders to be used for example with for dynamic update queries. The replacement of the fields variables is then done directly in the UpdateUser query.

type UserStorage

type UserStorage interface {
	// InitUsers should be called once to make sure all tables in the database exist etc.
	InitUsers() error
	// GetUser returns the user with the given id. If no such user exists it
	// should return nil and an error of type NoSuchUser.
	GetUser(id UserID) (*UserModel, error)
	// GetUserByName returns the user with the given name. If no such user exists
	// it should return nil and an error of type NoSuchUser.
	GetUserByName(username string) (*UserModel, error)
	// GetUserByEmail returns the user with the given email. If no such user
	// exists it should return nil and an error of type NoSuchUser.
	GetUserByEmail(email string) (*UserModel, error)
	// InsertUser inserts a new user to the store. It should set the id of the
	// provided user model to the new id and return that id as well.
	// If an user with the given credentials already exists (name or email, depending on which are enforced to be
	// unique) it should return InvalidUserID and an error of type UserExists.
	// The fields DateJoined is set to the current date (in UTC) and LastLogin is set to
	// the time zero value.
	// If the underlying driver does not support to get the last insert id
	// via LastInsertId InvalidUserID and an error of type NotSupported should be returned.
	// This indicates that the insertion took place but the id could not be obtained.
	InsertUser(user *UserModel) (UserID, error)
	// UpdateUser update the user with the given information, that is it uses
	// the user id to find the user and stores all new information.
	// fields is an optional argument which contains the fields to update.
	// The fields must be a subset of the UserModel attributes.
	// If given only these fields will be updated - user id is not allowed to be changed.
	// If fields is empty (nil or empty slice) all fields will be updated.
	// If the change of values would violate a consistency constraint (email or username already in use) it should not
	// update any fields but instead return an error of type AmbiguousCredentials.
	//
	// Updating a non-existing user should not lead to any error (returns nil).
	//
	// Short summary: If nil is returned everything is okay, but the user may not exist.
	// If any of the new values violates a database constraint (such as unique) AmbiguousCredentials is
	// returned.
	UpdateUser(id UserID, newCredentials *UserModel, fields []string) error
	// DeleteUser deletes the given user.
	// If no such user exists this will not be considered an error.
	DeleteUser(id UserID) error
	// ListUsers returns all users in the storage.
	// At the moment no functionality to sort / filter the users exists, thus this must be done
	// after retrieving them.
	//
	// The iterator must be called in the following way (very similar to sql.Rows):
	// If the iterator is retrieved without an error the Close() method of the iterator must be called
	// (usually as a deferred function call).
	// Before accessing any of the elements with Next(), HasNext() must be called.
	// Finally, a call to Err must be made in order to test if the iteration ended or if there was an
	// error.
	// See the AsUserSlice function for an example.
	ListUsers() (UserIterator, error)
}

UserStorage provides methods to store, retrieve, update and delete users from a database. MemdummyUserStorage provides a reference implementation but should never be used in any real code.

type UserVerifier

type UserVerifier func(u *UserModel) error

UserVerifier is a function that takes a user and returns an error if a given criteria isn't matched. For example we can check if username / email / password are given.

Note that this performs validation on a user model, thus it tests the password hash and cannot be used to verify if a password is valid according to some other criteria (for example minimum length). A clear text password should be checked before, see PasswordVerifier. They're also used to verify certain length properties for the database.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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