Skip to content

fix: Deeplinks are redirected correctly#5461

Draft
perkinsjr wants to merge 2 commits intomainfrom
fix/deeplinks-auth
Draft

fix: Deeplinks are redirected correctly#5461
perkinsjr wants to merge 2 commits intomainfrom
fix/deeplinks-auth

Conversation

@perkinsjr
Copy link
Member

@perkinsjr perkinsjr commented Mar 25, 2026

What does this PR do?

When opening a deep link while unauthenticated, sign-in would redirect to /apis instead of the original destination. Now the original path is preserved as a redirect query parameter through the entire auth flow middleware, OAuth, email verification, and org selection.

  • Middleware (proxy.ts) captures the current path in the redirect param
  • Server-side getAuth() sets a custom x-current-path header for fallback
  • SSO callback preserves redirect through org selection and email verify
  • Auto and manual org selection use window.location.href for clean state
  • Workspace slug is rewritten to match the selected org after sign-in
  • Open redirect prevention via allowlist validation on redirect paths

Fixes #5458

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

Scenario 1:

  • Login select a url of choice and copy it, log out
  • Paste it in url bar and log back in
  • Should be redirected back to the correct URL.

Scenario 2:

Scenario 3:

  • If a user selects a workspace and the workspace is a different URL
  • The workspace slug should be swapped so they get either 404 or they will land on a settings page related to that workspace.

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Ran make fmt on /go directory
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

When opening a deep link while unauthenticated, sign-in would redirect
  to /apis instead of the original destination. Now the original path is
  preserved as a `redirect` query parameter through the entire auth flow

 - middleware, OAuth, email verification, and org selection.
 - Middleware (proxy.ts) captures the current path in the redirect param
 - Server-side getAuth() sets a custom x-current-path header for
fallback
 - SSO callback preserves redirect through org selection and email
verify
  - Auto and manual org selection use window.location.href for clean
state
  - Workspace slug is rewritten to match the selected org after sign-in
  - Open redirect prevention via allowlist validation on redirect paths
so we can hijack into a new url or javacript.
@vercel
Copy link

vercel bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard Ready Ready Preview, Comment Mar 25, 2026 6:31pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

This pull request implements a redirect-back-to-original-link feature for the authentication flow. When users access a deep link while unauthenticated, their original destination is captured via query parameters, persisted through the sign-in process, and used to redirect them back after authentication and org selection complete.

Changes

Cohort / File(s) Summary
Redirect validation and utility infrastructure
web/apps/dashboard/app/auth/sign-in/redirect-utils.ts
New utility module exporting four functions: isSafeRedirectPath (validates redirect URLs as single-origin relative paths), saveRedirectUrl (persists URLs to sessionStorage), consumeRedirectUrl (retrieves and removes stored URLs), and resolveRedirectUrl (rewrites path segments when workspace slug changes).
Authentication middleware and guards
web/apps/dashboard/app/(app)/layout.tsx, web/apps/dashboard/lib/auth.ts, web/apps/dashboard/proxy.ts
Updated authentication error handling to capture current pathname/search as a redirect query parameter and append it to sign-in URLs. Added x-current-path header propagation from middleware to server components for downstream redirect logic.
Sign-in page and OAuth components
web/apps/dashboard/app/auth/sign-in/[[...sign-in]]/page.tsx, web/apps/dashboard/app/auth/sign-in/useSignIn.ts, web/apps/dashboard/app/auth/sign-in/oauth-signin.tsx, web/apps/dashboard/app/auth/sign-in/org-selector.tsx
Added redirect parameter extraction from query strings, validation via isSafeRedirectPath, persistence via saveRedirectUrl, and final resolution via resolveRedirectUrl. Updated post-org-selection navigation to use full-page redirects (window.location.href) instead of client-side routing to properly apply redirect resolution logic.
Auth actions and SSO callback
web/apps/dashboard/app/auth/actions.ts, web/apps/dashboard/app/auth/sso-callback/[[...sso-callback]]/route.ts
Added OrgSelectionSuccess type with optional workspaceSlug field. Updated completeOrgSelection to fetch workspace slug from the database and include it in the response. SSO callback now extracts redirectUrlComplete from OAuth state and propagates it via the redirect query parameter if safe.

