This commit is contained in:
David Nguyen
2025-01-31 14:09:02 +11:00
parent f7a98180d7
commit d7d0fca501
146 changed files with 1250 additions and 1263 deletions

View File

@ -1,5 +1,8 @@
import { env } from '@documenso/lib/utils/env';
// Todo: Delete
export const authDebugger = (message: string) => {
if (process.env.NODE_ENV === 'development') {
if (env('NODE_ENV') === 'development') {
console.log(`[DEBUG]: ${message}`);
}
};

View File

@ -4,6 +4,7 @@ import { Hono } from 'hono';
import { DateTime } from 'luxon';
import { z } from 'zod';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { AppError } from '@documenso/lib/errors/app-error';
import { jobsClient } from '@documenso/lib/jobs/client';
import { disableTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/disable-2fa';
@ -16,19 +17,25 @@ import { createUser } from '@documenso/lib/server-only/user/create-user';
import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password';
import { getMostRecentVerificationTokenByUserId } from '@documenso/lib/server-only/user/get-most-recent-verification-token-by-user-id';
import { resetPassword } from '@documenso/lib/server-only/user/reset-password';
import { verifyEmail } from '@documenso/lib/server-only/user/verify-email';
import { updatePassword } from '@documenso/lib/server-only/user/update-password';
import {
EMAIL_VERIFICATION_STATE,
verifyEmail,
} from '@documenso/lib/server-only/user/verify-email';
import { env } from '@documenso/lib/utils/env';
import { prisma } from '@documenso/prisma';
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
import { AuthenticationErrorCode } from '../lib/errors/error-codes';
import { onAuthorize } from '../lib/utils/authorizer';
import { getRequiredSession } from '../lib/utils/get-session';
import { getRequiredSession, getSession } from '../lib/utils/get-session';
import type { HonoAuthContext } from '../types/context';
import {
ZForgotPasswordSchema,
ZResetPasswordSchema,
ZSignInFormSchema,
ZSignUpRequestSchema,
ZSignInSchema,
ZSignUpSchema,
ZUpdatePasswordSchema,
ZVerifyEmailSchema,
} from '../types/email-password';
@ -36,7 +43,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
/**
* Authorize endpoint.
*/
.post('/authorize', zValidator('json', ZSignInFormSchema), async (c) => {
.post('/authorize', zValidator('json', ZSignInSchema), async (c) => {
const requestMetadata = c.get('requestMetadata');
const { email, password, totpCode, backupCode } = c.req.valid('json');
@ -48,8 +55,8 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
});
if (!user || !user.password) {
throw new AppError(AuthenticationErrorCode.NotFound, {
message: 'User not found',
throw new AppError(AuthenticationErrorCode.InvalidCredentials, {
message: 'Invalid email or password',
});
}
@ -85,7 +92,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
},
});
throw new AppError(AuthenticationErrorCode.IncorrectTwoFactorCode);
throw new AppError(AuthenticationErrorCode.InvalidTwoFactorCode);
}
}
@ -125,20 +132,20 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
/**
* Signup endpoint.
*/
.post('/signup', zValidator('json', ZSignUpRequestSchema), async (c) => {
// if (NEXT_PUBLIC_DISABLE_SIGNUP() === 'true') {
// throw new AppError('SIGNUP_DISABLED', {
// message: 'Signups are disabled.',
// });
// }
.post('/signup', zValidator('json', ZSignUpSchema), async (c) => {
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
throw new AppError('SIGNUP_DISABLED', {
message: 'Signups are disabled.',
});
}
const { name, email, password, signature, url } = c.req.valid('json');
// if (IS_BILLING_ENABLED() && url && url.length < 6) {
// throw new AppError(AppErrorCode.PREMIUM_PROFILE_URL, {
// message: 'Only subscribers can have a username shorter than 6 characters',
// });
// }
if (IS_BILLING_ENABLED() && url && url.length < 6) {
throw new AppError('PREMIUM_PROFILE_URL', {
message: 'Only subscribers can have a username shorter than 6 characters',
});
}
const user = await createUser({ name, email, password, signature, url });
@ -149,18 +156,59 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
},
});
// Todo: Check this.
return c.json({
user,
return c.text('OK', 201);
})
/**
* Update password endpoint.
*/
.post('/update-password', zValidator('json', ZUpdatePasswordSchema), async (c) => {
const { password, currentPassword } = c.req.valid('json');
const requestMetadata = c.get('requestMetadata');
const session = await getSession(c);
if (!session.isAuthenticated) {
throw new AppError(AuthenticationErrorCode.Unauthorized);
}
await updatePassword({
userId: session.user.id,
password,
currentPassword,
requestMetadata,
});
return c.text('OK', 201);
})
/**
* Verify email endpoint.
*/
.post('/verify-email', zValidator('json', ZVerifyEmailSchema), async (c) => {
await verifyEmail({ token: c.req.valid('json').token });
const { state, userId } = await verifyEmail({ token: c.req.valid('json').token });
return c.text('OK', 201);
// If email is verified, automatically authenticate user.
if (state === EMAIL_VERIFICATION_STATE.VERIFIED && userId !== null) {
await onAuthorize({ userId }, c);
}
return c.json({
state,
});
})
/**
* Resend verification email endpoint.
*/
.post('/resend-email', zValidator('json', ZVerifyEmailSchema), async (c) => {
const { state, userId } = await verifyEmail({ token: c.req.valid('json').token });
// If email is verified, automatically authenticate user.
if (state === EMAIL_VERIFICATION_STATE.VERIFIED && userId !== null) {
await onAuthorize({ userId }, c);
}
return c.json({
state,
});
})
/**
* Forgot password endpoint.
@ -180,9 +228,12 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
.post('/reset-password', zValidator('json', ZResetPasswordSchema), async (c) => {
const { token, password } = c.req.valid('json');
const requestMetadata = c.get('requestMetadata');
await resetPassword({
token,
password,
requestMetadata,
});
return c.text('OK', 201);
@ -291,9 +342,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
requestMetadata,
});
return c.json({
success: true,
});
return c.text('OK', 201);
},
)
/**

View File

@ -4,6 +4,7 @@ import { getCookie, setCookie } from 'hono/cookie';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { setupTwoFactorAuthentication } from '@documenso/lib/server-only/2fa/setup-2fa';
import { env } from '@documenso/lib/utils/env';
import { prisma } from '@documenso/prisma';
import { AuthenticationErrorCode } from '../lib/errors/error-codes';
@ -12,8 +13,8 @@ import { getRequiredSession } from '../lib/utils/get-session';
import type { HonoAuthContext } from '../types/context';
const options = {
clientId: import.meta.env.NEXT_PRIVATE_GOOGLE_CLIENT_ID,
clientSecret: import.meta.env.NEXT_PRIVATE_GOOGLE_CLIENT_SECRET,
clientId: env('NEXT_PRIVATE_GOOGLE_CLIENT_ID'),
clientSecret: env('NEXT_PRIVATE_GOOGLE_CLIENT_SECRET'),
redirectUri: 'http://localhost:3000/api/auth/google/callback',
scope: ['openid', 'email', 'profile'],
id: 'google',
@ -36,7 +37,7 @@ export const googleRoute = new Hono<HonoAuthContext>()
setCookie(c, 'google_oauth_state', state, {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
secure: env('NODE_ENV') === 'production',
maxAge: 60 * 10, // 10 minutes
sameSite: 'lax',
});
@ -44,7 +45,8 @@ export const googleRoute = new Hono<HonoAuthContext>()
setCookie(c, 'google_code_verifier', codeVerifier, {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
// Todo: Might not be node_env but something vite specific?
secure: env('NODE_ENV') === 'production',
maxAge: 60 * 10, // 10 minutes
sameSite: 'lax',
});

View File

@ -5,14 +5,14 @@ export const ZCurrentPasswordSchema = z
.min(6, { message: 'Must be at least 6 characters in length' })
.max(72);
export const ZSignInFormSchema = z.object({
export const ZSignInSchema = z.object({
email: z.string().email().min(1),
password: ZCurrentPasswordSchema,
totpCode: z.string().trim().optional(),
backupCode: z.string().trim().optional(),
});
export type TSignInFormSchema = z.infer<typeof ZSignInFormSchema>;
export type TSignInSchema = z.infer<typeof ZSignInSchema>;
export const ZPasswordSchema = z
.string()
@ -31,7 +31,7 @@ export const ZPasswordSchema = z
message: 'One special character is required',
});
export const ZSignUpRequestSchema = z.object({
export const ZSignUpSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
password: ZPasswordSchema,
@ -47,7 +47,7 @@ export const ZSignUpRequestSchema = z.object({
.optional(),
});
export type TSignUpRequestSchema = z.infer<typeof ZSignUpRequestSchema>;
export type TSignUpSchema = z.infer<typeof ZSignUpSchema>;
export const ZForgotPasswordSchema = z.object({
email: z.string().email().min(1),
@ -67,3 +67,10 @@ export const ZVerifyEmailSchema = z.object({
});
export type TVerifyEmailSchema = z.infer<typeof ZVerifyEmailSchema>;
export const ZUpdatePasswordSchema = z.object({
currentPassword: ZCurrentPasswordSchema,
password: ZPasswordSchema,
});
export type TUpdatePasswordSchema = z.infer<typeof ZUpdatePasswordSchema>;