auth

package
v0.0.0-...-05517d8 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: Apache-2.0 Imports: 58 Imported by: 0

README

Authentication and Authorization System for TMI

This package implements a secure, reliable, and robust authentication and authorization system for the TMI service, designed to work with the tmi-ux Angular web application.

Features

  • Multiple OAuth 2.0 Providers: Support for Google, GitHub, and Microsoft authentication
  • JWT-based Authentication: Secure JWT tokens for API authentication
  • Refresh Token Mechanism: Automatic token refresh without requiring re-authentication
  • Account Linking: Link multiple OAuth providers to a single user account
  • Role-based Authorization: Support for owner, writer, and reader roles
  • Redis Caching: High-performance caching of authorization data
  • Database Migrations: Automatic database schema management

Architecture

The authentication system uses a hybrid database approach:

  1. PostgreSQL as the primary persistent store for:

    • User accounts and profiles
    • OAuth provider configurations
    • Authorization data (roles, permissions)
    • Account linking information
  2. Redis for:

    • Authorization cache
    • Token management
    • Rate limiting
    • Session data

Setup

Prerequisites
  • PostgreSQL 12+
  • Redis 6+
  • Go 1.24+
Configuration
  1. Copy the .env.example file to .env:
cp .env.example .env
  1. Edit the .env file with your configuration values:
# PostgreSQL Configuration
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=your_postgres_user
POSTGRES_PASSWORD=your_postgres_password
POSTGRES_DB=tmi
POSTGRES_SSLMODE=disable

# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password
REDIS_DB=0

# JWT Configuration
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRATION_SECONDS=3600
JWT_SIGNING_METHOD=HS256

# OAuth Configuration
OAUTH_CALLBACK_URL=http://localhost:8080/oauth2/callback

# OAuth Provider Configuration
...
  1. Set up OAuth providers:

Integration

To integrate the authentication system into your application:

package main

import (
	"github.com/ericfitz/tmi/auth"
	"github.com/gin-gonic/gin"
)

func main() {
	// Load unified configuration
	config, err := config.Load()
	if err != nil {
		panic(err)
	}

	// Create a new Gin router
	router := gin.Default()

	// Initialize the authentication system with unified config
	// NOTE: Use InitAuthWithConfig, NOT the deprecated InitAuth function
	authHandlers, err := auth.InitAuthWithConfig(router, config)
	if err != nil {
		panic(err)
	}

	// Add your routes
	// ...

	// Start the server
	router.Run(":8080")
}

API Endpoints

OAuth Flow
  • GET /oauth2/providers - List available OAuth providers
  • GET /oauth2/authorize?idp={provider} - Redirect to OAuth provider for authentication
  • GET /oauth2/callback - Handle OAuth callback and issue JWT tokens
Token Management
  • POST /oauth2/token?idp={provider} - Exchange authorization code for JWT tokens
  • POST /oauth2/refresh - Refresh an expired JWT token
  • POST /oauth2/logout - Revoke a refresh token
User Information
  • GET /oauth2/me - Get current user information (requires authentication)

Authorization

To protect your API endpoints, use the authentication middleware:

// Create a new router group with authentication
protected := router.Group("/api")
protected.Use(authMiddleware.AuthRequired())

// Add routes that require authentication
protected.GET("/resource", getResource)

// Add routes that require specific roles
protected.POST("/resource", authMiddleware.RequireWriter(), createResource)
protected.PUT("/resource/:id", authMiddleware.RequireWriter(), updateResource)
protected.DELETE("/resource/:id", authMiddleware.RequireOwner(), deleteResource)

Database Migrations

The authentication system automatically runs database migrations on startup. The migration files are located in the migrations directory.

Cache Rebuilding

The authentication system includes a background job that periodically rebuilds the Redis cache from PostgreSQL to handle any potential inconsistencies.

Security Considerations

  • JWT tokens are short-lived (1 hour by default)
  • Refresh tokens are stored in Redis with automatic expiration
  • CSRF protection is implemented using state parameters
  • All sensitive data is stored securely
  • OAuth provider credentials are stored in environment variables

Documentation

Overview

