File size: 11,336 Bytes
9b403c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// 在本地嵌入Postgres中验证真实SQL权限;不连接外部项目或创建实际用户。
// MESSAGE_PGLITE_MODULE可指向工作区外临时安装的@electric-sql/pglite,避免新增生产依赖。
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'

const { PGlite } = await import(process.env.MESSAGE_PGLITE_MODULE || '@electric-sql/pglite')
const db = new PGlite()
const admin = '11111111-1111-4111-8111-111111111111'
const outsider = '22222222-2222-4222-8222-222222222222'
const adminSession = '33333333-3333-4333-8333-333333333333'
const outsiderSession = '44444444-4444-4444-8444-444444444444'
const keyId = '55555555-5555-4555-8555-555555555555'
const keyHash = 'a'.repeat(64)
let passed = 0

async function check(name, fn) {
  await fn()
  passed += 1
  console.log(`通过:${name}`)
}
async function scalar(sql, params = []) {
  const result = await db.query(sql, params)
  return Object.values(result.rows[0])[0]
}
async function login(user = admin, session = adminSession, method = 'password') {
  await db.exec('reset role; set role authenticated')
  await db.query('select set_config($1,$2,false)', ['request.jwt.claims', JSON.stringify({
    sub: user, session_id: session, role: 'authenticated', is_anonymous: false, amr: [{ method }],
  })])
}
async function anonymous() {
  await db.exec('reset role; set role anon')
  await db.query('select set_config($1,$2,false)', ['request.jwt.claims', '{}'])
}
async function denied(sql, params = []) {
  await assert.rejects(() => db.query(sql, params), error => error.code === '42501')
}

