fix: refactor auth router (#1983)

This commit is contained in:
David Nguyen
2025-08-25 08:24:32 +10:00
committed by GitHub
parent 5a5bfe6e34
commit 49fabeb0ec
22 changed files with 280 additions and 167 deletions

View File

@ -65,9 +65,9 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
});
const { mutateAsync: createPasskeyRegistrationOptions, isPending } =
trpc.auth.createPasskeyRegistrationOptions.useMutation();
trpc.auth.passkey.createRegistrationOptions.useMutation();
const { mutateAsync: createPasskey } = trpc.auth.createPasskey.useMutation();
const { mutateAsync: createPasskey } = trpc.auth.passkey.create.useMutation();
const onFormSubmit = async ({ passkeyName }: TCreatePasskeyFormSchema) => {
setFormError(null);

View File

@ -114,7 +114,7 @@ export const SignInForm = ({
}, [returnTo]);
const { mutateAsync: createPasskeySigninOptions } =
trpc.auth.createPasskeySigninOptions.useMutation();
trpc.auth.passkey.createSigninOptions.useMutation();
const form = useForm<TSignInFormSchema>({
values: {

View File

@ -77,7 +77,7 @@ export const DocumentSigningAuthPasskey = ({
});
const { mutateAsync: createPasskeyAuthenticationOptions } =
trpc.auth.createPasskeyAuthenticationOptions.useMutation();
trpc.auth.passkey.createAuthenticationOptions.useMutation();
const [formErrorCode, setFormErrorCode] = useState<string | null>(null);

View File

@ -93,7 +93,7 @@ export const DocumentSigningAuthProvider = ({
[documentAuthOptions, recipient],
);
const passkeyQuery = trpc.auth.findPasskeys.useQuery(
const passkeyQuery = trpc.auth.passkey.find.useQuery(
{
perPage: MAXIMUM_PASSKEYS,
},

View File

@ -62,7 +62,7 @@ export const SettingsSecurityPasskeyTableActions = ({
});
const { mutateAsync: updatePasskey, isPending: isUpdatingPasskey } =
trpc.auth.updatePasskey.useMutation({
trpc.auth.passkey.update.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
@ -84,7 +84,7 @@ export const SettingsSecurityPasskeyTableActions = ({
});
const { mutateAsync: deletePasskey, isPending: isDeletingPasskey } =
trpc.auth.deletePasskey.useMutation({
trpc.auth.passkey.delete.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),

View File

@ -26,7 +26,7 @@ export const SettingsSecurityPasskeyTable = () => {
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
const { data, isLoading, isLoadingError } = trpc.auth.findPasskeys.useQuery(
const { data, isLoading, isLoadingError } = trpc.auth.passkey.find.useQuery(
{
page: parsedSearchParams.page,
perPage: parsedSearchParams.perPage,

View File

@ -0,0 +1,17 @@
import { createPasskeyAuthenticationOptions } from '@documenso/lib/server-only/auth/create-passkey-authentication-options';
import { authenticatedProcedure } from '../trpc';
import {
ZCreatePasskeyAuthenticationOptionsRequestSchema,
ZCreatePasskeyAuthenticationOptionsResponseSchema,
} from './create-passkey-authentication-options.types';
export const createPasskeyAuthenticationOptionsRoute = authenticatedProcedure
.input(ZCreatePasskeyAuthenticationOptionsRequestSchema)
.output(ZCreatePasskeyAuthenticationOptionsResponseSchema)
.mutation(async ({ ctx, input }) => {
return await createPasskeyAuthenticationOptions({
userId: ctx.user.id,
preferredPasskeyId: input?.preferredPasskeyId,
});
});

View File

@ -0,0 +1,19 @@
import { z } from 'zod';
export const ZCreatePasskeyAuthenticationOptionsRequestSchema = z
.object({
preferredPasskeyId: z.string().optional(),
})
.optional();
export const ZCreatePasskeyAuthenticationOptionsResponseSchema = z.object({
tokenReference: z.string(),
options: z.any(), // PublicKeyCredentialRequestOptions type
});
export type TCreatePasskeyAuthenticationOptionsRequest = z.infer<
typeof ZCreatePasskeyAuthenticationOptionsRequestSchema
>;
export type TCreatePasskeyAuthenticationOptionsResponse = z.infer<
typeof ZCreatePasskeyAuthenticationOptionsResponseSchema
>;

View File

@ -0,0 +1,16 @@
import { createPasskeyRegistrationOptions } from '@documenso/lib/server-only/auth/create-passkey-registration-options';
import { authenticatedProcedure } from '../trpc';
import {
ZCreatePasskeyRegistrationOptionsRequestSchema,
ZCreatePasskeyRegistrationOptionsResponseSchema,
} from './create-passkey-registration-options.types';
export const createPasskeyRegistrationOptionsRoute = authenticatedProcedure
.input(ZCreatePasskeyRegistrationOptionsRequestSchema)
.output(ZCreatePasskeyRegistrationOptionsResponseSchema)
.mutation(async ({ ctx }) => {
return await createPasskeyRegistrationOptions({
userId: ctx.user.id,
});
});

View File

@ -0,0 +1,12 @@
import { z } from 'zod';
export const ZCreatePasskeyRegistrationOptionsRequestSchema = z.void();
export const ZCreatePasskeyRegistrationOptionsResponseSchema = z.any(); // PublicKeyCredentialCreationOptions type
export type TCreatePasskeyRegistrationOptionsRequest = z.infer<
typeof ZCreatePasskeyRegistrationOptionsRequestSchema
>;
export type TCreatePasskeyRegistrationOptionsResponse = z.infer<
typeof ZCreatePasskeyRegistrationOptionsResponseSchema
>;

View File

@ -0,0 +1,24 @@
import { createPasskeySigninOptions } from '@documenso/lib/server-only/auth/create-passkey-signin-options';
import { nanoid } from '@documenso/lib/universal/id';
import { procedure } from '../trpc';
import {
ZCreatePasskeySigninOptionsRequestSchema,
ZCreatePasskeySigninOptionsResponseSchema,
} from './create-passkey-signin-options.types';
export const createPasskeySigninOptionsRoute = procedure
.input(ZCreatePasskeySigninOptionsRequestSchema)
.output(ZCreatePasskeySigninOptionsResponseSchema)
.mutation(async () => {
const sessionIdToken = nanoid(16);
const [sessionId] = decodeURI(sessionIdToken).split('|');
const options = await createPasskeySigninOptions({ sessionId });
return {
options,
sessionId,
};
});

View File

@ -0,0 +1,15 @@
import { z } from 'zod';
export const ZCreatePasskeySigninOptionsRequestSchema = z.void();
export const ZCreatePasskeySigninOptionsResponseSchema = z.object({
options: z.any(), // PublicKeyCredentialRequestOptions type
sessionId: z.string(),
});
export type TCreatePasskeySigninOptionsRequest = z.infer<
typeof ZCreatePasskeySigninOptionsRequestSchema
>;
export type TCreatePasskeySigninOptionsResponse = z.infer<
typeof ZCreatePasskeySigninOptionsResponseSchema
>;

View File

@ -0,0 +1,21 @@
import type { RegistrationResponseJSON } from '@simplewebauthn/types';
import { createPasskey } from '@documenso/lib/server-only/auth/create-passkey';
import { authenticatedProcedure } from '../trpc';
import { ZCreatePasskeyRequestSchema, ZCreatePasskeyResponseSchema } from './create-passkey.types';
export const createPasskeyRoute = authenticatedProcedure
.input(ZCreatePasskeyRequestSchema)
.output(ZCreatePasskeyResponseSchema)
.mutation(async ({ ctx, input }) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const verificationResponse = input.verificationResponse as RegistrationResponseJSON;
return await createPasskey({
userId: ctx.user.id,
verificationResponse,
passkeyName: input.passkeyName,
requestMetadata: ctx.metadata.requestMetadata,
});
});

View File

@ -0,0 +1,13 @@
import { z } from 'zod';
import { ZRegistrationResponseJSONSchema } from '@documenso/lib/types/webauthn';
export const ZCreatePasskeyRequestSchema = z.object({
passkeyName: z.string().trim().min(1),
verificationResponse: ZRegistrationResponseJSONSchema,
});
export const ZCreatePasskeyResponseSchema = z.void();
export type TCreatePasskeyRequest = z.infer<typeof ZCreatePasskeyRequestSchema>;
export type TCreatePasskeyResponse = z.infer<typeof ZCreatePasskeyResponseSchema>;

View File

@ -0,0 +1,23 @@
import { deletePasskey } from '@documenso/lib/server-only/auth/delete-passkey';
import { authenticatedProcedure } from '../trpc';
import { ZDeletePasskeyRequestSchema, ZDeletePasskeyResponseSchema } from './delete-passkey.types';
export const deletePasskeyRoute = authenticatedProcedure
.input(ZDeletePasskeyRequestSchema)
.output(ZDeletePasskeyResponseSchema)
.mutation(async ({ ctx, input }) => {
const { passkeyId } = input;
ctx.logger.info({
input: {
passkeyId,
},
});
await deletePasskey({
userId: ctx.user.id,
passkeyId,
requestMetadata: ctx.metadata.requestMetadata,
});
});

View File

@ -0,0 +1,10 @@
import { z } from 'zod';
export const ZDeletePasskeyRequestSchema = z.object({
passkeyId: z.string().trim().min(1),
});
export const ZDeletePasskeyResponseSchema = z.void();
export type TDeletePasskeyRequest = z.infer<typeof ZDeletePasskeyRequestSchema>;
export type TDeletePasskeyResponse = z.infer<typeof ZDeletePasskeyResponseSchema>;

View File

@ -0,0 +1,18 @@
import { findPasskeys } from '@documenso/lib/server-only/auth/find-passkeys';
import { authenticatedProcedure } from '../trpc';
import { ZFindPasskeysRequestSchema, ZFindPasskeysResponseSchema } from './find-passkeys.types';
export const findPasskeysRoute = authenticatedProcedure
.input(ZFindPasskeysRequestSchema)
.output(ZFindPasskeysResponseSchema)
.query(async ({ input, ctx }) => {
const { page, perPage, orderBy } = input;
return await findPasskeys({
page,
perPage,
orderBy,
userId: ctx.user.id,
});
});

View File

@ -0,0 +1,33 @@
import { z } from 'zod';
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import PasskeySchema from '@documenso/prisma/generated/zod/modelSchema/PasskeySchema';
export const ZFindPasskeysRequestSchema = ZFindSearchParamsSchema.extend({
orderBy: z
.object({
column: z.enum(['createdAt', 'updatedAt', 'name']),
direction: z.enum(['asc', 'desc']),
})
.optional(),
});
export const ZFindPasskeysResponseSchema = ZFindResultResponse.extend({
data: z.array(
PasskeySchema.pick({
id: true,
userId: true,
name: true,
createdAt: true,
updatedAt: true,
lastUsedAt: true,
counter: true,
credentialDeviceType: true,
credentialBackedUp: true,
transports: true,
}),
),
});
export type TFindPasskeysRequest = z.infer<typeof ZFindPasskeysRequestSchema>;
export type TFindPasskeysResponse = z.infer<typeof ZFindPasskeysResponseSchema>;

View File

@ -1,113 +1,20 @@
import type { RegistrationResponseJSON } from '@simplewebauthn/types';
import { createPasskey } from '@documenso/lib/server-only/auth/create-passkey';
import { createPasskeyAuthenticationOptions } from '@documenso/lib/server-only/auth/create-passkey-authentication-options';
import { createPasskeyRegistrationOptions } from '@documenso/lib/server-only/auth/create-passkey-registration-options';
import { createPasskeySigninOptions } from '@documenso/lib/server-only/auth/create-passkey-signin-options';
import { deletePasskey } from '@documenso/lib/server-only/auth/delete-passkey';
import { findPasskeys } from '@documenso/lib/server-only/auth/find-passkeys';
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
import { nanoid } from '@documenso/lib/universal/id';
import { authenticatedProcedure, procedure, router } from '../trpc';
import {
ZCreatePasskeyAuthenticationOptionsMutationSchema,
ZCreatePasskeyMutationSchema,
ZDeletePasskeyMutationSchema,
ZFindPasskeysQuerySchema,
ZUpdatePasskeyMutationSchema,
} from './schema';
import { router } from '../trpc';
import { createPasskeyRoute } from './create-passkey';
import { createPasskeyAuthenticationOptionsRoute } from './create-passkey-authentication-options';
import { createPasskeyRegistrationOptionsRoute } from './create-passkey-registration-options';
import { createPasskeySigninOptionsRoute } from './create-passkey-signin-options';
import { deletePasskeyRoute } from './delete-passkey';
import { findPasskeysRoute } from './find-passkeys';
import { updatePasskeyRoute } from './update-passkey';
export const authRouter = router({
createPasskey: authenticatedProcedure
.input(ZCreatePasskeyMutationSchema)
.mutation(async ({ ctx, input }) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const verificationResponse = input.verificationResponse as RegistrationResponseJSON;
return await createPasskey({
userId: ctx.user.id,
verificationResponse,
passkeyName: input.passkeyName,
requestMetadata: ctx.metadata.requestMetadata,
});
}),
createPasskeyAuthenticationOptions: authenticatedProcedure
.input(ZCreatePasskeyAuthenticationOptionsMutationSchema)
.mutation(async ({ ctx, input }) => {
return await createPasskeyAuthenticationOptions({
userId: ctx.user.id,
preferredPasskeyId: input?.preferredPasskeyId,
});
}),
createPasskeyRegistrationOptions: authenticatedProcedure.mutation(async ({ ctx }) => {
return await createPasskeyRegistrationOptions({
userId: ctx.user.id,
});
passkey: router({
create: createPasskeyRoute,
createAuthenticationOptions: createPasskeyAuthenticationOptionsRoute,
createRegistrationOptions: createPasskeyRegistrationOptionsRoute,
createSigninOptions: createPasskeySigninOptionsRoute,
delete: deletePasskeyRoute,
find: findPasskeysRoute,
update: updatePasskeyRoute,
}),
createPasskeySigninOptions: procedure.mutation(async () => {
const sessionIdToken = nanoid(16);
const [sessionId] = decodeURI(sessionIdToken).split('|');
const options = await createPasskeySigninOptions({ sessionId });
return {
options,
sessionId,
};
}),
deletePasskey: authenticatedProcedure
.input(ZDeletePasskeyMutationSchema)
.mutation(async ({ ctx, input }) => {
const { passkeyId } = input;
ctx.logger.info({
input: {
passkeyId,
},
});
await deletePasskey({
userId: ctx.user.id,
passkeyId,
requestMetadata: ctx.metadata.requestMetadata,
});
}),
findPasskeys: authenticatedProcedure
.input(ZFindPasskeysQuerySchema)
.query(async ({ input, ctx }) => {
const { page, perPage, orderBy } = input;
return await findPasskeys({
page,
perPage,
orderBy,
userId: ctx.user.id,
});
}),
updatePasskey: authenticatedProcedure
.input(ZUpdatePasskeyMutationSchema)
.mutation(async ({ ctx, input }) => {
const { passkeyId, name } = input;
ctx.logger.info({
input: {
passkeyId,
},
});
await updatePasskey({
userId: ctx.user.id,
passkeyId,
name,
requestMetadata: ctx.metadata.requestMetadata,
});
}),
});

View File

@ -1,8 +1,5 @@
import { z } from 'zod';
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
import { ZRegistrationResponseJSONSchema } from '@documenso/lib/types/webauthn';
export const ZCurrentPasswordSchema = z
.string()
.min(6, { message: 'Must be at least 6 characters in length' })
@ -24,50 +21,3 @@ export const ZPasswordSchema = z
.refine((value) => value.length > 25 || /[`~<>?,./!@#$%^&*()\-_"'+=|{}[\];:\\]/.test(value), {
message: 'One special character is required',
});
export const ZSignUpMutationSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
password: ZPasswordSchema,
signature: z.string().nullish(),
url: z
.string()
.trim()
.toLowerCase()
.min(1)
.regex(/^[a-z0-9-]+$/, {
message: 'Username can only container alphanumeric characters and dashes.',
})
.optional(),
});
export const ZCreatePasskeyMutationSchema = z.object({
passkeyName: z.string().trim().min(1),
verificationResponse: ZRegistrationResponseJSONSchema,
});
export const ZCreatePasskeyAuthenticationOptionsMutationSchema = z
.object({
preferredPasskeyId: z.string().optional(),
})
.optional();
export const ZDeletePasskeyMutationSchema = z.object({
passkeyId: z.string().trim().min(1),
});
export const ZUpdatePasskeyMutationSchema = z.object({
passkeyId: z.string().trim().min(1),
name: z.string().trim().min(1),
});
export const ZFindPasskeysQuerySchema = ZFindSearchParamsSchema.extend({
orderBy: z
.object({
column: z.enum(['createdAt', 'updatedAt', 'name']),
direction: z.enum(['asc', 'desc']),
})
.optional(),
});
export type TSignUpMutationSchema = z.infer<typeof ZSignUpMutationSchema>;

View File

@ -0,0 +1,24 @@
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
import { authenticatedProcedure } from '../trpc';
import { ZUpdatePasskeyRequestSchema, ZUpdatePasskeyResponseSchema } from './update-passkey.types';
export const updatePasskeyRoute = authenticatedProcedure
.input(ZUpdatePasskeyRequestSchema)
.output(ZUpdatePasskeyResponseSchema)
.mutation(async ({ ctx, input }) => {
const { passkeyId, name } = input;
ctx.logger.info({
input: {
passkeyId,
},
});
await updatePasskey({
userId: ctx.user.id,
passkeyId,
name,
requestMetadata: ctx.metadata.requestMetadata,
});
});

View File

@ -0,0 +1,11 @@
import { z } from 'zod';
export const ZUpdatePasskeyRequestSchema = z.object({
passkeyId: z.string().trim().min(1),
name: z.string().trim().min(1),
});
export const ZUpdatePasskeyResponseSchema = z.void();
export type TUpdatePasskeyRequest = z.infer<typeof ZUpdatePasskeyRequestSchema>;
export type TUpdatePasskeyResponse = z.infer<typeof ZUpdatePasskeyResponseSchema>;