admin

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Feb 12, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

View Source
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.

View Source
const (
	ResourceTypeAdmin         = "admin"
	ResourceTypeAgent         = "agent"
	ResourceTypeToken         = "token"
	ResourceTypeJob           = "job"
	ResourceTypeTargetMapping = "target_mapping"
)

Resource type constants.

View Source
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
)
View Source
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

View Source
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

func DeriveNameFromEmail(email string) string

DeriveNameFromEmail derives a display name from an email address. E.g., "john.doe@example.com" -> "John Doe"

func ExtractAPIKeyPrefix

func ExtractAPIKeyPrefix(rawKey string) string

ExtractAPIKeyPrefix extracts the prefix from a raw API key for lookup. Returns empty string if the key format is invalid.

func GenerateAPIKey

func GenerateAPIKey() (string, error)

GenerateAPIKey generates a new API key. Returns the raw key string.

func HashAPIKeyBcrypt

func HashAPIKeyBcrypt(rawKey string) (string, error)

HashAPIKeyBcrypt hashes a raw API key using bcrypt. This should be used for new keys; existing SHA-256 hashes are verified differently.

func IsAdminAlreadyExists

func IsAdminAlreadyExists(err error) bool

IsAdminAlreadyExists checks if the error indicates an admin already exists.

func IsAdminInactive

func IsAdminInactive(err error) bool

IsAdminInactive checks if the error indicates an inactive admin.

func IsAdminNotFound

func IsAdminNotFound(err error) bool

IsAdminNotFound checks if the error indicates an admin was not found.

func IsAuditLogNotFound

func IsAuditLogNotFound(err error) bool

IsAuditLogNotFound checks if the error indicates an audit log was not found.

func IsAuthError

func IsAuthError(err error) bool

IsAuthError checks if the error is an authentication error.

func IsAuthorizationError

func IsAuthorizationError(err error) bool

IsAuthorizationError checks if the error is an authorization error.

func IsInvalidAPIKey

func IsInvalidAPIKey(err error) bool

IsInvalidAPIKey checks if the error indicates an invalid API key.

func IsSelfModificationError

func IsSelfModificationError(err error) bool

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

func (r AdminRole) CanCancelJobs() bool

CanCancelJobs checks if this role can cancel platform jobs.

func (AdminRole) CanManageAdmins

func (r AdminRole) CanManageAdmins() bool

CanManageAdmins checks if this role can manage other admin users.

func (AdminRole) CanManageAgents

func (r AdminRole) CanManageAgents() bool

CanManageAgents checks if this role can manage platform agents.

func (AdminRole) CanManageTokens

func (r AdminRole) CanManageTokens() bool

CanManageTokens checks if this role can manage bootstrap tokens.

func (AdminRole) CanViewAuditLogs

func (r AdminRole) CanViewAuditLogs() bool

CanViewAuditLogs checks if this role can view audit logs.

func (AdminRole) DisplayName

func (r AdminRole) DisplayName() string

DisplayName returns a human-readable name for the role.

func (AdminRole) IsValid

func (r AdminRole) IsValid() bool

IsValid checks if the admin role is valid.

func (AdminRole) String

func (r AdminRole) String() string

String returns the string representation of 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

func (a *AdminUser) APIKeyHash() string

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

func (a *AdminUser) APIKeyPrefix() string

APIKeyPrefix returns the API key prefix for identification in logs.

func (*AdminUser) Activate

func (a *AdminUser) Activate()

Activate activates the admin user.

func (*AdminUser) CanAuthenticate

func (a *AdminUser) CanAuthenticate() bool

CanAuthenticate checks if this admin can authenticate. Returns false if the account is locked or inactive.

func (*AdminUser) CreatedAt

func (a *AdminUser) CreatedAt() time.Time

CreatedAt returns when the admin user was created.

func (*AdminUser) CreatedBy

func (a *AdminUser) CreatedBy() *shared.ID

CreatedBy returns who created this admin user.

func (*AdminUser) Deactivate

func (a *AdminUser) Deactivate()

Deactivate deactivates the admin user.

func (*AdminUser) Email

func (a *AdminUser) Email() string

Email returns the admin user's email.

func (*AdminUser) FailedLoginCount

func (a *AdminUser) FailedLoginCount() int

FailedLoginCount returns the current failed login count.

func (*AdminUser) HasPermission

func (a *AdminUser) HasPermission(action string) bool

HasPermission checks if the admin has permission for a specific action.

func (*AdminUser) ID

func (a *AdminUser) ID() shared.ID

ID returns the admin user's ID.

func (*AdminUser) IsActive

func (a *AdminUser) IsActive() bool

IsActive returns whether the admin user is active.

func (*AdminUser) IsLocked

func (a *AdminUser) IsLocked() bool

IsLocked checks if the account is currently locked due to failed login attempts.

func (*AdminUser) LastFailedLoginAt

func (a *AdminUser) LastFailedLoginAt() *time.Time

LastFailedLoginAt returns when the last failed login occurred.

func (*AdminUser) LastFailedLoginIP

func (a *AdminUser) LastFailedLoginIP() string

LastFailedLoginIP returns the IP of the last failed login attempt.

func (*AdminUser) LastUsedAt

func (a *AdminUser) LastUsedAt() *time.Time

LastUsedAt returns when the admin user last used their API key.

func (*AdminUser) LastUsedIP

func (a *AdminUser) LastUsedIP() string

LastUsedIP returns the IP from which the admin user last used their API key.

func (*AdminUser) LockedUntil

func (a *AdminUser) LockedUntil() *time.Time

LockedUntil returns when the account lockout expires (nil if not locked).

func (*AdminUser) LockoutRemainingTime

func (a *AdminUser) LockoutRemainingTime() time.Duration

LockoutRemainingTime returns the remaining lockout time, or 0 if not locked.

func (*AdminUser) Name

func (a *AdminUser) Name() string

Name returns the admin user's name.

func (*AdminUser) RecordFailedLogin

func (a *AdminUser) RecordFailedLogin(ip string)

RecordFailedLogin records a failed login attempt and locks the account if necessary.

func (*AdminUser) RecordUsage

func (a *AdminUser) RecordUsage(ip string)

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) Role

func (a *AdminUser) Role() AdminRole

Role returns the admin user's role.

func (*AdminUser) RotateAPIKey

func (a *AdminUser) RotateAPIKey() (string, error)

RotateAPIKey generates a new API key for the admin user. Returns the new raw API key (only shown once!).

func (*AdminUser) UpdateEmail

func (a *AdminUser) UpdateEmail(email string) error

UpdateEmail updates the admin user's email.

func (*AdminUser) UpdateName

func (a *AdminUser) UpdateName(name string) error

UpdateName updates the admin user's name.

func (*AdminUser) UpdateRole

func (a *AdminUser) UpdateRole(role AdminRole) error

UpdateRole updates the admin user's role.

func (*AdminUser) UpdatedAt

func (a *AdminUser) UpdatedAt() time.Time

UpdatedAt returns when the admin user was last updated.

func (*AdminUser) VerifyAPIKey

func (a *AdminUser) VerifyAPIKey(rawKey string) bool

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

func (a *AuditLog) SetContext(ip, userAgent string)

SetContext sets the request context (IP, user agent).

func (*AuditLog) SetError

func (a *AuditLog) SetError(message string)

SetError marks the audit log as failed with an error message.

func (*AuditLog) SetRequest

func (a *AuditLog) SetRequest(method, path string, body map[string]interface{})

SetRequest sets the request details.

func (*AuditLog) SetResponse

func (a *AuditLog) SetResponse(status int)

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.

Jump to

Keyboard shortcuts

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