security

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: 50 Imported by: 0

README

ResolveSpec Security Provider

Type-safe, composable security system for ResolveSpec with support for authentication, column-level security (masking), and row-level security (filtering).

Features

  • ✅ Interface-Based - Type-safe providers instead of callbacks
  • ✅ Login/Logout Support - Built-in authentication lifecycle
  • ✅ Two-Factor Authentication (2FA) - Optional TOTP support for enhanced security
  • ✅ Composable - Mix and match different providers
  • ✅ No Global State - Each handler has its own security configuration
  • ✅ Testable - Easy to mock and test
  • ✅ Extensible - Implement custom providers for your needs
  • ✅ Stored Procedures - Database operations use PostgreSQL stored procedures where available, for security and maintainability
  • ✅ Direct Mode - Portable Go/SQL fallback for SQLite, MySQL, or Postgres without the stored procedures installed — no code changes required
  • ✅ OAuth2 / OpenID Connect - Built-in OAuth 2.1 + PKCE authorization server and OIDC provider (RFC 8414, 7591/7592, 7009, 7662, 9068, 9126, 9207, 9449, 8628, 8693): consent, rotating refresh tokens, JWT access tokens, logout, federation; plus an OIDC relying-party client. See OAUTH2_SERVER.md
  • ✅ Password Reset - Self-service password reset with secure token generation and session invalidation

Stored Procedure Architecture

On PostgreSQL, database-backed security providers use stored procedures by default. pkg/security itself contains no SQL; all database access lives in pkg/security/lookup, which can also run the same operations as direct SQL on tables (see Database access (lookup)).

Benefits
  • Security: Database logic is centralized and protected
  • Maintainability: Update database logic without recompiling Go code
  • Performance: Stored procedures are pre-compiled and optimized
  • Testability: Test database logic independently
  • Consistency: Standardized resolvespec_* naming convention
Available Stored Procedures
Procedure Purpose Used By
resolvespec_login Session-based login DatabaseAuthenticator
resolvespec_logout Session invalidation DatabaseAuthenticator
resolvespec_session Session validation DatabaseAuthenticator
resolvespec_login_api_key Exchange a raw header/generic API key for a session (defined in lookup/keystore_schema.sql; direct mode reads user_keys) DatabaseAuthenticator.LoginWithAPIKey
resolvespec_session_update Update session activity DatabaseAuthenticator
resolvespec_refresh_token Token refresh DatabaseAuthenticator
resolvespec_jwt_login JWT user validation JWTAuthenticator
resolvespec_jwt_logout JWT token blacklist JWTAuthenticator
resolvespec_column_security Load column rules DatabaseColumnSecurityProvider
resolvespec_row_security Load row templates DatabaseRowSecurityProvider
resolvespec_oauth_register_client Persist OAuth2 client (RFC 7591) OAuthServer / DatabaseAuthenticator
resolvespec_oauth_get_client Retrieve OAuth2 client by ID OAuthServer / DatabaseAuthenticator
resolvespec_oauth_save_code Persist authorization code OAuthServer / DatabaseAuthenticator
resolvespec_oauth_exchange_code Consume authorization code (single-use) OAuthServer / DatabaseAuthenticator
resolvespec_oauth_introspect Token introspection (RFC 7662) OAuthServer / DatabaseAuthenticator
resolvespec_oauth_revoke Token revocation (RFC 7009) OAuthServer / DatabaseAuthenticator
resolvespec_password_reset_request Create password reset token DatabaseAuthenticator
resolvespec_password_reset Validate token and set new password DatabaseAuthenticator

See lookup/database_schema.sql for complete stored procedure definitions and examples.

Not on Postgres, or don't have the procedures installed? See Database access (lookup) below: every provider can also work directly on tables, on SQLite, MySQL, SQL Server or plain Postgres.

Database access (lookup)

pkg/security itself contains no SQL. Every database-backed provider (DatabaseAuthenticator, JWTAuthenticator, column/row security, DatabaseTwoFactorProvider, DatabasePasskeyProvider, the OAuth2 methods/server, DatabaseKeyStore) calls a store interface from pkg/security/lookup. Two backends implement each store:

  • procedure (lookup/procedure): calls the resolvespec_* stored procedures (p_success / p_error / p_data contract). Postgres only.
  • direct (lookup/direct): plain parameterized SQL on tables, rendered by a per-database dialect (lookup/dialect: postgres, sqlite, mysql, mssql). Table and column names are configurable.

lookup/backends.New(db, cfg, opts) builds a lookup.Provider (all stores) and routes each operation to a backend. The security constructors do this for you from lookup.Config; pass a ready *lookup.Provider with LookupProvider / WithLookupProvider to share one between components.

Choosing the mode
type Config struct {
    Dialect   string         // "postgres", "sqlite", "mysql", "mssql", or one you registered; empty = detect from the driver
    Mode      lookup.Mode    // default for every operation
    Overrides map[lookup.Op]lookup.Mode // per-operation mode, e.g. lookup.OpSession: lookup.ModeDirect
    Procs     lookup.ProcNames // procedure names, empty fields keep the default
    Schema    lookup.Schema    // table/column names, missing entries keep the default
}
Mode Behaviour
ModeDefault (zero value) stored procedure on Postgres, direct SQL on every other dialect
ModeProcedure always the procedure; an error on a non-Postgres dialect
ModeDirect always direct SQL
ModeAuto Postgres: probe pg_proc once per procedure (cached, reset on reconnect), use it if present, else direct. Other dialects: direct

An impossible combination (procedure on SQLite) fails when the provider is built, not on the first request. If the dialect is not set and cannot be detected from the driver, Postgres is assumed. A bad configuration makes every call return the error (fail closed).

// SQLite or MySQL: nothing to configure, direct SQL is the default.
auth := security.NewDatabaseAuthenticator(sqliteDB)

// Postgres without the procedures installed: use tables only.
auth = security.NewDatabaseAuthenticatorWithOptions(db, security.DatabaseAuthenticatorOptions{
    Lookup: lookup.Config{Mode: lookup.ModeDirect},
})

// Postgres, procedures for everything except session lookups.
auth = security.NewDatabaseAuthenticatorWithOptions(db, security.DatabaseAuthenticatorOptions{
    Lookup: lookup.Config{Overrides: map[lookup.Op]lookup.Mode{lookup.OpSession: lookup.ModeDirect}},
})

tfa := security.NewDatabaseTwoFactorProvider(db, nil).WithLookup(lookup.Config{Mode: lookup.ModeDirect})

Other components take the same Lookup / LookupProvider options (or WithLookup / WithLookupProvider on the chain-style types).

Custom names
cfg := lookup.Config{
    Procs: lookup.ProcNames{Login: "myapp_login"},          // only override what differs
    Schema: lookup.Schema{
        lookup.EntityUsers: {Name: "app_users", Columns: map[string]string{"username": "login_name"}},
    },
}

Procedure names, table names and column names are validated as identifiers at construction. Schema entries may also set Schema to qualify a table (schema.table).

Schemas
File Purpose
lookup/database_schema.sql Postgres: tables and stored procedures (procedure backend)
lookup/keystore_schema.sql Postgres: user_keys table and key store procedures
lookup/ddl/{postgres,sqlite,mysql,mssql}.sql tables only, for the direct backend, with the default names

Read them from Go with ddl.SQL("sqlite") or, for drivers that reject multi-statement execution (MySQL, SQL Server), ddl.Statements("mysql"). Do not mix ddl/postgres.sql with database_schema.sql: the procedure schema stores passkey credential ids as bytea and OAuth lists as text[], the direct backend stores base64 / JSON text. The ddl files are starting points: adjust types and collations to your deployment, and set lookup.Config.Schema if you rename anything.

Column and row security

ColumnSecurityProvider / RowSecurityProvider read sec_group_members (optional), sec_column_rules and sec_row_rules, in both backends. A rule belongs to one user or one group; rules apply to the exact schema and table (case-insensitive, never a prefix); a blocking row rule wins, otherwise row templates are combined with AND. A non-numeric user reference is an error, and no rule means no restriction from this provider. WithNoGroupTables() skips the membership table.

Behavioral notes
  • Direct login, register, refresh, API-key login, password reset and passkey login run in one transaction.
  • Passwords are stored as bcrypt; legacy cleartext values are accepted at login and only rewritten when UpgradePasswordHash is enabled.
  • Session tokens use the shape sess_<hex>_<unix-timestamp> in both backends.
  • Direct mode stores bytea / array / jsonb values (passkey credentials, OAuth client lists, key meta) as base64 / JSON text; the Go API is unchanged.
  • OAuth authorization codes are consumed atomically.
  • Adding a database: implement dialect.Dialect, register it with dialect.Register, then set Config.Dialect.
  • Backend conformance: lookup/conformance is one behavioural suite run against every backend (go test ./pkg/security/lookup/backends -run TestConformance). SQLite runs always; Postgres (procedure and direct), MySQL and SQL Server run when RESOLVESPEC_TEST_PG_DSN, RESOLVESPEC_TEST_PG_DIRECT_DSN, RESOLVESPEC_TEST_MYSQL_DSN or RESOLVESPEC_TEST_MSSQL_DSN is set (see the comment in backends/conformance_test.go). Rows are prefixed and removed afterwards. With RESOLVESPEC_TEST_CONTAINERS=1 (and not -short) the container tests start a throwaway database with podman or docker (podman first), run the suite and remove the container, so no DSN is needed.
  • Migration from the old SQLNames / TableNames / QueryMode API: see breaking_changes.md.

Quick Start

import (
    "github.com/bitechdev/ResolveSpec/pkg/security"
    "github.com/bitechdev/ResolveSpec/pkg/restheadspec"
)

// 1. Create security providers
auth := security.NewJWTAuthenticator("your-secret-key", db)
colSec := security.NewDatabaseColumnSecurityProvider(db)
rowSec := security.NewDatabaseRowSecurityProvider(db)

// 2. Combine providers
provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec)

// 3. Create handler and register security hooks
handler := restheadspec.NewHandlerWithGORM(db)
securityList := security.NewSecurityList(provider)
restheadspec.RegisterSecurityHooks(handler, securityList)

// 4. Apply middleware
router := mux.NewRouter()
restheadspec.SetupMuxRoutes(router, handler)
router.Use(security.NewAuthMiddleware(securityList))
router.Use(security.SetSecurityMiddleware(securityList))

Architecture

Spec-Agnostic Design

The security system is completely spec-agnostic - it doesn't depend on any specific spec implementation. Instead, each spec (restheadspec, funcspec, resolvespec) implements its own security integration by adapting to the SecurityContext interface.

┌─────────────────────────────────────┐
│     Security Package (Generic)      │
│  - SecurityContext interface        │
│  - Security providers                │
│  - Core security logic               │
└─────────────────────────────────────┘
           ▲          ▲          ▲
           │          │          │
    ┌──────┘          │          └──────┐
    │                 │                 │
┌───▼────┐      ┌────▼─────┐     ┌────▼──────┐
│RestHead│      │ FuncSpec │     │ResolveSpec│
│  Spec  │      │          │     │           │
│        │      │          │     │           │
│Adapts  │      │ Adapts   │     │  Adapts   │
│to      │      │ to       │     │  to       │
│Security│      │ Security │     │  Security │
│Context │      │ Context  │     │  Context  │
└────────┘      └──────────┘     └───────────┘

Benefits:

  • ✅ No circular dependencies
  • ✅ Each spec can customize security integration
  • ✅ Easy to add new specs
  • ✅ Security logic is reusable across all specs
Core Interfaces

The security system is built on three main interfaces:

1. Authenticator

Handles user authentication lifecycle:

type Authenticator interface {
    Login(ctx context.Context, req LoginRequest) (*LoginResponse, error)
    Logout(ctx context.Context, req LogoutRequest) error
    Authenticate(r *http.Request) (*UserContext, error)
}
2. ColumnSecurityProvider

Manages column-level security (masking/hiding):

type ColumnSecurityProvider interface {
    GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error)
}

Write side (RegisterSecurityHooks, all specs except funcspec):

  • Columns with a hide or mask rule (single-element Path) are removed from create/update payloads in BeforeCreate/BeforeUpdate; the write is not rejected.
  • Match is case-insensitive on the rule path vs payload key, model field/JSON name or gorm column.
  • Rules are preloaded in BeforeHandle (outside the tx); the hook only reads the cache and fails closed if rules were not loaded.
  • Not covered: nested child records, nested Path (JSON sub-values), funcspec.
3. RowSecurityProvider

Manages row-level security (WHERE clause filtering):

type RowSecurityProvider interface {
    GetRowSecurity(ctx context.Context, userID int, schema, table string) (RowSecurity, error)
}
SecurityProvider

The main interface that combines all three:

type SecurityProvider interface {
    Authenticator
    ColumnSecurityProvider
    RowSecurityProvider
}
4. SecurityContext (Spec Integration Interface)

Each spec implements this interface to integrate with the security system:

type SecurityContext interface {
    GetContext() context.Context
    GetUserID() (int, bool)
    GetSchema() string
    GetEntity() string
    GetModel() interface{}
    GetQuery() interface{}
    SetQuery(interface{})
    GetResult() interface{}
    SetResult(interface{})
}

Implementation Examples:

  • restheadspec: Adapts restheadspec.HookContext → SecurityContext
  • funcspec: Adapts funcspec.HookContext → SecurityContext
  • resolvespec: Adapts resolvespec.HookContext → SecurityContext
UserContext

Enhanced user context with complete user information:

type UserContext struct {
    UserID    int            // User's unique ID
    UserName  string         // Username
    UserLevel int            // User privilege level
    SessionID string         // Current session ID
    RemoteID  string         // Remote system ID
    Roles     []string       // User roles
    Email     string         // User email
    Claims    map[string]any // Additional authentication claims
    Meta      map[string]any // Additional metadata (can hold any JSON-serializable values)
}

Available Implementations

Authenticators

HeaderAuthenticator - Simple header-based authentication:

auth := providers.NewHeaderAuthenticator()
// Expects: X-User-ID, X-User-Name, X-User-Level, etc.

DatabaseAuthenticator - Database session-based authentication (Recommended):

auth := security.NewDatabaseAuthenticator(db)
// Supports: Login, Logout, Session management, Token refresh
// All operations use stored procedures: resolvespec_login, resolvespec_logout,
// resolvespec_session, resolvespec_session_update, resolvespec_refresh_token
// Requires: users and user_sessions tables + stored procedures (see lookup/database_schema.sql)

JWTAuthenticator - JWT token authentication with login/logout:

auth := security.NewJWTAuthenticator("secret-key", db)
// Supports: Login, Logout, JWT token validation
// All operations use stored procedures: resolvespec_jwt_login, resolvespec_jwt_logout
// Note: Requires JWT library installation for token signing/verification

totp.Authenticator - Wraps any authenticator with TOTP 2FA:

baseAuth := security.NewDatabaseAuthenticator(db)

// Use in-memory provider (for testing)
tfaProvider := totp.NewMemoryProvider(nil)

// Or use database provider (for production)
tfaProvider := security.NewDatabaseTwoFactorProvider(db, nil)
// Requires: users table with totp fields, user_totp_backup_codes table
// Requires: resolvespec_totp_* stored procedures (see lookup/database_schema.sql)

auth := totp.NewAuthenticator(baseAuth, tfaProvider, nil)
// Supports: TOTP codes, backup codes, QR code generation
// Compatible with Google Authenticator, Microsoft Authenticator, Authy, etc.
Column Security Providers

DatabaseColumnSecurityProvider - Loads rules from database:

colSec := security.NewDatabaseColumnSecurityProvider(db)
// Uses stored procedure: resolvespec_column_security
// Reads sec_column_rules (user rules + rules of the user's sec_group_members groups)

ConfigColumnSecurityProvider - Static configuration:

rules := map[string][]security.ColumnSecurity{
    "public.employees": {
        {Path: []string{"ssn"}, Accesstype: "mask", MaskStart: 5},
    },
}
colSec := providers.NewConfigColumnSecurityProvider(rules)
Row Security Providers

DatabaseRowSecurityProvider - Loads filters from database:

rowSec := security.NewDatabaseRowSecurityProvider(db)
// Uses stored procedure: resolvespec_row_security

ConfigRowSecurityProvider - Static templates:

templates := map[string]string{
    "public.orders": "user_id = {UserID}",
}
blocked := map[string]bool{
    "public.admin_logs": true,
}
rowSec := providers.NewConfigRowSecurityProvider(templates, blocked)

Usage Examples

