Repository Reference
The repository.Repository struct handles all database interactions. It supports multiple SQL dialects (SQLite, PostgreSQL, MySQL) via bob.
type Repository struct {
// ...
}
Constructor
Open
Opens a database connection and returns a Repository instance.
func Open(opts Opts) (*Repository, error)
New
Creates a Repository from an existing sql.DB connection.
func New(db *sql.DB, dialect string) *Repository
User Methods
UserCreate
Inserts a new user record. automatically sets CreatedAt and UpdatedAt to the current UTC time if they are not provided.
func (r Repository) UserCreate(ctx context.Context, user *models.User) (*models.User, error)
UserGetByEmail
Retrieves a user by email. The lookup email (and, in UserCreate/UserUpdate, the stored email) is lowercased and trimmed before use, so lookups behave the same regardless of input casing or the DB dialect's default collation.
func (r Repository) UserGetByEmail(ctx context.Context, email string) (*models.User, error)
UserGetByID
Retrieves a user by ID (UUID).
func (r Repository) UserGetByID(ctx context.Context, id string) (*models.User, error)
UserGetByProvider
Retrieves a user by OAuth2 provider ID.
func (r Repository) UserGetByProvider(ctx context.Context, provider, providerID string) (*models.User, error)
UserGetByUsername
Retrieves a user by username. The lookup username (and, in UserCreate/UserUpdate, the stored username) is lowercased and trimmed before use, same as email, so lookups behave the same regardless of input casing or the DB dialect's default collation. Non-empty usernames are enforced unique across accounts at the database level (a partial/filtered unique index; empty usernames are exempt, same as phone).
func (r Repository) UserGetByUsername(ctx context.Context, username string) (*models.User, error)
UserGetByPhone
Retrieves a user by phone number (used by SMS OTP login).
func (r Repository) UserGetByPhone(ctx context.Context, phone string) (*models.User, error)
UserUpdate
Updates an existing user record using partial-update semantics: a zero-valued field ("", nil) is left untouched rather than overwritten, so a caller can pass a partially-populated models.User and only change the fields they set. Automatically updates UpdatedAt to the current UTC time.
EmailVerified, PhoneVerified, IsActive, and MfaEnabled are the exception: UserUpdate never touches them, regardless of what the struct carries. A bool has no "not set" zero value distinguishable from "set to false" the way ""/nil work for other fields, so honoring these four the same way every other field is honored would mean any partial models.User (e.g. fetched by ID and email only) silently deactivates the account and disables MFA. Use UserSetEmailVerified/UserSetPhoneVerified/UserSetMFAEnabled/UserSetLockoutState to change them instead.
func (r Repository) UserUpdate(ctx context.Context, user *models.User) (*models.User, error)
UserSetLockoutState
Sets the failed-attempt counter, an optional lockout expiry, and IsActive in one call. Used by both the account-lockout feature (temporary, auto-expiring) and admin suspend/reactivate (permanent, no expiry — lockedUntil is nil).
func (r Repository) UserSetLockoutState(ctx context.Context, userID string, attempts int, lockedUntil *time.Time, isActive bool) (*models.User, error)
UserSetEmailVerified
Sets EmailVerified for a single user; when set to true, also stamps EmailVerifiedAt to now.
func (r Repository) UserSetEmailVerified(ctx context.Context, userID string, verified bool) (*models.User, error)
UserSetPhoneVerified
Sets PhoneVerified for a single user.
func (r Repository) UserSetPhoneVerified(ctx context.Context, userID string, verified bool) (*models.User, error)
UserSetMFAEnabled
Sets MfaEnabled for a single user.
func (r Repository) UserSetMFAEnabled(ctx context.Context, userID string, enabled bool) (*models.User, error)
UserDelete
Deletes a user record.
func (r Repository) UserDelete(ctx context.Context, id string) error
UsersList
Search/filter/paginate users via models.UserListFilter (Search, Status, CreatedAfter/CreatedBefore, LastActiveAfter/LastActiveBefore). Search is a case-insensitive substring match against email or username on all 3 dialects (Postgres uses ILIKE, sqlite/mysql LIKE).
func (r Repository) UsersList(ctx context.Context, filter models.UserListFilter, limit, offset int) (users []*models.User, hasMore bool, err error)
Token Methods
[!NOTE]
Token.Tokenis stored and looked up as an opaque value at this layer —TokenCreate/TokenGetByTokendon't hash it themselves. Theservice.Authmethods built on top (TokenCreate,APIKeyCreate,InvitationCreate, ...) are what hash it (SHA-256, seeutil.HashToken) before calling down to these, and hash an incoming raw value the same way before looking it up. Call these repository methods directly (bypassingservice.Auth) and you're responsible for that hashing yourself — see Token Storage.
TokenCreate
Stores a new refresh token or other temporary token (reset, magic link).
func (r Repository) TokenCreate(ctx context.Context, token *models.Token) (*models.Token, error)
TokenGetByToken
Retrieves a token record by its value.
func (r Repository) TokenGetByToken(ctx context.Context, tokenValue string) (*models.Token, error)
TokenListByUserIDAndType
Lists a user's tokens of a given type (e.g. all trusted-device tokens, or all invitation tokens issued by a user).
func (r Repository) TokenListByUserIDAndType(ctx context.Context, userID, tokenType string) ([]*models.Token, error)
TokenListByUserID
Lists a user's most recent tokens of any type, newest first. Backs UserAuthHistory.
func (r Repository) TokenListByUserID(ctx context.Context, userID string, limit int) ([]*models.Token, error)
TokenRevoke
Marks a token as revoked.
func (r Repository) TokenRevoke(ctx context.Context, id string) error
TokenConsume
Atomically revokes a single-use token (UPDATE ... WHERE id = ? AND revoked = false), making the update itself the concurrency guard instead of a separate read-then-write. consumed is false if the token was already revoked -- by a prior legitimate use, or a concurrent caller that won the race -- which callers should treat as reuse, not silently ignore. Used by every single-use-token flow (TokenRefresh, PasswordResetConfirm, PasswordlessLogin, EmailChangeConfirm, InvitationAccept, MFALoginVerify, SMSOTPVerify, MFA recovery codes) in place of a plain TokenRevoke.
func (r Repository) TokenConsume(ctx context.Context, id string) (consumed bool, err error)
TokenRevokeAllByUserID
Revokes every active token for a user, regardless of type — sessions, API keys, MFA recovery codes, trusted devices, everything. Used where that blanket behavior is the intended policy (e.g. PasswordResetConfirm, on the theory that a password reset should force re-auth everywhere). Most callers mean to revoke only one class of token; use TokenRevokeAllByUserIDAndType for those, so e.g. confirming MFA enrollment doesn't collaterally revoke the user's API keys.
func (r Repository) TokenRevokeAllByUserID(ctx context.Context, userID string) error
TokenRevokeAllByUserIDAndType
Revokes every active token of the given type for a user, leaving other token types untouched — e.g. models.TokenTypeRefresh after a confirmed email change (sessions only, not API keys), or models.TokenTypeMFARecovery when MFA re-enrollment invalidates old recovery codes.
func (r Repository) TokenRevokeAllByUserIDAndType(ctx context.Context, userID, tokenType string) error
TokenRevokeFamily
Bulk-revokes every other active refresh token sharing the given rotation family_id (stored in Metadata), in one query. Used by TokenRefresh when it detects a replayed, already-rotated-out refresh token — a strong signal of theft. See Refresh Token Reuse Detection.
func (r Repository) TokenRevokeFamily(ctx context.Context, userID, familyID string) error
TokenRevokeSessions
Bulk-revokes all of a user's active refresh-token sessions, optionally excluding one (e.g. "log out other devices, keep this one"). Backs RevokeAllSessions.
func (r Repository) TokenRevokeSessions(ctx context.Context, userID, exceptID string) error
TokenBatchInsert
Inserts multiple tokens in a single multi-row INSERT instead of one per row — used for MFA recovery codes.
func (r Repository) TokenBatchInsert(ctx context.Context, tokens []*models.Token) error
TokenDelete
Permanently deletes a token.
func (r Repository) TokenDelete(ctx context.Context, id string) error
WebAuthn Credential Methods
Stores registered passkey/authenticator credentials for a user.
func (r Repository) WebauthnCredentialCreate(ctx context.Context, cred *models.WebauthnCredential) (*models.WebauthnCredential, error)
func (r Repository) WebauthnCredentialGetByID(ctx context.Context, id string) (*models.WebauthnCredential, error)
func (r Repository) WebauthnCredentialGetByCredentialID(ctx context.Context, credentialID string) (*models.WebauthnCredential, error)
func (r Repository) WebauthnCredentialListByUserID(ctx context.Context, userID string) ([]*models.WebauthnCredential, error)
func (r Repository) WebauthnCredentialUpdate(ctx context.Context, cred *models.WebauthnCredential) (*models.WebauthnCredential, error)
func (r Repository) WebauthnCredentialDelete(ctx context.Context, id string) error
WebAuthn Challenge Methods
Stores in-flight registration/login ceremony challenges. Login challenges are discoverable (usernameless), so no user is known yet — this is why challenges live in their own table with a nullable, unconstrained user_id instead of being stored as Token rows (whose user_id is NOT NULL with a foreign key).
func (r Repository) WebauthnChallengeCreate(ctx context.Context, ch *models.WebauthnChallenge) (*models.WebauthnChallenge, error)
func (r Repository) WebauthnChallengeGetBySessionKey(ctx context.Context, sessionKey string) (*models.WebauthnChallenge, error)
func (r Repository) WebauthnChallengeDelete(ctx context.Context, id string) error
Audit Log Methods
func (r Repository) AuditLogCreate(ctx context.Context, log *models.AuditLog) (*models.AuditLog, error)
func (r Repository) AuditLogListByUserID(ctx context.Context, userID string, filter models.AuditLogFilter, limit, offset int) (logs []*models.AuditLog, hasMore bool, err error)
RBAC Methods
Real RBAC — roles/permissions tables and their user_roles/role_permissions join tables — separate from the legacy User.Roles string field. See Roles & Permissions (RBAC).
func (r Repository) RoleCreate(ctx context.Context, role *models.Role) (*models.Role, error)
func (r Repository) RoleGetByID(ctx context.Context, id string) (*models.Role, error)
func (r Repository) RoleGetByName(ctx context.Context, name string) (*models.Role, error)
func (r Repository) RolesList(ctx context.Context) ([]*models.Role, error)
func (r Repository) RoleDelete(ctx context.Context, id string) error // cascades user_roles/role_permissions
func (r Repository) PermissionCreate(ctx context.Context, permission *models.Permission) (*models.Permission, error)
func (r Repository) PermissionGetByID(ctx context.Context, id string) (*models.Permission, error)
func (r Repository) PermissionGetByName(ctx context.Context, name string) (*models.Permission, error)
func (r Repository) PermissionsList(ctx context.Context) ([]*models.Permission, error)
func (r Repository) PermissionDelete(ctx context.Context, id string) error // cascades role_permissions
// The bool reports whether a row was actually inserted/deleted -- both are
// idempotent (ON CONFLICT DO NOTHING / INSERT IGNORE under the hood), so the
// service layer uses it to decide whether to fire an audit event, without a
// pre-fetch check.
func (r Repository) UserRoleGrant(ctx context.Context, userID, roleID string) (granted bool, err error)
func (r Repository) UserRoleRevoke(ctx context.Context, userID, roleID string) (revoked bool, err error)
func (r Repository) RolesByUserID(ctx context.Context, userID string) ([]*models.Role, error)
func (r Repository) RolePermissionGrant(ctx context.Context, roleID, permissionID string) (granted bool, err error)
func (r Repository) RolePermissionRevoke(ctx context.Context, roleID, permissionID string) error
func (r Repository) PermissionsByRoleID(ctx context.Context, roleID string) ([]*models.Permission, error)
// Resolved transitively through every role granted to the user.
func (r Repository) PermissionsByUserID(ctx context.Context, userID string) ([]*models.Permission, error)
Organization Methods
role_id on ezauth_org_members is a foreign key into ezauth_roles — org membership draws from the same role catalog as RBAC. See Organizations.
func (r Repository) OrganizationCreate(ctx context.Context, org *models.Organization) (*models.Organization, error)
func (r Repository) OrganizationGetByID(ctx context.Context, id string) (*models.Organization, error)
func (r Repository) OrganizationsList(ctx context.Context, limit, offset int) (orgs []*models.Organization, hasMore bool, err error)
func (r Repository) OrganizationDelete(ctx context.Context, id string) error // cascades org_members
// OrgMemberUpsert inserts, or updates role_id if the (org, user) pair already exists.
func (r Repository) OrgMemberUpsert(ctx context.Context, orgID, userID, roleID string) error
func (r Repository) OrgMemberRemove(ctx context.Context, orgID, userID string) error
func (r Repository) OrgMembersByOrgID(ctx context.Context, orgID string) ([]*models.OrgMember, error) // joined with ezauth_roles for RoleName
func (r Repository) OrganizationsByUserID(ctx context.Context, userID string) ([]*models.Organization, error)