auth

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Aug 8, 2026 License: AGPL-3.0 Imports: 27 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrEmailNotVerified = errors.New("please verify your email address before logging in")

ErrEmailNotVerified is returned by Login when the credentials are correct but the user has not verified their email address. The caller (handler) maps this to 403 with a clear message directing the user to check their email. Not recorded as a failed login attempt (the credentials are valid).

Functions

func WithClientIP added in v0.4.0

func WithClientIP(ctx context.Context, ip string) context.Context

WithClientIP returns ctx with the client IP attached, so the lockout logic can key on email+IP (G13). Callers that don't set this fall back to email-only keying (the pre-G13 behavior).

Types

type EmailVerifier

type EmailVerifier interface {
	SendVerification(ctx context.Context, userID, email string) error
}

EmailVerifier creates and sends email-verification tokens for new users. When set, Register creates an unverified account (email_verified=false) and calls Verify to send the verification link. When nil, Register marks the account email_verified=true immediately (dev/air-gapped mode — no email provider to verify with).

type KeyServiceInterface

type KeyServiceInterface interface {
	// InitializeUserKeysServerKEK provisions a DEK wrapped by the master-KEK
	// RootKeyProvider. All users are server-KEK-wrapped (the password tier
	// DEK tier has been removed).
	InitializeUserKeysServerKEK(ctx context.Context, userID, dekSource string) error
	UnlockDEK(ctx context.Context, userID string, password []byte, sessionID string, ttl time.Duration) error
	// UnlockDEKWithSigningKey is UnlockDEK + durable jwt_sessions write
	// (Epic 56). Login calls this with the active signing key (s.jwtSecret)
	// so the unlocked DEK survives Valkey restart / LRU eviction for the
	// JWT's remaining lifetime. Pass nil to fall back to Redis-only.
	UnlockDEKWithSigningKey(ctx context.Context, userID string, password []byte, sessionID string, ttl time.Duration, activeSigningKey []byte) error
	HasKeys(ctx context.Context, userID string) (bool, error)
	// GetDEK (Epic 56) takes the matched signing key so the rehydrate path
	// can derive the per-session KEK from the same key the JWT validated
	// under. Pass nil for API-key callers — rehydrate is skipped and
	// ErrDEKUnavailable is returned (correct: API keys have their own
	// durable DEK path via api_keys.WrappedDEK).
	GetDEK(ctx context.Context, sessionID string, matchedSigningKey []byte) ([]byte, error)
	CacheDEK(ctx context.Context, sessionID string, dek []byte, ttl time.Duration) error
	// DeleteDurableSessionsForUser (Epic 56) removes every jwt_sessions
	// row for a user. Called by RevokeAllUserSessions to keep the
	// durable store consistent with the Redis revocation markers — without
	// this, a stolen JWT could still rehydrate the DEK from PG after the
	// victim resets their password.
	DeleteDurableSessionsForUser(ctx context.Context, userID string) error
}

KeyServiceInterface abstracts the key service for DEK lifecycle.

type Service

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

Service handles authentication and authorization

func New

func New(cfg *config.Config, log *logger.Logger, dbService interfaces.DatabaseService, cacheService interfaces.CacheService) (*Service, error)

New creates a new auth service

func (*Service) AuthMiddleware

func (s *Service) AuthMiddleware() gin.HandlerFunc

AuthMiddleware returns a middleware that validates JWT tokens

func (*Service) AuthenticateAPIKey

func (s *Service) AuthenticateAPIKey(ctx context.Context, apiKey string) (string, error)

func (*Service) CheckResourceAccess

func (s *Service) CheckResourceAccess(userID, resourceType, resourceID, action string) bool

CheckResourceAccess checks if a user has access to a resource

func (*Service) ClearUserSuspended

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

ClearUserSuspended removes the revocation marker so an unsuspended user's existing tokens work again immediately (no TTL wait).

func (*Service) CreateAPIKey

