78 lines
5.9 KiB
JavaScript
78 lines
5.9 KiB
JavaScript
import { verifyRevokedSessions } from './system.mjs'
|
|
|
|
const MODULE = 'phase4-overseas-pda-operations'
|
|
const OPTIONS = { scenario: 'unified-pda-session-revocation', deploymentBlocker: true }
|
|
const PATH = '/api/pda/v1/last-leg/receiving-containers/current'
|
|
const id = value => value == null ? '' : String(value)
|
|
const success = value => value?.http === 200 && String(value?.body?.code) === '200'
|
|
export function warehouseClaim(token) {
|
|
try {
|
|
const raw = Buffer.from(token.split('.')[1], 'base64url').toString('utf8')
|
|
JSON.parse(raw) // Check JSON shape; obtain the ID from its original decimal lexeme.
|
|
const matches = [...raw.matchAll(/"overseas_warehouse_id"\s*:\s*(?:"([1-9][0-9]*)"|([1-9][0-9]*)(?=\s*[,}]))/g)]
|
|
return matches.length === 1 ? matches[0][1] || matches[0][2] : ''
|
|
}
|
|
catch { return '' }
|
|
}
|
|
|
|
// Called only after the owned actor's inventory operations finish. Credentials
|
|
// stay in the private handoff context and are never added to createdResources.
|
|
export async function verifyPdaWarehouseRevocation(context, picker) {
|
|
if (!picker?.userUpdate || !picker.password || !picker.refreshToken) throw Error('PDA撤销检查缺少本轮完整会话和资料')
|
|
const optionsResponse = await context.request('GET', '/api/platform/v1/warehouse-admin/overseas-receiving-options?page=1&size=200')
|
|
const page = context.apiSuccess(MODULE, '查询PDA仓绑定变更可用的权威收货仓', optionsResponse, '', OPTIONS)
|
|
const next = page?.records?.find(row => id(row.id) !== id(picker.userUpdate.overseasWarehouseId))
|
|
if (!next?.id) throw Error('PDA仓绑定变更缺少第二个正式收货仓')
|
|
const updatePath = `/api/platform/v1/iam/users/${picker.membershipId}`
|
|
const updated = await context.request('PUT', updatePath, { ...picker.userUpdate, overseasWarehouseId: id(next.id) })
|
|
context.assert(MODULE, '仅变更本轮PDA账号的海外仓绑定', updated,
|
|
success(updated) && id(updated.body.data.overseasWarehouseId) === id(next.id)
|
|
&& id(updated.body.data.phone) === id(picker.userUpdate.phone), `warehouseId=${id(next.id)}`, OPTIONS)
|
|
if (!success(updated)) throw Error('PDA仓绑定更新失败')
|
|
await verifyRevokedSessions(context, 'PDA仓绑定变更', [{ accessToken: picker.token, refreshToken: picker.refreshToken }], PATH)
|
|
const loggedIn = await context.request('POST', '/api/auth/login', { loginId: picker.username, password: picker.password }, '')
|
|
const session = context.apiSuccess(MODULE, 'PDA按新仓绑定重新登录', loggedIn, '', OPTIONS)
|
|
const valid = session?.accessToken && session?.refreshToken && warehouseClaim(session.accessToken) === id(next.id)
|
|
context.assert(MODULE, '新PDA会话只携带服务器签发的新仓绑定', loggedIn, Boolean(valid), `warehouseId=${id(next.id)}`, OPTIONS)
|
|
if (!valid) throw Error('PDA新仓会话身份不完整或绑定漂移')
|
|
const allowed = await context.request('GET', PATH, undefined, session.accessToken)
|
|
context.assert(MODULE, '新仓绑定会话可进入PDA且不借旧仓会话', allowed,
|
|
success(allowed) || (allowed.http === 200 && String(allowed.body?.code) === 'A0404'),
|
|
`HTTP=${allowed.http}, code=${allowed.body?.code || 'missing'}`, OPTIONS)
|
|
const roleCode = `PDA_UNBOUND_${picker.membershipId}`
|
|
const roleResponse = await context.request('POST', '/api/platform/v1/iam/roles',
|
|
{ roleCode, roleName: `无PDA权限验收-${picker.membershipId}`, description: context.marker })
|
|
const role = context.apiSuccess(MODULE, '创建本轮无PDA权限的隔离角色', roleResponse, '', OPTIONS)
|
|
if (!role?.id) throw Error('无PDA权限验收角色创建失败')
|
|
try {
|
|
const rolesResponse = await context.request('GET', '/api/platform/v1/iam/roles')
|
|
const roles = context.apiSuccess(MODULE, '核对隔离角色未获任何权限', rolesResponse, '', OPTIONS)
|
|
const isolated = roles?.find(row => id(row.id) === id(role.id))
|
|
const emptyPermissions = isolated?.roleCode === roleCode && isolated?.status === 'ACTIVE'
|
|
&& Array.isArray(isolated.permissionIds) && isolated.permissionIds.length === 0
|
|
context.assert(MODULE, '清空仓绑定使用本轮无权限角色', rolesResponse, emptyPermissions, `roleId=${id(role.id)}`, OPTIONS)
|
|
if (!emptyPermissions) throw Error('无PDA权限验收角色身份或权限不符合要求')
|
|
const cleared = await context.request('PUT', updatePath,
|
|
{ ...picker.userUpdate, roleIds: [id(role.id)], overseasWarehouseId: null, domesticWarehouseId: null })
|
|
context.assert(MODULE, '取消本轮PDA角色同时清空作业仓绑定', cleared,
|
|
success(cleared) && cleared.body.data.overseasWarehouseId == null && cleared.body.data.domesticWarehouseId == null
|
|
&& cleared.body.data.roleIds?.length === 1 && id(cleared.body.data.roleIds[0]) === id(role.id), '', OPTIONS)
|
|
if (!success(cleared)) throw Error('PDA角色及仓绑定清空失败')
|
|
await verifyRevokedSessions(context, 'PDA角色与仓绑定清空', [session], PATH)
|
|
const noBindingLogin = await context.request('POST', '/api/auth/login', { loginId: picker.username, password: picker.password }, '')
|
|
const token = success(noBindingLogin) ? noBindingLogin.body.data?.accessToken : null
|
|
if (token) {
|
|
const denied = await context.request('GET', PATH, undefined, token)
|
|
context.assert(MODULE, '无作业仓绑定的新会话不得进入PDA', denied,
|
|
denied.http === 403 || (denied.http === 200 && String(denied.body?.code) === 'A0403'), '', OPTIONS)
|
|
} else {
|
|
context.assert(MODULE, '无PDA身份不能重新取得作业会话', noBindingLogin,
|
|
[200, 401, 403].includes(noBindingLogin.http) && ['A0401', 'A0403'].includes(String(noBindingLogin.body?.code)), '', OPTIONS)
|
|
}
|
|
} finally {
|
|
const disabled = await context.request('PUT', `/api/platform/v1/iam/roles/${role.id}/status`, { status: 'DISABLED' })
|
|
context.assert(MODULE, 'finally停用本轮无权限验收角色', disabled,
|
|
success(disabled) && disabled.body.data.status === 'DISABLED', `roleId=${id(role.id)}`, OPTIONS)
|
|
}
|
|
}
|