metatron-open-seo/src/lib/auth-redirect.test.ts

110 lines
3.7 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("rejects backslash redirects that URL parsers treat as slashes", () => {
expect(normalizeAuthRedirect("/\\evil.test")).toBe("/");
expect(normalizeAuthRedirect("/path\\..\\evil")).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}`,
);
});
});