feat(auth): add opaque bearer sessions
This commit is contained in:
@@ -45,9 +45,11 @@ async function testSupportsBlobResponses() {
|
||||
assert.equal(payload, blob)
|
||||
}
|
||||
|
||||
async function testInjectsAuthenticatedUserHeaders() {
|
||||
const sessionStorage = new Map([
|
||||
[
|
||||
async function testInjectsBearerTokenWithoutUserControlledIdentityHeaders() {
|
||||
const sessionStorage = new Map([
|
||||
['x-financial-auth-access-token', 'opaque-access-token'],
|
||||
['x-financial-auth-expires-at', '2099-01-01T00:00:00.000Z'],
|
||||
[
|
||||
'x-financial-auth-user',
|
||||
JSON.stringify({
|
||||
username: 'admin',
|
||||
@@ -84,27 +86,16 @@ async function testInjectsAuthenticatedUserHeaders() {
|
||||
|
||||
await apiRequest('/knowledge/library')
|
||||
|
||||
assert.equal(capturedOptions.headers['x-auth-username'], 'admin')
|
||||
assert.equal(capturedOptions.headers['x-auth-name'], 'Admin User')
|
||||
assert.equal(capturedOptions.headers['x-auth-position'], 'System Manager')
|
||||
assert.equal(capturedOptions.headers['x-auth-grade'], 'M5')
|
||||
assert.equal(capturedOptions.headers['x-auth-employee-no'], 'E-001')
|
||||
assert.equal(capturedOptions.headers['x-auth-manager-name'], 'Approver User')
|
||||
assert.equal(capturedOptions.headers['x-auth-role-codes'], 'manager')
|
||||
assert.equal(capturedOptions.headers['x-auth-is-admin'], 'true')
|
||||
assert.equal(capturedOptions.headers.Authorization, 'Bearer opaque-access-token')
|
||||
assert.equal(capturedOptions.headers['x-auth-username'], undefined)
|
||||
assert.equal(capturedOptions.headers['x-auth-role-codes'], undefined)
|
||||
assert.equal(capturedOptions.headers['x-auth-is-admin'], undefined)
|
||||
}
|
||||
|
||||
async function testInjectsLegacyAdminHeaderFromSnakeCaseFlag() {
|
||||
async function testLoginCanDisableBearerInjection() {
|
||||
const sessionStorage = new Map([
|
||||
[
|
||||
'x-financial-auth-user',
|
||||
JSON.stringify({
|
||||
username: 'superadmin',
|
||||
name: 'superadmin',
|
||||
roleCodes: ['manager'],
|
||||
is_admin: true
|
||||
})
|
||||
]
|
||||
['x-financial-auth-access-token', 'stale-token'],
|
||||
['x-financial-auth-expires-at', '2099-01-01T00:00:00.000Z']
|
||||
])
|
||||
|
||||
global.window = {
|
||||
@@ -127,11 +118,74 @@ async function testInjectsLegacyAdminHeaderFromSnakeCaseFlag() {
|
||||
}
|
||||
}
|
||||
|
||||
await apiRequest('/reimbursements/claims/demo', { method: 'DELETE' })
|
||||
await apiRequest('/auth/login', {
|
||||
auth: false,
|
||||
handleUnauthorized: false,
|
||||
method: 'POST',
|
||||
body: '{}'
|
||||
})
|
||||
|
||||
assert.equal(capturedOptions.headers['x-auth-username'], 'superadmin')
|
||||
assert.equal(capturedOptions.headers['x-auth-role-codes'], 'manager')
|
||||
assert.equal(capturedOptions.headers['x-auth-is-admin'], 'true')
|
||||
assert.equal(capturedOptions.headers.Authorization, undefined)
|
||||
}
|
||||
|
||||
async function testRejectsCustomAuthorizationOverride() {
|
||||
const sessionStorage = new Map([
|
||||
['x-financial-auth-access-token', 'server-issued-token'],
|
||||
['x-financial-auth-expires-at', '2099-01-01T00:00:00.000Z']
|
||||
])
|
||||
global.window = {
|
||||
sessionStorage: {
|
||||
getItem(key) {
|
||||
return sessionStorage.get(key) ?? null
|
||||
}
|
||||
}
|
||||
}
|
||||
let capturedOptions = null
|
||||
global.fetch = async (_url, options) => {
|
||||
capturedOptions = options
|
||||
return { ok: true, async json() { return { ok: true } } }
|
||||
}
|
||||
|
||||
await apiRequest('/knowledge/library', {
|
||||
headers: { Authorization: 'Bearer attacker-token' }
|
||||
})
|
||||
|
||||
assert.equal(capturedOptions.headers.Authorization, 'Bearer server-issued-token')
|
||||
}
|
||||
|
||||
async function testUnauthorizedResponsePublishesSessionExpiredEvent() {
|
||||
const sessionStorage = new Map([
|
||||
['x-financial-auth-access-token', 'expired-token'],
|
||||
['x-financial-auth-expires-at', '2099-01-01T00:00:00.000Z']
|
||||
])
|
||||
const events = []
|
||||
global.window = {
|
||||
sessionStorage: {
|
||||
getItem(key) {
|
||||
return sessionStorage.get(key) ?? null
|
||||
}
|
||||
},
|
||||
dispatchEvent(event) {
|
||||
events.push(event.type)
|
||||
}
|
||||
}
|
||||
global.fetch = async () => ({
|
||||
ok: false,
|
||||
status: 401,
|
||||
async json() {
|
||||
return { detail: '登录会话已失效。' }
|
||||
}
|
||||
})
|
||||
|
||||
await assert.rejects(
|
||||
() => apiRequest('/auth/me'),
|
||||
(error) => {
|
||||
assert.equal(error.status, 401)
|
||||
assert.equal(error.code, 'AUTH_SESSION_EXPIRED')
|
||||
return true
|
||||
}
|
||||
)
|
||||
assert.deepEqual(events, ['x-financial:auth-expired'])
|
||||
}
|
||||
|
||||
async function testFormatsValidationErrors() {
|
||||
@@ -192,8 +246,10 @@ async function testRejectsWithCustomTimeoutMessage() {
|
||||
async function run() {
|
||||
await testUsesCustomContentTypeHeader()
|
||||
await testSupportsBlobResponses()
|
||||
await testInjectsAuthenticatedUserHeaders()
|
||||
await testInjectsLegacyAdminHeaderFromSnakeCaseFlag()
|
||||
await testInjectsBearerTokenWithoutUserControlledIdentityHeaders()
|
||||
await testLoginCanDisableBearerInjection()
|
||||
await testRejectsCustomAuthorizationOverride()
|
||||
await testUnauthorizedResponsePublishesSessionExpiredEvent()
|
||||
await testFormatsValidationErrors()
|
||||
await testRejectsWithCustomTimeoutMessage()
|
||||
console.log('api-request tests passed')
|
||||
|
||||
Reference in New Issue
Block a user