Documentation
¶
Overview ¶
Package adminx holds the primitives the admin UI is built from: passwords, one time codes, sessions and the audit log.
Everything here relies on the standard library only. An admin panel guards the production settings of a service, so the fewer moving parts under it, the better.
Index ¶
- Constants
- Variables
- func HashPassword(password string) (string, error)
- func NewSessionToken() (token, id string, err error)
- func NewTOTPSecret() (string, error)
- func NormalizeEmail(email string) string
- func SessionID(token string) string
- func TOTPCode(secret string, at time.Time) (string, error)
- func TOTPURI(issuer, account, secret string) string
- func VerifyPassword(hash, password string) error
- func VerifyTOTP(secret, code string, now time.Time, lastCounter int64) (int64, error)
- type AuditEntry
- type AuditRepo
- type Auth
- func (a *Auth) Cleanup(ctx context.Context) (int, error)
- func (a *Auth) CreateUser(ctx context.Context, email, password string, roles []string, twoFactor bool) (User, string, error)
- func (a *Auth) Login(ctx context.Context, email, password, code, ip, userAgent string) (string, error)
- func (a *Auth) Logout(ctx context.Context, token string) error
- func (a *Auth) Session(ctx context.Context, token string) (User, Session, error)
- func (a *Auth) SetPassword(ctx context.Context, id int64, password string) error
- type MemoryStore
- func (s *MemoryStore) Audit() AuditRepo
- func (s *MemoryStore) ByEmail(_ context.Context, email string) (User, error)
- func (s *MemoryStore) ByID(_ context.Context, id int64) (User, error)
- func (s *MemoryStore) Create(_ context.Context, u User) (User, error)
- func (s *MemoryStore) Delete(_ context.Context, id int64) error
- func (s *MemoryStore) List(context.Context) ([]User, error)
- func (s *MemoryStore) Sessions() SessionRepo
- func (s *MemoryStore) Update(_ context.Context, u User) error
- func (s *MemoryStore) Users() UserRepo
- type Session
- type SessionRepo
- type User
- type UserRepo
Constants ¶
const ( ActionLogin = "admin.login" ActionLogout = "admin.logout" ActionSettingSet = "settings.set" ActionSettingReset = "settings.reset" ActionUserCreate = "admin.user.create" ActionUserUpdate = "admin.user.update" ActionUserDelete = "admin.user.delete" )
Actions recorded by the admin panel itself. Project pages use their own names.
const DefaultSessionTTL = 12 * time.Hour
DefaultSessionTTL is how long a session lives.
const RoleAdmin = "admin"
RoleAdmin may do everything, including managing users. Other roles are the project's own business: the admin UI checks a page's roles against the ones a user has.
Variables ¶
var ( // ErrNoSession is returned when the token is unknown: it was never issued, or the // session has been revoked. ErrNoSession = errors.New("session not found") // ErrSessionExpired is returned when the session is too old. ErrSessionExpired = errors.New("the session has expired") )
var ( // ErrInvalidCode is returned when the code does not match the secret. ErrInvalidCode = errors.New("invalid one time code") // ErrCodeReused is returned when a code has already been used. A one time code that // works twice is not one time: an intercepted code would be enough to log in. ErrCodeReused = errors.New("the code has already been used") )
var ErrCodeRequired = errors.New("a one time code is required")
ErrCodeRequired is returned when the account has two factor authentication and no code was given.
var ErrNoUser = errors.New("user not found")
ErrNoUser is returned by a repository when there is no such user.
var ErrUserDisabled = errors.New("the account is disabled")
ErrUserDisabled is returned when a disabled account tries to log in.
var ErrWrongPassword = errors.New("wrong password")
ErrWrongPassword is returned when the password does not match the hash. It is the same error for a wrong password and for an unknown user, so the answer does not say which accounts exist.
Functions ¶
func HashPassword ¶
HashPassword hashes a password for storage. The result keeps the scheme, the iteration count and the salt, so a hash stays verifiable after the parameters change.
func NewSessionToken ¶
NewSessionToken returns the token for the cookie and the id to store. Only the hash is stored, so a database dump does not hand out live sessions.
func NewTOTPSecret ¶
NewTOTPSecret returns a fresh secret in the base32 form an authenticator app expects.
func NormalizeEmail ¶
NormalizeEmail is how an address is stored and looked up, so a capital letter in the login form does not create a second account.
func VerifyPassword ¶
VerifyPassword checks a password against a stored hash.
func VerifyTOTP ¶
VerifyTOTP checks a code and returns the counter it was issued for. A code from an earlier or the same counter as lastCounter is refused, which is what stops a code from being used twice; store the returned counter next to the user.
Types ¶
type AuditEntry ¶
type AuditEntry struct {
ID int64
At time.Time
Actor string // the email of the signed in user
Action string // what was done, "settings.set"
Target string // what it was done to, the setting key
Details string // what changed, in a form a human reads
IP string
}
AuditEntry is one recorded action. The log is append only: it answers who changed what, and an entry that can be edited answers nothing.
type AuditRepo ¶
type AuditRepo interface {
Add(ctx context.Context, e AuditEntry) error
// List returns the entries newest first. A zero before means from the beginning;
// otherwise the entries older than that id are returned.
List(ctx context.Context, limit int, before int64) ([]AuditEntry, error)
}
AuditRepo stores the log.
type Auth ¶
type Auth struct {
// Now is the clock. Tests replace it; production leaves it alone.
Now func() time.Time
// contains filtered or unexported fields
}
Auth is the sign in of the admin panel: it checks the password and the one time code and issues sessions.
func NewAuth ¶
func NewAuth(users UserRepo, sessions SessionRepo, ttl time.Duration) *Auth
NewAuth creates the service. A zero ttl means DefaultSessionTTL.
func (*Auth) CreateUser ¶
func (a *Auth) CreateUser(ctx context.Context, email, password string, roles []string, twoFactor bool) (User, string, error)
CreateUser adds an account and returns it together with the TOTP secret to show once. With twoFactor off the secret is empty.
func (*Auth) Login ¶
func (a *Auth) Login(ctx context.Context, email, password, code, ip, userAgent string) (string, error)
Login checks the credentials and returns the token for the cookie.
An unknown address and a wrong password give the same error, so the answer does not tell whether an account exists.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore keeps accounts, sessions and the log in memory. It is what tests of the admin panel and of project pages run on, with no database involved.
func (*MemoryStore) Audit ¶
func (s *MemoryStore) Audit() AuditRepo
Audit returns the store as an AuditRepo.
func (*MemoryStore) Delete ¶
func (s *MemoryStore) Delete(_ context.Context, id int64) error
Delete removes an account together with its sessions.
func (*MemoryStore) List ¶
func (s *MemoryStore) List(context.Context) ([]User, error)
List returns the accounts ordered by address.
func (*MemoryStore) Sessions ¶
func (s *MemoryStore) Sessions() SessionRepo
Sessions returns the store as a SessionRepo.
func (*MemoryStore) Update ¶
func (s *MemoryStore) Update(_ context.Context, u User) error
Update replaces an account.
func (*MemoryStore) Users ¶
func (s *MemoryStore) Users() UserRepo
Users returns the store as a UserRepo.
type Session ¶
type Session struct {
ID string // hash of the token, not the token itself
UserID int64
CreatedAt time.Time
ExpiresAt time.Time
IP string
UserAgent string
}
Session is a signed in browser.
type SessionRepo ¶
type SessionRepo interface {
Create(ctx context.Context, s Session) error
ByID(ctx context.Context, id string) (Session, error)
Delete(ctx context.Context, id string) error
DeleteByUser(ctx context.Context, userID int64) error
DeleteExpired(ctx context.Context, now time.Time) (int, error)
}
SessionRepo stores the sessions.
type User ¶
type User struct {
ID int64
Email string
PasswordHash string
TOTPSecret string // empty when two factor authentication is off
TOTPCounter int64 // the last used code, so it cannot be used again
Roles []string
Disabled bool
CreatedAt time.Time
LastLoginAt time.Time
}
User is an admin panel account.
type UserRepo ¶
type UserRepo interface {
ByEmail(ctx context.Context, email string) (User, error)
ByID(ctx context.Context, id int64) (User, error)
Create(ctx context.Context, u User) (User, error)
Update(ctx context.Context, u User) error
List(ctx context.Context) ([]User, error)
Delete(ctx context.Context, id int64) error
}
UserRepo stores the accounts.