User API
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
Admin - Full system access
- All content operations
- User management
- System settings
- Theme management
- Static site generation
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
- Always validate permissions on both client and server
- Handle rate limiting gracefully with user feedback
- Store tokens securely (consider httpOnly cookies for web apps)
- Implement session monitoring to handle expired tokens
- Use role-based UI rendering to show appropriate actions
- Validate input on both client and server sides
- Log security events for audit purposes
- Implement proper logout to clear all session data