feat: add TOTP multifactor authentication
This commit is contained in:
+57
-1
@@ -555,7 +555,7 @@ CREATE TABLE IF NOT EXISTS equipment_types (id TEXT PRIMARY KEY, name TEXT NOT N
|
||||
CREATE TABLE IF NOT EXISTS stat_definitions (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, value_kind TEXT NOT NULL, min_value REAL, max_value REAL, unit TEXT, active INTEGER NOT NULL DEFAULT 1);
|
||||
CREATE TABLE IF NOT EXISTS stat_equipment_types (stat_id TEXT NOT NULL REFERENCES stat_definitions(id), equipment_type_id TEXT NOT NULL REFERENCES equipment_types(id), PRIMARY KEY(stat_id, equipment_type_id));
|
||||
CREATE TABLE IF NOT EXISTS audit_log (id TEXT PRIMARY KEY, actor_id TEXT NOT NULL REFERENCES users(id), entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, action TEXT NOT NULL, before_json TEXT, after_json TEXT, created_at TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS sessions (id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS sessions (id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL, aal INTEGER NOT NULL DEFAULT 1 CHECK(aal IN (1,2)), aal_verified_at TEXT NOT NULL, aal2_verified_at TEXT);
|
||||
CREATE TABLE IF NOT EXISTS roles (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT);
|
||||
CREATE TABLE IF NOT EXISTS deleted_roles (role_id TEXT PRIMARY KEY, deleted_at TEXT NOT NULL, deleted_by TEXT NOT NULL REFERENCES users(id));
|
||||
CREATE TABLE IF NOT EXISTS permissions (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT);
|
||||
@@ -591,6 +591,8 @@ CREATE TABLE IF NOT EXISTS login_events (
|
||||
session_ended_at TEXT,
|
||||
session_end_reason TEXT,
|
||||
sensitive_purged_at TEXT,
|
||||
aal INTEGER CHECK(aal IS NULL OR aal IN (1,2)),
|
||||
aal2_verified_at TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS user_emails (
|
||||
@@ -643,6 +645,60 @@ CREATE TABLE IF NOT EXISTS deleted_accounts (
|
||||
reason TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS login_events_user_created_idx ON login_events(user_id, created_at DESC);
|
||||
CREATE TABLE IF NOT EXISTS mfa_totp_factors (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
state TEXT NOT NULL CHECK(state IN ('pending','active','revoked')),
|
||||
encrypted_secret TEXT NOT NULL,
|
||||
encryption_key_id TEXT NOT NULL,
|
||||
issuer TEXT NOT NULL DEFAULT 'SHD.build',
|
||||
account_label TEXT NOT NULL,
|
||||
algorithm TEXT NOT NULL DEFAULT 'SHA1' CHECK(algorithm='SHA1'),
|
||||
digits INTEGER NOT NULL DEFAULT 6 CHECK(digits=6),
|
||||
period_seconds INTEGER NOT NULL DEFAULT 30 CHECK(period_seconds=30),
|
||||
last_counter INTEGER,
|
||||
failed_attempts INTEGER NOT NULL DEFAULT 0 CHECK(failed_attempts BETWEEN 0 AND 5),
|
||||
locked_until TEXT,
|
||||
expires_at TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
activated_at TEXT,
|
||||
revoked_at TEXT,
|
||||
CHECK((state='pending' AND expires_at IS NOT NULL AND activated_at IS NULL AND revoked_at IS NULL)
|
||||
OR (state='active' AND activated_at IS NOT NULL AND expires_at IS NULL AND revoked_at IS NULL)
|
||||
OR (state='revoked' AND revoked_at IS NOT NULL))
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS mfa_totp_factors_one_active_idx
|
||||
ON mfa_totp_factors(user_id) WHERE state='active';
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS mfa_totp_factors_one_pending_idx
|
||||
ON mfa_totp_factors(user_id) WHERE state='pending';
|
||||
CREATE INDEX IF NOT EXISTS mfa_totp_factors_expiry_idx
|
||||
ON mfa_totp_factors(state,expires_at) WHERE state='pending';
|
||||
CREATE TABLE IF NOT EXISTS mfa_recovery_codes (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
code_hash TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
consumed_at TEXT,
|
||||
revoked_at TEXT,
|
||||
UNIQUE(user_id,code_hash)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS mfa_recovery_codes_available_idx
|
||||
ON mfa_recovery_codes(user_id,consumed_at,revoked_at);
|
||||
CREATE TABLE IF NOT EXISTS authentication_transactions (
|
||||
id TEXT PRIMARY KEY,
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
provider TEXT CHECK(provider IS NULL OR provider IN ('password','discord','magic_link','passkey')),
|
||||
kind TEXT NOT NULL CHECK(kind IN ('login','step_up')),
|
||||
return_path TEXT NOT NULL CHECK(length(return_path) BETWEEN 1 AND 2048 AND substr(return_path,1,1)='/'),
|
||||
required_aal INTEGER NOT NULL DEFAULT 2 CHECK(required_aal=2),
|
||||
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK(attempt_count BETWEEN 0 AND 5),
|
||||
expires_at TEXT NOT NULL,
|
||||
consumed_at TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS authentication_transactions_expiry_idx
|
||||
ON authentication_transactions(expires_at);
|
||||
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-data-editor','Data Editor','Access to ISAC game-data management');
|
||||
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-user-operator','User Operator','View and moderate SHD.build users');
|
||||
INSERT OR IGNORE INTO roles(id,name,description) VALUES('role-access-manager','Access Manager','Manage user roles and direct permissions');
|
||||
|
||||
Reference in New Issue
Block a user