Example 1: Complete Database-Backed Security with Sessions (restheadspec)
func main() {
    db := setupDatabase()

    // Run migrations (see lookup/database_schema.sql)
    // db.Exec("CREATE TABLE users ...")
    // db.Exec("CREATE TABLE user_sessions ...")

    // Create handler
    handler := restheadspec.NewHandlerWithGORM(db)

    // Create security providers
    auth := security.NewDatabaseAuthenticator(db) // Session-based auth
    colSec := security.NewDatabaseColumnSecurityProvider(db)
    rowSec := security.NewDatabaseRowSecurityProvider(db)

    // Combine providers
    provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec)
    securityList := security.NewSecurityList(provider)

    // Register security hooks for this spec
    restheadspec.RegisterSecurityHooks(handler, securityList)

    // Setup routes
    router := mux.NewRouter()

    // Add auth endpoints
    router.HandleFunc("/auth/login", handleLogin(securityList)).Methods("POST")
    router.HandleFunc("/auth/logout", handleLogout(securityList)).Methods("POST")
    router.HandleFunc("/auth/refresh", handleRefresh(securityList)).Methods("POST")

    // Setup API with security
    apiRouter := router.PathPrefix("/api").Subrouter()
    restheadspec.SetupMuxRoutes(apiRouter, handler)
    apiRouter.Use(security.NewAuthMiddleware(securityList))
    apiRouter.Use(security.SetSecurityMiddleware(securityList))

    http.ListenAndServe(":8080", router)
}

func handleLogin(securityList *security.SecurityList) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var req security.LoginRequest
        json.NewDecoder(r.Body).Decode(&req)

        // Add client info to claims
        req.Claims = map[string]any{
            "ip_address": r.RemoteAddr,
            "user_agent": r.UserAgent(),
        }

        resp, err := securityList.Provider().Login(r.Context(), req)
        if err != nil {
            http.Error(w, err.Error(), http.StatusUnauthorized)
            return
        }

        // Set session cookie (optional)
        http.SetCookie(w, &http.Cookie{
            Name:     "session_token",
            Value:    resp.Token,
            Expires:  time.Now().Add(24 * time.Hour),
            HttpOnly: true,
            Secure:   true, // Use in production with HTTPS
            SameSite: http.SameSiteStrictMode,
        })

        json.NewEncoder(w).Encode(resp)
    }
}

func handleRefresh(securityList *security.SecurityList) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("X-Refresh-Token")

        if refreshable, ok := securityList.Provider().(security.Refreshable); ok {
            resp, err := refreshable.RefreshToken(r.Context(), token)
            if err != nil {
                http.Error(w, err.Error(), http.StatusUnauthorized)
                return
}

Two-Factor Authentication (2FA)

Overview
  • Optional per-user - Enable/disable 2FA individually
  • TOTP standard - Compatible with Google Authenticator, Microsoft Authenticator, Authy, 1Password, etc.
  • Configurable - SHA1/SHA256/SHA512, 6/8 digits, custom time periods
  • Backup codes - One-time recovery codes with secure hashing
  • Clock skew - Handles time differences between client/server
Setup
// 1. Wrap existing authenticator with 2FA support
baseAuth := security.NewDatabaseAuthenticator(db)
tfaProvider := totp.NewMemoryProvider(nil) // Use custom DB implementation in production
tfaAuth := totp.NewAuthenticator(baseAuth, tfaProvider, nil)

// 2. Use as normal authenticator
provider := security.NewCompositeSecurityProvider(tfaAuth, colSec, rowSec)
securityList := security.NewSecurityList(provider)
Enable 2FA for User
// 1. Initiate 2FA setup
secret, err := tfaAuth.Setup2FA(userID, "MyApp", "user@example.com")
// Returns: secret.Secret, secret.QRCodeURL, secret.BackupCodes

// 2. User scans QR code with authenticator app
// Display secret.QRCodeURL as QR code image

// 3. User enters verification code from app
code := "123456" // From authenticator app
err = tfaAuth.Enable2FA(userID, secret.Secret, code)
// 2FA is now enabled for this user

// 4. Store backup codes securely and show to user once
// Display: secret.BackupCodes (10 codes)
Login Flow with 2FA
// 1. User provides credentials
req := security.LoginRequest{
    Username: "user@example.com",
    Password: "password",
}

resp, err := tfaAuth.Login(ctx, req)

// 2. Check if 2FA required
if resp.Requires2FA {
    // Prompt user for 2FA code
    code := getUserInput() // From authenticator app or backup code
    
    // 3. Login again with 2FA code
    req.TwoFactorCode = code
    resp, err = tfaAuth.Login(ctx, req)
    
    // 4. Success - token is returned
    token := resp.Token
}
Manage 2FA
// Disable 2FA
err := tfaAuth.Disable2FA(userID)

// Regenerate backup codes
newCodes, err := tfaAuth.RegenerateBackupCodes(userID, 10)

// Check status
has2FA, err := tfaProvider.Get2FAStatus(userID)
Custom 2FA Storage

Option 1: Use DatabaseTwoFactorProvider (Recommended)

// Uses PostgreSQL stored procedures for all operations
db := setupDatabase()

// Run migrations from lookup/database_schema.sql
// - Add totp_secret, totp_enabled, totp_enabled_at to users table
// - Create user_totp_backup_codes table
// - Create resolvespec_totp_* stored procedures

tfaProvider := security.NewDatabaseTwoFactorProvider(db, nil)
tfaAuth := totp.NewAuthenticator(baseAuth, tfaProvider, nil)

Option 2: Implement Custom Provider

Implement totp.AuthProvider for custom storage:

type DBTwoFactorProvider struct {
    db *gorm.DB
}

func (p *DBTwoFactorProvider) Enable2FA(userID int, secret string, backupCodes []string) error {
    // Store secret and hashed backup codes in database
    return p.db.Exec("UPDATE users SET totp_secret = ?, backup_codes = ? WHERE id = ?", 
        secret, hashCodes(backupCodes), userID).Error
}

func (p *DBTwoFactorProvider) Get2FASecret(userID int) (string, error) {
    var secret string
    err := p.db.Raw("SELECT totp_secret FROM users WHERE id = ?", userID).Scan(&secret).Error
    return secret, err
}

// Implement remaining methods: Generate2FASecret, Validate2FACode, Disable2FA,
// Get2FAStatus, GenerateBackupCodes, ValidateBackupCode
Configuration
config := &totp.Config{
    Algorithm:  "SHA256",  // SHA1, SHA256, SHA512
    Digits:     8,         // 6 or 8
    Period:     30,        // Seconds per code
    SkewWindow: 2,         // Accept codes ±2 periods
}

totp := totp.NewGenerator(config)
tfaAuth := totp.NewAuthenticator(baseAuth, tfaProvider, config)
API Response Structure
// LoginResponse with 2FA
type LoginResponse struct {
    Token              string              `json:"token"`
    Requires2FA        bool                `json:"requires_2fa"`
    TwoFactorSetupData *TwoFactorSecret    `json:"two_factor_setup,omitempty"`
    User               *UserContext        `json:"user"`
}

// TwoFactorSecret for setup
type TwoFactorSecret struct {
    Secret      string   `json:"secret"`         // Base32 encoded
    QRCodeURL   string   `json:"qr_code_url"`    // otpauth://totp/...
    BackupCodes []string `json:"backup_codes"`   // 10 recovery codes
}

// UserContext includes 2FA status
type UserContext struct {
    UserID           int    `json:"user_id"`
    TwoFactorEnabled bool   `json:"two_factor_enabled"`
    // ... other fields
}
Security Best Practices
  • Store secrets encrypted - Never store TOTP secrets in plain text

  • Hash backup codes - Use SHA-256 before storing

  • Rate limit - Limit 2FA verification attempts

  • Require password - Always verify password before disabling 2FA

  • Show backup codes once - Display only during setup/regeneration

  • Log 2FA events - Track enable/disable/failed attempts

  • Mark codes as used - Backup codes are single-use only

          json.NewEncoder(w).Encode(resp)
      } else {
          http.Error(w, "Refresh not supported", http.StatusNotImplemented)
      }
    

    } }


### Example 2: Config-Based Security (No Database)

