Documentation
¶
Overview ¶
Package store defines the persistence ports that authit's service packages depend on. authit assumes no specific database: a host application supplies a concrete implementation of the interfaces in this package (Postgres, SQLite, in-memory, ...). The memstore package ships a reference in-memory implementation of every interface here, suitable for tests and small apps.
authit ships no DDL: your schema, naming and migrations are yours. It does ship a reference one. schema.sql in the repository root is a complete, non-binding table set for every interface here, annotated with the places where the column set is not guessable from the struct definitions -- and sqlbstore/example_test.go applies it and runs the real flows over it, so it is checked rather than merely asserted. Start there rather than reverse-engineering the columns one type at a time.
Index ¶
- Variables
- type DeviceAuthorization
- type DeviceAuthorizationStatus
- type DeviceAuthorizationStore
- type EmailVerificationStore
- type EmailVerificationToken
- type FailedLoginAttempt
- type Invitation
- type InvitationStatus
- type InvitationStore
- type LockoutStore
- type Member
- type MemberStore
- type PasswordResetStore
- type PasswordResetToken
- type PendingTwoFactorSession
- type PendingTwoFactorStore
- type PersonalAccessToken
- type PersonalAccessTokenStore
- type RefreshToken
- type RefreshTokenStore
- type Role
- type Superuser
- type SuperuserRefreshToken
- type SuperuserRefreshTokenStore
- type SuperuserStore
- type TOTPSettings
- type TOTPStore
- type Team
- type TeamStore
- type User
- type UserStore
Constants ¶
This section is empty.
Variables ¶
var ErrConflict = errors.New("authit/store: conflict")
ErrConflict is returned when a create would violate a uniqueness constraint (e.g. an email or slug that already exists).
var ErrNotFound = errors.New("authit/store: not found")
ErrNotFound is returned by lookup methods when no matching record exists.
Functions ¶
This section is empty.
Types ¶
type DeviceAuthorization ¶
type DeviceAuthorization struct {
ID string
DeviceCodeHash string
UserCode string
ClientID string
Scope string
Status DeviceAuthorizationStatus
UserID *string // set once Status is Approved
ExpiresAt time.Time
IntervalSeconds int
LastPolledAt *time.Time
CreatedAt time.Time
}
DeviceAuthorization is one in-flight RFC 8628 device-authorization-grant request. UserCode is stored as plaintext (not hashed): it is short and low-entropy by design — the security property comes from rate-limiting guesses at the approval endpoint (the host application's job), not from the code being a secret. DeviceCode, by contrast, IS a secret (the CLI's poll credential) and only its hash is persisted.
type DeviceAuthorizationStatus ¶
type DeviceAuthorizationStatus string
DeviceAuthorizationStatus is the lifecycle state of a DeviceAuthorization.
const ( DeviceAuthorizationPending DeviceAuthorizationStatus = "pending" DeviceAuthorizationApproved DeviceAuthorizationStatus = "approved" DeviceAuthorizationDenied DeviceAuthorizationStatus = "denied" )
type DeviceAuthorizationStore ¶
type DeviceAuthorizationStore interface {
CreateDeviceAuthorization(ctx context.Context, d *DeviceAuthorization) error
GetDeviceAuthorizationByDeviceCodeHash(ctx context.Context, hash string) (*DeviceAuthorization, error)
GetDeviceAuthorizationByUserCode(ctx context.Context, userCode string) (*DeviceAuthorization, error)
UpdateDeviceAuthorization(ctx context.Context, d *DeviceAuthorization) error
DeleteDeviceAuthorization(ctx context.Context, id string) error
}
DeviceAuthorizationStore persists DeviceAuthorization records.
type EmailVerificationStore ¶
type EmailVerificationStore interface {
CreateEmailVerificationToken(ctx context.Context, t *EmailVerificationToken) error
GetEmailVerificationTokenByHash(ctx context.Context, hash string) (*EmailVerificationToken, error)
MarkEmailVerificationTokenUsed(ctx context.Context, id string) error
DeleteUserEmailVerificationTokens(ctx context.Context, userID string) error
}
EmailVerificationStore persists email verification tokens.
type EmailVerificationToken ¶
type EmailVerificationToken struct {
ID string
UserID string
TokenHash string
ExpiresAt time.Time
UsedAt *time.Time
CreatedAt time.Time
}
EmailVerificationToken is a single-use, time-limited token e-mailed to a user to prove ownership of their address. Only its hash is persisted.
type FailedLoginAttempt ¶
FailedLoginAttempt records one bad login attempt, keyed by email so lockout can be checked before a matching user is even confirmed to exist (this avoids leaking account existence through timing/behavior).
type Invitation ¶
type Invitation struct {
ID string
TeamID string
Email string
TokenHash string
Role Role
Status InvitationStatus
InvitedByID string
ExpiresAt time.Time
AcceptedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
Invitation represents an offer for an email address to join a Team with a given Role. Only the token's hash is persisted; the raw token is handed back to the caller once, at creation or resend.
type InvitationStatus ¶
type InvitationStatus string
InvitationStatus is the lifecycle state of an Invitation. There is deliberately no "expired" status: expiry is derived from ExpiresAt at read time rather than written back.
const ( InvitationPending InvitationStatus = "pending" InvitationAccepted InvitationStatus = "accepted" InvitationRevoked InvitationStatus = "revoked" )
type InvitationStore ¶
type InvitationStore interface {
CreateInvitation(ctx context.Context, i *Invitation) error
GetInvitation(ctx context.Context, id string) (*Invitation, error)
GetInvitationByTokenHash(ctx context.Context, hash string) (*Invitation, error)
ListInvitationsByTeam(ctx context.Context, teamID string) ([]*Invitation, error)
UpdateInvitation(ctx context.Context, i *Invitation) error
}
InvitationStore persists Invitation records.
type LockoutStore ¶
type LockoutStore interface {
RecordFailedLoginAttempt(ctx context.Context, a *FailedLoginAttempt) error
CountRecentFailedLoginAttempts(ctx context.Context, email string, since time.Time) (int, error)
ClearFailedLoginAttempts(ctx context.Context, email string) error
LockAccount(ctx context.Context, userID string) error
IsAccountLocked(ctx context.Context, userID string) (bool, error)
UnlockAccount(ctx context.Context, userID string) error
}
LockoutStore tracks failed logins and account lockout state.
It needs TWO tables, which is not visible from the types above. One holds FailedLoginAttempt rows (keyed by email, per that type's doc). The second holds the set of currently-locked accounts, and has no canonical authit type at all: LockAccount, IsAccountLocked and UnlockAccount are insert/exists/delete over a set of user ids, not CRUD over a struct, so its shape is entirely yours. All authit requires is that its user-id column is UNIQUE, so locking an already-locked account is idempotent rather than an error.
Implementing only the attempts table compiles cleanly and fails at runtime. See schema.sql (`account_locks`).
type Member ¶
type Member struct {
ID string
TeamID string
UserID *string
Role Role
DisplayName string
Email string
IsActive bool
CreatedAt time.Time
UpdatedAt time.Time
}
Member is the join between a User and a Team, carrying the role that governs authorization within that team. UserID is nullable so a team can track a member (e.g. pending an invitation, or a login-less contact) before or without a linked User.
type MemberStore ¶
type MemberStore interface {
CreateMember(ctx context.Context, m *Member) error
GetMember(ctx context.Context, id string) (*Member, error)
GetMemberByUserAndTeam(ctx context.Context, userID, teamID string) (*Member, error)
ListMembersByTeam(ctx context.Context, teamID string) ([]*Member, error)
ListMembershipsByUser(ctx context.Context, userID string) ([]*Member, error)
UpdateMember(ctx context.Context, m *Member) error
DeleteMember(ctx context.Context, id string) error
}
MemberStore persists Member records.
type PasswordResetStore ¶
type PasswordResetStore interface {
CreatePasswordResetToken(ctx context.Context, t *PasswordResetToken) error
GetPasswordResetTokenByHash(ctx context.Context, hash string) (*PasswordResetToken, error)
MarkPasswordResetTokenUsed(ctx context.Context, id string) error
DeleteUserPasswordResetTokens(ctx context.Context, userID string) error
}
PasswordResetStore persists password reset tokens.
type PasswordResetToken ¶
type PasswordResetToken struct {
ID string
UserID string
TokenHash string
ExpiresAt time.Time
UsedAt *time.Time
CreatedAt time.Time
}
PasswordResetToken is a single-use, time-limited token e-mailed to a user who requested a password reset. Only its hash is persisted.
type PendingTwoFactorSession ¶
type PendingTwoFactorSession struct {
ID string
UserID string
TokenHash string
ExpiresAt time.Time
CreatedAt time.Time
}
PendingTwoFactorSession is the short-lived token issued after a correct password when the account has TOTP enabled; it must be exchanged for a real session by presenting a valid TOTP or backup code.
type PendingTwoFactorStore ¶
type PendingTwoFactorStore interface {
CreatePendingTwoFactorSession(ctx context.Context, s *PendingTwoFactorSession) error
GetPendingTwoFactorSessionByHash(ctx context.Context, hash string) (*PendingTwoFactorSession, error)
DeletePendingTwoFactorSession(ctx context.Context, id string) error
}
PendingTwoFactorStore persists pending 2FA sessions.
type PersonalAccessToken ¶
type PersonalAccessToken struct {
ID string
UserID string
Name string
TokenHash string
Scopes []string
ExpiresAt *time.Time
LastUsedAt *time.Time
RevokedAt *time.Time
CreatedAt time.Time
}
PersonalAccessToken is a long-lived, named, scoped bearer credential a user creates for themselves — for a CLI, a script, or any caller that isn't going through an interactive login. Unlike RefreshToken, it is not paired with a short-lived access token: the raw value itself is the bearer credential, verified on every request via its hash.
type PersonalAccessTokenStore ¶
type PersonalAccessTokenStore interface {
CreatePersonalAccessToken(ctx context.Context, t *PersonalAccessToken) error
GetPersonalAccessToken(ctx context.Context, id string) (*PersonalAccessToken, error)
GetPersonalAccessTokenByHash(ctx context.Context, hash string) (*PersonalAccessToken, error)
ListPersonalAccessTokensByUser(ctx context.Context, userID string) ([]*PersonalAccessToken, error)
UpdatePersonalAccessToken(ctx context.Context, t *PersonalAccessToken) error
}
PersonalAccessTokenStore persists PersonalAccessToken records.
type RefreshToken ¶
type RefreshToken struct {
ID string
UserID string
TokenHash string
ExpiresAt time.Time
RevokedAt *time.Time
UserAgent string
IPAddress string
CreatedAt time.Time
}
RefreshToken is a server-side session record. The raw token is only ever returned to the caller once, at issuance; only TokenHash is persisted.
type RefreshTokenStore ¶
type RefreshTokenStore interface {
CreateRefreshToken(ctx context.Context, t *RefreshToken) error
GetRefreshTokenByHash(ctx context.Context, hash string) (*RefreshToken, error)
RevokeRefreshToken(ctx context.Context, id string) error
RevokeAllUserRefreshTokens(ctx context.Context, userID string) error
ListActiveRefreshTokens(ctx context.Context, userID string) ([]*RefreshToken, error)
}
RefreshTokenStore persists refresh tokens, which double as the list of a user's active sessions.
type Role ¶
type Role string
Role is a member's permission level within a team. The three-tier default below is a starting point, not a closed set — host applications may store and check any string value they like; authit's team package only assigns special meaning to RoleOwner (last-owner protections).
Roles are per-team by design, and that is a real limit rather than a gap waiting to be filled. A Role only exists attached to a Member, and a Member only exists attached to a Team, so there is no way to express a principal whose identity spans teams — a platform-level auditor, a consultant, a support engineer, a coach working across many client organizations. Such an identity belongs in your own model, joined to authit by user id, not squeezed in here: the workarounds (a synthetic team everyone joins, or a membership row per team) both break as soon as the principal needs to reach a team it holds no membership in.
The split that survives: authit answers "who is this", your model answers "what may they do". See the team package's doc comment.
type Superuser ¶
type Superuser struct {
ID string
Email string
PasswordHash string
DisplayName string
IsActive bool
CreatedBy *string
LastLoginAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
Superuser is an operator identity, deliberately unrelated to Team/Member roles: it has no organization and no role field. The only way to create one is through the superuser package's API (never exposed over a public registration endpoint), so a compromised user-facing flow can never mint one.
type SuperuserRefreshToken ¶
type SuperuserRefreshToken struct {
ID string
SuperuserID string
TokenHash string
ExpiresAt time.Time
RevokedAt *time.Time
UserAgent string
IPAddress string
CreatedAt time.Time
}
SuperuserRefreshToken is the admin-plane equivalent of RefreshToken, kept in its own store/table so a leaked user-session store dump can't be replayed as an admin session.
type SuperuserRefreshTokenStore ¶
type SuperuserRefreshTokenStore interface {
CreateSuperuserRefreshToken(ctx context.Context, t *SuperuserRefreshToken) error
GetSuperuserRefreshTokenByHash(ctx context.Context, hash string) (*SuperuserRefreshToken, error)
RevokeSuperuserRefreshToken(ctx context.Context, id string) error
RevokeAllSuperuserRefreshTokens(ctx context.Context, superuserID string) error
}
SuperuserRefreshTokenStore persists admin-plane refresh tokens.
type SuperuserStore ¶
type SuperuserStore interface {
CreateSuperuser(ctx context.Context, s *Superuser) error
GetSuperuserByID(ctx context.Context, id string) (*Superuser, error)
GetSuperuserByEmail(ctx context.Context, email string) (*Superuser, error)
ListSuperusers(ctx context.Context) ([]*Superuser, error)
UpdateSuperuser(ctx context.Context, s *Superuser) error
CountSuperusers(ctx context.Context) (int, error)
}
SuperuserStore persists Superuser records.
type TOTPSettings ¶
type TOTPSettings struct {
ID string
UserID string
SecretEncrypted []byte
Enabled bool
VerifiedAt *time.Time
RecoveryCodeHashes []string
RecoveryCodesUsed int
CreatedAt time.Time
UpdatedAt time.Time
}
TOTPSettings holds a user's TOTP secret (encrypted at rest by the caller before it reaches the store) and backup codes (hashed).
The field names are worth reading before writing the table: the obvious guesses (`confirmed`, `backup_codes`) are not what this type has. Enabled is the on/off flag, VerifiedAt records when enrollment was confirmed, and the backup codes are RecoveryCodeHashes plus a RecoveryCodesUsed counter.
RecoveryCodeHashes is a []string with no single obvious storage: a Postgres text[], a join table and a JSON column are all defensible, and the choice is yours -- see schema.sql, which uses text[].
type TOTPStore ¶
type TOTPStore interface {
CreateTOTPSettings(ctx context.Context, t *TOTPSettings) error
GetTOTPSettingsByUserID(ctx context.Context, userID string) (*TOTPSettings, error)
UpdateTOTPSettings(ctx context.Context, t *TOTPSettings) error
DeleteTOTPSettings(ctx context.Context, userID string) error
}
TOTPStore persists TOTP enrollment state.
type Team ¶
type Team struct {
ID string
Name string
Slug string
OwnerID string
CreatedAt time.Time
UpdatedAt time.Time
}
Team is an organization/tenant that users belong to via Member records.
type TeamStore ¶
type TeamStore interface {
CreateTeam(ctx context.Context, t *Team) error
GetTeam(ctx context.Context, id string) (*Team, error)
GetTeamBySlug(ctx context.Context, slug string) (*Team, error)
UpdateTeam(ctx context.Context, t *Team) error
DeleteTeam(ctx context.Context, id string) error
}
TeamStore persists Team records.
type User ¶
type User struct {
ID string
Email string
PasswordHash string
EmailVerified bool
EmailVerifiedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
User is a login-capable identity. Host applications are free to store additional profile fields elsewhere and join on ID; authit only needs the fields below to authenticate.