Documentation
¶
Overview ¶
Package domain defines authentication and authorization domain models and business logic.
It provides client-based authentication with policy-based authorization. Clients authenticate using secrets and are authorized via capability-based policies that control access to resource paths.
Package domain defines authentication and authorization domain models. Implements capability-based access control with clients, tokens, policies, and audit logging.
Index ¶
- Variables
- func HashTokenPlain(plain string) string
- func IsValidCapability(cap Capability) bool
- func MintClientSecret() (plain string, err error)
- func MintToken() (plain, hash string, err error)
- type AuditLog
- type AuditLogRepository
- type Capability
- type Client
- type ClientRepository
- type CreateClientInput
- type CreateClientOutput
- type Decision
- type IssueTokenInput
- type IssueTokenOutput
- type LockoutPolicy
- type LoginOutcome
- type PolicyDocument
- type Token
- type TokenRepository
- type UpdateClientInput
Constants ¶
This section is empty.
Variables ¶
var ( // ErrClientNotFound indicates a client with the specified ID was not found. ErrClientNotFound = errors.Wrap(errors.ErrNotFound, "client not found") // ErrTokenNotFound indicates a token with the specified ID was not found. ErrTokenNotFound = errors.Wrap(errors.ErrNotFound, "token not found") // ErrInvalidCredentials indicates the provided credentials are invalid. // This error is returned for both non-existent clients and incorrect secrets // to prevent user enumeration attacks. ErrInvalidCredentials = errors.Wrap(errors.ErrUnauthorized, "invalid credentials") // ErrClientInactive indicates the client exists but is not active. // Inactive clients cannot authenticate or issue tokens. ErrClientInactive = errors.Wrap(errors.ErrForbidden, "client is inactive") // ErrClientLocked indicates the client is temporarily locked due to too many failed // authentication attempts. The client must wait for the lockout period to expire. ErrClientLocked = errors.Wrap(errors.ErrLocked, "client is locked") // ErrSignatureInvalid indicates the audit log HMAC signature verification failed. // This typically means the audit log data has been tampered with after creation. ErrSignatureInvalid = errors.Wrap(errors.ErrInvalidInput, "audit log signature is invalid") // ErrSignatureMissing indicates the audit log does not have a cryptographic signature. // This is expected for legacy logs created before signature implementation. ErrSignatureMissing = errors.Wrap(errors.ErrNotFound, "audit log signature is missing") // ErrKekNotFoundForLog indicates the KEK referenced by an audit log signature // was not found in the KEK chain. This should not occur if KEK retention policy // is properly enforced (ON DELETE RESTRICT constraint). ErrKekNotFoundForLog = errors.Wrap( errors.ErrNotFound, "kek not found for audit log signature verification", ) )
Authentication and authorization errors.
Functions ¶
func HashTokenPlain ¶ added in v0.29.0
HashTokenPlain computes the canonical hex-encoded SHA-256 digest of a plaintext token. This is the single source of truth for how tokens are hashed both at issuance (storing the hash) and at authentication (looking up by hash). Drifting from this function on either side will silently invalidate every issued token.
func IsValidCapability ¶ added in v0.28.0
func IsValidCapability(cap Capability) bool
IsValidCapability checks if a capability is valid and exists in the system. Used for strict validation in policy parsing.
func MintClientSecret ¶ added in v0.29.0
MintClientSecret creates a fresh client secret and returns the plaintext value. The caller is responsible for hashing it under the chosen password-hash policy before persistence.
Types ¶
type AuditLog ¶
type AuditLog struct {
ID uuid.UUID
RequestID uuid.UUID
ClientID uuid.UUID
Capability Capability
Path string
Metadata map[string]any
Signature []byte // HMAC-SHA256 signature (32 bytes) for tamper detection
KekID *uuid.UUID // KEK used for signing (NULL for legacy unsigned logs)
IsSigned bool // True if signed, false for legacy logs
CreatedAt time.Time
}
AuditLog records authorization decisions for compliance and security monitoring. Captures client identity, requested resource path, required capability, and metadata. Used to track access patterns and investigate security incidents.
Cryptographic Integrity: All audit logs are signed with HMAC-SHA256 using KEK-derived signing keys to detect tampering. The Signature field contains the 32-byte HMAC, KekID references the KEK used for signing, and IsSigned distinguishes signed logs from legacy unsigned logs created before the feature.
func (*AuditLog) Canonical ¶ added in v0.29.0
Canonical returns the deterministic byte representation of this audit log used for HMAC signing. Format: RequestID || ClientID || capability (length-prefixed) || path (length-prefixed) || metadata JSON (length-prefixed) || created_at as Unix nanoseconds (8 bytes, big-endian).
func (*AuditLog) HasValidSignature ¶ added in v0.9.0
HasValidSignature checks if the audit log has complete signature data. Returns true only if the log is marked as signed, has a KEK ID, and contains a 32-byte HMAC signature.
type AuditLogRepository ¶ added in v0.29.0
type AuditLogRepository interface {
// Create stores a new audit log entry recording an authorization decision.
// Returns error if the audit log ID already exists or database operation fails.
Create(ctx context.Context, auditLog *AuditLog) error
// Get retrieves a single audit log by ID. Returns error if not found.
// Used for signature verification of specific audit logs.
Get(ctx context.Context, id uuid.UUID) (*AuditLog, error)
// ListCursor retrieves audit logs ordered by created_at descending (newest first) with cursor-based pagination
// and optional time-based filtering. If afterID is provided, returns logs with ID greater than afterID (UUIDv7 ordering).
// Accepts createdAtFrom and createdAtTo as optional filters (nil means no filter). Both boundaries are inclusive (>= and <=).
// Accepts clientID as an optional filter (nil means no filter).
// All timestamps are expected in UTC. Returns empty slice if no audit logs found. Limit is pre-validated (1-1000).
ListCursor(
ctx context.Context,
afterID *uuid.UUID,
limit int,
createdAtFrom, createdAtTo *time.Time,
clientID *uuid.UUID,
) ([]*AuditLog, error)
// DeleteOlderThan removes audit logs with created_at before the specified timestamp.
// When dryRun is true, returns count via SELECT COUNT(*) without deletion. When false,
// executes DELETE and returns affected rows. Supports transaction-aware operations via
// context propagation. All timestamps are expected in UTC.
DeleteOlderThan(ctx context.Context, olderThan time.Time, dryRun bool) (int64, error)
}
AuditLogRepository defines persistence operations for audit logs. Implementations must support transaction-aware operations via context propagation.
type Capability ¶
type Capability string
Capability defines the types of operations that can be performed on resources. Capabilities are used in policy documents to control client authorization.
const ( // ReadCapability allows reading resource data. ReadCapability Capability = "read" // WriteCapability allows creating or updating resource data. WriteCapability Capability = "write" // DeleteCapability allows removing resource data. DeleteCapability Capability = "delete" // EncryptCapability allows encrypting data using cryptographic keys. EncryptCapability Capability = "encrypt" // DecryptCapability allows decrypting data using cryptographic keys. DecryptCapability Capability = "decrypt" // RotateCapability allows rotating cryptographic keys. RotateCapability Capability = "rotate" )
func ValidCapabilities ¶ added in v0.28.0
func ValidCapabilities() []Capability
ValidCapabilities returns a list of all defined capabilities in the system. Used for validation and UI generation.
type Client ¶
type Client struct {
ID uuid.UUID // Unique identifier (UUIDv7)
Secret string //nolint:gosec // hashed client secret (not plaintext)
Name string // Human-readable client name
IsActive bool // Whether the client can authenticate
Policies []PolicyDocument // Authorization policies for this client
FailedAttempts int // Number of consecutive failed authentication attempts
LockedUntil *time.Time // Time until which the client is locked (nil if not locked)
CreatedAt time.Time
}
Client represents an authentication client with associated authorization policies. Clients are used to authenticate API requests and enforce access control.
func (*Client) AttemptLogin ¶ added in v0.29.0
func (c *Client) AttemptLogin( plain string, compare func(plain, hashed string) bool, policy LockoutPolicy, now time.Time, ) LoginOutcome
AttemptLogin runs the login state machine for this client and returns the outcome.
Order of checks:
- If LockedUntil is in the future, return Locked (attempt is not counted).
- If the client is not active, return Inactive (attempt is not counted).
- If compare returns false, return BadSecret with incremented attempts and, when MaxAttempts > 0 and the threshold is reached, a fresh LockedUntil.
- Otherwise return Authenticated with FailedAttempts reset to 0 and LockedUntil nil.
The receiver is not mutated; the outcome is the single source of truth for the new (FailedAttempts, LockedUntil) tuple. The caller persists it.
func (*Client) IsAllowed ¶
func (c *Client) IsAllowed(path string, capability Capability) bool
IsAllowed checks if the client's policies permit the given capability on the specified path. Uses case-sensitive path matching with wildcard support. Returns true if any policy matches the path and includes the capability.
Wildcard patterns:
- "*" matches everything (admin mode)
- "secret/*" matches any path starting with "secret/" (trailing wildcard - greedy)
- "/v1/keys/*/rotate" matches "/v1/keys/payment/rotate" (single-segment wildcard)
- "/v1/*/keys/*/rotate" matches "/v1/transit/keys/payment/rotate" (multiple wildcards)
Path matching rules:
- Exact match: "secret" matches only "secret"
- Trailing wildcard: "secret/*" matches "secret/app", "secret/app/db", etc.
- Mid-path wildcard: "/v1/keys/*/rotate" matches exactly 4 segments with 3rd being any value
- Case-sensitive: "Secret" does NOT match "secret"
type ClientRepository ¶ added in v0.29.0
type ClientRepository interface {
// Create stores a new client in the repository.
Create(ctx context.Context, client *Client) error
// Update modifies an existing client in the repository.
Update(ctx context.Context, client *Client) error
// Get retrieves a client by ID. Returns ErrClientNotFound if not found.
Get(ctx context.Context, clientID uuid.UUID) (*Client, error)
// ListCursor retrieves clients ordered by ID descending (newest first) with cursor-based pagination.
// If afterID is provided, returns clients with ID less than afterID (DESC order).
// Returns empty slice if no clients found. Limit is pre-validated (1-1000).
ListCursor(ctx context.Context, afterID *uuid.UUID, limit int) ([]*Client, error)
// UpdateLockState atomically updates the failed attempt counter and lock expiry.
// Pass failedAttempts=0 and lockedUntil=nil to reset the lock on successful auth.
UpdateLockState(ctx context.Context, clientID uuid.UUID, failedAttempts int, lockedUntil *time.Time) error
}
ClientRepository defines persistence operations for authentication clients. Implementations must support transaction-aware operations via context propagation.
type CreateClientInput ¶
type CreateClientInput struct {
Name string // Human-readable name for identifying the client
IsActive bool // Whether the client can authenticate immediately after creation
Policies []PolicyDocument // Authorization policies defining resource access permissions
}
CreateClientInput contains the parameters for creating a new authentication client. The client secret will be automatically generated and cannot be specified by the caller.
type CreateClientOutput ¶
type CreateClientOutput struct {
ID uuid.UUID // Unique identifier for the created client (UUIDv7)
PlainSecret string // Plain text secret for authentication (transmit securely, never log)
Name string // Human-readable client name
IsActive bool // Whether the client can authenticate
Policies []PolicyDocument // Authorization policies for this client
CreatedAt time.Time // Creation timestamp
}
CreateClientOutput contains the result of creating a new client. SECURITY: The PlainSecret is only returned once and must be securely transmitted to the client. It will never be retrievable again after this response.
type Decision ¶ added in v0.29.0
type Decision int
Decision is the outcome variant of Client.AttemptLogin.
const ( // DecisionAuthenticated indicates the secret matched on an active, unlocked client. DecisionAuthenticated Decision = iota // DecisionBadSecret indicates the client exists and is active, but the secret did not match. DecisionBadSecret // DecisionLocked indicates the client is within an active lockout window; no attempt was counted. DecisionLocked // DecisionInactive indicates the client has IsActive=false; no attempt was counted. DecisionInactive )
type IssueTokenInput ¶
type IssueTokenInput struct {
ClientID uuid.UUID
ClientSecret string //nolint:gosec // authentication credential field
}
IssueTokenInput contains client credentials for token issuance requests. Used during authentication to verify client identity before generating tokens.
type IssueTokenOutput ¶
IssueTokenOutput contains the newly issued authentication token and expiration. The PlainToken is only returned once and must be transmitted securely to the client.
type LockoutPolicy ¶ added in v0.29.0
LockoutPolicy configures how Client.AttemptLogin reacts to failures. MaxAttempts of zero disables locking; the counter is still incremented for visibility.
type LoginOutcome ¶ added in v0.29.0
LoginOutcome is the result of Client.AttemptLogin. It carries the post-attempt state the caller must persist (FailedAttempts, LockedUntil); the Client receiver is not mutated.
type PolicyDocument ¶
type PolicyDocument struct {
Path string `json:"path"` // Resource path pattern (supports "*" and "/*" wildcards)
Capabilities []Capability `json:"capabilities"` // List of allowed operations on the resource
}
PolicyDocument defines access control rules for a specific resource path. Policies use prefix matching with wildcard support for flexible authorization.
type Token ¶
type Token struct {
ID uuid.UUID // Unique identifier (UUIDv7)
TokenHash string // SHA-256 hash of the token string
ClientID uuid.UUID // ID of the client that owns this token
ExpiresAt time.Time // Token expiration timestamp
RevokedAt *time.Time // Token revocation timestamp (nil if active)
CreatedAt time.Time
}
Token represents an authentication token with expiration and revocation support. Tokens are stored as hashes and associated with a client for authentication.
type TokenRepository ¶ added in v0.29.0
type TokenRepository interface {
// Create stores a new token in the repository.
Create(ctx context.Context, token *Token) error
// Update modifies an existing token in the repository.
Update(ctx context.Context, token *Token) error
// Get retrieves a token by ID. Returns ErrTokenNotFound if not found.
Get(ctx context.Context, tokenID uuid.UUID) (*Token, error)
// GetByTokenHash retrieves a token by its SHA-256 hash value.
// Returns ErrTokenNotFound if no token matches the hash.
GetByTokenHash(ctx context.Context, tokenHash string) (*Token, error)
// RevokeByTokenID marks a specific token as revoked by setting its revoked_at timestamp.
RevokeByTokenID(ctx context.Context, tokenID uuid.UUID) error
// RevokeByClientID marks all active tokens for a specific client as revoked.
RevokeByClientID(ctx context.Context, clientID uuid.UUID) error
// PurgeExpiredAndRevoked permanently deletes tokens that are either expired or revoked
// and were created before the specified timestamp. Returns the number of deleted tokens.
PurgeExpiredAndRevoked(ctx context.Context, olderThan time.Time) (int64, error)
}
TokenRepository defines persistence operations for authentication tokens. Implementations must support transaction-aware operations via context propagation.
type UpdateClientInput ¶
type UpdateClientInput struct {
Name string // Updated human-readable name
IsActive bool // Updated active status (false prevents authentication)
Policies []PolicyDocument // Updated authorization policies
}
UpdateClientInput contains the mutable fields for updating an existing client. The client ID and secret cannot be modified through updates.