[#330] Form URL not signed in redirect#331
Conversation
📝 WalkthroughWalkthroughPages and the hero sign-in now construct and pass a callbackURL (including encoded form path and query params) when redirecting unauthenticated users to sign-in. A new sanitizeCallbackURL utility validates/sanitizes callback URLs and is integrated into server/client sign-in flows. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser/User
participant Form as App Form Page
participant Hero as Hero Component
participant AuthSvc as Auth Package (signInRoute / signIn)
participant Provider as Auth Provider (Discord)
participant AppURL as App Origin (env)
Browser->>Form: GET /forms/:formName[?query]
alt unauthenticated
Form->>Form: build callbackURL (encode path + serialize searchParams)
Form-->>Browser: 302 -> /sign-in?provider=discord&callbackURL=ENC(...)
Browser->>AuthSvc: GET /sign-in?provider=discord&callbackURL=ENC(...)
AuthSvc->>AppURL: parse env app URL
AuthSvc->>AuthSvc: sanitizeCallbackURL(ENC(...)) — ensure same-origin & valid path
AuthSvc->>Provider: redirect to provider auth flow
Provider-->>AuthSvc: callback on success
AuthSvc-->>Browser: redirect to sanitized callbackURL
Browser->>Form: GET sanitized callbackURL (back to form)
else authenticated
Form-->>Browser: render form
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/blade/src/app/forms/[formName]/page.tsx (1)
14-32: Consider extractingserializeSearchParamsto a shared helper.This helper is duplicated in the responseId form page; extracting to
~/lib/utils(or another shared module) avoids divergence.♻️ Example refactor (this file)
-import { extractProcedures } from "~/lib/utils"; +import { extractProcedures, serializeSearchParams } from "~/lib/utils"; @@ -function serializeSearchParams(searchParams: { - [key: string]: string | string[] | undefined; -}) { - const params = new URLSearchParams(); - - for (const [key, value] of Object.entries(searchParams)) { - if (value === undefined) continue; - if (Array.isArray(value)) { - for (const entry of value) { - params.append(key, entry); - } - continue; - } - params.set(key, value); - } - - const queryString = params.toString(); - return queryString ? `?${queryString}` : ""; -}
| const searchParams = useSearchParams(); | ||
| const requestedCallbackURL = searchParams.get("callbackURL"); | ||
| const callbackURL = | ||
| requestedCallbackURL?.startsWith("/forms/") === true | ||
| ? requestedCallbackURL | ||
| : "/dashboard"; |
There was a problem hiding this comment.
Normalize callbackURL before the /forms/ allowlist check.
startsWith("/forms/") on the raw query value can be bypassed with dot‑segments or encoded separators (e.g., /forms/../dashboard), letting non‑form paths through. Normalize first, then apply the allowlist.
🛠️ Suggested fix
- const requestedCallbackURL = searchParams.get("callbackURL");
- const callbackURL =
- requestedCallbackURL?.startsWith("/forms/") === true
- ? requestedCallbackURL
- : "/dashboard";
+ const requestedCallbackURL = searchParams.get("callbackURL");
+ const callbackURL = (() => {
+ if (!requestedCallbackURL) return "/dashboard";
+ try {
+ const url = new URL(requestedCallbackURL, window.location.origin);
+ const normalized = `${url.pathname}${url.search}`;
+ return normalized.startsWith("/forms/") ? normalized : "/dashboard";
+ } catch {
+ return "/dashboard";
+ }
+ })();Also applies to: 43-46
commit b4e3785 Author: Dylan Vidal <111909137+DVidal1205@users.noreply.github.com> Date: Sat Feb 7 17:50:41 2026 -0500 [#328] Update PR Workflow (#329) Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> commit 8751ca6 Author: Adrian Osorio Blanchard <osorioadrian04@gmail.com> Date: Sat Feb 7 17:07:19 2026 -0500 [#330] Form URL not signed in redirect (#331) commit c0f89b4 Author: DGoel1602 <rhygonfn@gmail.com> Date: Sat Feb 7 12:23:52 2026 -0500 chore: remove everything email-queue commit df04574 Author: DGoel1602 <rhygonfn@gmail.com> Date: Fri Feb 6 23:18:52 2026 -0500 chore: update .env.example commit ad63134 Author: DGoel1602 <rhygonfn@gmail.com> Date: Fri Feb 6 23:15:12 2026 -0500 fix: add @forge/email to blade commit e81a138 Author: DGoel1602 <rhygonfn@gmail.com> Date: Fri Feb 6 22:56:02 2026 -0500 fix: add early return in hacker data commit ce5a375 Author: DGoel1602 <rhygonfn@gmail.com> Date: Fri Feb 6 22:48:20 2026 -0500 fix: format + lint + typecheck commit b3d2be7 Author: DGoel1602 <rhygonfn@gmail.com> Date: Fri Feb 6 22:16:42 2026 -0500 fix: transition all emailing to listmonk commit 3a1e0d3 Author: DGoel1602 <rhygonfn@gmail.com> Date: Fri Feb 6 13:55:21 2026 -0500 feat: add email package
Closes #330
Why
rn when users hit forms, they are prompted to sign in which most of the time they are not. This then does not route them to forms but to the dashboard. This fixes that to reroute them to the form they were originally trying to submit. I had to work on callback urls to ensure tighter validation so we don’t allow sketchy redirects from people so pls test
What
/forms/[formName]/forms/[formName]/[responseId]callbackURLfrom search params and use it when valid (forms paths), otherwise fall backsanitizeCallbackURLin auth package to ensure safe same origin/ path redirectsproviderandcallbackURLin the server redirect urlsignInRouteto only cordauthfrom@forge/auth/serverTest Plan
Video
Screen.Recording.2026-02-07.at.4.10.19.PM.1.1.mp4
Summary by CodeRabbit