admin

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Feb 23, 2026 License: MIT Imports: 17 Imported by: 0

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

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

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

func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)

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

func GetSchema(dialect plugins.Dialect) (*plugins.Schema, error)

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

func (s *DefaultAdminStore) AssignRole(ctx context.Context, userID string, role string) error

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

func (s *DefaultAdminStore) Create(ctx context.Context, user User) (User, error)

Create creates a new user.

func (*DefaultAdminStore) Delete

func (s *DefaultAdminStore) Delete(ctx context.Context, id string) error

Delete removes a user.

func (*DefaultAdminStore) GetByEmail

func (s *DefaultAdminStore) GetByEmail(ctx context.Context, email string) (User, error)

GetByEmail retrieves a user by email.

func (*DefaultAdminStore) GetByID

func (s *DefaultAdminStore) GetByID(ctx context.Context, id string) (User, error)

GetByID retrieves a user by ID.

func (*DefaultAdminStore) GetRole

func (s *DefaultAdminStore) GetRole(ctx context.Context, userID string) (string, error)

GetRole retrieves a user's role.

func (*DefaultAdminStore) GetStats

func (s *DefaultAdminStore) GetStats(ctx context.Context) (StatsResponse, error)

GetStats retrieves system statistics.

func (*DefaultAdminStore) GetUserRaw

func (s *DefaultAdminStore) GetUserRaw(ctx context.Context, userID string) (map[string]any, error)

GetUserRaw retrieves a single user as raw map data.

func (*DefaultAdminStore) List

func (s *DefaultAdminStore) List(ctx context.Context, offset, limit int) ([]User, error)

List retrieves a paginated list of users.

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

func (s *DefaultAdminStore) RemoveRole(ctx context.Context, userID string, _ string) error

RemoveRole removes a role from a user.

func (*DefaultAdminStore) UnbanUser

func (s *DefaultAdminStore) UnbanUser(ctx context.Context, userID string) error

UnbanUser unbans a user.

func (*DefaultAdminStore) Update

func (s *DefaultAdminStore) Update(ctx context.Context, user User) error

Update updates user information.

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

func New(store Store, dialect ...plugins.Dialect) *Plugin

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

func (a *Plugin) AssignRole(ctx context.Context, userID string, role string) error

AssignRole assigns a role to a user programmatically.

func (*Plugin) BanUser

func (a *Plugin) BanUser(ctx context.Context, userID, reason string, expiresAt *time.Time) error

BanUser bans a user programmatically.

func (*Plugin) DeleteUser

func (a *Plugin) DeleteUser(ctx context.Context, userID string) error

DeleteUser permanently deletes a user account programmatically.

func (*Plugin) Dependencies

func (a *Plugin) Dependencies() []plugins.Dependency

Dependencies returns the plugin dependencies

func (*Plugin) Description

func (a *Plugin) Description() string

Description returns a human-readable description for logging and diagnostics.

func (*Plugin) DisableUser

func (a *Plugin) DisableUser(ctx context.Context, userID string) error

DisableUser disables a user account programmatically.

func (*Plugin) EnableUser

func (a *Plugin) EnableUser(ctx context.Context, userID string) error

EnableUser re-enables a user account programmatically.

func (*Plugin) EnrichUser

func (a *Plugin) EnrichUser(ctx context.Context, user *core.EnrichedUser) error

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

func (a *Plugin) EnrichUserMiddleware() func(http.Handler) http.Handler

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:

  1. Retrieve authenticated user from context
  2. Fetch user's role from database
  3. Add role to EnrichedUser via plugins.ExtendUser
  4. 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

func (a *Plugin) GetAdminUser(ctx context.Context, userID string) (User, error)

GetAdminUser retrieves a user with admin-specific information.

func (*Plugin) GetMigrations

func (a *Plugin) GetMigrations() []plugins.Migration

GetMigrations returns the plugin migrations

func (*Plugin) GetSchemas

func (a *Plugin) GetSchemas() []plugins.Schema

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

func (a *Plugin) GetUser(ctx context.Context, userID string) (User, error)

GetUser retrieves detailed information for a specific user programmatically.

func (*Plugin) GetUserRaw

func (a *Plugin) GetUserRaw(ctx context.Context, userID string) (map[string]any, error)

GetUserRaw retrieves detailed information for a specific user as raw map data programmatically.

func (*Plugin) GetUserRole

func (a *Plugin) GetUserRole(ctx context.Context, userID string) (string, error)

GetUserRole retrieves the role of a user programmatically.

func (*Plugin) Init

func (a *Plugin) Init(ctx context.Context, aegis plugins.Aegis) error

Init initializes the admin plugin and validates database schema.

Initialization Steps:

  1. Create DefaultAdminStore if custom store not provided
  2. Store session service reference for authentication middleware
  3. Build schema validation requirements (table + column checks)
  4. 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) ListUsers

func (a *Plugin) ListUsers(ctx context.Context, offset, limit int) ([]User, error)

ListUsers lists all users programmatically.

func (*Plugin) ListUsersRaw

func (a *Plugin) ListUsersRaw(ctx context.Context, offset, limit int) ([]map[string]any, error)

ListUsersRaw lists all users as raw map data programmatically.

func (*Plugin) MountRoutes

func (a *Plugin) MountRoutes(r router.Router, prefix string)

MountRoutes registers administrative management endpoints.

func (*Plugin) Name

func (a *Plugin) Name() string

Name returns the plugin identifier.

func (*Plugin) ProvidesAuthMethods

func (a *Plugin) ProvidesAuthMethods() []string

ProvidesAuthMethods returns the provided auth methods

func (*Plugin) RemoveRole

func (a *Plugin) RemoveRole(ctx context.Context, userID string, role string) error

RemoveRole removes a role from a user programmatically.

func (*Plugin) RequireAdminMiddleware

func (a *Plugin) RequireAdminMiddleware() func(http.Handler) http.Handler

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:

  1. First checks EnrichedUser context (if already enriched) - fast path
  2. Falls back to database lookup if not enriched - slow path

Authentication Flow:

  1. Retrieve authenticated user from context (via RequireAuthMiddleware)
  2. Check if role is already in EnrichedUser ("role" extension)
  3. If not found, fetch role from database
  4. Verify role is "admin"
  5. 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

func (a *Plugin) RequireRoleMiddleware(requiredRole string) func(http.Handler) http.Handler

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

func (a *Plugin) RequiresTables() []string

RequiresTables returns the required tables

func (*Plugin) UnbanUser

func (a *Plugin) UnbanUser(ctx context.Context, userID string) error

UnbanUser removes the ban from a user account programmatically.

func (*Plugin) Version

func (a *Plugin) Version() string

Version returns the plugin version for compatibility tracking.

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
}

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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