105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
getAuthRedirectFromSearch,
|
|
getCurrentAuthRedirectFromHref,
|
|
getOAuthAuthorizeRedirectFromSearch,
|
|
getOAuthSignedQuery,
|
|
getSignInHref,
|
|
getVerifyEmailSearch,
|
|
normalizeAuthRedirect,
|
|
} from "./auth-redirect";
|
|
|
|
const oauthSearch = new URLSearchParams({
|
|
response_type: "code",
|
|
client_id: "claude-client",
|
|
redirect_uri: "https://claude.ai/api/mcp/auth_callback",
|
|
scope: "offline_access mcp",
|
|
state: "state-123",
|
|
code_challenge: "challenge-123",
|
|
code_challenge_method: "S256",
|
|
resource: "https://app.openseo.so/mcp",
|
|
exp: "1778271800",
|
|
sig: "signed-value",
|
|
}).toString();
|
|
|
|
describe("auth redirect helpers", () => {
|
|
it("defaults unsafe or missing redirects to the app root", () => {
|
|
expect(normalizeAuthRedirect(undefined)).toBe("/");
|
|
expect(normalizeAuthRedirect("https://evil.example/app")).toBe("/");
|
|
expect(normalizeAuthRedirect("//evil.example/app")).toBe("/");
|
|
});
|
|
|
|
it("keeps same-origin relative redirects", () => {
|
|
expect(
|
|
normalizeAuthRedirect("/api/auth/oauth2/authorize?client_id=abc"),
|
|
).toBe("/api/auth/oauth2/authorize?client_id=abc");
|
|
});
|
|
|
|
it("rejects external and protocol-relative redirects", () => {
|
|
expect(normalizeAuthRedirect("https://evil.test")).toBe("/");
|
|
expect(normalizeAuthRedirect("//evil.test")).toBe("/");
|
|
});
|
|
|
|
it("builds sign-in links with the redirect query only when needed", () => {
|
|
expect(getSignInHref("/")).toBe("/sign-in");
|
|
expect(getSignInHref("/oauth-consent?client_id=abc")).toBe(
|
|
"/sign-in?redirect=%2Foauth-consent%3Fclient_id%3Dabc",
|
|
);
|
|
});
|
|
|
|
it("builds verify-email search params without a root redirect", () => {
|
|
expect(getVerifyEmailSearch("ben@example.com", "/")).toEqual({
|
|
email: "ben@example.com",
|
|
});
|
|
expect(
|
|
getVerifyEmailSearch("ben@example.com", "/onboarding?step=0"),
|
|
).toEqual({
|
|
email: "ben@example.com",
|
|
redirect: "/onboarding?step=0",
|
|
});
|
|
});
|
|
|
|
it("extracts the current path, query, and hash from hrefs", () => {
|
|
expect(
|
|
getCurrentAuthRedirectFromHref(
|
|
"https://open-seo.test/projects?tab=keywords#top",
|
|
),
|
|
).toBe("/projects?tab=keywords#top");
|
|
});
|
|
|
|
it("preserves safe internal redirects", () => {
|
|
expect(getAuthRedirectFromSearch("", "/app")).toBe("/app");
|
|
});
|
|
|
|
it("extracts Better Auth signed OAuth query parameters through sig", () => {
|
|
const signedQuery = getOAuthSignedQuery(
|
|
`${oauthSearch}&ignored_after_sig=true`,
|
|
);
|
|
|
|
expect(signedQuery).toBe(oauthSearch);
|
|
});
|
|
|
|
it("builds an internal OAuth authorize continuation redirect", () => {
|
|
const redirect = getOAuthAuthorizeRedirectFromSearch(oauthSearch);
|
|
|
|
expect(redirect).toBe(`/api/auth/oauth2/authorize?${oauthSearch}`);
|
|
expect(redirect).toContain("client_id=claude-client");
|
|
expect(redirect).toContain(
|
|
"redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback",
|
|
);
|
|
expect(redirect).toContain("scope=offline_access+mcp");
|
|
expect(redirect).toContain("state=state-123");
|
|
expect(redirect).toContain("code_challenge=challenge-123");
|
|
expect(redirect).toContain("code_challenge_method=S256");
|
|
expect(redirect).toContain("resource=https%3A%2F%2Fapp.openseo.so%2Fmcp");
|
|
expect(redirect).toContain("exp=1778271800");
|
|
expect(redirect).toContain("sig=signed-value");
|
|
});
|
|
|
|
it("prefers OAuth continuation over a generic redirect", () => {
|
|
expect(getAuthRedirectFromSearch(oauthSearch, "/app")).toBe(
|
|
`/api/auth/oauth2/authorize?${oauthSearch}`,
|
|
);
|
|
});
|
|
});
|