Treat duplicate recovery responses as status-only
Why: A lost confirmation or grant-rotation response must not imply that the one-time plaintext can be read again. Evidence: SELF receipt /tmp/iam-u3-frontend-idempotent-test.json and a normal pinned-UI build after the change. Impact: The owner view shows current proposal status and offers an explicit new-grant action only when the server returns no plaintext. Refs: IAM U3 r1 stages 4-5; Dev feature/iam-u3-account-directory-recovery
This commit is contained in:
+2
-2
@@ -99,8 +99,8 @@ export const api = {
|
||||
adminRevokeRecoveryProposal: (userId: string, id: string, expectedVersion: number) => auth.fetch(`${BASE}/api/admin/users/${encodeURIComponent(userId)}/recovery-proposals/${encodeURIComponent(id)}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ expectedVersion }) }).then(r => json<RecoveryProposal>(r)),
|
||||
listMyRecoveryProposals: () => auth.fetch(`${BASE}/api/me/recovery-proposals`).then(r => json<{ proposals: RecoveryProposal[] }>(r)).then(d => d.proposals ?? []),
|
||||
getMyRecoveryProposal: (id: string) => auth.fetch(`${BASE}/api/me/recovery-proposals/${encodeURIComponent(id)}`).then(r => json<RecoveryProposal>(r)),
|
||||
confirmMyRecoveryProposal: (id: string, expectedVersion: number) => auth.fetch(`${BASE}/api/auth/recovery-proposals/${encodeURIComponent(id)}/confirm`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ expectedVersion }) }).then(r => json<{ proposal: RecoveryProposal; grant: string }>(r)),
|
||||
rotateMyRecoveryGrant: (id: string, expectedVersion: number, expectedGeneration: number) => auth.fetch(`${BASE}/api/auth/recovery-proposals/${encodeURIComponent(id)}/rotate-grant`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ expectedVersion, expectedGeneration }) }).then(r => json<{ proposal: RecoveryProposal; grant: string }>(r)),
|
||||
confirmMyRecoveryProposal: (id: string, expectedVersion: number) => auth.fetch(`${BASE}/api/auth/recovery-proposals/${encodeURIComponent(id)}/confirm`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ expectedVersion }) }).then(r => json<{ proposal: RecoveryProposal; grant?: string }>(r)),
|
||||
rotateMyRecoveryGrant: (id: string, expectedVersion: number, expectedGeneration: number) => auth.fetch(`${BASE}/api/auth/recovery-proposals/${encodeURIComponent(id)}/rotate-grant`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ expectedVersion, expectedGeneration }) }).then(r => json<{ proposal: RecoveryProposal; grant?: string }>(r)),
|
||||
createConversation: (projectId: string, title?: string) => auth.fetch(`${BASE}/api/projects/${projectId}/conversations`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: title ?? '' }) }).then(r => json<Conversation>(r)),
|
||||
deleteConversation: (id: string) => auth.fetch(`${BASE}/api/conversations/${id}`, { method: 'DELETE' }),
|
||||
setConversationProfile: (id: string, profileId: string) => auth.fetch(`${BASE}/api/conversations/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ profileId }) }).then(r => json<Conversation>(r)),
|
||||
|
||||
@@ -59,6 +59,10 @@ export function MyAccountSettings() {
|
||||
try {
|
||||
const result = await api.confirmMyRecoveryProposal(proposal.id, proposal.version)
|
||||
setProposals(current => current?.map(item => item.id === proposal.id ? result.proposal : item) ?? [result.proposal])
|
||||
if (!result.grant) {
|
||||
setErr('提案已确认,但一次性明文不可读回。若仍保有原本人会话,可显式重新生成设密能力。')
|
||||
return
|
||||
}
|
||||
setGrant({ proposalId: proposal.id, value: result.grant })
|
||||
setNotice('本人确认成功。设密能力只在此响应可用,请立即兑换并设置新密码。')
|
||||
} catch (error) {
|
||||
@@ -74,6 +78,10 @@ export function MyAccountSettings() {
|
||||
try {
|
||||
const result = await api.rotateMyRecoveryGrant(proposal.id, proposal.version, proposal.generation)
|
||||
setProposals(current => current?.map(item => item.id === proposal.id ? result.proposal : item) ?? [result.proposal])
|
||||
if (!result.grant) {
|
||||
setErr('重新生成已提交,但一次性明文不可读回。请核对当前提案状态,再由本人显式操作。')
|
||||
return
|
||||
}
|
||||
setGrant({ proposalId: proposal.id, value: result.grant })
|
||||
setNotice('已重新生成设密能力;旧能力与派生受限会话已失效。')
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user