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
- Variables
- func CheckEmailMaxLen(email string) error
- func CheckFirstNameMaxLen(name string) error
- func CheckFirstNameSyntax(name string) error
- func CheckLastNameMaxLen(name string) error
- func CheckLastNameSyntax(name string) error
- func CheckPasswordHashMaxLen(password string) error
- func CheckUsernameMaxLen(username string) error
- func CheckUsernameSyntax(username string) error
- func ClassCounter(classes []RuneClass, s string) int
- func DigitClass(r rune) bool
- func GenSessionKey() (string, error)
- func IsEmailSyntaxValid(email string) error
- func LowerLetterClass(r rune) bool
- func RetrySessionInsert(storage SessionStorage, session *SessionEntry, numTries int) error
- func SpecialCharacterClass(r rune) bool
- func UpperLetterClass(r rune) bool
- func VerifiyNameExists(u *UserModel) error
- func VerifyEmailExists(u *UserModel) error
- func VerifyEmailSyntax(u *UserModel) error
- func VerifyPasswordExists(u *UserModel) error
- func VerifyStandardUserMaxLens(u *UserModel) error
- type AmbiguousCredentials
- type MemdummySessionStorage
- func (s *MemdummySessionStorage) CleanUp(referenceDate time.Time) (int64, error)
- func (s *MemdummySessionStorage) Clear()
- func (s *MemdummySessionStorage) DeleteForUser(user UserID) (int64, error)
- func (s *MemdummySessionStorage) DeleteSession(key string) error
- func (s *MemdummySessionStorage) GetSession(key string) (*SessionEntry, error)
- func (s *MemdummySessionStorage) InitSessions() error
- func (s *MemdummySessionStorage) InsertSession(session *SessionEntry) error
- type MemdummyUserStorage
- func (s *MemdummyUserStorage) Clear()
- func (s *MemdummyUserStorage) DeleteUser(id UserID) error
- func (s *MemdummyUserStorage) GetUser(id UserID) (*UserModel, error)
- func (s *MemdummyUserStorage) GetUserByEmail(email string) (*UserModel, error)
- func (s *MemdummyUserStorage) GetUserByName(username string) (*UserModel, error)
- func (s *MemdummyUserStorage) InitUsers() error
- func (s *MemdummyUserStorage) InsertUser(user *UserModel) (UserID, error)
- func (s *MemdummyUserStorage) ListUsers() (UserIterator, error)
- func (s *MemdummyUserStorage) UpdateUser(id UserID, newCredentials *UserModel, fields []string) error
- type NoSuchSession
- type NoSuchUser
- type NotSupported
- type PasswordVerifier
- type RetryInsertErr
- type RollbackErr
- type RuneClass
- type SQLBridge
- type SQLSessionStorage
- func (s *SQLSessionStorage) CleanUp(referenceDate time.Time) (int64, error)
- func (s *SQLSessionStorage) DeleteForUser(user UserID) (int64, error)
- func (s *SQLSessionStorage) DeleteSession(key string) error
- func (s *SQLSessionStorage) GetSession(key string) (*SessionEntry, error)
- func (s *SQLSessionStorage) InitSessions() error
- func (s *SQLSessionStorage) InsertSession(session *SessionEntry) error
- type SQLTemplateReplacer
- func (t *SQLTemplateReplacer) Apply(templateStr string) string
- func (t *SQLTemplateReplacer) Delete(key string)
- func (t *SQLTemplateReplacer) DeleteMany(keys ...string)
- func (t *SQLTemplateReplacer) HasKey(key string) bool
- func (t *SQLTemplateReplacer) Set(key, value string)
- func (t *SQLTemplateReplacer) SetMany(oldnew ...string)
- func (t *SQLTemplateReplacer) Update(other *SQLTemplateReplacer)
- func (t *SQLTemplateReplacer) UpdateDict(mapping map[string]string)
- type SQLUserIterator
- type SQLUserStorage
- func (s *SQLUserStorage) DeleteUser(id UserID) error
- func (s *SQLUserStorage) GetUser(id UserID) (*UserModel, error)
- func (s *SQLUserStorage) GetUserByEmail(email string) (*UserModel, error)
- func (s *SQLUserStorage) GetUserByName(username string) (*UserModel, error)
- func (s *SQLUserStorage) InitUsers() error
- func (s *SQLUserStorage) InsertUser(user *UserModel) (UserID, error)
- func (s *SQLUserStorage) ListUsers() (UserIterator, error)
- func (s *SQLUserStorage) UpdateUser(id UserID, newCredentials *UserModel, fields []string) error
- type SessionEntry
- type SessionExists
- type SessionSQL
- type SessionStorage
- type Storage
- type UserExists
- type UserID
- type UserIterator
- type UserModel
- type UserSQL
- type UserStorage
- type UserVerifier
Constants ¶
const ( // InvalidUserID is used when a user id is required but no user with the // given credentials was found. InvalidUserID = UserID(-1) )
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 )
const ( // SpecialChars defines the special characters that must be included in a password // to pass the SpecialCharacterClass. SpecialChars = "~!@#$%^&*()+=_-{}[]\\|:;?/<>," )
Variables ¶
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", } )
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.
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-.]+$)`) )
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 ¶
CheckEmailMaxLen tests if the email length is not longer than the allowed length (254 chars).
func CheckFirstNameMaxLen ¶
CheckFirstNameMaxLen tests if the name is not longer than the allowed length (50 chars).
func CheckFirstNameSyntax ¶
CheckFirstNameSyntax tests if all chars of the name are a unicode letter (class L).
func CheckLastNameMaxLen ¶
CheckLastNameMaxLen tests if the name is not longer than the allowed length (150 chars).
func CheckLastNameSyntax ¶
CheckLastNameSyntax tests if all chars of the name are a unicode letter (class L).
func CheckPasswordHashMaxLen ¶
CheckPasswordHashMaxLen tests if the password hash length is not longer than the allowed length (270 chars).
func CheckUsernameMaxLen ¶
CheckUsernameMaxLen tests if the username is not longer than the allowed length (150 chars).
func CheckUsernameSyntax ¶
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 ¶
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 GenSessionKey ¶
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 ¶
IsEmailSyntaxValid tests if the email is syntactically correct. It does however not check the length of the email.
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 ¶
SpecialCharacterClass tests if the rune is a special char from SpecialChars.
func VerifiyNameExists ¶
VerifiyNameExists tests if the user has a username. It is a UserVerifier.
func VerifyEmailExists ¶
VerifyEmailExists tests if the user has an email. It is a UserVerifier.
func VerifyEmailSyntax ¶
VerifyEmailSyntax tests if the user email is syntactically. It does however not check the length of the email.
func VerifyPasswordExists ¶
VerifyPasswordExists tests if the password is not empty. It is a UserVerifier.
func VerifyStandardUserMaxLens ¶
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.
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.
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.
type PasswordVerifier ¶
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.
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 ¶
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 ¶
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) 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 ¶
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.
type SessionSQL ¶
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.
type UserIterator ¶
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 ¶
Copy creates a copy of the user model and returns a new one with the same contens.
func (*UserModel) GetFieldByName ¶
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 ¶
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.