build: upgrade simplewebauthn packages from v9 to v13 (#2389)

The v9 packages are deprecated. This updates to v13 which includes
breaking API changes: optionsJSON wrapper for auth functions,
renamed properties (authenticator→credential), and base64 encoding
for credential IDs via isoBase64URL helper.
This commit is contained in:
Lucas Smith
2026-01-15 14:22:37 +11:00
committed by GitHub
parent c976e747e3
commit fabd69bd62
16 changed files with 361 additions and 53 deletions
+2 -1
View File
@@ -15,6 +15,7 @@
"@hono/standard-validator": "^0.2.0",
"@oslojs/crypto": "^1.0.1",
"@oslojs/encoding": "^1.1.0",
"@simplewebauthn/server": "^13.2.2",
"arctic": "^3.7.0",
"hono": "4.10.6",
"luxon": "^3.7.2",
@@ -22,4 +23,4 @@
"ts-pattern": "^5.9.0",
"zod": "^3.25.76"
}
}
}
+4 -3
View File
@@ -1,6 +1,7 @@
import { sValidator } from '@hono/standard-validator';
import { UserSecurityAuditLogType } from '@prisma/client';
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
import { Hono } from 'hono';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
@@ -80,9 +81,9 @@ export const passkeyRoute = new Hono<HonoAuthContext>()
expectedChallenge: challengeToken.token,
expectedOrigin: origin,
expectedRPID: rpId,
authenticator: {
credentialID: new Uint8Array(Array.from(passkey.credentialId)),
credentialPublicKey: new Uint8Array(passkey.credentialPublicKey),
credential: {
id: isoBase64URL.fromBuffer(passkey.credentialId),
publicKey: new Uint8Array(passkey.credentialPublicKey),
counter: Number(passkey.counter),
},
}).catch(() => null);
+1
View File
@@ -34,6 +34,7 @@
"@node-rs/bcrypt": "^1.10.7",
"@pdf-lib/fontkit": "^1.1.1",
"@scure/base": "^1.2.6",
"@simplewebauthn/server": "^13.2.2",
"@sindresorhus/slugify": "^3.0.0",
"@team-plain/typescript-sdk": "^5.11.0",
"@vvo/tzdb": "^6.196.0",
@@ -1,6 +1,7 @@
import type { Passkey } from '@prisma/client';
import { generateAuthenticationOptions } from '@simplewebauthn/server';
import type { AuthenticatorTransportFuture } from '@simplewebauthn/types';
import type { AuthenticatorTransportFuture } from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
import { DateTime } from 'luxon';
import { prisma } from '@documenso/prisma';
@@ -53,8 +54,7 @@ export const createPasskeyAuthenticationOptions = async ({
allowCredentials: preferredPasskey
? [
{
id: preferredPasskey.credentialId,
type: 'public-key',
id: isoBase64URL.fromBuffer(preferredPasskey.credentialId),
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
transports: preferredPasskey.transports as AuthenticatorTransportFuture[],
},
@@ -1,5 +1,6 @@
import { generateRegistrationOptions } from '@simplewebauthn/server';
import type { AuthenticatorTransportFuture } from '@simplewebauthn/types';
import type { AuthenticatorTransportFuture } from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
import { DateTime } from 'luxon';
import { prisma } from '@documenso/prisma';
@@ -32,14 +33,13 @@ export const createPasskeyRegistrationOptions = async ({
const options = await generateRegistrationOptions({
rpName,
rpID,
userID: userId.toString(),
userID: Buffer.from(userId.toString()),
userName: user.email,
userDisplayName: user.name ?? undefined,
timeout: PASSKEY_TIMEOUT,
attestationType: 'none',
excludeCredentials: passkeys.map((passkey) => ({
id: passkey.credentialId,
type: 'public-key',
id: isoBase64URL.fromBuffer(passkey.credentialId),
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
transports: passkey.transports as AuthenticatorTransportFuture[],
})),
@@ -1,6 +1,7 @@
import { UserSecurityAuditLogType } from '@prisma/client';
import { verifyRegistrationResponse } from '@simplewebauthn/server';
import type { RegistrationResponseJSON } from '@simplewebauthn/types';
import type { RegistrationResponseJSON } from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
import { prisma } from '@documenso/prisma';
@@ -83,20 +84,19 @@ export const createPasskey = async ({
});
}
const { credentialPublicKey, credentialID, counter, credentialDeviceType, credentialBackedUp } =
verification.registrationInfo;
const { credentialDeviceType, credentialBackedUp, credential } = verification.registrationInfo;
await prisma.$transaction(async (tx) => {
await tx.passkey.create({
data: {
userId,
name: passkeyName,
credentialId: Buffer.from(credentialID),
credentialPublicKey: Buffer.from(credentialPublicKey),
counter,
credentialId: Buffer.from(isoBase64URL.toBuffer(credential.id)),
credentialPublicKey: Buffer.from(credential.publicKey),
counter: credential.counter,
credentialDeviceType,
credentialBackedUp,
transports: verificationResponse.response.transports,
transports: credential.transports,
},
});
@@ -1,5 +1,6 @@
import type { Envelope, Recipient } from '@prisma/client';
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';
import { match } from 'ts-pattern';
import { prisma } from '@documenso/prisma';
@@ -252,9 +253,9 @@ const verifyPasskey = async ({
expectedChallenge: verificationToken.token,
expectedOrigin: origin,
expectedRPID: rpId,
authenticator: {
credentialID: new Uint8Array(Array.from(passkey.credentialId)),
credentialPublicKey: new Uint8Array(passkey.credentialPublicKey),
credential: {
id: isoBase64URL.fromBuffer(passkey.credentialId),
publicKey: new Uint8Array(passkey.credentialPublicKey),
counter: Number(passkey.counter),
},
}).catch(() => null); // May want to log this for insights.
+1
View File
@@ -12,6 +12,7 @@
"dependencies": {
"@documenso/lib": "*",
"@documenso/prisma": "*",
"@simplewebauthn/server": "^13.2.2",
"@tanstack/react-query": "5.90.10",
"@trpc/client": "11.8.1",
"@trpc/react-query": "11.8.1",
@@ -1,4 +1,4 @@
import type { RegistrationResponseJSON } from '@simplewebauthn/types';
import type { RegistrationResponseJSON } from '@simplewebauthn/server';
import { createPasskey } from '@documenso/lib/server-only/auth/create-passkey';