Documentation
¶
Overview ¶
Package admin provides role-based access control (RBAC) and administrative user management.
This plugin extends the auth.User model with:
- Role management (admin role assignment)
- User ban/unban functionality with expiry dates
- Platform statistics and analytics
- Administrative CRUD operations for user accounts
All admin endpoints require the 'admin' role via RequireAdminMiddleware.
Key Features:
- Role-based authorization (admin role)
- User ban management with reasons and expiry dates
- User enable/disable controls
- User listing with pagination
- Platform statistics
Database Extensions: Adds columns to the 'user' table:
- role (VARCHAR): User role (e.g., "admin")
- banned (BOOLEAN): Ban status
- ban_reason (TEXT): Ban reason text
- ban_expiry (TIMESTAMP): Ban expiration date (NULL for permanent)
- ban_counter (INTEGER): Number of times user has been banned
Route Structure:
- GET /admin/users - List all users (paginated)
- GET /admin/users/:id - Get user details
- POST /admin/users/:id/disable - Disable user account
- POST /admin/users/:id/enable - Enable user account
- DELETE /admin/users/:id - Delete user account
- POST /admin/users/:id/ban - Ban user with reason
- POST /admin/users/:id/unban - Unban user
- PUT /admin/users/:id/role - Update user role
- GET /admin/stats - Get platform statistics
Index ¶
- Constants
- func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)
- func GetSchema(dialect plugins.Dialect) (*plugins.Schema, error)
- func GetSchemaRequirements(dialect plugins.Dialect) []plugins.SchemaRequirement
- type BanUserRequest
- type DefaultAdminStore
- func (s *DefaultAdminStore) AssignRole(ctx context.Context, userID string, role string) error
- func (s *DefaultAdminStore) BanUser(ctx context.Context, userID, reason string, expiry *time.Time) error
- func (s *DefaultAdminStore) Count(ctx context.Context) (int, error)
- func (s *DefaultAdminStore) Create(ctx context.Context, user User) (User, error)
- func (s *DefaultAdminStore) Delete(ctx context.Context, id string) error
- func (s *DefaultAdminStore) GetByEmail(ctx context.Context, email string) (User, error)
- func (s *DefaultAdminStore) GetByID(ctx context.Context, id string) (User, error)
- func (s *DefaultAdminStore) GetRole(ctx context.Context, userID string) (string, error)
- func (s *DefaultAdminStore) GetStats(ctx context.Context) (StatsResponse, error)
- func (s *DefaultAdminStore) GetUserRaw(ctx context.Context, userID string) (map[string]any, error)
- func (s *DefaultAdminStore) List(ctx context.Context, offset, limit int) ([]User, error)
- func (s *DefaultAdminStore) ListUsersRaw(ctx context.Context, offset, limit int) ([]map[string]any, error)
- func (s *DefaultAdminStore) RemoveRole(ctx context.Context, userID string, _ string) error
- func (s *DefaultAdminStore) UnbanUser(ctx context.Context, userID string) error
- func (s *DefaultAdminStore) Update(ctx context.Context, user User) error
- type Plugin
- func (a *Plugin) AssignRole(ctx context.Context, userID string, role string) error
- func (a *Plugin) BanUser(ctx context.Context, userID, reason string, expiresAt *time.Time) error
- func (a *Plugin) DeleteUser(ctx context.Context, userID string) error
- func (a *Plugin) Dependencies() []plugins.Dependency
- func (a *Plugin) Description() string
- func (a *Plugin) DisableUser(ctx context.Context, userID string) error
- func (a *Plugin) EnableUser(ctx context.Context, userID string) error
- func (a *Plugin) EnrichUser(ctx context.Context, user *core.EnrichedUser) error
- func (a *Plugin) EnrichUserMiddleware() func(http.Handler) http.Handler
- func (a *Plugin) GetAdminUser(ctx context.Context, userID string) (User, error)
- func (a *Plugin) GetMigrations() []plugins.Migration
- func (a *Plugin) GetSchemas() []plugins.Schema
- func (a *Plugin) GetStats(ctx context.Context) (StatsResponse, error)
- func (a *Plugin) GetUser(ctx context.Context, userID string) (User, error)
- func (a *Plugin) GetUserRaw(ctx context.Context, userID string) (map[string]any, error)
- func (a *Plugin) GetUserRole(ctx context.Context, userID string) (string, error)
- func (a *Plugin) Init(ctx context.Context, aegis plugins.Aegis) error
- func (a *Plugin) ListUsers(ctx context.Context, offset, limit int) ([]User, error)
- func (a *Plugin) ListUsersRaw(ctx context.Context, offset, limit int) ([]map[string]any, error)
- func (a *Plugin) MountRoutes(r router.Router, prefix string)
- func (a *Plugin) Name() string
- func (a *Plugin) ProvidesAuthMethods() []string
- func (a *Plugin) RemoveRole(ctx context.Context, userID string, role string) error
- func (a *Plugin) RequireAdminMiddleware() func(http.Handler) http.Handler
- func (a *Plugin) RequireRoleMiddleware(requiredRole string) func(http.Handler) http.Handler
- func (a *Plugin) RequiresTables() []string
- func (a *Plugin) UnbanUser(ctx context.Context, userID string) error
- func (a *Plugin) Version() string
- type StatsResponse
- type Store
- type UpdateRoleRequest
- type User
- type UserListResponse
Constants ¶
const ( // ExtKeyRole is the key for user role in EnrichedUser extensions. // // Available as: // - In handlers: enriched.GetString("role") // - In middleware: plugins.GetUserExtensionString(ctx, ExtKeyRole) // - In JSON: {"role": "admin"} ExtKeyRole = "role" // ExtKeyPermissions is the key for user permissions in EnrichedUser extensions. // // Available as: // - In handlers: enriched.GetStringSlice("permissions") // - In JSON: {"permissions": [...]} // // Note: Currently not implemented - reserved for future use. ExtKeyPermissions = "permissions" // RoleAdmin is the default role for administrative users. RoleAdmin = "admin" )
Admin plugin context keys for EnrichedUser extensions.
These keys are used to store admin-specific data in the user context, making them available throughout the request lifecycle and in API responses.
Simple field names are used - they become top-level JSON fields in responses.
const ( // Request schemas SchemaBanUserRequest = "BanUserRequest" SchemaUpdateRoleRequest = "UpdateRoleRequest" // Response schemas SchemaAdminUser = "AdminUser" SchemaUserListResponse = "UserListResponse" SchemaAdminStats = "AdminStats" )
Schema names for OpenAPI specification generation.
Variables ¶
This section is empty.
Functions ¶
func GetMigrations ¶
GetMigrations returns all database migrations for the admin plugin.
This function loads migrations from embedded SQL files and returns them in version order. The initial schema is always treated as version 001.
Version Numbering:
- Version 001: Initial schema from internal/sql/<dialect>/schema.sql
- Version 002+: Additional migrations from migrations/<dialect>/<version>_<description>.<up|down>.sql
Migration File Format:
- Up migration: 002_add_ban_fields.up.sql
- Down migration: 002_add_ban_fields.down.sql
Parameters:
- dialect: Database dialect (postgres, mysql, sqlite)
Returns:
- []plugins.Migration: Sorted list of migrations (oldest first)
- error: If schema files cannot be read or parsed
func GetSchema ¶
GetSchema returns the database schema for the admin plugin.
The schema extends the 'user' table with admin-specific columns:
- role (VARCHAR): User role for RBAC (e.g., "admin", "moderator")
- banned (BOOLEAN): Ban status (true if user is banned)
- ban_reason (TEXT): Admin-provided reason for ban
- ban_expiry (TIMESTAMP, nullable): Ban expiration date (NULL for permanent)
- ban_counter (INTEGER): Number of times user has been banned
These extensions enable role-based authorization and ban management.
Parameters:
- dialect: Database dialect (postgres, mysql)
Returns:
- *plugins.Schema: Schema definition with SQL DDL
- error: If dialect is not supported
func GetSchemaRequirements ¶
func GetSchemaRequirements(dialect plugins.Dialect) []plugins.SchemaRequirement
GetSchemaRequirements returns schema validation requirements for the admin plugin.
This function defines structural requirements that must be satisfied for the plugin to function correctly. The Init() method validates these requirements at startup.
Validation Checks:
- Column existence: role, banned, ban_reason, ban_expiry, ban_counter in 'user' table
- Column properties: Data types, nullability (not fully implemented yet)
These checks help detect schema drift, incomplete migrations, or manual schema changes that could break admin functionality.
Parameters:
- dialect: Database dialect (postgres, mysql)
Returns:
- []plugins.SchemaRequirement: List of validation requirements
Types ¶
type BanUserRequest ¶
type BanUserRequest struct {
Reason string `json:"reason"` // Ban reason (required)
ExpiresAt *time.Time `json:"expiresAt,omitempty"` // Ban expiration (nil = permanent)
}
BanUserRequest represents a request to ban a user.
Validation:
- reason: Required, provides context for ban decision
- expiresAt: Optional, nil for permanent ban
Example (temporary ban):
{
"reason": "Spam",
"expiresAt": "2024-12-31T23:59:59Z"
}
Example (permanent ban):
{
"reason": "Terms of service violation",
"expiresAt": null
}
type DefaultAdminStore ¶
type DefaultAdminStore struct {
// contains filtered or unexported fields
}
DefaultAdminStore implements AdminStore using a SQL database.
This implementation uses sqlc-generated type-safe queries to manage users with admin-specific fields (role, ban status) in PostgreSQL, MySQL, or SQLite.
Database Schema Extensions: Adds columns to the 'user' table:
- role (VARCHAR): User role for RBAC
- banned (BOOLEAN): Ban status
- ban_reason (TEXT): Ban reason description
- ban_expiry (TIMESTAMP, nullable): Ban expiration date
- ban_counter (INTEGER): Number of bans (repeat offender tracking)
Thread Safety: Safe for concurrent use through database transactions.
func NewDefaultAdminStore ¶
func NewDefaultAdminStore(db *sql.DB) *DefaultAdminStore
NewDefaultAdminStore creates a new DefaultAdminStore backed by SQL.
The provided database connection must have the admin schema extensions applied.
Parameters:
- db: Active SQL database connection
Returns:
- *DefaultAdminStore: Configured store ready for use
func (*DefaultAdminStore) AssignRole ¶
AssignRole assigns a role to a user.
func (*DefaultAdminStore) BanUser ¶
func (s *DefaultAdminStore) BanUser(ctx context.Context, userID, reason string, expiry *time.Time) error
BanUser bans a user.
func (*DefaultAdminStore) Count ¶
func (s *DefaultAdminStore) Count(ctx context.Context) (int, error)
Count returns the total number of users.
func (*DefaultAdminStore) Delete ¶
func (s *DefaultAdminStore) Delete(ctx context.Context, id string) error
Delete removes a user.
func (*DefaultAdminStore) GetByEmail ¶
GetByEmail retrieves a user by email.
func (*DefaultAdminStore) GetStats ¶
func (s *DefaultAdminStore) GetStats(ctx context.Context) (StatsResponse, error)
GetStats retrieves system statistics.
func (*DefaultAdminStore) GetUserRaw ¶
GetUserRaw retrieves a single user as raw map data.
func (*DefaultAdminStore) ListUsersRaw ¶
func (s *DefaultAdminStore) ListUsersRaw(ctx context.Context, offset, limit int) ([]map[string]any, error)
ListUsersRaw lists users returning raw map data.
func (*DefaultAdminStore) RemoveRole ¶
RemoveRole removes a role from a user.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin provides role-based access control and administrative user management.
This plugin integrates with the core authentication system to provide:
- Plugin role verification via middleware
- User account management (enable/disable/delete)
- Ban management with expiry dates and reasons
- Platform statistics and analytics
The plugin implements plugins.UserEnricher to automatically add role information to authenticated users, making it available in API responses.
func New ¶
New creates a new Admin plugin instance.
Parameters:
- store: AdminStore implementation for database operations (can be nil, will use DefaultAdminStore)
- dialect: Optional database dialect (defaults to PostgreSQL)
Returns:
- *Admin: Configured admin plugin ready for initialization
Example:
admin := admin.New(nil, plugins.DialectPostgres) aegis.RegisterPlugin(admin)
func (*Plugin) AssignRole ¶
AssignRole assigns a role to a user programmatically.
func (*Plugin) DeleteUser ¶
DeleteUser permanently deletes a user account programmatically.
func (*Plugin) Dependencies ¶
func (a *Plugin) Dependencies() []plugins.Dependency
Dependencies returns the plugin dependencies
func (*Plugin) Description ¶
Description returns a human-readable description for logging and diagnostics.
func (*Plugin) DisableUser ¶
DisableUser disables a user account programmatically.
func (*Plugin) EnableUser ¶
EnableUser re-enables a user account programmatically.
func (*Plugin) EnrichUser ¶
EnrichUser implements plugins.UserEnricher to add admin-specific fields to user responses.
This method is called automatically by the authentication system after user lookup. It adds the user's role to the EnrichedUser, making it available in API responses without requiring separate queries.
Fields Added:
- "role" (string): User's role (e.g., "admin", empty if no role)
The enriched role is accessible via:
- In API responses: {"user": {"id": "...", "role": "admin", ...}}
- In middleware: plugins.GetUserExtensionString(ctx, ExtKeyRole)
- In handlers: enrichedUser.GetString("role")
Parameters:
- ctx: Request context
- user: EnrichedUser to populate with admin data
Returns:
- error: Always nil (role lookup failure is not an error)
func (*Plugin) EnrichUserMiddleware ¶
EnrichUserMiddleware fetches the user's role and adds it to the EnrichedUser.
This middleware should be used after RequireAuthMiddleware to add admin-specific data to the user context. The enriched data is automatically included in API responses.
Enrichment Process:
- Retrieve authenticated user from context
- Fetch user's role from database
- Add role to EnrichedUser via plugins.ExtendUser
- Role becomes available as {"role": "admin"} in JSON responses
Usage:
router.Use(auth.RequireAuthMiddleware()) router.Use(admin.EnrichUserMiddleware())
The role is then accessible via:
- core.GetUserExtensionString(ctx, "role")
- JSON responses: {"user": {"id": "...", "role": "admin", ...}}
func (*Plugin) GetAdminUser ¶
GetAdminUser retrieves a user with admin-specific information.
func (*Plugin) GetMigrations ¶
GetMigrations returns the plugin migrations
func (*Plugin) GetSchemas ¶
GetSchemas returns all schemas for all supported dialects
func (*Plugin) GetStats ¶
func (a *Plugin) GetStats(ctx context.Context) (StatsResponse, error)
GetStats returns platform statistics programmatically.
func (*Plugin) GetUser ¶
GetUser retrieves detailed information for a specific user programmatically.
func (*Plugin) GetUserRaw ¶
GetUserRaw retrieves detailed information for a specific user as raw map data programmatically.
func (*Plugin) GetUserRole ¶
GetUserRole retrieves the role of a user programmatically.
func (*Plugin) Init ¶
Init initializes the admin plugin and validates database schema.
Initialization Steps:
- Create DefaultAdminStore if custom store not provided
- Store session service reference for authentication middleware
- Build schema validation requirements (table + column checks)
- Validate admin schema extensions exist in database
Schema Validation: Checks for required columns in 'user' table:
- role: VARCHAR for role assignment
- banned, ban_reason, ban_expiry, ban_counter: Ban management fields
Parameters:
- ctx: Context for database operations
- aegis: Main Aegis instance providing DB access and services
Returns:
- error: If schema validation fails or initialization errors occur
func (*Plugin) ListUsersRaw ¶
ListUsersRaw lists all users as raw map data programmatically.
func (*Plugin) MountRoutes ¶
MountRoutes registers administrative management endpoints.
func (*Plugin) ProvidesAuthMethods ¶
ProvidesAuthMethods returns the provided auth methods
func (*Plugin) RemoveRole ¶
RemoveRole removes a role from a user programmatically.
func (*Plugin) RequireAdminMiddleware ¶
RequireAdminMiddleware ensures the user has the 'admin' role.
This middleware enforces admin-only access to protected routes. It checks for the admin role in two ways:
- First checks EnrichedUser context (if already enriched) - fast path
- Falls back to database lookup if not enriched - slow path
Authentication Flow:
- Retrieve authenticated user from context (via RequireAuthMiddleware)
- Check if role is already in EnrichedUser ("role" extension)
- If not found, fetch role from database
- Verify role is "admin"
- Enrich user context for subsequent handlers
Usage:
adminRouter := router.NewGroup("/admin")
adminRouter.Use(auth.RequireAuthMiddleware())
adminRouter.Use(admin.RequireAdminMiddleware())
Response Codes:
- 401 Unauthorized: User not authenticated
- 403 Forbidden: User authenticated but not admin
func (*Plugin) RequireRoleMiddleware ¶
RequireRoleMiddleware ensures the user has a specific role.
This is a generalized version of RequireAdminMiddleware for custom role requirements. It follows the same enrichment pattern (check context first, then database).
Usage:
// Require moderator role
router.Use(admin.RequireRoleMiddleware("moderator"))
Parameters:
- requiredRole: Role string to check (e.g., "admin", "moderator", "editor")
Response Codes:
- 401 Unauthorized: User not authenticated
- 403 Forbidden: User authenticated but lacks required role
func (*Plugin) RequiresTables ¶
RequiresTables returns the required tables
type StatsResponse ¶
type StatsResponse struct {
TotalUsers int `json:"totalUsers"` // Total registered users
}
StatsResponse represents platform statistics.
Example Response:
{
"totalUsers": 1234
}
type Store ¶
type Store interface {
// Create creates a new user with admin fields.
Create(ctx context.Context, user User) (User, error)
// GetByEmail retrieves a user by email address.
GetByEmail(ctx context.Context, email string) (User, error)
// GetByID retrieves a user by ID.
GetByID(ctx context.Context, id string) (User, error)
// Update updates user fields (name, email, disabled, etc.).
// Note: Does not update role - use AssignRole/RemoveRole instead.
Update(ctx context.Context, user User) error
// Delete soft-deletes a user by setting updated_at timestamp.
Delete(ctx context.Context, id string) error
// List retrieves paginated users.
List(ctx context.Context, offset, limit int) ([]User, error)
// ListUsersRaw retrieves paginated users as raw map data.
// This supports flexible admin UIs without schema changes.
ListUsersRaw(ctx context.Context, offset, limit int) ([]map[string]any, error)
// GetUserRaw retrieves a user as raw map data.
GetUserRaw(ctx context.Context, userID string) (map[string]any, error)
// Count returns total user count.
Count(ctx context.Context) (int, error)
// AssignRole assigns a role to a user (e.g., "admin").
AssignRole(ctx context.Context, userID string, role string) error
// RemoveRole removes a user's role.
RemoveRole(ctx context.Context, userID string, role string) error
// GetRole retrieves a user's role (empty string if no role).
GetRole(ctx context.Context, userID string) (string, error)
// BanUser bans a user with a reason and optional expiry date.
// If expiry is nil, the ban is permanent.
// Increments ban_counter for repeat offender tracking.
BanUser(ctx context.Context, userID, reason string, expiry *time.Time) error
// UnbanUser removes the ban from a user.
UnbanUser(ctx context.Context, userID string) error
// GetStats retrieves platform statistics.
GetStats(ctx context.Context) (StatsResponse, error)
}
Store defines the interface for admin user storage operations.
This interface extends the core auth.UserStore with admin-specific functionality:
- Role assignment and retrieval
- User ban management with expiry dates
- Platform statistics
- Raw database access for admin UI (flexible schema)
All methods are context-aware for cancellation and timeout support.
Thread Safety: Implementations must be safe for concurrent use.
type UpdateRoleRequest ¶ added in v1.2.2
type UpdateRoleRequest struct {
Role string `json:"role"` // New role to assign (required)
}
UpdateRoleRequest represents a request to update a user's role.
Validation:
- role: Required, the new role to assign to the user (e.g., "admin", "user")
Example:
{
"role": "admin"
}
type User ¶
type User struct {
auth.User
Role string `json:"role"` // User role for RBAC (e.g., "admin")
// Ban management fields
Banned bool `json:"banned"` // Current ban status
BanReason string `json:"banReason,omitempty"` // Reason for ban
BanExpiry *time.Time `json:"banExpiry,omitempty"` // Ban expiration (nil = permanent)
BanCounter int `json:"banCounter,omitempty"` // Number of bans (repeat offender tracking)
}
User represents a user with admin-specific extensions.
This model extends auth.User with role-based access control and ban management.
Admin Extensions:
- Role: User role (e.g., "admin") for authorization checks
- Banned: Whether user is currently banned
- BanReason: Admin-provided reason for ban
- BanExpiry: Ban expiration date (nil for permanent bans)
- BanCounter: Number of times user has been banned (for repeat offender tracking)
Database Mapping: These fields are stored in additional columns on the 'user' table:
- role (VARCHAR)
- banned (BOOLEAN)
- ban_reason (TEXT)
- ban_expiry (TIMESTAMP, nullable)
- ban_counter (INTEGER, default 0)
Example:
user := admin.User{
User: auth.User{ID: "user_123", Email: "[email protected]"},
Role: "admin",
Banned: false,
}
type UserListResponse ¶
type UserListResponse struct {
Users []User `json:"users"` // Users in current page
TotalCount int `json:"totalCount"` // Total number of users
Offset int `json:"offset"` // Current offset (for pagination)
Limit int `json:"limit"` // Page size
}
UserListResponse represents a paginated list of users.
Example Response:
{
"users": [{"id": "user_1", "email": "[email protected]", "role": "admin"}],
"totalCount": 100,
"offset": 0,
"limit": 20
}