-- Reference schema for the lookup direct backend: MySQL 8.0.16+ / MariaDB 10.2+. Direct backend only. Run each statement separately (see ddl.Statements) unless multiStatements=true. -- Table and column names are the lookup.DefaultSchema defaults, override them with lookup.Config.Schema. -- Generated by the project, edit freely for your deployment (types, collations, extra columns). -- password: bcrypt hash (nullable for OAuth2 users), legacy cleartext is accepted at login -- roles: comma-separated roles CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255), user_level INT DEFAULT 0, roles VARCHAR(500), is_active TINYINT(1) DEFAULT 1, created_at DATETIME NULL, updated_at DATETIME NULL, last_login_at DATETIME, program_user_id INT DEFAULT 0, program_user_table VARCHAR(255) DEFAULT '', remote_id VARCHAR(255), auth_provider VARCHAR(50), totp_secret VARCHAR(255), totp_enabled TINYINT(1) DEFAULT 0, totp_enabled_at DATETIME ); CREATE TABLE IF NOT EXISTS user_sessions ( id INT AUTO_INCREMENT PRIMARY KEY, session_token VARCHAR(500) NOT NULL UNIQUE, user_id INT NOT NULL, expires_at DATETIME NOT NULL, created_at DATETIME NULL, last_activity_at DATETIME NULL, ip_address VARCHAR(45), user_agent TEXT, access_token TEXT, refresh_token TEXT, token_type VARCHAR(50) DEFAULT 'Bearer', auth_provider VARCHAR(50), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user_sessions_user_id (user_id), INDEX idx_user_sessions_expires_at (expires_at) ); CREATE TABLE IF NOT EXISTS token_blacklist ( id INT AUTO_INCREMENT PRIMARY KEY, token VARCHAR(500) NOT NULL, user_id INT, expires_at DATETIME NOT NULL, created_at DATETIME NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); -- code_hash: SHA-256 hex of the backup code CREATE TABLE IF NOT EXISTS user_totp_backup_codes ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, code_hash VARCHAR(64) NOT NULL, used TINYINT(1) DEFAULT 0, used_at DATETIME, created_at DATETIME NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_totp_user_id (user_id), INDEX idx_totp_code_hash (code_hash) ); -- credential_id: base64 text -- public_key: base64 text -- aaguid: base64 text -- transports: JSON-encoded array CREATE TABLE IF NOT EXISTS user_passkey_credentials ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, credential_id VARCHAR(1400) CHARACTER SET ascii NOT NULL UNIQUE, public_key TEXT NOT NULL, attestation_type VARCHAR(50) DEFAULT 'none', aaguid TEXT, sign_count INT DEFAULT 0, clone_warning TINYINT(1) DEFAULT 0, transports TEXT, backup_eligible TINYINT(1) DEFAULT 0, backup_state TINYINT(1) DEFAULT 0, name VARCHAR(255), created_at DATETIME NULL, last_used_at DATETIME NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_passkey_user_id (user_id) ); -- token_hash: SHA-256 hex of the raw token CREATE TABLE IF NOT EXISTS user_password_resets ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, token_hash VARCHAR(64) NOT NULL UNIQUE, expires_at DATETIME NOT NULL, created_at DATETIME NULL, used TINYINT(1) DEFAULT 0, used_at DATETIME, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_pw_reset_user_id (user_id), INDEX idx_pw_reset_expires_at (expires_at) ); -- redirect_uris: JSON-encoded array -- grant_types: JSON-encoded array -- allowed_scopes: JSON-encoded array -- client_secret_hash: SHA-256 hex of the confidential-client secret, NULL for public clients CREATE TABLE IF NOT EXISTS oauth_clients ( id INT AUTO_INCREMENT PRIMARY KEY, client_id VARCHAR(255) NOT NULL UNIQUE, redirect_uris TEXT NOT NULL, client_name VARCHAR(255), grant_types TEXT, allowed_scopes TEXT, client_secret_hash TEXT, token_endpoint_auth_method VARCHAR(30) DEFAULT 'none', is_active TINYINT(1) DEFAULT 1, metadata TEXT, created_at DATETIME NULL ); -- scopes: JSON-encoded array CREATE TABLE IF NOT EXISTS oauth_codes ( id INT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(255) NOT NULL UNIQUE, client_id VARCHAR(255) NOT NULL, redirect_uri TEXT NOT NULL, client_state TEXT, code_challenge VARCHAR(255) NOT NULL, code_challenge_method VARCHAR(10) DEFAULT 'S256', session_token TEXT NOT NULL, refresh_token TEXT, scopes TEXT, expires_at DATETIME NOT NULL, extra TEXT, created_at DATETIME NULL, INDEX idx_oauth_codes_expires (expires_at) ); -- oauth_consents.scopes / oauth_refresh_tokens.scopes+extra / oauth_device_codes.scopes / oauth_par_requests.params: JSON text CREATE TABLE IF NOT EXISTS oauth_consents ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, client_id VARCHAR(255) NOT NULL, scopes TEXT, created_at DATETIME NULL, expires_at DATETIME NOT NULL, INDEX idx_oauth_consents_user_client (user_id, client_id) ); CREATE TABLE IF NOT EXISTS oauth_refresh_tokens ( id INT AUTO_INCREMENT PRIMARY KEY, token_hash VARCHAR(64) NOT NULL UNIQUE, family_id VARCHAR(64) NOT NULL, client_id VARCHAR(255) NOT NULL, user_id INT NOT NULL, session_token VARCHAR(255), scopes TEXT, extra TEXT, created_at DATETIME NULL, expires_at DATETIME NOT NULL, used_at DATETIME, revoked_at DATETIME, INDEX idx_oauth_refresh_family (family_id), INDEX idx_oauth_refresh_session (session_token), INDEX idx_oauth_refresh_expires (expires_at) ); CREATE TABLE IF NOT EXISTS oauth_device_codes ( id INT AUTO_INCREMENT PRIMARY KEY, device_hash VARCHAR(64) NOT NULL UNIQUE, user_code VARCHAR(32) NOT NULL UNIQUE, client_id VARCHAR(255) NOT NULL, scopes TEXT, status VARCHAR(16) NOT NULL DEFAULT 'pending', user_id INT, session_token VARCHAR(255), poll_interval INT NOT NULL DEFAULT 5, created_at DATETIME NULL, expires_at DATETIME NOT NULL, last_polled_at DATETIME, INDEX idx_oauth_device_expires (expires_at) ); CREATE TABLE IF NOT EXISTS oauth_par_requests ( id INT AUTO_INCREMENT PRIMARY KEY, request_uri VARCHAR(255) NOT NULL UNIQUE, client_id VARCHAR(255) NOT NULL, params TEXT, created_at DATETIME NULL, expires_at DATETIME NOT NULL, INDEX idx_oauth_par_expires (expires_at) ); CREATE TABLE IF NOT EXISTS oauth_jti ( id INT AUTO_INCREMENT PRIMARY KEY, jti_key VARCHAR(255) NOT NULL UNIQUE, expires_at DATETIME NOT NULL, INDEX idx_oauth_jti_expires (expires_at) ); -- key_hash: SHA-256 hex -- scopes: JSON-encoded array -- meta: JSON-encoded object CREATE TABLE IF NOT EXISTS user_keys ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, key_type VARCHAR(50) NOT NULL, key_hash VARCHAR(64) NOT NULL UNIQUE, name VARCHAR(255) NOT NULL DEFAULT '', scopes TEXT, meta TEXT, expires_at DATETIME, created_at DATETIME NULL, last_used_at DATETIME, is_active TINYINT(1) DEFAULT 1, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user_keys_user_id (user_id), INDEX idx_user_keys_key_type (key_type) ); -- Optional: omit to use per-user rules only. CREATE TABLE IF NOT EXISTS sec_group_members ( group_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY (group_id, user_id), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); -- column_path: dot path under the table: col or col.sub.field -- access_type: mask, hide, read, ... -- extra_filters: JSON object CREATE TABLE IF NOT EXISTS sec_column_rules ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, group_id INT, schema_name VARCHAR(255) NOT NULL, table_name VARCHAR(255) NOT NULL, column_path VARCHAR(255) NOT NULL, access_type VARCHAR(50) NOT NULL, mask_start INT DEFAULT 0, mask_end INT DEFAULT 0, mask_invert TINYINT(1) DEFAULT 0, mask_char VARCHAR(10) DEFAULT '*', extra_filters TEXT, is_active TINYINT(1) NOT NULL DEFAULT 1, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, CHECK ((user_id IS NULL AND group_id IS NOT NULL) OR (user_id IS NOT NULL AND group_id IS NULL)), INDEX idx_sec_column_rules_table (schema_name, table_name) ); -- template: SQL fragment, e.g. user_id = {UserID} CREATE TABLE IF NOT EXISTS sec_row_rules ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, group_id INT, schema_name VARCHAR(255) NOT NULL, table_name VARCHAR(255) NOT NULL, template TEXT, has_block TINYINT(1) NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, CHECK ((user_id IS NULL AND group_id IS NOT NULL) OR (user_id IS NOT NULL AND group_id IS NULL)), INDEX idx_sec_row_rules_table (schema_name, table_name) );