2026-09-10 22:57:52 +08:00
|
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
|
import { createRequire } from 'node:module'
|
|
|
|
|
|
|
2026-09-22 19:08:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 小程序鉴权契约守卫
|
|
|
|
|
|
*
|
|
|
|
|
|
* 覆盖三类此前只在真机/开发者工具上才会暴露的缺陷(2026-09-22 首页 401 事故):
|
|
|
|
|
|
* 1. hydrate:没有 access token 时不得恢复登录态。
|
|
|
|
|
|
* 2. 登录/刷新:后端返回字段是 accessToken(不是 token),若字段名写错,
|
|
|
|
|
|
* token 永远写不进 storage → 每个请求都不带 Authorization → 全站 401。
|
|
|
|
|
|
* 3. 刷新:后端 /auth/refresh 需要 userId(用于定位 Redis 中的 refresh token),
|
|
|
|
|
|
* 缺失会直接 400,导致刷新失败、登录态被误清。
|
|
|
|
|
|
*/
|
2026-09-10 22:57:52 +08:00
|
|
|
|
const removed = []
|
2026-09-22 19:08:16 +08:00
|
|
|
|
const storage = {}
|
2026-09-10 22:57:52 +08:00
|
|
|
|
globalThis.wx = {
|
|
|
|
|
|
getStorageSync(key) {
|
2026-09-22 19:08:16 +08:00
|
|
|
|
if (key in storage) return storage[key]
|
2026-09-10 22:57:52 +08:00
|
|
|
|
if (key === 'isLoggedIn') return { isLoggedIn: true, userId: 'stale-user' }
|
|
|
|
|
|
return ''
|
|
|
|
|
|
},
|
2026-09-22 19:08:16 +08:00
|
|
|
|
setStorageSync(key, value) {
|
|
|
|
|
|
storage[key] = value
|
|
|
|
|
|
},
|
2026-09-10 22:57:52 +08:00
|
|
|
|
removeStorageSync(key) {
|
|
|
|
|
|
removed.push(key)
|
2026-09-22 19:08:16 +08:00
|
|
|
|
delete storage[key]
|
2026-09-10 22:57:52 +08:00
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const require = createRequire(import.meta.url)
|
2026-09-22 19:08:16 +08:00
|
|
|
|
|
|
|
|
|
|
// ---- 1. hydrate:无 token 不得恢复登录态 ----
|
2026-09-10 22:57:52 +08:00
|
|
|
|
const authStore = require('../miniapp/stores/authStore.js')
|
|
|
|
|
|
authStore.hydrate()
|
|
|
|
|
|
assert.equal(authStore.state.isLoggedIn, false, '没有 access token 时不得恢复登录态')
|
|
|
|
|
|
assert.ok(removed.includes('isLoggedIn'), '应清理失真的登录快照')
|
|
|
|
|
|
|
2026-09-22 19:08:16 +08:00
|
|
|
|
const api = require('../miniapp/utils/api.js')
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 2. 登录:必须持久化 accessToken(后端字段名为 accessToken)----
|
|
|
|
|
|
api.post = async () => ({ accessToken: 'ACC-1', refreshToken: 'REF-1', userId: 'u-1', expiresIn: 7200 })
|
|
|
|
|
|
await authStore.loginWithWechat('wx-code')
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
|
storage.accessToken,
|
|
|
|
|
|
'ACC-1',
|
|
|
|
|
|
'登录后必须把后端返回的 accessToken 写入 storage(字段名写成 token 会落空 → 全站 401)',
|
|
|
|
|
|
)
|
|
|
|
|
|
assert.equal(storage.refreshToken, 'REF-1', '登录后必须写入 refreshToken')
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 3. 刷新:必须带 userId + refreshToken,并持久化新的 accessToken ----
|
|
|
|
|
|
let refreshBody = null
|
|
|
|
|
|
api.post = async (url, body) => {
|
|
|
|
|
|
if (url === '/auth/refresh') {
|
|
|
|
|
|
refreshBody = body
|
|
|
|
|
|
return { accessToken: 'ACC-2', refreshToken: 'REF-2', expiresIn: 7200 }
|
|
|
|
|
|
}
|
|
|
|
|
|
return {}
|
|
|
|
|
|
}
|
|
|
|
|
|
await authStore.refresh()
|
|
|
|
|
|
assert.ok(refreshBody, '刷新必须发起 /auth/refresh 请求')
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
|
refreshBody.userId,
|
|
|
|
|
|
'u-1',
|
|
|
|
|
|
'刷新请求必须携带 userId(后端据此定位 Redis refresh token,缺失会返回 400)',
|
|
|
|
|
|
)
|
|
|
|
|
|
assert.equal(refreshBody.refreshToken, 'REF-1', '刷新请求必须携带 refreshToken')
|
|
|
|
|
|
assert.equal(storage.accessToken, 'ACC-2', '刷新成功后必须持久化新的 accessToken')
|
|
|
|
|
|
|
2026-09-10 22:57:52 +08:00
|
|
|
|
console.log('Miniapp auth contracts passed')
|