lookup

package
v1.3.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 7, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

View Source
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

View Source
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.

View Source
var (
	ErrUsernameExists = errors.New("username already exists")
	ErrEmailExists    = errors.New("email already exists")
)

Registration conflicts reported by AuthStore.Register in direct mode.

View Source
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.

View Source
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

func ApplyTxSettings(ctx context.Context, tx common.Database, settings map[string]string) error

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

func FirstColumn(e Entity) string

FirstColumn returns the name of the first logical column of an entity (used by the direct backend for existence checks).

func FromDatabase

func FromDatabase(db common.Database) (*sql.DB, string, error)

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

type Column struct {
	Entity Entity
	Name   string
}

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.

func (Column) String

func (c Column) String() string

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

func (c Config) EffectiveMode(op Op, dialect string) (Mode, error)

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.

func (Config) Resolve

func (c Config) Resolve() (*Resolved, error)

Resolve merges c with the defaults and validates the result.

func (Config) ResolveDialect

func (c Config) ResolveDialect(db *sql.DB) (dialect.Dialect, error)

ResolveDialect returns the dialect for db: the configured one, or detected from the driver.

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"
)

func AllOps

func AllOps() []Op

AllOps lists every operation, so callers can resolve or validate modes up front.

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.

func (ProcNames) Merge

func (p ProcNames) Merge(override ProcNames) ProcNames

Merge returns a copy of p with every non-empty field of override applied.

func (ProcNames) Validate

func (p ProcNames) Validate() error

Validate checks that every name is a safe (optionally schema-qualified) identifier.

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 Resolved

type Resolved struct {
	Config
	Procs  ProcNames
	Schema Schema
}

Resolved is a Config merged with defaults and validated.

type Schema

type Schema map[Entity]Table

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) Col

func (s Schema) Col(c Column) string

Col returns the physical column name for a logical column (unquoted).

func (Schema) Merge

func (s Schema) Merge(override Schema) Schema

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

func (s Schema) SchemaName(e Entity) string

SchemaName returns the optional schema qualifier of an entity.

func (Schema) TableName

func (s Schema) TableName(e Entity) string

TableName returns the physical table name of an entity (unqualified, unquoted).

func (Schema) Validate

func (s Schema) Validate() error

Validate checks that the schema only names known entities and columns and that every identifier is safe. It is meant to run on the merged (default + override) schema.

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.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL