User API

On this page

Overview

The User API provides comprehensive user management capabilities including authentication, user CRUD operations, role management, and session handling. The system supports role-based access control with admin and editor roles.

User Roles

Role Hierarchy

  1. Admin - Full system access

    • All content operations
    • User management
    • System settings
    • Theme management
    • Static site generation
  2. Editor - Content management

    • Create, edit, delete posts and pages
    • Media management
    • Limited settings access
    • Cannot manage users or generate static site

Permission Matrix

Operation Admin Editor
User Management
Static Generation
Site Settings
Content Management
Media Management
Theme Management
Menu Management
View Own Profile
Edit Own Profile
View Other Profiles

The system is essentially Admin vs Editor (for now), where Editor is really more like a "Content Manager" role with nearly full system access. The only things editors can't do are manage users and generate static sites.

This makes the "Editor" role quite powerful - they can completely change the site's appearance, settings, and content, just not manage user accounts or create static exports.

Authentication Endpoints

Login

POST /api/auth/login

Request Body:

{
    "username": "admin",
    "password": "secure_password"
}

Success Response (200):

{
    "success": true,
    "data": {
        "user": {
            "id": "user_abc123",
            "username": "admin",
            "email": "admin@example.com",
            "role": "admin",
            "createdAt": "2024-01-01T00:00:00.000Z"
        },
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "expiresAt": 1703980800000
    }
}

Error Response (401):

{
    "success": false,
    "error": "Invalid username or password"
}

Rate Limited Response (429):

{
    "success": false,
    "error": "Account temporarily locked. Try again in 12 minutes."
}

Logout

POST /api/auth/logout

Headers:

Authorization: Bearer {token}

Success Response (200):

{
    "success": true
}

User Management Endpoints

Get Current User

GET /api/users/me

Headers:

Authorization: Bearer {token}

Success Response (200):

{
    "success": true,
    "data": {
        "id": "user_abc123",
        "username": "admin",
        "email": "admin@example.com",
        "role": "admin",
        "createdAt": "2024-01-01T00:00:00.000Z",
        "updatedAt": "2024-01-01T00:00:00.000Z"
    }
}

Get All Users

GET /api/users

Requires Admin role

Success Response (200):

{
    "success": true,
    "data": [
        {
            "id": "user_abc123",
            "username": "admin",
            "email": "admin@example.com",
            "role": "admin",
            "createdAt": "2024-01-01T00:00:00.000Z",
            "updatedAt": "2024-01-01T00:00:00.000Z"
        },
        {
            "id": "user_def456",
            "username": "editor1",
            "email": "editor@example.com",
            "role": "editor",
            "createdAt": "2024-01-02T00:00:00.000Z",
            "updatedAt": "2024-01-02T00:00:00.000Z"
        }
    ]
}

Get Specific User

GET /api/users/{id}

Requires Admin role or own user ID

Success Response (200):

{
    "success": true,
    "data": {
        "id": "user_def456",
        "username": "editor1",
        "email": "editor@example.com",
        "role": "editor",
        "createdAt": "2024-01-02T00:00:00.000Z",
        "updatedAt": "2024-01-02T00:00:00.000Z"
    }
}

Create User

POST /api/users

Requires Admin role

Request Body:

{
    "username": "newuser",
    "password": "secure_password123",
    "email": "newuser@example.com",
    "role": "editor"
}

Success Response (201):

{
    "success": true,
    "data": {
        "id": "user_ghi789",
        "username": "newuser",
        "email": "newuser@example.com",
        "role": "editor",
        "createdAt": "2024-01-03T00:00:00.000Z",
        "updatedAt": "2024-01-03T00:00:00.000Z"
    }
}

Validation Error (400):

{
    "success": false,
    "error": "Username, password, and email are required"
}

Conflict Error (500):

{
    "success": false,
    "error": "Username or email already exists"
}

Update User

PUT /api/users/{id}

Requires Admin role or own user ID

Request Body:

{
    "username": "updated_username",
    "email": "updated@example.com",
    "password": "new_password123",
    "role": "admin"
}

