69 lines
4.4 KiB
JavaScript
69 lines
4.4 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import { verifyPdaWarehouseRevocation, warehouseClaim } from './unified-pda-session-revocation.mjs'
|
|
import { pickerSessionHandoff } from './phase4-overseas-pda.mjs'
|
|
|
|
const token = warehouse => 'header.' + Buffer.from(JSON.stringify({ overseas_warehouse_id: warehouse })).toString('base64url') + '.signature'
|
|
test('signed binding identity retains 64-bit decimal precision for numeric and string claims', () => {
|
|
const make = raw => 'header.' + Buffer.from(raw).toString('base64url') + '.signature'
|
|
assert.equal(warehouseClaim(make('{"overseas_warehouse_id":193000000000200004}')), '193000000000200004')
|
|
assert.equal(warehouseClaim(token('193000000000200004')), '193000000000200004')
|
|
assert.equal(warehouseClaim(make('{"overseas_warehouse_id":null}')), '')
|
|
assert.equal(warehouseClaim(make('{"overseas_warehouse_id":22,"overseas_warehouse_id":23}')), '')
|
|
})
|
|
function fixture(wrongWarehouse = false) {
|
|
const checks = [], calls = []
|
|
let cleared = false
|
|
let isolatedRole
|
|
const picker = { membershipId: '7', username: 'offline-user', password: 'offline-password',
|
|
token: 'old-access', refreshToken: 'old-refresh', userUpdate: { displayName: 'offline', phone: '13900000001',
|
|
email: 'offline@example.invalid', tenantId: '0', roleIds: ['9'], overseasWarehouseId: '11', domesticWarehouseId: null } }
|
|
const response = (data, code = '200', http = 200) => ({ http, body: { code, data } })
|
|
const context = { assert: (_m, name, _r, passed, evidence) => checks.push({ name, passed, evidence }),
|
|
apiSuccess: (_m, _n, r) => r.body.code === '200' ? r.body.data : null,
|
|
request: async (method, path, body, bearer) => {
|
|
calls.push({ method, path, body, bearer })
|
|
if (path.includes('receiving-options')) return response({ records: [{ id: '11' }, { id: '22' }] })
|
|
if (path === '/api/platform/v1/iam/roles' && method === 'POST') {
|
|
isolatedRole = { ...body, id: '99', status: 'ACTIVE', permissionIds: [] }
|
|
return response(isolatedRole)
|
|
}
|
|
if (path === '/api/platform/v1/iam/roles') return response([isolatedRole])
|
|
if (path === '/api/platform/v1/iam/roles/99/status') return response({ status: 'DISABLED' })
|
|
if (method === 'PUT') {
|
|
assert.equal(body.phone, picker.userUpdate.phone)
|
|
assert.equal(body.email, picker.userUpdate.email)
|
|
assert.ok(body.roleIds.length > 0, 'IAM user edits require a role')
|
|
cleared = body.roleIds.includes('99')
|
|
return response({ ...body })
|
|
}
|
|
if (path === '/api/auth/login') return response({ accessToken: token(cleared ? null : wrongWarehouse ? '11' : '22'), refreshToken: 'new-refresh' })
|
|
if (path === '/api/auth/refresh') {
|
|
assert.equal(bearer, '')
|
|
return response(null, 'A0401')
|
|
}
|
|
if (bearer === 'old-access' || (cleared && bearer === token('22'))) return response(null, 'A0401', 401)
|
|
return cleared ? response(null, 'A0403', 403) : response(null, 'A0404')
|
|
} }
|
|
return { context, picker: pickerSessionHandoff(picker), checks, calls }
|
|
}
|
|
test('binding changes revoke both tokens and role/binding clearing prevents fresh PDA access', async () => {
|
|
const { context, picker, checks, calls } = fixture()
|
|
await verifyPdaWarehouseRevocation(context, picker)
|
|
for (const name of ['PDA仓绑定变更旧Access失效', 'PDA仓绑定变更旧Refresh不能恢复会话',
|
|
'新PDA会话只携带服务器签发的新仓绑定', 'PDA角色与仓绑定清空旧Access失效',
|
|
'PDA角色与仓绑定清空旧Refresh不能恢复会话', '无作业仓绑定的新会话不得进入PDA']) {
|
|
assert.equal(checks.find(row => row.name === name)?.passed, true, name)
|
|
}
|
|
assert.ok(checks.every(row => row.passed))
|
|
assert.ok(checks.every(row => !/offline-password|old-refresh|new-refresh/.test(row.evidence || '')))
|
|
assert.equal(calls.filter(row => row.method === 'PUT' && row.path.includes('/iam/users/')).length, 2)
|
|
assert.ok(calls.some(row => row.path === '/api/platform/v1/iam/roles/99/status'))
|
|
assert.ok(calls.some(row => row.path === '/api/auth/refresh' && row.body.refreshToken === 'old-refresh'))
|
|
})
|
|
test('fresh login retaining the original warehouse blocks instead of accepting token presence', async () => {
|
|
const { context, picker, checks } = fixture(true)
|
|
await assert.rejects(verifyPdaWarehouseRevocation(context, picker), /绑定漂移/)
|
|
assert.ok(checks.some(row => !row.passed))
|
|
})
|