```go
func main() {
    db := setupDatabase()
    handler := restheadspec.NewHandlerWithGORM(db)

    // Static column security rules
    columnRules := map[string][]security.ColumnSecurity{
        "public.employees": {
            {Path: []string{"ssn"}, Accesstype: "mask", MaskStart: 5},
            {Path: []string{"salary"}, Accesstype: "hide"},
        },
    }

    // Static row security templates
    rowTemplates := map[string]string{
        "public.orders": "user_id = {UserID}",
    }

    // Create providers
    auth := providers.NewHeaderAuthenticator()
    colSec := providers.NewConfigColumnSecurityProvider(columnRules)
    rowSec := providers.NewConfigRowSecurityProvider(rowTemplates, nil)

    // Combine providers and register hooks
    provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec)
    securityList := security.NewSecurityList(provider)
    restheadspec.RegisterSecurityHooks(handler, securityList)

    // Setup routes...
}
Example 3: FuncSpec Security (SQL Query API)
import (
    "github.com/bitechdev/ResolveSpec/pkg/funcspec"
    "github.com/bitechdev/ResolveSpec/pkg/security"
)

func main() {
    db := setupDatabase()

    // Create funcspec handler
    handler := funcspec.NewHandler(db)

    // Create security providers
    auth := security.NewJWTAuthenticator("secret-key", db)
    colSec := security.NewDatabaseColumnSecurityProvider(db)
    rowSec := security.NewDatabaseRowSecurityProvider(db)

    // Combine providers
    provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec)
    securityList := security.NewSecurityList(provider)

    // Register security hooks (audit logging)
    funcspec.RegisterSecurityHooks(handler, securityList)

    // Note: funcspec operates on raw SQL queries, so row/column
    // security is limited. Security should be enforced at the
    // SQL function level or via database policies.

    // Setup routes...
}
Example 4: ResolveSpec Security (REST API)
import (
    "github.com/bitechdev/ResolveSpec/pkg/resolvespec"
    "github.com/bitechdev/ResolveSpec/pkg/security"
)

func main() {
    db := setupDatabase()
    registry := common.NewModelRegistry()

    // Register models
    registry.RegisterModel("public.users", &User{})
    registry.RegisterModel("public.orders", &Order{})

    // Create resolvespec handler
    handler := resolvespec.NewHandler(db, registry)

    // Create security providers
    auth := security.NewDatabaseAuthenticator(db)
    colSec := security.NewDatabaseColumnSecurityProvider(db)
    rowSec := security.NewDatabaseRowSecurityProvider(db)

    // Combine providers
    provider := security.NewCompositeSecurityProvider(auth, colSec, rowSec)
    securityList := security.NewSecurityList(provider)

    // Register security hooks for resolvespec
    resolvespec.RegisterSecurityHooks(handler, securityList)

    // Setup routes...
}
Example 5: Custom Provider

Implement your own provider for complete control:

type MySecurityProvider struct {
    db *gorm.DB
}

func (p *MySecurityProvider) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) {
    // Your custom login logic
}

func (p *MySecurityProvider) Logout(ctx context.Context, req security.LogoutRequest) error {
    // Your custom logout logic
}

func (p *MySecurityProvider) Authenticate(r *http.Request) (*security.UserContext, error) {
    // Your custom authentication logic
}

func (p *MySecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
    // Your custom column security logic
}

func (p *MySecurityProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) {
    // Your custom row security logic
}

// Use it with any spec
provider := &MySecurityProvider{db: db}
securityList := security.NewSecurityList(provider)

// Register with restheadspec
restheadspec.RegisterSecurityHooks(restHandler, securityList)

// Or with funcspec
funcspec.RegisterSecurityHooks(funcHandler, securityList)

// Or with resolvespec
resolvespec.RegisterSecurityHooks(resolveHandler, securityList)

Security Features

Column Security (Masking/Hiding)

Mask SSN (show last 4 digits):

{
    Path:       []string{"ssn"},
    Accesstype: "mask",
    MaskStart:  5,
    MaskChar:   "*",
}
// "123-45-6789" → "*****6789"

Hide entire field:

{
    Path:       []string{"salary"},
    Accesstype: "hide",
}
// Field returns 0 or empty

Nested JSON field masking:

{
    Path:       []string{"address", "street"},
    Accesstype: "mask",
    MaskStart:  10,
}
Row Security (Filtering)

User isolation:

{
    Template: "user_id = {UserID}",
}
// Users only see their own records

Tenant isolation:

{
    Template: "tenant_id = {TenantID} AND user_id = {UserID}",
}

Block all access:

{
    HasBlock: true,
}
// Completely blocks access to the table

Template variables:

  • {UserID} - Current user's ID
  • {PrimaryKeyName} - Primary key column
  • {TableName} - Table name
  • {SchemaName} - Schema name

Request Flow

HTTP Request
    ↓
NewOptionalAuthMiddleware (security package)  ← recommended for spec routes
    ├─ Calls provider.Authenticate(request)
    ├─ On success: adds authenticated UserContext to context
    └─ On failure: adds guest UserContext (UserID=0) to context
    ↓
SetSecurityMiddleware (security package)
    └─ Adds SecurityList to context
    ↓
Spec Handler (restheadspec/funcspec/resolvespec/websocketspec/mqttspec)
    └─ Resolves schema + entity + model from request
    ↓
BeforeHandle Hook (registered by spec via RegisterSecurityHooks)
    ├─ Adapts spec's HookContext → SecurityContext
    ├─ Calls security.CheckModelAuthAllowed(secCtx, operation)
    │   ├─ Loads model rules from context or registry
    │   ├─ SecurityDisabled → allow
    │   ├─ CanPublicRead/Create/Update/Delete → allow unauthenticated
    │   └─ UserID == 0 → 401 unauthorized
    └─ On error: aborts with 401
    ↓
BeforeRead Hook (registered by spec)
    ├─ Adapts spec's HookContext → SecurityContext
    ├─ Calls security.LoadSecurityRules(secCtx, securityList)
    │   ├─ Calls provider.GetColumnSecurity()
    │   └─ Calls provider.GetRowSecurity()
    └─ Caches security rules
    ↓
BeforeScan Hook (registered by spec)
    ├─ Adapts spec's HookContext → SecurityContext
    ├─ Calls security.ApplyRowSecurity(secCtx, securityList)
    └─ Applies row security (adds WHERE clause to query)
    ↓
Database Query (with security filters)
    ↓
AfterRead Hook (registered by spec)
    ├─ Adapts spec's HookContext → SecurityContext
    ├─ Calls security.ApplyColumnSecurity(secCtx, securityList)
    ├─ Applies column security (masks/hides fields)
    └─ Calls security.LogDataAccess(secCtx)
    ↓
HTTP Response (secured data)

Key Points:

  • NewOptionalAuthMiddleware never rejects — it sets guest context on auth failure; BeforeHandle enforces auth after model resolution
  • BeforeHandle fires after model resolution, giving access to model rules and user context simultaneously
  • Each spec registers its own hooks that adapt to SecurityContext
  • Security rules are loaded once and cached for the request
  • Row security is applied to the query (database level)
  • Column security is applied to results (application level)

Testing

The interface-based design makes testing straightforward:

// Mock authenticator for tests
type MockAuthenticator struct {
    UserToReturn *security.UserContext
    ErrorToReturn error
}

func (m *MockAuthenticator) Authenticate(r *http.Request) (*security.UserContext, error) {
    return m.UserToReturn, m.ErrorToReturn
}

// Use in tests
func TestMyHandler(t *testing.T) {
    mockAuth := &MockAuthenticator{
        UserToReturn: &security.UserContext{UserID: 123},
    }

    provider := security.NewCompositeSecurityProvider(
        mockAuth,
        &MockColumnSecurity{},
        &MockRowSecurity{},
    )

    securityList, _ := security.NewSecurityList(provider)
    restheadspec.RegisterSecurityHooks(handler, securityList)
    // ... test your handler
}

Migration Guide

From Old Callback System

If you're upgrading from the old callback-based system:

Old:

security.GlobalSecurity.AuthenticateCallback = myAuthFunc
security.GlobalSecurity.LoadColumnSecurityCallback = myColSecFunc
security.GlobalSecurity.LoadRowSecurityCallback = myRowSecFunc
security.SetupSecurityProvider(handler, &security.GlobalSecurity)

New:

// 1. Wrap your functions in a provider
type MyProvider struct{}

func (p *MyProvider) Authenticate(r *http.Request) (*security.UserContext, error) {
    userID, roles, err := myAuthFunc(r)
    return &security.UserContext{UserID: userID, Roles: strings.Split(roles, ",")}, err
}

func (p *MyProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
    return myColSecFunc(userID, schema, table)
}

func (p *MyProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) {
    return myRowSecFunc(userID, schema, table)
}

func (p *MyProvider) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) {
    return nil, fmt.Errorf("not implemented")
}

func (p *MyProvider) Logout(ctx context.Context, req security.LogoutRequest) error {
    return nil
}

// 2. Create security list and register hooks
provider := &MyProvider{}
securityList := security.NewSecurityList(provider)

// 3. Register with your spec
restheadspec.RegisterSecurityHooks(handler, securityList)
From Old SetupSecurityProvider API

If you're upgrading from the previous interface-based system:

Old:

securityList := security.SetupSecurityProvider(handler, provider)

New:

securityList := security.NewSecurityList(provider)
restheadspec.RegisterSecurityHooks(handler, securityList) // or funcspec/resolvespec

Password Reset

DatabaseAuthenticator implements PasswordResettable for self-service password reset.

Flow
  1. User submits email or username → RequestPasswordReset → server generates a token and returns it for out-of-band delivery (email, SMS, etc.)
  2. User submits the raw token + new password → CompletePasswordReset → password updated, all sessions invalidated
DB Requirements

Run the migrations in lookup/database_schema.sql:

  • user_password_resets table (user_id, token_hash SHA-256, expires_at, used, used_at)
  • resolvespec_password_reset_request stored procedure
  • resolvespec_password_reset stored procedure

Requires the pgcrypto extension (gen_random_bytes, digest) — already used by resolvespec_login.

Usage
auth := security.NewDatabaseAuthenticator(db)

// Step 1 — initiate reset (call after user submits their email)
resp, err := auth.RequestPasswordReset(ctx, security.PasswordResetRequest{
    Email: "user@example.com",
})
// resp.Token is the raw token — deliver it out-of-band
// resp.ExpiresIn is 3600 (1 hour)
// Always returns success regardless of whether the user exists (anti-enumeration)

// Step 2 — complete reset (call after user submits token + new password)
err = auth.CompletePasswordReset(ctx, security.PasswordResetCompleteRequest{
    Token:       rawToken,
    NewPassword: "newSecurePassword",
})
// On success: password updated, all active sessions deleted
Security Notes
  • The raw token is never stored; only its SHA-256 hash is persisted
  • Requesting a reset invalidates any previous unused tokens for that user
  • Tokens expire after 1 hour
  • Completing a reset deletes all active sessions, forcing re-login
  • RequestPasswordReset always returns success even when the email/username is not found, preventing user enumeration
  • Hash the new password with bcrypt before storing (pgcrypto crypt/gen_salt) — see the TODO comment in resolvespec_password_reset
Procedure names

Set through lookup.Config.Procs (lookup.ProcNames):

lookup.ProcNames{
    PasswordResetRequest:  "resolvespec_password_reset_request", // default
    PasswordResetComplete: "resolvespec_password_reset",         // default
}

OAuth2 Authorization Server

The complete guide (consent, OIDC, refresh rotation, DPoP, PAR, device grant, token exchange, logout, relying-party client) is in OAUTH2_SERVER.md. The table below lists the original endpoints.

OAuthServer is a generic OAuth 2.1 + PKCE authorization server. It is not tied to any spec — pkg/resolvemcp uses it, but it can be used standalone with any http.ServeMux.

Endpoints
Method Path RFC
GET /.well-known/oauth-authorization-server RFC 8414 — server metadata
POST /oauth/register RFC 7591 — dynamic client registration
GET /oauth/authorize OAuth 2.1 — start authorization / provider selection
POST /oauth/authorize OAuth 2.1 — login form submission
POST /oauth/token OAuth 2.1 — code exchange + refresh
POST /oauth/revoke RFC 7009 — token revocation
POST /oauth/introspect RFC 7662 — token introspection
GET {ProviderCallbackPath} External provider redirect target
Config
cfg := security.OAuthServerConfig{
    Issuer:               "https://example.com",      // Required — token issuer URL
    ProviderCallbackPath: "/oauth/provider/callback", // External provider redirect target
    LoginTitle:           "My App Login",             // HTML login page title
    PersistClients:       true,  // Store clients in DB (multi-instance safe)
    PersistCodes:         true,  // Store codes in DB (multi-instance safe)
    DefaultScopes:        []string{"openid", "profile"}, // Returned when no scope requested
    AccessTokenTTL:       time.Hour,
    AuthCodeTTL:          5 * time.Minute,
}
Field Default Notes
Issuer — Required; trailing slash is trimmed automatically
ProviderCallbackPath /oauth/provider/callback
LoginTitle "Sign in"
PersistClients false Set true for multi-instance
PersistCodes false Set true for multi-instance; does not require PersistClients
DefaultScopes ["openid","profile","email"]
AccessTokenTTL 24h Also used as expires_in in token responses
AuthCodeTTL 2m
Operating Modes

Mode 1 — Direct login (username/password form)

Pass a *DatabaseAuthenticator to NewOAuthServer. The server renders a login form at GET /oauth/authorize and issues tokens via the stored session after login.

auth := security.NewDatabaseAuthenticator(db)
srv := security.NewOAuthServer(cfg, auth)

Mode 2 — External provider federation

Pass a *DatabaseAuthenticator for persistence (authorization codes, revoke, introspect) and register external providers. The authorize endpoint redirects to the specified provider (via the provider query param) or to the first registered provider by default.

auth := security.NewDatabaseAuthenticator(db)
srv := security.NewOAuthServer(cfg, auth)
srv.RegisterExternalProvider(googleAuth, "google")
srv.RegisterExternalProvider(githubAuth, "github")

Mode 3 — Both

Pass auth for the login form and also register external providers. The authorize page shows both a login form and provider buttons.

srv := security.NewOAuthServer(cfg, auth)
srv.RegisterExternalProvider(googleAuth, "google")
Standalone Usage
mux := http.NewServeMux()
mux.Handle("/.well-known/", srv.HTTPHandler())
mux.Handle("/oauth/", srv.HTTPHandler())
mux.Handle(cfg.ProviderCallbackPath, srv.HTTPHandler())

http.ListenAndServe(":8080", mux)
DB Persistence

When PersistClients: true or PersistCodes: true, the server calls the corresponding DatabaseAuthenticator methods. Both flags default to false (in-memory maps). Enable both for multi-instance deployments.

Requires oauth_clients and oauth_codes tables + 6 stored procedures from lookup/database_schema.sql.

New DB Types
type OAuthServerClient struct {
    ClientID      string   `json:"client_id"`
    RedirectURIs  []string `json:"redirect_uris"`
    ClientName    string   `json:"client_name,omitempty"`
    GrantTypes    []string `json:"grant_types"`
    AllowedScopes []string `json:"allowed_scopes,omitempty"`
}

type OAuthCode struct {
    Code                string    `json:"code"`
    ClientID            string    `json:"client_id"`
    RedirectURI         string    `json:"redirect_uri"`
    ClientState         string    `json:"client_state,omitempty"`
    CodeChallenge       string    `json:"code_challenge"`
    CodeChallengeMethod string    `json:"code_challenge_method"`
    SessionToken        string    `json:"session_token"`
    Scopes              []string  `json:"scopes,omitempty"`
    ExpiresAt           time.Time `json:"expires_at"`
}

type OAuthTokenInfo struct {
    Active   bool     `json:"active"`
    Sub      string   `json:"sub,omitempty"`
    Username string   `json:"username,omitempty"`
    Email    string   `json:"email,omitempty"`
    Roles    []string `json:"roles,omitempty"`
    Exp      int64    `json:"exp,omitempty"`
    Iat      int64    `json:"iat,omitempty"`
}
DatabaseAuthenticator OAuth Methods
auth.OAuthRegisterClient(ctx, client)  // RFC 7591 — persist client
auth.OAuthGetClient(ctx, clientID)     // retrieve client
auth.OAuthSaveCode(ctx, code)          // persist authorization code
auth.OAuthExchangeCode(ctx, code)      // consume code (single-use, deletes on read)
auth.OAuthIntrospectToken(ctx, token)  // RFC 7662 — returns OAuthTokenInfo
auth.OAuthRevokeToken(ctx, token)      // RFC 7009 — revoke session
Procedure names
type ProcNames struct {
    // ... existing fields ...
    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"
}

The main changes:

  1. Security package no longer knows about specific spec types
  2. Each spec registers its own security hooks
  3. More flexible - same security provider works with all specs

Documentation

File Description
QUICK_REFERENCE.md Quick reference guide with examples
KEYSTORE.md Per-user auth keys and key stores
OAUTH2.md OAuth2 client login
OAUTH2_SERVER.md OAuth 2.1 / OpenID Connect server and relying-party client (full guide)
OAUTH2_REFRESH_QUICK_REFERENCE.md / OAUTH2_REFRESH_TOKEN_IMPLEMENTATION.md OAuth2 refresh tokens
PASSKEY_QUICK_REFERENCE.md WebAuthn passkeys
SECURITY_FEATURES.md Security feature overview
breaking_changes.md Migration notes (lookup refactor, full OAuth2/OIDC schema changes)
examples.go, examples_funcspec.go, oauth2_examples.go, oauth2_full_example.go, passkey_examples.go Working provider implementations

API Reference

Context Helpers

Get user information from request context:

userCtx, ok := security.GetUserContext(ctx)
userID, ok := security.GetUserID(ctx)
userName, ok := security.GetUserName(ctx)
userLevel, ok := security.GetUserLevel(ctx)
sessionID, ok := security.GetSessionID(ctx)
remoteID, ok := security.GetRemoteID(ctx)
roles, ok := security.GetUserRoles(ctx)
email, ok := security.GetUserEmail(ctx)
Optional Interfaces

Implement these for additional features:

Refreshable - Token refresh support:

type Refreshable interface {
    RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error)
}

Validatable - Token validation:

type Validatable interface {
    ValidateToken(ctx context.Context, token string) (bool, error)
}

Cacheable - Cache management:

type Cacheable interface {
    ClearCache(ctx context.Context, userID int, schema, table string) error
}

PasswordResettable - Self-service password reset:

type PasswordResettable interface {
    RequestPasswordReset(ctx context.Context, req PasswordResetRequest) (*PasswordResetResponse, error)
    CompletePasswordReset(ctx context.Context, req PasswordResetCompleteRequest) error
}

Benefits Over Callbacks

Feature Old (Callbacks) New (Interfaces)
Type Safety ❌ Callbacks can be nil ✅ Compile-time verification
Global State ❌ GlobalSecurity variable ✅ Dependency injection
Testability ⚠️ Need to set globals ✅ Easy to mock
Composability ❌ Single provider only ✅ Mix and match
Login/Logout ❌ Not supported ✅ Built-in
Extensibility ⚠️ Limited ✅ Optional interfaces

Common Patterns

Caching Security Rules
type CachedProvider struct {
    inner security.ColumnSecurityProvider
    cache *cache.Cache
}

func (p *CachedProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
    key := fmt.Sprintf("%d:%s.%s", userID, schema, table)
    if cached, found := p.cache.Get(key); found {
        return cached.([]security.ColumnSecurity), nil
    }

    rules, err := p.inner.GetColumnSecurity(ctx, userID, schema, table)
    if err == nil {
        p.cache.Set(key, rules, cache.DefaultExpiration)
    }
    return rules, err
}
Role-Based Security
func (p *MyProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]security.ColumnSecurity, error) {
    userCtx, _ := security.GetUserContext(ctx)

    if contains(userCtx.Roles, "admin") {
        return []security.ColumnSecurity{}, nil // No restrictions
    }

    return loadRestrictionsForUser(userID, schema, table), nil
}
Multi-Tenant Isolation
func (p *MyProvider) GetRowSecurity(ctx context.Context, userID int, schema, table string) (security.RowSecurity, error) {
    tenantID := getUserTenant(userID)

    return security.RowSecurity{
        Template: fmt.Sprintf("tenant_id = %d AND user_id = {UserID}", tenantID),
    }, nil
}

Model-Level Access Control

Use ModelRules (from pkg/modelregistry) to control per-entity auth behavior:

modelregistry.RegisterModelWithRules("public.products", &Product{}, modelregistry.ModelRules{
    SecurityDisabled: false,   // true = skip all auth checks
    CanPublicRead:    true,    // unauthenticated GET allowed
    CanPublicCreate:  false,   // requires auth
    CanPublicUpdate:  false,   // requires auth
    CanPublicDelete:  false,   // requires auth
    CanUpdate:        true,    // authenticated users can update
    CanDelete:        false,   // authenticated users cannot delete
})

CheckModelAuthAllowed(secCtx, operation) applies these rules in BeforeHandle:

  1. SecurityDisabled → allow all
  2. CanPublicRead/Create/Update/Delete → allow unauthenticated for that operation
  3. Guest (UserID == 0) → return 401
  4. Authenticated → allow (operation-specific CanUpdate/CanDelete checked in BeforeUpdate/BeforeDelete)

Middleware and Handler API

NewAuthMiddleware

Standard middleware that authenticates all requests and returns 401 on failure:

router.Use(security.NewAuthMiddleware(securityList))
NewOptionalAuthMiddleware

Middleware for spec routes — always continues; sets guest context on auth failure:

// Use with RegisterSecurityHooks — auth enforcement is deferred to BeforeHandle
apiRouter.Use(security.NewOptionalAuthMiddleware(securityList))
apiRouter.Use(security.SetSecurityMiddleware(securityList))
restheadspec.RegisterSecurityHooks(handler, securityList)  // registers BeforeHandle

Routes can skip authentication using the SkipAuth helper:

func PublicHandler(w http.ResponseWriter, r *http.Request) {
    ctx := security.SkipAuth(r.Context())
    // This route will bypass authentication
    // A guest user context will be set instead
}

router.Handle("/public", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx := security.SkipAuth(r.Context())
    PublicHandler(w, r.WithContext(ctx))
}))

When authentication is skipped, a guest user context is automatically set:

  • UserID: 0
  • UserName: "guest"
  • Roles: ["guest"]
  • RemoteID: Request's remote address

Routes can use optional authentication with the OptionalAuth helper:

func OptionalAuthHandler(w http.ResponseWriter, r *http.Request) {
    ctx := security.OptionalAuth(r.Context())
    r = r.WithContext(ctx)

    // This route will try to authenticate
    // If authentication succeeds, authenticated user context is set
    // If authentication fails, guest user context is set instead

    userCtx, _ := security.GetUserContext(r.Context())
    if userCtx.UserID == 0 {
        // Guest user
        fmt.Fprintf(w, "Welcome, guest!")
    } else {
        // Authenticated user
        fmt.Fprintf(w, "Welcome back, %s!", userCtx.UserName)
    }
}

router.Handle("/home", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx := security.OptionalAuth(r.Context())
    OptionalAuthHandler(w, r.WithContext(ctx))
}))

Authentication Modes Summary:

  • Required (default): Authentication must succeed or returns 401
  • SkipAuth: Bypasses authentication entirely, always sets guest context
  • OptionalAuth: Tries authentication, falls back to guest context if it fails
NewAuthHandler

Standalone authentication handler (without middleware wrapping):

// Use when you need authentication logic without middleware
authHandler := security.NewAuthHandler(securityList, myHandler)
http.Handle("/api/protected", authHandler)
NewOptionalAuthHandler

Standalone optional authentication handler that tries to authenticate but falls back to guest:

// Use for routes that should work for both authenticated and guest users
optionalHandler := security.NewOptionalAuthHandler(securityList, myHandler)
http.Handle("/home", optionalHandler)

// Example handler that checks user context
func myHandler(w http.ResponseWriter, r *http.Request) {
    userCtx, _ := security.GetUserContext(r.Context())
    if userCtx.UserID == 0 {
        fmt.Fprintf(w, "Welcome, guest!")
    } else {
        fmt.Fprintf(w, "Welcome back, %s!", userCtx.UserName)
    }
}
Helper Functions

Extract user information from context:

// Get full user context
userCtx, ok := security.GetUserContext(ctx)

// Get specific fields
userID, ok := security.GetUserID(ctx)
userName, ok := security.GetUserName(ctx)
userLevel, ok := security.GetUserLevel(ctx)
sessionID, ok := security.GetSessionID(ctx)
remoteID, ok := security.GetRemoteID(ctx)
roles, ok := security.GetUserRoles(ctx)
email, ok := security.GetUserEmail(ctx)
meta, ok := security.GetUserMeta(ctx)
Metadata Support

The Meta field in UserContext can hold any JSON-serializable values:

// Set metadata during login
loginReq := security.LoginRequest{
    Username: "user@example.com",
    Password: "password",
    Meta: map[string]any{
        "department": "engineering",
        "location": "US",
        "preferences": map[string]any{
            "theme": "dark",
        },
    },
}

// Access metadata in handlers
meta, ok := security.GetUserMeta(ctx)
if ok {
    department := meta["department"].(string)
}

License

Part of the ResolveSpec project.

Documentation

Index

Constants

View Source
const (
	// Context keys for user information
	UserIDKey       contextKey = "user_id"
	UserNameKey     contextKey = "user_name"
	UserLevelKey    contextKey = "user_level"
	SessionIDKey    contextKey = "session_id"
	SessionRIDKey   contextKey = "session_rid"
	RemoteIDKey     contextKey = "remote_id"
	UserRolesKey    contextKey = "user_roles"
	UserEmailKey    contextKey = "user_email"
	UserContextKey  contextKey = "user_context"
	UserMetaKey     contextKey = "user_meta"
	SkipAuthKey     contextKey = "skip_auth"
	OptionalAuthKey contextKey = "optional_auth"
	ModelRulesKey   contextKey = "model_rules"
)
View Source
const (
	KeyTypeJWTSecret  = sectypes.KeyTypeJWTSecret
	KeyTypeHeaderAPI  = sectypes.KeyTypeHeaderAPI
	KeyTypeOAuth2     = sectypes.KeyTypeOAuth2
	KeyTypeGenericAPI = sectypes.KeyTypeGenericAPI
)

Variables

View Source
var ErrNoColumnSecurity = errors.New("no column security data")

ErrNoColumnSecurity is the column-security equivalent of ErrNoRowSecurity.

View Source
var ErrNoRowSecurity = errors.New("no row security data")

ErrNoRowSecurity is returned by GetRowSecurityTemplate when no row security entry is loaded for the user and table. It means "no rules", as opposed to a failure, which callers must treat as fatal.

Functions

func ApplyColumnSecurity added in v0.0.63

func ApplyColumnSecurity(secCtx SecurityContext, securityList *SecurityList) error

ApplyColumnSecurity is a public wrapper for applyColumnSecurity that accepts a SecurityContext This allows other packages to apply column-level security using the generic interface

func ApplyRowSecurity added in v0.0.63

func ApplyRowSecurity(secCtx SecurityContext, securityList *SecurityList) error

ApplyRowSecurity is a public wrapper for applyRowSecurity that accepts a SecurityContext This allows other packages to apply row-level security using the generic interface

func ApplyTxSettings added in v1.2.7

func ApplyTxSettings(secCtx SecurityContext, 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. The SQL lives in lookup.ApplyTxSettings.

func ApplyWriteColumnSecurity added in v1.2.7

func ApplyWriteColumnSecurity(secCtx SecurityContext, securityList *SecurityList) error

ApplyWriteColumnSecurity removes columns the user may not see (column security "hide" or "mask") from the create/update payload in place, so a hidden or masked column can never be written. It only reads the rules cache (see PreloadSecurityRules) and never queries the provider, so it is safe inside the transaction. Models with security disabled are skipped. Without a loaded rule set for a known user it fails closed.

func CheckModelAuthAllowed added in v1.0.56

func CheckModelAuthAllowed(secCtx SecurityContext, operation string) error

CheckModelAuthAllowed checks whether the requested operation is permitted based on model rules and the current user's authentication state. It is intended for use in a BeforeHandle hook, fired after model resolution.

Logic:

  1. Load model rules from context (set by NewModelAuthMiddleware) or fall back to registry.
  2. SecurityDisabled → allow.
  3. operation == "read" && CanPublicRead → allow.
  4. operation == "create" && CanPublicCreate → allow.
  5. operation == "update" && CanPublicUpdate → allow.
  6. operation == "delete" && CanPublicDelete → allow.
  7. Guest (UserID == 0) → return "authentication required".
  8. Authenticated user → allow (operation-specific checks remain in BeforeUpdate/BeforeDelete).

func CheckModelCreateAllowed added in v1.2.12

func CheckModelCreateAllowed(secCtx SecurityContext) error

CheckModelCreateAllowed returns an error if CanCreate is false for the model. Rules are read from context with a fallback to the model registry; an unregistered model is allowed.

func CheckModelDeleteAllowed added in v1.0.55

func CheckModelDeleteAllowed(secCtx SecurityContext) error

CheckModelDeleteAllowed is the public wrapper for checkModelDeleteAllowed.

func CheckModelUpdateAllowed added in v1.0.55

func CheckModelUpdateAllowed(secCtx SecurityContext) error

CheckModelUpdateAllowed is the public wrapper for checkModelUpdateAllowed.

func ClearSessionCookie added in v1.0.64

func ClearSessionCookie(w http.ResponseWriter, opts ...SessionCookieOptions)

ClearSessionCookie expires the session_token cookie, effectively logging the user out on the browser side. Call this after a successful Authenticator.Logout() call.

Example:

err := auth.Logout(r.Context(), req)
if err != nil { ... }
security.ClearSessionCookie(w)

func ExampleOAuth2AllProviders added in v1.0.48

func ExampleOAuth2AllProviders()

Example: All OAuth2 Providers at Once

func ExampleOAuth2Complete added in v1.0.48

func ExampleOAuth2Complete()

Example: Complete OAuth2 Integration with Database Setup

func ExampleOAuth2Custom added in v1.0.48

func ExampleOAuth2Custom()

Example: Custom OAuth2 Provider

func ExampleOAuth2FullClient added in v1.2.12

func ExampleOAuth2FullClient()

ExampleOAuth2FullClient is the relying-party side: log users in with any OpenID Connect provider (including the server above). Discovery, PKCE, nonce and id_token validation are automatic.

func ExampleOAuth2FullServer added in v1.2.12

func ExampleOAuth2FullServer()

ExampleOAuth2FullServer runs a complete OAuth 2.1 / OpenID Connect provider: login, consent, rotating refresh tokens, JWT access tokens, DPoP, PAR, the device grant and token exchange. OAUTH2_SERVER.md walks through every endpoint.

func ExampleOAuth2GitHub added in v1.0.48

func ExampleOAuth2GitHub()

Example: OAuth2 Authentication with GitHub

func ExampleOAuth2Google added in v1.0.48

func ExampleOAuth2Google()

Example: OAuth2 Authentication with Google

func ExampleOAuth2Logout added in v1.0.48

func ExampleOAuth2Logout()

Example: OAuth2 Logout

func ExampleOAuth2MultiProvider added in v1.0.48

func ExampleOAuth2MultiProvider()

Example: Multi-Provider OAuth2 with Security Integration

func ExampleOAuth2TokenRefresh added in v1.0.48

func ExampleOAuth2TokenRefresh()

Example: OAuth2 with Token Refresh

func GetModelRulesFromContext added in v1.0.55

func GetModelRulesFromContext(ctx context.Context) (modelregistry.ModelRules, bool)

GetModelRulesFromContext extracts ModelRules stored by NewModelAuthMiddleware

func GetRemoteID added in v0.0.63

func GetRemoteID(ctx context.Context) (string, bool)

GetRemoteID extracts the remote ID from context

func GetSessionCookie added in v1.0.64

func GetSessionCookie(r *http.Request, opts ...SessionCookieOptions) string

GetSessionCookie returns the session token value from the request cookie, or empty string if not present.

Example:

token := security.GetSessionCookie(r)

func GetSessionID added in v0.0.63

func GetSessionID(ctx context.Context) (string, bool)

GetSessionID extracts the session ID from context

func GetSessionRID added in v0.0.81

func GetSessionRID(ctx context.Context) (int64, bool)

GetSessionID extracts the session ID from context

func GetUserEmail added in v0.0.63

func GetUserEmail(ctx context.Context) (string, bool)

GetUserEmail extracts user email from context

func GetUserID

func GetUserID(ctx context.Context) (int, bool)

GetUserID extracts the user ID from context

func GetUserLevel added in v0.0.63

func GetUserLevel(ctx context.Context) (int, bool)

GetUserLevel extracts the user level from context

func GetUserMeta added in v0.0.64

func GetUserMeta(ctx context.Context) (map[string]any, bool)

GetUserMeta extracts user metadata from context

func GetUserName added in v0.0.63

func GetUserName(ctx context.Context) (string, bool)

GetUserName extracts the user name from context

func GetUserRoles

func GetUserRoles(ctx context.Context) ([]string, bool)

GetUserRoles extracts user roles from context

func IsModelSecurityDisabled added in v1.1.49

func IsModelSecurityDisabled(secCtx SecurityContext) bool

IsModelSecurityDisabled reports whether all model-level security processing is disabled for the model. This is distinct from ShouldSkipRowSecurity: CanPublicRead skips row filtering for reads but must still allow other read security, such as column masking, to be loaded.

func LoadSecurityRules added in v0.0.63

func LoadSecurityRules(secCtx SecurityContext, securityList *SecurityList) error

LoadSecurityRules is a public wrapper for loadSecurityRules that accepts a SecurityContext This allows other packages to load security rules using the generic interface

func LogDataAccess added in v0.0.63

func LogDataAccess(secCtx SecurityContext) error

LogDataAccess is a public wrapper for logDataAccess that accepts a SecurityContext This allows other packages to use the audit logging functionality

func NewAuthHandler added in v0.0.64

func NewAuthHandler(securityList *SecurityList, next http.Handler) http.Handler

NewAuthHandler creates an authentication handler that can be used standalone This handler performs authentication and returns 401 if authentication fails Use this when you need authentication logic without middleware wrapping

func NewAuthMiddleware added in v0.0.63

func NewAuthMiddleware(securityList *SecurityList) func(http.Handler) http.Handler

NewAuthMiddleware creates an authentication middleware with the given security list This middleware extracts user authentication from the request and adds it to context Routes can skip authentication by setting SkipAuthKey context value (use SkipAuth helper) Routes can use optional authentication by setting OptionalAuthKey context value (use OptionalAuth helper) When authentication is skipped or fails with optional auth, a guest user context is set instead

func NewModelAuthMiddleware added in v1.0.55

func NewModelAuthMiddleware(securityList *SecurityList, modelName string) func(http.Handler) http.Handler

NewModelAuthMiddleware creates authentication middleware that respects ModelRules for the given model name. It first checks if ModelRules are set for the model:

  • If SecurityDisabled is true, authentication is skipped and a guest context is set.
  • Otherwise, all checks from NewAuthMiddleware apply (SkipAuthKey, provider check, OptionalAuthKey, Authenticate).

If the model is not found in any registry, the middleware falls back to standard NewAuthMiddleware behaviour.

func NewOptionalAuthHandler added in v0.0.64

func NewOptionalAuthHandler(securityList *SecurityList, next http.Handler) http.Handler

NewOptionalAuthHandler creates an optional authentication handler that can be used standalone This handler tries to authenticate but falls back to guest context if authentication fails Use this for routes that should show personalized content for authenticated users but still work for guests

func NewOptionalAuthMiddleware added in v1.0.56

func NewOptionalAuthMiddleware(securityList *SecurityList) func(http.Handler) http.Handler

NewOptionalAuthMiddleware creates authentication middleware that always continues. On auth failure, a guest user context is set instead of returning 401. Intended for spec routes where auth enforcement is deferred to a BeforeHandle hook after model resolution.

func OptionalAuth added in v0.0.64

func OptionalAuth(ctx context.Context) context.Context

OptionalAuth returns a context with optional auth flag set to true Use this to mark routes that should try to authenticate, but fall back to guest if authentication fails

func PasskeyAuthenticationExample added in v1.0.48

func PasskeyAuthenticationExample()

PasskeyAuthenticationExample demonstrates passkey (WebAuthn/FIDO2) authentication

func PasskeyClientSideExample added in v1.0.48

func PasskeyClientSideExample() string

PasskeyClientSideExample shows the client-side JavaScript code needed

func PasskeyHTTPHandlersExample added in v1.0.48

func PasskeyHTTPHandlersExample(auth *DatabaseAuthenticator)

PasskeyHTTPHandlersExample shows HTTP handlers for passkey authentication

func PreloadSecurityRules added in v1.2.7

func PreloadSecurityRules(secCtx SecurityContext, securityList *SecurityList, operation string) error

PreloadSecurityRules loads column/row security rules into the SecurityList cache for read operations. Call it from a BeforeHandle hook, i.e. before the handler opens its transaction, so the provider queries do not need a second pooled connection while the transaction holds one. Later LoadSecurityRules calls in the same request are then cache hits. Reads load column and row rules; create/update load the column rules that ApplyWriteColumnSecurity reads. Other operations and models with security disabled are skipped.

func SetSecurityMiddleware

func SetSecurityMiddleware(securityList *SecurityList) func(http.Handler) http.Handler

SetSecurityMiddleware adds security context to requests This middleware should be applied after AuthMiddleware

func SetSessionCookie added in v1.0.64

func SetSessionCookie(w http.ResponseWriter, loginResp *LoginResponse, opts ...SessionCookieOptions)

SetSessionCookie writes the session_token cookie to the response after a successful login. Call this immediately after a successful Authenticator.Login() call.

Example:

resp, err := auth.Login(r.Context(), req)
if err != nil { ... }
security.SetSessionCookie(w, resp)
json.NewEncoder(w).Encode(resp)

func ShouldSkipRowSecurity added in v1.1.49

func ShouldSkipRowSecurity(secCtx SecurityContext, operation string) bool

ShouldSkipRowSecurity reports whether row-security enforcement should be skipped for the operation. It uses the same model-rule resolution as CheckModelAuthAllowed so the model registry remains the single source of truth for security behavior.

func SkipAuth added in v0.0.64

func SkipAuth(ctx context.Context) context.Context

SkipAuth returns a context with skip auth flag set to true Use this to mark routes that should bypass authentication middleware

func StampTxSettings added in v1.2.7

func StampTxSettings(secCtx SecurityContext, list *SecurityList, tx common.Database) error

StampTxSettings runs the list's TxSettingsFunc and applies the result to tx as transaction-local settings (set_config(name, value, true)). No-op when no function is configured or it returns no settings. tx must be the transaction itself, never the pool: the settings are lost on any other connection.

func WithAuth added in v0.0.83

func WithAuth(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request)

WithAuth wraps an HTTPFuncType handler with required authentication This function performs authentication and returns 401 if authentication fails Use this for handlers that require authenticated users

Usage:

handler := funcspec.NewHandler(db)
wrappedHandler := security.WithAuth(handler.SqlQueryList("SELECT * FROM orders WHERE user_id = [rid_user]", false, false, false), securityList)
router.HandleFunc("/api/orders", wrappedHandler)

func WithAuthAndSecurity added in v0.0.83

func WithAuthAndSecurity(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request)

WithAuthAndSecurity wraps an HTTPFuncType handler with both authentication and security context This is a convenience function that combines WithAuth and WithSecurityContext Use this when you need both authentication and security context for a handler

Usage:

handler := funcspec.NewHandler(db)
wrappedHandler := security.WithAuthAndSecurity(handler.SqlQueryList("SELECT * FROM users", false, false, false), securityList)
router.HandleFunc("/api/users", wrappedHandler)

func WithOptionalAuth added in v0.0.83

func WithOptionalAuth(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request)

WithOptionalAuth wraps an HTTPFuncType handler with optional authentication This function tries to authenticate but falls back to guest context if authentication fails Use this for handlers that should show personalized content for authenticated users but still work for guests

Usage:

handler := funcspec.NewHandler(db)
wrappedHandler := security.WithOptionalAuth(handler.SqlQueryList("SELECT * FROM products", false, false, false), securityList)
router.HandleFunc("/api/products", wrappedHandler)

func WithOptionalAuthAndSecurity added in v0.0.83

func WithOptionalAuthAndSecurity(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request)

WithOptionalAuthAndSecurity wraps an HTTPFuncType handler with optional authentication and security context This is a convenience function that combines WithOptionalAuth and WithSecurityContext Use this when you want optional authentication and security context for a handler

Usage:

handler := funcspec.NewHandler(db)
wrappedHandler := security.WithOptionalAuthAndSecurity(handler.SqlQueryList("SELECT * FROM products", false, false, false), securityList)
router.HandleFunc("/api/products", wrappedHandler)

func WithSecurityContext added in v0.0.83

func WithSecurityContext(handler func(http.ResponseWriter, *http.Request), securityList *SecurityList) func(http.ResponseWriter, *http.Request)

WithSecurityContext wraps an HTTPFuncType handler with security context This function allows you to add security context to specific handler functions without needing to apply middleware globally

Usage:

handler := funcspec.NewHandler(db)
wrappedHandler := security.WithSecurityContext(handler.SqlQueryList("SELECT * FROM users", false, false, false), securityList)
router.HandleFunc("/api/users", wrappedHandler)

Types

type APIKeyLoginable added in v1.2.12

type APIKeyLoginable interface {
	// LoginWithAPIKey validates the raw API key and creates a session for its user.
	// Unknown, expired and inactive keys all yield the same generic error.
	LoginWithAPIKey(ctx context.Context, rawKey string, claims map[string]any) (*LoginResponse, error)
}

APIKeyLoginable allows providers to exchange a raw API key for a session.

type AccessTokenClaims added in v1.2.12

type AccessTokenClaims struct {
	Subject   string
	UserID    int
	ClientID  string
	Scopes    []string
	Audience  []string
	JTI       string
	SessionID string
	ExpiresAt time.Time
	// DPoPKey is the thumbprint of the key the token is bound to, or "".
	DPoPKey string
	// JWT is true for RFC 9068 JWT access tokens.
	JWT bool
}

AccessTokenClaims describes a verified access token.

type Authenticator added in v0.0.63

type Authenticator interface {
	// Login authenticates credentials and returns a token
	Login(ctx context.Context, req LoginRequest) (*LoginResponse, error)

	// LoginWithCookie authenticates credentials and, when cookie sessions are enabled,
	// writes the session cookie to w. Implementations that do not support cookies
	// should delegate to Login and ignore w.
	LoginWithCookie(ctx context.Context, req LoginRequest, w http.ResponseWriter) (*LoginResponse, error)

	// Logout invalidates a user's session/token
	Logout(ctx context.Context, req LogoutRequest) error

	// LogoutWithCookie invalidates a user's session/token and, when cookie sessions are
	// enabled, clears the session cookie on w. Implementations that do not support cookies
	// should delegate to Logout and ignore w.
	LogoutWithCookie(ctx context.Context, req LogoutRequest, w http.ResponseWriter) error

	// Authenticate extracts and validates user from HTTP request
	// Returns UserContext or error if authentication fails
	Authenticate(r *http.Request) (*UserContext, error)

	// SetAuthenticateCallback registers a fallback called when primary authentication fails.
	// If the callback returns a non-nil UserContext, that result is used instead of the error.
	SetAuthenticateCallback(fn func(r *http.Request) (*UserContext, error))
}

Authenticator handles user authentication operations

type CONTEXT_KEY added in v0.0.20

type CONTEXT_KEY string
const SECURITY_CONTEXT_KEY CONTEXT_KEY = "SecurityList"

type Cacheable added in v0.0.63

type Cacheable interface {
	// ClearCache clears cached security rules for a user/entity
	ClearCache(ctx context.Context, userID int, schema, table string) error
}

Cacheable allows providers to support caching of security rules

type ChainAuthenticator added in v1.0.108

type ChainAuthenticator struct {
	// contains filtered or unexported fields
}

ChainAuthenticator tries each authenticator in order, returning the first success. Login and Logout are delegated to the primary authenticator.

func NewChainAuthenticator added in v1.0.108

func NewChainAuthenticator(primary Authenticator, rest ...Authenticator) *ChainAuthenticator

NewChainAuthenticator creates a ChainAuthenticator from the given authenticators. At least one authenticator is required; the first is treated as primary for Login/Logout.

func (*ChainAuthenticator) Authenticate added in v1.0.108

func (c *ChainAuthenticator) Authenticate(r *http.Request) (*UserContext, error)

func (*ChainAuthenticator) Login added in v1.0.108

func (*ChainAuthenticator) LoginWithAPIKey added in v1.2.12

func (c *ChainAuthenticator) LoginWithAPIKey(ctx context.Context, rawKey string, claims map[string]any) (*LoginResponse, error)

LoginWithAPIKey tries each authenticator that supports API key login and returns the first success. Failures collapse to one generic error.

func (*ChainAuthenticator) LoginWithCookie added in v1.0.112

func (*ChainAuthenticator) Logout added in v1.0.108

func (*ChainAuthenticator) LogoutWithCookie added in v1.0.112

func (c *ChainAuthenticator) LogoutWithCookie(ctx context.Context, req LogoutRequest, w http.ResponseWriter) error

func (*ChainAuthenticator) SetAuthenticateCallback added in v1.0.112

func (c *ChainAuthenticator) SetAuthenticateCallback(fn func(r *http.Request) (*UserContext, error))

type ColumnSecurity

type ColumnSecurity = sectypes.ColumnSecurity

type ColumnSecurityProvider added in v0.0.63

type ColumnSecurityProvider interface {
	// GetColumnSecurity loads column security rules for a user and entity
	GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error)
}

ColumnSecurityProvider handles column-level security (masking/hiding)

type CompositeSecurityProvider added in v0.0.63

type CompositeSecurityProvider struct {
	// contains filtered or unexported fields
}

CompositeSecurityProvider combines multiple security providers Allows separating authentication, column security, and row security concerns

func NewCompositeSecurityProvider added in v0.0.63

func NewCompositeSecurityProvider(
	auth Authenticator,
	colSec ColumnSecurityProvider,
	rowSec RowSecurityProvider,
) (*CompositeSecurityProvider, error)

NewCompositeSecurityProvider creates a composite provider All parameters are required

func (*CompositeSecurityProvider) Authenticate added in v0.0.63

func (c *CompositeSecurityProvider) Authenticate(r *http.Request) (*UserContext, error)

Authenticate delegates to the authenticator

func (*CompositeSecurityProvider) ClearCache added in v0.0.63

func (c *CompositeSecurityProvider) ClearCache(ctx context.Context, userID int, schema, table string) error

ClearCache implements Cacheable if any provider supports it

func (*CompositeSecurityProvider) GetColumnSecurity added in v0.0.63

func (c *CompositeSecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error)

GetColumnSecurity delegates to the column security provider

func (*CompositeSecurityProvider) GetRowSecurity added in v0.0.63

func (c *CompositeSecurityProvider) GetRowSecurity(ctx context.Context, userRef any, schema, table string) (RowSecurity, error)

GetRowSecurity delegates to the row security provider

func (*CompositeSecurityProvider) Login added in v0.0.63

Login delegates to the authenticator

func (*CompositeSecurityProvider) LoginWithAPIKey added in v1.2.12

func (c *CompositeSecurityProvider) LoginWithAPIKey(ctx context.Context, rawKey string, claims map[string]any) (*LoginResponse, error)

LoginWithAPIKey implements APIKeyLoginable if the authenticator supports it

func (*CompositeSecurityProvider) LoginWithCookie added in v1.0.110

LoginWithCookie delegates to the authenticator

func (*CompositeSecurityProvider) Logout added in v0.0.63

Logout delegates to the authenticator

func (*CompositeSecurityProvider) LogoutWithCookie added in v1.0.110

LogoutWithCookie delegates to the authenticator

func (*CompositeSecurityProvider) RefreshToken added in v0.0.63

func (c *CompositeSecurityProvider) RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error)

RefreshToken implements Refreshable if the authenticator supports it

func (*CompositeSecurityProvider) SetAuthenticateCallback added in v1.0.112

func (c *CompositeSecurityProvider) SetAuthenticateCallback(fn func(r *http.Request) (*UserContext, error))

SetAuthenticateCallback delegates to the authenticator

func (*CompositeSecurityProvider) ValidateToken added in v0.0.63

func (c *CompositeSecurityProvider) ValidateToken(ctx context.Context, token string) (bool, error)

ValidateToken implements Validatable if the authenticator supports it

type CreateKeyRequest added in v1.0.78

type CreateKeyRequest = sectypes.CreateKeyRequest

type CreateKeyResponse added in v1.0.78

type CreateKeyResponse = sectypes.CreateKeyResponse

type DatabaseAuthenticator added in v0.0.63

type DatabaseAuthenticator struct {
	// contains filtered or unexported fields
}

DatabaseAuthenticator provides session-based authentication with database storage All database operations go through stored procedures for security and consistency Procedure names and modes are configured through lookup.Config (see lookup.DefaultProcNames) See lookup/database_schema.sql for procedure definitions Also supports multiple OAuth2 providers configured with WithOAuth2() Also supports passkey authentication configured with WithPasskey()

func NewDatabaseAuthenticator added in v0.0.63

func NewDatabaseAuthenticator(db *sql.DB) *DatabaseAuthenticator

func NewDatabaseAuthenticatorWithOptions added in v0.0.86

func NewDatabaseAuthenticatorWithOptions(db *sql.DB, opts DatabaseAuthenticatorOptions) *DatabaseAuthenticator

func NewFacebookAuthenticator added in v1.0.48

func NewFacebookAuthenticator(clientID, clientSecret, redirectURL string, db *sql.DB) *DatabaseAuthenticator

NewFacebookAuthenticator creates a DatabaseAuthenticator configured for Facebook OAuth2

func NewGitHubAuthenticator added in v1.0.48

func NewGitHubAuthenticator(clientID, clientSecret, redirectURL string, db *sql.DB) *DatabaseAuthenticator

NewGitHubAuthenticator creates a DatabaseAuthenticator configured for GitHub OAuth2

func NewGoogleAuthenticator added in v1.0.48

func NewGoogleAuthenticator(clientID, clientSecret, redirectURL string, db *sql.DB) *DatabaseAuthenticator

NewGoogleAuthenticator creates a DatabaseAuthenticator configured for Google OAuth2

func NewMicrosoftAuthenticator added in v1.0.48

func NewMicrosoftAuthenticator(clientID, clientSecret, redirectURL string, db *sql.DB) *DatabaseAuthenticator

NewMicrosoftAuthenticator creates a DatabaseAuthenticator configured for Microsoft OAuth2

func NewMultiProviderAuthenticator added in v1.0.48

func NewMultiProviderAuthenticator(db *sql.DB, configs map[string]OAuth2Config) *DatabaseAuthenticator

NewMultiProviderAuthenticator creates a DatabaseAuthenticator with all major OAuth2 providers configured

func (*DatabaseAuthenticator) Authenticate added in v0.0.63

func (a *DatabaseAuthenticator) Authenticate(r *http.Request) (*UserContext, error)

func (*DatabaseAuthenticator) BeginPasskeyAuthentication added in v1.0.48

BeginPasskeyAuthentication initiates passkey authentication

func (*DatabaseAuthenticator) BeginPasskeyRegistration added in v1.0.48

BeginPasskeyRegistration initiates passkey registration for a user

func (*DatabaseAuthenticator) ClearCache added in v0.0.86

func (a *DatabaseAuthenticator) ClearCache(token string) error

ClearCache removes a specific token from the cache or clears all cache if token is empty

func (*DatabaseAuthenticator) ClearUserCache added in v0.0.86

func (a *DatabaseAuthenticator) ClearUserCache(userID int) error

ClearUserCache removes all cache entries for a specific user ID

func (*DatabaseAuthenticator) Close added in v1.2.0

func (a *DatabaseAuthenticator) Close() error

Close stops the background OAuth2 state cleanup goroutines and waits for in-flight session activity updates. It is safe to call more than once.

func (*DatabaseAuthenticator) CompletePasskeyRegistration added in v1.0.48

func (a *DatabaseAuthenticator) CompletePasskeyRegistration(ctx context.Context, req PasskeyRegisterRequest) (*PasskeyCredential, error)

CompletePasskeyRegistration completes passkey registration

func (*DatabaseAuthenticator) CompletePasswordReset added in v1.0.85

func (a *DatabaseAuthenticator) CompletePasswordReset(ctx context.Context, req PasswordResetCompleteRequest) error

CompletePasswordReset implements PasswordResettable. It validates the token and updates the user's password via resolvespec_password_reset.

func (*DatabaseAuthenticator) DeletePasskeyCredential added in v1.0.48

func (a *DatabaseAuthenticator) DeletePasskeyCredential(ctx context.Context, userID int, credentialID string) error

DeletePasskeyCredential removes a passkey credential

func (*DatabaseAuthenticator) GetPasskeyCredentials added in v1.0.48

func (a *DatabaseAuthenticator) GetPasskeyCredentials(ctx context.Context, userID int) ([]PasskeyCredential, error)

GetPasskeyCredentials returns all passkey credentials for a user

func (*DatabaseAuthenticator) Login added in v0.0.63

func (*DatabaseAuthenticator) LoginWithAPIKey added in v1.2.12

func (a *DatabaseAuthenticator) LoginWithAPIKey(ctx context.Context, rawKey string, claims map[string]any) (*LoginResponse, error)

LoginWithAPIKey implements APIKeyLoginable. It validates a raw header/generic API key and creates a session for the key's user. Unknown, expired and inactive keys all return errInvalidAPIKey; the raw key is never logged. Procedure-only: the key and user lookup live in resolvespec_login_api_key so the underlying schema can differ per database; there is no direct-SQL path.

func (*DatabaseAuthenticator) LoginWithCookie added in v1.0.109

LoginWithCookie performs a login and, when EnableCookieSession is true, writes the session cookie to w using the configured CookieOptions. The LoginResponse is returned regardless of whether cookie sessions are enabled.

func (*DatabaseAuthenticator) LoginWithPasskey added in v1.0.48

func (a *DatabaseAuthenticator) LoginWithPasskey(ctx context.Context, req PasskeyLoginRequest) (*LoginResponse, error)

LoginWithPasskey authenticates a user using a passkey and creates a session

func (*DatabaseAuthenticator) Logout added in v0.0.63

func (*DatabaseAuthenticator) LogoutWithCookie added in v1.0.109

func (a *DatabaseAuthenticator) LogoutWithCookie(ctx context.Context, req LogoutRequest, w http.ResponseWriter) error

LogoutWithCookie performs a logout and, when EnableCookieSession is true, clears the session cookie on w. The logout itself is performed regardless of the cookie flag.

func (*DatabaseAuthenticator) OAuth2GenerateState added in v1.0.48

func (a *DatabaseAuthenticator) OAuth2GenerateState() (string, error)

OAuth2GenerateState generates a random state string for CSRF protection

func (*DatabaseAuthenticator) OAuth2GetAuthURL added in v1.0.48

func (a *DatabaseAuthenticator) OAuth2GetAuthURL(providerName, state string) (string, error)

OAuth2GetAuthURL returns the OAuth2 authorization URL for redirecting users

func (*DatabaseAuthenticator) OAuth2GetAuthURLWithOptions added in v1.2.12

func (a *DatabaseAuthenticator) OAuth2GetAuthURLWithOptions(providerName, state string, opts OAuth2AuthOptions) (string, error)

OAuth2GetAuthURLWithOptions is OAuth2GetAuthURL with OpenID Connect request parameters. For an OIDC provider (and with UsePKCE) it also creates the PKCE verifier and the nonce, which are kept with the state until the callback.

func (*DatabaseAuthenticator) OAuth2GetProviders added in v1.0.48

func (a *DatabaseAuthenticator) OAuth2GetProviders() []string

OAuth2GetProviders returns list of configured OAuth2 provider names

func (*DatabaseAuthenticator) OAuth2HandleCallback added in v1.0.48

func (a *DatabaseAuthenticator) OAuth2HandleCallback(ctx context.Context, providerName, code, state string) (*LoginResponse, error)

OAuth2HandleCallback handles the OAuth2 callback and exchanges code for token

func (*DatabaseAuthenticator) OAuth2HandleCallbackRequest added in v1.2.12

func (a *DatabaseAuthenticator) OAuth2HandleCallbackRequest(ctx context.Context, providerName string, r *http.Request) (*LoginResponse, error)

OAuth2HandleCallbackRequest is OAuth2HandleCallback for the redirect request itself. Besides code and state it honours the error parameters and the RFC 9207 "iss" parameter, which protects against mix-up attacks when several providers are in use.

func (*DatabaseAuthenticator) OAuth2LogoutURL added in v1.2.12

func (a *DatabaseAuthenticator) OAuth2LogoutURL(ctx context.Context, providerName, idTokenHint, postLogoutRedirect, state string) (string, error)

OAuth2LogoutURL returns the provider's RP-initiated logout URL (OIDC RP-Initiated Logout 1.0). idTokenHint is LoginResponse.Meta["id_token"]. It fails when the provider has no end_session_endpoint.

func (*DatabaseAuthenticator) OAuth2RefreshToken added in v1.0.48

func (a *DatabaseAuthenticator) OAuth2RefreshToken(ctx context.Context, refreshToken, providerName string) (*LoginResponse, error)

OAuth2RefreshToken refreshes an expired OAuth2 access token using the refresh token Takes the refresh token and returns a new LoginResponse with updated tokens

func (*DatabaseAuthenticator) OAuthDeleteClient added in v1.2.12

func (a *DatabaseAuthenticator) OAuthDeleteClient(ctx context.Context, clientID string) error

OAuthDeleteClient deactivates a registered client (RFC 7592).

func (*DatabaseAuthenticator) OAuthExchangeCode added in v1.0.78

func (a *DatabaseAuthenticator) OAuthExchangeCode(ctx context.Context, code string) (*OAuthCode, error)

OAuthExchangeCode retrieves and deletes an authorization code (single use).

func (*DatabaseAuthenticator) OAuthGetClient added in v1.0.78

func (a *DatabaseAuthenticator) OAuthGetClient(ctx context.Context, clientID string) (*OAuthServerClient, error)

OAuthGetClient retrieves a registered client by ID.

func (*DatabaseAuthenticator) OAuthGetUser added in v1.2.12

func (a *DatabaseAuthenticator) OAuthGetUser(ctx context.Context, userID int) (*UserContext, error)

OAuthGetUser returns the active user with the given id.

func (*DatabaseAuthenticator) OAuthGrants added in v1.2.12

func (a *DatabaseAuthenticator) OAuthGrants() lookup.OAuthGrantStore

OAuthGrants returns the store holding consents, managed refresh tokens, device codes, pushed authorization requests and the replay cache.

func (*DatabaseAuthenticator) OAuthIntrospectToken added in v1.0.78

func (a *DatabaseAuthenticator) OAuthIntrospectToken(ctx context.Context, token string) (*OAuthTokenInfo, error)

OAuthIntrospectToken validates a token and returns its metadata (RFC 7662).

func (*DatabaseAuthenticator) OAuthRegisterClient added in v1.0.78

func (a *DatabaseAuthenticator) OAuthRegisterClient(ctx context.Context, client *OAuthServerClient) (*OAuthServerClient, error)

OAuthRegisterClient persists an OAuth2 client registration.

func (*DatabaseAuthenticator) OAuthRevokeToken added in v1.0.78

func (a *DatabaseAuthenticator) OAuthRevokeToken(ctx context.Context, token string) error

OAuthRevokeToken revokes a token by deleting the session (RFC 7009).

func (*DatabaseAuthenticator) OAuthSaveCode added in v1.0.78

func (a *DatabaseAuthenticator) OAuthSaveCode(ctx context.Context, code *OAuthCode) error

OAuthSaveCode persists an authorization code.

func (*DatabaseAuthenticator) OAuthUpdateClient added in v1.2.12

func (a *DatabaseAuthenticator) OAuthUpdateClient(ctx context.Context, client *OAuthServerClient) error

OAuthUpdateClient replaces the registered metadata of a client (RFC 7592).

func (*DatabaseAuthenticator) RefreshToken added in v0.0.63

func (a *DatabaseAuthenticator) RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error)

RefreshToken implements Refreshable interface

func (*DatabaseAuthenticator) Register added in v1.0.48

Register implements Registrable interface

func (*DatabaseAuthenticator) RequestPasswordReset added in v1.0.85

RequestPasswordReset implements PasswordResettable. It calls the stored procedure resolvespec_password_reset_request and returns the reset token and expiry.

func (*DatabaseAuthenticator) SetAuthenticateCallback added in v1.0.112

func (a *DatabaseAuthenticator) SetAuthenticateCallback(fn func(r *http.Request) (*UserContext, error))

func (*DatabaseAuthenticator) UpdatePasskeyCredentialName added in v1.0.48

func (a *DatabaseAuthenticator) UpdatePasskeyCredentialName(ctx context.Context, userID int, credentialID string, name string) error

UpdatePasskeyCredentialName updates the friendly name of a credential

func (*DatabaseAuthenticator) WithOAuth2 added in v1.0.48

WithOAuth2 configures OAuth2 support for the DatabaseAuthenticator Can be called multiple times to add multiple OAuth2 providers Returns the same DatabaseAuthenticator instance for method chaining

func (*DatabaseAuthenticator) WithOIDC added in v1.2.12

WithOIDC registers an OpenID Connect provider. The endpoints come from the issuer's discovery document. Login uses PKCE and a nonce, and the id_token is validated on callback and refresh.

func (*DatabaseAuthenticator) WithPasskey added in v1.0.48

WithPasskey configures the DatabaseAuthenticator with a passkey provider

type DatabaseAuthenticatorExample added in v0.0.63

type DatabaseAuthenticatorExample struct {
	// contains filtered or unexported fields
}

func NewDatabaseAuthenticatorExample added in v0.0.63

func NewDatabaseAuthenticatorExample(db *gorm.DB) *DatabaseAuthenticatorExample

func (*DatabaseAuthenticatorExample) Authenticate added in v0.0.63

func (a *DatabaseAuthenticatorExample) Authenticate(r *http.Request) (*UserContext, error)

func (*DatabaseAuthenticatorExample) Login added in v0.0.63

func (*DatabaseAuthenticatorExample) Logout added in v0.0.63

func (*DatabaseAuthenticatorExample) RefreshToken added in v0.0.63

func (a *DatabaseAuthenticatorExample) RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error)

Optional: Implement Refreshable interface

type DatabaseAuthenticatorOptions added in v0.0.86

type DatabaseAuthenticatorOptions struct {
	// CacheTTL is the duration to cache user contexts
	// Default: 5 minutes
	CacheTTL time.Duration
	// Cache is an optional cache instance. If nil, uses the default cache
	Cache *cache.Cache
	// PasskeyProvider is an optional passkey provider for WebAuthn/FIDO2 authentication
	PasskeyProvider PasskeyProvider
	// Lookup selects dialect, query mode and procedure/table/column names.
	// The zero value uses stored procedures on Postgres and direct SQL elsewhere.
	Lookup lookup.Config
	// LookupProvider, when set, is used instead of building one from Lookup and the db.
	LookupProvider *lookup.Provider
	// DBFactory is called to obtain a fresh *sql.DB when the existing connection is closed.
	// If nil, reconnection is disabled.
	DBFactory func() (*sql.DB, error)
	// EnableCookieSession enables cookie-based session management.
	// When true, Authenticate reads the session token from the cookie named by
	// CookieOptions.Name (default "session_token") in addition to the Authorization header,
	// and LoginWithCookie / LogoutWithCookie automatically set / clear the cookie.
	EnableCookieSession bool
	// UpgradePasswordHash, when true, rewrites a legacy cleartext password as a
	// bcrypt hash after a successful login. It is off by default and is never
	// enabled automatically: legacy cleartext values are still accepted at login,
	// but stored rows are left untouched unless this is set.
	UpgradePasswordHash bool
	// CookieOptions configures the session cookie written by LoginWithCookie.
	// Only used when EnableCookieSession is true.
	CookieOptions SessionCookieOptions
	// AuthenticateCallback is a fallback called when the primary authentication (database
	// session lookup) fails. If non-nil and the callback returns a non-nil UserContext,
	// that result is used in place of the failure.
	AuthenticateCallback func(r *http.Request) (*UserContext, error)
}

DatabaseAuthenticatorOptions configures the database authenticator

type DatabaseColumnSecurityProvider added in v0.0.63

type DatabaseColumnSecurityProvider struct {
	// contains filtered or unexported fields
}

DatabaseColumnSecurityProvider loads column security through the lookup package (stored procedure on Postgres by default, direct SQL elsewhere).

func NewDatabaseColumnSecurityProvider added in v0.0.63

func NewDatabaseColumnSecurityProvider(db *sql.DB) *DatabaseColumnSecurityProvider

func (*DatabaseColumnSecurityProvider) GetColumnSecurity added in v0.0.63

func (p *DatabaseColumnSecurityProvider) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error)

func (*DatabaseColumnSecurityProvider) WithDBFactory added in v1.0.78

func (p *DatabaseColumnSecurityProvider) WithDBFactory(factory func() (*sql.DB, error)) *DatabaseColumnSecurityProvider

func (*DatabaseColumnSecurityProvider) WithLookup added in v1.2.12

WithLookup configures dialect, query mode and names. Call before first use.

func (*DatabaseColumnSecurityProvider) WithLookupProvider added in v1.2.12

WithLookupProvider uses an existing provider instead of building one.

func (*DatabaseColumnSecurityProvider) WithNoGroupTables added in v1.2.12

WithNoGroupTables skips group membership when loading rules in direct mode.

type DatabaseKeyStore added in v1.0.78

type DatabaseKeyStore struct {
	// contains filtered or unexported fields
}

DatabaseKeyStore is a KeyStore backed by the lookup package (stored procedures on Postgres by default, direct SQL elsewhere). The raw key is never passed to the database.

See lookup/keystore_schema.sql for the required table and procedure definitions.

Note: DeleteKey invalidates the cache entry for the deleted key. Due to the cache TTL, a deleted key may continue to authenticate for up to CacheTTL (default 2 minutes) if the cache entry cannot be invalidated.

func NewDatabaseKeyStore added in v1.0.78

func NewDatabaseKeyStore(db *sql.DB, opts ...DatabaseKeyStoreOptions) *DatabaseKeyStore

NewDatabaseKeyStore creates a DatabaseKeyStore with optional configuration.

func (*DatabaseKeyStore) CreateKey added in v1.0.78

CreateKey generates a raw key, stores its SHA-256 hash via the create procedure, and returns the raw key once.

func (*DatabaseKeyStore) DeleteKey added in v1.0.78

func (ks *DatabaseKeyStore) DeleteKey(ctx context.Context, userID int, keyID int64) error

DeleteKey soft-deletes a key after verifying ownership and invalidates its cache entry. The delete procedure returns the key_hash so no separate lookup is needed. Note: cache invalidation is best-effort; a cached entry may persist for up to CacheTTL.

func (*DatabaseKeyStore) GetUserKeys added in v1.0.78

func (ks *DatabaseKeyStore) GetUserKeys(ctx context.Context, userID int, keyType KeyType) ([]UserKey, error)

GetUserKeys returns all active, non-expired keys for the given user. Pass an empty KeyType to return all types.

func (*DatabaseKeyStore) ValidateKey added in v1.0.78

func (ks *DatabaseKeyStore) ValidateKey(ctx context.Context, rawKey string, keyType KeyType) (*UserKey, error)

ValidateKey hashes the raw key and calls the validate procedure. Results are cached for CacheTTL to reduce DB load on hot paths.

type DatabaseKeyStoreOptions added in v1.0.78

type DatabaseKeyStoreOptions struct {
	// Cache is an optional cache instance. If nil, uses the default cache.
	Cache *cache.Cache
	// CacheTTL is the duration to cache ValidateKey results.
	// Default: 2 minutes.
	CacheTTL time.Duration
	// Lookup selects dialect, query mode and procedure/table/column names.
	// The zero value uses stored procedures on Postgres and direct SQL elsewhere.
	Lookup lookup.Config
	// LookupProvider, when set, is used instead of building one from Lookup and the db.
	LookupProvider *lookup.Provider
	// DBFactory is called to obtain a fresh *sql.DB when the existing connection is closed.
	// If nil, reconnection is disabled.
	DBFactory func() (*sql.DB, error)
}

DatabaseKeyStoreOptions configures DatabaseKeyStore.

type DatabasePasskeyProvider added in v1.0.48

type DatabasePasskeyProvider struct {
	// contains filtered or unexported fields
}

DatabasePasskeyProvider implements PasskeyProvider on top of the lookup package (stored procedures on Postgres by default, direct SQL elsewhere).

func NewDatabasePasskeyProvider added in v1.0.48

func NewDatabasePasskeyProvider(db *sql.DB, opts DatabasePasskeyProviderOptions) *DatabasePasskeyProvider

NewDatabasePasskeyProvider creates a new database-backed passkey provider

func (*DatabasePasskeyProvider) BeginAuthentication added in v1.0.48

func (p *DatabasePasskeyProvider) BeginAuthentication(ctx context.Context, username string) (*PasskeyAuthenticationOptions, error)

BeginAuthentication creates authentication options for passkey login

func (*DatabasePasskeyProvider) BeginRegistration added in v1.0.48

func (p *DatabasePasskeyProvider) BeginRegistration(ctx context.Context, userID int, username, displayName string) (*PasskeyRegistrationOptions, error)

BeginRegistration creates registration options for a new passkey

func (*DatabasePasskeyProvider) CompleteAuthentication added in v1.0.48

func (p *DatabasePasskeyProvider) CompleteAuthentication(ctx context.Context, response PasskeyAuthenticationResponse, expectedChallenge []byte) (int, error)

CompleteAuthentication verifies a passkey assertion and returns the user ID NOTE: This is a simplified implementation. In production, you should use a WebAuthn library like github.com/go-webauthn/webauthn to properly verify the assertion signature.

func (*DatabasePasskeyProvider) CompleteRegistration added in v1.0.48

func (p *DatabasePasskeyProvider) CompleteRegistration(ctx context.Context, userID int, response PasskeyRegistrationResponse, expectedChallenge []byte) (*PasskeyCredential, error)

CompleteRegistration verifies and stores a new passkey credential NOTE: This is a simplified implementation. In production, you should use a WebAuthn library like github.com/go-webauthn/webauthn to properly verify attestation and parse credentials.

func (*DatabasePasskeyProvider) DeleteCredential added in v1.0.48

func (p *DatabasePasskeyProvider) DeleteCredential(ctx context.Context, userID int, credentialID string) error

DeleteCredential removes a passkey credential

func (*DatabasePasskeyProvider) GetCredentials added in v1.0.48

func (p *DatabasePasskeyProvider) GetCredentials(ctx context.Context, userID int) ([]PasskeyCredential, error)

GetCredentials returns all passkey credentials for a user

func (*DatabasePasskeyProvider) UpdateCredentialName added in v1.0.48

func (p *DatabasePasskeyProvider) UpdateCredentialName(ctx context.Context, userID int, credentialID string, name string) error

UpdateCredentialName updates the friendly name of a credential

type DatabasePasskeyProviderOptions added in v1.0.48

type DatabasePasskeyProviderOptions struct {
	// RPID is the Relying Party ID (typically your domain, e.g., "example.com")
	RPID string
	// RPName is the display name for your relying party
	RPName string
	// RPOrigin is the expected origin (e.g., "https://example.com")
	RPOrigin string
	// Timeout is the timeout for operations in milliseconds (default: 60000)
	Timeout int64
	// Lookup selects dialect, query mode and procedure/table/column names.
	Lookup lookup.Config
	// LookupProvider, when set, is used instead of building one from Lookup and the db.
	LookupProvider *lookup.Provider
	// DBFactory is called to obtain a fresh *sql.DB when the existing connection is closed.
	// If nil, reconnection is disabled.
	DBFactory func() (*sql.DB, error)
}

DatabasePasskeyProviderOptions configures the passkey provider

type DatabaseRowSecurityProvider added in v0.0.63

type DatabaseRowSecurityProvider struct {
	// contains filtered or unexported fields
}

DatabaseRowSecurityProvider loads row security through the lookup package (stored procedure on Postgres by default, direct SQL elsewhere).

func NewDatabaseRowSecurityProvider added in v0.0.63

func NewDatabaseRowSecurityProvider(db *sql.DB) *DatabaseRowSecurityProvider

func (*DatabaseRowSecurityProvider) GetRowSecurity added in v0.0.63

func (p *DatabaseRowSecurityProvider) GetRowSecurity(ctx context.Context, userRef any, schema, table string) (RowSecurity, error)

func (*DatabaseRowSecurityProvider) WithDBFactory added in v1.0.78

func (p *DatabaseRowSecurityProvider) WithDBFactory(factory func() (*sql.DB, error)) *DatabaseRowSecurityProvider

func (*DatabaseRowSecurityProvider) WithLookup added in v1.2.12

WithLookup configures dialect, query mode and names. Call before first use.

func (*DatabaseRowSecurityProvider) WithLookupProvider added in v1.2.12

WithLookupProvider uses an existing provider instead of building one.

func (*DatabaseRowSecurityProvider) WithNoGroupTables added in v1.2.12

WithNoGroupTables skips group membership when loading rules in direct mode.

type DatabaseTwoFactorProvider added in v1.0.48

type DatabaseTwoFactorProvider struct {
	// contains filtered or unexported fields
}

DatabaseTwoFactorProvider implements TwoFactorAuthProvider on top of the lookup package (stored procedures on Postgres by default, direct SQL elsewhere). See lookup/database_schema.sql for procedure definitions

func NewDatabaseTwoFactorProvider added in v1.0.48

func NewDatabaseTwoFactorProvider(db *sql.DB, config *totp.Config) *DatabaseTwoFactorProvider

NewDatabaseTwoFactorProvider creates a new database-backed 2FA provider

func (*DatabaseTwoFactorProvider) Disable2FA added in v1.0.48

func (p *DatabaseTwoFactorProvider) Disable2FA(userID int) error

Disable2FA deactivates 2FA for a user

func (*DatabaseTwoFactorProvider) Enable2FA added in v1.0.48

func (p *DatabaseTwoFactorProvider) Enable2FA(userID int, secret string, backupCodes []string) error

Enable2FA activates 2FA for a user

func (*DatabaseTwoFactorProvider) Generate2FASecret added in v1.0.48

func (p *DatabaseTwoFactorProvider) Generate2FASecret(userID int, issuer, accountName string) (*TwoFactorSecret, error)

Generate2FASecret creates a new secret for a user

func (*DatabaseTwoFactorProvider) GenerateBackupCodes added in v1.0.48

func (p *DatabaseTwoFactorProvider) GenerateBackupCodes(userID int, count int) ([]string, error)

GenerateBackupCodes creates backup codes for 2FA

func (*DatabaseTwoFactorProvider) Get2FASecret added in v1.0.48

func (p *DatabaseTwoFactorProvider) Get2FASecret(userID int) (string, error)

Get2FASecret retrieves the user's 2FA secret

func (*DatabaseTwoFactorProvider) Get2FAStatus added in v1.0.48

func (p *DatabaseTwoFactorProvider) Get2FAStatus(userID int) (bool, error)

Get2FAStatus checks if user has 2FA enabled

func (*DatabaseTwoFactorProvider) Validate2FACode added in v1.0.48

func (p *DatabaseTwoFactorProvider) Validate2FACode(secret string, code string) (bool, error)

Validate2FACode verifies a TOTP code

func (*DatabaseTwoFactorProvider) ValidateBackupCode added in v1.0.48

func (p *DatabaseTwoFactorProvider) ValidateBackupCode(userID int, code string) (bool, error)

ValidateBackupCode checks and consumes a backup code

func (*DatabaseTwoFactorProvider) WithDBFactory added in v1.1.25

func (p *DatabaseTwoFactorProvider) WithDBFactory(factory func() (*sql.DB, error)) *DatabaseTwoFactorProvider

WithDBFactory configures a factory used to reopen the database connection if it is closed.

func (*DatabaseTwoFactorProvider) WithLookup added in v1.2.12

WithLookup configures dialect, query mode and names. Call before first use.

func (*DatabaseTwoFactorProvider) WithLookupProvider added in v1.2.12

WithLookupProvider uses an existing provider instead of building one.

type HeaderAuthenticatorExample added in v0.0.63

type HeaderAuthenticatorExample struct {
}

func NewHeaderAuthenticatorExample added in v0.0.63

func NewHeaderAuthenticatorExample() *HeaderAuthenticatorExample

func (*HeaderAuthenticatorExample) Authenticate added in v0.0.63

func (a *HeaderAuthenticatorExample) Authenticate(r *http.Request) (*UserContext, error)

func (*HeaderAuthenticatorExample) Login added in v0.0.63

func (*HeaderAuthenticatorExample) Logout added in v0.0.63

type JWTAuthenticator added in v0.0.63

type JWTAuthenticator struct {
	// contains filtered or unexported fields
}

JWTAuthenticator provides JWT token-based authentication All database operations go through stored procedures Procedure names and modes are configured through lookup.Config (see lookup.DefaultProcNames) NOTE: JWT signing/verification requires github.com/golang-jwt/jwt/v5 to be installed and imported

func NewJWTAuthenticator added in v0.0.63

func NewJWTAuthenticator(secretKey string, db *sql.DB) *JWTAuthenticator

func (*JWTAuthenticator) Authenticate added in v0.0.63

func (a *JWTAuthenticator) Authenticate(r *http.Request) (*UserContext, error)

func (*JWTAuthenticator) Login added in v0.0.63

func (*JWTAuthenticator) LoginWithCookie added in v1.0.110

func (a *JWTAuthenticator) LoginWithCookie(ctx context.Context, req LoginRequest, w http.ResponseWriter) (*LoginResponse, error)

func (*JWTAuthenticator) Logout added in v0.0.63

func (a *JWTAuthenticator) Logout(ctx context.Context, req LogoutRequest) error

func (*JWTAuthenticator) LogoutWithCookie added in v1.0.110

func (a *JWTAuthenticator) LogoutWithCookie(ctx context.Context, req LogoutRequest, w http.ResponseWriter) error

func (*JWTAuthenticator) WithDBFactory added in v1.0.78

func (a *JWTAuthenticator) WithDBFactory(factory func() (*sql.DB, error)) *JWTAuthenticator

WithDBFactory configures a factory used to reopen the database connection if it is closed.

func (*JWTAuthenticator) WithLookup added in v1.2.12

func (a *JWTAuthenticator) WithLookup(cfg lookup.Config) *JWTAuthenticator

WithLookup configures dialect, query mode and names. Call before first use.

func (*JWTAuthenticator) WithLookupProvider added in v1.2.12

func (a *JWTAuthenticator) WithLookupProvider(p *lookup.Provider) *JWTAuthenticator

WithLookupProvider uses an existing provider instead of building one.

func (*JWTAuthenticator) WithPasswordHashUpgrade added in v1.2.0

func (a *JWTAuthenticator) WithPasswordHashUpgrade(enabled bool) *JWTAuthenticator

WithPasswordHashUpgrade explicitly enables (or disables) upgrading legacy cleartext passwords to bcrypt after a successful login. Off by default.

type JWTAuthenticatorExample added in v0.0.63

type JWTAuthenticatorExample struct {
	// contains filtered or unexported fields
}

func NewJWTAuthenticatorExample added in v0.0.63

func NewJWTAuthenticatorExample(secretKey string, db *gorm.DB) *JWTAuthenticatorExample

func (*JWTAuthenticatorExample) Authenticate added in v0.0.63

func (a *JWTAuthenticatorExample) Authenticate(r *http.Request) (*UserContext, error)

func (*JWTAuthenticatorExample) Login added in v0.0.63

func (*JWTAuthenticatorExample) Logout added in v0.0.63

type KeyStore added in v1.0.78

type KeyStore interface {
	// CreateKey generates a new key, stores its hash, and returns the raw key once.
	CreateKey(ctx context.Context, req CreateKeyRequest) (*CreateKeyResponse, error)

	// GetUserKeys returns all active, non-expired keys for a user.
	// Pass an empty KeyType to return all types.
	GetUserKeys(ctx context.Context, userID int, keyType KeyType) ([]UserKey, error)

	// DeleteKey soft-deletes a key by ID after verifying ownership.
	DeleteKey(ctx context.Context, userID int, keyID int64) error

	// ValidateKey checks a raw key, returns the matching UserKey on success.
	// The implementation hashes the raw key before any lookup.
	// Pass an empty KeyType to accept any type.
	ValidateKey(ctx context.Context, rawKey string, keyType KeyType) (*UserKey, error)
}

KeyStore manages per-user auth keys with pluggable storage backends. Implementations: ConfigKeyStore (static list) and DatabaseKeyStore (stored procedures).

type KeyType added in v1.0.78

type KeyType = sectypes.KeyType

type LoginRequest added in v0.0.63

type LoginRequest = sectypes.LoginRequest

type LoginResponse added in v0.0.63

type LoginResponse = sectypes.LoginResponse

type LogoutRequest added in v0.0.63

type LogoutRequest = sectypes.LogoutRequest

type OAuth2AuthOptions added in v1.2.12

type OAuth2AuthOptions struct {
	LoginHint string
	Prompt    string // none, login, consent, select_account
	MaxAge    *int
	ACRValues string
	Extra     map[string]string
}

OAuth2AuthOptions are optional OpenID Connect authentication request parameters.

type OAuth2Config added in v1.0.48

type OAuth2Config struct {
	ClientID     string
	ClientSecret string
	RedirectURL  string
	Scopes       []string
	AuthURL      string
	TokenURL     string
	UserInfoURL  string
	ProviderName string

	// Optional: Custom user info parser
	// If not provided, will use standard claims (sub, email, name)
	UserInfoParser func(userInfo map[string]any) (*UserContext, error)

	// Issuer turns the provider into an OpenID Connect provider: PKCE and a nonce are used and
	// the id_token returned by the token endpoint is validated (signature, iss, aud, exp, nonce,
	// at_hash). WithOIDC fills the endpoints in by discovery; with WithOAuth2 set JWKSURL
	// as well. UserInfoURL stays optional: the id_token claims are used when it is empty.
	Issuer string
	// JWKSURL is the provider's key set. Only needed with WithOAuth2; WithOIDC discovers it.
	JWKSURL string
	// EndSessionURL is the provider's RP-initiated logout endpoint (discovered by WithOIDC).
	EndSessionURL string
	// UsePKCE sends a PKCE S256 challenge for a provider that is not OIDC. It is always on in OIDC mode.
	UsePKCE bool
	// AllowedAlgs lists the id_token signature algorithms to accept. Default: RS256, PS256, ES256, ES384.
	AllowedAlgs []string
	// AuthStyle selects how the client authenticates at the token endpoint: "basic", "post" or ""
	// (try basic, fall back to post).
	AuthStyle string
	// HTTPClient is used for discovery, JWKS, token and userinfo requests.
	HTTPClient *http.Client
	// ClockSkew tolerates clock differences when validating the id_token. Default 1 minute.
	ClockSkew time.Duration
}

OAuth2Config contains configuration for OAuth2 authentication

type OAuth2Provider added in v1.0.48

type OAuth2Provider struct {
	// contains filtered or unexported fields
}

OAuth2Provider holds configuration and state for a single OAuth2 provider

type OAuthClaimsProvider added in v1.2.12

type OAuthClaimsProvider func(ctx context.Context, req OAuthClaimsRequest) (map[string]any, error)

OAuthClaimsProvider returns the claims for a user. The server only includes the standard claims the granted scopes entitle the client to (profile, email, address, phone) plus every claim the client requested explicitly; claims outside those sets are dropped.

type OAuthClaimsRequest added in v1.2.12

type OAuthClaimsRequest struct {
	UserID int
	Sub    string
	Scopes []string
	// Requested holds the names the client asked for individually through the OIDC "claims"
	// request parameter for this destination.
	Requested []string
	// Destination is "id_token" or "userinfo".
	Destination string
	// Base are the claims the server already knows (sub, preferred_username, email).
	Base map[string]any
}

OAuthClaimsRequest is the input of an OAuthClaimsProvider.

type OAuthCode added in v1.0.78

type OAuthCode = sectypes.OAuthCode

type OAuthConsentPage added in v1.2.12

type OAuthConsentPage struct {
	Title      string
	Action     string
	State      string // opaque, must be posted back as the "req" field
	ClientName string
	ClientURI  string
	LogoURI    string
	Scopes     []OAuthScopeInfo
	User       string
	Hidden     map[string]string
}

OAuthConsentPage is the data of the consent page template.

type OAuthLoginPage added in v1.2.12

type OAuthLoginPage struct {
	Title      string
	Error      string
	Action     string // form action
	State      string // opaque, must be posted back as the "req" field
	ClientName string
	LoginHint  string
	// Extra hidden fields to post back (device flow).
	Hidden map[string]string
}

OAuthLoginPage is the data of the login page template.

type OAuthSSOCookieConfig added in v1.2.12

type OAuthSSOCookieConfig struct {
	Name     string        // default "resolvespec_sso"
	Path     string        // default "/"
	TTL      time.Duration // default 8h
	SameSite http.SameSite // default Lax
	// Insecure sends the cookie over plain HTTP. By default Secure is on whenever Issuer is https.
	Insecure bool
	Disable  bool // never set a cookie: every authorization request authenticates again
}

OAuthSSOCookieConfig configures the SSO cookie.

type OAuthScopeInfo added in v1.2.12

type OAuthScopeInfo struct {
	Name        string
	Description string
}

OAuthScopeInfo is one line of the consent screen.

type OAuthServer added in v1.0.78

type OAuthServer struct {
	// contains filtered or unexported fields
}

OAuthServer is an OAuth 2.1 authorization server and OpenID Connect provider.

It can act as both:

  • A direct identity provider using DatabaseAuthenticator username/password login
  • A federation layer that delegates authentication to external OAuth2 providers (Google, GitHub, Microsoft, etc.) registered via RegisterExternalProvider

Endpoints (see OAUTH2_SERVER.md for parameters and examples):

GET  /.well-known/oauth-authorization-server   RFC 8414 server metadata
GET  /.well-known/openid-configuration         OIDC Discovery
GET  /.well-known/oauth-protected-resource     RFC 9728 protected resource metadata
POST /oauth/register                            RFC 7591 dynamic client registration
GET|PUT|DELETE /oauth/register/{client_id}      RFC 7592 client management
GET|POST /oauth/authorize                       authorization endpoint (PKCE S256 required)
POST /oauth/token                               authorization_code, refresh_token, client_credentials,
                                                 device_code and token-exchange grants
POST /oauth/par                                 RFC 9126 pushed authorization requests
POST /oauth/device_authorization                RFC 8628 device authorization
GET|POST /oauth/device                          device verification page
POST /oauth/revoke                              RFC 7009 token revocation
POST /oauth/introspect                          RFC 7662 token introspection
GET|POST /oauth/userinfo                        OIDC UserInfo
GET  /oauth/jwks.json                           JWKS
GET|POST /oauth/logout                          OIDC RP-initiated logout
GET  {ProviderCallbackPath}                     external provider callback

func NewOAuthServer added in v1.0.78

func NewOAuthServer(cfg OAuthServerConfig, auth *DatabaseAuthenticator) *OAuthServer

NewOAuthServer creates a new OAuth2 / OIDC authorization server.

Pass a DatabaseAuthenticator to enable direct username/password login (the server acts as its own identity provider). Pass nil to use only external providers. External providers are added separately via RegisterExternalProvider.

Call Close() to stop background goroutines when the server is no longer needed.

func (*OAuthServer) Close added in v1.0.78

func (s *OAuthServer) Close()

Close stops the background goroutines started by NewOAuthServer. It is safe to call Close multiple times.

func (*OAuthServer) HTTPHandler added in v1.0.78

func (s *OAuthServer) HTTPHandler() http.Handler

HTTPHandler returns an http.Handler that serves all RFC-required OAuth2 endpoints. Mount it at the root of your HTTP server alongside the MCP transport.

mux := http.NewServeMux()
mux.Handle("/", oauthServer.HTTPHandler())
mux.Handle("/mcp/", mcpTransport)

func (*OAuthServer) ProviderCallbackPath added in v1.0.78

func (s *OAuthServer) ProviderCallbackPath() string

ProviderCallbackPath returns the configured path for external provider callbacks.

func (*OAuthServer) RegisterExternalProvider added in v1.0.78

func (s *OAuthServer) RegisterExternalProvider(auth *DatabaseAuthenticator, providerName string)

RegisterExternalProvider adds an external OAuth2 provider (Google, GitHub, Microsoft, etc.) that handles user authentication via redirect. The DatabaseAuthenticator must have been configured with WithOAuth2(providerName, ...) before calling this. Multiple providers can be registered; the first is used as the default. All providers must be registered before the server starts serving requests.

func (*OAuthServer) RegisterTrustedClient added in v1.2.12

func (s *OAuthServer) RegisterTrustedClient(ctx context.Context, c OAuthServerClient) (registered *OAuthServerClient, plainSecret string, err error)

RegisterTrustedClient registers a client programmatically and returns its plaintext secret (empty for a public client). Unlike dynamic registration it may set FirstParty (skips the consent screen), RequireConsent and RequirePAR, which a remote caller must not control. ClientID is generated when empty. Leave ClientSecretHash empty and set TokenEndpointAuthMethod to a client_secret_* method to have a secret generated.

func (*OAuthServer) VerifyAccessToken added in v1.2.12

func (s *OAuthServer) VerifyAccessToken(ctx context.Context, token string, opts VerifyAccessTokenOptions) (*AccessTokenClaims, error)

VerifyAccessToken validates an access token issued by this server (JWT or opaque) against the store, so revoked tokens are rejected.

type OAuthServerClient added in v1.0.78

type OAuthServerClient = sectypes.OAuthServerClient

type OAuthServerConfig added in v1.0.78

type OAuthServerConfig struct {
	// Issuer is the public base URL of this server (e.g. "https://api.example.com"). It is
	// the "iss" of every token and the base of every endpoint URL. A path is allowed
	// ("https://example.com/auth"); the server then also answers the RFC 8414 path-insertion
	// well-known URLs.
	Issuer string

	// ProviderCallbackPath is the path on this server that external OAuth2 providers
	// redirect back to. Defaults to "/oauth/provider/callback".
	ProviderCallbackPath string

	// LoginTitle is shown on the built-in login form when the server acts as its own
	// identity provider. Defaults to "Sign in".
	LoginTitle string

	// PersistClients stores registered clients in the database when a DatabaseAuthenticator is provided.
	// Clients registered during a session survive server restarts.
	PersistClients bool

	// PersistCodes stores authorization codes in the database.
	// Useful for multi-instance deployments. Defaults to in-memory.
	PersistCodes bool

	// DefaultScopes lists scopes advertised in server metadata and granted to clients that
	// register without allowed_scopes. Defaults to ["openid","profile","email"].
	DefaultScopes []string

	// AccessTokenTTL is the issued token lifetime. Defaults to 24h.
	AccessTokenTTL time.Duration

	// AuthCodeTTL is the auth code lifetime. Defaults to 2 minutes.
	AuthCodeTTL time.Duration

	// ResourceIdentifier is this server's protected-resource identifier, advertised in
	// RFC 9728 metadata. Defaults to Issuer.
	ResourceIdentifier string

	// SigningKey signs id_tokens (RS256) and is exposed via the JWKS endpoint. If nil and
	// SigningKeys is empty, an RSA-2048 key is generated in memory when the server starts.
	// Supply a persistent key for multi-instance deployments so tokens remain verifiable
	// across restarts and instances.
	SigningKey *rsa.PrivateKey

	// SigningKeys supersedes SigningKey. The first key is the default; all are published in
	// the JWKS, which is how a key is rotated (add the new key first, publish, then remove the
	// old one once its tokens have expired). RSA and ECDSA (P-256/P-384) keys are supported.
	SigningKeys []OAuthSigningKey

	// CookieSecret keys the HMAC that protects the SSO cookie and the state carried through
	// the login and consent forms. Defaults to a value derived from the first signing key, so
	// instances sharing a signing key share sessions.
	CookieSecret []byte

	// SSOCookie configures the browser session cookie that makes prompt=none, max_age,
	// single sign-on and logout work.
	SSOCookie OAuthSSOCookieConfig

	// RequireConsent shows a consent screen for every client that is not first-party and has
	// no stored consent covering the requested scopes. A client can also opt in with its
	// require_consent metadata.
	RequireConsent bool

	// ConsentTTL is how long a stored consent is honoured. Defaults to 90 days.
	ConsentTTL time.Duration

	// ManagedRefreshTokens makes the server issue and rotate its own refresh tokens (stored
	// hashed, one family per grant, reuse of a rotated token revokes the family). When false,
	// the refresh token of the underlying DatabaseAuthenticator is passed through as before.
	ManagedRefreshTokens bool

	// RefreshTokenTTL is the absolute lifetime of a refresh token family. Defaults to 30 days.
	RefreshTokenTTL time.Duration

	// JWTAccessTokens issues RFC 9068 JWT access tokens instead of opaque session tokens.
	// Resource servers can verify them locally (VerifyAccessToken).
	JWTAccessTokens bool

	// AccessTokenAudience is the "aud" of JWT access tokens that were not requested for a
	// specific resource. Defaults to ResourceIdentifier.
	AccessTokenAudience string

	// EnableDPoP accepts RFC 9449 DPoP proofs at the token and userinfo endpoints and binds
	// the issued tokens to the proof key.
	EnableDPoP bool

	// EnablePAR serves the RFC 9126 pushed authorization request endpoint; RequirePAR makes it
	// mandatory for every client.
	EnablePAR  bool
	RequirePAR bool
	PARTTL     time.Duration // default 90 seconds

	// EnableDeviceFlow serves the RFC 8628 device authorization grant.
	EnableDeviceFlow  bool
	DeviceCodeTTL     time.Duration // default 10 minutes
	DevicePollSeconds int           // minimum poll interval, default 5

	// EnableTokenExchange serves the RFC 8693 token exchange grant.
	EnableTokenExchange bool

	// ClaimsProvider supplies the user claims for id_tokens and UserInfo (profile, email,
	// address, phone, custom claims). The default returns sub, preferred_username and email.
	ClaimsProvider OAuthClaimsProvider

	// SupportedACR lists the authentication context class references advertised and accepted.
	SupportedACR []string

	// DisableLogout does not serve the RP-initiated logout endpoint.
	DisableLogout bool

	// InitialAccessToken, when set, must be presented as a Bearer token to register a client.
	InitialAccessToken string

	// AllowAnonymousIntrospection lets callers without client credentials use the revocation
	// and introspection endpoints. By default they must authenticate as a client.
	AllowAnonymousIntrospection bool

	// RateLimiter, when set, is called for every request to a token-issuing endpoint
	// ("authorize", "token", "par", "device", "register", "introspect", "revoke", "userinfo",
	// "logout"). Returning false answers 429.
	RateLimiter func(r *http.Request, endpoint string) bool

	// AllowPrivateNetworkFetch lets the server fetch client jwks_uri documents from loopback and
	// private addresses. Leave false in production (SSRF protection).
	AllowPrivateNetworkFetch bool

	// ScopeDescriptions are shown next to each scope on the consent screen. Built-in
	// descriptions exist for openid, profile, email and offline_access.
	ScopeDescriptions map[string]string

	// LoginTemplate and ConsentTemplate replace the built-in pages. They receive
	// OAuthLoginPage and OAuthConsentPage.
	LoginTemplate   *template.Template
	ConsentTemplate *template.Template
}

OAuthServerConfig configures the OAuth2 / OpenID Connect authorization server.

Every field except Issuer is optional. The zero value of each new option keeps the behaviour of earlier versions; see OAUTH2_SERVER.md for the full guide.

type OAuthSigningKey added in v1.2.12

type OAuthSigningKey struct {
	// ID is the JWKS "kid". Derived from the public key (RFC 7638 thumbprint) when empty.
	ID string
	// Key is an *rsa.PrivateKey (RS256) or an *ecdsa.PrivateKey (ES256 for P-256, ES384 for P-384).
	Key crypto.Signer
	// Alg overrides the algorithm inferred from Key (RS256, PS256, ES256, ES384).
	Alg string
}

OAuthSigningKey is a key the authorization server signs tokens with. The first configured key is the default; the others are published in the JWKS so tokens signed before a rotation stay verifiable, and a client can ask for one by id_token_signed_response_alg.

type OAuthTokenInfo added in v1.0.78

type OAuthTokenInfo = sectypes.OAuthTokenInfo

type OIDCConfig added in v1.2.12

type OIDCConfig struct {
	// Issuer is the provider's issuer URL; /.well-known/openid-configuration is fetched from it.
	Issuer       string
	ClientID     string
	ClientSecret string
	RedirectURL  string
	// Scopes defaults to openid, profile, email.
	Scopes       []string
	ProviderName string // default "oidc"

	// Optional, see OAuth2Config.
	UserInfoParser func(userInfo map[string]any) (*UserContext, error)
	AllowedAlgs    []string
	AuthStyle      string
	HTTPClient     *http.Client
	ClockSkew      time.Duration
}

OIDCConfig configures an OpenID Connect provider found by discovery.

type PasskeyAuthenticationOptions added in v1.0.48

type PasskeyAuthenticationOptions = sectypes.PasskeyAuthenticationOptions

type PasskeyAuthenticationResponse added in v1.0.48

type PasskeyAuthenticationResponse = sectypes.PasskeyAuthenticationResponse

func ParsePasskeyAuthenticationResponse added in v1.0.48

func ParsePasskeyAuthenticationResponse(data []byte) (*PasskeyAuthenticationResponse, error)

ParsePasskeyAuthenticationResponse parses a JSON passkey authentication response

type PasskeyAuthenticatorAssertionResponse added in v1.0.48

type PasskeyAuthenticatorAssertionResponse = sectypes.PasskeyAuthenticatorAssertionResponse

type PasskeyAuthenticatorAttestationResponse added in v1.0.48

type PasskeyAuthenticatorAttestationResponse = sectypes.PasskeyAuthenticatorAttestationResponse

type PasskeyAuthenticatorSelection added in v1.0.48

type PasskeyAuthenticatorSelection = sectypes.PasskeyAuthenticatorSelection

type PasskeyBeginAuthenticationRequest added in v1.0.48

type PasskeyBeginAuthenticationRequest struct {
	Username string `json:"username,omitempty"` // Optional for resident key flow
}

PasskeyBeginAuthenticationRequest contains options for starting passkey authentication

type PasskeyBeginRegistrationRequest added in v1.0.48

type PasskeyBeginRegistrationRequest struct {
	UserID      int    `json:"user_id"`
	Username    string `json:"username"`
	DisplayName string `json:"display_name"`
}

PasskeyBeginRegistrationRequest contains options for starting passkey registration

type PasskeyCredential added in v1.0.48

type PasskeyCredential = sectypes.PasskeyCredential

type PasskeyCredentialDescriptor added in v1.0.48

type PasskeyCredentialDescriptor = sectypes.PasskeyCredentialDescriptor

type PasskeyCredentialParam added in v1.0.48

type PasskeyCredentialParam = sectypes.PasskeyCredentialParam

type PasskeyLoginRequest added in v1.0.48

type PasskeyLoginRequest struct {
	Response          PasskeyAuthenticationResponse `json:"response"`
	ExpectedChallenge []byte                        `json:"expected_challenge"`
	Claims            map[string]any                `json:"claims"` // Additional login data
}

PasskeyLoginRequest contains passkey authentication data

type PasskeyProvider added in v1.0.48

type PasskeyProvider interface {
	// BeginRegistration creates registration options for a new passkey
	BeginRegistration(ctx context.Context, userID int, username, displayName string) (*PasskeyRegistrationOptions, error)

	// CompleteRegistration verifies and stores a new passkey credential
	CompleteRegistration(ctx context.Context, userID int, response PasskeyRegistrationResponse, expectedChallenge []byte) (*PasskeyCredential, error)

	// BeginAuthentication creates authentication options for passkey login
	BeginAuthentication(ctx context.Context, username string) (*PasskeyAuthenticationOptions, error)

	// CompleteAuthentication verifies a passkey assertion and returns the user
	CompleteAuthentication(ctx context.Context, response PasskeyAuthenticationResponse, expectedChallenge []byte) (int, error)

	// GetCredentials returns all passkey credentials for a user
	GetCredentials(ctx context.Context, userID int) ([]PasskeyCredential, error)

	// DeleteCredential removes a passkey credential
	DeleteCredential(ctx context.Context, userID int, credentialID string) error

	// UpdateCredentialName updates the friendly name of a credential
	UpdateCredentialName(ctx context.Context, userID int, credentialID string, name string) error
}

PasskeyProvider handles passkey registration and authentication

type PasskeyRegisterRequest added in v1.0.48

type PasskeyRegisterRequest struct {
	UserID            int                         `json:"user_id"`
	Response          PasskeyRegistrationResponse `json:"response"`
	ExpectedChallenge []byte                      `json:"expected_challenge"`
	CredentialName    string                      `json:"credential_name,omitempty"`
}

PasskeyRegisterRequest contains passkey registration data

type PasskeyRegistrationOptions added in v1.0.48

type PasskeyRegistrationOptions = sectypes.PasskeyRegistrationOptions

type PasskeyRegistrationResponse added in v1.0.48

type PasskeyRegistrationResponse = sectypes.PasskeyRegistrationResponse

func ParsePasskeyRegistrationResponse added in v1.0.48

func ParsePasskeyRegistrationResponse(data []byte) (*PasskeyRegistrationResponse, error)

ParsePasskeyRegistrationResponse parses a JSON passkey registration response

type PasskeyRelyingParty added in v1.0.48

type PasskeyRelyingParty = sectypes.PasskeyRelyingParty

type PasskeyUser added in v1.0.48

type PasskeyUser = sectypes.PasskeyUser

type PasswordResetCompleteRequest added in v1.0.85

type PasswordResetCompleteRequest = sectypes.PasswordResetCompleteRequest

type PasswordResetRequest added in v1.0.85

type PasswordResetRequest = sectypes.PasswordResetRequest

type PasswordResetResponse added in v1.0.85

type PasswordResetResponse = sectypes.PasswordResetResponse

type PasswordResettable added in v1.0.85

type PasswordResettable interface {
	// RequestPasswordReset creates a reset token for the given email/username
	RequestPasswordReset(ctx context.Context, req PasswordResetRequest) (*PasswordResetResponse, error)

	// CompletePasswordReset validates the token and sets the new password
	CompletePasswordReset(ctx context.Context, req PasswordResetCompleteRequest) error
}

PasswordResettable allows providers to support self-service password reset

type Refreshable added in v0.0.63

type Refreshable interface {
	// RefreshToken exchanges a refresh token for a new access token
	RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error)
}

Refreshable allows providers to support token refresh

type RegisterRequest added in v1.0.48

type RegisterRequest = sectypes.RegisterRequest

type Registrable added in v1.0.48

type Registrable interface {
	// Register creates a new user account
	Register(ctx context.Context, req RegisterRequest) (*LoginResponse, error)
}

Registrable allows providers to support user registration

type RowSecurity

type RowSecurity = sectypes.RowSecurity

type RowSecurityProvider added in v0.0.63

type RowSecurityProvider interface {
	// GetRowSecurity loads row security rules for a user and entity.
	// userRef identifies the user and is opaque to the caller: it may be an int ID,
	// a string/UUID, or the full *security.UserContext (see SecurityContext.GetUserRef),
	// so providers backed by non-integer user identifiers (e.g. UUIDs) or that need
	// access to JWT claims can implement row security without relying on a numeric ID.
	GetRowSecurity(ctx context.Context, userRef any, schema, table string) (RowSecurity, error)
}

RowSecurityProvider handles row-level security (filtering)

type SecurityContext added in v0.0.67

type SecurityContext interface {
	GetContext() context.Context
	GetUserID() (int, bool)
	// GetUserRef returns an opaque user identifier for row security lookups.
	// Unlike GetUserID, it is not required to be an integer: implementations backed by
	// non-integer identifiers (e.g. UUIDs) can return a string, or the full
	// *security.UserContext so a RowSecurityProvider can read JWT claims directly.
	GetUserRef() (any, bool)
	GetSchema() string
	GetEntity() string
	GetModel() interface{}
	GetQuery() interface{}
	SetQuery(interface{})
	GetResult() interface{}
	SetResult(interface{})
}

SecurityContext is a generic interface that any spec can implement to integrate with security features This interface abstracts the common security context needs across different specs

type SecurityList

type SecurityList struct {
	ColumnSecurityMutex sync.RWMutex
	ColumnSecurity      map[string][]ColumnSecurity
	RowSecurityMutex    sync.RWMutex
	RowSecurity         map[string]RowSecurity
	// contains filtered or unexported fields
}

SecurityList manages security state and caching It wraps a SecurityProvider and provides caching and utility methods

func GetSecurityList added in v0.0.83

func GetSecurityList(ctx context.Context) (*SecurityList, bool)

GetSecurityList extracts the SecurityList from request context

func NewSecurityList added in v0.0.63

func NewSecurityList(provider SecurityProvider) (*SecurityList, error)

NewSecurityList creates a new security list with the given provider

func (*SecurityList) ApplyColumnSecurity

func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType reflect.Type, pUserID int, pSchema, pTablename string) (out reflect.Value, err error)

func (*SecurityList) ClearSecurity

func (m *SecurityList) ClearSecurity(pUserID int, pSchema, pTablename string) error

func (*SecurityList) ColumSecurityApplyOnRecord

func (m *SecurityList) ColumSecurityApplyOnRecord(prevRecord reflect.Value, newRecord reflect.Value, modelType reflect.Type, pUserID int, pSchema, pTablename string) ([]string, error)

func (*SecurityList) GetRowSecurityTemplate

func (m *SecurityList) GetRowSecurityTemplate(pUserRef any, pSchema, pTablename string) (out RowSecurity, err error)

func (*SecurityList) LoadColumnSecurity

func (m *SecurityList) LoadColumnSecurity(ctx context.Context, pUserID int, pSchema, pTablename string, pOverwrite bool) error

func (*SecurityList) LoadRowSecurity

func (m *SecurityList) LoadRowSecurity(ctx context.Context, pUserRef any, pSchema, pTablename string, pOverwrite bool) (RowSecurity, error)

func (*SecurityList) Provider added in v0.0.63

func (m *SecurityList) Provider() SecurityProvider

Provider returns the underlying security provider

func (*SecurityList) SetTxSettings added in v1.2.7

func (m *SecurityList) SetTxSettings(fn TxSettingsFunc)

SetTxSettings sets the function that provides transaction-local settings for every transaction opened by a spec that registered its security hooks with this list. Pass nil to disable. May be called before or after RegisterSecurityHooks.

func (*SecurityList) TxSettings added in v1.2.7

func (m *SecurityList) TxSettings() TxSettingsFunc

TxSettings returns the configured TxSettingsFunc, or nil.

type SecurityProvider added in v0.0.63

type SecurityProvider interface {
	Authenticator
	ColumnSecurityProvider
	RowSecurityProvider
}

SecurityProvider is the main interface combining all security concerns

type SessionCookieOptions added in v1.0.64

type SessionCookieOptions struct {
	// Name is the cookie name. Defaults to "session_token".
	Name string
	// Path is the cookie path. Defaults to "/".
	Path string
	// Domain restricts the cookie to a specific domain. Empty means current host.
	Domain string
	// Secure sets the Secure flag. Defaults to true.
	// Set to false only in local development over HTTP.
	Secure *bool
	// SameSite sets the SameSite policy. Defaults to http.SameSiteLaxMode.
	SameSite http.SameSite
}

SessionCookieOptions configures the session cookie set by SetSessionCookie. All fields are optional; sensible secure defaults are applied when omitted.

type TwoFactorSecret added in v1.0.48

type TwoFactorSecret = sectypes.TwoFactorSecret

type TxSettingsFunc added in v1.2.7

type TxSettingsFunc func(secCtx SecurityContext) (map[string]string, error)

TxSettingsFunc returns the transaction-local settings (e.g. RLS GUCs such as "app.user_id") to stamp on a transaction. It runs once per transaction, at OnTxBegin, before any other SQL. Returning an error rolls the transaction back.

type UserContext added in v0.0.63

type UserContext = sectypes.UserContext

func GetUserContext added in v0.0.63

func GetUserContext(ctx context.Context) (*UserContext, bool)

GetUserContext extracts the full user context from request context

type UserKey added in v1.0.78

type UserKey = sectypes.UserKey

type Validatable added in v0.0.63

type Validatable interface {
	// ValidateToken checks if a token is valid without extracting full user context
	ValidateToken(ctx context.Context, token string) (bool, error)
}

Validatable allows providers to validate tokens without full authentication

type VerifyAccessTokenOptions added in v1.2.12

type VerifyAccessTokenOptions struct {
	// Audience, when set, must be one of the token's audiences.
	Audience string
	// Scopes that must all be granted.
	Scopes []string
}

VerifyAccessTokenOptions tunes VerifyAccessToken.

type WriteDataContext added in v1.2.7

type WriteDataContext interface {
	GetData() interface{}
	SetData(interface{})
}

WriteDataContext is implemented by security contexts that expose the create/update payload of the operation in flight.

Directories

Path Synopsis
Package lookup owns every database read and write the security package needs.
Package lookup owns every database read and write the security package needs.
backends
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.
conformance
Package conformance is the shared behavioural suite every lookup backend must pass.
Package conformance is the shared behavioural suite every lookup backend must pass.
ddl
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.
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.
direct
Package direct is the table-backed implementation of the lookup stores.
Package direct is the table-backed implementation of the lookup stores.
procedure
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