From a66f5356888f2398d4e781a5270b2f0f54270dc4 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:53:36 +0000 Subject: [PATCH 1/2] chore: store return URLs in session Stores the return URL in the session rather than using cookies. --- package.json | 1 + pnpm-lock.yaml | 3 +++ server/api/auth/atproto.get.ts | 13 ++++--------- server/api/registry/badge/[type]/[...pkg].get.ts | 11 +++++++---- shared/types/userSession.ts | 2 ++ 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 232c817540..9ddbc56142 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "nuxt": "4.3.0", "nuxt-og-image": "5.1.13", "ofetch": "1.5.1", + "ohash": "2.0.11", "perfect-debounce": "2.1.0", "sanitize-html": "2.17.0", "semver": "7.7.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a7aaf5f84..3725685657 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,6 +146,9 @@ importers: ofetch: specifier: 1.5.1 version: 1.5.1 + ohash: + specifier: 2.0.11 + version: 2.0.11 perfect-debounce: specifier: 2.1.0 version: 2.1.0 diff --git a/server/api/auth/atproto.get.ts b/server/api/auth/atproto.get.ts index cfd76b4ec0..109b35f011 100644 --- a/server/api/auth/atproto.get.ts +++ b/server/api/auth/atproto.get.ts @@ -69,7 +69,7 @@ export default defineEventHandler(async event => { if (!query.code) { // Validate returnTo is a safe relative path (prevent open redirect) - // Only set cookie on initial auth request, not the callback + // Store in session on initial auth request, not the callback let redirectPath = '/' try { const clientOrigin = new URL(clientUri).origin @@ -81,12 +81,7 @@ export default defineEventHandler(async event => { // Invalid URL, fall back to root } - setCookie(event, 'auth_return_to', redirectPath, { - maxAge: 60 * 5, - httpOnly: true, - // secure only if NOT in dev mode - secure: !import.meta.dev, - }) + await session.update({ returnTo: redirectPath }) try { const handle = query.handle?.toString() const create = query.create?.toString() @@ -148,8 +143,8 @@ export default defineEventHandler(async event => { }) } - const returnToURL = getCookie(event, 'auth_return_to') || '/' - deleteCookie(event, 'auth_return_to') + const returnToURL = session.data.returnTo || '/' + await session.update({ returnTo: undefined }) return sendRedirect(event, returnToURL) }) diff --git a/server/api/registry/badge/[type]/[...pkg].get.ts b/server/api/registry/badge/[type]/[...pkg].get.ts index 7e5f72cdf9..6092913fed 100644 --- a/server/api/registry/badge/[type]/[...pkg].get.ts +++ b/server/api/registry/badge/[type]/[...pkg].get.ts @@ -1,4 +1,5 @@ import * as v from 'valibot' +import { hash } from 'ohash' import { createError, getRouterParam, getQuery, setHeader } from 'h3' import { PackageRouteParamsSchema } from '#shared/schemas/package' import { CACHE_MAX_AGE_ONE_HOUR, ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants' @@ -11,11 +12,13 @@ const OSV_QUERY_API = 'https://api.osv.dev/v1/query' const BUNDLEPHOBIA_API = 'https://bundlephobia.com/api/size' const NPMS_API = 'https://api.npms.io/v2/package' +const SafeStringSchema = v.pipe(v.string(), v.regex(/^[^<>"&]*$/, 'Invalid characters')) + const QUERY_SCHEMA = v.object({ - color: v.optional(v.string()), + color: v.optional(SafeStringSchema), name: v.optional(v.string()), - labelColor: v.optional(v.string()), - label: v.optional(v.string()), + labelColor: v.optional(SafeStringSchema), + label: v.optional(SafeStringSchema), }) const COLORS = { @@ -338,7 +341,7 @@ export default defineCachedEventHandler( const type = getRouterParam(event, 'type') ?? 'version' const pkg = getRouterParam(event, 'pkg') ?? '' const query = getQuery(event) - return `badge:${type}:${pkg}:${JSON.stringify(query)}` + return `badge:${type}:${pkg}:${hash(query)}` }, }, ) diff --git a/shared/types/userSession.ts b/shared/types/userSession.ts index dea7decd33..9a7b6dc1b2 100644 --- a/shared/types/userSession.ts +++ b/shared/types/userSession.ts @@ -12,4 +12,6 @@ export interface UserServerSession { // multiple did logins per server session oauthSession: NodeSavedSession | undefined oauthState: NodeSavedState | undefined + // Temporary storage for post-auth redirect path during OAuth flow + returnTo?: string } From 3cb43b582922e9ddf97dc75ab957addda94e625b Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:14:04 +0000 Subject: [PATCH 2/2] chore: revert this bit --- server/api/auth/atproto.get.ts | 13 +++++++++---- shared/types/userSession.ts | 2 -- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/server/api/auth/atproto.get.ts b/server/api/auth/atproto.get.ts index 109b35f011..cfd76b4ec0 100644 --- a/server/api/auth/atproto.get.ts +++ b/server/api/auth/atproto.get.ts @@ -69,7 +69,7 @@ export default defineEventHandler(async event => { if (!query.code) { // Validate returnTo is a safe relative path (prevent open redirect) - // Store in session on initial auth request, not the callback + // Only set cookie on initial auth request, not the callback let redirectPath = '/' try { const clientOrigin = new URL(clientUri).origin @@ -81,7 +81,12 @@ export default defineEventHandler(async event => { // Invalid URL, fall back to root } - await session.update({ returnTo: redirectPath }) + setCookie(event, 'auth_return_to', redirectPath, { + maxAge: 60 * 5, + httpOnly: true, + // secure only if NOT in dev mode + secure: !import.meta.dev, + }) try { const handle = query.handle?.toString() const create = query.create?.toString() @@ -143,8 +148,8 @@ export default defineEventHandler(async event => { }) } - const returnToURL = session.data.returnTo || '/' - await session.update({ returnTo: undefined }) + const returnToURL = getCookie(event, 'auth_return_to') || '/' + deleteCookie(event, 'auth_return_to') return sendRedirect(event, returnToURL) }) diff --git a/shared/types/userSession.ts b/shared/types/userSession.ts index 9a7b6dc1b2..dea7decd33 100644 --- a/shared/types/userSession.ts +++ b/shared/types/userSession.ts @@ -12,6 +12,4 @@ export interface UserServerSession { // multiple did logins per server session oauthSession: NodeSavedSession | undefined oauthState: NodeSavedState | undefined - // Temporary storage for post-auth redirect path during OAuth flow - returnTo?: string }