Success Response (200):

{
    "success": true,
    "data": {
        "id": "user_def456",
        "username": "updated_username",
        "email": "updated@example.com",
        "role": "admin",
        "updatedAt": "2024-01-03T12:00:00.000Z"
    }
}

Permission Notes:

  • Only admins can manage users
  • Password changes require the new password in request

Delete User

DELETE /api/users/{id}

Requires Admin role

Request Body:

"user_id_to_delete"

Success Response (200):

{
    "success": true
}

Protection Errors:

{
    "success": false,
    "error": "Cannot delete your own account"
}
{
    "success": false,
    "error": "Cannot delete the last admin user"
}

Security Features

Password Security

  • Argon2 Hashing - Industry-standard password hashing
  • Salt Generation - Automatic salt generation for each password
  • Configurable Parameters:
    • Memory cost: 64MB
    • Time cost: 3 iterations
    • Parallelism: 2 threads

Rate Limiting

Login Attempts:

  • Maximum: 5 failed attempts per username
  • Lockout: 15 minutes
  • Scope: Per username (prevents enumeration)

Rate Limit Headers:

X-RateLimit-Limit: 5
X-RateLimit-Remaining: 3
X-RateLimit-Reset: 1703980800

Session Management

Token Properties:

  • Expiration: 24 hours (configurable)
  • Storage: Server-side session store
  • Invalidation: Automatic cleanup of expired sessions
  • Security: Cryptographically secure random tokens

Usage Examples

User Authentication Flow

class AuthService {
    constructor() {
        this.token = localStorage.getItem("authToken")
        this.user = JSON.parse(localStorage.getItem("userData") || "null")
    }

    async login(username, password) {
        try {
            const response = await fetch("/api/auth/login", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({ username, password }),
            })

            const result = await response.json()

            if (result.success) {
                this.token = result.data.token
                this.user = result.data.user

                // Store for persistence
                localStorage.setItem("authToken", this.token)
                localStorage.setItem("userData", JSON.stringify(this.user))
                localStorage.setItem("tokenExpiry", result.data.expiresAt.toString())

                return result.data
            } else {
                throw new Error(result.error)
            }
        } catch (error) {
            console.error("Login failed:", error)
            throw error
        }
    }

    async logout() {
        try {
            if (this.token) {
                await fetch("/api/auth/logout", {
                    method: "POST",
                    headers: {
                        Authorization: `Bearer ${this.token}`,
                    },
                })
            }
        } catch (error) {
            console.error("Logout error:", error)
        } finally {
            // Always clear local data
            this.token = null
            this.user = null
            localStorage.removeItem("authToken")
            localStorage.removeItem("userData")
            localStorage.removeItem("tokenExpiry")
        }
    }

    isAuthenticated() {
        if (!this.token) return false

        const expiry = localStorage.getItem("tokenExpiry")
        if (!expiry) return false

        return Date.now() < parseInt(expiry)
    }

    hasRole(role) {
        return this.user && this.user.role === role
    }

    isAdmin() {
        return this.hasRole("admin")
    }

    isEditor() {
        return this.hasRole("editor") || this.hasRole("admin")
    }
}

User Management Operations

class UserManager {
    constructor(authService) {
        this.auth = authService
    }

    async getUsers() {
        if (!this.auth.isAdmin()) {
            throw new Error("Admin access required")
        }

        try {
            const response = await fetch("/api/users", {
                headers: {
                    Authorization: `Bearer ${this.auth.token}`,
                },
            })

            const result = await response.json()

            if (result.success) {
                return result.data
            } else {
                throw new Error(result.error)
            }
        } catch (error) {
            console.error("Failed to fetch users:", error)
            throw error
        }
    }

    async createUser(userData) {
        if (!this.auth.isAdmin()) {
            throw new Error("Admin access required")
        }

        try {
            const response = await fetch("/api/users", {
                method: "POST",
                headers: {
                    Authorization: `Bearer ${this.auth.token}`,
                    "Content-Type": "application/json",
                },
                body: JSON.stringify(userData),
            })

            const result = await response.json()

            if (result.success) {
                return result.data
            } else {
                throw new Error(result.error)
            }
        } catch (error) {
            console.error("Failed to create user:", error)
            throw error
        }
    }

