Skip to main content

Authentication & Authorization System

Overview

Heimdall uses a comprehensive authentication and authorization system supporting:

  • OAuth Users - Multiple OAuth providers (Twitch, Discord, GitHub, YouTube/Google, Kick, Steam, Trovo). Seeded platforms: email, twitch, discord, github, google (YouTube), kick, steam, trovogithub, kick, steam, and trovo ship disabled (enabled = false)
  • 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)
├── Platform Accounts (OAuth + email/password credentials)
├── 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

The User table is a clean identity record. Credentials (passwords, OAuth tokens) live in PlatformAccount, and 2FA data lives in UserTwoFactor / TwoFactorBackupCode.

CREATE TABLE "User" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username TEXT NOT NULL UNIQUE,
primary_platform_account_id UUID, -- FK to PlatformAccount
privacy_mode BOOLEAN NOT NULL DEFAULT false,
preferred_locale VARCHAR(5) DEFAULT 'en',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_login_at TIMESTAMPTZ,
last_login_platform_account_id UUID, -- FK to PlatformAccount
deleted_at TIMESTAMPTZ -- soft delete (NULL = active)
);

Platform Accounts (OAuth + Email/Password Credentials)

All user credentials and linked accounts — both OAuth accounts and email/password accounts — live here. There is no separate LinkedAccount table.

CREATE TABLE "PlatformAccount" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
platform_id TEXT NOT NULL REFERENCES "Platform"(id) ON DELETE CASCADE,
platform_user_id TEXT NOT NULL, -- OAuth: external ID; email: the email address
username TEXT NOT NULL, -- display name
email TEXT,
avatar_url TEXT,
password_hash TEXT, -- Argon2 (email platform only, NULL for OAuth)
email_verified_at TIMESTAMPTZ, -- email platform only
user_id UUID NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(platform_id, platform_user_id)
);

Sessions (OAuth login flow)

CREATE TABLE "Session" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
session_token TEXT UNIQUE NOT NULL,
expires TIMESTAMPTZ NOT NULL,
ip_address TEXT,
user_agent TEXT,
last_activity_at TIMESTAMPTZ DEFAULT NOW(),
provider TEXT, -- auth provider slug (twitch, discord, email, ...)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

API Keys

Keys are hashed with SHA-256; only the hash and a short display prefix are stored.

CREATE TABLE "ApiKey" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key_hash TEXT UNIQUE NOT NULL, -- SHA-256 hash of the API key
key_prefix TEXT NOT NULL, -- first 11 chars for display (e.g. "hm_abc12345")
name TEXT NOT NULL,
description TEXT,
user_id UUID REFERENCES "User"(id) ON DELETE CASCADE,
scopes TEXT[] NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT true,
is_system BOOLEAN NOT NULL DEFAULT false, -- system keys (device/service auth) cannot be deleted via API
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_two_factor 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,
admin_only BOOLEAN NOT NULL DEFAULT false, -- hide from non-admins in the UI
display_order INTEGER NOT NULL DEFAULT 0, -- sort order within a resource group
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(resource, action)
);

Junction Tables

