diff --git a/web/app/auth/refresh/__tests__/route.spec.ts b/web/app/auth/refresh/__tests__/route.spec.ts index c86b6261a8..9503b63f7e 100644 --- a/web/app/auth/refresh/__tests__/route.spec.ts +++ b/web/app/auth/refresh/__tests__/route.spec.ts @@ -68,7 +68,7 @@ describe('auth refresh route', () => { const fetchHeaders = fetchMock.mock.calls[0]?.[1]?.headers as Headers expect(fetchHeaders.get('cookie')).toBe('refresh_token=old-refresh') expect(response.status).toBe(303) - expect(response.headers.get('location')).toBe('http://localhost:3000/apps?category=workflow') + expect(response.headers.get('location')).toBe('/apps?category=workflow') expect(getSetCookieHeaders(response.headers)).toEqual([ 'access_token=new-access; Path=/; HttpOnly', 'refresh_token=new-refresh; Path=/; HttpOnly', @@ -85,7 +85,7 @@ describe('auth refresh route', () => { )) expect(response.status).toBe(303) - expect(response.headers.get('location')).toBe('http://localhost:3000/signin?redirect_url=%2Fapps') + expect(response.headers.get('location')).toBe('/signin?redirect_url=%2Fapps') }) it('should ignore cross-origin redirect targets', async () => { @@ -99,6 +99,19 @@ describe('auth refresh route', () => { )) expect(response.status).toBe(303) - expect(response.headers.get('location')).toBe('http://localhost:3000/signin?redirect_url=%2Fapps') + expect(response.headers.get('location')).toBe('/signin?redirect_url=%2Fapps') + }) + + it('should not leak internal request origin when redirecting to signin', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(null, { status: 401 }))) + const { GET } = await import('../route') + + const response = await GET(createRequest( + 'http://internal-service:3000/auth/refresh?redirect_url=%2F', + 'refresh_token=expired', + )) + + expect(response.status).toBe(303) + expect(response.headers.get('location')).toBe('/signin?redirect_url=%2F') }) }) diff --git a/web/app/auth/refresh/route.ts b/web/app/auth/refresh/route.ts index 998f5a5ffe..7dafee480f 100644 --- a/web/app/auth/refresh/route.ts +++ b/web/app/auth/refresh/route.ts @@ -18,11 +18,10 @@ const resolveAbsoluteUrlPrefix = (value: string) => { } } -const resolveServerConsoleApiUrl = (pathname: string, requestUrl: URL) => { +const resolveServerConsoleApiUrl = (pathname: string) => { const requestPath = withoutLeadingSlash(pathname) const apiPrefix = SERVER_CONSOLE_API_PREFIX || resolveAbsoluteUrlPrefix(API_PREFIX) - || new URL(API_PREFIX, requestUrl.origin).toString() if (!apiPrefix) return null @@ -65,10 +64,10 @@ const getSetCookieHeaders = (headers: Headers) => { return setCookie ? [setCookie] : [] } -const createRedirectResponse = (request: Request, pathname: string, setCookies: string[] = []) => { +const createRedirectResponse = (pathname: string, setCookies: string[] = []) => { const headers = new Headers({ 'Cache-Control': 'no-store', - 'Location': new URL(pathname, request.url).toString(), + 'Location': pathname, }) for (const cookie of setCookies) @@ -80,17 +79,16 @@ const createRedirectResponse = (request: Request, pathname: string, setCookies: }) } -const createSigninRedirectResponse = (request: Request, redirectPath: string) => - createRedirectResponse(request, `${basePath}/signin?redirect_url=${encodeURIComponent(redirectPath)}`) +const createSigninRedirectResponse = (redirectPath: string) => + createRedirectResponse(`${basePath}/signin?redirect_url=${encodeURIComponent(redirectPath)}`) export async function GET(request: Request) { - const requestUrl = new URL(request.url) const redirectPath = resolveSafeRedirectPath(request) - const refreshUrl = resolveServerConsoleApiUrl(REFRESH_TOKEN_PATH, requestUrl) + const refreshUrl = resolveServerConsoleApiUrl(REFRESH_TOKEN_PATH) const cookie = request.headers.get('cookie') if (!refreshUrl || !cookie) - return createSigninRedirectResponse(request, redirectPath) + return createSigninRedirectResponse(redirectPath) try { const response = await fetch(refreshUrl, { @@ -103,11 +101,11 @@ export async function GET(request: Request) { }) if (!response.ok) - return createSigninRedirectResponse(request, redirectPath) + return createSigninRedirectResponse(redirectPath) - return createRedirectResponse(request, redirectPath, getSetCookieHeaders(response.headers)) + return createRedirectResponse(redirectPath, getSetCookieHeaders(response.headers)) } catch { - return createSigninRedirectResponse(request, redirectPath) + return createSigninRedirectResponse(redirectPath) } }