    async updateUser(userId, updates) {
        const canUpdate = this.auth.isAdmin() || this.auth.user.id === userId

        if (!canUpdate) {
            throw new Error("Insufficient permissions")
        }

        // Non-admins cannot change roles
        if (!this.auth.isAdmin() && updates.role) {
            delete updates.role
        }

        try {
            const response = await fetch(`/api/users/${userId}`, {
                method: "PUT",
                headers: {
                    Authorization: `Bearer ${this.auth.token}`,
                    "Content-Type": "application/json",
                },
                body: JSON.stringify(updates),
            })

            const result = await response.json()

            if (result.success) {
                // Update local user data if updating own profile
                if (this.auth.user.id === userId) {
                    this.auth.user = { ...this.auth.user, ...result.data }
                    localStorage.setItem("userData", JSON.stringify(this.auth.user))
                }

                return result.data
            } else {
                throw new Error(result.error)
            }
        } catch (error) {
            console.error("Failed to update user:", error)
            throw error
        }
    }

    async deleteUser(userId) {
        if (!this.auth.isAdmin()) {
            throw new Error("Admin access required")
        }

        if (this.auth.user.id === userId) {
            throw new Error("Cannot delete your own account")
        }

        try {
            const response = await fetch(`/api/users/${userId}`, {
                method: "DELETE",
                headers: {
                    Authorization: `Bearer ${this.auth.token}`,
                    "Content-Type": "application/json",
                },
                body: JSON.stringify(userId),
            })

            const result = await response.json()

            if (result.success) {
                return true
            } else {
                throw new Error(result.error)
            }
        } catch (error) {
            console.error("Failed to delete user:", error)
            throw error
        }
    }
}

Role-Based UI Component

class RoleBasedComponent {
    constructor(authService) {
        this.auth = authService
    }

    /**
     * Renders user actions - only available to admins since only they can see user lists
     * Editors cannot access user management endpoints at all except view only /api/users/me
     */
    renderUserActions(user) {
        // Only admins can see user lists and perform user actions
        if (!this.auth.isAdmin()) {
            return []
        }

        const actions = []

        // Admin can view any profile
        actions.push({
            label: "View Profile",
            action: () => this.viewProfile(user.id),
            available: true,
        })

        // Admin can edit any profile
        actions.push({
            label: "Edit Profile",
            action: () => this.editProfile(user.id),
            available: true,
        })

        // Admin can delete other users (but not themselves)
        actions.push({
            label: "Delete User",
            action: () => this.deleteUser(user.id),
            available: this.auth.user.id !== user.id,
            dangerous: true,
        })

        return actions.filter((action) => action.available)
    }

    /**
     * Renders management panel - accessible to both admin and editor
     * Only specific functions are admin-only
     */
    renderManagementPanel() {
        // Both admin and editor can access the management interface
        if (!this.auth.isAuthenticated()) {
            return "<div>Access Denied - Authentication Required</div>"
        }

        const commonActions = `
            <button onclick="contentManager.showPosts()">Manage Posts</button>
            <button onclick="contentManager.showPages()">Manage Pages</button>
            <button onclick="mediaManager.showLibrary()">Media Library</button>
            <button onclick="themeManager.showThemes()">Manage Themes</button>
            <button onclick="systemManager.showSettings()">System Settings</button>
            <button onclick="menuManager.showMenu()">Manage Menu</button>
        `

        const adminOnlyActions = this.auth.isAdmin()
            ? `
            <hr>
            <h3>Admin Only</h3>
            <button onclick="userManager.showCreateUserForm()">Create User</button>
            <button onclick="userManager.showUserList()">Manage Users</button>
            <button onclick="systemManager.generateStaticSite()">Generate Static Site</button>
        `
            : ""

        return `
            <div class="management-panel">
                <h2>${this.auth.isAdmin() ? "Admin" : "Editor"} Dashboard</h2>
                ${commonActions}
                ${adminOnlyActions}
            </div>
        `
    }

