-- SQLite schema for mbkauthe. -- -- This mirrors the auth-relevant subset of docs/schema/db.sql (Postgres). -- Tables belonging to the wider MBKTech platform rather than mbkauthe -- itself (todos, notifications, categories, etc.) are intentionally not -- included here — add those separately in your own app schema if needed. -- -- Notes on the Postgres -> SQLite translation: -- * "integer GENERATED ... AS IDENTITY" -> INTEGER PRIMARY KEY AUTOINCREMENT -- * "boolean" -> INTEGER (0/1) -- * "jsonb"/"json" -> TEXT (store JSON as text) -- * "timestamp with time zone" -> TEXT (CURRENT_TIMESTAMP format) -- * "uuid DEFAULT gen_random_uuid()" -> TEXT DEFAULT (uuid v4 expression) -- * GIN indexes / COMMENT ON TABLE -> omitted (no SQLite equivalent) PRAGMA foreign_keys = ON; -- Table: Users CREATE TABLE IF NOT EXISTS "Users" ( id INTEGER PRIMARY KEY AUTOINCREMENT, "UserName" TEXT UNIQUE, "Password" TEXT DEFAULT '12345670', "Active" INTEGER DEFAULT 0, "Role" TEXT DEFAULT 'NormalUser', "HaveMailAccount" INTEGER DEFAULT 0, "AllowedApps" TEXT DEFAULT '["Portal", "mbkauthe"]', created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, last_login TEXT, "PasswordEnc" TEXT, "FullName" TEXT, "UserId" TEXT UNIQUE CHECK ("UserId" IS NULL OR length("UserId") = 9), email TEXT DEFAULT 'support@mbktech.org', "Image" TEXT DEFAULT 'https://portal.mbktech.org/icon.svg', "Bio" TEXT DEFAULT 'I am ....', "SocialAccounts" TEXT DEFAULT '{}', "Positions" TEXT DEFAULT '{"Not_Permanent": "Member Is Not Permanent"}' ); CREATE INDEX IF NOT EXISTS idx_users_active ON "Users" ("Active"); CREATE INDEX IF NOT EXISTS idx_users_email ON "Users" (email); CREATE INDEX IF NOT EXISTS idx_users_last_login ON "Users" (last_login); CREATE INDEX IF NOT EXISTS idx_users_role ON "Users" ("Role"); CREATE INDEX IF NOT EXISTS idx_users_username ON "Users" ("UserName"); CREATE INDEX IF NOT EXISTS idx_users_userid ON "Users" ("UserId"); -- Table: ApiTokens CREATE TABLE IF NOT EXISTS "ApiTokens" ( id INTEGER PRIMARY KEY AUTOINCREMENT, "UserName" TEXT NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE, "Name" TEXT NOT NULL CHECK (length(trim("Name")) > 0), "TokenHash" TEXT NOT NULL UNIQUE, "Prefix" TEXT NOT NULL, "Permissions" TEXT DEFAULT '{"scope": "read-only", "allowedApps": null}' NOT NULL, "LastUsed" TEXT, "CreatedAt" TEXT DEFAULT CURRENT_TIMESTAMP, "ExpiresAt" TEXT, CHECK ("ExpiresAt" IS NULL OR "ExpiresAt" > "CreatedAt") ); CREATE INDEX IF NOT EXISTS idx_apitokens_expires ON "ApiTokens" ("ExpiresAt") WHERE "ExpiresAt" IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_apitokens_username_created ON "ApiTokens" ("UserName", "CreatedAt" DESC); -- Table: PasswordResets CREATE TABLE IF NOT EXISTS "PasswordResets" ( id INTEGER PRIMARY KEY AUTOINCREMENT, "UserName" TEXT NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE, "resetToken" TEXT, "resetTokenExpires" TEXT, "resetAttempts" INTEGER DEFAULT 0, "lastResetAttempt" TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_password_resets_token ON "PasswordResets" ("resetToken"); -- Table: Sessions (mbkauthe's own app-session table — distinct from the -- express-session store table below, which is just "session") CREATE TABLE IF NOT EXISTS "Sessions" ( id TEXT PRIMARY KEY DEFAULT ( lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2))) || '-4' || substr(lower(hex(randomblob(2))), 2) || '-' || substr('89ab', (abs(random()) % 4) + 1, 1) || substr(lower(hex(randomblob(2))), 2) || '-' || lower(hex(randomblob(6))) ), "UserName" TEXT NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE, created_at TEXT DEFAULT CURRENT_TIMESTAMP, expires_at TEXT, meta TEXT ); CREATE INDEX IF NOT EXISTS idx_sessions_expires ON "Sessions" (expires_at) WHERE expires_at IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_sessions_user_created ON "Sessions" ("UserName", created_at); CREATE INDEX IF NOT EXISTS idx_sessions_username_expires ON "Sessions" ("UserName", expires_at); -- Table: TrustedDevices CREATE TABLE IF NOT EXISTS "TrustedDevices" ( id INTEGER PRIMARY KEY AUTOINCREMENT, "UserName" TEXT NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE, "DeviceToken" TEXT NOT NULL UNIQUE, "DeviceName" TEXT, "UserAgent" TEXT, "IpAddress" TEXT, "CreatedAt" TEXT DEFAULT CURRENT_TIMESTAMP, "ExpiresAt" TEXT NOT NULL, "LastUsed" TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_trusted_devices_expires ON "TrustedDevices" ("ExpiresAt"); CREATE INDEX IF NOT EXISTS idx_trusted_devices_username_expires ON "TrustedDevices" ("UserName", "ExpiresAt"); CREATE INDEX IF NOT EXISTS idx_trusted_devices_token_user_expires ON "TrustedDevices" ("DeviceToken", "UserName", "ExpiresAt"); -- Table: TwoFA CREATE TABLE IF NOT EXISTS "TwoFA" ( "UserName" TEXT NOT NULL PRIMARY KEY REFERENCES "Users"("UserName") ON DELETE CASCADE, "TwoFAStatus" INTEGER DEFAULT 0 NOT NULL, "TwoFASecret" TEXT ); CREATE INDEX IF NOT EXISTS idx_twofa_username_status ON "TwoFA" ("UserName", "TwoFAStatus"); -- Table: session (express-session store; see lib/session/SqliteSessionStore.js) CREATE TABLE IF NOT EXISTS "session" ( sid TEXT PRIMARY KEY, sess TEXT NOT NULL, expire TEXT NOT NULL, username TEXT REFERENCES "Users"("UserName") ON DELETE CASCADE, last_activity TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_session_expire ON "session" (expire); -- Table: user_github CREATE TABLE IF NOT EXISTS user_github ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_name TEXT REFERENCES "Users"("UserName") ON DELETE CASCADE, github_id TEXT UNIQUE, github_username TEXT, access_token TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP, installation_id INTEGER, installation_target_type TEXT ); CREATE INDEX IF NOT EXISTS idx_user_github_user_name ON user_github (user_name); -- Table: user_google CREATE TABLE IF NOT EXISTS user_google ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_name TEXT REFERENCES "Users"("UserName"), google_id TEXT UNIQUE, google_email TEXT, access_token TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); -- Seed user (hash-only). Default password is "12345678" for this sample account. -- Hash was generated using mbkauthe's "hashPassword(password, username)" function. INSERT INTO "Users" ("UserName", "PasswordEnc", "Role", "Active", "HaveMailAccount", "FullName") VALUES ('support', 'b8b10c1c9006d8c30ab81c412463c65ff6dae3293d9bfbaf5fd8e275081d0947f000a828004e2fbd3a8f6ef5a35ae3eddd4c57b00ecab376b12e607a16a57459', 'SuperAdmin', 1, 0, 'Support User') ON CONFLICT("UserName") DO NOTHING;