dns-worker 1.0.2-3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +294 -0
  3. package/dist/serverfull.mjs +11777 -0
  4. package/migrations/0000_init.sql +80 -0
  5. package/migrations/0001_move_lists_to_d1.sql +12 -0
  6. package/migrations/0002_activity_tracking.sql +7 -0
  7. package/migrations/0003_fix_profiles_cascade.sql +21 -0
  8. package/migrations/0004_system_settings.sql +12 -0
  9. package/migrations/0005_add_bloom_storage.sql +3 -0
  10. package/migrations/0006_cleanup_profiles_table.sql +18 -0
  11. package/migrations/0007_user_activity_and_totp.sql +37 -0
  12. package/migrations/0008_add_profile_key.sql +8 -0
  13. package/migrations/0009_add_profile_blooms.sql +5 -0
  14. package/migrations/0010_session_device_info.sql +3 -0
  15. package/migrations/0011_session_created_at.sql +2 -0
  16. package/migrations/0012_profile_bloom_chunks.sql +9 -0
  17. package/migrations/0013_add_session_revoked_to_activity_log.sql +29 -0
  18. package/migrations/0014_add_session_geo.sql +3 -0
  19. package/migrations/0015_add_signup_to_activity_log.sql +30 -0
  20. package/migrations/0016_add_session_rotation_counter.sql +2 -0
  21. package/migrations/0017_add_access_points.sql +31 -0
  22. package/migrations/0018_add_list_updated_at.sql +2 -0
  23. package/migrations/0019_add_list_sync_error.sql +2 -0
  24. package/migrations/0020_add_session_last_active.sql +5 -0
  25. package/migrations/0021_add_user_timezone.sql +2 -0
  26. package/migrations/0022_add_user_locale.sql +2 -0
  27. package/migrations/0023_add_user_password_version.sql +2 -0
  28. package/migrations/0024_session_pin_and_pause.sql +37 -0
  29. package/migrations/0025_add_secret_table.sql +10 -0
  30. package/migrations/0026_session_lock_config.sql +2 -0
  31. package/migrations/0027_add_session_id_to_activity_log.sql +2 -0
  32. package/migrations/0028_rename_session_id_to_session_id_hash.sql +2 -0
  33. package/migrations/0029_profile_blooms_staging.sql +12 -0
  34. package/migrations/0030_add_list_blooms.sql +7 -0
  35. package/migrations/0031_list_blooms_chunks.sql +13 -0
  36. package/migrations/0032_profile_blooms_cascade.sql +40 -0
  37. package/migrations/0033_remove_system_secrets.sql +2 -0
  38. package/migrations/0034_remove_list_entries.sql +2 -0
  39. package/migrations/0035_add_envelope_encryption_columns.sql +5 -0
  40. package/migrations/0036_add_cf_proxy_cidrs_setting.sql +7 -0
  41. package/migrations/0037_optimize_logs_composite_indexes.sql +25 -0
  42. package/migrations/0038_optimize_logs_table_and_indexes.sql +49 -0
  43. package/migrations/0039_add_lists_profile_id_index.sql +5 -0
  44. package/migrations/0040_reduce_logs_indexes_write_cost.sql +9 -0
  45. package/migrations/0041_create_log_hourly_rollups.sql +21 -0
  46. package/migrations/0042_add_rule_created_at.sql +3 -0
  47. package/migrations/0043_create_client_hourly_rollups.sql +26 -0
  48. package/migrations/0044_create_destination_hourly_rollups.sql +29 -0
  49. package/migrations/0045_transform_logs_without_rowid.sql +38 -0
  50. package/migrations/0046_add_passkeys.sql +19 -0
  51. package/migrations/0047_add_passkey_actions_to_activity_log.sql +35 -0
  52. package/migrations/0048_add_recovery_key_rotated_to_activity_log.sql +35 -0
  53. package/migrations/0049_optimize_rollups_and_add_domain_rollups.sql +26 -0
  54. package/migrations/0050_add_dest_columns_to_logs.sql +6 -0
  55. package/migrations/0051_add_registration_enabled_setting.sql +2 -0
  56. package/package.json +71 -0
  57. package/static/assets/AccountView-BvRvODjN.js +4 -0
  58. package/static/assets/AnalyticsView-DQ7hDChh.js +2 -0
  59. package/static/assets/AuthView-Bwv6hbdi.js +2 -0
  60. package/static/assets/DestinationMap-BEQNbcb-.js +1 -0
  61. package/static/assets/FilteringView-BWD3e-DS.js +1 -0
  62. package/static/assets/LogsView-kQy7qTGd.js +5 -0
  63. package/static/assets/QRCodeCanvas-X07w5x_G.js +2 -0
  64. package/static/assets/RulesView-3d5hga0L.js +1 -0
  65. package/static/assets/SettingsView-B9D0kPUF.js +1 -0
  66. package/static/assets/SetupView-D7d-Gsdr.js +1 -0
  67. package/static/assets/SwipeableDrawer-B-MfxTWM.js +1 -0
  68. package/static/assets/TrendChart-B20w7SzD.js +53 -0
  69. package/static/assets/allPaths-BEI3QvIM.js +1 -0
  70. package/static/assets/allPathsLoader-DM2JAMeC.js +1 -0
  71. package/static/assets/browser-icErLjuM.js +1 -0
  72. package/static/assets/date-C4b8kvr-.js +1 -0
  73. package/static/assets/de-DE-CXKS_pep.js +10 -0
  74. package/static/assets/es-ES-B2e3ea35.js +10 -0
  75. package/static/assets/formatCountryName-CSBD8pP8.js +1 -0
  76. package/static/assets/fr-FR-1qQHEpoW.js +10 -0
  77. package/static/assets/getFlagEmoji-_Wxhq0lT.js +1 -0
  78. package/static/assets/index-7FKlsF79.js +16 -0
  79. package/static/assets/index-DdQuGqR5.css +2 -0
  80. package/static/assets/ja-JP-CCVeJXwX.js +10 -0
  81. package/static/assets/ko-KR-bQ1okpC2.js +10 -0
  82. package/static/assets/obex_cat_eye_logo-256-CgYNvUIC.webp +0 -0
  83. package/static/assets/paths-CfqdBnl8.js +1 -0
  84. package/static/assets/paths-cc-gUzxh.js +1 -0
  85. package/static/assets/rolldown-runtime-C4Xdq4VV.js +1 -0
  86. package/static/assets/ru-RU-DeRsms_o.js +10 -0
  87. package/static/assets/services-fIMx5oSx.js +1 -0
  88. package/static/assets/splitPathsBySizeLoader-BgBwSA38.js +1 -0
  89. package/static/assets/vendor-i18next-cO7FDf_e.js +1 -0
  90. package/static/assets/vendor-icons-16-BgwRBCeU.js +1 -0
  91. package/static/assets/vendor-icons-20-X5NPN60g.js +1 -0
  92. package/static/assets/vendor-icons-other-Bme3ywH9.js +2 -0
  93. package/static/assets/vendor-react-Kt98jRfM.js +11 -0
  94. package/static/assets/vendor-ui-core-BpucEp_q.js +1 -0
  95. package/static/assets/vendor-utils-CfYGGC23.js +9 -0
  96. package/static/assets/vi-VN-BcXfqmzH.js +10 -0
  97. package/static/assets/zh-CN-D8YK7j27.js +12 -0
  98. package/static/assets/zh-TW-C2_-uaZk.js +12 -0
  99. package/static/favicon.webp +0 -0
  100. package/static/index.html +27 -0
  101. package/static/manifest.webmanifest +1 -0
  102. package/static/registerSW.js +1 -0
  103. package/static/sw.js +1 -0
