Documentation
¶
Overview ¶
Package lookup owns every database read and write the security package needs. pkg/security itself contains no SQL: it calls the store interfaces defined here.
Each store has a procedure implementation (stored procedures, the Postgres default) and a direct implementation (tables through a dialect-driven query builder). Which one runs is decided per operation by Config.EffectiveMode.
Index ¶
- Constants
- Variables
- func ApplyTxSettings(ctx context.Context, tx common.Database, settings map[string]string) error
- func FirstColumn(e Entity) string
- func FromDatabase(db common.Database) (*sql.DB, string, error)
- type AuthStore
- type Column
- type Config
- type Consent
- type DeviceCode
- type DeviceStatus
- type Entity
- type KeyStore
- type Mode
- type OAuthClientStore
- type OAuthGrantStore
- type OAuthRefreshSession
- type OAuthSession
- type OAuthUserStore
- type Op
- type PasskeyCredentialRecord
- type PasskeyCredentialRef
- type PasskeyStore
- type PolicyStore
- type ProcNames
- type Provider
- type PushedRequest
- type RefreshToken
- type Resolved
- type Schema
- type TOTPStore
- type Table
Constants ¶
const ( DialectPostgres = "postgres" DialectSQLite = "sqlite" DialectMySQL = "mysql" DialectMSSQL = "mssql" )
Dialect names understood by Config.Dialect. The dialect package (step 2) owns the implementations; the names are defined here so Config can be validated without it.
Variables ¶
var ( // ErrRefreshInvalid: the refresh token is unknown, expired or revoked. ErrRefreshInvalid = errors.New("invalid refresh token") // ErrRefreshReused: a refresh token that was already rotated was presented again. The // store has revoked the whole token family. ErrRefreshReused = errors.New("refresh token reuse detected") // ErrDevicePending, ErrDeviceSlowDown, ErrDeviceDenied and ErrDeviceExpired are the RFC 8628 // polling outcomes other than success. ErrDevicePending = errors.New("authorization pending") ErrDeviceSlowDown = errors.New("slow down") ErrDeviceDenied = errors.New("access denied") ErrDeviceExpired = errors.New("device code expired") // ErrNotFound: the requested record does not exist or has expired. ErrNotFound = errors.New("not found") )
Errors returned by OAuthGrantStore. Callers compare with errors.Is.
var ( ErrUsernameExists = errors.New("username already exists") ErrEmailExists = errors.New("email already exists") )
Registration conflicts reported by AuthStore.Register in direct mode.
var ( UsersID = col(EntityUsers, "id") UsersUsername = col(EntityUsers, "username") UsersEmail = col(EntityUsers, "email") UsersPassword = col(EntityUsers, "password") UsersUserLevel = col(EntityUsers, "user_level") UsersRoles = col(EntityUsers, "roles") UsersIsActive = col(EntityUsers, "is_active") UsersCreatedAt = col(EntityUsers, "created_at") UsersUpdatedAt = col(EntityUsers, "updated_at") UsersLastLoginAt = col(EntityUsers, "last_login_at") UsersProgramUserID = col(EntityUsers, "program_user_id") UsersProgramUserTable = col(EntityUsers, "program_user_table") UsersRemoteID = col(EntityUsers, "remote_id") UsersAuthProvider = col(EntityUsers, "auth_provider") UsersTOTPSecret = col(EntityUsers, "totp_secret") UsersTOTPEnabled = col(EntityUsers, "totp_enabled") UsersTOTPEnabledAt = col(EntityUsers, "totp_enabled_at") SessionsID = col(EntityUserSessions, "id") SessionsToken = col(EntityUserSessions, "session_token") SessionsUserID = col(EntityUserSessions, "user_id") SessionsExpiresAt = col(EntityUserSessions, "expires_at") SessionsCreatedAt = col(EntityUserSessions, "created_at") SessionsLastActivityAt = col(EntityUserSessions, "last_activity_at") SessionsIPAddress = col(EntityUserSessions, "ip_address") SessionsUserAgent = col(EntityUserSessions, "user_agent") SessionsAccessToken = col(EntityUserSessions, "access_token") SessionsRefreshToken = col(EntityUserSessions, "refresh_token") SessionsTokenType = col(EntityUserSessions, "token_type") SessionsAuthProvider = col(EntityUserSessions, "auth_provider") BlacklistID = col(EntityTokenBlacklist, "id") BlacklistToken = col(EntityTokenBlacklist, "token") BlacklistUserID = col(EntityTokenBlacklist, "user_id") BlacklistExpiresAt = col(EntityTokenBlacklist, "expires_at") BlacklistCreatedAt = col(EntityTokenBlacklist, "created_at") BackupCodesID = col(EntityUserTOTPBackupCodes, "id") BackupCodesUserID = col(EntityUserTOTPBackupCodes, "user_id") BackupCodesCodeHash = col(EntityUserTOTPBackupCodes, "code_hash") BackupCodesUsed = col(EntityUserTOTPBackupCodes, "used") BackupCodesUsedAt = col(EntityUserTOTPBackupCodes, "used_at") BackupCodesCreatedAt = col(EntityUserTOTPBackupCodes, "created_at") PasskeyID = col(EntityUserPasskeyCredentials, "id") PasskeyUserID = col(EntityUserPasskeyCredentials, "user_id") PasskeyCredentialID = col(EntityUserPasskeyCredentials, "credential_id") PasskeyPublicKey = col(EntityUserPasskeyCredentials, "public_key") PasskeyAttestationType = col(EntityUserPasskeyCredentials, "attestation_type") PasskeyAAGUID = col(EntityUserPasskeyCredentials, "aaguid") PasskeySignCount = col(EntityUserPasskeyCredentials, "sign_count") PasskeyCloneWarning = col(EntityUserPasskeyCredentials, "clone_warning") PasskeyTransports = col(EntityUserPasskeyCredentials, "transports") PasskeyBackupEligible = col(EntityUserPasskeyCredentials, "backup_eligible") PasskeyBackupState = col(EntityUserPasskeyCredentials, "backup_state") PasskeyName = col(EntityUserPasskeyCredentials, "name") PasskeyCreatedAt = col(EntityUserPasskeyCredentials, "created_at") PasskeyLastUsedAt = col(EntityUserPasskeyCredentials, "last_used_at") ResetsID = col(EntityUserPasswordResets, "id") ResetsUserID = col(EntityUserPasswordResets, "user_id") ResetsTokenHash = col(EntityUserPasswordResets, "token_hash") ResetsExpiresAt = col(EntityUserPasswordResets, "expires_at") ResetsCreatedAt = col(EntityUserPasswordResets, "created_at") ResetsUsed = col(EntityUserPasswordResets, "used") ResetsUsedAt = col(EntityUserPasswordResets, "used_at") OAuthClientsID = col(EntityOAuthClients, "id") OAuthClientsClientID = col(EntityOAuthClients, "client_id") OAuthClientsRedirectURIs = col(EntityOAuthClients, "redirect_uris") OAuthClientsClientName = col(EntityOAuthClients, "client_name") OAuthClientsGrantTypes = col(EntityOAuthClients, "grant_types") OAuthClientsAllowedScopes = col(EntityOAuthClients, "allowed_scopes") OAuthClientsClientSecretHash = col(EntityOAuthClients, "client_secret_hash") OAuthClientsTokenEndpointAuthMethod = col(EntityOAuthClients, "token_endpoint_auth_method") OAuthClientsIsActive = col(EntityOAuthClients, "is_active") OAuthClientsMetadata = col(EntityOAuthClients, "metadata") OAuthClientsCreatedAt = col(EntityOAuthClients, "created_at") OAuthCodesID = col(EntityOAuthCodes, "id") OAuthCodesCode = col(EntityOAuthCodes, "code") OAuthCodesClientID = col(EntityOAuthCodes, "client_id") OAuthCodesRedirectURI = col(EntityOAuthCodes, "redirect_uri") OAuthCodesClientState = col(EntityOAuthCodes, "client_state") OAuthCodesCodeChallenge = col(EntityOAuthCodes, "code_challenge") OAuthCodesCodeChallengeMethod = col(EntityOAuthCodes, "code_challenge_method") OAuthCodesSessionToken = col(EntityOAuthCodes, "session_token") OAuthCodesRefreshToken = col(EntityOAuthCodes, "refresh_token") OAuthCodesScopes = col(EntityOAuthCodes, "scopes") OAuthCodesExpiresAt = col(EntityOAuthCodes, "expires_at") OAuthCodesCreatedAt = col(EntityOAuthCodes, "created_at") OAuthCodesExtra = col(EntityOAuthCodes, "extra") OAuthConsentsID = col(EntityOAuthConsents, "id") OAuthConsentsUserID = col(EntityOAuthConsents, "user_id") OAuthConsentsClientID = col(EntityOAuthConsents, "client_id") OAuthConsentsScopes = col(EntityOAuthConsents, "scopes") OAuthConsentsCreatedAt = col(EntityOAuthConsents, "created_at") OAuthConsentsExpiresAt = col(EntityOAuthConsents, "expires_at") OAuthRefreshID = col(EntityOAuthRefreshTokens, "id") OAuthRefreshTokenHash = col(EntityOAuthRefreshTokens, "token_hash") OAuthRefreshFamilyID = col(EntityOAuthRefreshTokens, "family_id") OAuthRefreshClientID = col(EntityOAuthRefreshTokens, "client_id") OAuthRefreshUserID = col(EntityOAuthRefreshTokens, "user_id") OAuthRefreshSessionToken = col(EntityOAuthRefreshTokens, "session_token") OAuthRefreshScopes = col(EntityOAuthRefreshTokens, "scopes") OAuthRefreshExtra = col(EntityOAuthRefreshTokens, "extra") OAuthRefreshCreatedAt = col(EntityOAuthRefreshTokens, "created_at") OAuthRefreshExpiresAt = col(EntityOAuthRefreshTokens, "expires_at") OAuthRefreshUsedAt = col(EntityOAuthRefreshTokens, "used_at") OAuthRefreshRevokedAt = col(EntityOAuthRefreshTokens, "revoked_at") OAuthDeviceID = col(EntityOAuthDeviceCodes, "id") OAuthDeviceHash = col(EntityOAuthDeviceCodes, "device_hash") OAuthDeviceUserCode = col(EntityOAuthDeviceCodes, "user_code") OAuthDeviceClientID = col(EntityOAuthDeviceCodes, "client_id") OAuthDeviceScopes = col(EntityOAuthDeviceCodes, "scopes") OAuthDeviceStatus = col(EntityOAuthDeviceCodes, "status") OAuthDeviceUserID = col(EntityOAuthDeviceCodes, "user_id") OAuthDeviceSessionToken = col(EntityOAuthDeviceCodes, "session_token") OAuthDeviceInterval = col(EntityOAuthDeviceCodes, "poll_interval") OAuthDeviceCreatedAt = col(EntityOAuthDeviceCodes, "created_at") OAuthDeviceExpiresAt = col(EntityOAuthDeviceCodes, "expires_at") OAuthDeviceLastPolledAt = col(EntityOAuthDeviceCodes, "last_polled_at") OAuthPARID = col(EntityOAuthPARRequests, "id") OAuthPARRequestURI = col(EntityOAuthPARRequests, "request_uri") OAuthPARClientID = col(EntityOAuthPARRequests, "client_id") OAuthPARParams = col(EntityOAuthPARRequests, "params") OAuthPARCreatedAt = col(EntityOAuthPARRequests, "created_at") OAuthPARExpiresAt = col(EntityOAuthPARRequests, "expires_at") OAuthJTIID = col(EntityOAuthJTI, "id") OAuthJTIKey = col(EntityOAuthJTI, "jti_key") OAuthJTIExpiresAt = col(EntityOAuthJTI, "expires_at") KeysID = col(EntityUserKeys, "id") KeysUserID = col(EntityUserKeys, "user_id") KeysKeyType = col(EntityUserKeys, "key_type") KeysKeyHash = col(EntityUserKeys, "key_hash") KeysName = col(EntityUserKeys, "name") KeysScopes = col(EntityUserKeys, "scopes") KeysMeta = col(EntityUserKeys, "meta") KeysExpiresAt = col(EntityUserKeys, "expires_at") KeysCreatedAt = col(EntityUserKeys, "created_at") KeysLastUsedAt = col(EntityUserKeys, "last_used_at") KeysIsActive = col(EntityUserKeys, "is_active") GroupMembersGroupID = col(EntitySecGroupMembers, "group_id") GroupMembersUserID = col(EntitySecGroupMembers, "user_id") ColRulesID = col(EntitySecColumnRules, "id") ColRulesUserID = col(EntitySecColumnRules, "user_id") ColRulesGroupID = col(EntitySecColumnRules, "group_id") ColRulesSchemaName = col(EntitySecColumnRules, "schema_name") ColRulesTableName = col(EntitySecColumnRules, "table_name") ColRulesColumnPath = col(EntitySecColumnRules, "column_path") ColRulesAccessType = col(EntitySecColumnRules, "access_type") ColRulesMaskStart = col(EntitySecColumnRules, "mask_start") ColRulesMaskEnd = col(EntitySecColumnRules, "mask_end") ColRulesMaskInvert = col(EntitySecColumnRules, "mask_invert") ColRulesMaskChar = col(EntitySecColumnRules, "mask_char") ColRulesExtraFilters = col(EntitySecColumnRules, "extra_filters") ColRulesIsActive = col(EntitySecColumnRules, "is_active") RowRulesID = col(EntitySecRowRules, "id") RowRulesUserID = col(EntitySecRowRules, "user_id") RowRulesGroupID = col(EntitySecRowRules, "group_id") RowRulesSchemaName = col(EntitySecRowRules, "schema_name") RowRulesTableName = col(EntitySecRowRules, "table_name") RowRulesTemplate = col(EntitySecRowRules, "template") RowRulesHasBlock = col(EntitySecRowRules, "has_block") RowRulesIsActive = col(EntitySecRowRules, "is_active") )
Logical columns. The names are the default physical column names.
var ErrInvalidAPIKey = errors.New("invalid api key")
ErrInvalidAPIKey is the single error LoginAPIKey returns for unknown, expired, inactive and wrong-type keys, so callers cannot tell them apart.
Functions ¶
func ApplyTxSettings ¶
ApplyTxSettings sets each entry as a transaction-local setting on tx, in name order. Postgres only; any other driver with a non-empty map is an error so a missing RLS stamp fails closed.
func FirstColumn ¶
FirstColumn returns the name of the first logical column of an entity (used by the direct backend for existence checks).
func FromDatabase ¶
FromDatabase extracts the *sql.DB and the dialect name from an application's common.Database (bun, gorm or pgsql adapter), so callers do not have to dig the connection out or set Config.Dialect by hand. The returned name is the adapter's normalised DriverName ("postgres", "sqlite", "mssql", "mysql") and is empty when the adapter reports a driver the dialect registry does not know; set Config.Dialect explicitly in that case.
Transaction adapters do not expose a *sql.DB and are rejected.
Types ¶
type AuthStore ¶
type AuthStore interface {
Login(ctx context.Context, req sectypes.LoginRequest) (*sectypes.LoginResponse, error)
Register(ctx context.Context, req sectypes.RegisterRequest) (*sectypes.LoginResponse, error)
Logout(ctx context.Context, req sectypes.LogoutRequest) error
// Session resolves a session token to its user. reference says where the token came
// from ("authenticate", "cookie", "refresh"); the procedure backend passes it through.
Session(ctx context.Context, token, reference string) (*sectypes.UserContext, error)
// TouchSession records last activity for a session token. user is the context the
// session resolved to; the procedure backend passes it to the update procedure.
TouchSession(ctx context.Context, token string, user *sectypes.UserContext) error
Refresh(ctx context.Context, refreshToken string) (*sectypes.LoginResponse, error)
// LoginAPIKey logs in with a raw header/generic API key. Unknown, expired, inactive and
// wrong-type keys all return the same error.
LoginAPIKey(ctx context.Context, rawKey string, claims map[string]any) (*sectypes.LoginResponse, error)
JWTLogin(ctx context.Context, req sectypes.LoginRequest) (*sectypes.LoginResponse, error)
JWTLogout(ctx context.Context, req sectypes.LogoutRequest) error
ResetRequest(ctx context.Context, req sectypes.PasswordResetRequest) (*sectypes.PasswordResetResponse, error)
ResetComplete(ctx context.Context, req sectypes.PasswordResetCompleteRequest) error
}
AuthStore covers sessions, login, registration and password reset.
type Column ¶
Column is a typed key naming one logical column of an entity. The physical column name is looked up in the Schema, so every column of every entity is configurable.
type Config ¶
type Config struct {
// Dialect names a registered dialect ("postgres", "sqlite", "mysql", "mssql", or one added
// with dialect.Register). Empty = detect from the driver.
Dialect string
// Mode is the default mode for every operation. See ModeDefault.
Mode Mode
// Overrides sets the mode per operation, e.g. direct for OpSession, procedure for OpLogin.
Overrides map[Op]Mode
// Procs overrides procedure names; empty fields keep the default.
Procs ProcNames
// Schema overrides table and column names; missing entries keep the default.
Schema Schema
}
Config selects dialect, mode and naming. The zero value is valid: dialect detected from the driver, default mode per dialect, default procedure/table/column names.
func (Config) EffectiveMode ¶
EffectiveMode resolves the mode for one operation: a per-operation override wins over Config.Mode, and ModeDefault is replaced by the dialect default. The result is ModeProcedure, ModeDirect or ModeAuto; ModeAuto only survives on Postgres, where the caller must probe the procedure. dialect is the resolved dialect name.
type Consent ¶
type Consent struct {
UserID int `json:"user_id"`
ClientID string `json:"client_id"`
Scopes []string `json:"scopes"`
ExpiresAt time.Time `json:"expires_at"`
}
Consent records that a user allowed a client to act with Scopes.
type DeviceCode ¶
type DeviceCode struct {
DeviceHash string `json:"device_hash"`
UserCode string `json:"user_code"`
ClientID string `json:"client_id"`
Scopes []string `json:"scopes,omitempty"`
Status DeviceStatus `json:"status"`
UserID int `json:"user_id,omitempty"`
SessionToken string `json:"session_token,omitempty"`
Interval int `json:"interval"` // minimum seconds between polls
ExpiresAt time.Time `json:"expires_at"`
}
DeviceCode is a pending RFC 8628 device authorization. DeviceHash is the SHA-256 hash of the device_code returned to the device; UserCode is stored as typed by the user (normalised).
type DeviceStatus ¶
type DeviceStatus string
DeviceStatus is the state of an RFC 8628 device authorization.
const ( DevicePending DeviceStatus = "pending" DeviceApproved DeviceStatus = "approved" DeviceDenied DeviceStatus = "denied" )
type Entity ¶
type Entity string
Entity identifies one table the direct backend reads or writes.
const ( EntityUsers Entity = "users" EntityUserSessions Entity = "user_sessions" EntityTokenBlacklist Entity = "token_blacklist" EntityUserTOTPBackupCodes Entity = "user_totp_backup_codes" EntityUserPasskeyCredentials Entity = "user_passkey_credentials" //nolint:gosec // table name, not a credential EntityUserPasswordResets Entity = "user_password_resets" EntityOAuthClients Entity = "oauth_clients" EntityOAuthCodes Entity = "oauth_codes" EntityOAuthConsents Entity = "oauth_consents" EntityOAuthRefreshTokens Entity = "oauth_refresh_tokens" //nolint:gosec // table name, not a credential EntityOAuthDeviceCodes Entity = "oauth_device_codes" EntityOAuthPARRequests Entity = "oauth_par_requests" EntityOAuthJTI Entity = "oauth_jti" EntityUserKeys Entity = "user_keys" EntitySecGroupMembers Entity = "sec_group_members" EntitySecColumnRules Entity = "sec_column_rules" EntitySecRowRules Entity = "sec_row_rules" )
type KeyStore ¶
type KeyStore interface {
Create(ctx context.Context, req sectypes.CreateKeyRequest, keyHash string) (*sectypes.UserKey, error)
List(ctx context.Context, userID int, keyType sectypes.KeyType) ([]sectypes.UserKey, error)
// Delete soft-deletes a key after verifying ownership and returns its hash so callers can
// invalidate caches. The hash is empty when the backend cannot report it.
Delete(ctx context.Context, userID int, keyID int64) (keyHash string, err error)
Validate(ctx context.Context, keyHash string, keyType sectypes.KeyType) (*sectypes.UserKey, error)
}
KeyStore persists per-user auth keys. Hashing and raw-key generation happen in Go, so the store only sees key hashes.
type Mode ¶
type Mode string
Mode selects how a store talks to the database.
const ( // ModeDefault (the zero value) resolves to ModeProcedure on Postgres and ModeDirect elsewhere. ModeDefault Mode = "" // ModeProcedure always calls the configured stored procedure; a missing procedure is an error. ModeProcedure Mode = "procedure" // ModeDirect always works on the tables through the dialect builder. ModeDirect Mode = "direct" // ModeAuto probes the procedure once per operation on Postgres (cached) and uses it when // present, otherwise direct. Other dialects resolve to ModeDirect. ModeAuto Mode = "auto" )
type OAuthClientStore ¶
type OAuthClientStore interface {
RegisterClient(ctx context.Context, client *sectypes.OAuthServerClient) (*sectypes.OAuthServerClient, error)
GetClient(ctx context.Context, clientID string) (*sectypes.OAuthServerClient, error)
SaveCode(ctx context.Context, code *sectypes.OAuthCode) error
// ExchangeCode atomically consumes an authorization code.
ExchangeCode(ctx context.Context, code string) (*sectypes.OAuthCode, error)
Introspect(ctx context.Context, token string) (*sectypes.OAuthTokenInfo, error)
Revoke(ctx context.Context, token string) error
// UpdateClient rewrites the registration fields of an existing client (RFC 7592).
UpdateClient(ctx context.Context, client *sectypes.OAuthServerClient) error
// DeleteClient deactivates a client.
DeleteClient(ctx context.Context, clientID string) error
}
OAuthClientStore persists the OAuth2 authorization server state (RFC 7591 clients, authorization codes, token introspection and revocation).
type OAuthGrantStore ¶
type OAuthGrantStore interface {
// SaveConsent replaces the consent of (UserID, ClientID).
SaveConsent(ctx context.Context, c Consent) error
// GetConsent returns the unexpired consent or ErrNotFound.
GetConsent(ctx context.Context, userID int, clientID string) (*Consent, error)
RevokeConsent(ctx context.Context, userID int, clientID string) error
SaveRefresh(ctx context.Context, t RefreshToken) error
// RotateRefresh atomically consumes the token with hash oldHash and stores next in the same
// family. It returns the consumed token. An unknown, expired or revoked token is
// ErrRefreshInvalid. A token that was already consumed revokes its family and returns the
// token together with ErrRefreshReused so the caller can end the session.
RotateRefresh(ctx context.Context, oldHash string, next RefreshToken) (*RefreshToken, error)
// PeekRefresh returns the token without consuming it. Unknown, expired and revoked tokens are
// ErrRefreshInvalid; an already rotated token is returned so RotateRefresh can report its reuse.
PeekRefresh(ctx context.Context, hash string) (*RefreshToken, error)
RevokeRefreshFamily(ctx context.Context, familyID string) error
// RevokeRefreshBySession revokes every refresh token bound to a session token.
RevokeRefreshBySession(ctx context.Context, sessionToken string) error
CreateDevice(ctx context.Context, d DeviceCode) error
// DeviceByUserCode returns the unexpired pending device authorization or ErrNotFound.
DeviceByUserCode(ctx context.Context, userCode string) (*DeviceCode, error)
// DeviceDecide approves or denies the device authorization of userCode.
DeviceDecide(ctx context.Context, userCode string, approve bool, userID int, sessionToken string) error
// DevicePoll implements the token endpoint side: it enforces the poll interval and returns
// one of ErrDevicePending, ErrDeviceSlowDown, ErrDeviceDenied, ErrDeviceExpired or, once
// approved, the record (consumed: it cannot be polled again).
DevicePoll(ctx context.Context, deviceHash string) (*DeviceCode, error)
SavePushedRequest(ctx context.Context, r PushedRequest) error
// ConsumePushedRequest returns and deletes the request or ErrNotFound.
ConsumePushedRequest(ctx context.Context, requestURI string) (*PushedRequest, error)
// SeenJTI records key until expires and reports whether it was already recorded. It is the
// replay cache for DPoP proofs and client assertions.
SeenJTI(ctx context.Context, key string, expires time.Time) (bool, error)
}
OAuthGrantStore persists the OAuth2 authorization server state that is not a client, a code or a session: consents, refresh tokens, device codes, pushed requests and the replay cache.
type OAuthRefreshSession ¶
type OAuthRefreshSession struct {
UserID int `json:"user_id"`
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
Expiry time.Time `json:"expiry"`
}
OAuthRefreshSession is the stored token state needed to refresh an OAuth2 login.
type OAuthSession ¶
type OAuthSession struct {
SessionToken string
UserID int
AccessToken string
RefreshToken string
TokenType string
ExpiresAt time.Time
Provider string
}
OAuthSession is the session row written after an OAuth2 client login.
type OAuthUserStore ¶
type OAuthUserStore interface {
GetOrCreateUser(ctx context.Context, user *sectypes.UserContext, provider string) (int, error)
CreateSession(ctx context.Context, session OAuthSession) error
GetByRefreshToken(ctx context.Context, refreshToken string) (*OAuthRefreshSession, error)
UpdateRefreshToken(ctx context.Context, userID int, oldRefreshToken, newSessionToken, newAccessToken, newRefreshToken string, expiresAt time.Time) error
GetUser(ctx context.Context, userID int) (*sectypes.UserContext, error)
}
OAuthUserStore persists users and sessions created through OAuth2 client login.
type Op ¶
type Op string
Op names one store operation so its mode can be overridden individually.
const ( OpLogin Op = "login" OpRegister Op = "register" OpLogout Op = "logout" OpSession Op = "session" OpTouchSession Op = "touch_session" OpRefresh Op = "refresh" OpLoginAPIKey Op = "login_api_key" //nolint:gosec // operation name, not a credential OpJWTLogin Op = "jwt_login" OpJWTLogout Op = "jwt_logout" OpResetRequest Op = "reset_request" OpResetComplete Op = "reset_complete" OpKeyCreate Op = "key_create" OpKeyList Op = "key_list" OpKeyDelete Op = "key_delete" OpKeyValidate Op = "key_validate" OpOAuthRegisterClient Op = "oauth_register_client" OpOAuthGetClient Op = "oauth_get_client" OpOAuthSaveCode Op = "oauth_save_code" OpOAuthExchangeCode Op = "oauth_exchange_code" OpOAuthIntrospect Op = "oauth_introspect" OpOAuthRevoke Op = "oauth_revoke" OpOAuthUpdateClient Op = "oauth_update_client" OpOAuthDeleteClient Op = "oauth_delete_client" OpOAuthGetOrCreateUser Op = "oauth_get_or_create_user" OpOAuthCreateSession Op = "oauth_create_session" OpOAuthGetRefreshToken Op = "oauth_get_refresh_token" //nolint:gosec // operation name, not a credential OpOAuthUpdateRefreshToken Op = "oauth_update_refresh_token" //nolint:gosec // operation name, not a credential OpOAuthGetUser Op = "oauth_get_user" OpOAuthSaveConsent Op = "oauth_save_consent" OpOAuthGetConsent Op = "oauth_get_consent" OpOAuthRevokeConsent Op = "oauth_revoke_consent" OpOAuthSaveRefresh Op = "oauth_save_refresh" //nolint:gosec // operation name, not a credential OpOAuthRotateRefresh Op = "oauth_rotate_refresh" //nolint:gosec // operation name, not a credential OpOAuthPeekRefresh Op = "oauth_peek_refresh" //nolint:gosec // operation name, not a credential OpOAuthRevokeRefreshFamily Op = "oauth_revoke_refresh_family" //nolint:gosec // operation name, not a credential OpOAuthRevokeRefreshByUser Op = "oauth_revoke_refresh_session" //nolint:gosec // operation name, not a credential OpOAuthCreateDevice Op = "oauth_create_device" OpOAuthDeviceByUserCode Op = "oauth_device_by_user_code" OpOAuthDeviceDecide Op = "oauth_device_decide" OpOAuthDevicePoll Op = "oauth_device_poll" OpOAuthSavePAR Op = "oauth_save_par" OpOAuthConsumePAR Op = "oauth_consume_par" OpOAuthSeenJTI Op = "oauth_seen_jti" OpPasskeyStore Op = "passkey_store" OpPasskeyGet Op = "passkey_get" OpPasskeyUpdateCounter Op = "passkey_update_counter" OpPasskeyList Op = "passkey_list" OpPasskeyDelete Op = "passkey_delete" OpPasskeyRename Op = "passkey_rename" OpPasskeyByUsername Op = "passkey_by_username" //nolint:gosec // operation name, not a credential OpPasskeyLogin Op = "passkey_login" OpTOTPEnable Op = "totp_enable" OpTOTPDisable Op = "totp_disable" OpTOTPStatus Op = "totp_status" OpTOTPSecret Op = "totp_secret" OpTOTPRegenerateBackup Op = "totp_regenerate_backup" OpTOTPValidateBackupCode Op = "totp_validate_backup_code" OpColumnSecurity Op = "column_security" OpRowSecurity Op = "row_security" )
type PasskeyCredentialRecord ¶
type PasskeyCredentialRecord struct {
UserID int
CredentialID string // base64
PublicKey string // base64
AttestationType string
SignCount uint32
Transports []string
BackupEligible bool
BackupState bool
Name string
}
PasskeyCredentialRecord is a credential as persisted: byte fields are base64 text.
type PasskeyCredentialRef ¶
type PasskeyCredentialRef struct {
CredentialID string `json:"credential_id"`
Transports []string `json:"transports"`
}
PasskeyCredentialRef is a credential id and transports, as returned for a username lookup.
type PasskeyStore ¶
type PasskeyStore interface {
Store(ctx context.Context, rec PasskeyCredentialRecord) (int64, error)
// Get returns the owner and signature counter of a credential.
Get(ctx context.Context, credentialID string) (userID int, signCount uint32, err error)
UpdateCounter(ctx context.Context, credentialID string, newCounter uint32) (cloneWarning bool, err error)
List(ctx context.Context, userID int) ([]sectypes.PasskeyCredential, error)
Delete(ctx context.Context, userID int, credentialID string) error
Rename(ctx context.Context, userID int, credentialID, name string) error
ByUsername(ctx context.Context, username string) (userID int, creds []PasskeyCredentialRef, err error)
Login(ctx context.Context, userID int, claims map[string]any) (*sectypes.LoginResponse, error)
}
PasskeyStore persists WebAuthn credentials.
type PolicyStore ¶
type PolicyStore interface {
ColumnSecurity(ctx context.Context, userID int, schema, table string) ([]sectypes.ColumnSecurity, error)
RowSecurity(ctx context.Context, userRef any, schema, table string) (sectypes.RowSecurity, error)
}
PolicyStore loads column and row security rules. No rules is an empty result, never an error; failures are errors so callers fail closed.
type ProcNames ¶
type ProcNames struct {
// Auth procedures (DatabaseAuthenticator)
Login string // default: "resolvespec_login"
Register string // default: "resolvespec_register"
Logout string // default: "resolvespec_logout"
Session string // default: "resolvespec_session"
SessionUpdate string // default: "resolvespec_session_update"
RefreshToken string // default: "resolvespec_refresh_token"
LoginAPIKey string // default: "resolvespec_login_api_key"
// JWT procedures (JWTAuthenticator)
JWTLogin string // default: "resolvespec_jwt_login"
JWTLogout string // default: "resolvespec_jwt_logout"
// Security policy procedures
ColumnSecurity string // default: "resolvespec_column_security"
RowSecurity string // default: "resolvespec_row_security"
// TOTP procedures (DatabaseTwoFactorProvider)
TOTPEnable string // default: "resolvespec_totp_enable"
TOTPDisable string // default: "resolvespec_totp_disable"
TOTPGetStatus string // default: "resolvespec_totp_get_status"
TOTPGetSecret string // default: "resolvespec_totp_get_secret"
TOTPRegenerateBackup string // default: "resolvespec_totp_regenerate_backup_codes"
TOTPValidateBackupCode string // default: "resolvespec_totp_validate_backup_code"
// Passkey procedures (DatabasePasskeyProvider)
PasskeyStoreCredential string // default: "resolvespec_passkey_store_credential"
PasskeyGetCredsByUsername string // default: "resolvespec_passkey_get_credentials_by_username"
PasskeyGetCredential string // default: "resolvespec_passkey_get_credential"
PasskeyUpdateCounter string // default: "resolvespec_passkey_update_counter"
PasskeyGetUserCredentials string // default: "resolvespec_passkey_get_user_credentials"
PasskeyDeleteCredential string // default: "resolvespec_passkey_delete_credential"
PasskeyUpdateName string // default: "resolvespec_passkey_update_name"
PasskeyLogin string // default: "resolvespec_passkey_login"
// Password reset procedures (DatabaseAuthenticator)
PasswordResetRequest string // default: "resolvespec_password_reset_request"
PasswordResetComplete string // default: "resolvespec_password_reset"
// OAuth2 procedures (DatabaseAuthenticator OAuth2 methods)
OAuthGetOrCreateUser string // default: "resolvespec_oauth_getorcreateuser"
OAuthCreateSession string // default: "resolvespec_oauth_createsession"
OAuthGetRefreshToken string // default: "resolvespec_oauth_getrefreshtoken"
OAuthUpdateRefreshToken string // default: "resolvespec_oauth_updaterefreshtoken"
OAuthGetUser string // default: "resolvespec_oauth_getuser"
// OAuth2 server procedures (OAuthServer persistence)
OAuthRegisterClient string // default: "resolvespec_oauth_register_client"
OAuthGetClient string // default: "resolvespec_oauth_get_client"
OAuthSaveCode string // default: "resolvespec_oauth_save_code"
OAuthExchangeCode string // default: "resolvespec_oauth_exchange_code"
OAuthIntrospect string // default: "resolvespec_oauth_introspect"
OAuthRevoke string // default: "resolvespec_oauth_revoke"
OAuthUpdateClient string // default: "resolvespec_oauth_update_client"
OAuthDeleteClient string // default: "resolvespec_oauth_delete_client"
// OAuth2 server grant procedures (consents, refresh tokens, device codes, PAR, replay cache).
// Each takes a jsonb request and returns (p_success, p_error, p_data).
OAuthSaveConsent string // default: "resolvespec_oauth_save_consent"
OAuthGetConsent string // default: "resolvespec_oauth_get_consent"
OAuthRevokeConsent string // default: "resolvespec_oauth_revoke_consent"
OAuthSaveRefresh string // default: "resolvespec_oauth_save_refresh"
OAuthRotateRefresh string // default: "resolvespec_oauth_rotate_refresh"
OAuthPeekRefresh string // default: "resolvespec_oauth_peek_refresh"
OAuthRevokeRefreshFamily string // default: "resolvespec_oauth_revoke_refresh_family"
OAuthRevokeRefreshByUser string // default: "resolvespec_oauth_revoke_refresh_session"
OAuthCreateDevice string // default: "resolvespec_oauth_create_device"
OAuthDeviceByUserCode string // default: "resolvespec_oauth_device_by_user_code"
OAuthDeviceDecide string // default: "resolvespec_oauth_device_decide"
OAuthDevicePoll string // default: "resolvespec_oauth_device_poll"
OAuthSavePAR string // default: "resolvespec_oauth_save_par"
OAuthConsumePAR string // default: "resolvespec_oauth_consume_par"
OAuthSeenJTI string // default: "resolvespec_oauth_seen_jti"
// Keystore procedures (KeyStore)
KeystoreGetUserKeys string // default: "resolvespec_keystore_get_user_keys"
KeystoreCreateKey string // default: "resolvespec_keystore_create_key"
KeystoreDeleteKey string // default: "resolvespec_keystore_delete_key"
KeystoreValidateKey string // default: "resolvespec_keystore_validate_key"
}
ProcNames holds the stored procedure (function) names used by the procedure backend. It replaces security.SQLNames and security.KeyStoreSQLNames. Zero fields mean "default" when merged with DefaultProcNames.
func DefaultProcNames ¶
func DefaultProcNames() ProcNames
DefaultProcNames returns the default resolvespec_* procedure names.
type Provider ¶
type Provider struct {
Auth AuthStore
Keys KeyStore
OAuthClient OAuthClientStore
OAuthUser OAuthUserStore
OAuthGrant OAuthGrantStore
Passkey PasskeyStore
TOTP TOTPStore
Policy PolicyStore
}
Provider bundles every store. Security constructors take a Provider.
type PushedRequest ¶
type PushedRequest struct {
RequestURI string `json:"request_uri"`
ClientID string `json:"client_id"`
Params map[string]string `json:"params"`
ExpiresAt time.Time `json:"expires_at"`
}
PushedRequest is an RFC 9126 pushed authorization request.
type RefreshToken ¶
type RefreshToken struct {
TokenHash string `json:"token_hash"`
FamilyID string `json:"family_id"`
ClientID string `json:"client_id"`
UserID int `json:"user_id"`
SessionToken string `json:"session_token"`
Scopes []string `json:"scopes,omitempty"`
Extra map[string]any `json:"extra,omitempty"` // nonce, auth_time, acr, sid, dpop_jkt, resource
ExpiresAt time.Time `json:"expires_at"`
}
RefreshToken is a server-managed refresh token. Only the SHA-256 hash of the raw token is stored.
type Schema ¶
Schema maps every entity to its physical table and columns. The zero value is valid and means "all defaults"; use DefaultSchema for the explicit baseline.
func DefaultSchema ¶
func DefaultSchema() Schema
DefaultSchema returns the baseline schema: every entity and column under its default name.
func (Schema) Merge ¶
Merge returns a copy of s with every non-empty field of override applied. Unknown entities or columns in override are kept so Validate can report them.
func (Schema) SchemaName ¶
SchemaName returns the optional schema qualifier of an entity.
type TOTPStore ¶
type TOTPStore interface {
Enable(ctx context.Context, userID int, secret string, hashedCodes []string) error
Disable(ctx context.Context, userID int) error
Status(ctx context.Context, userID int) (bool, error)
Secret(ctx context.Context, userID int) (string, error)
RegenerateBackupCodes(ctx context.Context, userID int, hashedCodes []string) error
ValidateBackupCode(ctx context.Context, userID int, codeHash string) (bool, error)
}
TOTPStore persists two-factor state. Backup codes arrive already hashed.
type Table ¶
type Table struct {
// Schema optionally qualifies the table (schema.table). Empty = unqualified.
Schema string
// Name is the physical table name. Empty = default (the entity name).
Name string
// Columns maps logical column name -> physical column name. Missing = default.
Columns map[string]string
}
Table maps one entity to a physical table and its columns.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package backends assembles a lookup.Provider: it builds the procedure and direct stores for one database and routes every operation to one of them according to lookup.Config.
|
Package backends assembles a lookup.Provider: it builds the procedure and direct stores for one database and routes every operation to one of them according to lookup.Config. |
|
Package conformance is the shared behavioural suite every lookup backend must pass.
|
Package conformance is the shared behavioural suite every lookup backend must pass. |
|
Package ddl holds the reference table schemas for the lookup direct backend, one per dialect.
|
Package ddl holds the reference table schemas for the lookup direct backend, one per dialect. |
|
Package dialect holds the per-database adaptors used by the lookup direct backend.
|
Package dialect holds the per-database adaptors used by the lookup direct backend. |
|
Package direct is the table-backed implementation of the lookup stores.
|
Package direct is the table-backed implementation of the lookup stores. |
|
Package procedure is the stored-procedure backend of lookup: it calls the resolvespec_* functions (names from lookup.ProcNames) and keeps their p_success / p_error / p_data contract.
|
Package procedure is the stored-procedure backend of lookup: it calls the resolvespec_* functions (names from lookup.ProcNames) and keeps their p_success / p_error / p_data contract. |