CREATE TABLE "UserRole" (
user_id UUID 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 UUID 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 UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
client_id UUID 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 UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "User"(id) ON DELETE CASCADE,
exported_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ip_address INET,
user_agent TEXT,
export_format VARCHAR(50) NOT NULL DEFAULT 'zip',
file_size_bytes BIGINT,
locale VARCHAR(10),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

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 NameIDDescriptionPermissionsRequires 2FA
Super Adminrole_super_adminFull system access, bypasses all permission checks*:* (wildcard)Yes
Adminrole_adminFull administrative access*:* (wildcard)Yes
Moderatorrole_moderatorContent moderation and GPS managementgps:read, gps:write, gps:delete, users:read, users:ban, stats:read, backend:access, storage:read, storage:write, storage:edit, storage:download, storage:delete, discord:read, discord:guild.read, discord:guild.sync, discord:guild.warn, discord:guild.timeout, discord:guild.kick, discord:guild.banYes
Userrole_userStandard user access(none — users always manage their own 2FA/account)No
Developerrole_developerOAuth and API key management (external developers)oauth_clients:read, oauth_clients:create, oauth_clients:update, oauth_clients:delete, api_keys:read, api_keys:write, api_keys:delete, backend:accessYes

API Roles

Role NameIDDescriptionPermissions
API Full Accessrole_api_full_accessFull API access*:* (wildcard)
API Read Onlyrole_api_read_onlyRead-only API accessgps:read, users:read, roles:read, permissions:read, api_keys:read, settings:read, stats:read, oauth_clients:read, two_factor:read, storage:read, storage:download, devices:read, trips:read, trip_categories:read, locations:read, geofences:read, pegel:read

Super Admin

A user becomes a super admin if any of these conditions are true:

  1. Has the role_super_admin role - The Super Admin role with ID role_super_admin
  2. Has a role named super_admin (case-sensitive) - Legacy support
  3. Has the *:* permission - The wildcard permission granting access to all resources

Super admin users:

  • Have is_super_admin: true in 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 data
  • gps:write - Create/update GPS data
  • gps:delete - Delete GPS data

Users Resource

  • users:read - View users
  • users:write - Create/update users
  • users:delete - Delete users

Roles Resource

  • roles:read - View roles
  • roles:write - Create/update roles
  • roles:delete - Delete roles

Permissions Resource

  • permissions:read - View permissions
  • permissions:write - Create/update permissions
  • permissions:delete - Delete permissions

API Keys Resource

  • api_keys:read - View API keys
  • api_keys:write - Create/update API keys
  • api_keys:delete - Delete API keys

Settings Resource

  • settings:read - View settings
  • settings:write - Create/update settings
  • settings:delete - Delete settings

Stats Resource

  • stats:read - View statistics

Storage Resource

  • storage:read - View storage files and metadata
  • storage:write - Upload files to storage
  • storage:edit - Edit file metadata (rename, move, update metadata)
  • storage:download - Download files from storage
  • storage:delete - Delete files from storage

Two-Factor Auth Resource

  • two_factor:read - View 2FA status for other users (users can always view their own)
  • two_factor:write - Modify 2FA for other users (users can always manage their own)
  • two_factor:manage - Manage 2FA for any user (admin only)

OAuth Clients Resource

  • oauth_clients:read - View OAuth clients
  • oauth_clients:create - Create OAuth clients
  • oauth_clients:update - Update OAuth clients
  • oauth_clients:delete - Delete OAuth clients

Audit Resource

  • audit:read - Read all audit logs (admin view)
  • audit:write - Write audit events (internal apps)

Backend Resource

  • backend:access - Access the backend application

Discord Resource

  • discord:read - Main Discord access (sidebar, guilds list, bot settings)
  • discord:edit - Edit Discord bot settings
  • discord:delete - Delete Discord guilds from the database
  • discord:bot.admin - Access Discord bot admin commands
  • discord:guild.read - View guild data, members, channels, roles
  • discord:guild.edit - Edit guild data and member information
  • discord:guild.sync - Resync a guild (channels, roles, members)
  • discord:guild.warn - Warn Discord members
  • discord:guild.timeout - Timeout Discord members
  • discord:guild.kick - Kick Discord members
  • discord:guild.ban - Ban Discord members

Integrations Resource

  • integrations:read - View platform integrations
  • integrations:manage - Connect/disconnect platform integrations

Devices Resource

  • devices:read - View devices
  • devices:write - Create/update devices
  • devices:delete - Delete devices

Trips Resource

  • trips:read - View trips
  • trips:write - Create/update trips
  • trips:delete - Delete trips

Trip Categories Resource

  • trip_categories:read - View trip categories
  • trip_categories:write - Create/update trip categories
  • trip_categories:delete - Delete trip categories

Locations Resource

  • locations:read - View locations
  • locations:write - Create/update locations
  • locations:delete - Delete locations

Geofences Resource

  • geofences:read - View geofences
  • geofences:write - Create/update geofences
  • geofences:delete - Delete geofences

Pegel Resource

  • pegel:read - View water level (PegelOnline) data

API Key Scopes

Scopes provide additional restrictions beyond permissions:

  • gps:read - Read GPS data
  • gps:write - Write GPS data
  • users:read - Read users
  • users:write - Write users
  • settings:read - Read settings
  • settings: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

Auth logic lives in the heimdall-auth crate (crates/heimdall-auth/), which provides OAuth 2.0 / OpenID Connect flows (PKCE, access/refresh/ID tokens), RBAC permission checking, TOTP/2FA, and cryptographic utilities. The AuthContext type itself is defined in heimdall-db::models.

Auth middleware lives in heimdall-rest::middleware and OAuth HTTP endpoints live in heimdall-rest::oauth.

// crates/heimdall-db/src/models/auth.rs
pub struct AuthContext {
pub user: Option<User>,
pub api_key: Option<ApiKey>,
pub session_id: Option<Uuid>, // DB session ID (session-based auth)
pub roles: Vec<String>, // role names (for display)
pub role_ids: Vec<String>, // role IDs (for programmatic checks)
pub permissions: Vec<String>, // ["gps:read", "gps:write"]
pub scopes: Vec<String>, // API key / OAuth scopes
pub is_super_admin: bool,
}

impl AuthContext {
pub fn has_permission(&self, resource: &str, action: &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 session
  • POST /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 status
  • POST /v1/2fa/setup - Generate TOTP secret and QR code
  • POST /v1/2fa/enable - Enable 2FA with verification code
  • POST /v1/2fa/disable - Disable 2FA
  • POST /v1/2fa/verify - Verify TOTP code
  • GET /v1/2fa/backup-codes - Get backup codes
  • POST /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 history
  • POST /v1/user/delete - Request account deletion
  • DELETE /v1/user/delete - Cancel account deletion
  • GET /v1/user/deletion-status - Get deletion status

Connected Apps (OAuth Consents)

  • GET /v1/user/connected-apps - List connected apps
  • DELETE /v1/user/connected-apps/:id - Revoke app access
  • DELETE /v1/user/connected-apps - Revoke all app access

Security Considerations

  1. API Keys

    • Generate using cryptographically secure random
    • Store hashed version (SHA-256)
    • Prefix with hm_ for identification
    • Support expiration dates
    • Track last used timestamp
  2. 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
  3. 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
  4. Permissions

    • Cached per request to avoid N+1 queries
    • Wildcard support (*:*, gps:*)
    • Hierarchical checking (most specific first)
  5. Rate Limiting

    • Per user/API key
    • Different limits for different roles
    • Stricter limits for write operations
  6. 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