mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-26 09:12:16 +00:00
Merge commit from fork
This commit is contained in:
@@ -16,8 +16,14 @@ export function handleServerActionResponse(
|
||||
}
|
||||
|
||||
if ("redirect" in response && response.redirect) {
|
||||
router.push(response.redirect);
|
||||
return true;
|
||||
if (isSafeRedirectUri(response.redirect)) {
|
||||
router.push(response.redirect);
|
||||
return true;
|
||||
} else {
|
||||
console.warn("handleServerActionResponse: Blocked unsafe redirect URI:", response.redirect);
|
||||
setError("Unsafe redirect URI was blocked");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ("samlData" in response && response.samlData) {
|
||||
|
||||
@@ -188,4 +188,52 @@ describe("loginWithOIDCAndSession", () => {
|
||||
|
||||
expect(result).toEqual({ error: "Unknown error occurred" });
|
||||
});
|
||||
|
||||
it("should reject javascript: defaultRedirectUri in FailedPrecondition path (XSS prevention)", async () => {
|
||||
const { ConnectError } = await import("@zitadel/client");
|
||||
vi.mocked(sessionModule.isSessionValid).mockResolvedValue(true);
|
||||
vi.mocked(zitadelModule.createCallback).mockRejectedValue(new ConnectError("already handled", 9));
|
||||
vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({
|
||||
defaultRedirectUri: "javascript:alert(document.cookie)",
|
||||
} as any);
|
||||
|
||||
const result = await loginWithOIDCAndSession({
|
||||
serviceUrl: mockServiceUrl,
|
||||
authRequest: mockAuthRequest,
|
||||
sessionId: mockSessionId,
|
||||
sessions: mockSessions,
|
||||
sessionCookies: mockCookies,
|
||||
});
|
||||
|
||||
// Should NOT redirect to the javascript: URI — must fall back to /signedin
|
||||
expect(result).toHaveProperty("redirect");
|
||||
if ("redirect" in result) {
|
||||
expect(result.redirect).not.toContain("javascript:");
|
||||
expect(result.redirect).toContain("/signedin");
|
||||
}
|
||||
});
|
||||
|
||||
it("should reject data: defaultRedirectUri in FailedPrecondition path (XSS prevention)", async () => {
|
||||
const { ConnectError } = await import("@zitadel/client");
|
||||
vi.mocked(sessionModule.isSessionValid).mockResolvedValue(true);
|
||||
vi.mocked(zitadelModule.createCallback).mockRejectedValue(new ConnectError("already handled", 9));
|
||||
vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({
|
||||
defaultRedirectUri: "data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==",
|
||||
} as any);
|
||||
|
||||
const result = await loginWithOIDCAndSession({
|
||||
serviceUrl: mockServiceUrl,
|
||||
authRequest: mockAuthRequest,
|
||||
sessionId: mockSessionId,
|
||||
sessions: mockSessions,
|
||||
sessionCookies: mockCookies,
|
||||
});
|
||||
|
||||
// Should NOT redirect to the data: URI — must fall back to /signedin
|
||||
expect(result).toHaveProperty("redirect");
|
||||
if ("redirect" in result) {
|
||||
expect(result.redirect).not.toContain("data:");
|
||||
expect(result.redirect).toContain("/signedin");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isSafeRedirectUri } from "@/lib/client-utils";
|
||||
import { Cookie } from "@/lib/cookies";
|
||||
import { isClassifiedError } from "@/lib/grpc/interceptors/error-classification";
|
||||
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
||||
@@ -77,8 +78,10 @@ export async function loginWithOIDCAndSession({
|
||||
organization: selectedSession.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
if (loginSettings?.defaultRedirectUri) {
|
||||
if (loginSettings?.defaultRedirectUri && isSafeRedirectUri(loginSettings.defaultRedirectUri)) {
|
||||
return { redirect: loginSettings.defaultRedirectUri };
|
||||
} else if (loginSettings?.defaultRedirectUri) {
|
||||
console.warn("loginWithOIDCAndSession: Unsafe defaultRedirectUri prevented:", loginSettings.defaultRedirectUri);
|
||||
}
|
||||
|
||||
const signedinUrl = "/signedin";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isSafeRedirectUri } from "@/lib/client-utils";
|
||||
import { Cookie } from "@/lib/cookies";
|
||||
import { isClassifiedError } from "@/lib/grpc/interceptors/error-classification";
|
||||
import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname";
|
||||
@@ -96,8 +97,10 @@ export async function loginWithSAMLAndSession({
|
||||
organization: selectedSession.factors?.user?.organizationId,
|
||||
});
|
||||
|
||||
if (loginSettings?.defaultRedirectUri) {
|
||||
if (loginSettings?.defaultRedirectUri && isSafeRedirectUri(loginSettings.defaultRedirectUri)) {
|
||||
return { redirect: loginSettings.defaultRedirectUri };
|
||||
} else if (loginSettings?.defaultRedirectUri) {
|
||||
console.warn("loginWithSAMLAndSession: Unsafe defaultRedirectUri prevented:", loginSettings.defaultRedirectUri);
|
||||
}
|
||||
|
||||
const signedinUrl = "/signedin";
|
||||
|
||||
Reference in New Issue
Block a user