Skip to main content

Authentication & Authorization System

Overview

Heimdall uses a comprehensive authentication and authorization system supporting:

  • OAuth Users - Twitch OAuth via next-auth (frontend)
  • API Keys - Programmatic access with scopes and permissions
  • RBAC - Role-Based Access Control with granular permissions

Architecture

Entities

User (OAuth)
├── Sessions (next-auth managed)
├── API Keys (user-owned)
└── User Roles (many-to-many)

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
└── 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) → next-auth → Twitch OAuth

Session Created

JWT Token (next-auth)

API Request → Bearer Token → Validate Session → User Context

2. API Key Authentication

API Request → Bearer Token → Lookup API Key → Check Active

User Context (if owned)

API Key Context

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,
twitch_id TEXT UNIQUE NOT NULL,
username TEXT NOT NULL,
email TEXT,
avatar_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_login_at TIMESTAMPTZ
);

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,
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)
);

Default Roles

System Roles

  1. Super Admin

    • Full system access
    • Cannot be deleted
    • Permissions: *:* (all resources, all actions)
  2. Admin

    • Manage users, roles, permissions
    • Manage all GPS data
    • Manage settings
    • Permissions: users:*, roles:*, permissions:*, gps:*, settings:*, stats:read
  3. GPS Manager

    • Full GPS data management
    • Permissions: gps:read, gps:write, gps:delete, stats:read
  4. Viewer

    • Read-only access
    • Permissions: gps:read, stats:read
  5. API Key Manager

    • Create and manage API keys
    • Permissions: api_keys:read, api_keys:write, api_keys:delete

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

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

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

Migration from Old System

Old ApiToken table will be migrated to new ApiKey table:

  • role field (ADMIN/VIEWER) → roles via ApiKeyRole
  • System-created keys remain functional
  • User-owned keys link to users

Security Considerations

  1. API Keys

    • Generate using cryptographically secure random
    • Store hashed version (SHA-256)
    • Prefix with hmdl_ for identification
    • Support expiration dates
    • Track last used timestamp
  2. Sessions

    • Managed by next-auth
    • JWT tokens with short expiration
    • Secure httpOnly cookies
  3. Permissions

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

    • Per user/API key
    • Different limits for different roles
    • Stricter limits for write operations