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
-
Super Admin
- Full system access
- Cannot be deleted
- Permissions:
*:*(all resources, all actions)
-
Admin
- Manage users, roles, permissions
- Manage all GPS data
- Manage settings
- Permissions:
users:*, roles:*, permissions:*, gps:*, settings:*, stats:read
-
GPS Manager
- Full GPS data management
- Permissions:
gps:read, gps:write, gps:delete, stats:read
-
Viewer
- Read-only access
- Permissions:
gps:read, stats:read
-
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 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
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)
Migration from Old System
Old ApiToken table will be migrated to new ApiKey table:
rolefield (ADMIN/VIEWER) → roles viaApiKeyRole- System-created keys remain functional
- User-owned keys link to users
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 next-auth
- JWT tokens with short expiration
- Secure httpOnly cookies
-
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