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
@@ -155,12 +155,28 @@ export const completeDocumentWithToken = async ({
});
}
// Check ACCESS AUTH 2FA validation during document completion
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
const { derivedRecipientAccessAuth, derivedRecipientActionAuth } = extractDocumentAuthMethods({
documentAuth: envelope.authOptions,
recipientAuth: recipient.authOptions,
});
if (derivedRecipientActionAuth.includes(DocumentAuth.EXTERNAL_TWO_FACTOR_AUTH)) {
const validProof = await prisma.signingSessionTwoFactorProof.findFirst({
where: {
sessionId: token,
envelopeId: envelope.id,
expiresAt: { gt: new Date() },
},
});
if (!validProof) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'External 2FA verification required before completing document',
statusCode: 403,
});
}
}
if (derivedRecipientAccessAuth.includes(DocumentAuth.TWO_FACTOR_AUTH)) {
if (!accessAuthOptions) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
@@ -33,6 +33,7 @@ type IsRecipientAuthorizedOptions = {
* using the user ID.
*/
authOptions?: TDocumentAuthMethods;
recipientToken?: string;
};
const getUserByEmail = async (email: string) => {
@@ -58,6 +59,7 @@ export const isRecipientAuthorized = async ({
recipient,
userId,
authOptions,
recipientToken,
}: IsRecipientAuthorizedOptions): Promise<boolean> => {
const { derivedRecipientAccessAuth, derivedRecipientActionAuth } = extractDocumentAuthMethods({
documentAuth: documentAuthOptions,
@@ -168,6 +170,21 @@ export const isRecipientAuthorized = async ({
password,
});
})
.with({ type: DocumentAuth.EXTERNAL_TWO_FACTOR_AUTH }, async () => {
if (!recipientToken) {
return false;
}
const validProof = await prisma.signingSessionTwoFactorProof.findFirst({
where: {
sessionId: recipientToken,
envelopeId: recipient.envelopeId,
expiresAt: { gt: new Date() },
},
});
return !!validProof;
})
.with({ type: DocumentAuth.EXPLICIT_NONE }, () => {
return true;
})
@@ -11,6 +11,7 @@ export type ValidateFieldAuthOptions = {
field: Field;
userId?: number;
authOptions?: TRecipientActionAuth;
recipientToken?: string;
};
/**
@@ -24,6 +25,7 @@ export const validateFieldAuth = async ({
field,
userId,
authOptions,
recipientToken,
}: ValidateFieldAuthOptions) => {
// Override all non-signature fields to not require any auth.
if (field.type !== FieldType.SIGNATURE) {
@@ -36,6 +38,7 @@ export const validateFieldAuth = async ({
recipient,
userId,
authOptions,
recipientToken,
});
if (!isValid) {