    /**
     * Renders content actions available to the current user
     */
    renderContentActions() {
        const actions = []

        // Both admin and editor have full content management access
        if (this.auth.isAuthenticated()) {
            actions.push(
                "Create Post",
                "Create Page",
                "Manage Media",
                "Manage Themes",
                "System Settings",
                "Menu Management"
            )
        }

        // Admin-only actions
        if (this.auth.isAdmin()) {
            actions.push("User Management", "Generate Static Site")
        }

        return actions
    }

    /**
     * Renders profile actions for the current user
     * NOTE: Only admins can actually access profile endpoints
     * Editors cannot view or edit ANY profiles, including their own
     */
    renderCurrentUserProfile() {
        // Only admins have access to profile functionality
        if (!this.auth.isAdmin()) {
            return []
        }

        return [
            {
                label: "View My Profile",
                action: () => this.viewProfile(this.auth.user.id),
                available: true,
            },
            {
                label: "Edit My Profile",
                action: () => this.editProfile(this.auth.user.id),
                available: true,
            },
        ]
    }

    /**
     * Check if current user can perform a specific action
     */
    canPerformAction(action) {
        const permissions = {
            // Admin-only actions
            manage_users: this.auth.isAdmin(),
            generate_static: this.auth.isAdmin(),
            view_user_list: this.auth.isAdmin(),

            // Both admin and editor actions
            manage_content: this.auth.isAuthenticated(),
            manage_media: this.auth.isAuthenticated(),
            manage_themes: this.auth.isAuthenticated(),
            manage_settings: this.auth.isAuthenticated(),
            manage_menu: this.auth.isAuthenticated(),

            // Profile actions - ADMIN ONLY
            view_own_profile: this.auth.isAdmin(),
            edit_own_profile: this.auth.isAdmin(),
        }

        return permissions[action] || false
    }
}

Session Management

class SessionManager {
    constructor(authService) {
        this.auth = authService
        this.checkInterval = null
    }

    startSessionMonitoring() {
        // Check session validity every 5 minutes
        this.checkInterval = setInterval(() => {
            this.checkSession()
        }, 5 * 60 * 1000)
    }

    stopSessionMonitoring() {
        if (this.checkInterval) {
            clearInterval(this.checkInterval)
            this.checkInterval = null
        }
    }

    async checkSession() {
        if (!this.auth.isAuthenticated()) {
            this.handleExpiredSession()
            return
        }

        try {
            // Verify with server
            const response = await fetch("/api/users/me", {
                headers: {
                    Authorization: `Bearer ${this.auth.token}`,
                },
            })

            if (response.status === 401) {
                this.handleExpiredSession()
            }
        } catch (error) {
            console.error("Session check failed:", error)
        }
    }

    handleExpiredSession() {
        this.stopSessionMonitoring()
        this.auth.logout()

        // Show login modal or redirect
        this.showSessionExpiredNotification()
    }

    showSessionExpiredNotification() {
        // Implementation depends on your UI framework
        alert("Your session has expired. Please log in again.")
        window.location.href = "/login"
    }
}

Error Handling

Common Error Scenarios

Insufficient Permissions (403):

{
    "success": false,
    "error": "Forbidden"
}

User Not Found (404):

{
    "success": false,
    "error": "User not found"
}

Validation Errors (400):

{
    "success": false,
    "error": "Username, password, and email are required"
}

Rate Limiting (429):

{
    "success": false,
    "error": "Account temporarily locked. Try again in 12 minutes."
}

Best Practices

  1. Always validate permissions on both client and server
  2. Handle rate limiting gracefully with user feedback
  3. Store tokens securely (consider httpOnly cookies for web apps)
  4. Implement session monitoring to handle expired tokens
  5. Use role-based UI rendering to show appropriate actions
  6. Validate input on both client and server sides
  7. Log security events for audit purposes
  8. Implement proper logout to clear all session data