141 lines
4.3 KiB
TypeScript
141 lines
4.3 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { mkdtemp, writeFile, rm } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { astroComponent } from "../modules/astro/index.ts";
|
|
import { TemplateContext } from "../core/context.ts";
|
|
import { createViews } from "../views/api.ts";
|
|
import {
|
|
authFromRequest,
|
|
authGuard,
|
|
authIntegration,
|
|
} from "../integrations/auth.ts";
|
|
import { App } from "../core/app.ts";
|
|
import { resolveConfig } from "../core/config.ts";
|
|
import { renderMdx } from "../modules/mdx/index.ts";
|
|
import { compileCss } from "../views/css/index.ts";
|
|
|
|
test("Astro imports compose with typed props and SPUG slots", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "astro-compose-"));
|
|
try {
|
|
await writeFile(
|
|
join(root, "Title.astro"),
|
|
"---\nconst { title } = Astro.props;\n---\n<h2>{title}</h2>",
|
|
);
|
|
await writeFile(
|
|
join(root, "Card.astro"),
|
|
'---\nimport Title from "./Title.astro";\nconst { title } = Astro.props;\n---\n<article><Title title={title} /><slot /></article>',
|
|
);
|
|
await writeFile(
|
|
join(root, "index.spug"),
|
|
'component Card\n title: "Composed"\n render content\n p Slot body',
|
|
);
|
|
const context = new TemplateContext().import(
|
|
"Card",
|
|
astroComponent(join(root, "Card.astro")),
|
|
);
|
|
const views = createViews({ viewsDir: root, context });
|
|
const html = await views.renderFile("index");
|
|
expect(html).toContain("<h2>Composed</h2>");
|
|
expect(html).toContain("<p>Slot body</p>");
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("form authentication issues an opaque session and rejects forged identity cookies", async () => {
|
|
const app = await App.create({
|
|
config: resolveConfig(),
|
|
integrations: [
|
|
authIntegration({
|
|
login: (email, password) =>
|
|
password === "secret" ? { id: email } : undefined,
|
|
}),
|
|
],
|
|
});
|
|
const response = await app.fetch(
|
|
new Request("https://example.test/auth/login", {
|
|
method: "POST",
|
|
headers: { origin: "https://example.test" },
|
|
body: new URLSearchParams({ email: "ada", password: "secret" }),
|
|
}),
|
|
);
|
|
expect(response.status).toBe(302);
|
|
const cookie = response.headers.get("set-cookie")!;
|
|
expect(
|
|
(
|
|
await authFromRequest(
|
|
new Request("https://example.test", { headers: { cookie } }),
|
|
)
|
|
)?.id,
|
|
).toBe("ada");
|
|
expect(
|
|
await authFromRequest(
|
|
new Request("https://example.test", {
|
|
headers: { cookie: "muddle_auth=%7B%22id%22%3A%22admin%22%7D" },
|
|
}),
|
|
),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
test("OAuth callback validates browser-bound state and exchanges a PKCE code", async () => {
|
|
const original = globalThis.fetch;
|
|
let exchanged = false;
|
|
globalThis.fetch = (async (
|
|
input: string | URL | Request,
|
|
init?: RequestInit,
|
|
) => {
|
|
if (String(input).endsWith("/token")) {
|
|
const body = init?.body as URLSearchParams;
|
|
expect(body.get("code_verifier")?.length).toBeGreaterThan(40);
|
|
exchanged = true;
|
|
return Response.json({ access_token: "test-token" });
|
|
}
|
|
return Response.json({ sub: "oauth-user" });
|
|
}) as typeof fetch;
|
|
try {
|
|
const app = await App.create({
|
|
config: resolveConfig(),
|
|
integrations: [
|
|
authIntegration({
|
|
providers: [
|
|
{
|
|
name: "test",
|
|
authorizationUrl: "https://provider.test/authorize",
|
|
tokenUrl: "https://provider.test/token",
|
|
userInfoUrl: "https://provider.test/user",
|
|
clientId: "test",
|
|
redirectUri: "https://app.test/auth/test/callback",
|
|
identity: (profile) => ({ id: String(profile.sub) }),
|
|
},
|
|
],
|
|
}),
|
|
],
|
|
});
|
|
const start = await app.fetch(new Request("https://app.test/auth/test"));
|
|
const authorization = new URL(start.headers.get("location")!);
|
|
expect(authorization.searchParams.get("code_challenge_method")).toBe(
|
|
"S256",
|
|
);
|
|
const callback = `https://app.test/auth/test/callback?code=test-code&state=${authorization.searchParams.get("state")}`;
|
|
expect((await app.fetch(new Request(callback))).status).toBe(400);
|
|
const response = await app.fetch(
|
|
new Request(callback, {
|
|
headers: { cookie: start.headers.get("set-cookie")! },
|
|
}),
|
|
);
|
|
expect(response.status).toBe(302);
|
|
expect(exchanged).toBe(true);
|
|
expect(
|
|
(
|
|
await app.fetch(
|
|
new Request(callback, {
|
|
headers: { cookie: start.headers.get("set-cookie")! },
|
|
}),
|
|
)
|
|
).status,
|
|
).toBe(400);
|
|
} finally {
|
|
globalThis.fetch = original;
|
|
}
|
|
});
|