Documentation
¶
Overview ¶
Package admin defines the AdminUser domain entity for platform administration. Admin users are platform operators (NOT tenant users) with API key authentication and role-based access control for managing platform agents, bootstrap tokens, and other platform-level resources.
Index ¶
- Constants
- Variables
- func DeriveNameFromEmail(email string) string
- func ExtractAPIKeyPrefix(rawKey string) string
- func GenerateAPIKey() (string, error)
- func HashAPIKeyBcrypt(rawKey string) (string, error)
- func IsAdminAlreadyExists(err error) bool
- func IsAdminInactive(err error) bool
- func IsAdminNotFound(err error) bool
- func IsAuditLogNotFound(err error) bool
- func IsAuthError(err error) bool
- func IsAuthorizationError(err error) bool
- func IsInvalidAPIKey(err error) bool
- func IsSelfModificationError(err error) bool
- type AdminRole
- func (r AdminRole) CanCancelJobs() bool
- func (r AdminRole) CanManageAdmins() bool
- func (r AdminRole) CanManageAgents() bool
- func (r AdminRole) CanManageTokens() bool
- func (r AdminRole) CanViewAuditLogs() bool
- func (r AdminRole) DisplayName() string
- func (r AdminRole) IsValid() bool
- func (r AdminRole) String() string
- type AdminUser
- func (a *AdminUser) APIKeyHash() string
- func (a *AdminUser) APIKeyPrefix() string
- func (a *AdminUser) Activate()
- func (a *AdminUser) CanAuthenticate() bool
- func (a *AdminUser) CreatedAt() time.Time
- func (a *AdminUser) CreatedBy() *shared.ID
- func (a *AdminUser) Deactivate()
- func (a *AdminUser) Email() string
- func (a *AdminUser) FailedLoginCount() int
- func (a *AdminUser) HasPermission(action string) bool
- func (a *AdminUser) ID() shared.ID
- func (a *AdminUser) IsActive() bool
- func (a *AdminUser) IsLocked() bool
- func (a *AdminUser) LastFailedLoginAt() *time.Time
- func (a *AdminUser) LastFailedLoginIP() string
- func (a *AdminUser) LastUsedAt() *time.Time
- func (a *AdminUser) LastUsedIP() string
- func (a *AdminUser) LockedUntil() *time.Time
- func (a *AdminUser) LockoutRemainingTime() time.Duration
- func (a *AdminUser) Name() string
- func (a *AdminUser) RecordFailedLogin(ip string)
- func (a *AdminUser) RecordUsage(ip string)
- func (a *AdminUser) ResetFailedLogins()
- func (a *AdminUser) Role() AdminRole
- func (a *AdminUser) RotateAPIKey() (string, error)
- func (a *AdminUser) UpdateEmail(email string) error
- func (a *AdminUser) UpdateName(name string) error
- func (a *AdminUser) UpdateRole(role AdminRole) error
- func (a *AdminUser) UpdatedAt() time.Time
- func (a *AdminUser) VerifyAPIKey(rawKey string) bool
- type AuditLog
- type AuditLogBuilder
- func (b *AuditLogBuilder) Build() *AuditLog
- func (b *AuditLogBuilder) Context(ip, userAgent string) *AuditLogBuilder
- func (b *AuditLogBuilder) Error(message string) *AuditLogBuilder
- func (b *AuditLogBuilder) Request(method, path string, body map[string]interface{}) *AuditLogBuilder
- func (b *AuditLogBuilder) Resource(resourceType string, resourceID *shared.ID, resourceName string) *AuditLogBuilder
- func (b *AuditLogBuilder) Response(status int) *AuditLogBuilder
- type AuditLogFilter
- type AuditLogRepository
- type Filter
- type Repository
Constants ¶
const ( // Admin user actions AuditActionAdminCreate = "admin.create" AuditActionAdminUpdate = "admin.update" AuditActionAdminDelete = "admin.delete" AuditActionAdminActivate = "admin.activate" AuditActionAdminDeactivate = "admin.deactivate" AuditActionAdminRotateKey = "admin.rotate_key" // Platform agent actions AuditActionAgentCreate = "agent.create" AuditActionAgentUpdate = "agent.update" AuditActionAgentDelete = "agent.delete" AuditActionAgentEnable = "agent.enable" AuditActionAgentDisable = "agent.disable" // Bootstrap token actions AuditActionTokenCreate = "token.create" AuditActionTokenRevoke = "token.revoke" AuditActionTokenDelete = "token.delete" // Platform job actions AuditActionJobCancel = "job.cancel" // Target mapping actions AuditActionTargetMappingCreate = "target_mapping.create" AuditActionTargetMappingUpdate = "target_mapping.update" AuditActionTargetMappingDelete = "target_mapping.delete" // Authentication actions AuditActionAuthSuccess = "auth.success" AuditActionAuthFailure = "auth.failure" )
Audit action constants for consistent naming.
const ( ResourceTypeAdmin = "admin" ResourceTypeAgent = "agent" ResourceTypeToken = "token" ResourceTypeJob = "job" ResourceTypeTargetMapping = "target_mapping" )
Resource type constants.
const ( // APIKeyLength is the length of generated API keys in bytes (31 bytes = 248 bits). // Combined with "oc-admin-" prefix (9 bytes), total is 71 bytes which fits // within bcrypt's 72-byte input limit (Go 1.24+ enforces this strictly). APIKeyLength = 31 // APIKeyPrefix is the prefix for admin API keys. APIKeyPrefix = "oc-admin-" // BcryptCost is the bcrypt cost factor for API key hashing. // Cost of 12 provides good security while keeping auth under 1 second. BcryptCost = 12 )
const ( // MaxFailedLoginAttempts is the maximum number of failed login attempts before lockout. MaxFailedLoginAttempts = 10 // LockoutDuration is the duration for which an account is locked after too many failed attempts. LockoutDuration = 30 * time.Minute )
Variables ¶
var ( // ErrAdminNotFound is returned when an admin user is not found. ErrAdminNotFound = fmt.Errorf("%w: admin user not found", shared.ErrNotFound) // ErrAdminAlreadyExists is returned when an admin with the same email exists. ErrAdminAlreadyExists = fmt.Errorf("%w: admin user with this email already exists", shared.ErrAlreadyExists) // ErrInvalidAPIKey is returned when the API key is invalid. ErrInvalidAPIKey = fmt.Errorf("%w: invalid admin API key", shared.ErrUnauthorized) // ErrAdminInactive is returned when the admin user is inactive. ErrAdminInactive = fmt.Errorf("%w: admin user is inactive", shared.ErrForbidden) // ErrInsufficientRole is returned when the admin lacks required permissions. ErrInsufficientRole = fmt.Errorf("%w: insufficient role permissions", shared.ErrForbidden) // ErrCannotDeleteSelf is returned when an admin tries to delete themselves. ErrCannotDeleteSelf = fmt.Errorf("%w: cannot delete your own admin account", shared.ErrForbidden) // ErrCannotDeactivateSelf is returned when an admin tries to deactivate themselves. ErrCannotDeactivateSelf = fmt.Errorf("%w: cannot deactivate your own admin account", shared.ErrForbidden) // ErrCannotDemoteSelf is returned when an admin tries to demote themselves. ErrCannotDemoteSelf = fmt.Errorf("%w: cannot demote your own admin account", shared.ErrForbidden) // ErrLastSuperAdmin is returned when trying to remove the last super admin. ErrLastSuperAdmin = fmt.Errorf("%w: cannot remove the last super admin", shared.ErrForbidden) // ErrAuditLogNotFound is returned when an audit log is not found. ErrAuditLogNotFound = fmt.Errorf("%w: audit log not found", shared.ErrNotFound) )
Domain errors for admin operations.
Functions ¶
func DeriveNameFromEmail ¶
DeriveNameFromEmail derives a display name from an email address. E.g., "john.doe@example.com" -> "John Doe"
func ExtractAPIKeyPrefix ¶
ExtractAPIKeyPrefix extracts the prefix from a raw API key for lookup. Returns empty string if the key format is invalid.
func GenerateAPIKey ¶
GenerateAPIKey generates a new API key. Returns the raw key string.
func HashAPIKeyBcrypt ¶
HashAPIKeyBcrypt hashes a raw API key using bcrypt. This should be used for new keys; existing SHA-256 hashes are verified differently.
func IsAdminAlreadyExists ¶
IsAdminAlreadyExists checks if the error indicates an admin already exists.
func IsAdminInactive ¶
IsAdminInactive checks if the error indicates an inactive admin.
func IsAdminNotFound ¶
IsAdminNotFound checks if the error indicates an admin was not found.
func IsAuditLogNotFound ¶
IsAuditLogNotFound checks if the error indicates an audit log was not found.
func IsAuthError ¶
IsAuthError checks if the error is an authentication error.
func IsAuthorizationError ¶
IsAuthorizationError checks if the error is an authorization error.
func IsInvalidAPIKey ¶
IsInvalidAPIKey checks if the error indicates an invalid API key.
func IsSelfModificationError ¶
IsSelfModificationError checks if the error is a self-modification error.
Types ¶
type AdminRole ¶
type AdminRole string
AdminRole represents the role of an admin user. Follows simple RBAC with three levels.
const ( // AdminRoleSuperAdmin has full access to all platform operations. // Can manage other admin users. AdminRoleSuperAdmin AdminRole = "super_admin" // AdminRoleOpsAdmin can manage agents, tokens, and view audit logs. // Cannot manage other admin users. AdminRoleOpsAdmin AdminRole = "ops_admin" // AdminRoleReadonly can only view platform resources. // No write/modify operations. AdminRoleReadonly AdminRole = "readonly" )
const RoleViewer AdminRole = AdminRoleReadonly
RoleViewer is an alias for AdminRoleReadonly for API compatibility
func (AdminRole) CanCancelJobs ¶
CanCancelJobs checks if this role can cancel platform jobs.
func (AdminRole) CanManageAdmins ¶
CanManageAdmins checks if this role can manage other admin users.
func (AdminRole) CanManageAgents ¶
CanManageAgents checks if this role can manage platform agents.
func (AdminRole) CanManageTokens ¶
CanManageTokens checks if this role can manage bootstrap tokens.
func (AdminRole) CanViewAuditLogs ¶
CanViewAuditLogs checks if this role can view audit logs.
func (AdminRole) DisplayName ¶
DisplayName returns a human-readable name for the role.
type AdminUser ¶
type AdminUser struct {
// contains filtered or unexported fields
}
AdminUser represents a platform administrator. Uses private fields for sensitive data with controlled access via getters.
func NewAdminUser ¶
func NewAdminUser(email, name string, role AdminRole, createdBy *shared.ID) (*AdminUser, string, error)
NewAdminUser creates a new AdminUser entity with a generated API key. Returns the admin user, the raw API key (only shown once!), and any error. The raw API key must be securely transmitted to the admin and never stored.
func Reconstitute ¶
func Reconstitute( id shared.ID, email, name string, apiKeyHash, apiKeyPrefix string, role AdminRole, isActive bool, lastUsedAt *time.Time, lastUsedIP string, failedLoginCount int, lockedUntil *time.Time, lastFailedLoginAt *time.Time, lastFailedLoginIP string, createdAt time.Time, createdBy *shared.ID, updatedAt time.Time, ) *AdminUser
Reconstitute creates an AdminUser from database values (no validation). Used when loading from the database.
func (*AdminUser) APIKeyHash ¶
APIKeyHash returns the API key hash for database storage. NOTE: This is needed for repository operations but should NOT be exposed via API.
func (*AdminUser) APIKeyPrefix ¶
APIKeyPrefix returns the API key prefix for identification in logs.
func (*AdminUser) CanAuthenticate ¶
CanAuthenticate checks if this admin can authenticate. Returns false if the account is locked or inactive.
func (*AdminUser) Deactivate ¶
func (a *AdminUser) Deactivate()
Deactivate deactivates the admin user.
func (*AdminUser) FailedLoginCount ¶
FailedLoginCount returns the current failed login count.
func (*AdminUser) HasPermission ¶
HasPermission checks if the admin has permission for a specific action.
func (*AdminUser) IsLocked ¶
IsLocked checks if the account is currently locked due to failed login attempts.
func (*AdminUser) LastFailedLoginAt ¶
LastFailedLoginAt returns when the last failed login occurred.
func (*AdminUser) LastFailedLoginIP ¶
LastFailedLoginIP returns the IP of the last failed login attempt.
func (*AdminUser) LastUsedAt ¶
LastUsedAt returns when the admin user last used their API key.
func (*AdminUser) LastUsedIP ¶
LastUsedIP returns the IP from which the admin user last used their API key.
func (*AdminUser) LockedUntil ¶
LockedUntil returns when the account lockout expires (nil if not locked).
func (*AdminUser) LockoutRemainingTime ¶
LockoutRemainingTime returns the remaining lockout time, or 0 if not locked.
func (*AdminUser) RecordFailedLogin ¶
RecordFailedLogin records a failed login attempt and locks the account if necessary.
func (*AdminUser) RecordUsage ¶
RecordUsage records API key usage (IP and timestamp).
func (*AdminUser) ResetFailedLogins ¶
func (a *AdminUser) ResetFailedLogins()
ResetFailedLogins resets the failed login counter (called on successful login).
func (*AdminUser) RotateAPIKey ¶
RotateAPIKey generates a new API key for the admin user. Returns the new raw API key (only shown once!).
func (*AdminUser) UpdateEmail ¶
UpdateEmail updates the admin user's email.
func (*AdminUser) UpdateName ¶
UpdateName updates the admin user's name.
func (*AdminUser) UpdateRole ¶
UpdateRole updates the admin user's role.
func (*AdminUser) VerifyAPIKey ¶
VerifyAPIKey verifies if the provided raw API key matches this admin's key. Uses bcrypt comparison which is constant-time by design.
type AuditLog ¶
type AuditLog struct {
ID shared.ID
// Who performed the action
AdminID *shared.ID // May be nil if admin was deleted
AdminEmail string // Preserved even if admin is deleted
// What action was performed
Action string // e.g., "agent.create", "token.revoke"
ResourceType string // e.g., "agent", "token", "admin"
ResourceID *shared.ID // ID of affected resource
ResourceName string // Name for display (preserved if resource deleted)
// Request details (sanitized - no secrets)
RequestMethod string
RequestPath string
RequestBody map[string]interface{} // Sensitive fields should be redacted
// Response
ResponseStatus int
// Context
IPAddress string
UserAgent string
// Result
Success bool
ErrorMessage string
// Timestamp (immutable)
CreatedAt time.Time
}
AuditLog represents an immutable audit log entry for admin actions. Once created, audit logs cannot be modified or deleted (append-only).
func NewAuditLog ¶
func NewAuditLog( admin *AdminUser, action string, resourceType string, resourceID *shared.ID, resourceName string, ) *AuditLog
NewAuditLog creates a new AuditLog entry.
func (*AuditLog) SetContext ¶
SetContext sets the request context (IP, user agent).
func (*AuditLog) SetRequest ¶
SetRequest sets the request details.
func (*AuditLog) SetResponse ¶
SetResponse sets the response status.
type AuditLogBuilder ¶
type AuditLogBuilder struct {
// contains filtered or unexported fields
}
AuditLogBuilder provides a fluent API for creating audit logs.
func NewAuditLogBuilder ¶
func NewAuditLogBuilder(admin *AdminUser, action string) *AuditLogBuilder
NewAuditLogBuilder creates a new AuditLogBuilder.
func (*AuditLogBuilder) Build ¶
func (b *AuditLogBuilder) Build() *AuditLog
Build returns the completed AuditLog.
func (*AuditLogBuilder) Context ¶
func (b *AuditLogBuilder) Context(ip, userAgent string) *AuditLogBuilder
Context sets the request context.
func (*AuditLogBuilder) Error ¶
func (b *AuditLogBuilder) Error(message string) *AuditLogBuilder
Error marks the log as failed.
func (*AuditLogBuilder) Request ¶
func (b *AuditLogBuilder) Request(method, path string, body map[string]interface{}) *AuditLogBuilder
Request sets the request details.
func (*AuditLogBuilder) Resource ¶
func (b *AuditLogBuilder) Resource(resourceType string, resourceID *shared.ID, resourceName string) *AuditLogBuilder
Resource sets the resource being acted upon.
func (*AuditLogBuilder) Response ¶
func (b *AuditLogBuilder) Response(status int) *AuditLogBuilder
Response sets the response status.
type AuditLogFilter ¶
type AuditLogFilter struct {
AdminID *shared.ID
AdminEmail string
Action string
ResourceType string
ResourceID *shared.ID
Success *bool
StartTime *time.Time
EndTime *time.Time
Search string // Search in action, resource_name, error_message
}
AuditLogFilter represents filter options for listing audit logs.
type AuditLogRepository ¶
type AuditLogRepository interface {
// Create creates a new audit log entry.
Create(ctx context.Context, log *AuditLog) error
// GetByID retrieves an audit log by ID.
GetByID(ctx context.Context, id shared.ID) (*AuditLog, error)
// List lists audit logs with filters and pagination.
// Results are ordered by created_at DESC (newest first).
List(ctx context.Context, filter AuditLogFilter, page pagination.Pagination) (pagination.Result[*AuditLog], error)
// ListByAdmin lists audit logs for a specific admin.
ListByAdmin(ctx context.Context, adminID shared.ID, page pagination.Pagination) (pagination.Result[*AuditLog], error)
// ListByResource lists audit logs for a specific resource.
ListByResource(ctx context.Context, resourceType string, resourceID shared.ID, page pagination.Pagination) (pagination.Result[*AuditLog], error)
// Count counts audit logs with optional filter.
Count(ctx context.Context, filter AuditLogFilter) (int64, error)
// GetRecentActions returns the most recent actions (for dashboard).
GetRecentActions(ctx context.Context, limit int) ([]*AuditLog, error)
// GetFailedActions returns recent failed actions (for monitoring).
GetFailedActions(ctx context.Context, since time.Duration, limit int) ([]*AuditLog, error)
// DeleteOlderThan deletes audit logs older than the specified time.
// This is used for compliance-based retention policies.
// Returns the number of logs deleted.
// Note: This is a destructive operation. Ensure proper backups before running.
DeleteOlderThan(ctx context.Context, olderThan time.Time) (int64, error)
// CountOlderThan counts audit logs older than the specified time.
// Used to estimate the number of logs that will be deleted.
CountOlderThan(ctx context.Context, olderThan time.Time) (int64, error)
}
AuditLogRepository defines the interface for audit log persistence. Audit logs are append-only (no update or delete operations).
type Filter ¶
type Filter struct {
Role *AdminRole
IsActive *bool
Email string // Partial match
Search string // Search in email and name
}
Filter represents filter options for listing admin users.
type Repository ¶
type Repository interface {
// Create creates a new admin user.
Create(ctx context.Context, admin *AdminUser) error
// GetByID retrieves an admin user by ID.
GetByID(ctx context.Context, id shared.ID) (*AdminUser, error)
// GetByEmail retrieves an admin user by email.
GetByEmail(ctx context.Context, email string) (*AdminUser, error)
// GetByAPIKeyPrefix retrieves an admin user by API key prefix.
// Used as the first step in API key authentication (fast lookup).
GetByAPIKeyPrefix(ctx context.Context, prefix string) (*AdminUser, error)
// List lists admin users with filters and pagination.
List(ctx context.Context, filter Filter, page pagination.Pagination) (pagination.Result[*AdminUser], error)
// Update updates an admin user.
Update(ctx context.Context, admin *AdminUser) error
// Delete deletes an admin user.
Delete(ctx context.Context, id shared.ID) error
// AuthenticateByAPIKey authenticates an admin user by raw API key.
// This is the complete authentication flow:
// 1. Extract prefix from raw key
// 2. Look up admin by prefix (fast indexed lookup)
// 3. Verify full hash (constant-time comparison)
// 4. Check if admin is active
// Returns the admin user if authentication succeeds.
AuthenticateByAPIKey(ctx context.Context, rawKey string) (*AdminUser, error)
// RecordUsage records API key usage (IP and timestamp).
RecordUsage(ctx context.Context, id shared.ID, ip string) error
// Count counts admin users with optional filter.
Count(ctx context.Context, filter Filter) (int, error)
// CountByRole counts admin users by role.
CountByRole(ctx context.Context, role AdminRole) (int, error)
}
Repository defines the interface for admin user persistence.