func (s *Service) CreateAPIKey(ctx context.Context, userID string, req types.CreateAPIKeyRequest, sessionID string, matchedSigningKey []byte) (*types.APIKey, error)

CreateAPIKey creates a new API key for the user. When req.DecryptAccess is true, the user's DEK is wrapped under the new API key's derived KEK so API-key auth can read encrypted user_secrets. matchedSigningKey is the JWT signing key that validated the caller's session (Epic 56); pass nil for API-key-authenticated callers (a key cannot be created from an API-key session anyway — the existing sessionID check requires a JWT).

func (*Service) DeleteAPIKey

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

func (*Service) EachSigningKey

func (s *Service) EachSigningKey(fn func(key []byte) bool)

EachSigningKey satisfies secrets.SigningKeyEnumerator so KeyService's GetDEKForUser (used by background/auto-push paths) can iterate the same set of active + previous signing keys that parseTokenAcceptingRotatedKeys uses at JWT validation time. Primary key first, then previous keys in most-recent-rotation-first order.

The callback receives a FRESH COPY of each key on every invocation; implementations that store or retain the bytes past the callback return must copy again. Zeroed slice is not returned — callers zero their own copies.

func (*Service) GenerateToken

func (s *Service) GenerateToken(userID string) (string, error)

GenerateToken generates a JWT token for a user using the configured tokenDuration. It delegates to GenerateTokenWithDuration, which is the canonical implementation.

func (*Service) GenerateTokenWithDuration

func (s *Service) GenerateTokenWithDuration(userID string, duration time.Duration) (string, error)

GenerateTokenWithDuration generates a JWT token for a user with an explicit TTL. This is the canonical token-generation implementation; GenerateToken delegates here. Not exposed on the AuthService interface — callers outside the auth package use GenerateToken, which always uses the configured tokenDuration.

func (*Service) GetUserID

func (s *Service) GetUserID(c *gin.Context) string

GetUserID gets the user ID from the context

func (*Service) IssueTokenAndUnlockDEK added in v0.7.0

func (s *Service) IssueTokenAndUnlockDEK(ctx context.Context, userID string, ttl time.Duration, dekSource string) (string, error)

IssueTokenAndUnlockDEK is the non-password login completion: generate a JWT and unlock (and, if necessary, first provision) the caller's server-KEK DEK so the issued session is immediately usable for personal-secret operations. Used by the SSO callback (Epic 58). The password argument is intentionally absent — these users authenticate without one, and the keyService.UnlockDEKWithSigningKey path branches on users.dek_source to unwrap via the master-KEK provider.

Provisioning is idempotent-guarded via HasKeys: an SSO user who already has a server-KEK DEK (the common case after first login) skips re-provisioning; a user created before this epic with no keys is provisioned on first post-epic login (the implicit backfill the design recommends).

func (*Service) ListAPIKeys

func (s *Service) ListAPIKeys(ctx context.Context, userID string) ([]*types.APIKey, error)

func (*Service) Login

func (*Service) MarkUserSuspended

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

MarkUserSuspended writes a per-user revocation marker so the auth middleware rejects the user's existing JWTs/API keys the instant the admin suspends them, without waiting for the next per-request GetUser or depending on the DB (which may be briefly unavailable). The TTL is max(tokenDuration, rememberMeDuration) so the marker outlives every outstanding token — including remember-me sessions (720h default), which outlast standard tokens (24h). Unsuspends call ClearUserSuspended for an immediate recovery (no TTL wait).

func (*Service) OptionalAuthMiddleware

func (s *Service) OptionalAuthMiddleware() gin.HandlerFunc

OptionalAuthMiddleware is like AuthMiddleware but never aborts. It sets "userID" in the context when a valid JWT/API key is present, and calls c.Next() unconditionally. Handlers that use this middleware must check the userID themselves and handle the unauthenticated case.

D19: a suspended user is treated as unauthenticated here — no userID, sessionID, or role is set — so they cannot exercise any authenticated capability. They retain access only to the anonymous surface (the same surface any unauthenticated caller sees). The middleware still does not abort, preserving its contract for public+optional-auth endpoints.

