fix(auth): follow server redirect after passkey sign-in
This commit is contained in:
1 parent
6123a6865e
commit
bef67b0a24
5 files changed
+63
-6
No files matched your search
@@ -114,8 +114,9 @@ test("verification components can resend without application-owned schema files"
|
||||
}
|
||||
});
|
||||
|
||||
test("auth browser runtime refuses cross-origin navigation targets", () => {
|
||||
test("auth browser runtime refuses cross-origin navigation unless explicitly server-approved", () => {
|
||||
const source = readFileSync(join(directory, "..", "assets", "client", "auth.js"), "utf8");
|
||||
expect(source).toContain("if (url.origin !== location.origin) return false");
|
||||
expect(source).toContain("function navigate(target, allowCrossOrigin = false)");
|
||||
expect(source).toContain("if (!allowCrossOrigin) return false");
|
||||
expect(source).toContain("!window.AbortController");
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { expect, test, spyOn } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Window } from "happy-dom";
|
||||
|
||||
test("passkey sign-in follows the server handoff, including cross-app destinations", async () => {
|
||||
for (const redirect of [
|
||||
"https://account.example.test/?sso_code=synthetic-code",
|
||||
"/signed-in",
|
||||
"javascript:alert(1)",
|
||||
]) {
|
||||
const window = new Window({ url: "https://sso.example.test/login" });
|
||||
const assign = spyOn(window.location, "assign").mockImplementation(() => {});
|
||||
try {
|
||||
window.document.body.innerHTML = `<div data-auth-passkey><button>Sign in</button><p data-auth-status></p></div>`;
|
||||
Object.defineProperty(window, "PublicKeyCredential", { value: class {} });
|
||||
Object.defineProperty(window.navigator, "credentials", {
|
||||
value: {
|
||||
get: async () => ({
|
||||
id: "synthetic-credential",
|
||||
type: "public-key",
|
||||
rawId: new Uint8Array([1]),
|
||||
response: {
|
||||
clientDataJSON: new Uint8Array([1]),
|
||||
authenticatorData: new Uint8Array([2]),
|
||||
signature: new Uint8Array([3]),
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
window.fetch = (async (url: string) =>
|
||||
Response.json(
|
||||
url.endsWith("/options")
|
||||
? { ok: true, key: "test-key", options: { challenge: "AQID" } }
|
||||
: { ok: true, redirect },
|
||||
)) as any;
|
||||
window.eval(readFileSync(new URL("../assets/client/auth.js", import.meta.url), "utf8"));
|
||||
await (window as any).WRNexusAuth.authenticatePasskey(window.document.querySelector("div"));
|
||||
if (redirect.startsWith("javascript:")) {
|
||||
expect(assign).not.toHaveBeenCalled();
|
||||
} else {
|
||||
expect(assign).toHaveBeenCalledWith(new URL(redirect, window.location.href).href);
|
||||
}
|
||||
} finally {
|
||||
assign.mockRestore();
|
||||
await window.happyDOM.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user