db: store all credentials in one SHA-256-hashed table

API keys, pre-auth keys and OAuth clients/tokens share one table and verify
path. Secrets carry 256 bits of crypto/rand entropy, so a SHA-256 digest
needs no stretching; bcrypt/argon2id rows rehash on use until 0.32.
This commit is contained in:
Kristoffer Dalby
2026-09-23 16:12:18 +00:00
committed by Kristoffer Dalby
parent e90500e3a9
commit 393dd3e2d9
39 changed files with 2176 additions and 680 deletions
+20 -51
View File
@@ -38,67 +38,35 @@ CREATE UNIQUE INDEX idx_provider_identifier ON users(provider_identifier) WHERE
CREATE UNIQUE INDEX idx_name_provider_identifier ON users(name, provider_identifier);
CREATE UNIQUE INDEX idx_name_no_provider_identifier ON users(name) WHERE provider_identifier IS NULL;
CREATE TABLE pre_auth_keys(
-- Unified store for every authenticatable secret (API keys, pre-auth keys,
-- OAuth clients and access tokens), discriminated by kind. Only a hash of the
-- secret is stored (SHA-256; legacy bcrypt/Argon2id until next use); identifier
-- is the public lookup value, unique within a kind. Per-kind columns are sparse
-- by design.
CREATE TABLE credentials(
id integer PRIMARY KEY AUTOINCREMENT,
key text,
prefix text,
kind text NOT NULL CHECK(kind IN ('api','authkey','oauth_client','oauth_token')),
identifier text,
hash blob,
user_id integer,
description text,
scopes text,
tags text,
reusable numeric,
ephemeral numeric DEFAULT false,
used numeric DEFAULT false,
tags text,
last_seen datetime,
client_id text,
created_at datetime,
expiration datetime,
revoked datetime,
created_at datetime,
CONSTRAINT fk_pre_auth_keys_user FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL
CONSTRAINT fk_credentials_user FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL,
CONSTRAINT chk_credentials_hash CHECK(hash IS NOT NULL OR revoked IS NOT NULL),
CONSTRAINT chk_credentials_hash_format CHECK(hash IS NULL OR substr(CAST(hash AS TEXT), 1, 1) = '$')
);
CREATE UNIQUE INDEX idx_pre_auth_keys_prefix ON pre_auth_keys(prefix) WHERE prefix IS NOT NULL AND prefix != '';
CREATE TABLE api_keys(
id integer PRIMARY KEY AUTOINCREMENT,
prefix text,
hash blob,
user_id integer,
expiration datetime,
last_seen datetime,
created_at datetime
);
CREATE UNIQUE INDEX idx_api_keys_prefix ON api_keys(prefix);
-- OAuth 2.0 client-credentials clients for the v2 API. client_id is public and
-- embedded in the secret (hskey-client-<client_id>-<secret>); only the bcrypt
-- hash of the secret is stored. Mirrors the api_keys security model.
CREATE TABLE oauth_clients(
id integer PRIMARY KEY AUTOINCREMENT,
client_id text,
secret_hash blob,
scopes text,
tags text,
description text,
user_id integer,
created_at datetime,
revoked datetime
);
CREATE UNIQUE INDEX idx_oauth_clients_client_id ON oauth_clients(client_id);
-- Short-lived bearer access tokens minted by an oauth_client. Stored as a bcrypt
-- hash of the secret, looked up by prefix.
CREATE TABLE oauth_access_tokens(
id integer PRIMARY KEY AUTOINCREMENT,
prefix text,
hash blob,
client_id text,
scopes text,
tags text,
expiration datetime,
created_at datetime
);
CREATE UNIQUE INDEX idx_oauth_access_tokens_prefix ON oauth_access_tokens(prefix);
CREATE UNIQUE INDEX idx_credentials_identifier ON credentials(kind, identifier);
CREATE INDEX idx_credentials_user_id ON credentials(user_id);
CREATE TABLE nodes(
id integer PRIMARY KEY AUTOINCREMENT,
@@ -127,8 +95,9 @@ CREATE TABLE nodes(
deleted_at datetime,
CONSTRAINT fk_nodes_user FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT fk_nodes_auth_key FOREIGN KEY(auth_key_id) REFERENCES pre_auth_keys(id)
CONSTRAINT fk_nodes_auth_key FOREIGN KEY(auth_key_id) REFERENCES credentials(id)
);
CREATE INDEX idx_nodes_auth_key_id ON nodes(auth_key_id);
CREATE TABLE policies(
id integer PRIMARY KEY AUTOINCREMENT,