func (*Service) ProvisionServerKEKKeys added in v0.7.0

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

ProvisionServerKEKKeys provisions a server-KEK-wrapped DEK for a user who has none (SSO auto-provisioned under Epic 58, passkey-only under Epic 59). It is provisions a DEK wrapped by the master RootKeyProvider — the DEK is recoverable from the master KEK (operator-controlled). The keyService flips users.dek_source to 'server_kek' atomically with the key material write. Exposed for the SSO/passkey flows via the UserKeyManager capability (consumed by the sso package).

func (*Service) Register

func (s *Service) Register(ctx context.Context, req types.RegisterRequest) (*types.AuthResponse, error)

func (*Service) RevokeAllUserSessions

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

RevokeAllUserSessions revokes all outstanding JWTs for a user by writing "revoked" under each tracked jti key AND hash key (both paths that ValidateToken checks). Used by password-reset confirm (US-49.5) so a stolen JWT stops working after the victim resets their password.

func (*Service) RevokeToken

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

RevokeToken revokes a JWT token. ctx propagates the caller's deadline/cancellation into the cache calls (US-46.5 / #224 P2); the 5s cap is retained as a per-call safety bound derived from ctx.

func (*Service) SetEmailVerifier

func (s *Service) SetEmailVerifier(v EmailVerifier)

SetEmailVerifier wires the email-verification hook. Optional — nil means Register auto-verifies (dev mode without an email provider).

func (*Service) SetInstanceSettings

func (s *Service) SetInstanceSettings(svc interfaces.SettingsReader)

SetInstanceSettings injects the instance settings service for runtime config reads.

func (*Service) SetKeyService

func (s *Service) SetKeyService(ks KeyServiceInterface)

SetKeyService sets the optional key service for secret management.

func (*Service) SetMasterKey

func (s *Service) SetMasterKey(key []byte)

SetMasterKey sets the server master key used for encrypting API key ciphertext (enabling DEK re-wrap on rotation). Derived from LLMSAFESPACES_MASTER_SECRET.

func (*Service) SetRootKeyProvider

func (s *Service) SetRootKeyProvider(provider secrets.RootKeyProvider)

SetRootKeyProvider sets the RootKeyProvider for API key at-rest encryption.

func (*Service) Start

func (s *Service) Start() error

Start initializes the auth service

func (*Service) Stop

func (s *Service) Stop() error

Stop cleans up the auth service

func (*Service) ValidateToken

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

ValidateToken validates a JWT token or API key.

func (*Service) ValidateTokenWithClientIP

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

ValidateTokenWithClientIP validates a JWT token or API key, enforcing allowed_cidrs when clientIP is non-empty. ctx propagates the caller's deadline/cancellation into the cache + DB calls (US-46.5 / issue #224); the 5s cap is retained as a per-call safety bound derived from ctx.

The token-validation cache value uses the format "userID|matchedKeyIndex" (Epic 56 Step 3) so a cache hit can surface the matched signing-key index without re-parsing the JWT. Legacy entries (pre-deploy) are bare "userID"; the reader treats them as matchedKeyIndex = -1 ("unknown, caller must re-parse if it needs the key"). The "revoked" sentinel keeps its original meaning.

func (*Service) VerifyPassword

func (s *Service) VerifyPassword(ctx context.Context, userID string, password []byte) error

VerifyPassword checks the supplied password against the stored bcrypt hash for userID. Returns nil on match, ErrInvalidPassword on any mismatch / not-found / DB error. The error returned is uniform — callers must NOT differentiate between "wrong password" and "user does not exist" because doing so leaks user-existence status (the same reason Login returns the generic "invalid credentials" message).

bcrypt.CompareHashAndPassword runs in constant time relative to the hash cost, so timing-channel leakage is bounded by the bcrypt cost (12 in this codebase) regardless of password length.

Jump to

Keyboard shortcuts

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