mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 01:01:49 +10:00
Merge branch 'feat/passkey' into feat/document-passkey-test
This commit is contained in:
@ -11,6 +11,7 @@ import { match } from 'ts-pattern';
|
|||||||
import { UAParser } from 'ua-parser-js';
|
import { UAParser } from 'ua-parser-js';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { MAXIMUM_PASSKEYS } from '@documenso/lib/constants/auth';
|
||||||
import { AppError } from '@documenso/lib/errors/app-error';
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||||
@ -179,7 +180,7 @@ export const CreatePasskeyDialog = ({ trigger, ...props }: CreatePasskeyDialogPr
|
|||||||
|
|
||||||
<AlertDescription className="mt-2">
|
<AlertDescription className="mt-2">
|
||||||
If you do not want to use the authenticator prompted, you can close it, which will
|
If you do not want to use the authenticator prompted, you can close it, which will
|
||||||
then display the next avaliable authenticator.
|
then display the next available authenticator.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
@ -189,6 +190,11 @@ export const CreatePasskeyDialog = ({ trigger, ...props }: CreatePasskeyDialogPr
|
|||||||
.with('ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED', () => (
|
.with('ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED', () => (
|
||||||
<AlertDescription>This passkey has already been registered.</AlertDescription>
|
<AlertDescription>This passkey has already been registered.</AlertDescription>
|
||||||
))
|
))
|
||||||
|
.with('TOO_MANY_PASSKEYS', () => (
|
||||||
|
<AlertDescription>
|
||||||
|
You cannot have more than {MAXIMUM_PASSKEYS} passkeys.
|
||||||
|
</AlertDescription>
|
||||||
|
))
|
||||||
.with('InvalidStateError', () => (
|
.with('InvalidStateError', () => (
|
||||||
<>
|
<>
|
||||||
<AlertTitle className="text-sm">
|
<AlertTitle className="text-sm">
|
||||||
|
|||||||
@ -32,3 +32,8 @@ export const USER_SECURITY_AUDIT_LOG_MAP: { [key in UserSecurityAuditLogType]: s
|
|||||||
* The duration to wait for a passkey to be verified in MS.
|
* The duration to wait for a passkey to be verified in MS.
|
||||||
*/
|
*/
|
||||||
export const PASSKEY_TIMEOUT = 60000;
|
export const PASSKEY_TIMEOUT = 60000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of passkeys are user can have.
|
||||||
|
*/
|
||||||
|
export const MAXIMUM_PASSKEYS = 50;
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type { RegistrationResponseJSON } from '@simplewebauthn/types';
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { MAXIMUM_PASSKEYS } from '../../constants/auth';
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { getAuthenticatorRegistrationOptions } from '../../utils/authenticator';
|
import { getAuthenticatorRegistrationOptions } from '../../utils/authenticator';
|
||||||
@ -21,12 +22,23 @@ export const createPasskey = async ({
|
|||||||
verificationResponse,
|
verificationResponse,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}: CreatePasskeyOptions) => {
|
}: CreatePasskeyOptions) => {
|
||||||
await prisma.user.findFirstOrThrow({
|
const { _count } = await prisma.user.findFirstOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: userId,
|
id: userId,
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
passkeys: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (_count.passkeys >= MAXIMUM_PASSKEYS) {
|
||||||
|
throw new AppError('TOO_MANY_PASSKEYS');
|
||||||
|
}
|
||||||
|
|
||||||
const verificationToken = await prisma.verificationToken.findFirst({
|
const verificationToken = await prisma.verificationToken.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@ -110,10 +110,7 @@ export const authRouter = router({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw AppError.parseErrorToTRPCError(err);
|
||||||
code: 'BAD_REQUEST',
|
|
||||||
message: 'We were unable to create this passkey. Please try again later.',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user