* chore: bump version to 2.21.12 and implement safe redirect path validation

- Updated application version from 2.21.11 to 2.21.12 in package.json.
- Introduced `getSafeRedirectPath` and `isSafeRedirectPath` utility functions to validate user-supplied redirect URLs, enhancing security against open redirect attacks.
* fix: address page reload issue in Admin tests for CI
This commit is contained in:
Giancarlo Buomprisco
2025-12-09 23:34:10 +08:00
committed by GitHub
parent 2f78e16dfa
commit 44137016cb
15 changed files with 128 additions and 31 deletions

View File

@@ -21,6 +21,9 @@
"./auth": "./src/auth.ts",
"./types": "./src/types.ts"
},
"dependencies": {
"@kit/shared": "workspace:*"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",

View File

@@ -6,6 +6,8 @@ import {
SupabaseClient,
} from '@supabase/supabase-js';
import { isSafeRedirectPath } from '@kit/shared/utils';
/**
* @name createAuthCallbackService
* @description Creates an instance of the AuthCallbackService
@@ -71,9 +73,11 @@ class AuthCallbackService {
const errorPath = params.errorPath ?? '/auth/callback/error';
// remove the query params from the url
// remove the auth-related query params from the url
searchParams.delete('token_hash');
searchParams.delete('type');
searchParams.delete('next');
searchParams.delete('callback');
// if we have a next path, we redirect to that path
if (nextPath) {
@@ -132,7 +136,11 @@ class AuthCallbackService {
const nextUrlPathFromParams = searchParams.get('next');
const errorPath = params.errorPath ?? '/auth/callback/error';
const nextUrl = nextUrlPathFromParams ?? params.redirectPath;
// Validate the next URL to prevent open redirect attacks
const nextUrl =
nextUrlPathFromParams && isSafeRedirectPath(nextUrlPathFromParams)
? nextUrlPathFromParams
: params.redirectPath;
if (authCode) {
try {
@@ -188,6 +196,7 @@ class AuthCallbackService {
/**
* Parses a redirect URL and extracts the destination path and query params
* Handles nested 'next' parameters for chained redirects
* Validates paths to prevent open redirect attacks
*/
private parseRedirectDestination(redirectParam: string | null): {
path: string;
@@ -197,29 +206,45 @@ class AuthCallbackService {
return null;
}
// First, try as a simple relative path with optional query string
const [pathPart, queryPart] = redirectParam.split('?') as [
string,
string | undefined,
];
if (isSafeRedirectPath(pathPart)) {
return {
path: pathPart,
params: new URLSearchParams(queryPart ?? ''),
};
}
// Handle full URLs (e.g., from Supabase callback parameter)
try {
const redirectUrl = new URL(redirectParam);
const url = new URL(redirectParam);
// check for nested 'next' parameter (chained redirect)
const nestedNext = redirectUrl.searchParams.get('next');
// Check for nested 'next' parameter - this is the final destination
const nestedNext = url.searchParams.get('next');
if (nestedNext) {
// use the nested path as the final destination
if (nestedNext && isSafeRedirectPath(nestedNext)) {
return {
path: nestedNext,
params: redirectUrl.searchParams,
params: url.searchParams,
};
}
// no nested redirect, use the pathname directly
return {
path: redirectUrl.pathname,
params: redirectUrl.searchParams,
};
// No nested next, use pathname if safe
if (isSafeRedirectPath(url.pathname)) {
return {
path: url.pathname,
params: url.searchParams,
};
}
} catch {
// invalid URL, ignore
return null;
// Invalid URL, ignore
}
return null;
}
private isLocalhost(host: string | null) {

View File

@@ -2,6 +2,8 @@ import type { Provider } from '@supabase/supabase-js';
import { useMutation } from '@tanstack/react-query';
import { getSafeRedirectPath } from '@kit/shared/utils';
import { useSupabase } from './use-supabase';
export function useLinkIdentityWithProvider(
@@ -14,7 +16,12 @@ export function useLinkIdentityWithProvider(
const mutationFn = async (provider: Provider) => {
const origin = window.location.origin;
const redirectToPath = props.redirectToPath ?? '/home/settings';
// Validate the redirect path to prevent open redirect attacks
const redirectToPath = getSafeRedirectPath(
props.redirectToPath,
'/home/settings',
);
const url = new URL('/auth/callback', origin);
url.searchParams.set('redirectTo', redirectToPath);