@@ -0,0 +1,80 @@
1
+ -- Profiles table
2
+ CREATE TABLE IF NOT EXISTS profiles (
3
+ id TEXT PRIMARY KEY, -- 6-char alphanumeric ID
4
+ name TEXT NOT NULL,
5
+ settings TEXT NOT NULL, -- JSON object: { upstream, ecs, log_retention, default_policy }
6
+ owner_id TEXT REFERENCES users(id),
7
+ created_at INTEGER NOT NULL,
8
+ updated_at INTEGER NOT NULL
9
+ );
10
+
11
+ -- Users table
12
+ CREATE TABLE IF NOT EXISTS users (
13
+ id TEXT PRIMARY KEY,
14
+ username TEXT NOT NULL UNIQUE COLLATE NOCASE,
15
+ hashed_password TEXT NOT NULL,
16
+ role TEXT DEFAULT 'user' CHECK(role IN ('admin', 'user')),
17
+ created_at INTEGER NOT NULL
18
+ );
19
+
20
+ -- Sessions table (Lucia v3 requirement)
21
+ CREATE TABLE IF NOT EXISTS sessions (
22
+ id TEXT PRIMARY KEY,
23
+ user_id TEXT NOT NULL,
24
+ expires_at INTEGER NOT NULL,
25
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
26
+ );
27
+
28
+ -- Custom Rules table
29
+ CREATE TABLE IF NOT EXISTS rules (
30
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
31
+ profile_id TEXT NOT NULL,
32
+ type TEXT CHECK(type IN ('ALLOW', 'BLOCK', 'REDIRECT')) NOT NULL,
33
+ pattern TEXT NOT NULL, -- Domain or wildcard
34
+ value TEXT, -- legacy redirect value (optional)
35
+ record_type TEXT DEFAULT 'A',
36
+ v_a TEXT,
37
+ v_aaaa TEXT,
38
+ v_txt TEXT,
39
+ v_cname TEXT,
40
+ priority INTEGER DEFAULT 0,
41
+ FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
42
+ );
43
+
44
+ -- External Blocklists subscriptions
45
+ CREATE TABLE IF NOT EXISTS lists (
46
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
47
+ profile_id TEXT NOT NULL,
48
+ url TEXT NOT NULL,
49
+ enabled INTEGER DEFAULT 1,
50
+ last_synced_at INTEGER,
51
+ FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
52
+ );
53
+
54
+ -- Resolution Logs table
55
+ CREATE TABLE IF NOT EXISTS logs (
56
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
57
+ profile_id TEXT NOT NULL,
58
+ timestamp INTEGER NOT NULL, -- Unix timestamp
59
+ client_ip TEXT,
60
+ geo_country TEXT, -- ISO 3166-1 alpha-2
61
+ domain TEXT NOT NULL,
62
+ record_type TEXT NOT NULL,
63
+ action TEXT CHECK(action IN ('PASS', 'BLOCK', 'REDIRECT', 'FAIL')) NOT NULL,
64
+ reason TEXT, -- Rule ID or List Name
65
+ answer TEXT,
66
+ dest_geoip TEXT,
67
+ ecs TEXT,
68
+ upstream TEXT,
69
+ latency INTEGER,
70
+ FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
71
+ );
72
+
73
+ -- Indexes
74
+ CREATE INDEX IF NOT EXISTS idx_rules_profile ON rules(profile_id);
75
+ CREATE INDEX IF NOT EXISTS idx_lists_profile ON lists(profile_id);
76
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_time ON logs(profile_id, timestamp);
77
+ CREATE INDEX IF NOT EXISTS idx_logs_domain ON logs(domain);
78
+ CREATE INDEX IF NOT EXISTS idx_users_username ON users(username COLLATE NOCASE);
79
+ CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
80
+ CREATE INDEX IF NOT EXISTS idx_profiles_owner ON profiles(owner_id);
@@ -0,0 +1,12 @@
1
+ -- Migration to move external list data from KV to D1
2
+ CREATE TABLE IF NOT EXISTS list_entries (
3
+ profile_id TEXT NOT NULL,
4
+ domain TEXT NOT NULL,
5
+ PRIMARY KEY (profile_id, domain)
6
+ );
7
+
8
+ CREATE INDEX IF NOT EXISTS idx_list_entries_profile ON list_entries(profile_id);
9
+
10
+ -- We also need a way to store the Bloom Filter and sync metadata in D1
11
+ ALTER TABLE profiles ADD COLUMN list_bloom BLOB;
12
+ ALTER TABLE profiles ADD COLUMN list_updated_at INTEGER;
@@ -0,0 +1,7 @@
1
+ -- Add activity tracking
2
+ ALTER TABLE users ADD COLUMN last_active_at INTEGER;
3
+ ALTER TABLE profiles ADD COLUMN last_active_at INTEGER;
4
+
5
+ -- Initialize existing records
6
+ UPDATE users SET last_active_at = created_at WHERE last_active_at IS NULL;
7
+ UPDATE profiles SET last_active_at = created_at WHERE last_active_at IS NULL;
@@ -0,0 +1,21 @@
1
+ -- Fix profiles foreign key to support ON DELETE CASCADE
2
+ -- 1. Create a temporary table with the correct constraint
3
+ CREATE TABLE profiles_new (
4
+ id TEXT PRIMARY KEY,
5
+ name TEXT NOT NULL,
6
+ settings TEXT NOT NULL,
7
+ owner_id TEXT REFERENCES users(id) ON DELETE CASCADE,
8
+ created_at INTEGER NOT NULL,
9
+ updated_at INTEGER NOT NULL,
10
+ last_active_at INTEGER
11
+ );
12
+
13
+ -- 2. Copy data from old table
14
+ INSERT INTO profiles_new SELECT id, name, settings, owner_id, created_at, updated_at, last_active_at FROM profiles;
15
+
16
+ -- 3. Drop old table and rename new one
17
+ DROP TABLE profiles;
18
+ ALTER TABLE profiles_new RENAME TO profiles;
19
+
20
+ -- 4. Re-create indexes for the new table
21
+ CREATE INDEX IF NOT EXISTS idx_profiles_owner ON profiles(owner_id);
@@ -0,0 +1,12 @@
1
+ -- 系统全局配置表
2
+ CREATE TABLE IF NOT EXISTS system_settings (
3
+ key TEXT PRIMARY KEY,
4
+ value TEXT NOT NULL,
5
+ updated_at INTEGER NOT NULL
6
+ );
7
+
8
+ -- 预设默认值
9
+ INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES ('turnstile_site_key', '', 0);
10
+ INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES ('turnstile_secret_key', '', 0);
11
+ INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES ('turnstile_enabled_signup', 'false', 0);
12
+ INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES ('turnstile_enabled_login', 'false', 0);
@@ -0,0 +1,3 @@
1
+ -- Add Bloom filter storage to profiles table
2
+ ALTER TABLE profiles ADD COLUMN list_bloom TEXT;
3
+ ALTER TABLE profiles ADD COLUMN list_updated_at INTEGER;
@@ -0,0 +1,18 @@
1
+ -- Remove Bloom filter columns from profiles table (deprecating migration 0005 approach)
2
+ -- SQLite doesn't support DROP COLUMN, so we recreate the table
3
+ CREATE TABLE profiles_temp (
4
+ id TEXT PRIMARY KEY,
5
+ owner_id TEXT REFERENCES users(id) ON DELETE CASCADE,
6
+ name TEXT NOT NULL,
7
+ settings TEXT NOT NULL,
8
+ created_at INTEGER NOT NULL,
9
+ updated_at INTEGER NOT NULL,
10
+ last_active_at INTEGER
11
+ );
12
+
13
+ INSERT INTO profiles_temp SELECT id, owner_id, name, settings, created_at, updated_at, last_active_at FROM profiles;
14
+
15
+ DROP TABLE profiles;
16
+ ALTER TABLE profiles_temp RENAME TO profiles;
17
+
18
+ CREATE INDEX IF NOT EXISTS idx_profiles_owner ON profiles(owner_id);
@@ -0,0 +1,37 @@
1
+ -- 用户安全活动日志
2
+ CREATE TABLE IF NOT EXISTS user_activity_log (
3
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
4
+ user_id TEXT NOT NULL,
5
+ action TEXT NOT NULL CHECK(action IN (
6
+ 'login_success', 'login_fail', 'logout',
7
+ 'password_change_success', 'password_change_fail',
8
+ 'totp_verify_success', 'totp_verify_fail',
9
+ 'totp_setup', 'totp_removed',
10
+ 'recovery_key_used'
11
+ )),
12
+ ip_address TEXT,
13
+ user_agent TEXT,
14
+ timestamp INTEGER NOT NULL,
15
+ extra TEXT, -- JSON 附加信息 (如失败原因)
16
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
17
+ );
18
+
19
+ -- 待验证的 TOTP 临时会话 (密码验证通过,等待 TOTP 验证)
20
+ CREATE TABLE IF NOT EXISTS pending_totp_sessions (
21
+ id TEXT PRIMARY KEY,
22
+ user_id TEXT NOT NULL,
23
+ expires_at INTEGER NOT NULL,
24
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
25
+ );
26
+
27
+ -- TOTP 相关字段
28
+ ALTER TABLE users ADD COLUMN totp_secret TEXT;
29
+ ALTER TABLE users ADD COLUMN totp_enabled INTEGER DEFAULT 0;
30
+ -- 跳过密码模式:启用后登录只需 TOTP,不需要密码
31
+ ALTER TABLE users ADD COLUMN totp_skip_password INTEGER DEFAULT 0;
32
+ -- 恢复密钥:JSON 数组,存储 SHA-256 哈希后的 8 个一次性密钥
33
+ ALTER TABLE users ADD COLUMN totp_recovery_keys TEXT;
34
+
35
+ -- 索引
36
+ CREATE INDEX IF NOT EXISTS idx_activity_log_user ON user_activity_log(user_id, timestamp DESC);
37
+ CREATE INDEX IF NOT EXISTS idx_pending_totp_expires ON pending_totp_sessions(expires_at);
@@ -0,0 +1,8 @@
1
+ -- Add profile_key column to profiles table
2
+ ALTER TABLE profiles ADD COLUMN profile_key TEXT;
3
+
4
+ -- Backfill profile_key with existing id for backward compatibility
5
+ UPDATE profiles SET profile_key = LOWER(id) WHERE profile_key IS NULL;
6
+
7
+ -- Create unique index
8
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_profiles_key ON profiles(profile_key);
@@ -0,0 +1,5 @@
1
+ CREATE TABLE IF NOT EXISTS profile_blooms (
2
+ profile_id TEXT PRIMARY KEY,
3
+ bloom_filter BLOB,
4
+ updated_at INTEGER NOT NULL
5
+ );
@@ -0,0 +1,3 @@
1
+ -- Add device info to sessions table
2
+ ALTER TABLE sessions ADD COLUMN ip_address TEXT;
3
+ ALTER TABLE sessions ADD COLUMN user_agent TEXT;
@@ -0,0 +1,2 @@
1
+ -- Add created_at to sessions table
2
+ ALTER TABLE sessions ADD COLUMN created_at INTEGER;
@@ -0,0 +1,9 @@
1
+ DROP TABLE IF EXISTS profile_blooms;
2
+
3
+ CREATE TABLE profile_blooms (
4
+ profile_id TEXT NOT NULL,
5
+ chunk_index INTEGER NOT NULL,
6
+ bloom_filter_chunk BLOB NOT NULL,
7
+ updated_at INTEGER NOT NULL,
8
+ PRIMARY KEY (profile_id, chunk_index)
9
+ );
@@ -0,0 +1,29 @@
1
+ PRAGMA foreign_keys=off;
2
+
3
+ CREATE TABLE user_activity_log_new (
4
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
5
+ user_id TEXT NOT NULL,
6
+ action TEXT NOT NULL CHECK(action IN (
7
+ 'login_success', 'login_fail', 'logout',
8
+ 'password_change_success', 'password_change_fail',
9
+ 'totp_verify_success', 'totp_verify_fail',
10
+ 'totp_setup', 'totp_removed',
11
+ 'recovery_key_used',
12
+ 'session_revoked'
13
+ )),
14
+ ip_address TEXT,
15
+ user_agent TEXT,
16
+ timestamp INTEGER NOT NULL,
17
+ extra TEXT,
18
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
19
+ );
20
+
21
+ INSERT INTO user_activity_log_new SELECT * FROM user_activity_log;
22
+
23
+ DROP TABLE user_activity_log;
24
+
25
+ ALTER TABLE user_activity_log_new RENAME TO user_activity_log;
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_activity_log_user ON user_activity_log(user_id, timestamp DESC);
28
+
29
+ PRAGMA foreign_keys=on;
@@ -0,0 +1,3 @@
1
+ -- Add latitude and longitude columns to sessions table
2
+ ALTER TABLE sessions ADD COLUMN latitude REAL;
3
+ ALTER TABLE sessions ADD COLUMN longitude REAL;
@@ -0,0 +1,30 @@
1
+ PRAGMA foreign_keys=off;
2
+
3
+ CREATE TABLE user_activity_log_new (
4
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
5
+ user_id TEXT NOT NULL,
6
+ action TEXT NOT NULL CHECK(action IN (
7
+ 'signup',
8
+ 'login_success', 'login_fail', 'logout',
9
+ 'password_change_success', 'password_change_fail',
10
+ 'totp_verify_success', 'totp_verify_fail',
11
+ 'totp_setup', 'totp_removed',
12
+ 'recovery_key_used',
13
+ 'session_revoked'
14
+ )),
15
+ ip_address TEXT,
16
+ user_agent TEXT,
17
+ timestamp INTEGER NOT NULL,
18
+ extra TEXT,
19
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
20
+ );
21
+
22
+ INSERT INTO user_activity_log_new SELECT * FROM user_activity_log;
23
+
24
+ DROP TABLE user_activity_log;
25
+
26
+ ALTER TABLE user_activity_log_new RENAME TO user_activity_log;
27
+
28
+ CREATE INDEX IF NOT EXISTS idx_activity_log_user ON user_activity_log(user_id, timestamp DESC);
29
+
30
+ PRAGMA foreign_keys=on;
@@ -0,0 +1,2 @@
1
+ -- Add rotation_counter for Refresh Token Rotation (RTR) tracking
2
+ ALTER TABLE sessions ADD COLUMN rotation_counter INTEGER DEFAULT 0;
@@ -0,0 +1,31 @@
1
+ -- Create access_points table
2
+ CREATE TABLE IF NOT EXISTS access_points (
3
+ id TEXT PRIMARY KEY,
4
+ profile_id TEXT NOT NULL,
5
+ name TEXT NOT NULL,
6
+ token TEXT NOT NULL UNIQUE,
7
+ created_at INTEGER NOT NULL,
8
+ updated_at INTEGER NOT NULL,
9
+ FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
10
+ );
11
+
12
+ CREATE INDEX IF NOT EXISTS idx_access_points_profile ON access_points(profile_id);
13
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_access_points_token ON access_points(token);
14
+
15
+ -- Backfill access points for existing profiles
16
+ -- We use lower(hex(randomblob(6))) to generate a 12-character hex ID for each existing profile's default access point
17
+ INSERT INTO access_points (id, profile_id, name, token, created_at, updated_at)
18
+ SELECT
19
+ lower(hex(randomblob(6))),
20
+ id,
21
+ 'Device-1',
22
+ COALESCE(profile_key, lower(id)),
23
+ created_at,
24
+ updated_at
25
+ FROM profiles;
26
+
27
+ -- Alter logs table to add access_point_id
28
+ ALTER TABLE logs ADD COLUMN access_point_id TEXT REFERENCES access_points(id) ON DELETE SET NULL;
29
+
30
+ -- Create an index to speed up log filtering by access point
31
+ CREATE INDEX IF NOT EXISTS idx_logs_access_point ON logs(access_point_id);
@@ -0,0 +1,2 @@
1
+ -- Add list_updated_at column to profiles table
2
+ ALTER TABLE profiles ADD COLUMN list_updated_at INTEGER;
@@ -0,0 +1,2 @@
1
+ -- Add sync_error column to lists table
2
+ ALTER TABLE lists ADD COLUMN sync_error TEXT;
@@ -0,0 +1,5 @@
1
+ -- Add last_active_at to sessions table for idle timeout tracking
2
+ ALTER TABLE sessions ADD COLUMN last_active_at INTEGER;
3
+
4
+ -- Initialize last_active_at for existing sessions
5
+ UPDATE sessions SET last_active_at = COALESCE(created_at, expires_at - 86400) WHERE last_active_at IS NULL;
@@ -0,0 +1,2 @@
1
+ -- Add timezone column to users table for customized time formatting
2
+ ALTER TABLE users ADD COLUMN timezone TEXT DEFAULT NULL;
@@ -0,0 +1,2 @@
1
+ -- Add locale column to users table for customized date and time formatting
2
+ ALTER TABLE users ADD COLUMN locale TEXT DEFAULT 'en-US';
@@ -0,0 +1,2 @@
1
+ -- Add password_version to users table
2
+ ALTER TABLE users ADD COLUMN password_version INTEGER DEFAULT 1;
@@ -0,0 +1,37 @@
1
+ -- Add pin_hash to users
2
+ ALTER TABLE users ADD COLUMN pin_hash TEXT;
3
+
4
+ -- Add is_paused to sessions
5
+ ALTER TABLE sessions ADD COLUMN is_paused INTEGER DEFAULT 0;
6
+
7
+ -- Recreate user_activity_log to support pin_verify_success and pin_verify_fail check constraints
8
+ CREATE TABLE IF NOT EXISTS user_activity_log_new (
9
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ user_id TEXT NOT NULL,
11
+ action TEXT NOT NULL CHECK(action IN (
12
+ 'signup', 'login_success', 'login_fail', 'logout',
13
+ 'password_change_success', 'password_change_fail',
14
+ 'totp_verify_success', 'totp_verify_fail',
15
+ 'totp_setup', 'totp_removed',
16
+ 'recovery_key_used', 'session_revoked',
17
+ 'pin_verify_success', 'pin_verify_fail'
18
+ )),
19
+ ip_address TEXT,
20
+ user_agent TEXT,
21
+ timestamp INTEGER NOT NULL,
22
+ extra TEXT,
23
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
24
+ );
25
+
26
+ -- Copy data
27
+ INSERT INTO user_activity_log_new (id, user_id, action, ip_address, user_agent, timestamp, extra)
28
+ SELECT id, user_id, action, ip_address, user_agent, timestamp, extra FROM user_activity_log;
29
+
30
+ -- Drop old table
31
+ DROP TABLE user_activity_log;
32
+
33
+ -- Rename new table
34
+ ALTER TABLE user_activity_log_new RENAME TO user_activity_log;
35
+
36
+ -- Recreate index
37
+ CREATE INDEX IF NOT EXISTS idx_activity_log_user ON user_activity_log(user_id, timestamp DESC);
@@ -0,0 +1,10 @@
1
+ -- Create system_secrets table
2
+ CREATE TABLE IF NOT EXISTS system_secrets (
3
+ key TEXT PRIMARY KEY,
4
+ value TEXT NOT NULL,
5
+ updated_at INTEGER NOT NULL
6
+ );
7
+
8
+ -- Migrate existing jwt_secret from system_settings if it exists
9
+ INSERT OR IGNORE INTO system_secrets (key, value, updated_at)
10
+ SELECT key, value, updated_at FROM system_settings WHERE key = 'jwt_secret';
@@ -0,0 +1,2 @@
1
+ -- Add session_lock_timeout to users table
2
+ ALTER TABLE users ADD COLUMN session_lock_timeout INTEGER DEFAULT 15;
@@ -0,0 +1,2 @@
1
+ -- Add session_id column to user_activity_log table
2
+ ALTER TABLE user_activity_log ADD COLUMN session_id TEXT;
@@ -0,0 +1,2 @@
1
+ -- Rename session_id column to session_id_hash in user_activity_log table
2
+ ALTER TABLE user_activity_log RENAME COLUMN session_id TO session_id_hash;
@@ -0,0 +1,12 @@
1
+ -- A/B Bloom Filter upgrade: staging table holds the bloom currently being built.
2
+ -- Active DNS queries always read from profile_blooms (the fully-completed bloom).
3
+ -- Once all lists in a sync cycle are done, the staging bloom is atomically
4
+ -- promoted to profile_blooms, eliminating partial/inconsistent states during sync.
5
+
6
+ CREATE TABLE IF NOT EXISTS profile_blooms_staging (
7
+ profile_id TEXT NOT NULL,
8
+ chunk_index INTEGER NOT NULL,
9
+ bloom_filter_chunk BLOB NOT NULL,
10
+ updated_at INTEGER NOT NULL,
11
+ PRIMARY KEY (profile_id, chunk_index)
12
+ );
@@ -0,0 +1,7 @@
1
+ -- Migration to add list-level Bloom Filters
2
+ CREATE TABLE IF NOT EXISTS list_blooms (
3
+ list_id INTEGER PRIMARY KEY,
4
+ bloom_filter BLOB NOT NULL,
5
+ updated_at INTEGER NOT NULL,
6
+ FOREIGN KEY (list_id) REFERENCES lists(id) ON DELETE CASCADE
7
+ );
@@ -0,0 +1,13 @@
1
+ -- Migration to recreate list_blooms with chunking support
2
+ DROP TABLE IF EXISTS list_blooms;
3
+
4
+ CREATE TABLE list_blooms (
5
+ list_id INTEGER NOT NULL,
6
+ chunk_index INTEGER NOT NULL,
7
+ bloom_filter_chunk BLOB NOT NULL,
8
+ updated_at INTEGER NOT NULL,
9
+ PRIMARY KEY (list_id, chunk_index),
10
+ FOREIGN KEY (list_id) REFERENCES lists(id) ON DELETE CASCADE
11
+ );
12
+
13
+ CREATE INDEX IF NOT EXISTS idx_list_blooms_list ON list_blooms(list_id);
@@ -0,0 +1,40 @@
1
+ -- Recreate profile_blooms with foreign key cascade constraint
2
+ CREATE TABLE IF NOT EXISTS profile_blooms_new (
3
+ profile_id TEXT NOT NULL,
4
+ chunk_index INTEGER NOT NULL,
5
+ bloom_filter_chunk BLOB NOT NULL,
6
+ updated_at INTEGER NOT NULL,
7
+ PRIMARY KEY (profile_id, chunk_index),
8
+ FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
9
+ );
10
+
11
+ -- Copy existing data, automatically filter out orphan records
12
+ INSERT INTO profile_blooms_new (profile_id, chunk_index, bloom_filter_chunk, updated_at)
13
+ SELECT pb.profile_id, pb.chunk_index, pb.bloom_filter_chunk, pb.updated_at
14
+ FROM profile_blooms pb
15
+ INNER JOIN profiles p ON pb.profile_id = p.id;
16
+
17
+ DROP TABLE profile_blooms;
18
+ ALTER TABLE profile_blooms_new RENAME TO profile_blooms;
19
+
20
+ -- Recreate profile_blooms_staging with foreign key cascade constraint
21
+ CREATE TABLE IF NOT EXISTS profile_blooms_staging_new (
22
+ profile_id TEXT NOT NULL,
23
+ chunk_index INTEGER NOT NULL,
24
+ bloom_filter_chunk BLOB NOT NULL,
25
+ updated_at INTEGER NOT NULL,
26
+ PRIMARY KEY (profile_id, chunk_index),
27
+ FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
28
+ );
29
+
30
+ -- Copy existing data, automatically filter out orphan records
31
+ INSERT INTO profile_blooms_staging_new (profile_id, chunk_index, bloom_filter_chunk, updated_at)
32
+ SELECT pbs.profile_id, pbs.chunk_index, pbs.bloom_filter_chunk, pbs.updated_at
33
+ FROM profile_blooms_staging pbs
34
+ INNER JOIN profiles p ON pbs.profile_id = p.id;
35
+
36
+ DROP TABLE profile_blooms_staging;
37
+ ALTER TABLE profile_blooms_staging_new RENAME TO profile_blooms_staging;
38
+
39
+ -- Clean up legacy jwt_secret from system_settings table
40
+ DELETE FROM system_settings WHERE key = 'jwt_secret';
@@ -0,0 +1,2 @@
1
+ -- Drop system_secrets table
2
+ DROP TABLE IF EXISTS system_secrets;
@@ -0,0 +1,2 @@
1
+ -- Drop obsolete list_entries table
2
+ DROP TABLE IF EXISTS list_entries;
@@ -0,0 +1,5 @@
1
+ -- Migration to add Envelope Encryption columns for user TOTP secrets
2
+ ALTER TABLE users ADD COLUMN totp_secret_encrypted TEXT;
3
+ ALTER TABLE users ADD COLUMN totp_secret_dek TEXT;
4
+ ALTER TABLE users ADD COLUMN totp_recovery_keys_encrypted TEXT;
5
+ ALTER TABLE users ADD COLUMN totp_recovery_keys_dek TEXT;
@@ -0,0 +1,7 @@
1
+ -- 初始化 Cloudflare Proxy CIDR 列表至系统配置表
2
+ INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES (
3
+ 'cf_proxy_cidrs',
4
+ '["173.245.48.0/20","103.21.244.0/22","103.22.200.0/22","103.31.4.0/22","141.101.64.0/18","108.162.192.0/18","190.93.240.0/20","188.114.96.0/20","197.234.240.0/22","198.41.128.0/17","162.158.0.0/15","104.16.0.0/13","104.24.0.0/14","172.64.0.0/13","131.0.72.0/22","2400:cb00::/32","2606:4700::/32","2803:f800::/32","2405:b500::/32","2405:8100::/32","2a06:98c0::/29","2c0f:f248::/32"]',
5
+ 0
6
+ );
7
+ INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES ('cf_proxy_cidrs_updated_at', '0', 0);
@@ -0,0 +1,25 @@
1
+ -- 1. Composite indexes for high-frequency log filtering with time ordering
2
+ -- Access point filtering
3
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_ap_time ON logs(profile_id, access_point_id, timestamp);
4
+
5
+ -- Action / status filtering (PASS, BLOCK, etc.)
6
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_action_time ON logs(profile_id, action, timestamp);
7
+
8
+ -- Client geo-country filtering
9
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_geo_time ON logs(profile_id, geo_country, timestamp);
10
+
11
+ -- Block / allow reason filtering
12
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_reason_time ON logs(profile_id, reason, timestamp);
13
+
14
+ -- Exact domain filtering per profile
15
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_domain_time ON logs(profile_id, domain, timestamp);
16
+
17
+ -- DNS record type filtering (A, AAAA, HTTPS, etc.)
18
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_record_type_time ON logs(profile_id, record_type, timestamp);
19
+
20
+ -- 2. System indexes to prevent full-table scans during scheduled jobs
21
+ -- Profiles list sync ordering index (eliminates full table scan on cron list sync)
22
+ CREATE INDEX IF NOT EXISTS idx_profiles_list_updated ON profiles(list_updated_at);
23
+
24
+ -- Sessions expiration range index (eliminates full table scan on expired sessions purge)
25
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
@@ -0,0 +1,49 @@
1
+ -- 1. Create the new optimized logs table
2
+ -- Improvements:
3
+ -- - Removed AUTOINCREMENT: uses SQLite native 64-bit rowid, eliminating 1 read and 1 write to sqlite_sequence per insert
4
+ -- - Removed FOREIGN KEY constraints: eliminates 2 index-seek reads to profiles and access_points per insert
5
+ CREATE TABLE IF NOT EXISTS logs_new (
6
+ id INTEGER PRIMARY KEY,
7
+ profile_id TEXT NOT NULL,
8
+ timestamp INTEGER NOT NULL,
9
+ client_ip TEXT,
10
+ geo_country TEXT,
11
+ domain TEXT NOT NULL,
12
+ record_type TEXT NOT NULL,
13
+ action TEXT CHECK(action IN ('PASS', 'BLOCK', 'REDIRECT', 'FAIL')) NOT NULL,
14
+ reason TEXT,
15
+ answer TEXT,
16
+ dest_geoip TEXT,
17
+ ecs TEXT,
18
+ upstream TEXT,
19
+ latency INTEGER,
20
+ access_point_id TEXT
21
+ );
22
+
23
+ -- 2. Migrate unexpired logs within 90-day retention window
24
+ -- (Automatically purges expired records, reducing database disk size)
25
+ INSERT INTO logs_new (id, profile_id, timestamp, client_ip, geo_country, domain, record_type, action, reason, answer, dest_geoip, ecs, upstream, latency, access_point_id)
26
+ SELECT id, profile_id, timestamp, client_ip, geo_country, domain, record_type, action, reason, answer, dest_geoip, ecs, upstream, latency, access_point_id
27
+ FROM logs
28
+ WHERE timestamp >= (strftime('%s', 'now') - 7776000);
29
+
30
+ -- 3. Replace logs table
31
+ DROP TABLE logs;
32
+ ALTER TABLE logs_new RENAME TO logs;
33
+
34
+ -- 4. Clean up sqlite_sequence entry if present
35
+ DELETE FROM sqlite_sequence WHERE name = 'logs';
36
+
37
+ -- 5. Create strictly curated composite indexes (3 core indexes)
38
+ -- Baseline time index for default profile queries and range cleanup
39
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_time ON logs(profile_id, timestamp);
40
+
41
+ -- Device-level time index for access point filtering
42
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_ap_time ON logs(profile_id, access_point_id, timestamp);
43
+
44
+ -- Action-level time index for ALLOW / BLOCK filtering and top stats
45
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_action_time ON logs(profile_id, action, timestamp);
46
+
47
+ -- 6. System indexes to prevent full-table scans in background jobs
48
+ CREATE INDEX IF NOT EXISTS idx_profiles_list_updated ON profiles(list_updated_at);
49
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
@@ -0,0 +1,5 @@
1
+ -- Migration 0039: Add indexes to optimize list sync target queries
2
+ -- Accelerates list sync target lookup in profiles and existence check on lists
3
+
4
+ CREATE INDEX IF NOT EXISTS idx_profiles_list_updated_at ON profiles(list_updated_at);
5
+ CREATE INDEX IF NOT EXISTS idx_lists_profile_id ON lists(profile_id);
@@ -0,0 +1,9 @@
1
+ -- Migration 0040: Reduce logs index write amplification from 4 to 2 rows per insert
2
+ -- Drops secondary action and access point indexes; retains single primary (profile_id, timestamp) index.
3
+ -- This reduces D1 write rows per DNS log insert/delete by 50% and lowers database storage usage.
4
+
5
+ DROP INDEX IF EXISTS idx_logs_profile_ap_time;
6
+ DROP INDEX IF EXISTS idx_logs_profile_action_time;
7
+
8
+ -- Ensure baseline profile time index is present for time-range filtering, pagination, and cleanup
9
+ CREATE INDEX IF NOT EXISTS idx_logs_profile_time ON logs(profile_id, timestamp);
@@ -0,0 +1,21 @@
1
+ -- Migration 0041: Create log_hourly_rollups table for high-performance analytics aggregation
2
+ -- Reduces D1 read row scans on analytics queries (getSummary, getTrend) by >98%
3
+
4
+ CREATE TABLE IF NOT EXISTS log_hourly_rollups (
5
+ profile_id TEXT NOT NULL,
6
+ hour_timestamp INTEGER NOT NULL,
7
+ action TEXT NOT NULL,
8
+ count INTEGER NOT NULL DEFAULT 0,
9
+ PRIMARY KEY (profile_id, hour_timestamp, action)
10
+ );
11
+
12
+ -- Backfill completed historical hours older than current hour
13
+ INSERT OR REPLACE INTO log_hourly_rollups (profile_id, hour_timestamp, action, count)
14
+ SELECT
15
+ profile_id,
16
+ (timestamp / 3600) * 3600 AS hour_timestamp,
17
+ action,
18
+ COUNT(*) AS count
19
+ FROM logs
20
+ WHERE timestamp < (CAST(strftime('%s', 'now') AS INTEGER) / 3600) * 3600
21
+ GROUP BY profile_id, (timestamp / 3600) * 3600, action;