Sequence Diagram

sequenceDiagram
    actor User
    participant Client as Client Browser
    participant Middleware as Middleware<br/>(proxy.ts)
    participant Layout as Layout/Guards<br/>(layout.tsx, auth.ts)
    participant SignIn as Sign-in Page
    participant Actions as Auth Actions
    participant DB as Database
    participant OAuth as OAuth Provider

    User->>Client: Click deep link<br/>(/workspace/projects/proj_X)
    Client->>Middleware: GET /workspace/projects/proj_X
    
    alt Unauthenticated
        Middleware->>Middleware: No session cookie
        Middleware->>SignIn: Redirect to /auth/sign-in?<br/>redirect=%2Fworkspace%2Fprojects%2Fproj_X
        SignIn->>SignIn: Extract & validate<br/>redirect param
        SignIn->>Client: persist redirect to<br/>sessionStorage
        Client->>User: Show sign-in form
        
        User->>OAuth: Initiate OAuth flow
        OAuth->>OAuth: Authenticate user
        OAuth->>SignIn: Return to callback<br/>with state
        SignIn->>SignIn: Extract redirectUrlComplete<br/>from state
        
        Client->>Actions: completeOrgSelection(orgId)
        Actions->>DB: Query workspace slug<br/>for selected org
        DB-->>Actions: Return slug
        Actions-->>Client: Return with workspaceSlug
        
        Client->>Client: Retrieve redirect from<br/>sessionStorage
        Client->>Client: resolveRedirectUrl(<br/>redirect, workspaceSlug)<br/>Rewrite first path<br/>segment if needed
        Client->>Client: window.location.href =<br/>resolved URL
        Client->>Middleware: GET /workspace/projects/proj_X<br/>(now authenticated)
        
        Middleware->>Middleware: Session exists,<br/>set x-current-path header
        Middleware->>Layout: Forward request
        Layout->>Layout: Verify auth, render content
        Layout-->>Client: Render page
        Client->>User: Display deployment details
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing deeplinks to be redirected correctly after sign-in, which is the core objective of this PR.
Description check ✅ Passed The PR description is comprehensive, covering what the changes do, implementation details, testing scenarios, and completed checklist items. All major template sections are filled out appropriately.
Linked Issues check ✅ Passed The PR successfully addresses issue #5458 by preserving the original deep-link path through the entire auth flow and redirecting users back to their intended destination after sign-in.
Out of Scope Changes check ✅ Passed All changes are directly related to the objective of preserving and restoring deep links through the authentication flow, with no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deeplinks-auth

Comment @coderabbitai help to get the list of available commands and usage tips.

@perkinsjr perkinsjr marked this pull request as draft March 25, 2026 18:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
web/apps/dashboard/app/auth/sign-in/redirect-utils.ts (1)

19-28: Path traversal segments (..) pass validation and can defeat workspace slug rewriting.

The regex allows . characters, so .. segments pass validation. This isn't an open redirect vulnerability (paths stay same-origin), but it can bypass the workspace slug rewriting logic:

  1. Attacker crafts: /attacker-slug/../sensitive-page
  2. resolveRedirectUrl rewrites to: /user-workspace/../sensitive-page
  3. Browser normalizes to: /sensitive-page

Consider explicitly rejecting .. and . segments:

