feat: password reauthentication for documents and recipients (#1827)

Adds password reauthentication to our existing reauth providers,
additionally swaps from an exclusive provider to an inclusive type where
multiple methods can be selected to offer a this or that experience.
This commit is contained in:
Lucas Smith
2025-06-07 00:27:19 +10:00
committed by GitHub
parent ce66da0055
commit 55c8632620
62 changed files with 985 additions and 466 deletions

View File

@ -3,6 +3,7 @@ import { useLingui } from '@lingui/react';
import { FieldType, SigningStatus } from '@prisma/client';
import { DateTime } from 'luxon';
import { redirect } from 'react-router';
import { prop, sortBy } from 'remeda';
import { match } from 'ts-pattern';
import { UAParser } from 'ua-parser-js';
import { renderSVG } from 'uqr';
@ -133,18 +134,30 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
recipientAuth: recipient.authOptions,
});
let authLevel = match(extractedAuthMethods.derivedRecipientActionAuth)
const insertedAuditLogsWithFieldAuth = sortBy(
auditLogs.DOCUMENT_FIELD_INSERTED.filter(
(log) => log.data.recipientId === recipient.id && log.data.fieldSecurity,
),
[prop('createdAt'), 'desc'],
);
const actionAuthMethod = insertedAuditLogsWithFieldAuth.at(0)?.data?.fieldSecurity?.type;
let authLevel = match(actionAuthMethod)
.with('ACCOUNT', () => _(msg`Account Re-Authentication`))
.with('TWO_FACTOR_AUTH', () => _(msg`Two-Factor Re-Authentication`))
.with('PASSWORD', () => _(msg`Password Re-Authentication`))
.with('PASSKEY', () => _(msg`Passkey Re-Authentication`))
.with('EXPLICIT_NONE', () => _(msg`Email`))
.with(null, () => null)
.with(undefined, () => null)
.exhaustive();
if (!authLevel) {
authLevel = match(extractedAuthMethods.derivedRecipientAccessAuth)
const accessAuthMethod = extractedAuthMethods.derivedRecipientAccessAuth.at(0);
authLevel = match(accessAuthMethod)
.with('ACCOUNT', () => _(msg`Account Authentication`))
.with(null, () => _(msg`Email`))
.with(undefined, () => _(msg`Email`))
.exhaustive();
}

View File

@ -47,9 +47,9 @@ export async function loader({ params, request }: Route.LoaderArgs) {
});
// Ensure typesafety when we add more options.
const isAccessAuthValid = match(derivedRecipientAccessAuth)
const isAccessAuthValid = match(derivedRecipientAccessAuth.at(0))
.with(DocumentAccessAuth.ACCOUNT, () => Boolean(session.user))
.with(null, () => true)
.with(undefined, () => true)
.exhaustive();
if (!isAccessAuthValid) {

View File

@ -68,9 +68,9 @@ export async function loader({ params, request }: Route.LoaderArgs) {
}),
]);
const isAccessAuthValid = match(derivedRecipientAccessAuth)
.with(DocumentAccessAuth.ACCOUNT, () => user !== null)
.with(null, () => true)
const isAccessAuthValid = match(derivedRecipientAccessAuth.at(0))
.with(DocumentAccessAuth.ACCOUNT, () => !!user)
.with(undefined, () => true)
.exhaustive();
if (!isAccessAuthValid) {

View File

@ -81,9 +81,9 @@ export async function loader({ params, request }: Route.LoaderArgs) {
documentAuth: document.authOptions,
});
const isAccessAuthValid = match(derivedRecipientAccessAuth)
.with(DocumentAccessAuth.ACCOUNT, () => user !== null)
.with(null, () => true)
const isAccessAuthValid = match(derivedRecipientAccessAuth.at(0))
.with(DocumentAccessAuth.ACCOUNT, () => user && user.email === recipient.email)
.with(undefined, () => true)
.exhaustive();
if (!isAccessAuthValid) {

View File

@ -38,8 +38,6 @@ export async function loader({ request }: Route.LoaderArgs) {
const recipient = await getRecipientByToken({ token });
console.log('document', document.id);
return { document, recipient };
}),
);