mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
fix: add auth session lifetime
This commit is contained in:
@ -1,11 +1,10 @@
|
||||
// Todo: (RR7) Test, used AI to migrate this component from NextJS to Remix.
|
||||
import satori from 'satori';
|
||||
import sharp from 'sharp';
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { getRecipientOrSenderByShareLinkSlug } from '@documenso/lib/server-only/document/get-recipient-or-sender-by-share-link-slug';
|
||||
|
||||
import type { ShareHandlerAPIResponse } from '../api+/share';
|
||||
import type { Route } from './+types/share.$slug.opengraph';
|
||||
|
||||
export const runtime = 'edge';
|
||||
@ -37,9 +36,9 @@ export const loader = async ({ params }: Route.LoaderArgs) => {
|
||||
),
|
||||
]);
|
||||
|
||||
const recipientOrSender: ShareHandlerAPIResponse = await fetch(
|
||||
new URL(`/api/share?slug=${slug}`, baseUrl),
|
||||
).then(async (res) => res.json());
|
||||
const recipientOrSender = await getRecipientOrSenderByShareLinkSlug({
|
||||
slug,
|
||||
});
|
||||
|
||||
if ('error' in recipientOrSender) {
|
||||
return Response.json({ error: 'Not found' }, { status: 404 });
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { getRecipientOrSenderByShareLinkSlug } from '@documenso/lib/server-only/share/get-recipient-or-sender-by-share-link-slug';
|
||||
|
||||
import type { Route } from './+types/share';
|
||||
|
||||
export type ShareHandlerAPIResponse =
|
||||
| Awaited<ReturnType<typeof getRecipientOrSenderByShareLinkSlug>>
|
||||
| { error: string };
|
||||
|
||||
// Todo: (RR7) Test
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const slug = url.searchParams.get('slug');
|
||||
|
||||
if (typeof slug !== 'string') {
|
||||
throw new Error('Invalid slug');
|
||||
}
|
||||
|
||||
const data = await getRecipientOrSenderByShareLinkSlug({
|
||||
slug,
|
||||
});
|
||||
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
return Response.json({ error: 'Not found' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,5 @@
|
||||
import { stripeWebhookHandler } from '@documenso/ee/server-only/stripe/webhook/handler';
|
||||
|
||||
// Todo: (RR7)
|
||||
// export const config = {
|
||||
// api: { bodyParser: false },
|
||||
// };
|
||||
import type { Route } from './+types/webhook.trigger';
|
||||
|
||||
export async function action({ request }: Route.ActionArgs) {
|
||||
|
||||
@ -2,16 +2,6 @@ import { handlerTriggerWebhooks } from '@documenso/lib/server-only/webhooks/trig
|
||||
|
||||
import type { Route } from './+types/webhook.trigger';
|
||||
|
||||
// Todo: (RR7)
|
||||
// export const config = {
|
||||
// maxDuration: 300,
|
||||
// api: {
|
||||
// bodyParser: {
|
||||
// sizeLimit: '50mb',
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
|
||||
export async function action({ request }: Route.ActionArgs) {
|
||||
return handlerTriggerWebhooks(request);
|
||||
}
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
import type { Context, Next } from 'hono';
|
||||
import { getCookie } from 'hono/cookie';
|
||||
|
||||
import { AppDebugger } from '@documenso/lib/utils/debugger';
|
||||
|
||||
const debug = new AppDebugger('Middleware');
|
||||
|
||||
/**
|
||||
* Middleware for initial page loads.
|
||||
*
|
||||
* You won't be able to easily handle sequential page loads because they will be
|
||||
* called under `path.data`
|
||||
*
|
||||
* Example an initial page load would be `/documents` then if the user click templates
|
||||
* the path here would be `/templates.data`.
|
||||
*/
|
||||
export const appMiddleware = async (c: Context, next: Next) => {
|
||||
const { req } = c;
|
||||
const { path } = req;
|
||||
|
||||
// Basic paths to ignore.
|
||||
if (path.startsWith('/api') || path.endsWith('.data') || path.startsWith('/__manifest')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
debug.log('Path', path);
|
||||
|
||||
const preferredTeamUrl = getCookie(c, 'preferred-team-url');
|
||||
|
||||
const referrer = c.req.header('referer');
|
||||
const referrerUrl = referrer ? new URL(referrer) : null;
|
||||
const referrerPathname = referrerUrl ? referrerUrl.pathname : null;
|
||||
|
||||
// // Whether to reset the preferred team url cookie if the user accesses a non team page from a team page.
|
||||
// const resetPreferredTeamUrl =
|
||||
// referrerPathname &&
|
||||
// referrerPathname.startsWith('/t/') &&
|
||||
// (!path.startsWith('/t/') || path === '/');
|
||||
|
||||
// // Redirect root page to `/documents` or `/t/{preferredTeamUrl}/documents`.
|
||||
// if (path === '/') {
|
||||
// debug.log('Redirecting from root to documents');
|
||||
|
||||
// const redirectUrlPath = formatDocumentsPath(
|
||||
// resetPreferredTeamUrl ? undefined : preferredTeamUrl,
|
||||
// );
|
||||
|
||||
// const redirectUrl = new URL(redirectUrlPath, req.url);
|
||||
|
||||
// return c.redirect(redirectUrl);
|
||||
// }
|
||||
|
||||
// // Set the preferred team url cookie if user accesses a team page.
|
||||
// if (path.startsWith('/t/')) {
|
||||
// setCookie(c, 'preferred-team-url', path.split('/')[2]);
|
||||
// return next();
|
||||
// }
|
||||
|
||||
// // Clear preferred team url cookie if user accesses a non team page from a team page.
|
||||
// if (resetPreferredTeamUrl || path === '/documents') {
|
||||
// debug.log('Resetting preferred team url');
|
||||
|
||||
// deleteCookie(c, 'preferred-team-url');
|
||||
// return next();
|
||||
// }
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
import { Hono } from 'hono';
|
||||
import { bodyLimit } from 'hono/body-limit';
|
||||
import { contextStorage } from 'hono/context-storage';
|
||||
import { timeout } from 'hono/timeout';
|
||||
|
||||
import { tsRestHonoApp } from '@documenso/api/hono';
|
||||
import { auth } from '@documenso/auth/server';
|
||||
@ -20,30 +22,31 @@ export interface HonoEnv {
|
||||
|
||||
const app = new Hono<HonoEnv>();
|
||||
|
||||
/**
|
||||
* Global middleware limits.
|
||||
*/
|
||||
app.use(timeout(120000)); // Two minute timeout.
|
||||
app.use(bodyLimit({ maxSize: 50 * 1024 * 1024 })); // 50mb size limit.
|
||||
|
||||
/**
|
||||
* Attach session and context to requests.
|
||||
*/
|
||||
app.use(contextStorage());
|
||||
app.use(appContext);
|
||||
|
||||
/**
|
||||
* Middleware for initial page loads.
|
||||
*/
|
||||
// app.use('*', appMiddleware);
|
||||
|
||||
// Auth server.
|
||||
app.route('/api/auth', auth);
|
||||
|
||||
// Files route.
|
||||
app.route('/api/files', filesRoute);
|
||||
|
||||
// API servers. Todo: (RR7) Configure max durations, etc?
|
||||
// API servers.
|
||||
app.route('/api/v1', tsRestHonoApp);
|
||||
app.use('/api/jobs/*', jobsClient.getApiHandler());
|
||||
app.use('/api/trpc/*', reactRouterTrpcServer);
|
||||
|
||||
// Unstable API server routes. Order matters for these two.
|
||||
app.get(`${API_V2_BETA_URL}/openapi.json`, (c) => c.json(openApiDocument));
|
||||
app.use(`${API_V2_BETA_URL}/*`, async (c) => openApiTrpcServerHandler(c)); // Todo: (RR7) Add next()?
|
||||
app.use(`${API_V2_BETA_URL}/*`, async (c) => openApiTrpcServerHandler(c));
|
||||
|
||||
export default app;
|
||||
|
||||
@ -11,8 +11,6 @@ export const openApiTrpcServerHandler = async (c: Context) => {
|
||||
return createOpenApiFetchHandler<typeof appRouter>({
|
||||
endpoint: API_V2_BETA_URL,
|
||||
router: appRouter,
|
||||
// Todo: (RR7) Test this, since it's not using the createContext params.
|
||||
// Todo: (RR7) Reduce calls since we fetch on most request? maybe
|
||||
createContext: async () => createTrpcContext({ c, requestSource: 'apiV2' }),
|
||||
req: c.req.raw,
|
||||
onError: (opts) => handleTrpcRouterError(opts, 'apiV2'),
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
|
||||
/**
|
||||
* How long a session should live for in milliseconds.
|
||||
*/
|
||||
export const AUTH_SESSION_LIFETIME = 1000 * 60 * 60 * 24 * 30; // 30 days.
|
||||
|
||||
export type OAuthClientOptions = {
|
||||
id: string;
|
||||
scope: string[];
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
import { appLog } from '@documenso/lib/utils/debugger';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
|
||||
import { AUTH_SESSION_LIFETIME } from '../../config';
|
||||
import { generateSessionToken } from './session';
|
||||
|
||||
export const sessionCookieName = formatSecureCookieName('sessionId');
|
||||
@ -33,7 +34,7 @@ export const sessionCookieOptions = {
|
||||
sameSite: useSecureCookies ? 'none' : 'lax', // Todo: (RR7) This feels wrong?
|
||||
secure: useSecureCookies,
|
||||
domain: getCookieDomain(),
|
||||
// Todo: (RR7) Max age for specific auth cookies.
|
||||
expires: new Date(Date.now() + AUTH_SESSION_LIFETIME),
|
||||
} as const;
|
||||
|
||||
export const extractSessionCookieFromHeaders = (headers: Headers): string | null => {
|
||||
|
||||
@ -5,6 +5,8 @@ import { type Session, type User, UserSecurityAuditLogType } from '@prisma/clien
|
||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AUTH_SESSION_LIFETIME } from '../../config';
|
||||
|
||||
/**
|
||||
* The user object to pass around the app.
|
||||
*
|
||||
@ -54,7 +56,7 @@ export const createSession = async (
|
||||
userId,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date(),
|
||||
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),
|
||||
expiresAt: new Date(Date.now() + AUTH_SESSION_LIFETIME),
|
||||
ipAddress: metadata.ipAddress ?? null,
|
||||
userAgent: metadata.userAgent ?? null,
|
||||
};
|
||||
|
||||
@ -34,7 +34,9 @@ export const getRecipientOrSenderByShareLinkSlug = async ({
|
||||
documentId,
|
||||
email,
|
||||
},
|
||||
include: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
signatures: true,
|
||||
},
|
||||
});
|
||||
@ -1,7 +1,6 @@
|
||||
import type { ErrorHandlerOptions } from '@trpc/server/unstable-core-do-not-import';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
import { buildLogger } from '@documenso/lib/utils/logger';
|
||||
|
||||
const logger = buildLogger();
|
||||
@ -11,11 +10,6 @@ export const handleTrpcRouterError = (
|
||||
{ error, path }: Pick<ErrorHandlerOptions<undefined>, 'error' | 'path'>,
|
||||
source: 'trpc' | 'apiV1' | 'apiV2',
|
||||
) => {
|
||||
// Always log the error on production for now.
|
||||
if (env('NODE_ENV') !== 'development') {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
const appError = AppError.parseError(error.cause || error);
|
||||
|
||||
const isAppError = error.cause instanceof AppError;
|
||||
@ -30,6 +24,8 @@ export const handleTrpcRouterError = (
|
||||
const isLoggableTrpcError = !isAppError && errorCodesToAlertOn.includes(error.code);
|
||||
|
||||
if (isLoggableAppError || isLoggableTrpcError) {
|
||||
console.error(error);
|
||||
|
||||
logger.error(error, {
|
||||
method: path,
|
||||
context: {
|
||||
|
||||
Reference in New Issue
Block a user