fix: rework passwd protected file logic

This commit is contained in:
diced
2026-04-25 19:07:39 -07:00
parent eeb1c51fb2
commit 33104ce1be
5 changed files with 35 additions and 21 deletions

View File

@@ -1,13 +1,13 @@
import * as cookie from 'cookie';
import { FastifyRequest } from 'fastify';
import { config as zConfig } from '@/lib/config'; import { config as zConfig } from '@/lib/config';
import { Config } from '@/lib/config/validate'; import { Config } from '@/lib/config/validate';
import { verifyPassword } from '@/lib/crypto'; import { verifyPassword } from '@/lib/crypto';
import { prisma } from '@/lib/db'; import { prisma } from '@/lib/db';
import { getPasswordCookie } from '@/lib/passwordCookie';
import { renderHtml } from '@/lib/ssr/renderHtml'; import { renderHtml } from '@/lib/ssr/renderHtml';
import { ZiplineTheme } from '@/lib/theme'; import { ZiplineTheme } from '@/lib/theme';
import { createRoutes } from './routes'; // This should include the `/url/:id` route import * as cookie from 'cookie';
import { FastifyRequest } from 'fastify';
import { createRoutes } from './routes';
export async function render( export async function render(
{ {
@@ -53,7 +53,7 @@ export async function render(
} }
const cookies = cookie.parse(req.headers.cookie || ''); const cookies = cookie.parse(req.headers.cookie || '');
const pw = cookies[`url_pw_${urlEntry.id}`]; const pw = getPasswordCookie(cookies, 'url', urlEntry.id);
const hasPassword = !!urlEntry.password; const hasPassword = !!urlEntry.password;
const data = { const data = {

View File

@@ -14,7 +14,9 @@ import { File, fileSelect } from '@/lib/db/models/file';
import { User, userSelect } from '@/lib/db/models/user'; import { User, userSelect } from '@/lib/db/models/user';
import { parseString } from '@/lib/parser'; import { parseString } from '@/lib/parser';
import { parserMetrics } from '@/lib/parser/metrics'; import { parserMetrics } from '@/lib/parser/metrics';
import { getPasswordCookie } from '@/lib/passwordCookie';
import { createZiplineSsr } from '@/lib/ssr/createZiplineSsr'; import { createZiplineSsr } from '@/lib/ssr/createZiplineSsr';
import { stripHtml } from '@/lib/stripHtml';
import type { ZiplineTheme } from '@/lib/theme'; import type { ZiplineTheme } from '@/lib/theme';
import { readThemes } from '@/lib/theme/file'; import { readThemes } from '@/lib/theme/file';
import * as cookie from 'cookie'; import * as cookie from 'cookie';
@@ -22,7 +24,6 @@ import { FastifyRequest } from 'fastify';
import { renderToString } from 'react-dom/server'; import { renderToString } from 'react-dom/server';
import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router-dom'; import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router-dom';
import { createRoutes } from './routes'; import { createRoutes } from './routes';
import { stripHtml } from '@/lib/stripHtml';
export const getFile = async (id: string) => export const getFile = async (id: string) =>
prisma.file.findFirst({ prisma.file.findFirst({
@@ -95,7 +96,7 @@ export async function render(
const config = { website: { theme: zConfig.website.theme } }; const config = { website: { theme: zConfig.website.theme } };
const cookies = cookie.parse(req.headers.cookie || ''); const cookies = cookie.parse(req.headers.cookie || '');
const pw = cookies[`file_pw_${file.id}`]; const pw = getPasswordCookie(cookies, 'file', file.id);
const hasPassword = !!file.password; const hasPassword = !!file.password;
if (hasPassword) { if (hasPassword) {

23
src/lib/passwordCookie.ts Normal file
View File

@@ -0,0 +1,23 @@
import { config } from '@/lib/config';
import { decrypt, encrypt } from '@/lib/crypto';
import { FastifyReply } from 'fastify';
export function setPasswordCookie(res: FastifyReply, kind: 'file' | 'url', id: string, password: string) {
res.cookie(`${kind}_pw_${id}`, encrypt(password, config.core.secret), {
sameSite: 'lax',
expires: new Date(Date.now() + 15 * 60_000),
httpOnly: true,
secure: config.core.returnHttpsUrls,
path: '/',
});
}
export function getPasswordCookie(cookies: Record<string, string | undefined>, kind: 'file' | 'url', id: string) {
const cookie = cookies[`${kind}_pw_${id}`];
if (!cookie) return null;
try {
return decrypt(cookie, config.core.secret);
} catch {
return null;
}
}

View File

@@ -4,6 +4,7 @@ import { prisma } from '@/lib/db';
import { log } from '@/lib/logger'; import { log } from '@/lib/logger';
import { secondlyRatelimit } from '@/lib/ratelimits'; import { secondlyRatelimit } from '@/lib/ratelimits';
import { zStringTrimmed } from '@/lib/validation'; import { zStringTrimmed } from '@/lib/validation';
import { setPasswordCookie } from '@/lib/passwordCookie';
import typedPlugin from '@/server/typedPlugin'; import typedPlugin from '@/server/typedPlugin';
import z from 'zod'; import z from 'zod';
@@ -61,13 +62,7 @@ export default typedPlugin(
} }
logger.info(`${file.name} was accessed with the correct password`, { ua: req.headers['user-agent'] }); logger.info(`${file.name} was accessed with the correct password`, { ua: req.headers['user-agent'] });
res.cookie('file_pw_' + file.id, req.body.password, { setPasswordCookie(res, 'file', file.id, req.body.password);
sameSite: 'lax',
maxAge: 60,
httpOnly: false,
secure: false,
path: '/',
});
return res.send({ success: true }); return res.send({ success: true });
}, },

View File

@@ -4,6 +4,7 @@ import { prisma } from '@/lib/db';
import { log } from '@/lib/logger'; import { log } from '@/lib/logger';
import { secondlyRatelimit } from '@/lib/ratelimits'; import { secondlyRatelimit } from '@/lib/ratelimits';
import { zStringTrimmed } from '@/lib/validation'; import { zStringTrimmed } from '@/lib/validation';
import { setPasswordCookie } from '@/lib/passwordCookie';
import typedPlugin from '@/server/typedPlugin'; import typedPlugin from '@/server/typedPlugin';
import z from 'zod'; import z from 'zod';
@@ -58,13 +59,7 @@ export default typedPlugin(
ua: req.headers['user-agent'], ua: req.headers['user-agent'],
}); });
res.cookie('url_pw_' + url.id, req.body.password, { setPasswordCookie(res, 'url', url.id, req.body.password);
sameSite: 'lax',
maxAge: 60,
httpOnly: false,
secure: false,
path: '/',
});
return res.send({ success: true }); return res.send({ success: true });
}, },