This commit is contained in:
David Nguyen
2025-02-12 18:39:00 +11:00
parent 15922d447b
commit 4c57095ee1
11 changed files with 72 additions and 231 deletions

View File

@ -2,9 +2,11 @@ import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';
import type { ContentfulStatusCode } from 'hono/utils/http-status';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { setCsrfCookie } from './lib/session/session-cookies';
import { emailPasswordRoute } from './routes/email-password';
import { googleRoute } from './routes/google';
import { passkeyRoute } from './routes/passkey';
@ -16,8 +18,28 @@ import type { HonoAuthContext } from './types/context';
export const auth = new Hono<HonoAuthContext>()
.use(async (c, next) => {
c.set('requestMetadata', extractRequestMetadata(c.req.raw));
// Todo: Maybe use auth URL.
const validOrigin = new URL(NEXT_PUBLIC_WEBAPP_URL()).origin;
const headerOrigin = c.req.header('Origin');
if (headerOrigin && headerOrigin !== validOrigin) {
return c.json(
{
message: 'Forbidden',
statusCode: 403,
},
403,
);
}
await next();
})
.get('/csrf', async (c) => {
const csrfToken = await setCsrfCookie(c);
return c.json({ csrfToken });
})
.route('/', sessionRoute)
.route('/', signOutRoute)
.route('/email-password', emailPasswordRoute)

View File

@ -6,6 +6,8 @@ import { useSecureCookies } from '@documenso/lib/constants/auth';
import { appLog } from '@documenso/lib/utils/debugger';
import { env } from '@documenso/lib/utils/env';
import { generateSessionToken } from './session';
export const sessionCookieName = 'sessionId';
const getAuthSecret = () => {
@ -30,7 +32,7 @@ const getAuthDomain = () => {
export const sessionCookieOptions = {
httpOnly: true,
path: '/',
sameSite: useSecureCookies ? 'none' : 'lax',
sameSite: useSecureCookies ? 'none' : 'lax', // Todo: This feels wrong?
secure: useSecureCookies,
domain: getAuthDomain(),
// Todo: Max age for specific auth cookies.
@ -89,3 +91,23 @@ export const setSessionCookie = async (c: Context, sessionToken: string) => {
export const deleteSessionCookie = (c: Context) => {
deleteCookie(c, sessionCookieName, sessionCookieOptions);
};
export const getCsrfCookie = async (c: Context) => {
const csrfToken = await getSignedCookie(c, getAuthSecret(), 'csrfToken');
return csrfToken || null;
};
export const setCsrfCookie = async (c: Context) => {
const csrfToken = generateSessionToken();
await setSignedCookie(c, 'csrfToken', csrfToken, getAuthSecret(), {
...sessionCookieOptions,
// Explicity set to undefined for session lived cookie.
expires: undefined,
maxAge: undefined,
});
return csrfToken;
};

View File

@ -25,6 +25,7 @@ import { prisma } from '@documenso/prisma';
import { UserSecurityAuditLogType } from '@documenso/prisma/client';
import { AuthenticationErrorCode } from '../lib/errors/error-codes';
import { getCsrfCookie } from '../lib/session/session-cookies';
import { onAuthorize } from '../lib/utils/authorizer';
import { getRequiredSession, getSession } from '../lib/utils/get-session';
import type { HonoAuthContext } from '../types/context';
@ -45,7 +46,16 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
.post('/authorize', sValidator('json', ZSignInSchema), async (c) => {
const requestMetadata = c.get('requestMetadata');
const { email, password, totpCode, backupCode } = c.req.valid('json');
const { email, password, totpCode, backupCode, csrfToken } = c.req.valid('json');
const csrfCookieToken = await getCsrfCookie(c);
// Todo: Add logging here.
if (csrfToken !== csrfCookieToken || !csrfCookieToken) {
throw new AppError(AuthenticationErrorCode.InvalidRequest, {
message: 'Invalid CSRF token',
});
}
const user = await prisma.user.findFirst({
where: {

View File

@ -10,6 +10,7 @@ export const ZSignInSchema = z.object({
password: ZCurrentPasswordSchema,
totpCode: z.string().trim().optional(),
backupCode: z.string().trim().optional(),
csrfToken: z.string().trim(),
});
export type TSignInSchema = z.infer<typeof ZSignInSchema>;