try {
  // 仅模拟Supabase系统表的必要结构,应用的表、函数、RLS与grant来自实际迁移。
  await db.exec(`
    create role anon; create role authenticated; create role service_role;
    create schema auth;
    create table auth.users(id uuid primary key, is_anonymous boolean default false, banned_until timestamptz);
    create table auth.sessions(id uuid primary key, user_id uuid, not_after timestamptz, created_at timestamptz default now());
    create function auth.jwt() returns jsonb language sql stable as
      'select coalesce(nullif(current_setting(''request.jwt.claims'',true),''''),''{}'')::jsonb';
    create function auth.uid() returns uuid language sql stable as 'select (auth.jwt()->>''sub'')::uuid';
  `)
  await db.exec(await readFile(new URL('../../supabase/migrations/20261001030957_message_auth_keys.sql', import.meta.url), 'utf8'))
  await db.query('insert into auth.users(id) values ($1),($2)', [admin, outsider])
  await db.query('insert into auth.sessions(id,user_id) values ($1,$2),($3,$4)', [adminSession, admin, outsiderSession, outsider])
  await db.query('insert into public.message_admins(user_id) values ($1)', [admin])

  await check('所有Message表启用RLS', async () => {
    const rows = (await db.query("select relrowsecurity from pg_class where relname in ('message_admins','message_api_keys','message_auth_audit')")).rows
    assert.equal(rows.length, 3)
    assert.ok(rows.every(row => row.relrowsecurity))
  })
  await check('public RPC无SECURITY DEFINER', async () => {
    assert.equal(await scalar("select count(*)::integer from pg_proc p join pg_namespace n on p.pronamespace=n.oid where n.nspname='public' and p.proname like 'message_%' and p.prosecdef"), 0)
  })
  await anonymous()
  await check('匿名不能读取哈希或授予管理员', async () => {
    await denied('select key_hash from public.message_api_keys')
    await denied('insert into public.message_admins(user_id) values ($1)', [outsider])
    await denied('select public.message_current_admin()')
  })
  await check('匿名校验错误哈希只返回null', async () => {
    assert.equal(await scalar('select public.message_validate_api_key($1,$2)', ['alpha', 'invalid']), null)
  })
  await login(outsider, outsiderSession)
  await check('普通注册用户无管理员身份与管理能力', async () => {
    assert.equal(await scalar('select public.message_current_admin()'), false)
    await denied('select public.message_list_api_keys($1)', ['alpha'])
    await denied('insert into public.message_admins(user_id) values ($1)', [outsider])
  })
  await login(admin, outsiderSession)
  await check('其他用户的session不能绑定管理员', async () => {
    assert.equal(await scalar('select public.message_current_admin()'), false)
  })
  await login()
  await check('显式管理员与有效session通过身份检查', async () => {
    assert.equal(await scalar('select public.message_current_admin()'), true)
    assert.equal(await scalar('select public.message_recovery_admin()'), false)
  })
  await check('管理员也不能直接读取key_hash或修改membership', async () => {
    await denied('select key_hash from public.message_api_keys')
    await denied('delete from public.message_admins')
  })
  await check('密钥创建输出不含哈希且有效期受限', async () => {
    const create = 'select public.message_create_api_key($1,$2,$3,$4,$5,$6)'
    const params = [keyId, 'alpha', 'fixture', 'msg_key.55555555', keyHash, 30]
    const created = await scalar(create, params)
    assert.equal(created.id, keyId)
    assert.equal(created.expires_at - created.created_at, 30 * 86400)
    assert.equal(Object.hasOwn(created, 'key_hash'), false)
    assert.equal(Object.hasOwn(created, 'key'), false)
    for (const days of [0, 366, null]) {
      await assert.rejects(() => scalar(create, [...params.slice(0, 5), days]), error => error.code === '22023')
    }
  })
  await check('列表只有当前profile且不泄露哈希', async () => {
    const rows = await scalar('select public.message_list_api_keys($1)', ['alpha'])
    assert.equal(rows.length, 1)
    assert.equal(Object.hasOwn(rows[0], 'key_hash'), false)
    assert.deepEqual(await scalar('select public.message_list_api_keys($1)', ['beta']), [])
  })
  await anonymous()
  await check('匿名密钥校验只能取得绑定id/profile', async () => {
    assert.deepEqual(await scalar('select public.message_validate_api_key($1,$2)', ['alpha', keyHash]), { id: keyId, profile_id: 'alpha' })
    assert.equal(await scalar('select public.message_validate_api_key($1,$2)', ['beta', keyHash]), null)
    assert.equal(await scalar('select public.message_validate_api_key($1,$2)', ['alpha', 'b'.repeat(64)]), null)
    await denied('select public.message_auth_audit()')
  })
  await login(admin, adminSession, 'recovery')
  await check('恢复会话可核对身份但无任何管理RPC权限', async () => {
    assert.equal(await scalar('select public.message_recovery_admin()'), true)
    assert.equal(await scalar('select public.message_current_admin()'), false)
    await denied('select public.message_list_api_keys($1)', ['alpha'])
    await denied('select public.message_revoke_api_key($1,$2)', ['alpha', keyId])
    await denied('select public.message_auth_audit()')
    await denied('select public.message_record_auth_event($1)', ['logout'])
  })
  await login()
  await check('封禁管理员不能通过RPC或客户端key认证', async () => {
    await db.exec('reset role')
    await db.query("update auth.users set banned_until=now()+interval '1 day' where id=$1", [admin])
    await login()
    assert.equal(await scalar('select public.message_current_admin()'), false)
    assert.equal(await scalar('select public.message_validate_api_key($1,$2)', ['alpha', keyHash]), null)
    await db.exec('reset role')
    await db.query('update auth.users set banned_until=null where id=$1', [admin])
    await login()
  })
  await check('管理员绝对会话12小时、恢复会话10分钟上限', async () => {
    await db.exec('reset role')
    await db.query("update auth.sessions set created_at=now()-interval '13 hours' where id=$1", [adminSession])
    await login()
    assert.equal(await scalar('select public.message_current_admin()'), false)
    await db.exec('reset role')
    await db.query("update auth.sessions set created_at=now()-interval '11 minutes' where id=$1", [adminSession])
    await login(admin, adminSession, 'recovery')
    assert.equal(await scalar('select public.message_recovery_admin()'), false)
    await login()
    assert.equal(await scalar('select public.message_current_admin()'), true)
    await db.exec('reset role')
    await db.query('update auth.sessions set created_at=now() where id=$1', [adminSession])
    await login()
  })
  await check('撤销不会跨profile且立刻阻止key验证', async () => {
    assert.equal(await scalar('select public.message_revoke_api_key($1,$2)', ['beta', keyId]), false)
    assert.equal(await scalar('select public.message_revoke_api_key($1,$2)', ['alpha', keyId]), true)
    assert.equal(await scalar('select public.message_revoke_api_key($1,$2)', ['alpha', keyId]), false)
    assert.equal(await scalar('select public.message_validate_api_key($1,$2)', ['alpha', keyHash]), null)
  })
  await check('审计仅有固定事件且重复撤销不重复写入', async () => {
    await scalar('select public.message_record_auth_event($1)', ['login.succeeded'])
    await scalar('select public.message_record_auth_event($1)', ['logout'])
    const rows = await scalar('select public.message_auth_audit()')
    assert.equal(rows.length, 4)
    assert.deepEqual(rows.map(row => row.event).sort(), ['key.created', 'key.revoked', 'login.succeeded', 'logout'])
    assert.ok(rows.every(row => !Object.hasOwn(row, 'key_hash') && !Object.hasOwn(row, 'key')))
    await assert.rejects(() => scalar('select public.message_record_auth_event($1)', ['untrusted-secret']), error => error.code === '22023')
  })
  await db.exec('reset role')
  await db.query("update public.message_api_keys set revoked_at=null,created_at=now()-interval '2 days',expires_at=now()-interval '1 day' where id=$1", [keyId])
  await anonymous()
  await check('过期密钥拒绝认证', async () => {
    assert.equal(await scalar('select public.message_validate_api_key($1,$2)', ['alpha', keyHash]), null)
  })
  await db.exec('reset role')
  await db.query("update public.message_api_keys set expires_at=now()+interval '1 day' where id=$1", [keyId])
  await db.query('delete from public.message_admins where user_id=$1', [admin])
  await anonymous()
  await check('取消管理员membership立即禁用其客户端keys', async () => {
    assert.equal(await scalar('select public.message_validate_api_key($1,$2)', ['alpha', keyHash]), null)
  })
  await db.exec('reset role')
  await db.query('insert into public.message_admins(user_id) values($1)', [admin])
  await db.query("update auth.sessions set not_after=now()-interval '1 minute' where id=$1", [adminSession])
  await login()
  await check('达到session截止时间拒绝管理', async () => {
    assert.equal(await scalar('select public.message_current_admin()'), false)
    await denied('select public.message_list_api_keys($1)', ['alpha'])
  })
  await db.exec('reset role')
  await db.query('delete from auth.sessions where id=$1', [adminSession])
  await login()
  await check('已退出的session即使旧JWT尚未到期也拒绝管理', async () => {
    assert.equal(await scalar('select public.message_current_admin()'), false)
    await denied('select public.message_list_api_keys($1)', ['alpha'])
  })
  console.log(`全部${passed}项Postgres安全验证通过`)
} finally {
  await db.close()
}