-- Reference schema for the lookup direct backend: Microsoft SQL Server 2016+. Direct backend only. Run each statement separately (see ddl.Statements). -- 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 IF OBJECT_ID(N'users', N'U') IS NULL CREATE TABLE users ( id INT IDENTITY(1,1) PRIMARY KEY, username NVARCHAR(255) NOT NULL UNIQUE, email NVARCHAR(255) NOT NULL UNIQUE, password NVARCHAR(255), user_level INT DEFAULT 0, roles NVARCHAR(500), is_active BIT DEFAULT 1, created_at DATETIME2 DEFAULT SYSUTCDATETIME(), updated_at DATETIME2 DEFAULT SYSUTCDATETIME(), last_login_at DATETIME2, program_user_id INT DEFAULT 0, program_user_table NVARCHAR(255) DEFAULT '', remote_id NVARCHAR(255), auth_provider NVARCHAR(50), totp_secret NVARCHAR(255), totp_enabled BIT DEFAULT 0, totp_enabled_at DATETIME2 ); IF OBJECT_ID(N'user_sessions', N'U') IS NULL CREATE TABLE user_sessions ( id INT IDENTITY(1,1) PRIMARY KEY, session_token NVARCHAR(450) NOT NULL UNIQUE, user_id INT NOT NULL, expires_at DATETIME2 NOT NULL, created_at DATETIME2 DEFAULT SYSUTCDATETIME(), last_activity_at DATETIME2 DEFAULT SYSUTCDATETIME(), ip_address NVARCHAR(45), user_agent NVARCHAR(MAX), access_token NVARCHAR(MAX), refresh_token NVARCHAR(MAX), token_type NVARCHAR(50) DEFAULT 'Bearer', auth_provider NVARCHAR(50), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_user_sessions_user_id' AND object_id = OBJECT_ID(N'user_sessions')) CREATE INDEX idx_user_sessions_user_id ON user_sessions(user_id); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_user_sessions_expires_at' AND object_id = OBJECT_ID(N'user_sessions')) CREATE INDEX idx_user_sessions_expires_at ON user_sessions(expires_at); IF OBJECT_ID(N'token_blacklist', N'U') IS NULL CREATE TABLE token_blacklist ( id INT IDENTITY(1,1) PRIMARY KEY, token NVARCHAR(500) NOT NULL, user_id INT, expires_at DATETIME2 NOT NULL, created_at DATETIME2 DEFAULT SYSUTCDATETIME(), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); -- code_hash: SHA-256 hex of the backup code IF OBJECT_ID(N'user_totp_backup_codes', N'U') IS NULL CREATE TABLE user_totp_backup_codes ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, code_hash NVARCHAR(64) NOT NULL, used BIT DEFAULT 0, used_at DATETIME2, created_at DATETIME2 DEFAULT SYSUTCDATETIME(), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_totp_user_id' AND object_id = OBJECT_ID(N'user_totp_backup_codes')) CREATE INDEX idx_totp_user_id ON user_totp_backup_codes(user_id); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_totp_code_hash' AND object_id = OBJECT_ID(N'user_totp_backup_codes')) CREATE INDEX idx_totp_code_hash ON user_totp_backup_codes(code_hash); -- credential_id: base64 text -- public_key: base64 text -- aaguid: base64 text -- transports: JSON-encoded array IF OBJECT_ID(N'user_passkey_credentials', N'U') IS NULL CREATE TABLE user_passkey_credentials ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, credential_id VARCHAR(900) NOT NULL UNIQUE, public_key NVARCHAR(MAX) NOT NULL, attestation_type NVARCHAR(50) DEFAULT 'none', aaguid NVARCHAR(MAX), sign_count INT DEFAULT 0, clone_warning BIT DEFAULT 0, transports NVARCHAR(MAX), backup_eligible BIT DEFAULT 0, backup_state BIT DEFAULT 0, name NVARCHAR(255), created_at DATETIME2 DEFAULT SYSUTCDATETIME(), last_used_at DATETIME2 DEFAULT SYSUTCDATETIME(), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_passkey_user_id' AND object_id = OBJECT_ID(N'user_passkey_credentials')) CREATE INDEX idx_passkey_user_id ON user_passkey_credentials(user_id); -- token_hash: SHA-256 hex of the raw token IF OBJECT_ID(N'user_password_resets', N'U') IS NULL CREATE TABLE user_password_resets ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, token_hash NVARCHAR(64) NOT NULL UNIQUE, expires_at DATETIME2 NOT NULL, created_at DATETIME2 DEFAULT SYSUTCDATETIME(), used BIT DEFAULT 0, used_at DATETIME2, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_pw_reset_user_id' AND object_id = OBJECT_ID(N'user_password_resets')) CREATE INDEX idx_pw_reset_user_id ON user_password_resets(user_id); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_pw_reset_expires_at' AND object_id = OBJECT_ID(N'user_password_resets')) CREATE INDEX idx_pw_reset_expires_at ON user_password_resets(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 IF OBJECT_ID(N'oauth_clients', N'U') IS NULL CREATE TABLE oauth_clients ( id INT IDENTITY(1,1) PRIMARY KEY, client_id NVARCHAR(255) NOT NULL UNIQUE, redirect_uris NVARCHAR(MAX) NOT NULL, client_name NVARCHAR(255), grant_types NVARCHAR(MAX), allowed_scopes NVARCHAR(MAX), client_secret_hash NVARCHAR(MAX), token_endpoint_auth_method NVARCHAR(30) DEFAULT 'none', is_active BIT DEFAULT 1, metadata NVARCHAR(MAX), created_at DATETIME2 DEFAULT SYSUTCDATETIME() ); -- scopes: JSON-encoded array IF OBJECT_ID(N'oauth_codes', N'U') IS NULL CREATE TABLE oauth_codes ( id INT IDENTITY(1,1) PRIMARY KEY, code NVARCHAR(255) NOT NULL UNIQUE, client_id NVARCHAR(255) NOT NULL, redirect_uri NVARCHAR(MAX) NOT NULL, client_state NVARCHAR(MAX), code_challenge NVARCHAR(255) NOT NULL, code_challenge_method NVARCHAR(10) DEFAULT 'S256', session_token NVARCHAR(MAX) NOT NULL, refresh_token NVARCHAR(MAX), scopes NVARCHAR(MAX), expires_at DATETIME2 NOT NULL, extra NVARCHAR(MAX), created_at DATETIME2 DEFAULT SYSUTCDATETIME() ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_oauth_codes_expires' AND object_id = OBJECT_ID(N'oauth_codes')) CREATE INDEX idx_oauth_codes_expires ON oauth_codes(expires_at); -- oauth_consents.scopes / oauth_refresh_tokens.scopes+extra / oauth_device_codes.scopes / oauth_par_requests.params: JSON text IF OBJECT_ID(N'oauth_consents', N'U') IS NULL CREATE TABLE oauth_consents ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, client_id NVARCHAR(255) NOT NULL, scopes NVARCHAR(MAX), created_at DATETIME2 DEFAULT SYSUTCDATETIME(), expires_at DATETIME2 NOT NULL ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_oauth_consents_user_client' AND object_id = OBJECT_ID(N'oauth_consents')) CREATE INDEX idx_oauth_consents_user_client ON oauth_consents(user_id, client_id); IF OBJECT_ID(N'oauth_refresh_tokens', N'U') IS NULL CREATE TABLE oauth_refresh_tokens ( id INT IDENTITY(1,1) PRIMARY KEY, token_hash NVARCHAR(64) NOT NULL UNIQUE, family_id NVARCHAR(64) NOT NULL, client_id NVARCHAR(255) NOT NULL, user_id INT NOT NULL, session_token NVARCHAR(255), scopes NVARCHAR(MAX), extra NVARCHAR(MAX), created_at DATETIME2 DEFAULT SYSUTCDATETIME(), expires_at DATETIME2 NOT NULL, used_at DATETIME2, revoked_at DATETIME2 ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_oauth_refresh_family' AND object_id = OBJECT_ID(N'oauth_refresh_tokens')) CREATE INDEX idx_oauth_refresh_family ON oauth_refresh_tokens(family_id); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_oauth_refresh_session' AND object_id = OBJECT_ID(N'oauth_refresh_tokens')) CREATE INDEX idx_oauth_refresh_session ON oauth_refresh_tokens(session_token); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_oauth_refresh_expires' AND object_id = OBJECT_ID(N'oauth_refresh_tokens')) CREATE INDEX idx_oauth_refresh_expires ON oauth_refresh_tokens(expires_at); IF OBJECT_ID(N'oauth_device_codes', N'U') IS NULL CREATE TABLE oauth_device_codes ( id INT IDENTITY(1,1) PRIMARY KEY, device_hash NVARCHAR(64) NOT NULL UNIQUE, user_code NVARCHAR(32) NOT NULL UNIQUE, client_id NVARCHAR(255) NOT NULL, scopes NVARCHAR(MAX), status NVARCHAR(16) NOT NULL DEFAULT 'pending', user_id INT, session_token NVARCHAR(255), poll_interval INT NOT NULL DEFAULT 5, created_at DATETIME2 DEFAULT SYSUTCDATETIME(), expires_at DATETIME2 NOT NULL, last_polled_at DATETIME2 ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_oauth_device_expires' AND object_id = OBJECT_ID(N'oauth_device_codes')) CREATE INDEX idx_oauth_device_expires ON oauth_device_codes(expires_at); IF OBJECT_ID(N'oauth_par_requests', N'U') IS NULL CREATE TABLE oauth_par_requests ( id INT IDENTITY(1,1) PRIMARY KEY, request_uri NVARCHAR(255) NOT NULL UNIQUE, client_id NVARCHAR(255) NOT NULL, params NVARCHAR(MAX), created_at DATETIME2 DEFAULT SYSUTCDATETIME(), expires_at DATETIME2 NOT NULL ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_oauth_par_expires' AND object_id = OBJECT_ID(N'oauth_par_requests')) CREATE INDEX idx_oauth_par_expires ON oauth_par_requests(expires_at); IF OBJECT_ID(N'oauth_jti', N'U') IS NULL CREATE TABLE oauth_jti ( id INT IDENTITY(1,1) PRIMARY KEY, jti_key NVARCHAR(255) NOT NULL UNIQUE, expires_at DATETIME2 NOT NULL ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_oauth_jti_expires' AND object_id = OBJECT_ID(N'oauth_jti')) CREATE INDEX idx_oauth_jti_expires ON oauth_jti(expires_at); -- key_hash: SHA-256 hex -- scopes: JSON-encoded array -- meta: JSON-encoded object IF OBJECT_ID(N'user_keys', N'U') IS NULL CREATE TABLE user_keys ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, key_type NVARCHAR(50) NOT NULL, key_hash NVARCHAR(64) NOT NULL UNIQUE, name NVARCHAR(255) NOT NULL DEFAULT '', scopes NVARCHAR(MAX), meta NVARCHAR(MAX), expires_at DATETIME2, created_at DATETIME2 DEFAULT SYSUTCDATETIME(), last_used_at DATETIME2, is_active BIT DEFAULT 1, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_user_keys_user_id' AND object_id = OBJECT_ID(N'user_keys')) CREATE INDEX idx_user_keys_user_id ON user_keys(user_id); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_user_keys_key_type' AND object_id = OBJECT_ID(N'user_keys')) CREATE INDEX idx_user_keys_key_type ON user_keys(key_type); -- Optional: omit to use per-user rules only. IF OBJECT_ID(N'sec_group_members', N'U') IS NULL CREATE TABLE 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 IF OBJECT_ID(N'sec_column_rules', N'U') IS NULL CREATE TABLE sec_column_rules ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT, group_id INT, schema_name NVARCHAR(255) NOT NULL, table_name NVARCHAR(255) NOT NULL, column_path NVARCHAR(255) NOT NULL, access_type NVARCHAR(50) NOT NULL, mask_start INT DEFAULT 0, mask_end INT DEFAULT 0, mask_invert BIT DEFAULT 0, mask_char NVARCHAR(10) DEFAULT '*', extra_filters NVARCHAR(MAX), is_active BIT 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)) ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_sec_column_rules_table' AND object_id = OBJECT_ID(N'sec_column_rules')) CREATE INDEX idx_sec_column_rules_table ON sec_column_rules(schema_name, table_name); -- template: SQL fragment, e.g. user_id = {UserID} IF OBJECT_ID(N'sec_row_rules', N'U') IS NULL CREATE TABLE sec_row_rules ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT, group_id INT, schema_name NVARCHAR(255) NOT NULL, table_name NVARCHAR(255) NOT NULL, template NVARCHAR(MAX), has_block BIT NOT NULL DEFAULT 0, is_active BIT 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)) ); IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'idx_sec_row_rules_table' AND object_id = OBJECT_ID(N'sec_row_rules')) CREATE INDEX idx_sec_row_rules_table ON sec_row_rules(schema_name, table_name);