🛡️ Proposed fix to reject traversal segments
   for (let i = 1; i < segments.length; i++) {
     // Allow empty segments only for trailing slash (last segment)
     if (segments[i] === "" && i === segments.length - 1) {
       continue;
     }
+    // Reject path traversal segments
+    if (segments[i] === "." || segments[i] === "..") {
+      return false;
+    }
     // Segments must be non-empty and contain only URL-safe path characters
     if (!segments[i] || !/^[a-zA-Z0-9\-._~!$&'()*+,;=@%]+$/.test(segments[i])) {
       return false;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/apps/dashboard/app/auth/sign-in/redirect-utils.ts` around lines 19 - 28,
The validation loop over URL path segments (the for loop that checks segments[i]
with the regex in redirect-utils.ts) currently allows "." and ".." because the
regex includes dots; update the segment validation to explicitly reject
traversal segments by returning false when a segment equals "." or ".." (in
addition to the existing empty/regex checks) so resolveRedirectUrl cannot be
bypassed by path traversal tokens; locate the loop that iterates over segments
and add the explicit checks for "." and ".." before the regex test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/apps/dashboard/app/auth/sign-in/org-selector.tsx`:
- Around line 89-95: The redirect rewrite is currently applied to all safe paths
causing global routes like /settings or /apis to be overwritten; update the flow
around resolveRedirectUrl (called in org-selector.tsx with consumeRedirectUrl
and result.workspaceSlug) so it only rewrites the first path segment when the
deepLink is a workspace-scoped route (e.g., matches your workspace route pattern
or prefix used across the app), otherwise leave the original deepLink intact and
skip resolveRedirectUrl. In practice, add a guard that detects workspace-scoped
paths before calling resolveRedirectUrl (or make resolveRedirectUrl perform this
check internally) and fall back to result.redirectTo or the original deepLink
when the path is not workspace-scoped.

In `@web/apps/dashboard/app/auth/sso-callback/`[[...sso-callback]]/route.ts:
- Around line 19-29: The code double-decodes the state by calling
decodeURIComponent on the value from request.nextUrl.searchParams.get("state")
and uses unsafe casts to access redirectUrlComplete; remove the
decodeURIComponent call and parse the raw state with JSON.parse, then replace
the inline `as` assertions with a proper type guard (e.g., function
isRedirectState(x: unknown): x is { redirectUrlComplete: string }) or validate
with a schema (Zod) before reading redirectUrlComplete, and apply the same fix
in web/apps/dashboard/lib/auth/workos.ts.

In `@web/apps/dashboard/proxy.ts`:
- Around line 84-89: The middleware currently sets x-current-path on the
response via headers.set and calls NextResponse.next({ headers }), so downstream
server components can't read it; change the NextResponse.next call to forward
the headers as request headers by calling NextResponse.next({ request: { headers
} }) so that code reading request headers (e.g., web/apps/dashboard/lib/auth.ts
using headers() from 'next/headers') can access x-current-path; locate the
headers variable and the NextResponse.next invocation in proxy.ts and update the
options shape accordingly.

---

Nitpick comments:
In `@web/apps/dashboard/app/auth/sign-in/redirect-utils.ts`:
- Around line 19-28: The validation loop over URL path segments (the for loop
that checks segments[i] with the regex in redirect-utils.ts) currently allows
"." and ".." because the regex includes dots; update the segment validation to
explicitly reject traversal segments by returning false when a segment equals
"." or ".." (in addition to the existing empty/regex checks) so
resolveRedirectUrl cannot be bypassed by path traversal tokens; locate the loop
that iterates over segments and add the explicit checks for "." and ".." before
the regex test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 59fd12b7-1e14-490d-86c8-4cb6308f5131

📥 Commits

Reviewing files that changed from the base of the PR and between 27ac65e and cec0ca0.

📒 Files selected for processing (10)
  • web/apps/dashboard/app/(app)/layout.tsx
  • web/apps/dashboard/app/auth/actions.ts
  • web/apps/dashboard/app/auth/hooks/useSignIn.ts
  • web/apps/dashboard/app/auth/sign-in/[[...sign-in]]/page.tsx
  • web/apps/dashboard/app/auth/sign-in/oauth-signin.tsx
  • web/apps/dashboard/app/auth/sign-in/org-selector.tsx
  • web/apps/dashboard/app/auth/sign-in/redirect-utils.ts
  • web/apps/dashboard/app/auth/sso-callback/[[...sso-callback]]/route.ts
  • web/apps/dashboard/lib/auth.ts
  • web/apps/dashboard/proxy.ts

Comment on lines +19 to +29
const state = request.nextUrl.searchParams.get("state");
if (state) {
try {
const parsed: unknown = JSON.parse(decodeURIComponent(state));
const redirectUrlComplete =
typeof parsed === "object" &&
parsed !== null &&
"redirectUrlComplete" in parsed &&
typeof (parsed as { redirectUrlComplete: unknown }).redirectUrlComplete === "string"
? (parsed as { redirectUrlComplete: string }).redirectUrlComplete
: null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does URLSearchParams.get()return percent-decoded values, and would callingdecodeURIComponent()on that result double-decode query parameter content such as%2For%25?

💡 Result:

Yes, URLSearchParams.get returns percent-decoded values. Calling decodeURIComponent on the result would double-decode query parameter content such as %2F (which decodes to /) or %25 (which decodes to %). When URLSearchParams parses a query string like ?key=%2F%25, it percent-decodes the value to "/%" during parsing. The get("key") method then returns this already-decoded string "/%". Applying decodeURIComponent to "/%" would interpret % as a percent-encoded sequence: - %2F in the original query becomes / (already decoded by URLSearchParams). - %25 becomes %. Further decoding "/%" treats the % (not followed by valid hex digits in this context, but if it were %25 originally leading to literal %25 decoded to %, then %2F%25 -> / % no—wait, precise: Example from MDN: new URLSearchParams("%24%25%26=%28%29%2B").get("$%&") returns "+" - %24%25%26 decodes to $%& (key) - %28%29%2B decodes to + (value) If value had %2F, it becomes /. Thus, the returned value has percent sequences already decoded to characters. decodeURIComponent on it would attempt to decode any remaining %XX in those characters, causing double-decoding for originally double-encoded content or errors/invalid decoding otherwise. Do not call decodeURIComponent on URLSearchParams.get results; they are ready-to-use decoded strings per the URL standard.

Citations:


🏁 Script executed:

# Check if the file exists and read the relevant section
fd "route.ts" --path "*sso-callback*" | head -5

Repository: unkeyed/unkey

Length of output: 272


🏁 Script executed:

# Once we locate the file, read lines 15-35 to see context
if [ -f "web/apps/dashboard/app/auth/sso-callback/\[\[...\]\]/route.ts" ]; then
  sed -n '15,35p' "web/apps/dashboard/app/auth/sso-callback/[[...]]/route.ts" || true
fi

# Try a broader search
find . -type f -name "route.ts" -path "*sso-callback*" 2>/dev/null | head -3

Repository: unkeyed/unkey

Length of output: 128


🏁 Script executed:

# Search for files with SSO callback routing
git ls-files | grep -i "sso-callback" | grep -i "route"

Repository: unkeyed/unkey

Length of output: 126


🏁 Script executed:

cat -n "web/apps/dashboard/app/auth/sso-callback/[[...sso-callback]]/route.ts" | sed -n '15,35p'

Repository: unkeyed/unkey

Length of output: 1095


🏁 Script executed:

# Check if there's a tsconfig and what TypeScript settings are used
find . -name "tsconfig.json" -o -name "tsconfig.*.json" | head -5

Repository: unkeyed/unkey

Length of output: 238


🏁 Script executed:

# Look at the full context of the function to understand the parsing intent
cat -n "web/apps/dashboard/app/auth/sso-callback/[[...sso-callback]]/route.ts" | sed -n '1,50p'

Repository: unkeyed/unkey

Length of output: 2496


🏁 Script executed:

# Check if the fault library is imported or used elsewhere
rg "fault" --type ts --type tsx | head -10

Repository: unkeyed/unkey

Length of output: 84


🏁 Script executed:

# Check for type guard patterns or validation utilities in the auth directory
rg "type.*=.*is" web/apps/dashboard/app/auth --type ts --type tsx | head -10

Repository: unkeyed/unkey

Length of output: 84


🏁 Script executed:

# Check if fault library is imported anywhere
rg "import.*fault" --type ts | head -10

Repository: unkeyed/unkey

Length of output: 1714


🏁 Script executed:

# Look for type narrowing patterns used elsewhere in the codebase
rg "typeof.*===.*string" web/apps/dashboard/app/auth | head -10

Repository: unkeyed/unkey

Length of output: 341


🏁 Script executed:

# Check tsconfig to understand TypeScript version and strict settings
cat "./web/apps/dashboard/tsconfig.json"

Repository: unkeyed/unkey

Length of output: 773


🏁 Script executed:

# Check if there's a type guard helper or validation pattern used in auth module
rg "function.*is" web/apps/dashboard/lib/auth --type ts | head -10

Repository: unkeyed/unkey

Length of output: 920


🏁 Script executed:

# Look for schema validation patterns in the codebase (zod, etc.)
rg "import.*z\.|import.*schema" web/apps/dashboard/app/auth --type ts | head -10

Repository: unkeyed/unkey

Length of output: 39


🏁 Script executed:

# Test TypeScript type narrowing behavior with unknown and in operator
cat > /tmp/test-narrowing.ts << 'EOF'
// Test if direct property access works after typeof and in checks
const parsed: unknown = { redirectUrlComplete: "test" };

if (typeof parsed === "object" && parsed !== null && "redirectUrlComplete" in parsed) {
  // Can we access parsed.redirectUrlComplete directly without as?
  const url = parsed.redirectUrlComplete; // This line would error in strict TS
}
EOF
cat /tmp/test-narrowing.ts

Repository: unkeyed/unkey

Length of output: 418


🏁 Script executed:

# Check TypeScript version in package.json
rg '"typescript"' --max-count 5 | head -5

Repository: unkeyed/unkey

Length of output: 337


🏁 Script executed:

# Look for type guard patterns that might be better suited for this
rg "as const satisfies" web/apps/dashboard --type ts | head -5

Repository: unkeyed/unkey

Length of output: 213


🏁 Script executed:

# Check if there's a better pattern for parsing JSON with type safety in the codebase
rg "JSON.parse" web/apps/dashboard/lib/auth --type ts -A 3 | head -20

Repository: unkeyed/unkey

Length of output: 311


🏁 Script executed:

# Check the full context of workos.ts to see how it handles state parsing
cat -n "web/apps/dashboard/lib/auth/workos.ts" | sed -n '1,100p'

Repository: unkeyed/unkey

Length of output: 3459


🏁 Script executed:

# Look for the complete parseState or state handling logic in workos.ts
rg "JSON.parse.*decodeURIComponent.*state" web/apps/dashboard/lib/auth -B 5 -A 5 | head -30

Repository: unkeyed/unkey

Length of output: 773


🏁 Script executed:

# Let me check if there's a validation/parsing utility function in the codebase
rg "type.*guard|const.*is.*=.*\(|function.*is" web/apps/dashboard/lib/auth --type ts -A 2 | head -30

Repository: unkeyed/unkey

Length of output: 1946


🏁 Script executed:

# Check if there's a schema validation approach (zod, etc.) used elsewhere for parsing
rg "z\." web/apps/dashboard/lib/auth --type ts | head -10

Repository: unkeyed/unkey

Length of output: 39


🏁 Script executed:

# Verify if the exact suggested fix from review comment would actually compile
# by checking for similar patterns in codebase that safely narrow unknown
rg "typeof.*in.*typeof" web/apps/dashboard --type ts | head -10

Repository: unkeyed/unkey

Length of output: 770


🏁 Script executed:

# Check if there are any type guard utilities in the codebase
fd "type.*guard|is.*type" web --type ts | head -10

Repository: unkeyed/unkey

Length of output: 226


🏁 Script executed:

# Look for type predicate patterns or helper functions
rg "is.*\(.*\).*:" web/apps/dashboard --type ts | head -10

Repository: unkeyed/unkey

Length of output: 1457


🏁 Script executed:

# Check if the suggested fix would actually work by looking for similar patterns
rg "in.*parsed" web/apps/dashboard --type ts -B 2 -A 2 | head -20

Repository: unkeyed/unkey

Length of output: 1742


Remove double-decode and unsafe type assertions.

request.nextUrl.searchParams.get("state") is already percent-decoded by the URL standard. Calling decodeURIComponent(state) will double-decode, corrupting percent sequences like %2F (slash) or %25 (percent sign) in legitimate deep-link URLs and potentially throwing on invalid sequences, causing valid redirects to fail silently in the catch block.

Additionally, the as casts on lines 27–28 violate the "Never compromise type safety" guideline. While the type narrowing checks establish that parsed is an object with a redirectUrlComplete property, TypeScript's unknown type does not permit direct property access even after in checks. A proper type guard function or validation library (e.g., Zod) is required to narrow safely without type assertions.

Suggested approach

Remove the decodeURIComponent() call:

-          const parsed: unknown = JSON.parse(decodeURIComponent(state));
+          const parsed: unknown = JSON.parse(state);

For type-safe narrowing without as, consider a type predicate:

+          function isStateObject(obj: unknown): obj is { redirectUrlComplete: string } {
+            return (
+              typeof obj === "object" &&
+              obj !== null &&
+              "redirectUrlComplete" in obj &&
+              typeof (obj as any).redirectUrlComplete === "string"
+            );
+          }
           
           const redirectUrlComplete =
-            typeof parsed === "object" &&
-            parsed !== null &&
-            "redirectUrlComplete" in parsed &&
-            typeof (parsed as { redirectUrlComplete: unknown }).redirectUrlComplete === "string"
-              ? (parsed as { redirectUrlComplete: string }).redirectUrlComplete
+            isStateObject(parsed)
+              ? parsed.redirectUrlComplete
               : null;

Or use a schema validation library like Zod for JSON parsing at OAuth boundaries.

Note: web/apps/dashboard/lib/auth/workos.ts has the same double-decode pattern and should be fixed similarly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const state = request.nextUrl.searchParams.get("state");
if (state) {
try {
const parsed: unknown = JSON.parse(decodeURIComponent(state));
const redirectUrlComplete =
typeof parsed === "object" &&
parsed !== null &&
"redirectUrlComplete" in parsed &&
typeof (parsed as { redirectUrlComplete: unknown }).redirectUrlComplete === "string"
? (parsed as { redirectUrlComplete: string }).redirectUrlComplete
: null;
const state = request.nextUrl.searchParams.get("state");
if (state) {
try {
const parsed: unknown = JSON.parse(state);
function isStateObject(obj: unknown): obj is { redirectUrlComplete: string } {
return (
typeof obj === "object" &&
obj !== null &&
"redirectUrlComplete" in obj &&
typeof (obj as any).redirectUrlComplete === "string"
);
}
const redirectUrlComplete =
isStateObject(parsed)
? parsed.redirectUrlComplete
: null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/apps/dashboard/app/auth/sso-callback/`[[...sso-callback]]/route.ts around
lines 19 - 29, The code double-decodes the state by calling decodeURIComponent
on the value from request.nextUrl.searchParams.get("state") and uses unsafe
casts to access redirectUrlComplete; remove the decodeURIComponent call and
parse the raw state with JSON.parse, then replace the inline `as` assertions
with a proper type guard (e.g., function isRedirectState(x: unknown): x is {
redirectUrlComplete: string }) or validate with a schema (Zod) before reading
redirectUrlComplete, and apply the same fix in
web/apps/dashboard/lib/auth/workos.ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Redirect back to original link after sign-in

1 participant