Package auth — /me/identities/link/* handlers (#383).

Package auth — /oauth2/step_up handler (#397).

Index

Constants

View Source
const (
	// AccessTokenCookieName is the cookie name for the JWT access token
	AccessTokenCookieName = "tmi_access_token"
	// RefreshTokenCookieName is the cookie name for the refresh token
	RefreshTokenCookieName = "tmi_refresh_token" // #nosec G101 -- cookie name constant, not a credential
	// MaxCookieSize is the practical browser cookie size limit
	MaxCookieSize = 4093
)
View Source
const (
	// MinVerifierLength is the minimum length for a code verifier (43 characters)
	MinVerifierLength = 43
	// MaxVerifierLength is the maximum length for a code verifier (128 characters)
	MaxVerifierLength = 128
	// VerifierByteLength is the number of random bytes to generate (32 bytes = 43 base64url chars)
	VerifierByteLength = 32
)

PKCE constants per RFC 7636

View Source
const DefaultProviderCacheTTL = 60 * time.Second

DefaultProviderCacheTTL is the default TTL for the database provider cache.

View Source
const DelegationTokenTTL = 60 * time.Second

DelegationTokenTTL is the wall-clock budget a delegation token is valid for. The threat-model spec (T18) calls for a tight bound — the addon's invocation window — so that a leaked token has a small attack surface. 60 seconds matches the addon-invocation budget called out in #358.

View Source
const (
	// UserCacheTTL defines how long user data is cached
	UserCacheTTL = 15 * time.Minute
)
View Source
const (

	// UserContextKey is the key for the user in the Gin context
	UserContextKey contextKey = "user"
)

Variables

View Source
var DefaultClaimMappings = map[string]string{
	"subject_claim":        "sub",
	"email_claim":          "email",
	"name_claim":           "name",
	"given_name_claim":     "given_name",
	"family_name_claim":    "family_name",
	"picture_claim":        "picture",
	"email_verified_claim": "email_verified",
	"groups_claim":         "groups",
}

DefaultClaimMappings provides standard claim names for common OAuth providers

View Source
var ErrLinkedIdentityNotFound = errors.New("linked identity not found")

ErrLinkedIdentityNotFound is returned when a linked identity row is not found or the caller is not the owner.

View Source
var ErrUserNotFound = fmt.Errorf("user not found: %w", repository.ErrUserNotFound)

ErrUserNotFound is returned by Service user-lookup methods when no user matches. It wraps repository.ErrUserNotFound (itself wrapping dberrors.ErrNotFound) so callers can use errors.Is at any level instead of string-matching; the message keeps the historical "user not found" prefix that legacy substring checks rely on (#719).

View Source
var TestUsers = struct {
	Admin    User
	Regular  User
	External User
}{
	Admin: User{
		InternalUUID:   "admin-internal-uuid",
		Provider:       "tmi",
		ProviderUserID: "admin@example.com",
		Email:          "admin@example.com",
		Name:           "Admin User",
		EmailVerified:  true,
		Groups:         []string{"admins"},
		IsAdmin:        true,
		CreatedAt:      time.Now(),
		ModifiedAt:     time.Now(),
	},
	Regular: User{
		InternalUUID:   "regular-internal-uuid",
		Provider:       "tmi",
		ProviderUserID: "user@example.com",
		Email:          "user@example.com",
		Name:           "Regular User",
		EmailVerified:  true,
		Groups:         []string{},
		IsAdmin:        false,
		CreatedAt:      time.Now(),
		ModifiedAt:     time.Now(),
	},
	External: User{
		InternalUUID:   "external-internal-uuid",
		Provider:       "google",
		ProviderUserID: "external@gmail.com",
		Email:          "external@gmail.com",
		Name:           "External User",
		EmailVerified:  true,
		Groups:         []string{},
		IsAdmin:        false,
		CreatedAt:      time.Now(),
		ModifiedAt:     time.Now(),
	},
}

TestUsers provides standard test user identities for auth testing

Functions

func AssembleOAuthProviders

func AssembleOAuthProviders(settings []ProviderSetting) map[string]OAuthProviderConfig

AssembleOAuthProviders groups settings by provider ID and assembles OAuthProviderConfig structs. Exported so the api package can use it for enable-validation. SEM@01c02cfa6ab0177d2afcd8cdcc078f4a59973080: convert flat provider settings into a map of OAuth provider configs keyed by ID (pure)

func AssembleSAMLProviders

func AssembleSAMLProviders(settings []ProviderSetting) map[string]SAMLProviderConfig

AssembleSAMLProviders groups settings by provider ID and assembles SAMLProviderConfig structs. Exported so the api package can use it for enable-validation. SEM@78155d54490599e00095eb72b817575bb1e8da5b: convert flat provider settings into a map of SAML provider configs keyed by ID (pure)

func BuildIdentityLinkAuthorizationURL

func BuildIdentityLinkAuthorizationURL(provider Provider, cfg OAuthProviderConfig, state string) (string, error)

BuildIdentityLinkAuthorizationURL builds the upstream authorize URL for an identity-link round-trip. Appends prompt=select_account to force account selection at the provider. Strong providers (those that honor prompt=consent) also get prompt="select_account consent" so the user explicitly re-authorizes scope grants. SAML providers are not supported for identity-link. SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: build an authorization URL that forces account selection and, for strong providers, consent (pure)

func BuildStepUpAuthorizationURL

func BuildStepUpAuthorizationURL(provider Provider, cfg OAuthProviderConfig, state string) (string, error)

BuildStepUpAuthorizationURL builds the upstream authorize URL for a step-up round-trip. For OAuth/OIDC providers it appends prompt=login and max_age=0 to the URL returned by provider.GetAuthorizationURL(state). SAML callers must not use this function; they call GetAuthorizationURLForceAuthn on the SAML provider directly. SEM@381909438c48d60df5164d4ea214359f1b52ebdf: build an authorization URL that forces interactive re-authentication via prompt=login and max_age=0 (pure)

func ClearTokenCookies

func ClearTokenCookies(c *gin.Context, opts CookieOptions)

ClearTokenCookies clears both token cookies by setting MaxAge=-1. Cookie attributes (Path, Domain, HttpOnly, Secure, SameSite) must match the values used when setting for browsers to clear correctly. SEM@65af9b7db2850b6e18076df15ed522c8df4bb64c: expire and clear both token cookies from the HTTP response

func ComputeS256Challenge

func ComputeS256Challenge(codeVerifier string) string

ComputeS256Challenge computes the S256 code challenge from a code verifier Returns base64url(SHA256(codeVerifier)) SEM@7f2e891b97d9b875349295375fb64355109504b5: compute the S256 PKCE code challenge from a code verifier (pure)

func ExpectUserCreate

func ExpectUserCreate(mock sqlmock.Sqlmock)

ExpectUserCreate sets up mock expectation for user creation SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: register a sqlmock expectation for an INSERT INTO users statement (mutates shared state)

func ExpectUserDelete

func ExpectUserDelete(mock sqlmock.Sqlmock, userID string)

ExpectUserDelete sets up mock expectation for user deletion SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: register a sqlmock expectation for a DELETE FROM users statement by user ID (mutates shared state)

func ExpectUserQuery

func ExpectUserQuery(mock sqlmock.Sqlmock, email string, user *User)

ExpectUserQuery sets up mock expectation for a user query by email SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: register a sqlmock expectation for a user lookup by email (mutates shared state)

func ExpectUserUpdate

func ExpectUserUpdate(mock sqlmock.Sqlmock)

ExpectUserUpdate sets up mock expectation for user update SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: register a sqlmock expectation for an UPDATE users statement (mutates shared state)

func ExtractAccessTokenFromCookie

func ExtractAccessTokenFromCookie(c *gin.Context) string

ExtractAccessTokenFromCookie returns the access token from the request cookie, or empty string if not present. SEM@314b7ae8fe586a75ecee2e8fa7103d3193f15f7c: fetch the access token string from the request cookie, or empty string if absent (pure)

func ExtractRefreshTokenFromCookie

func ExtractRefreshTokenFromCookie(c *gin.Context) string

ExtractRefreshTokenFromCookie returns the refresh token from the request cookie, or empty string if not present. SEM@314b7ae8fe586a75ecee2e8fa7103d3193f15f7c: fetch the refresh token string from the request cookie, or empty string if absent (pure)

func GenerateCodeVerifier

func GenerateCodeVerifier() (string, error)

GenerateCodeVerifier generates a cryptographically secure random code verifier Returns a 43-character base64url-encoded string (32 random bytes) SEM@cdbe48c974fb76e1161972733b30bb0d1c02c3b1: generate a cryptographically random PKCE code verifier per RFC 7636 (pure)

func GetDatabaseManager deprecated

func GetDatabaseManager() *db.Manager

GetDatabaseManager returns the global database manager.

Deprecated: Use db.GetGlobalManager() instead. This function is retained for backward compatibility with code that uses auth.GetDatabaseManager() after calling auth.InitAuthWithConfig(). SEM@3080aafd268e1adeeb4b0e7b35049f3b5e926c7c: fetch the global database manager; deprecated in favor of db.GetGlobalManager (pure)

func InitAuth

func InitAuth(router *gin.Engine) error

InitAuth initializes the authentication system SEM@acf29174839ed9f1cb1950265092e2bdacdcb5bd: initialize the auth subsystem: DB, Redis, migrations, service, and background jobs

func IntegrationExample

func IntegrationExample()

IntegrationExample shows how to integrate the authentication system with the main application SEM@5bee0ccf713bf421a1f87a8a81f7ce423e3ef627: print developer guidance for wiring the auth system into the main server (pure)

func MockEmptyUserRows

func MockEmptyUserRows(mock sqlmock.Sqlmock) *sqlmock.Rows

MockEmptyUserRows returns empty rows for user queries SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build an empty sqlmock row set for user queries that return no results (pure)

func MockUserRow

func MockUserRow(mock sqlmock.Sqlmock, user User) *sqlmock.Rows

MockUserRow returns mock SQL rows for a user query SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build sqlmock rows representing a single user record (pure)

func SetTokenCookies

func SetTokenCookies(c *gin.Context, tokenPair TokenPair, opts CookieOptions)

SetTokenCookies sets HttpOnly cookies for access and refresh tokens on the response. Both cookies are HttpOnly to prevent JavaScript access (XSS protection). The access token cookie uses SameSite=Lax (safe for REST APIs that don't mutate on GET). The refresh token cookie uses SameSite=Strict with Path=/oauth2 for maximum protection. SEM@65af9b7db2850b6e18076df15ed522c8df4bb64c: set HttpOnly access and refresh token cookies on the HTTP response

func SetupMockDB

func SetupMockDB(t *testing.T) (*sql.DB, sqlmock.Sqlmock)

SetupMockDB creates a mock SQL database for testing SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build a mock SQL database and sqlmock controller for unit tests (pure)

func SetupMockRedis

func SetupMockRedis(t *testing.T) (*redis.Client, *miniredis.Miniredis)

SetupMockRedis creates a mock Redis client using miniredis SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build an in-process miniredis server and Redis client for unit tests (pure)

func Shutdown

func Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the authentication system SEM@3080aafd268e1adeeb4b0e7b35049f3b5e926c7c: close all database and Redis connections held by the global auth manager

func ValidateAllOAuthProviders

func ValidateAllOAuthProviders(ctx context.Context, client *DiscoveryClient, providers map[string]OAuthProviderConfig) []string

ValidateAllOAuthProviders classifies and validates every enabled OAuth provider. Returns a slice of human-readable error messages; an empty slice means all enabled providers are safe to start. Disabled providers are skipped. SEM@590a86b0b387cc4227809d93d3f4f0942a8bf915: classify and validate every enabled OAuth provider, returning all config errors

func ValidateClassifiedProvider

func ValidateClassifiedProvider(p ClassifiedProvider, cfg OAuthProviderConfig) []string

ValidateClassifiedProvider returns a slice of error messages describing reasons the provider config is not safe to enable. An empty slice means the provider is OK. SEM@6348e71f577bd1e4c952467c7bb6ffd800ab73c1: validate a classified provider has required subject_claim config or OIDC compliance (pure)

func ValidateCodeChallenge

func ValidateCodeChallenge(codeVerifier, codeChallenge, method string) error

ValidateCodeChallenge validates that a code verifier matches the code challenge Uses constant-time comparison to prevent timing attacks SEM@7f2e891b97d9b875349295375fb64355109504b5: validate that a code verifier matches a code challenge using constant-time S256 comparison (pure)

func ValidateCodeChallengeFormat

func ValidateCodeChallengeFormat(challenge string) error

ValidateCodeChallengeFormat validates the format of a code challenge SEM@7f2e891b97d9b875349295375fb64355109504b5: validate a PKCE code challenge meets length and character requirements (pure)

func ValidateCodeVerifierFormat

func ValidateCodeVerifierFormat(verifier string) error

ValidateCodeVerifierFormat validates the format of a code verifier SEM@7f2e891b97d9b875349295375fb64355109504b5: validate a PKCE code verifier meets RFC 7636 length and character requirements (pure)

func ValidateOAuthProvider

func ValidateOAuthProvider(p OAuthProviderConfig) []string

ValidateOAuthProvider checks that required fields are present for an enabled OAuth provider. Returns a list of missing field names, or nil if valid. SEM@35152cade8dd1f51e8debcb763b3aadbb9e5be9e: validate required fields for an OAuth provider config; return missing field names (pure)

func ValidateSAMLProvider

func ValidateSAMLProvider(p SAMLProviderConfig) []string

ValidateSAMLProvider checks that required fields are present for an enabled SAML provider. Returns a list of missing field names, or nil if valid. SEM@35152cade8dd1f51e8debcb763b3aadbb9e5be9e: validate required fields for a SAML provider config; return missing field names (pure)

func ValidateTokenClaims

func ValidateTokenClaims(t *testing.T, claims *Claims, user User)

ValidateTokenClaims is a helper to validate common JWT claims SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: assert that JWT claims match the expected user's subject, email, and name (pure)

Types

type AdminChecker

type AdminChecker interface {
	IsAdmin(ctx context.Context, userInternalUUID *string, provider string, groupUUIDs []string) (bool, error)
	IsSecurityReviewer(ctx context.Context, userInternalUUID *string, provider string, groupUUIDs []string) (bool, error)
	GetGroupUUIDsByNames(ctx context.Context, provider string, groupNames []string) ([]string, error)
}

AdminChecker is an interface for checking if a user is an administrator or security reviewer SEM@a0040890dd7b1940f542d4211d4338cd0e713cbc: contract for checking admin and security-reviewer roles for a user (pure)

type AuthScenario

type AuthScenario struct {
	Name           string
	User           User
	RequiredRole   string
	ExpectedAccess bool
	SetupContext   func(context.Context) context.Context
}

AuthScenario represents an authorization test scenario SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: data type grouping a user, required role, and expected access decision for authorization tests (pure)

type BaseProvider

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

BaseProvider provides common functionality for all providers SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: concrete OAuth 2.0 provider with SSRF-hardened HTTP client and configurable endpoints

func NewBaseProvider

func NewBaseProvider(config OAuthProviderConfig, callbackURL string) (*BaseProvider, error)

NewBaseProvider creates a new base OAuth provider SEM@e55d63794c48585aafab36880122df63ab8ab1be: build a BaseProvider from config, wiring an OAuth2 config and hardened HTTP client

func (*BaseProvider) ExchangeCode

func (p *BaseProvider) ExchangeCode(ctx context.Context, code string) (*TokenResponse, error)

ExchangeCode exchanges an authorization code for tokens SEM@1e2b7727631da52c28fed17143510d6ce64bfe65: exchange an OAuth2 authorization code for access, refresh, and ID tokens

func (*BaseProvider) GetAuthorizationURL

func (p *BaseProvider) GetAuthorizationURL(state string) string

GetAuthorizationURL returns the authorization URL with the given state SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: build the OAuth2 authorization URL for a given state parameter (pure)

func (*BaseProvider) GetOAuth2Config

func (p *BaseProvider) GetOAuth2Config() *oauth2.Config

GetOAuth2Config returns the OAuth2 configuration SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: return the provider's OAuth2 configuration (pure)

func (*BaseProvider) GetUserInfo

func (p *BaseProvider) GetUserInfo(ctx context.Context, accessToken string) (*UserInfo, error)

GetUserInfo gets user information from the provider SEM@3d0d5a8cf02fa74fad102f0f99c2b936a164bbea: fetch user profile claims from all configured userinfo endpoints using an access token (reads network)

func (*BaseProvider) ValidateIDToken

func (p *BaseProvider) ValidateIDToken(ctx context.Context, idToken string) (*IDTokenClaims, error)

ValidateIDToken validates an ID token SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: reject ID token validation; base provider does not support ID tokens (pure)

type Claims

type Claims struct {
	Email              string             `json:"email"`
	EmailVerified      bool               `json:"email_verified,omitempty"`
	Name               string             `json:"name"`
	IdentityProvider   string             `json:"idp,omitempty"`                      // Identity provider
	Groups             []string           `json:"groups,omitempty"`                   // User's groups from IdP
	IsAdministrator    *bool              `json:"tmi_is_administrator,omitempty"`     // TMI Administrators group membership
	IsSecurityReviewer *bool              `json:"tmi_is_security_reviewer,omitempty"` // TMI Security Reviewers group membership
	Delegation         *DelegationContext `json:"delegation,omitempty"`               // T18: scoped delegation token for addon invocations
	// AuthTime is the timestamp (Unix seconds) of the user's last interactive
	// IdP authentication. OIDC-standard claim. #355 step-up middleware reads
	// this to decide whether a /admin/* write requires re-authentication.
	// Refresh-token rotation preserves this value (refresh proves possession
	// of the refresh token, not freshness of the human).
	AuthTime *jwt.NumericDate `json:"auth_time,omitempty"`
	jwt.RegisteredClaims
}

Claims represents the JWT claims SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: JWT claims struct carrying email, groups, role flags, delegation context, and auth_time (pure)

type ClaimsEnricher

type ClaimsEnricher interface {
	// EnrichClaims checks built-in group membership and resolves TMI-managed group names for a user.
	// Returns whether the user is an administrator, security reviewer, and the user's TMI group names.
	EnrichClaims(ctx context.Context, userInternalUUID string, provider string, groupNames []string) (isAdmin bool, isSecurityReviewer bool, tmiGroupNames []string, err error)
}

ClaimsEnricher enriches JWT claims with application-specific data (e.g., group membership) that cannot be directly accessed from the auth package without creating circular dependencies. SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: interface for enriching JWT claims with TMI group membership and role flags

type ClassifiedProvider

type ClassifiedProvider struct {
	ProviderID     string
	Classification ProviderClassification
	DiscoveryDoc   *OIDCDiscoveryDoc // nil for ClassificationNonOIDC
}

SEM@8745e8dc70d45d797efdea06bea563dc07b57029: pairing of an OAuth provider ID with its OIDC classification and discovery document

func ClassifyProvider

func ClassifyProvider(ctx context.Context, client *DiscoveryClient, providerID string, cfg OAuthProviderConfig) ClassifiedProvider

ClassifyProvider buckets a provider based on discovery probe results and configured userinfo URL. Compares the *primary* userinfo endpoint only (cfg.UserInfo[0]); secondary/additional endpoints are operator extensions and never affect classification. SEM@ca3b728abe3e870c10f19a9fcbf6502559196aa0: probe OIDC discovery for a provider and classify its userinfo compliance level

type ClientCallbackAllowList

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

ClientCallbackAllowList validates client_callback URLs supplied to /oauth2/authorize against a configured set of allowed patterns. A pattern ending in "*" is a prefix match; all others are exact matches.

An allowlist with zero patterns rejects every URL (fail-closed). This closes the open-redirect / OAuth phishing surface (T16) by ensuring an attacker cannot smuggle a malicious client_callback through the authorize endpoint. SEM@72ef5c64a4ca8965f90ee105cc73893284c60b1a: allowlist for validating OAuth client_callback URLs against configured patterns (pure)

func NewClientCallbackAllowList

func NewClientCallbackAllowList(patterns []string) *ClientCallbackAllowList

NewClientCallbackAllowList creates an allow-list from the given URL patterns. Empty entries are dropped. SEM@72ef5c64a4ca8965f90ee105cc73893284c60b1a: build a ClientCallbackAllowList from URL patterns, dropping empty entries (pure)

func (*ClientCallbackAllowList) Allowed

func (a *ClientCallbackAllowList) Allowed(url string) bool

Allowed returns true if url matches at least one configured pattern. An empty allowlist always returns false (fail-closed). SEM@72ef5c64a4ca8965f90ee105cc73893284c60b1a: check whether a URL matches at least one allowlist pattern; fail-closed on empty list (pure)

func (*ClientCallbackAllowList) Configured

func (a *ClientCallbackAllowList) Configured() bool

Configured returns true if the allowlist has at least one pattern. Used by /oauth2/authorize to surface a startup warning when the allowlist is empty. SEM@72ef5c64a4ca8965f90ee105cc73893284c60b1a: report whether the allowlist has at least one configured pattern (pure)

type ClientCredential

type ClientCredential struct {
	ID               uuid.UUID
	OwnerUUID        uuid.UUID
	ClientID         string
	ClientSecretHash string
	Name             string
	Description      string
	IsActive         bool
	LastUsedAt       *time.Time
	CreatedAt        time.Time
	ModifiedAt       time.Time
	ExpiresAt        *time.Time
}

ClientCredential represents an OAuth 2.0 client credential for machine-to-machine authentication SEM@2e1e229947d57021bf27a7c51c052e3e2a18c98e: domain model for an OAuth 2.0 client credential used in machine-to-machine authentication

func CreateTestClientCredential

func CreateTestClientCredential(ownerUUID uuid.UUID, name string) *ClientCredential

CreateTestClientCredential creates a test client credential SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build a ClientCredential fixture with a placeholder bcrypt hash (pure)

type ClientCredentialCreateParams

type ClientCredentialCreateParams struct {
	OwnerUUID        uuid.UUID
	ClientID         string
	ClientSecretHash string
	Name             string
	Description      string
	ExpiresAt        *time.Time
}

ClientCredentialCreateParams contains parameters for creating a new client credential SEM@2e1e229947d57021bf27a7c51c052e3e2a18c98e: parameters for creating a new client credential

type ClientCredentialTestCase

type ClientCredentialTestCase struct {
	Name          string
	ClientID      string
	ClientSecret  string
	ExpectSuccess bool
	ExpectedError string
	SetupMock     func(sqlmock.Sqlmock)
	VerifyResult  func(*testing.T, *TokenPair)
}

ClientCredentialTestCase represents a test case for client credential operations SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: data type grouping client credential inputs, mock setup, and expected outcomes for table-driven tests (pure)

type Config

type Config struct {
	Database  DatabaseConfig // Database config with URL-based connection string
	Redis     RedisConfig
	JWT       JWTConfig
	OAuth     OAuthConfig
	SAML      SAMLConfig
	BuildMode string // dev, test, or production
}

Config holds all authentication configuration SEM@fe6575f1c15d84b67ee9853a0e59055c1ebe44b6: top-level auth configuration aggregating database, Redis, JWT, OAuth, and SAML settings (pure)

func ConfigFromUnified

func ConfigFromUnified(unified *config.Config) Config

ConfigFromUnified converts unified config to auth-specific config SEM@72ef5c64a4ca8965f90ee105cc73893284c60b1a: convert the unified application config to the auth-package Config struct (pure)

func LoadConfig

func LoadConfig() (Config, error)

LoadConfig loads configuration from environment variables. This uses DATABASE_URL as the primary database configuration method. SEM@e03fc554584eab95175850a0591c019a25ec0d56: build the auth Config from environment variables; fail if TMI_DATABASE_URL is absent (reads env)

func (*Config) GetEnabledProviders

func (c *Config) GetEnabledProviders() []OAuthProviderConfig

GetEnabledProviders returns a slice of enabled OAuth providers SEM@0ecd9032832c43f28451afc85a42dd56721c8b3c: list all enabled OAuth provider configs (pure)

func (*Config) GetJWTDuration

func (c *Config) GetJWTDuration() time.Duration

GetJWTDuration returns the JWT expiration duration SEM@d885c7955d5a30affb8ddde84ee1cf757aab2a6b: return the JWT expiration as a time.Duration (pure)

func (*Config) GetProvider

func (c *Config) GetProvider(providerID string) (OAuthProviderConfig, bool)

GetProvider returns a specific OAuth provider configuration SEM@0ecd9032832c43f28451afc85a42dd56721c8b3c: fetch an enabled OAuth provider by ID; return false if absent or disabled (pure)

func (*Config) ToGormConfig

func (c *Config) ToGormConfig() db.GormConfig

ToGormConfig converts Config to db.GormConfig for GORM database connections. It parses the DATABASE_URL to extract connection parameters. SEM@fe6575f1c15d84b67ee9853a0e59055c1ebe44b6: convert the auth database config to a GORM connection config, parsing the database URL (pure)

func (*Config) ToRedisConfig

func (c *Config) ToRedisConfig() db.RedisConfig

ToRedisConfig converts Config to db.RedisConfig SEM@a251f60c11fe9831021be2539ff7d746fbd65b2c: convert the auth Redis config to a db.RedisConfig for initializing the Redis client (pure)

func (*Config) ValidateConfig

func (c *Config) ValidateConfig() error

ValidateConfig validates the configuration SEM@e03fc554584eab95175850a0591c019a25ec0d56: validate auth config fields including signing-method key requirements and secret uniqueness; reject invalid configs (pure)

type CookieOptions

type CookieOptions struct {
	Domain     string // Cookie domain (hostname)
	Secure     bool   // Require HTTPS
	Enabled    bool   // Whether cookie-based auth is enabled
	ExpiresIn  int    // Access token cookie MaxAge in seconds
	RefreshTTL int    // Refresh token cookie MaxAge in seconds
}

CookieOptions holds configuration for session cookie operations SEM@314b7ae8fe586a75ecee2e8fa7103d3193f15f7c: configuration for HttpOnly session token cookie attributes (pure)

type DatabaseConfig

type DatabaseConfig struct {
	URL                  string // DATABASE_URL - contains all connection parameters
	OracleWalletLocation string // path to Oracle wallet for ADB (cannot be in URL)

	// Connection pool configuration
	MaxOpenConns    int // Maximum open connections (default: 10)
	MaxIdleConns    int // Maximum idle connections (default: 2)
	ConnMaxLifetime int // Max connection lifetime in seconds (default: 240)
	ConnMaxIdleTime int // Max idle time in seconds (default: 30)
}

DatabaseConfig holds unified database configuration. Database type is determined from the URL scheme (postgres://, mysql://, etc.) SEM@fe6575f1c15d84b67ee9853a0e59055c1ebe44b6: database connection settings including URL, Oracle wallet path, and connection pool limits (pure)

type DefaultProviderRegistry

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

DefaultProviderRegistry merges immutable config/env providers with mutable database-sourced providers assembled from system_settings rows. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: TTL-cached registry merging config-file and database OAuth and SAML provider configs (reads DB)

func NewDefaultProviderRegistry

func NewDefaultProviderRegistry(
	configOAuth map[string]OAuthProviderConfig,
	configSAML map[string]SAMLProviderConfig,
	settings ProviderSettingsReader,
) *DefaultProviderRegistry

NewDefaultProviderRegistry creates a new DefaultProviderRegistry with the given config/env providers and a settings reader for database-sourced providers. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: build a DefaultProviderRegistry from static config maps and a database settings reader (pure)

func (*DefaultProviderRegistry) GetEnabledOAuthProviders

func (r *DefaultProviderRegistry) GetEnabledOAuthProviders() map[string]OAuthProviderConfig

GetEnabledOAuthProviders returns all enabled OAuth providers from all sources. Config/env providers shadow database-sourced providers with the same ID. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: list all enabled OAuth provider configs from config and database sources (reads DB)

func (*DefaultProviderRegistry) GetEnabledSAMLProviders

func (r *DefaultProviderRegistry) GetEnabledSAMLProviders() map[string]SAMLProviderConfig

GetEnabledSAMLProviders returns all enabled SAML providers from all sources. Config/env providers shadow database-sourced providers with the same ID. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: list all enabled SAML provider configs from config and database sources (reads DB)

func (*DefaultProviderRegistry) GetOAuthProvider

func (r *DefaultProviderRegistry) GetOAuthProvider(id string) (OAuthProviderConfig, bool)

GetOAuthProvider returns the OAuth provider configuration for the given ID. Config/env providers take precedence over database-sourced providers. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: fetch an OAuth provider config by ID; config-file entries shadow database entries (reads DB)

func (*DefaultProviderRegistry) GetSAMLProvider

func (r *DefaultProviderRegistry) GetSAMLProvider(id string) (SAMLProviderConfig, bool)

GetSAMLProvider returns the SAML provider configuration for the given ID. Config/env providers take precedence over database-sourced providers. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: fetch a SAML provider config by ID; config-file entries shadow database entries (reads DB)

func (*DefaultProviderRegistry) InvalidateCache

func (r *DefaultProviderRegistry) InvalidateCache()

InvalidateCache marks the database provider cache as dirty so it will be refreshed on the next access. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: mark the database provider cache dirty to force refresh on next access (mutates shared state)

type DelegationContext

type DelegationContext struct {
	// AddonID is the addon being invoked.
	AddonID string `json:"addon_id"`
	// DeliveryID is the unique webhook delivery record this token was
	// minted for. Replays of the same delivery will mint distinct tokens
	// (one per attempt), all sharing this DeliveryID.
	DeliveryID string `json:"delivery_id"`
	// ThreatModelID is the parent threat model the invocation targets.
	// Writes to other threat models with this token are out of scope; a
	// future hardening pass can add per-resource allowlist enforcement
	// (the schema field `subject_authority: invoker` is the route-level
	// gate today; the resource-level scope check is residual scope).
	ThreatModelID string `json:"threat_model_id"`
}

DelegationContext is the addon-invocation scope embedded in a delegation JWT (T18, #358). Its presence on a token means: "this token impersonates the invoker for the duration of one specific addon invocation against one specific threat model — do not allow it to escape that scope".

Routes that addons hit on the write-back path declare `x-tmi-authz: { subject_authority: "invoker" }` to require this token shape (and to reject service-account-only tokens). The token's `sub` is the invoker's provider_user_id; the rest of the user-identity claims (email, name, provider, groups, tmi_is_security_reviewer) are copied from the invoker so existing handler code reads the invoker's identity transparently. SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: scoped addon-invocation context embedded in delegation JWTs to limit token authority (pure)

type DeletionChallenge

type DeletionChallenge struct {
	ChallengeText string    `json:"challenge_text"`
	ExpiresAt     time.Time `json:"expires_at"`
}

DeletionChallenge contains challenge information for user deletion SEM@bd740ab90ce24a669adc1fa8b8153efbd33bac10: confirmation challenge text and expiry issued to a user before self-deletion (pure)

type DeletionResult

type DeletionResult struct {
	ThreatModelsTransferred int    `json:"threat_models_transferred"`
	ThreatModelsDeleted     int    `json:"threat_models_deleted"`
	UserEmail               string `json:"user_email"`
}

DeletionResult contains statistics about the user deletion operation SEM@bd740ab90ce24a669adc1fa8b8153efbd33bac10: summary of transferred and deleted threat models produced by a user deletion (pure)

type DiscoveryClient

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

SEM@5c1bcf212b418d73d444551267f29e1025089cb6: HTTP client with TTL cache and singleflight deduplication for OIDC discovery requests (mutates shared state)

func NewDiscoveryClient

func NewDiscoveryClient(timeout, cacheTTL time.Duration) *DiscoveryClient

SEM@e55d63794c48585aafab36880122df63ab8ab1be: build an OIDC discovery client with redirect-refusing HTTP transport and a TTL cache (pure)

func (*DiscoveryClient) Discover

func (c *DiscoveryClient) Discover(ctx context.Context, issuerURL string) (*OIDCDiscoveryDoc, error)

Discover fetches and validates the OIDC discovery doc for issuerURL. Returns a valid doc on success. Returns (nil, nil) when the issuer is not OIDC-compliant (404, network error, invalid JSON, or doc fails IsValid) — callers should treat nil-doc as "not OIDC" rather than as an error. Returns (nil, err) only for programmer errors (e.g. invalid issuerURL).

Concurrent first-fetches for the same issuer collapse into a single upstream request via singleflight (#292); subsequent calls hit the cache. SEM@5c1bcf212b418d73d444551267f29e1025089cb6: fetch and cache the OIDC discovery document for an issuer, collapsing concurrent requests (mutates shared state)

type GenericOIDCProvider

type GenericOIDCProvider struct {
	BaseProvider
	// contains filtered or unexported fields
}

GenericOIDCProvider is a generic OIDC provider SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: OIDC provider extending BaseProvider with discovery-backed ID token verification (pure)

func NewGenericOIDCProvider

func NewGenericOIDCProvider(config OAuthProviderConfig, callbackURL string) (*GenericOIDCProvider, error)

NewGenericOIDCProvider creates a new generic OIDC provider SEM@1e2b7727631da52c28fed17143510d6ce64bfe65: build a GenericOIDCProvider via OIDC discovery, falling back gracefully on issuer mismatch (reads network)

func (*GenericOIDCProvider) ValidateIDToken

func (p *GenericOIDCProvider) ValidateIDToken(ctx context.Context, idToken string) (*IDTokenClaims, error)

ValidateIDToken validates an ID token SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: validate an OIDC ID token using the provider verifier and return its claims (pure)

type GormLinkedIdentityStore

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

GormLinkedIdentityStore is the GORM-backed implementation of LinkedIdentityStore. SEM@211793c39ea528b3d2da244f3504963c40584df7: GORM-backed store for linked OAuth identity records (reads DB)

func NewGormLinkedIdentityStore

func NewGormLinkedIdentityStore(db *gorm.DB) *GormLinkedIdentityStore

NewGormLinkedIdentityStore returns a new GormLinkedIdentityStore. SEM@211793c39ea528b3d2da244f3504963c40584df7: build a GORM-backed linked identity store from a db handle (pure)

func (*GormLinkedIdentityStore) Create

Create inserts a new linked identity row. SEM@211793c39ea528b3d2da244f3504963c40584df7: store a new linked identity record; return typed duplicate error on constraint violation (reads DB)

func (*GormLinkedIdentityStore) CreateExclusive

CreateExclusive performs a check-then-create inside a single serializable transaction, eliminating the TOCTOU race that exists when the re-check and the insert run in separate statements. The unique index remains the final backstop; this method surfaces the conflict as dberrors.ErrDuplicate before reaching the constraint so that callers get a typed error in both the serializable-read-caught and the constraint-caught paths.

PKCE protects a public-client code exchange that does not exist in the link flow; the pending-token + UUID-matched step-up-fresh confirm is the binding mechanism. A serializable transaction here is the correct concurrency guard. SEM@053baa340d412aa135be32953dfcb6133af89b4d: create a linked identity inside a serializable transaction to prevent TOCTOU race on duplicate binding (reads DB)

func (*GormLinkedIdentityStore) Delete

func (s *GormLinkedIdentityStore) Delete(ctx context.Context, id, ownerUUID string) error

Delete removes the linked identity identified by id, scoped to ownerUUID. SEM@211793c39ea528b3d2da244f3504963c40584df7: delete a linked identity scoped to an owner UUID; return not-found if no row matches (reads DB)

func (*GormLinkedIdentityStore) GetByProviderSub

func (s *GormLinkedIdentityStore) GetByProviderSub(ctx context.Context, provider, providerUserID string) (models.LinkedIdentity, error)

GetByProviderSub looks up a linked identity by provider and provider-user-id. SEM@211793c39ea528b3d2da244f3504963c40584df7: fetch a linked identity by OAuth provider and provider user ID (reads DB)

func (*GormLinkedIdentityStore) ListByUser

func (s *GormLinkedIdentityStore) ListByUser(ctx context.Context, userInternalUUID string) ([]models.LinkedIdentity, error)

ListByUser returns all linked identities owned by userInternalUUID. SEM@211793c39ea528b3d2da244f3504963c40584df7: list all linked identities owned by a user UUID (reads DB)

func (*GormLinkedIdentityStore) TouchLastUsed

func (s *GormLinkedIdentityStore) TouchLastUsed(ctx context.Context, id string) error

TouchLastUsed updates last_used_at to now for the given identity id. SEM@211793c39ea528b3d2da244f3504963c40584df7: update last_used_at timestamp to now for the given linked identity (reads DB)

type GroupDeletionResult

type GroupDeletionResult struct {
	ThreatModelsDeleted  int    `json:"threat_models_deleted"`
	ThreatModelsRetained int    `json:"threat_models_retained"`
	GroupName            string `json:"group_name"`
}

GroupDeletionResult contains statistics about the group deletion operation SEM@9f44e79e7d62c7dfd68d7566466e5b7ddab1f34a: counts of threat models deleted and retained when a group is removed (pure)

type Handlers

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

Handlers provides HTTP handlers for authentication SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: aggregate of auth HTTP handler dependencies including service, config, and auditors (pure)

func InitAuthWithConfig deprecated

func InitAuthWithConfig(router *gin.Engine, unified *config.Config) (*Handlers, error)

InitAuthWithConfig initializes the auth system with unified configuration.

Deprecated: Use InitAuthWithDB for explicit dependency injection. This function creates its own database manager internally, which can lead to duplicate initialization and DRY violations. Prefer passing a pre-initialized db.Manager to InitAuthWithDB instead. SEM@ebd32e782424ee1fd1698669b7522b6ab3eccf42: build the auth service: connect DB/Redis, migrate schema, register handlers (deprecated) (mutates DB)

func InitAuthWithDB

func InitAuthWithDB(dbManager *db.Manager, unified *config.Config) (*Handlers, error)

InitAuthWithDB initializes the auth system with an existing database manager. This is the preferred initialization method for explicit dependency injection. The caller is responsible for initializing the database connections before calling this function. SEM@72ef5c64a4ca8965f90ee105cc73893284c60b1a: initialize the auth system using an injected database manager and unified config

func NewHandlers

func NewHandlers(service *Service, config Config) *Handlers

NewHandlers creates new authentication handlers SEM@d885c7955d5a30affb8ddde84ee1cf757aab2a6b: build an auth Handlers instance bound to a service and config (pure)

func (*Handlers) Authorize

func (h *Handlers) Authorize(c *gin.Context)

Authorize redirects to the OAuth provider's authorization page SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: validate PKCE and scope parameters and redirect the user to the OAuth provider's authorization endpoint

func (*Handlers) Callback

func (h *Handlers) Callback(c *gin.Context)

Callback handles the OAuth callback SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: handle the OAuth provider redirect, parse state, and dispatch PKCE or identity-link flow

func (*Handlers) Config

func (h *Handlers) Config() Config

Config returns the auth config (getter for unexported field) SEM@0eb4bf778ed84abb8fa3d433bf42cc7928258257: return the auth config from the handler (pure)

func (h *Handlers) ConfirmIdentityLink(c *gin.Context)

ConfirmIdentityLink handles POST /me/identities/link/confirm. Consumes the one-time pending link token and inserts the linked identity row. Returns 201 with the new LinkedIdentity on success. SEM@fc8e2c83f6aaba09d10a2ed6f6e78a5075d278ba: consume a one-time link token and persist the linked identity for the current user (writes DB)

func (*Handlers) Exchange

func (h *Handlers) Exchange(c *gin.Context)

Exchange exchanges an authorization code for tokens (legacy endpoint, delegates to handleAuthorizationCodeGrant) SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: handle the authorization_code token exchange endpoint with PKCE validation

func (*Handlers) GetJWKS

func (h *Handlers) GetJWKS(c *gin.Context)

GetJWKS returns the JSON Web Key Set for JWT signature verification SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: handle GET /.well-known/jwks.json and return the server's public signing key set

func (*Handlers) GetOAuthAuthorizationServerMetadata

func (h *Handlers) GetOAuthAuthorizationServerMetadata(c *gin.Context)

GetOAuthAuthorizationServerMetadata returns OAuth 2.0 Authorization Server metadata SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: handle GET /.well-known/oauth-authorization-server and return RFC 8414 metadata

func (*Handlers) GetOAuthProtectedResourceMetadata

func (h *Handlers) GetOAuthProtectedResourceMetadata(c *gin.Context)

GetOAuthProtectedResourceMetadata returns OAuth 2.0 protected resource metadata as per RFC 9728 SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: handle GET /.well-known/oauth-protected-resource and return RFC 9728 metadata

func (*Handlers) GetOpenIDConfiguration

func (h *Handlers) GetOpenIDConfiguration(c *gin.Context)

GetOpenIDConfiguration returns OpenID Connect Discovery metadata SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: handle GET /.well-known/openid-configuration and return OIDC discovery document

func (h *Handlers) GetPendingIdentityLink(c *gin.Context)

GetPendingIdentityLink handles GET /me/identities/link/pending/{link_id}. Returns the pending link details (both sides) if the token exists and belongs to the authenticated user. Returns 404 on any mismatch. SEM@053baa340d412aa135be32953dfcb6133af89b4d: fetch pending identity link details for the authenticated user by link token

func (*Handlers) GetProviders

func (h *Handlers) GetProviders(c *gin.Context)

GetProviders returns the available OAuth providers SEM@c1ae98795fcc480287e8ef03be0c86587e974cc5: list enabled OAuth providers with public endpoint URLs and resolved sign-in icons

func (*Handlers) GetSAMLMetadata

func (h *Handlers) GetSAMLMetadata(c *gin.Context, providerID string)

GetSAMLMetadata returns SAML service provider metadata SEM@3256ece0f5730b6c910aa6e61025555c7726a4a5: fetch and return SP metadata XML for a SAML provider

func (*Handlers) GetSAMLProviders

func (h *Handlers) GetSAMLProviders(c *gin.Context)

GetSAMLProviders returns the available SAML providers SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: list enabled SAML providers with initialization status and public URLs

func (*Handlers) HandleIdentityLinkCallback

func (h *Handlers) HandleIdentityLinkCallback(c *gin.Context, code string, stateData *callbackStateData) error

HandleIdentityLinkCallback is called from the shared Callback handler when stateData.IdentityLink is true. It performs a server-side code exchange to obtain the provider's user info (provider, sub, email, name) WITHOUT storing the IdP tokens. It stages a pending link record in Redis and redirects to the client_callback with link_pending={token}. SEM@fc8e2c83f6aaba09d10a2ed6f6e78a5075d278ba: exchange OAuth code for provider user info and stage a pending identity link in Redis

func (*Handlers) InitiateSAMLLogin

func (h *Handlers) InitiateSAMLLogin(c *gin.Context, providerID string, clientCallback *string)

InitiateSAMLLogin starts SAML authentication flow SEM@3256ece0f5730b6c910aa6e61025555c7726a4a5: build a SAML auth request, store relay state, and redirect to the IdP

func (*Handlers) IntrospectToken

func (h *Handlers) IntrospectToken(c *gin.Context)

IntrospectToken handles token introspection requests per RFC 7662 SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: handle RFC 7662 token introspection, returning active status and claims for a given token (reads DB)

func (*Handlers) Logout

func (h *Handlers) Logout(c *gin.Context)

Logout is deprecated - use RevokeToken for RFC 7009 compliance or MeLogout for self-logout Kept for backward compatibility, delegates to MeLogout SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: deprecated alias that delegates to MeLogout for backward compatibility

func (*Handlers) Me

func (h *Handlers) Me(c *gin.Context)

Me returns the current user SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: return the authenticated user's profile with groups and admin status (reads DB)

func (*Handlers) MeLogout

func (h *Handlers) MeLogout(c *gin.Context)

MeLogout revokes the caller's own JWT token This is a convenience endpoint that doesn't require passing the token in the body SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: revoke the caller's own JWT access token and clear session cookies (mutates shared state)

func (*Handlers) ProcessSAMLLogout

func (h *Handlers) ProcessSAMLLogout(c *gin.Context, providerID string, samlRequest string)

ProcessSAMLLogout handles SAML single logout SEM@3256ece0f5730b6c910aa6e61025555c7726a4a5: validate a SAML logout request and invalidate the user's sessions (writes DB)

func (*Handlers) ProcessSAMLResponse

func (h *Handlers) ProcessSAMLResponse(c *gin.Context, providerID string, samlResponse string, relayState string)

ProcessSAMLResponse handles SAML assertion consumer service SEM@bad36697a83ba8606ae7e598eb5fe21f3afebcaa: handle the SAML ACS callback: validate assertion, issue token pair, and redirect or return JSON

func (*Handlers) Refresh

func (h *Handlers) Refresh(c *gin.Context)

Refresh refreshes an access token SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: exchange a refresh token or cookie for a new JWT token pair (reads DB)

func (*Handlers) RevokeToken

func (h *Handlers) RevokeToken(c *gin.Context)

RevokeToken revokes a token per RFC 7009 OAuth 2.0 Token Revocation The token to revoke is passed in the request body, not the Authorization header. Authentication: Bearer token OR client credentials (client_id/client_secret) SEM@6cdf4b6d0226e518be3ef44423f6712f7c1d2717: handle RFC 7009 token revocation requests authenticated by Bearer token or client credentials

func (*Handlers) RuntimeConfigReader

func (h *Handlers) RuntimeConfigReader() RuntimeConfigReader

RuntimeConfigReader returns the wired reader, or nil if none was set.

Exposed so components built outside the auth package — the JWT authenticator in cmd/server, which needs auth.everyone_is_a_reviewer — can resolve operational settings through the same reader rather than reading a boot-time config snapshot that ignores the database (#794). SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: return the registered runtime config reader, or nil when unset (pure)

func (*Handlers) Service

func (h *Handlers) Service() *Service

Service returns the auth service (getter for unexported field) SEM@41fea1c48a3526015f75a5e401ec4970c6c9dfcf: return the auth service from the handler (pure)

func (*Handlers) SetAdminChecker

func (h *Handlers) SetAdminChecker(checker AdminChecker)

SetAdminChecker sets the admin checker for the handlers SEM@ccfd74278ac51e8904765cbf4218077a55750258: register the admin-role checker on the handler (mutates shared state)

func (*Handlers) SetCookieOptions

func (h *Handlers) SetCookieOptions(opts CookieOptions)

SetCookieOptions sets the cookie configuration for session cookie management SEM@314b7ae8fe586a75ecee2e8fa7103d3193f15f7c: configure session cookie options on the handler (mutates shared state)

func (*Handlers) SetIdentityLinkAuditor

func (h *Handlers) SetIdentityLinkAuditor(a *IdentityLinkAuditor)

SetIdentityLinkAuditor wires the identity-link audit writer. Safe to call multiple times; nil disables identity-link auditing. #383. SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: register the identity-link audit writer on the handler (mutates shared state)

func (*Handlers) SetIdentityLinkStore

func (h *Handlers) SetIdentityLinkStore(store LinkedIdentityStore)

SetIdentityLinkStore wires the linked identity store. Safe to call multiple times; nil disables server-side identity lookups during link operations. #383. SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: register the linked identity store on the handler (mutates shared state)

func (*Handlers) SetProviderRegistry

func (h *Handlers) SetProviderRegistry(registry ProviderRegistry)

SetProviderRegistry sets the provider registry for unified provider lookup. SEM@d526a06f3040d3424d4deb08071cd87ae770937f: register the OAuth provider registry on the handler (mutates shared state)

func (*Handlers) SetRuntimeConfigReader

func (h *Handlers) SetRuntimeConfigReader(r RuntimeConfigReader)

SetRuntimeConfigReader wires the DB-backed operational config reader used by handlers at request time. Safe to call multiple times. A nil reader makes handlers fall back to the YAML snapshot in h.config — see RuntimeConfigReader for the contract. (#419) SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: register the DB-backed runtime config reader on the handler (mutates shared state)

func (*Handlers) SetStepUpAuditor

func (h *Handlers) SetStepUpAuditor(a *StepUpAuditor)

SetStepUpAuditor wires the step-up audit writer. Safe to call multiple times; nil disables step-up auditing (used in tests AND in production when admin-audit middleware is disabled — see cmd/server/main.go). #397. SEM@dd66d35bda6952fa6d623976b1adb6177685fe6d: register the step-up audit writer on the handler (mutates shared state)

func (*Handlers) SetTokenLockout

func (h *Handlers) SetTokenLockout(l *OAuthTokenLockout)

SetTokenLockout overrides the per-client_id lockout. Used in tests. SEM@a3245d875ac2cfb50e40e8e8ffcceb6c913a13f0: override the OAuth token lockout implementation, used in tests (mutates shared state)

func (*Handlers) SetUserGroupsFetcher

func (h *Handlers) SetUserGroupsFetcher(fetcher UserGroupsFetcher)

SetUserGroupsFetcher sets the user groups fetcher for the handlers SEM@a0040890dd7b1940f542d4211d4338cd0e713cbc: register the user-groups fetcher on the handler (mutates shared state)

func (h *Handlers) StartIdentityLink(c *gin.Context)

StartIdentityLink handles POST /me/identities/link/start. It validates the request, builds OAuth state, stores it in Redis, and returns the authorization URL + state token for the client to use. SEM@053baa340d412aa135be32953dfcb6133af89b4d: initiate OAuth flow to link a second identity; return authorization URL and state

func (*Handlers) StepUp

func (h *Handlers) StepUp(c *gin.Context)

StepUp is the GET /oauth2/step_up handler. See docs/superpowers/specs/2026-05-10-oauth2-step-up-design.md.

This is the strong-provider path only. The weak-provider short-circuit (rotate-in-place) is implemented in Task 6. SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: handle step-up authentication request, routing to weak or strong re-auth path

func (*Handlers) Token

func (h *Handlers) Token(c *gin.Context)

Token exchanges an authorization code for tokens SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: dispatch OAuth2 token endpoint across authorization_code, refresh_token, and client_credentials grant types

type IDTokenClaims

type IDTokenClaims struct {
	Subject       string `json:"sub"`
	Email         string `json:"email,omitempty"`
	EmailVerified bool   `json:"email_verified,omitempty"`
	Name          string `json:"name,omitempty"`
	GivenName     string `json:"given_name,omitempty"`
	FamilyName    string `json:"family_name,omitempty"`
	Picture       string `json:"picture,omitempty"`
	Locale        string `json:"locale,omitempty"`
	Issuer        string `json:"iss"`
	Audience      string `json:"aud"`
	ExpiresAt     int64  `json:"exp"`
	IssuedAt      int64  `json:"iat"`
}

IDTokenClaims contains the claims from an ID token SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: standard OIDC ID token claims including subject, email, and expiry

type IdentityLinkActor

type IdentityLinkActor struct {
	Email          string
	Provider       string
	ProviderUserID string
	DisplayName    string
	UserUUID       string
}

IdentityLinkActor identifies the user performing a link operation. All four identity fields are denormalized into the audit row (matches SystemAuditEntry pattern; rows survive user deletion). SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: denormalized user identity fields for an identity-link audit record (pure)

type IdentityLinkAuditor

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

IdentityLinkAuditor wraps a SystemAuditWriter with the field shapes specific to identity-link events. Fail-open: write failures are logged but do not propagate. SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: fail-open auditor that records identity link and unlink events to the system audit log

func NewIdentityLinkAuditor

func NewIdentityLinkAuditor(writer SystemAuditWriter) *IdentityLinkAuditor

NewIdentityLinkAuditor returns an auditor. writer may be nil (in which case audit calls are no-ops with a debug log; matches the existing fail-open posture). SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: build an IdentityLinkAuditor wrapping the given system audit writer (pure)

func (*IdentityLinkAuditor) LogComplete

func (a *IdentityLinkAuditor) LogComplete(
	ctx context.Context,
	actor IdentityLinkActor,
	accountProvider, accountSub, linkedProvider, linkedSub string,
) error

LogComplete records a successful identity-link completion. Both sides' (provider, sub) are redacted in the audit payload. SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: record a successful identity link audit event with redacted provider subject IDs

func (*IdentityLinkAuditor) LogFailed

func (a *IdentityLinkAuditor) LogFailed(
	ctx context.Context,
	actor IdentityLinkActor,
	reason string,
	extras map[string]string,
) error

LogFailed records an identity-link attempt that failed (e.g. upstream error, code-exchange failure). reason is a short stable code. extras are inlined into the payload. SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: record a failed identity-link attempt audit event with reason code

func (*IdentityLinkAuditor) LogRejected

func (a *IdentityLinkAuditor) LogRejected(
	ctx context.Context,
	actor IdentityLinkActor,
	reason string,
	extras map[string]string,
) error

LogRejected records an identity-link attempt that was rejected before any upstream round-trip (e.g. service-account caller, already-bound identity). SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: record a pre-flight-rejected identity-link attempt audit event with reason code

func (a *IdentityLinkAuditor) LogUnlink(
	ctx context.Context,
	actor IdentityLinkActor,
	linkedProvider, linkedSub string,
) error

LogUnlink records the removal of a linked identity. Both sides' (provider, sub) are redacted in the audit payload. SEM@d89a562535e2240eeb7f556a3f619d28fe9c5613: record an identity unlink audit event with redacted provider subject IDs

type InMemoryStateStore

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

InMemoryStateStore implements StateStore using in-memory storage SEM@2fbab585a899780eb5d718ec784b7c730c732113: in-memory StateStore implementation with mutex protection and periodic expiry cleanup (mutates shared state)

func NewInMemoryStateStore

func NewInMemoryStateStore() *InMemoryStateStore

NewInMemoryStateStore creates a new in-memory state store SEM@2fbab585a899780eb5d718ec784b7c730c732113: build and start an in-memory state store with background expiry cleanup

func (*InMemoryStateStore) Close

func (s *InMemoryStateStore) Close()

Close stops the cleanup goroutine SEM@2fbab585a899780eb5d718ec784b7c730c732113: stop the background expiry cleanup goroutine (mutates shared state)

func (*InMemoryStateStore) DeletePKCEChallenge

func (s *InMemoryStateStore) DeletePKCEChallenge(ctx context.Context, state string) error

DeletePKCEChallenge removes PKCE challenge from store SEM@7f2e891b97d9b875349295375fb64355109504b5: clear the PKCE challenge from a state entry without removing the entry itself (mutates shared state)

func (*InMemoryStateStore) DeleteState

func (s *InMemoryStateStore) DeleteState(ctx context.Context, state string) error

DeleteState removes a state from the store SEM@2fbab585a899780eb5d718ec784b7c730c732113: delete an OAuth state token and all associated data (mutates shared state)

func (*InMemoryStateStore) GetCallbackURL

func (s *InMemoryStateStore) GetCallbackURL(ctx context.Context, state string) (string, error)

GetCallbackURL retrieves the callback URL for a state SEM@2fbab585a899780eb5d718ec784b7c730c732113: fetch the callback URL associated with a valid OAuth state token (pure)

func (*InMemoryStateStore) GetPKCEChallenge

func (s *InMemoryStateStore) GetPKCEChallenge(ctx context.Context, state string) (challenge, method string, err error)

GetPKCEChallenge retrieves PKCE code challenge and method for a state SEM@7f2e891b97d9b875349295375fb64355109504b5: fetch the PKCE code challenge and method for a valid OAuth state token; reject if expired (pure)

func (*InMemoryStateStore) StoreCallbackURL

func (s *InMemoryStateStore) StoreCallbackURL(ctx context.Context, state, callbackURL string, ttl time.Duration) error

StoreCallbackURL stores a callback URL with a state SEM@2fbab585a899780eb5d718ec784b7c730c732113: store a callback URL against an OAuth state token with TTL (mutates shared state)

func (*InMemoryStateStore) StorePKCEChallenge

func (s *InMemoryStateStore) StorePKCEChallenge(ctx context.Context, state, codeChallenge, challengeMethod string, ttl time.Duration) error

StorePKCEChallenge stores PKCE code challenge with associated method SEM@7f2e891b97d9b875349295375fb64355109504b5: store a PKCE code challenge and method against an OAuth state token (mutates shared state)

func (*InMemoryStateStore) StoreState

func (s *InMemoryStateStore) StoreState(ctx context.Context, state, data string, ttl time.Duration) error

StoreState stores state with associated data SEM@2fbab585a899780eb5d718ec784b7c730c732113: store an OAuth state token with associated data and TTL (mutates shared state)

func (*InMemoryStateStore) ValidateState

func (s *InMemoryStateStore) ValidateState(ctx context.Context, state string) (string, error)

ValidateState validates state and returns associated data SEM@2fbab585a899780eb5d718ec784b7c730c732113: validate an OAuth state token and return its associated data; reject if expired (pure)

type JWK

type JWK struct {
	KeyType   string   `json:"kty"`
	Use       string   `json:"use,omitempty"`
	KeyOps    []string `json:"key_ops,omitempty"`
	KeyID     string   `json:"kid,omitempty"`
	Algorithm string   `json:"alg,omitempty"`
	// RSA parameters
	N string `json:"n,omitempty"` // RSA modulus
	E string `json:"e,omitempty"` // RSA exponent
	// ECDSA parameters
	Curve string `json:"crv,omitempty"` // Elliptic curve
	X     string `json:"x,omitempty"`   // X coordinate
	Y     string `json:"y,omitempty"`   // Y coordinate
}

JWK represents a JSON Web Key SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: DTO representing a single JSON Web Key with RSA and ECDSA fields (pure)

type JWKSResponse

type JWKSResponse struct {
	Keys []JWK `json:"keys"`
}

JWKSResponse represents a JSON Web Key Set response SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: DTO wrapping a JSON Web Key Set response (pure)

type JWTConfig

type JWTConfig struct {
	Secret              string
	ExpirationSeconds   int
	SigningMethod       string // HS256, RS256, ES256
	KeyID               string // Key ID for JWKS (defaults to "1")
	RefreshTokenDays    int    // Refresh token TTL in days (default: 7)
	SessionLifetimeDays int    // Absolute session lifetime in days (default: 7)
	// RSA Keys (for RS256)
	RSAPrivateKeyPath string // Path to RSA private key file
	RSAPublicKeyPath  string // Path to RSA public key file
	RSAPrivateKey     string // RSA private key as string (alternative to file path)
	RSAPublicKey      string // RSA public key as string (alternative to file path)
	// ECDSA Keys (for ES256)
	ECDSAPrivateKeyPath string // Path to ECDSA private key file
	ECDSAPublicKeyPath  string // Path to ECDSA public key file
	ECDSAPrivateKey     string // ECDSA private key as string (alternative to file path)
	ECDSAPublicKey      string // ECDSA public key as string (alternative to file path)
}

JWTConfig holds JWT configuration SEM@36538e427d89135597d0d3615fcf217f9f4088e4: JWT signing and expiration settings supporting HS256, RS256, and ES256 algorithms (pure)

type JWTKeyManager

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

JWTKeyManager manages JWT signing and verification keys SEM@3d0d5a8cf02fa74fad102f0f99c2b936a164bbea: holder for JWT signing and verification keys and the configured signing method (pure)

func NewJWTKeyManager

func NewJWTKeyManager(config JWTConfig) (*JWTKeyManager, error)

NewJWTKeyManager creates a new JWT key manager SEM@70ff47b7829f38ef04399520210ae8765d39495d: build and initialize a JWT key manager by loading keys for the configured signing method

func SetupTestKeyManager

func SetupTestKeyManager(t *testing.T) *JWTKeyManager

SetupTestKeyManager creates a key manager for testing SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build a JWT key manager with a fixed HMAC secret for unit tests (pure)

func (*JWTKeyManager) CreateToken

func (m *JWTKeyManager) CreateToken(claims jwt.Claims) (string, error)

CreateToken creates a new JWT token with the configured signing method SEM@70ff47b7829f38ef04399520210ae8765d39495d: sign a JWT with the configured signing key and return the token string (pure)

func (*JWTKeyManager) GetPublicKey

func (m *JWTKeyManager) GetPublicKey() any

GetPublicKey returns the public key for JWKS endpoint (for asymmetric methods) SEM@3d0d5a8cf02fa74fad102f0f99c2b936a164bbea: return the public key for asymmetric algorithms, for use by the JWKS endpoint (pure)

func (*JWTKeyManager) GetSigningMethod

func (m *JWTKeyManager) GetSigningMethod() string

GetSigningMethod returns the current signing method SEM@41fea1c48a3526015f75a5e401ec4970c6c9dfcf: return the configured JWT signing algorithm name (pure)

func (*JWTKeyManager) VerifyToken

func (m *JWTKeyManager) VerifyToken(tokenString string, claims jwt.Claims) (*jwt.Token, error)

VerifyToken verifies a JWT token using the configured verification key SEM@3d0d5a8cf02fa74fad102f0f99c2b936a164bbea: validate a JWT signature and claims, returning the parsed token; reject if invalid (pure)

type LinkedIdentityInput

type LinkedIdentityInput struct {
	UserInternalUUID string
	Provider         string
	ProviderUserID   string
	Email            string
	Name             string
}

LinkedIdentityInput holds the fields needed to create a new linked identity. SEM@211793c39ea528b3d2da244f3504963c40584df7: input fields required to create a new linked identity record (pure)

type LinkedIdentityStore

type LinkedIdentityStore interface {
	// Create inserts a new linked identity row. Returns a dberrors.ErrDuplicate-
	// wrapped error if the (provider, provider_user_id) pair already exists.
	Create(ctx context.Context, input LinkedIdentityInput) (models.LinkedIdentity, error)

	// CreateExclusive checks for an existing binding (in linked_identities) and
	// creates the row inside a single serializable transaction, eliminating the
	// check-then-act race between concurrent confirm calls. The caller is
	// responsible for checking the users (primary identity) table before calling
	// this method — that cross-table check is performed by the handler using the
	// same serializable transaction indirectly via the retry wrapper.
	// Returns dberrors.ErrDuplicate if the (provider, provider_user_id) pair is
	// already present in linked_identities.
	CreateExclusive(ctx context.Context, input LinkedIdentityInput) (models.LinkedIdentity, error)

	// GetByProviderSub looks up a linked identity by provider and provider-user-id.
	// Returns ErrLinkedIdentityNotFound if no row matches.
	GetByProviderSub(ctx context.Context, provider, providerUserID string) (models.LinkedIdentity, error)

	// ListByUser returns all linked identities owned by userInternalUUID.
	// Returns an empty slice (not an error) when none exist.
	ListByUser(ctx context.Context, userInternalUUID string) ([]models.LinkedIdentity, error)

	// TouchLastUsed updates last_used_at to now for the given identity id.
	TouchLastUsed(ctx context.Context, id string) error

	// Delete removes the linked identity identified by id, scoped to ownerUUID.
	// Returns ErrLinkedIdentityNotFound if no row matches both id and ownerUUID.
	Delete(ctx context.Context, id, ownerUUID string) error
}

LinkedIdentityStore is the persistence interface for the linked_identities table. SEM@053baa340d412aa135be32953dfcb6133af89b4d: persistence interface for linked OAuth identity records (reads DB)

type LockoutDecision

type LockoutDecision struct {
	Locked     bool          // true when the caller should be rejected with 429
	RetryAfter time.Duration // Retry-After hint to surface in HTTP headers
	Count      int64         // current failure count (0 if no lock)
}

LockoutDecision is the result of a Check call. SEM@a3245d875ac2cfb50e40e8e8ffcceb6c913a13f0: result of a lockout check: locked status, Retry-After duration, and failure count (pure)

type OAuthAuthorizationServerMetadata

type OAuthAuthorizationServerMetadata struct {
	Issuer                            string   `json:"issuer"`
	AuthorizationEndpoint             string   `json:"authorization_endpoint"`
	TokenEndpoint                     string   `json:"token_endpoint"`
	JWKSURI                           string   `json:"jwks_uri,omitempty"`
	ScopesSupported                   []string `json:"scopes_supported,omitempty"`
	ResponseTypesSupported            []string `json:"response_types_supported"`
	ResponseModesSupported            []string `json:"response_modes_supported,omitempty"`
	GrantTypesSupported               []string `json:"grant_types_supported,omitempty"`
	TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
	CodeChallengeMethodsSupported     []string `json:"code_challenge_methods_supported,omitempty"`
	IntrospectionEndpoint             string   `json:"introspection_endpoint,omitempty"`
	RevocationEndpoint                string   `json:"revocation_endpoint,omitempty"`
}

OAuthAuthorizationServerMetadata represents OAuth 2.0 Authorization Server Metadata SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: DTO carrying RFC 8414 authorization server metadata fields (pure)

type OAuthConfig

type OAuthConfig struct {
	CallbackURL string
	Providers   map[string]OAuthProviderConfig
	// ClientCallbackAllowList is the list of allowed client_callback URLs for
	// /oauth2/authorize. Each entry is an exact URL or a wildcard pattern
	// ending in "*". An empty list rejects any client_callback (fail-closed).
	ClientCallbackAllowList []string
}

OAuthConfig holds OAuth configuration SEM@d5abf2700f59ec278f7e45a485c9d19c90b0050f: OAuth callback URL, provider map, and client-callback allowlist settings (pure)

type OAuthProtectedResourceMetadata

type OAuthProtectedResourceMetadata struct {
	Resource                              string   `json:"resource"`
	ScopesSupported                       []string `json:"scopes_supported,omitempty"`
	AuthorizationServers                  []string `json:"authorization_servers,omitempty"`
	JWKSURI                               string   `json:"jwks_uri,omitempty"`
	BearerMethodsSupported                []string `json:"bearer_methods_supported,omitempty"`
	ResourceName                          string   `json:"resource_name,omitempty"`
	ResourceDocumentation                 string   `json:"resource_documentation,omitempty"`
	TLSClientCertificateBoundAccessTokens bool     `json:"tls_client_certificate_bound_access_tokens"`
}

OAuthProtectedResourceMetadata represents OAuth 2.0 protected resource metadata as defined in RFC 9728 SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: DTO carrying RFC 9728 protected resource metadata fields (pure)

type OAuthProviderConfig

type OAuthProviderConfig struct {
	ID               string             `json:"id"`
	Name             string             `json:"name"`
	Enabled          bool               `json:"enabled"`
	Icon             string             `json:"icon"`
	ClientID         string             `json:"client_id"`
	ClientSecret     string             `json:"client_secret"`
	AuthorizationURL string             `json:"authorization_url"`
	TokenURL         string             `json:"token_url"`
	UserInfo         []UserInfoEndpoint `json:"userinfo"`
	Issuer           string             `json:"issuer"`
	JWKSURL          string             `json:"jwks_url"`
	Scopes           []string           `json:"scopes"`
	AdditionalParams map[string]string  `json:"additional_params"`
	AuthHeaderFormat string             `json:"auth_header_format,omitempty"` // Default: "Bearer %s"
	AcceptHeader     string             `json:"accept_header,omitempty"`      // Default: "application/json"
}

OAuthProviderConfig holds configuration for an OAuth provider SEM@93e972b21be4fbdf788d2884f25d14b33d41b98e: full configuration for a single OAuth provider including credentials, endpoints, and scopes (pure)

type OAuthTokenLockout

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

OAuthTokenLockout is a Redis-backed exponential-backoff lockout for the /oauth2/token endpoint. It counts failed grant attempts per key (typically the client_id) and emits a Retry-After hint that grows with the failure count. Closes T15 (brute-force of client_credentials) — a per-IP rate limiter does not catch an attacker rotating IPs against a single client.

The counter is stored as a plain integer at key `oauth_token_failures:{key}` with a 1h TTL. A successful grant deletes the key; a quiet period also expires it. SEM@a3245d875ac2cfb50e40e8e8ffcceb6c913a13f0: Redis-backed exponential-backoff lockout tracker for OAuth token grant failures (mutates shared state)

func NewOAuthTokenLockout

func NewOAuthTokenLockout(client *redis.Client) *OAuthTokenLockout

NewOAuthTokenLockout constructs a lockout backed by the given Redis client. A nil client returns a no-op lockout. SEM@a3245d875ac2cfb50e40e8e8ffcceb6c913a13f0: build a Redis-backed OAuth token lockout tracker (pure)

func (*OAuthTokenLockout) Check

Check returns the current lockout state for the given subject. Returns {Locked: false} if Redis is unavailable — failing open is safer than rejecting valid clients during a Redis outage. SEM@a3245d875ac2cfb50e40e8e8ffcceb6c913a13f0: fetch the current lockout state for a subject; fails open when Redis is unavailable (reads DB)

func (*OAuthTokenLockout) RecordFailure

func (l *OAuthTokenLockout) RecordFailure(ctx context.Context, key string) (LockoutDecision, error)

RecordFailure increments the counter and (re)applies the TTL. Returns the post-increment count and the new lockout decision so the caller can surface the updated Retry-After to the client. SEM@a3245d875ac2cfb50e40e8e8ffcceb6c913a13f0: increment the failure counter and refresh its TTL; return the updated lockout decision (mutates shared state)

func (*OAuthTokenLockout) Reset

func (l *OAuthTokenLockout) Reset(ctx context.Context, key string)

Reset clears the failure counter for the given subject. Called on a successful grant. SEM@a3245d875ac2cfb50e40e8e8ffcceb6c913a13f0: delete the failure counter for a subject on successful grant (mutates shared state)

type OIDCDiscoveryDoc

type OIDCDiscoveryDoc struct {
	Issuer                 string   `json:"issuer"`
	AuthorizationEndpoint  string   `json:"authorization_endpoint"`
	TokenEndpoint          string   `json:"token_endpoint"`
	UserinfoEndpoint       string   `json:"userinfo_endpoint"`
	JWKSURI                string   `json:"jwks_uri"`
	SubjectTypesSupported  []string `json:"subject_types_supported"`
	ResponseTypesSupported []string `json:"response_types_supported"`
}

OIDCDiscoveryDoc represents the subset of an OpenID Connect Discovery 1.0 metadata document we need to classify an OAuth provider. Field names match the spec; only the fields we consume are declared. SEM@5f9f526cf6b26f69441543993290f8ffaedac64a: subset of an OIDC Discovery 1.0 metadata document used to classify an OAuth provider (pure)

func (*OIDCDiscoveryDoc) IsValid

func (d *OIDCDiscoveryDoc) IsValid() bool

IsValid reports whether doc has the minimum fields required by the OIDC Discovery 1.0 spec. userinfo_endpoint is RECOMMENDED rather than REQUIRED; callers that need it should check separately. SEM@5f9f526cf6b26f69441543993290f8ffaedac64a: validate that a discovery document has the minimum required OIDC fields (pure)

type OpenIDConfiguration

type OpenIDConfiguration struct {
	Issuer                            string   `json:"issuer"`
	AuthorizationEndpoint             string   `json:"authorization_endpoint"`
	TokenEndpoint                     string   `json:"token_endpoint"`
	UserInfoEndpoint                  string   `json:"userinfo_endpoint"`
	JWKSURI                           string   `json:"jwks_uri"`
	ScopesSupported                   []string `json:"scopes_supported"`
	ResponseTypesSupported            []string `json:"response_types_supported"`
	ResponseModesSupported            []string `json:"response_modes_supported,omitempty"`
	GrantTypesSupported               []string `json:"grant_types_supported,omitempty"`
	SubjectTypesSupported             []string `json:"subject_types_supported"`
	IDTokenSigningAlgValuesSupported  []string `json:"id_token_signing_alg_values_supported"`
	TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
	ClaimsSupported                   []string `json:"claims_supported,omitempty"`
	CodeChallengeMethodsSupported     []string `json:"code_challenge_methods_supported,omitempty"`
	IntrospectionEndpoint             string   `json:"introspection_endpoint,omitempty"`
	RevocationEndpoint                string   `json:"revocation_endpoint,omitempty"`
}

OpenIDConfiguration represents the OpenID Connect Discovery metadata SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: DTO carrying OIDC discovery metadata fields (pure)

type Provider

type Provider interface {
	// GetOAuth2Config returns the OAuth2 configuration
	GetOAuth2Config() *oauth2.Config

	// GetAuthorizationURL returns the authorization URL with the given state
	GetAuthorizationURL(state string) string

	// ExchangeCode exchanges an authorization code for tokens
	ExchangeCode(ctx context.Context, code string) (*TokenResponse, error)

	// GetUserInfo gets user information from the provider
	GetUserInfo(ctx context.Context, accessToken string) (*UserInfo, error)

	// ValidateIDToken validates an ID token
	ValidateIDToken(ctx context.Context, idToken string) (*IDTokenClaims, error)
}

Provider is the interface for OAuth providers SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: interface for OAuth identity providers covering authorization, token exchange, and user info

func NewProvider

func NewProvider(config OAuthProviderConfig, callbackURL string) (Provider, error)

NewProvider creates a new OAuth provider based on the provider ID SEM@5bacd53eee87984ab4d2aab453afb59913aa79dc: build an OAuth Provider instance for the given provider config, selecting TMI, OIDC, or base OAuth2 implementation

type ProviderClassification

type ProviderClassification int

SEM@8745e8dc70d45d797efdea06bea563dc07b57029: enumeration of OIDC compliance levels for an OAuth provider

const (
	// ClassificationNonOIDC is the zero value (fail-closed default): discovery
	// failed or no issuer configured. No guarantee about userinfo response shape.
	// Explicit subject_claim is required.
	ClassificationNonOIDC ProviderClassification = iota

	// ClassificationOIDCCustomUserinfo: discovery succeeds but the configured
	// userinfo URL differs from the discovery doc's userinfo_endpoint. The
	// operator is calling a non-OIDC userinfo endpoint (e.g. Microsoft Graph
	// /me instead of Microsoft's OIDC userinfo). Explicit subject_claim is
	// required.
	ClassificationOIDCCustomUserinfo

	// ClassificationOIDCCompliant: discovery succeeds AND configured userinfo
	// URL matches the discovery doc's userinfo_endpoint. Default `sub` mapping
	// is safe.
	ClassificationOIDCCompliant
)

func (ProviderClassification) String

func (c ProviderClassification) String() string

SEM@8745e8dc70d45d797efdea06bea563dc07b57029: convert a ProviderClassification to its human-readable name (pure)

type ProviderInfo

type ProviderInfo struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Icon        string `json:"icon"`
	AuthURL     string `json:"auth_url"`
	TokenURL    string `json:"token_url"`
	RedirectURI string `json:"redirect_uri"`
	ClientID    string `json:"client_id"`
}

ProviderInfo contains information about an OAuth provider SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: public OAuth provider metadata returned to clients

type ProviderRegistry

type ProviderRegistry interface {
	GetOAuthProvider(id string) (OAuthProviderConfig, bool)
	GetEnabledOAuthProviders() map[string]OAuthProviderConfig
	GetSAMLProvider(id string) (SAMLProviderConfig, bool)
	GetEnabledSAMLProviders() map[string]SAMLProviderConfig
	InvalidateCache()
}

ProviderRegistry provides unified access to OAuth and SAML provider configurations from all sources (config, environment, database). SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: interface for fetching and listing OAuth and SAML provider configurations (pure)

type ProviderSetting

type ProviderSetting struct {
	Key   string
	Value string
}

ProviderSetting is a minimal representation of a setting key/value pair. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: key-value pair representing a single provider configuration setting (pure)

type ProviderSettingsReader

type ProviderSettingsReader interface {
	ListByPrefix(ctx context.Context, prefix string) ([]ProviderSetting, error)
}

ProviderSettingsReader is a minimal interface defined in the auth package to avoid a circular dependency on the api package. The api.SettingsService satisfies this interface via the ProviderSettingsReaderAdapter. SEM@fbd0dc5c450a6a5b20010ce4f5ee16e3e49ca16e: interface for fetching provider settings key-value pairs by key prefix (reads DB)

type RedisConfig

type RedisConfig struct {
	Host     string
	Port     string
	Password string
	DB       int
}

RedisConfig holds Redis configuration SEM@d885c7955d5a30affb8ddde84ee1cf757aab2a6b: Redis connection settings including host, port, password, and DB index (pure)

type RuntimeConfigReader

type RuntimeConfigReader interface {
	// GetClientCallbackAllowList returns the configured allowlist for the
	// /oauth2/authorize and /oauth2/step_up client_callback parameter.
	//
	// Three outcomes:
	//   - exists == false: no DB row. Caller falls back to the YAML
	//     snapshot, which preserves first-run dev workflows.
	//   - exists == true,  err == nil: returns the parsed allowlist.
	//   - exists == true,  err != nil: DB row is present but unusable
	//     (corrupt JSON, decryption failure, etc.). Caller MUST treat
	//     this as fail-closed (reject every client_callback) — silently
	//     falling back to YAML would defeat the open-redirect mitigation.
	GetClientCallbackAllowList(ctx context.Context) (list []string, exists bool, err error)

	// IsSAMLEnabled reports whether SAML auth is enabled.
	IsSAMLEnabled(ctx context.Context) bool

	// GetOAuthCallbackURL returns the configured OAuth callback URL used
	// when redirecting back from an external provider.
	GetOAuthCallbackURL(ctx context.Context) string

	// IsEveryoneAReviewer reports whether every authenticated user should be
	// auto-added to the Security Reviewers group.
	//
	// Before #794 this setting had no runtime reader at all: the only
	// consumer read config.Auth.EveryoneIsAReviewer directly, so the
	// database row existed, was displayed by the admin API, and did
	// absolutely nothing. Editing it at runtime appeared to work and
	// silently had no effect.
	//
	// Returns false on any read error (fail-closed): failing to grant a
	// group membership is recoverable, granting one wrongly is not.
	IsEveryoneAReviewer(ctx context.Context) bool
}

RuntimeConfigReader supplies operational config values that the auth handlers need at request time. The values are DB-backed via the SettingsService introduced in #415; this interface lets the auth package read them without importing internal/config or api (both of which would create import cycles).

Implementations should be cheap to call per-request (Get() backed by a short TTL cache in SettingsService is fine). A nil reader is interpreted by handlers as "no DB available" and they fall back to the YAML snapshot in h.config — that path exists for unit tests and for the brief window during startup before SetRuntimeConfigReader is wired.

See #419 for the motivation. Once every cfg.* operational read in the auth package has been moved here, the YAML snapshot fallback can be removed and h.config.OAuth.* / h.config.SAML.* trimmed to bootstrap-only fields. SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: interface for reading live auth configuration from the DB at request time (reads DB)

type SAMLConfig

type SAMLConfig struct {
	Enabled   bool                          `json:"enabled"`
	Providers map[string]SAMLProviderConfig `json:"providers"`
}

SAMLConfig holds SAML configuration SEM@2fbab585a899780eb5d718ec784b7c730c732113: SAML enabled flag and map of SAML provider configurations (pure)

type SAMLManager

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

SAMLManager manages SAML providers SEM@2fbab585a899780eb5d718ec784b7c730c732113: manage a registry of initialized SAML providers with thread-safe access (mutates shared state)

func NewSAMLManager

func NewSAMLManager(service *Service) *SAMLManager

NewSAMLManager creates a new SAML manager SEM@2fbab585a899780eb5d718ec784b7c730c732113: build an empty SAMLManager bound to the given auth service (pure)

func (*SAMLManager) EnsureProvider

func (m *SAMLManager) EnsureProvider(id string, config SAMLProviderConfig) error

EnsureProvider lazily initializes a SAML provider if not already initialized. Idempotent: if the provider is already initialized, returns immediately. Thread-safe: uses the manager's mutex to prevent concurrent initialization. SEM@78155d54490599e00095eb72b817575bb1e8da5b: lazily initialize and register a SAML provider if not already present, thread-safe (mutates shared state)

func (*SAMLManager) GetProvider

func (m *SAMLManager) GetProvider(id string) (*saml.SAMLProvider, error)

GetProvider returns a SAML provider by ID SEM@2fbab585a899780eb5d718ec784b7c730c732113: fetch an initialized SAML provider by ID, returning an error if not found (pure)

func (*SAMLManager) InitializeProviders

func (m *SAMLManager) InitializeProviders(config SAMLConfig, stateStore StateStore) error

InitializeProviders initializes all configured SAML providers SEM@78155d54490599e00095eb72b817575bb1e8da5b: register all enabled SAML providers from config, skipping failures without aborting (mutates shared state)

func (*SAMLManager) IsProviderInitialized

func (m *SAMLManager) IsProviderInitialized(id string) bool

IsProviderInitialized checks if a SAML provider was successfully initialized SEM@8af03cfea628820f921f3922831bbb27c7aa2b02: report whether a SAML provider with the given ID has been registered (pure)

func (*SAMLManager) ListProviders

func (m *SAMLManager) ListProviders() []string

ListProviders returns a list of configured SAML provider IDs SEM@2fbab585a899780eb5d718ec784b7c730c732113: list the IDs of all registered SAML providers (pure)

func (*SAMLManager) ProcessSAMLResponse

func (m *SAMLManager) ProcessSAMLResponse(ctx context.Context, providerID string, samlResponse string, relayState string) (*User, *TokenPair, error)

ProcessSAMLResponse processes a SAML response for any provider SEM@2fbab585a899780eb5d718ec784b7c730c732113: validate a SAML assertion, resolve or create the user, and issue a JWT token pair (reads DB)

type SAMLProviderConfig

type SAMLProviderConfig struct {
	ID                  string `json:"id"`
	Name                string `json:"name"`
	Enabled             bool   `json:"enabled"`
	Icon                string `json:"icon"`
	EntityID            string `json:"entity_id"`
	MetadataURL         string `json:"metadata_url"`
	MetadataXML         string `json:"metadata_xml"`
	ACSURL              string `json:"acs_url"`
	SLOURL              string `json:"slo_url"`
	SPPrivateKey        string `json:"sp_private_key"`
	SPPrivateKeyPath    string `json:"sp_private_key_path"`
	SPCertificate       string `json:"sp_certificate"`
	SPCertificatePath   string `json:"sp_certificate_path"`
	IDPMetadataURL      string `json:"idp_metadata_url"`
	IDPMetadataB64XML   string `json:"idp_metadata_b64xml"`
	AllowIDPInitiated   bool   `json:"allow_idp_initiated"`
	ForceAuthn          bool   `json:"force_authn"`
	SignRequests        bool   `json:"sign_requests"`
	NameIDAttribute     string `json:"name_id_attribute"`
	EmailAttribute      string `json:"email_attribute"`
	NameAttribute       string `json:"name_attribute"`
	GivenNameAttribute  string `json:"given_name_attribute"`
	FamilyNameAttribute string `json:"family_name_attribute"`
	GroupsAttribute     string `json:"groups_attribute"`
}

SAMLProviderConfig holds configuration for a SAML provider SEM@78155d54490599e00095eb72b817575bb1e8da5b: full configuration for a single SAML provider including SP/IDP keys, endpoints, and attribute mappings (pure)

type SAMLProviderInfo

type SAMLProviderInfo struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Icon        string `json:"icon"`
	AuthURL     string `json:"auth_url"`
	MetadataURL string `json:"metadata_url"`
	EntityID    string `json:"entity_id"`
	ACSURL      string `json:"acs_url"`
	SLOURL      string `json:"slo_url,omitempty"`
	Initialized bool   `json:"initialized"`
}

SAMLProviderInfo contains public information about a SAML provider SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: public SAML provider metadata returned to clients

type Service

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

Service provides authentication and authorization functionality SEM@1eb7997add7b39214eac29d20050d7968745a98d: core auth service struct holding JWT, SAML, user repo, and caching dependencies

func NewService

func NewService(dbManager *db.Manager, config Config) (*Service, error)

NewService creates a new authentication service SEM@8af03cfea628820f921f3922831bbb27c7aa2b02: build and initialize the auth service with JWT key manager, SAML, and user repos (reads DB)

func (*Service) BlacklistToken

func (s *Service) BlacklistToken(ctx context.Context, tokenString string) error

BlacklistToken adds a JWT token to the blacklist so it can no longer be used. This should be called when a user is deleted or logs out to invalidate their tokens. SEM@0538436fe19e71299239f10214d737a09cf94961: add a JWT to the Redis token blacklist to invalidate it immediately (reads DB)

func (*Service) CacheUser

func (s *Service) CacheUser(ctx context.Context, user User) error

CacheUser stores a user in Redis cache with multiple lookup keys SEM@89d554e793900a75b5703e1d10c9d58f57ceadc6: store a user in Redis under ID, email, and provider lookup keys (reads DB)

func (*Service) CacheUserGroups

func (s *Service) CacheUserGroups(ctx context.Context, email, idp string, groups []string) error

CacheUserGroups caches user groups in Redis for the session duration SEM@3d0d5a8cf02fa74fad102f0f99c2b936a164bbea: store user group membership in Redis for the JWT session duration (reads DB)

func (*Service) ClearUserGroups

func (s *Service) ClearUserGroups(ctx context.Context, email string) error

ClearUserGroups clears cached user groups from Redis (used on logout) SEM@85ed60a219cd0aba38e90907408068f8235d4cc1: delete cached group membership for a user from Redis on logout (reads DB)

func (*Service) CreateClientCredential

func (s *Service) CreateClientCredential(ctx context.Context, params ClientCredentialCreateParams) (*ClientCredential, error)

CreateClientCredential creates a new client credential in the database SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: store a new client credential and return the persisted entity (mutates DB)

func (*Service) CreateUser

func (s *Service) CreateUser(ctx context.Context, user User) (User, error)

CreateUser creates a new user SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: store a new user and populate the Redis cache entry (reads DB)

func (*Service) DeactivateClientCredential

func (s *Service) DeactivateClientCredential(ctx context.Context, id uuid.UUID, ownerUUID uuid.UUID) error

DeactivateClientCredential deactivates a client credential (soft delete) SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: soft-delete a client credential owned by a given user (mutates shared state)

func (*Service) DeleteClientCredential

func (s *Service) DeleteClientCredential(ctx context.Context, id uuid.UUID, ownerUUID uuid.UUID) error

DeleteClientCredential permanently deletes a client credential SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: permanently delete a client credential owned by a given user (mutates shared state)

func (*Service) DeleteGroupAndData

func (s *Service) DeleteGroupAndData(ctx context.Context, internalUUID string) (*GroupDeletionResult, error)

DeleteGroupAndData deletes a TMI-managed group by internal UUID and handles threat model cleanup Uses internal_uuid for precise identification to avoid issues with duplicate group_names SEM@96488469dcfa20f1b615dc581cdcefa18cb974ae: delete a TMI-managed group by internal UUID and cascade threat model cleanup (reads DB)

func (*Service) DeleteUser

func (s *Service) DeleteUser(ctx context.Context, id string) error

DeleteUser deletes a user by internal UUID SEM@cf201bc8c1eab7bf74de941e50508142b759ca75: delete a user by internal UUID and invalidate all Redis cache keys (reads DB)

func (*Service) DeleteUserAndData

func (s *Service) DeleteUserAndData(ctx context.Context, userEmail string) (*DeletionResult, error)

DeleteUserAndData deletes a user by email and handles ownership transfer for threat models. Used by the self-deletion flow (DELETE /me) where identity comes from JWT email. SEM@cd187b523b66aef0fa87861d3a929c2017787b86: delete a user by email and transfer or remove owned threat models (mutates shared state)

func (*Service) DeleteUserByInternalUUID

func (s *Service) DeleteUserByInternalUUID(ctx context.Context, internalUUID string) (*DeletionResult, error)

DeleteUserByInternalUUID deletes a user by internal UUID and handles ownership transfer. Used by admin deletion to avoid multi-hop identity resolution that can target the wrong user. SEM@cd187b523b66aef0fa87861d3a929c2017787b86: delete a user by internal UUID and transfer or remove owned threat models (mutates shared state)

func (*Service) GenerateDeletionChallenge

func (s *Service) GenerateDeletionChallenge(ctx context.Context, userEmail string) (*DeletionChallenge, error)

GenerateDeletionChallenge creates a challenge token for user deletion SEM@a37a0039279be689bb07be2113fe86024a410a4b: generate and store a one-time deletion confirmation challenge for the user (mutates shared state)

func (*Service) GenerateTokens

func (s *Service) GenerateTokens(ctx context.Context, user User) (TokenPair, error)

GenerateTokens generates a new JWT token pair for a user with auth_time = now. Use this for fresh interactive logins; use GenerateTokensWithAuthTime to preserve auth_time across refresh-token rotation. SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: mint a JWT token pair for a fresh interactive login with auth_time = now (reads DB)

func (*Service) GenerateTokensWithAuthTime

func (s *Service) GenerateTokensWithAuthTime(ctx context.Context, user User, userInfo *UserInfo, authTime time.Time) (TokenPair, error)

GenerateTokensWithAuthTime is the canonical token-mint entry point. The authTime parameter is the timestamp of the user's last interactive IdP authentication. For fresh logins, pass time.Now(). For refresh-token rotation, pass the preserved auth_time from the previous JWT. SEM@d6ba1258a9717617fbc06be1d06d85ad56f1ccdf: mint a JWT access/refresh token pair preserving a caller-supplied auth_time (reads DB)

func (*Service) GenerateTokensWithUserInfo

func (s *Service) GenerateTokensWithUserInfo(ctx context.Context, user User, userInfo *UserInfo) (TokenPair, error)

GenerateTokensWithUserInfo generates a new JWT token pair for a user with optional provider UserInfo and auth_time = now. SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: mint a JWT token pair enriched with provider UserInfo, auth_time = now (reads DB)

func (*Service) GetCachedGroups

func (s *Service) GetCachedGroups(ctx context.Context, email string) (string, []string, error)

GetCachedGroups retrieves cached user groups from Redis SEM@3d0d5a8cf02fa74fad102f0f99c2b936a164bbea: retrieve cached group membership and IdP name for a user from Redis (reads DB)

func (*Service) GetCachedUserByEmail

func (s *Service) GetCachedUserByEmail(ctx context.Context, email string) (*User, error)

GetCachedUserByEmail retrieves a user from cache by email SEM@9745b416c50726fc3ca5d4637364ba55d6ba0699: fetch a cached user from Redis by email address (reads DB)

func (*Service) GetCachedUserByID

func (s *Service) GetCachedUserByID(ctx context.Context, userID string) (*User, error)

GetCachedUserByID retrieves a user from cache by internal UUID SEM@9745b416c50726fc3ca5d4637364ba55d6ba0699: fetch a cached user from Redis by internal UUID (reads DB)

func (*Service) GetCachedUserByProvider

func (s *Service) GetCachedUserByProvider(ctx context.Context, provider, providerUserID string) (*User, error)

GetCachedUserByProvider retrieves a user from cache by provider and provider user ID SEM@9745b416c50726fc3ca5d4637364ba55d6ba0699: fetch a cached user from Redis by provider and provider user ID (reads DB)

func (*Service) GetClientCredentialByClientID

func (s *Service) GetClientCredentialByClientID(ctx context.Context, clientID string) (*ClientCredential, error)

GetClientCredentialByClientID retrieves a client credential by its client_id SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: fetch a client credential by its client_id string (reads DB)

func (*Service) GetKeyManager

func (s *Service) GetKeyManager() *JWTKeyManager

GetKeyManager returns the JWT key manager (getter for unexported field) SEM@41fea1c48a3526015f75a5e401ec4970c6c9dfcf: return the JWT key manager (pure)

func (*Service) GetLinkedIdentityByProviderSub

func (s *Service) GetLinkedIdentityByProviderSub(ctx context.Context, provider, providerUserID string) (models.LinkedIdentity, error)

GetLinkedIdentityByProviderSub looks up a linked identity by provider and provider-user-id. Returns ErrLinkedIdentityNotFound if no row matches or the store is not wired. SEM@1eb7997add7b39214eac29d20050d7968745a98d: look up a linked identity by provider and provider user ID (reads DB)

func (*Service) GetPrimaryProviderID

func (s *Service) GetPrimaryProviderID(ctx context.Context, userID string) (string, error)

GetPrimaryProviderID gets the provider user ID for a user Note: In the new architecture, each user has exactly one provider stored directly on the users table SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: fetch the primary provider user ID stored on the user record (reads DB)

func (*Service) GetSAMLManager

func (s *Service) GetSAMLManager() *SAMLManager

GetSAMLManager returns the SAML manager (getter for unexported field) SEM@2fbab585a899780eb5d718ec784b7c730c732113: return the SAML manager (pure)

func (*Service) GetUserByAnyProviderID

func (s *Service) GetUserByAnyProviderID(ctx context.Context, providerUserID string) (User, error)

GetUserByAnyProviderID gets a user by provider ID across all providers This allows provider-independent authorization using IdP user IDs NOTE: This can return ambiguous results if the same provider_user_id exists for multiple providers SEM@cf201bc8c1eab7bf74de941e50508142b759ca75: fetch a user by provider user ID across all providers (reads DB)

func (*Service) GetUserByEmail

func (s *Service) GetUserByEmail(ctx context.Context, email string) (User, error)

GetUserByEmail gets a user by email SEM@cf201bc8c1eab7bf74de941e50508142b759ca75: fetch a user by email, checking Redis cache before the DB (reads DB)

func (*Service) GetUserByID

func (s *Service) GetUserByID(ctx context.Context, id string) (User, error)

GetUserByID gets a user by internal UUID SEM@cf201bc8c1eab7bf74de941e50508142b759ca75: fetch a user by internal UUID, checking Redis cache before the DB (reads DB)

func (*Service) GetUserByInternalUUID

func (s *Service) GetUserByInternalUUID(ctx context.Context, uuid string) (User, error)

GetUserByInternalUUID gets a user by their internal UUID. SEM@1eb7997add7b39214eac29d20050d7968745a98d: fetch a user by internal UUID; delegates to GetUserByID (reads DB)

func (*Service) GetUserByProviderAndEmail

func (s *Service) GetUserByProviderAndEmail(ctx context.Context, provider, email string) (User, error)

GetUserByProviderAndEmail gets a user by provider and email address This is used as a fallback when provider_user_id doesn't match but same provider + email does SEM@cf201bc8c1eab7bf74de941e50508142b759ca75: fetch a user by provider and email as a fallback to provider user ID lookup (reads DB)

func (*Service) GetUserByProviderID

func (s *Service) GetUserByProviderID(ctx context.Context, provider, providerUserID string) (User, error)

GetUserByProviderID gets a user by provider and provider user ID SEM@cf201bc8c1eab7bf74de941e50508142b759ca75: fetch a user by provider and provider user ID, checking Redis cache first (reads DB)

func (*Service) GetUserProviders

func (s *Service) GetUserProviders(ctx context.Context, userID string) ([]UserProvider, error)

GetUserProviders gets the OAuth provider for a user Note: In the new architecture, each user has exactly one provider SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: list OAuth provider bindings for a user (reads DB)

func (*Service) GormDB

func (s *Service) GormDB() *gorm.DB

GormDB returns the underlying GORM database connection. Used by services that need to wrap operations in retryable transactions. SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: return the underlying GORM database connection (pure)

func (*Service) HandleClientCredentialsGrant

func (s *Service) HandleClientCredentialsGrant(ctx context.Context, clientID, clientSecret string) (*TokenPair, error)

HandleClientCredentialsGrant processes OAuth 2.0 Client Credentials Grant (RFC 6749 Section 4.4) Returns an access token for machine-to-machine authentication SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: validate client credentials and mint a service-account JWT without refresh token (reads DB)

func (*Service) InvalidateUserCache

func (s *Service) InvalidateUserCache(ctx context.Context, user User) error

InvalidateUserCache removes a user from all cache keys SEM@89d554e793900a75b5703e1d10c9d58f57ceadc6: delete all Redis cache keys for a user (ID, email, provider) (reads DB)

func (*Service) InvalidateUserSessions

func (s *Service) InvalidateUserSessions(ctx context.Context, userID string) error

InvalidateUserSessions invalidates all sessions for a user SEM@cdd711a5407558ac89d03c4548a877007b74e7cd: delete all Redis session keys for a user, terminating all active sessions (reads DB)

func (*Service) IssueAddonDelegationToken

func (s *Service) IssueAddonDelegationToken(
	ctx context.Context,
	invoker *User,
	addonID, deliveryID, threatModelID uuid.UUID,
) (string, error)

IssueAddonDelegationToken mints a short-lived JWT that impersonates the invoker for one addon-invocation write-back. The token's claims:

  • `sub` is the invoker's provider_user_id (matches the invoker's normal login token), so existing JWT middleware and downstream ACL checks resolve to the invoker without modification.
  • `email`, `name`, `idp`, `groups`, `tmi_is_security_reviewer` are copied from the invoker. `tmi_is_administrator` is FORCED to false regardless of the invoker's actual administrator membership — a delegation token never grants admin authority, so the addon cannot escape its mandate even if invoked by an admin.
  • `delegation` carries the addon/delivery/threat-model scope.
  • `aud` is the issuer (self-issued, like normal user tokens) so the existing JWT validator accepts it.
  • `exp` is now+DelegationTokenTTL.

Callers (the webhook delivery worker) should call this once per delivery attempt — the previous attempt's token will have expired by the time a retry fires, and minting fresh tokens keeps the invoker's revocation / group-membership state current. SEM@a227ace8f890d3c768cb52f4b2b1c1817699c88e: build and sign a scoped delegation JWT for an addon webhook invocation, stripping admin privileges

func (*Service) ListClientCredentialsByOwner

func (s *Service) ListClientCredentialsByOwner(ctx context.Context, ownerUUID uuid.UUID) ([]*ClientCredential, error)

ListClientCredentialsByOwner retrieves all client credentials for a given owner SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: list all client credentials belonging to a given owner (reads DB)

func (*Service) RefreshToken

func (s *Service) RefreshToken(ctx context.Context, refreshToken string) (TokenPair, error)

RefreshToken refreshes an access token using a refresh token. Implements single-use rotation (old token deleted) and absolute session expiration. SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: rotate a refresh token and issue a new token pair, enforcing absolute session expiration (reads DB)

func (*Service) RevokeToken

func (s *Service) RevokeToken(ctx context.Context, refreshToken string) error

RevokeToken revokes a refresh token SEM@d885c7955d5a30affb8ddde84ee1cf757aab2a6b: delete a refresh token from Redis to revoke the session (reads DB)

func (*Service) SetClaimsEnricher

func (s *Service) SetClaimsEnricher(enricher ClaimsEnricher)

SetClaimsEnricher sets the claims enricher for JWT token generation SEM@a0040890dd7b1940f542d4211d4338cd0e713cbc: register the claims enricher used during JWT token generation (mutates shared state)

func (*Service) SetLinkedIdentityStore

func (s *Service) SetLinkedIdentityStore(store LinkedIdentityStore)

SetLinkedIdentityStore wires a LinkedIdentityStore into the service, enabling Tier 1b linked-identity resolution during OAuth login. SEM@1eb7997add7b39214eac29d20050d7968745a98d: wire a linked identity store enabling Tier 1b login resolution (mutates shared state)

func (*Service) SetPreUserDeleteHook

func (s *Service) SetPreUserDeleteHook(h UserContentTokenRevoker)

SetPreUserDeleteHook registers a hook that is called before each user deletion to perform best-effort content-token revocations at the provider side. The hook is called with the user's internal UUID before the DB row (and its FK-cascaded child rows) is removed, giving the implementation access to the token data. Pass nil to clear the hook. SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: register a hook to revoke provider tokens before user deletion (mutates shared state)

func (*Service) SetProviderRegistry

func (s *Service) SetProviderRegistry(registry ProviderRegistry)

SetProviderRegistry sets the provider registry for unified provider lookup. SEM@d526a06f3040d3424d4deb08071cd87ae770937f: register the provider registry for unified OAuth provider lookup (mutates shared state)

func (*Service) TouchLinkedIdentityLastUsed

func (s *Service) TouchLinkedIdentityLastUsed(ctx context.Context, id string) error

TouchLinkedIdentityLastUsed updates last_used_at for the linked identity with the given id. SEM@1eb7997add7b39214eac29d20050d7968745a98d: update the last_used_at timestamp on a linked identity record (reads DB)

func (*Service) TransferOwnership

func (s *Service) TransferOwnership(ctx context.Context, sourceUserUUID, targetUserUUID string) (*TransferResult, error)

TransferOwnership transfers all owned threat models and survey responses from one user to another. The source user retains "writer" access. SEM@cdbe48c974fb76e1161972733b30bb0d1c02c3b1: transfer all owned threat models and survey responses from one user to another, retaining writer access for the source (mutates shared state)

func (*Service) UpdateClientCredentialLastUsed

func (s *Service) UpdateClientCredentialLastUsed(ctx context.Context, id uuid.UUID) error

UpdateClientCredentialLastUsed updates the last_used_at timestamp for a client credential SEM@b4b216a8ad19c2ca17d1d9e7466281e90c7b2f41: update the last-used timestamp for a client credential (reads DB)

func (*Service) UpdateUser

func (s *Service) UpdateUser(ctx context.Context, user User) error

UpdateUser updates an existing user SEM@cf201bc8c1eab7bf74de941e50508142b759ca75: persist user profile changes and invalidate the Redis cache (reads DB)

func (*Service) ValidateDeletionChallenge

func (s *Service) ValidateDeletionChallenge(ctx context.Context, userEmail, challengeText string) error

ValidateDeletionChallenge verifies the challenge string matches the stored token SEM@bd740ab90ce24a669adc1fa8b8153efbd33bac10: validate the user's deletion challenge response against the stored token (reads DB)

func (*Service) ValidateToken

func (s *Service) ValidateToken(tokenString string) (*Claims, error)

ValidateToken validates a JWT token SEM@3d0d5a8cf02fa74fad102f0f99c2b936a164bbea: validate a JWT signature, issuer, and audience; return its claims (pure)

type StateStore

type StateStore interface {
	// StoreState stores state with associated data and expiration
	StoreState(ctx context.Context, state, data string, ttl time.Duration) error
	// ValidateState checks if state is valid and returns associated data
	ValidateState(ctx context.Context, state string) (string, error)
	// GetCallbackURL retrieves the callback URL associated with a state
	GetCallbackURL(ctx context.Context, state string) (string, error)
	// StoreCallbackURL stores a callback URL with a state
	StoreCallbackURL(ctx context.Context, state, callbackURL string, ttl time.Duration) error
	// DeleteState removes state from store
	DeleteState(ctx context.Context, state string) error
	// StorePKCEChallenge stores PKCE code challenge with associated method
	StorePKCEChallenge(ctx context.Context, state, codeChallenge, challengeMethod string, ttl time.Duration) error
	// GetPKCEChallenge retrieves PKCE code challenge and method for a state
	GetPKCEChallenge(ctx context.Context, state string) (challenge, method string, err error)
	// DeletePKCEChallenge removes PKCE challenge from store
	DeletePKCEChallenge(ctx context.Context, state string) error
}

StateStore is an interface for storing and retrieving state information SEM@7f2e891b97d9b875349295375fb64355109504b5: interface for storing and validating OAuth state, callback URLs, and PKCE challenges

type StepUpActor

type StepUpActor struct {
	Email          string
	Provider       string
	ProviderUserID string
	DisplayName    string
}

StepUpActor identifies the user whose step-up event is being recorded. All four fields are denormalized into the audit row (matches the SystemAuditEntry pattern; rows survive user deletion). SEM@2993ca8c06b610c81da5355fd0a4befd651c08fa: value struct identifying the user whose step-up event is being audited (pure)

type StepUpAuditor

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

StepUpAuditor wraps a SystemAuditWriter with the field shapes specific to step-up events. Fail-open: write failures are logged but do not propagate. SEM@2993ca8c06b610c81da5355fd0a4befd651c08fa: fail-open auditor that writes step-up authentication events to the system audit log

func NewStepUpAuditor

func NewStepUpAuditor(writer SystemAuditWriter) *StepUpAuditor

NewStepUpAuditor returns an auditor. writer may be nil (in which case audit calls are no-ops with a debug log; matches the existing fail-open posture). SEM@2993ca8c06b610c81da5355fd0a4befd651c08fa: build a StepUpAuditor with the given writer; nil writer makes all calls no-ops (pure)

func (*StepUpAuditor) LogComplete

func (a *StepUpAuditor) LogComplete(ctx context.Context, actor StepUpActor, strength StepUpStrength, providerID, mode string) error

LogComplete records a successful step-up. Strength carries strong|weak; mode carries round_trip|short_circuit. SEM@2993ca8c06b610c81da5355fd0a4befd651c08fa: record a successful step-up authentication event with strength and provider to the audit log

func (*StepUpAuditor) LogFailed

func (a *StepUpAuditor) LogFailed(ctx context.Context, actor StepUpActor, reason string, extras map[string]string) error

LogFailed records a step-up that did not complete successfully. reason is the short stable code (identity_mismatch, access_denied, state_expired, etc.). extras are inlined into the payload; values are redacted via redactStepUpAttemptedEmail when the key is "attempted_email". SEM@2993ca8c06b610c81da5355fd0a4befd651c08fa: record a failed step-up attempt, redacting any attempted email in the payload

func (*StepUpAuditor) LogRejected

func (a *StepUpAuditor) LogRejected(ctx context.Context, actor StepUpActor, reason string, extras map[string]string) error

LogRejected records a step-up attempt that was rejected before the upstream round-trip began (e.g., CC-grant caller, invalid provider). SEM@2993ca8c06b610c81da5355fd0a4befd651c08fa: record a step-up attempt rejected before the upstream round-trip, with reason extras

type StepUpStrength

type StepUpStrength int

StepUpStrength classifies whether a given provider can guarantee a fresh interactive re-authentication on demand. Strong providers honor OIDC's prompt=login + max_age=0 (or SAML's ForceAuthn=true). Weak providers do not, and step-up against them is short-circuited with an audit marker.

See docs/superpowers/specs/2026-05-10-oauth2-step-up-design.md. SEM@43dacd547eec5eefa97d1c84417548679f11c037: enum classifying whether a provider can enforce interactive re-authentication on demand (pure)

const (
	StepUpStrong StepUpStrength = iota
	StepUpWeak
)

func ClassifyStepUpStrength

func ClassifyStepUpStrength(cfg OAuthProviderConfig) StepUpStrength

ClassifyStepUpStrength returns the step-up strength for the given provider config. Rules (first match wins):

  1. ID in knownStrongProviderIDs → Strong
  2. ID in knownWeakProviderIDs → Weak
  3. Has Issuer AND JWKSURL (i.e., OIDC) → Strong (generic OIDC providers honor prompt=login per the OIDC spec)
  4. Otherwise → Weak (pure-OAuth2 fallback; safest default)

SAML providers are classified Strong by callers via a separate path; this function operates on OAuth provider configs only. SEM@43dacd547eec5eefa97d1c84417548679f11c037: classify an OAuth provider config as strong or weak step-up based on allowlists and OIDC capability (pure)

func (StepUpStrength) String

func (s StepUpStrength) String() string

SEM@43dacd547eec5eefa97d1c84417548679f11c037: convert a StepUpStrength value to its human-readable string label (pure)

type SystemAuditRecord

type SystemAuditRecord struct {
	ActorEmail       string
	ActorProvider    string
	ActorProviderID  string
	ActorDisplayName string
	HTTPMethod       string
	HTTPPath         string
	FieldPath        string
	OldValueRedacted *string
	NewValueRedacted *string
	ChangeSummary    *string
	CreatedAt        time.Time
}

SystemAuditRecord is a transport struct mapping 1:1 to api/models.SystemAuditEntry. Defined here so package auth does not import package api (which would create a cycle). SEM@2993ca8c06b610c81da5355fd0a4befd651c08fa: transport struct carrying actor identity and change details for a system audit entry (pure)

type SystemAuditWriter

type SystemAuditWriter interface {
	WriteSystemAudit(ctx context.Context, entry SystemAuditRecord) error
}

SystemAuditWriter is the minimal write surface required by step-up audit helpers. The concrete implementation in package api wraps GORM/Postgres; tests inject a memory implementation. SEM@2993ca8c06b610c81da5355fd0a4befd651c08fa: interface for writing system audit records to a persistent store

type TestHelper

type TestHelper struct {
	DB          *sql.DB
	Mock        sqlmock.Sqlmock
	Redis       *redis.Client
	MiniRedis   *miniredis.Miniredis
	KeyManager  *JWTKeyManager
	StateStore  StateStore
	TestContext context.Context
}

TestHelper provides utilities for testing auth package functionality SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: test fixture bundling mocked DB, Redis, JWT key manager, and state store for auth tests

func NewTestHelper

func NewTestHelper(t *testing.T) *TestHelper

NewTestHelper creates a new test helper with mocked dependencies SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build a test helper with mocked SQL DB, Redis, and JWT key manager (pure)

func (*TestHelper) Cleanup

func (h *TestHelper) Cleanup()

Cleanup releases all resources held by the test helper SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: release Redis client, miniredis, and DB connections held by the test helper

func (*TestHelper) FastForwardRedis

func (h *TestHelper) FastForwardRedis(duration time.Duration)

FastForwardRedis advances time in miniredis for TTL testing SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: advance miniredis internal clock to trigger TTL expiry in tests (mutates shared state)

func (*TestHelper) FlushRedis

func (h *TestHelper) FlushRedis()

FlushRedis clears all keys in miniredis SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: delete all keys from the test Redis instance (mutates shared state)

func (*TestHelper) GetRedisKey

func (h *TestHelper) GetRedisKey(key string) (string, error)

GetRedisKey gets a key from miniredis for testing SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: fetch a string value by key from the test Redis instance

func (*TestHelper) SetRedisKey

func (h *TestHelper) SetRedisKey(key, value string, expiration time.Duration) error

SetRedisKey sets a key in miniredis for testing SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: store a key-value pair with expiration in the test Redis instance

type TestProvider

type TestProvider struct {
	*BaseProvider
	// contains filtered or unexported fields
}

TestProvider implements the TMI internal OAuth provider In dev/test builds (TMI_BUILD_MODE=dev|test): supports Authorization Code flow with ephemeral user creation In production builds: Only supports Client Credentials Grant for machine-to-machine authentication SEM@4798263136c0951661870727f56effca70bb94bb: internal OAuth provider for dev/test builds supporting authorization code flow with ephemeral users

func NewTestProvider

func NewTestProvider(config OAuthProviderConfig, callbackURL string) *TestProvider

NewTestProvider creates a new test OAuth provider SEM@e55d63794c48585aafab36880122df63ab8ab1be: build a TestProvider configured with a well-known test secret and the given OAuth config (pure)

func (*TestProvider) ExchangeCode

func (p *TestProvider) ExchangeCode(ctx context.Context, code string) (*TokenResponse, error)

ExchangeCode validates the authorization code and returns tokens only for valid codes SEM@8173e355a916d49598c943a5c7218a708f032f81: validate a test authorization code and return a fake token response embedding any login_hint (pure)

func (*TestProvider) GetAuthorizationURL

func (p *TestProvider) GetAuthorizationURL(state string) string

GetAuthorizationURL returns the test authorization URL For the test provider, we'll create a direct callback URL instead of an external redirect SEM@0a07a7223c986c6b65b4c7eaad0d824831641173: build a test callback URL encoding a fake auth code and state parameter (pure)

func (*TestProvider) GetUserInfo

func (p *TestProvider) GetUserInfo(ctx context.Context, accessToken string) (*UserInfo, error)

GetUserInfo returns fake user information SEM@3e48a58cb418d2e7a4f04f1288fa11cb942bc99e: return user info for a test access token, using login_hint identity or generating a random test user (pure)

func (*TestProvider) ValidateIDToken

func (p *TestProvider) ValidateIDToken(ctx context.Context, idToken string) (*IDTokenClaims, error)

ValidateIDToken validates the test ID token (always succeeds) SEM@3e48a58cb418d2e7a4f04f1288fa11cb942bc99e: validate a test ID token and return claims for the login_hint user or a random test user (pure)

type TokenBlacklist

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

TokenBlacklist manages blacklisted JWT tokens using Redis SEM@41fea1c48a3526015f75a5e401ec4970c6c9dfcf: Redis-backed store for revoked JWT tokens to prevent reuse after logout (reads DB)

func NewTokenBlacklist

func NewTokenBlacklist(redisClient *redis.Client, keyManager *JWTKeyManager) *TokenBlacklist

NewTokenBlacklist creates a new token blacklist service SEM@70ff47b7829f38ef04399520210ae8765d39495d: build a Redis-backed token blacklist service (reads DB)

func (*TokenBlacklist) BlacklistToken

func (tb *TokenBlacklist) BlacklistToken(ctx context.Context, tokenString string) error

BlacklistToken adds a JWT token to the blacklist SEM@70ff47b7829f38ef04399520210ae8765d39495d: store a JWT in the revocation list until it expires (reads DB)

func (*TokenBlacklist) CleanupExpiredTokens

func (tb *TokenBlacklist) CleanupExpiredTokens(ctx context.Context) error

CleanupExpiredTokens removes expired entries from the blacklist This is handled automatically by Redis TTL, but this method can be used for monitoring or manual cleanup if needed SEM@70ff47b7829f38ef04399520210ae8765d39495d: no-op stub; Redis TTL handles blacklist expiry automatically (pure)

func (*TokenBlacklist) IsTokenBlacklisted

func (tb *TokenBlacklist) IsTokenBlacklisted(ctx context.Context, tokenString string) (bool, error)

IsTokenBlacklisted checks if a JWT token is blacklisted SEM@70ff47b7829f38ef04399520210ae8765d39495d: check whether a JWT has been revoked (reads DB)

type TokenIntrospectionResponse

type TokenIntrospectionResponse struct {
	Active    bool   `json:"active"`
	Sub       string `json:"sub,omitempty"`
	Email     string `json:"email,omitempty"`
	Name      string `json:"name,omitempty"`
	Iat       int64  `json:"iat,omitempty"`
	Exp       int64  `json:"exp,omitempty"`
	Aud       string `json:"aud,omitempty"`
	Iss       string `json:"iss,omitempty"`
	TokenType string `json:"token_type,omitempty"`
	Scope     string `json:"scope,omitempty"`
}

TokenIntrospectionResponse represents the response from token introspection SEM@28792aa3991e394010e49c040d3db2d5f14a6eff: RFC 7662 token introspection response payload with active status and standard claims

type TokenPair

type TokenPair struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	ExpiresIn    int    `json:"expires_in"`
	TokenType    string `json:"token_type"`
}

TokenPair contains an access token and a refresh token SEM@65af9b7db2850b6e18076df15ed522c8df4bb64c: access and refresh token response returned after successful authentication (pure)

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	RefreshToken string `json:"refresh_token,omitempty"`
	ExpiresIn    int    `json:"expires_in"`
	IDToken      string `json:"id_token,omitempty"`
}

TokenResponse contains the response from the token endpoint SEM@b14a829fd98bc22eaf2939ee51854649b9620cb0: OAuth token endpoint response containing access, refresh, and ID tokens

type TokenTestCase

type TokenTestCase struct {
	Name           string
	User           User
	ExpectedError  bool
	ExpectedClaims func(*testing.T, *Claims)
}

TokenTestCase represents a test case for token operations SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: data type grouping token operation inputs and expected outcomes for table-driven tests (pure)

type TransferResult

type TransferResult struct {
	ThreatModelIDs    []string `json:"threat_model_ids"`
	SurveyResponseIDs []string `json:"survey_response_ids"`
}

TransferResult contains statistics about the ownership transfer operation SEM@36c1f84217ecf3f5087ad65186cd974b9b4df275: value object summarising which threat model and survey response IDs were transferred

type User

type User struct {
	InternalUUID       string     `json:"internal_uuid"`    // Internal system UUID (cached but excluded from API responses via convertUserToAPIResponse)
	Provider           string     `json:"provider"`         // OAuth provider: "tmi", "google", "github", "microsoft", "azure"
	ProviderUserID     string     `json:"provider_user_id"` // Provider's user ID (from JWT sub claim)
	Email              string     `json:"email"`
	Name               string     `json:"name"` // Display name for UI presentation
	EmailVerified      bool       `json:"email_verified"`
	AccessToken        *string    `json:"-"`                    // OAuth access token (not exposed in JSON) - nullable
	RefreshToken       *string    `json:"-"`                    // OAuth refresh token (not exposed in JSON) - nullable
	TokenExpiry        *time.Time `json:"-"`                    // Token expiration time (not exposed in JSON) - nullable
	Groups             []string   `json:"groups,omitempty"`     // Groups from identity provider (not stored in DB)
	IsAdmin            bool       `json:"is_admin"`             // Whether user has administrator privileges
	IsSecurityReviewer bool       `json:"is_security_reviewer"` // Whether user is a security reviewer
	Automation         *bool      `json:"automation,omitempty"` // Whether this is an automation/service account (server-managed, nullable)
	CreatedAt          time.Time  `json:"created_at"`
	ModifiedAt         time.Time  `json:"modified_at"`
	LastLogin          *time.Time `json:"last_login,omitempty"` // nullable - may be NULL for auto-created admin users
}

User represents a user in the system SEM@24dcbaf59ea6bfe4e66c3f1fbc4863c809cfdc0e: domain user struct with provider identity, roles, and OAuth token fields

func CreateTestUser

func CreateTestUser(provider, email string) User

CreateTestUser creates a test user with default values SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build a User value with default fields for a given provider and email (pure)

func CreateTestUserWithGroups

func CreateTestUserWithGroups(provider, email string, groups []string) User

CreateTestUserWithGroups creates a test user with specific groups SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build a test user assigned to specific groups (pure)

func CreateTestUserWithRole

func CreateTestUserWithRole(provider, email string, isAdmin bool) User

CreateTestUserWithRole creates a test user with specific admin status SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build a test user with a specific admin role flag (pure)

type UserContentTokenRevoker

type UserContentTokenRevoker interface {
	// RevokeUserTokens attempts to revoke all content tokens belonging to
	// userID at their respective providers. It never returns an error.
	RevokeUserTokens(ctx context.Context, userID string)
}

UserContentTokenRevoker is called before a user is deleted to sweep any per-user OAuth tokens at the provider side. Implementations must be best-effort: revocation failures must be logged but must never block user deletion. The interface lives in the auth package to avoid a circular import with the api package (which provides the concrete implementation). SEM@18f87a010aa0bba84d6fa6221cfb289094caf982: interface for revoking all provider-side OAuth tokens before a user is deleted

type UserGroupInfo

type UserGroupInfo struct {
	InternalUUID string `json:"internal_uuid"`
	GroupName    string `json:"group_name"`
	Name         string `json:"name,omitempty"`
}

UserGroupInfo represents a TMI-managed group that a user belongs to SEM@a0040890dd7b1940f542d4211d4338cd0e713cbc: group membership record linking a user to a TMI-managed group (pure)

type UserGroupsFetcher

type UserGroupsFetcher interface {
	GetUserGroups(ctx context.Context, userInternalUUID string) ([]UserGroupInfo, error)
}

UserGroupsFetcher retrieves TMI-managed group memberships for a user SEM@a0040890dd7b1940f542d4211d4338cd0e713cbc: contract for fetching TMI-managed group memberships for a user (pure)

type UserInfo

type UserInfo struct {
	ID            string   `json:"id,omitempty"`
	Email         string   `json:"email,omitempty"`
	EmailVerified bool     `json:"email_verified,omitempty"`
	Name          string   `json:"name,omitempty"`
	GivenName     string   `json:"given_name,omitempty"`
	FamilyName    string   `json:"family_name,omitempty"`
	Picture       string   `json:"picture,omitempty"`
	Locale        string   `json:"locale,omitempty"`
	IdP           string   `json:"idp,omitempty"`    // Identity provider ID
	Groups        []string `json:"groups,omitempty"` // Groups from identity provider
}

UserInfo contains user information from the provider SEM@0dcfe60d024e5cd95a40b61fc489253e670af6ce: user identity and group membership returned from an OAuth provider's userinfo endpoint

func CreateTestUserInfo

func CreateTestUserInfo(email, name, idp string, groups []string) *UserInfo

CreateTestUserInfo creates UserInfo for testing OAuth responses SEM@ac74bec7c763b2f6486d3fe0a6731458c37e43c5: build a UserInfo value representing an OAuth provider's identity response (pure)

type UserInfoEndpoint

type UserInfoEndpoint struct {
	URL    string            `json:"url"`
	Claims map[string]string `json:"claims"`
}

UserInfoEndpoint represents a single userinfo endpoint and its claim mappings SEM@93e972b21be4fbdf788d2884f25d14b33d41b98e: a single OAuth userinfo endpoint URL with its claim-to-field mappings (pure)

type UserProvider

type UserProvider struct {
	ID             string    `json:"id"`
	UserID         string    `json:"user_id"`
	Provider       string    `json:"provider"`
	ProviderUserID string    `json:"provider_user_id"`
	Email          string    `json:"email"`
	IsPrimary      bool      `json:"is_primary"`
	CreatedAt      time.Time `json:"created_at"`
	LastLogin      time.Time `json:"last_login"`
}

UserProvider represents a user's OAuth provider SEM@3d0d5a8cf02fa74fad102f0f99c2b936a164bbea: OAuth provider binding record linking a user to a provider identity (pure)

Directories

Path Synopsis
Package repository provides database repository interfaces and implementations for the auth service.
Package repository provides database repository interfaces and implementations for the auth service.

Jump to

Keyboard shortcuts

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