import { prisma } from '@documenso/prisma'; import type { AuthenticatorTransportFuture } from '@simplewebauthn/server'; import { generateRegistrationOptions } from '@simplewebauthn/server'; import { isoBase64URL } from '@simplewebauthn/server/helpers'; import { DateTime } from 'luxon'; import { PASSKEY_TIMEOUT } from '../../constants/auth'; import { getAuthenticatorOptions } from '../../utils/authenticator'; type CreatePasskeyRegistrationOptions = { userId: number; }; export const createPasskeyRegistrationOptions = async ({ userId }: CreatePasskeyRegistrationOptions) => { const user = await prisma.user.findFirstOrThrow({ where: { id: userId, }, select: { name: true, email: true, passkeys: true, }, }); const { passkeys } = user; const { rpName, rpId: rpID } = getAuthenticatorOptions(); const options = await generateRegistrationOptions({ rpName, rpID, userID: Buffer.from(userId.toString()), userName: user.email, userDisplayName: user.name ?? undefined, timeout: PASSKEY_TIMEOUT, attestationType: 'none', excludeCredentials: passkeys.map((passkey) => ({ id: isoBase64URL.fromBuffer(passkey.credentialId), // eslint-disable-next-line @typescript-eslint/consistent-type-assertions transports: passkey.transports as AuthenticatorTransportFuture[], })), }); await prisma.verificationToken.create({ data: { userId, token: options.challenge, expires: DateTime.now().plus({ minutes: 2 }).toJSDate(), identifier: 'PASSKEY_CHALLENGE', }, }); return options; };