Files
documenso/packages/trpc/server/auth-router/router.ts
Catalin Pit 8b58f10cbe feat: add cta on complete page (#1028)
![CleanShot 2024-03-18 at 11 45
40](https://github.com/documenso/documenso/assets/25515812/ae3b88de-359d-4019-866a-a76097bbb0fe)
![CleanShot 2024-03-18 at 11 46
25](https://github.com/documenso/documenso/assets/25515812/b5ff7078-623e-476c-8800-17d14bc8efa9)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a "Claim Account" feature allowing new users to sign up by
providing their name, email, and password.
- Enhanced user experience for both logged-in and non-logged-in users
with improved UI/UX and additional functionality.

- **Enhancements**
- Implemented form validation and error handling for a smoother sign-up
process.
	- Integrated analytics to track user actions during account claiming.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Lucas Smith <me@lucasjamessmith.me>
Co-authored-by: David Nguyen <davidngu28@gmail.com>
2024-04-11 10:09:04 +03:00

244 lines
7.5 KiB
TypeScript

import type { RegistrationResponseJSON } from '@simplewebauthn/types';
import { TRPCError } from '@trpc/server';
import { parse } from 'cookie-es';
import { env } from 'next-runtime-env';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { ErrorCode } from '@documenso/lib/next-auth/error-codes';
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 { compareSync } from '@documenso/lib/server-only/auth/hash';
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
import { createUser } from '@documenso/lib/server-only/user/create-user';
import { sendConfirmationToken } from '@documenso/lib/server-only/user/send-confirmation-token';
import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { authenticatedProcedure, procedure, router } from '../trpc';
import {
ZCreatePasskeyAuthenticationOptionsMutationSchema,
ZCreatePasskeyMutationSchema,
ZDeletePasskeyMutationSchema,
ZFindPasskeysQuerySchema,
ZSignUpMutationSchema,
ZUpdatePasskeyMutationSchema,
ZVerifyPasswordMutationSchema,
} from './schema';
const NEXT_PUBLIC_DISABLE_SIGNUP = () => env('NEXT_PUBLIC_DISABLE_SIGNUP');
export const authRouter = router({
signup: procedure.input(ZSignUpMutationSchema).mutation(async ({ input }) => {
try {
if (NEXT_PUBLIC_DISABLE_SIGNUP() === 'true') {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Signups are disabled.',
});
}
const { name, email, password, signature, url } = input;
if (IS_BILLING_ENABLED() && url && url.length < 6) {
throw new AppError(
AppErrorCode.PREMIUM_PROFILE_URL,
'Only subscribers can have a username shorter than 6 characters',
);
}
const user = await createUser({ name, email, password, signature, url });
await sendConfirmationToken({ email: user.email });
return user;
} catch (err) {
console.error(err);
const error = AppError.parseError(err);
if (error.code !== AppErrorCode.UNKNOWN_ERROR) {
throw AppError.parseErrorToTRPCError(error);
}
let message =
'We were unable to create your account. Please review the information you provided and try again.';
if (err instanceof Error && err.message === 'User already exists') {
message = 'User with this email already exists. Please use a different email address.';
}
throw new TRPCError({
code: 'BAD_REQUEST',
message,
});
}
}),
verifyPassword: authenticatedProcedure
.input(ZVerifyPasswordMutationSchema)
.mutation(({ ctx, input }) => {
const user = ctx.user;
const { password } = input;
if (!user.password) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: ErrorCode.INCORRECT_PASSWORD,
});
}
const valid = compareSync(password, user.password);
return valid;
}),
createPasskey: authenticatedProcedure
.input(ZCreatePasskeyMutationSchema)
.mutation(async ({ ctx, input }) => {
try {
// 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: extractNextApiRequestMetadata(ctx.req),
});
} catch (err) {
console.error(err);
throw AppError.parseErrorToTRPCError(err);
}
}),
createPasskeyAuthenticationOptions: authenticatedProcedure
.input(ZCreatePasskeyAuthenticationOptionsMutationSchema)
.mutation(async ({ ctx, input }) => {
try {
return await createPasskeyAuthenticationOptions({
userId: ctx.user.id,
preferredPasskeyId: input?.preferredPasskeyId,
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'We were unable to create the authentication options for the passkey. Please try again later.',
});
}
}),
createPasskeyRegistrationOptions: authenticatedProcedure.mutation(async ({ ctx }) => {
try {
return await createPasskeyRegistrationOptions({
userId: ctx.user.id,
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message:
'We were unable to create the registration options for the passkey. Please try again later.',
});
}
}),
createPasskeySigninOptions: procedure.mutation(async ({ ctx }) => {
const cookies = parse(ctx.req.headers.cookie ?? '');
const sessionIdToken =
cookies['__Host-next-auth.csrf-token'] || cookies['next-auth.csrf-token'];
if (!sessionIdToken) {
throw new Error('Missing CSRF token');
}
const [sessionId] = decodeURI(sessionIdToken).split('|');
try {
return await createPasskeySigninOptions({ sessionId });
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to create the options for passkey signin. Please try again later.',
});
}
}),
deletePasskey: authenticatedProcedure
.input(ZDeletePasskeyMutationSchema)
.mutation(async ({ ctx, input }) => {
try {
const { passkeyId } = input;
await deletePasskey({
userId: ctx.user.id,
passkeyId,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to delete this passkey. Please try again later.',
});
}
}),
findPasskeys: authenticatedProcedure
.input(ZFindPasskeysQuerySchema)
.query(async ({ input, ctx }) => {
try {
const { page, perPage, orderBy } = input;
return await findPasskeys({
page,
perPage,
orderBy,
userId: ctx.user.id,
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to find passkeys. Please try again later.',
});
}
}),
updatePasskey: authenticatedProcedure
.input(ZUpdatePasskeyMutationSchema)
.mutation(async ({ ctx, input }) => {
try {
const { passkeyId, name } = input;
await updatePasskey({
userId: ctx.user.id,
passkeyId,
name,
requestMetadata: extractNextApiRequestMetadata(ctx.req),
});
} catch (err) {
console.error(err);
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'We were unable to update this passkey. Please try again later.',
});
}
}),
});