refactor(signing-2fa): simplify server-side and UI code for external 2FA

- Extract throwVerificationError helper in verify-signing-two-factor-token.ts
- Extract throwIssuanceDenied helper in issue-signing-two-factor-token.ts
- Eliminate duplicated attemptsRemaining state in UI component
- Use imported SIGNING_2FA_VERIFY_REASON_CODES constants
- Add statusQuery.refetch() after failed verify for single source of truth
- Fix TypeScript control flow with explicit returns after throws
This commit is contained in:
ephraimduncan
2026-02-10 12:39:13 +00:00
parent f1c91c4951
commit bd56929db1
34 changed files with 2406 additions and 21 deletions
+67
View File
@@ -50,6 +50,14 @@ export const ZDocumentAuditLogTypeSchema = z.enum([
'DOCUMENT_ACCESS_AUTH_2FA_REQUESTED', // When ACCESS AUTH 2FA is requested.
'DOCUMENT_ACCESS_AUTH_2FA_VALIDATED', // When ACCESS AUTH 2FA is successfully validated.
'DOCUMENT_ACCESS_AUTH_2FA_FAILED', // When ACCESS AUTH 2FA validation fails.
// External signing 2FA events.
'EXTERNAL_2FA_TOKEN_ISSUED',
'EXTERNAL_2FA_TOKEN_ISSUE_DENIED',
'EXTERNAL_2FA_TOKEN_VERIFY_SUCCEEDED',
'EXTERNAL_2FA_TOKEN_VERIFY_FAILED',
'EXTERNAL_2FA_TOKEN_CONSUMED',
'EXTERNAL_2FA_TOKEN_REVOKED',
]);
export const ZDocumentAuditLogEmailTypeSchema = z.enum([
@@ -694,6 +702,59 @@ export const ZDocumentAuditLogEventDocumentDelegatedOwnerCreatedSchema = z.objec
}),
});
const ZExternal2FARecipientDataSchema = z.object({
recipientId: z.number(),
recipientEmail: z.string(),
recipientName: z.string(),
});
export const ZDocumentAuditLogEventExternal2FATokenIssuedSchema = z.object({
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.EXTERNAL_2FA_TOKEN_ISSUED),
data: ZExternal2FARecipientDataSchema.extend({
tokenId: z.string(),
reasonCode: z.string().optional(),
}),
});
export const ZDocumentAuditLogEventExternal2FATokenIssueDeniedSchema = z.object({
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.EXTERNAL_2FA_TOKEN_ISSUE_DENIED),
data: ZExternal2FARecipientDataSchema.extend({
reasonCode: z.string(),
}),
});
export const ZDocumentAuditLogEventExternal2FATokenVerifySucceededSchema = z.object({
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.EXTERNAL_2FA_TOKEN_VERIFY_SUCCEEDED),
data: ZExternal2FARecipientDataSchema.extend({
tokenId: z.string(),
}),
});
export const ZDocumentAuditLogEventExternal2FATokenVerifyFailedSchema = z.object({
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.EXTERNAL_2FA_TOKEN_VERIFY_FAILED),
data: ZExternal2FARecipientDataSchema.extend({
tokenId: z.string(),
reasonCode: z.string(),
attemptsUsed: z.number(),
attemptLimit: z.number(),
}),
});
export const ZDocumentAuditLogEventExternal2FATokenConsumedSchema = z.object({
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.EXTERNAL_2FA_TOKEN_CONSUMED),
data: ZExternal2FARecipientDataSchema.extend({
tokenId: z.string(),
}),
});
export const ZDocumentAuditLogEventExternal2FATokenRevokedSchema = z.object({
type: z.literal(DOCUMENT_AUDIT_LOG_TYPE.EXTERNAL_2FA_TOKEN_REVOKED),
data: ZExternal2FARecipientDataSchema.extend({
tokenId: z.string(),
reasonCode: z.string(),
}),
});
export const ZDocumentAuditLogBaseSchema = z.object({
id: z.string(),
createdAt: z.date(),
@@ -739,6 +800,12 @@ export const ZDocumentAuditLogSchema = ZDocumentAuditLogBaseSchema.and(
ZDocumentAuditLogEventRecipientAddedSchema,
ZDocumentAuditLogEventRecipientUpdatedSchema,
ZDocumentAuditLogEventRecipientRemovedSchema,
ZDocumentAuditLogEventExternal2FATokenIssuedSchema,
ZDocumentAuditLogEventExternal2FATokenIssueDeniedSchema,
ZDocumentAuditLogEventExternal2FATokenVerifySucceededSchema,
ZDocumentAuditLogEventExternal2FATokenVerifyFailedSchema,
ZDocumentAuditLogEventExternal2FATokenConsumedSchema,
ZDocumentAuditLogEventExternal2FATokenRevokedSchema,
]),
);
+10
View File
@@ -9,6 +9,7 @@ export const ZDocumentAuthTypesSchema = z.enum([
'ACCOUNT',
'PASSKEY',
'TWO_FACTOR_AUTH',
'EXTERNAL_TWO_FACTOR_AUTH',
'PASSWORD',
'EXPLICIT_NONE',
]);
@@ -40,6 +41,10 @@ const ZDocumentAuth2FASchema = z.object({
method: z.enum(['email', 'authenticator']).default('authenticator').optional(),
});
const ZDocumentAuthExternal2FASchema = z.object({
type: z.literal(DocumentAuth.EXTERNAL_TWO_FACTOR_AUTH),
});
/**
* All the document auth methods for both accessing and actioning.
*/
@@ -48,6 +53,7 @@ export const ZDocumentAuthMethodsSchema = z.discriminatedUnion('type', [
ZDocumentAuthExplicitNoneSchema,
ZDocumentAuthPasskeySchema,
ZDocumentAuth2FASchema,
ZDocumentAuthExternal2FASchema,
ZDocumentAuthPasswordSchema,
]);
@@ -73,6 +79,7 @@ export const ZDocumentActionAuthSchema = z.discriminatedUnion('type', [
ZDocumentAuthAccountSchema,
ZDocumentAuthPasskeySchema,
ZDocumentAuth2FASchema,
ZDocumentAuthExternal2FASchema,
ZDocumentAuthPasswordSchema,
]);
export const ZDocumentActionAuthTypesSchema = z
@@ -80,6 +87,7 @@ export const ZDocumentActionAuthTypesSchema = z
DocumentAuth.ACCOUNT,
DocumentAuth.PASSKEY,
DocumentAuth.TWO_FACTOR_AUTH,
DocumentAuth.EXTERNAL_TWO_FACTOR_AUTH,
DocumentAuth.PASSWORD,
])
.describe(
@@ -108,6 +116,7 @@ export const ZRecipientActionAuthSchema = z.discriminatedUnion('type', [
ZDocumentAuthAccountSchema,
ZDocumentAuthPasskeySchema,
ZDocumentAuth2FASchema,
ZDocumentAuthExternal2FASchema,
ZDocumentAuthPasswordSchema,
ZDocumentAuthExplicitNoneSchema,
]);
@@ -116,6 +125,7 @@ export const ZRecipientActionAuthTypesSchema = z
DocumentAuth.ACCOUNT,
DocumentAuth.PASSKEY,
DocumentAuth.TWO_FACTOR_AUTH,
DocumentAuth.EXTERNAL_TWO_FACTOR_AUTH,
DocumentAuth.PASSWORD,
DocumentAuth.EXPLICIT_NONE,
])
+7
View File
@@ -32,6 +32,8 @@ export const ZClaimFlagsSchema = z.object({
authenticationPortal: z.boolean().optional(),
allowLegacyEnvelopes: z.boolean().optional(),
externalSigning2fa: z.boolean().optional(),
});
export type TClaimFlags = z.infer<typeof ZClaimFlagsSchema>;
@@ -94,6 +96,11 @@ export const SUBSCRIPTION_CLAIM_FEATURE_FLAGS: Record<
key: 'allowLegacyEnvelopes',
label: 'Allow Legacy Envelopes',
},
externalSigning2fa: {
key: 'externalSigning2fa',
label: 'External signing 2FA',
isEnterprise: true,
},
};
export enum INTERNAL_CLAIM_ID {