Documentation
¶
Index ¶
- Variables
- func WithClientIP(ctx context.Context, ip string) context.Context
- type EmailVerifier
- type KeyServiceInterface
- type Service
- func (s *Service) AuthMiddleware() gin.HandlerFunc
- func (s *Service) AuthenticateAPIKey(ctx context.Context, apiKey string) (string, error)
- func (s *Service) CheckResourceAccess(userID, resourceType, resourceID, action string) bool
- func (s *Service) ClearUserSuspended(ctx context.Context, userID string) error
- func (s *Service) CreateAPIKey(ctx context.Context, userID string, req types.CreateAPIKeyRequest, ...) (*types.APIKey, error)
- func (s *Service) DeleteAPIKey(ctx context.Context, userID, keyID string) error
- func (s *Service) EachSigningKey(fn func(key []byte) bool)
- func (s *Service) GenerateToken(userID string) (string, error)
- func (s *Service) GenerateTokenWithDuration(userID string, duration time.Duration) (string, error)
- func (s *Service) GetUserID(c *gin.Context) string
- func (s *Service) IssueTokenAndUnlockDEK(ctx context.Context, userID string, ttl time.Duration, dekSource string) (string, error)
- func (s *Service) ListAPIKeys(ctx context.Context, userID string) ([]*types.APIKey, error)
- func (s *Service) Login(ctx context.Context, req types.LoginRequest) (*types.AuthResponse, error)
- func (s *Service) MarkUserSuspended(ctx context.Context, userID string) error
- func (s *Service) OptionalAuthMiddleware() gin.HandlerFunc
- func (s *Service) ProvisionServerKEKKeys(ctx context.Context, userID string) error
- func (s *Service) Register(ctx context.Context, req types.RegisterRequest) (*types.AuthResponse, error)
- func (s *Service) RevokeAllUserSessions(ctx context.Context, userID string) error
- func (s *Service) RevokeToken(ctx context.Context, token string) error
- func (s *Service) SetEmailVerifier(v EmailVerifier)
- func (s *Service) SetInstanceSettings(svc interfaces.SettingsReader)
- func (s *Service) SetKeyService(ks KeyServiceInterface)
- func (s *Service) SetMasterKey(key []byte)
- func (s *Service) SetRootKeyProvider(provider secrets.RootKeyProvider)
- func (s *Service) Start() error
- func (s *Service) Stop() error
- func (s *Service) ValidateToken(ctx context.Context, tokenString string) (string, error)
- func (s *Service) ValidateTokenWithClientIP(ctx context.Context, tokenString, clientIP string) (string, error)
- func (s *Service) VerifyPassword(ctx context.Context, userID string, password []byte) error
Constants ¶
This section is empty.
Variables ¶
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 ¶
Types ¶
type EmailVerifier ¶
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 {
InitializeUserKeys(ctx context.Context, userID string, password []byte) (recoveryKeyHex string, err error)
// InitializeUserKeysServerKEK (Epic 58) provisions a DEK wrapped by the
// master-KEK RootKeyProvider rather than a password-derived KEK, for an SSO
// auto-provisioned user who has no password. The store atomically flips
// users.dek_source to 'server_kek' alongside the user_keys insert.
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 (*Service) CheckResourceAccess ¶
CheckResourceAccess checks if a user has access to a resource
func (*Service) ClearUserSuspended ¶
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 (*Service) EachSigningKey ¶
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 ¶
GenerateToken generates a JWT token for a user using the configured tokenDuration. It delegates to GenerateTokenWithDuration, which is the canonical implementation.
func (*Service) GenerateTokenWithDuration ¶
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) 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 (*Service) Login ¶
func (s *Service) Login(ctx context.Context, req types.LoginRequest) (*types.AuthResponse, error)
func (*Service) MarkUserSuspended ¶
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
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 the server-KEK analog of InitializeUserKeys: no password, no recovery blob — 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 ¶
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 ¶
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 ¶
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) ValidateToken ¶
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 ¶
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.