Authentication & Authorization System
Overview
Heimdall uses a comprehensive authentication and authorization system supporting:
- OAuth Users - Multiple OAuth providers via NextAuth (Twitch, Discord, YouTube, GitHub)
- Email/Password - Traditional authentication with email verification
- Two-Factor Authentication - TOTP-based 2FA with backup codes
- API Keys - Programmatic access with scopes and permissions
- RBAC - Role-Based Access Control with granular permissions
- GDPR Compliance - Data export, account deletion, privacy controls
Architecture
Entities
User
├── Sessions (NextAuth managed)
├── API Keys (user-owned)
├── User Roles (many-to-many)
├── Two-Factor Auth (TOTP secret, backup codes)
├── Linked Accounts (OAuth providers)
├── Connected Apps (OAuth consents)
└── Data Export Logs
API Key
├── Owner (User, optional for system keys)
├── Scopes (array of scope strings)
└── API Key Roles (many-to-many)
Role
├── Name (e.g., "Admin", "Viewer", "GPS Manager")
├── Description
├── Requires 2FA (boolean)
└── Permissions (many-to-many)
Permission
├── Resource (e.g., "gps", "users", "settings")
├── Action (e.g., "read", "write", "delete")
├── Description
Authentication Flow
1. User Authentication (OAuth)
Frontend (Next.js) → NextAuth → OAuth Provider (Twitch/Discord/YouTube/GitHub)
↓
Session Created
↓
JWT Token (NextAuth)
↓
API Request → Bearer Token → Validate Session → Check 2FA → User Context
2. User Authentication (Email/Password)
Frontend (Next.js) → Login Form → API Validate Credentials
↓
Check 2FA Required
↓
Verify TOTP/Backup Code
↓
Session Created
3. API Key Authentication
API Request → Bearer Token → Lookup API Key → Check Active
↓
User Context (if owned)
↓
API Key Context
4. Two-Factor Authentication Flow
Login Success → Check 2FA Enabled
↓
┌──────┴──────┐
│ │
2FA Enabled 2FA Disabled
│ │
▼ ▼
Show 2FA Form Grant Access
│
▼
Verify Code
(TOTP or Backup)
│
▼
Grant Access
Authorization Flow
Request → Auth Context (User/API Key)
↓
Get Roles
↓
Get Permissions
↓
Check Permission (resource:action)
↓
Allow/Deny
Database Schema
Users
CREATE TABLE "User" (
id TEXT PRIMARY KEY,
username TEXT NOT NULL,
display_name TEXT,
email TEXT,
email_verified BOOLEAN DEFAULT FALSE,
password_hash TEXT,
avatar_url TEXT,
two_factor_enabled BOOLEAN DEFAULT FALSE,
two_factor_secret TEXT,
backup_codes TEXT[],
privacy_mode BOOLEAN DEFAULT FALSE,
is_scheduled_for_deletion BOOLEAN DEFAULT FALSE,
scheduled_deletion_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_login_at TIMESTAMPTZ
);
Linked Accounts (OAuth Providers)
CREATE TABLE "LinkedAccount" (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
platform_id TEXT NOT NULL,
platform_slug TEXT NOT NULL,
platform_name TEXT,
provider_account_id TEXT NOT NULL,
username TEXT,
email TEXT,
is_primary BOOLEAN DEFAULT FALSE,
is_oauth BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(platform_id, provider_account_id)
);
Sessions (next-auth managed)
CREATE TABLE "Session" (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
session_token TEXT UNIQUE NOT NULL,
expires TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
API Keys
CREATE TABLE "ApiKey" (
id TEXT PRIMARY KEY,
key TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
description TEXT,
user_id TEXT REFERENCES "User"(id) ON DELETE CASCADE,
scopes TEXT[] NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT true,
expires_at TIMESTAMPTZ,
last_used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Roles
CREATE TABLE "Role" (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
description TEXT,
is_system BOOLEAN NOT NULL DEFAULT false,
requires_2fa BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Permissions
CREATE TABLE "Permission" (
id TEXT PRIMARY KEY,
resource TEXT NOT NULL,
action TEXT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(resource, action)
);
Junction Tables
CREATE TABLE "UserRole" (
user_id TEXT NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
role_id TEXT NOT NULL REFERENCES "Role"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, role_id)
);
CREATE TABLE "ApiKeyRole" (
api_key_id TEXT NOT NULL REFERENCES "ApiKey"(id) ON DELETE CASCADE,
role_id TEXT NOT NULL REFERENCES "Role"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (api_key_id, role_id)
);
CREATE TABLE "RolePermission" (
role_id TEXT NOT NULL REFERENCES "Role"(id) ON DELETE CASCADE,
permission_id TEXT NOT NULL REFERENCES "Permission"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (role_id, permission_id)
);
OAuth Consents (Connected Apps)
CREATE TABLE "OAuthConsent" (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
client_id TEXT NOT NULL REFERENCES "OAuthClient"(id) ON DELETE CASCADE,
scopes TEXT[] NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, client_id)
);
Data Export Logs
CREATE TABLE "DataExportLog" (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
exported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
file_size_bytes BIGINT
);
Default Roles
Case-Sensitive Role Names
Role names are case-sensitive. Always use the exact capitalization shown below when checking roles programmatically (e.g., "Admin" not "admin").
Use Role IDs for Programmatic Checks
Best Practice: Use role_ids instead of roles (names) for programmatic role checks. Role IDs are:
- Immutable - They never change, even if role names are renamed
- Case-insensitive - No capitalization issues
- Stable - Safe for long-term use in code
The API returns both roles (names for display) and role_ids (IDs for programmatic use).
System Roles
| Role Name | ID | Description | Permissions | Requires 2FA |
|---|---|---|---|---|
| Super Admin | role_super_admin | Full system access, bypasses all permission checks | *:* (wildcard) | Yes |
| Admin | role_admin | Full administrative access | *:* (wildcard) | Yes |
| Moderator | role_moderator | Content moderation and GPS management | gps:*, users:read, users:ban, settings:read, stats:read, two_factor:* | Yes |
| User | role_user | Standard user access | two_factor:read, two_factor:write | No |
| Developer | role_developer | OAuth and API key management | oauth_clients:*, api_keys:*, two_factor:* | Yes |
API Roles
| Role Name | ID | Description | Permissions |
|---|---|---|---|
| API Full Access | role_api_full_access | Full API access | *:* (wildcard) |
| API Read Only | role_api_read_only | Read-only API access | gps:read, users:read, roles:read, permissions:read, api_keys:read, settings:read, stats:read, oauth_clients:read |
Super Admin
A user becomes a super admin if any of these conditions are true:
- Has the
role_super_adminrole - TheSuper Adminrole with IDrole_super_admin - Has a role named
super_admin(case-sensitive) - Legacy support - Has the
*:*permission - The wildcard permission granting access to all resources
Super admin users:
- Have
is_super_admin: truein their permissions response - Bypass all permission checks
- Can access all resources and actions
- Can modify system roles
Default Permissions
GPS Resource
gps:read- View GPS datagps:write- Create/update GPS datagps:delete- Delete GPS data
Users Resource
users:read- View usersusers:write- Create/update usersusers:delete- Delete users
Roles Resource
roles:read- View rolesroles:write- Create/update rolesroles:delete- Delete roles
Permissions Resource
permissions:read- View permissionspermissions:write- Create/update permissionspermissions:delete- Delete permissions
API Keys Resource
api_keys:read- View API keysapi_keys:write- Create/update API keysapi_keys:delete- Delete API keys
Settings Resource
settings:read- View settingssettings:write- Create/update settingssettings:delete- Delete settings
Stats Resource
stats:read- View statistics
Two-Factor Auth Resource
two_factor:read- View 2FA statustwo_factor:write- Enable/disable 2FAtwo_factor:admin- Manage 2FA for other users
OAuth Clients Resource
oauth_clients:read- View OAuth clientsoauth_clients:write- Create/update OAuth clientsoauth_clients:delete- Delete OAuth clients
API Key Scopes
Scopes provide additional restrictions beyond permissions:
gps:read- Read GPS datagps:write- Write GPS datausers:read- Read usersusers:write- Write userssettings:read- Read settingssettings:write- Write settings*- All scopes (admin keys)
Scopes act as an additional layer: an API key needs both the scope AND the permission through roles.
Auth Context
pub struct AuthContext {
pub user: Option<User>,
pub api_key: Option<ApiKey>,
pub permissions: Vec<String>, // ["gps:read", "gps:write"]
pub scopes: Vec<String>, // API key scopes
}
impl AuthContext {
pub fn has_permission(&self, permission: &str) -> bool;
pub fn has_scope(&self, scope: &str) -> bool;
pub fn can(&self, resource: &str, action: &str) -> bool;
}
Endpoints
Authentication
POST /v1/auth/session- Validate next-auth sessionPOST /v1/auth/refresh- Refresh session
Users
GET /v1/users- List users (users:read)GET /v1/users/:id- Get user (users:read)PATCH /v1/users/:id- Update user (users:write)DELETE /v1/users/:id- Delete user (users:delete)GET /v1/users/:id/roles- Get user roles (users:read)POST /v1/users/:id/roles- Add role to user (users:write)DELETE /v1/users/:id/roles/:roleId- Remove role (users:write)
API Keys
GET /v1/api-keys- List API keys (api_keys:read)POST /v1/api-keys- Create API key (api_keys:write)GET /v1/api-keys/:id- Get API key (api_keys:read)PATCH /v1/api-keys/:id- Update API key (api_keys:write)DELETE /v1/api-keys/:id- Delete API key (api_keys:delete)POST /v1/api-keys/:id/rotate- Rotate key (api_keys:write)
Roles
GET /v1/roles- List roles (roles:read)POST /v1/roles- Create role (roles:write)GET /v1/roles/:id- Get role (roles:read)PATCH /v1/roles/:id- Update role (roles:write)DELETE /v1/roles/:id- Delete role (roles:delete)GET /v1/roles/:id/permissions- Get role permissions (roles:read)POST /v1/roles/:id/permissions- Add permission (roles:write)DELETE /v1/roles/:id/permissions/:permId- Remove permission (roles:write)
Permissions
GET /v1/permissions- List permissions (permissions:read)POST /v1/permissions- Create permission (permissions:write)GET /v1/permissions/:id- Get permission (permissions:read)DELETE /v1/permissions/:id- Delete permission (permissions:delete)
Two-Factor Authentication
GET /v1/2fa/status- Get 2FA statusPOST /v1/2fa/setup- Generate TOTP secret and QR codePOST /v1/2fa/enable- Enable 2FA with verification codePOST /v1/2fa/disable- Disable 2FAPOST /v1/2fa/verify- Verify TOTP codeGET /v1/2fa/backup-codes- Get backup codesPOST /v1/2fa/backup-codes/regenerate- Regenerate backup codes
GDPR / Data Management
GET /v1/user/export- Export user data (GDPR)GET /v1/user/export/logs- Get export historyPOST /v1/user/delete- Request account deletionDELETE /v1/user/delete- Cancel account deletionGET /v1/user/deletion-status- Get deletion status
Connected Apps (OAuth Consents)
GET /v1/user/connected-apps- List connected appsDELETE /v1/user/connected-apps/:id- Revoke app accessDELETE /v1/user/connected-apps- Revoke all app access
Security Considerations
-
API Keys
- Generate using cryptographically secure random
- Store hashed version (SHA-256)
- Prefix with
hmdl_for identification - Support expiration dates
- Track last used timestamp
-
Sessions
- Managed by NextAuth
- JWT tokens with short expiration
- Secure httpOnly cookies
- App-specific cookie names to prevent session sharing:
- ID App:
id.session-token - Backend:
backend.session-token - Policies:
policies.session-token
- ID App:
-
Two-Factor Authentication
- TOTP-based (RFC 6238)
- 30-second time window
- Compatible with Google Authenticator, Authy, 1Password
- 10 single-use backup codes
- Role-based 2FA requirements
-
Permissions
- Cached per request to avoid N+1 queries
- Wildcard support (
*:*,gps:*) - Hierarchical checking (most specific first)
-
Rate Limiting
- Per user/API key
- Different limits for different roles
- Stricter limits for write operations
-
GDPR Compliance
- Data export in structured format (JSON)
- Account deletion with 30-day grace period
- User anonymization on deletion
- Privacy mode to hide sensitive data
- Audit logging of data exports