AUTH
tcmts.cli.test.e2e.server.AUTH · #vwzjhiln · term · runs anywhere
export const AUTH = `// tcm: namespace auth
import assert from "node:assert/strict";
import { session, setSession, clearSession, revokeSessions, rateLimit, HttpError, request, withRequest } from "#codebase/lib/server";
const USERS: Record<string, string> = { ada: "lovelace" };
export async function login(name: string, password: string): Promise<string> {
if (USERS[name] !== password) throw new HttpError(401, "wrong name or password");
setSession({ user: name });
return name;
}
export async function me(): Promise<string> {
const user = session().user;
if (typeof user !== "string") throw new HttpError(401, "sign in first");
return user + " via " + (request().headers.get("x-client") ?? "?");
}
export async function logout(): Promise<void> {
clearSession();
}
export async function signOutEverywhere(): Promise<number> {
const user = session().user;
return revokeSessions((s) => s.user === user);
}
export async function ping(): Promise<string> {
rateLimit("ping:" + request().ip, { max: 2, per: 60 });
return "pong";
}
/** Streams to the browser; the request context lasts as long as the stream. */
export async function* ticks(n: number): AsyncGenerator<string> {
const who = session().user ?? "anon";
for (let i = 1; i <= n; i++) {
await new Promise((r) => setTimeout(r, 20));
yield i + ":" + who;
}
}
export async function crash(): Promise<number> {
if (request()) throw new Error("db password is hunter2");
return 0;
}
export async function main() {
const button = (label: string, run: () => Promise<unknown>) => {
const b = document.createElement("button");
b.textContent = label;
b.onclick = async () => (document.title = String(await run()));
document.body.append(b);
};
button("log in", () => login("ada", "lovelace"));
button("who am I", () => me());
button("log out", () => logout());
button("crash", () => crash());
button("sign out everywhere", () => signOutEverywhere());
button("ping", () => ping());
button("ticks", async () => {
const got: string[] = [];
for await (const t of ticks(3)) got.push(t);
return got.join(",");
});
}
/** In a real browser: log in, then iterate a server-side stream with for-await. */
export async function streamApp() {
await login("ada", "lovelace");
const got: string[] = [];
for await (const t of ticks(3)) got.push(t);
document.querySelector("#app")!.textContent = got.join(",");
}
// plain HTTP endpoints, routed by name (served with --http auth.api)
// tcm: path auth.api.health
export const health = (req: Request) => ({ ok: true, method: req.method });
// tcm: path auth.api.hooks.payment
export async function paymentHook(req: Request): Promise<Response> {
const event = (await req.json()) as { type: string };
return new Response(JSON.stringify({ received: event.type }), { status: 202, headers: { "x-handled-by": "tcm" } });
}
// server functions are testable without a server: withRequest simulates the call
export const testLoginThenMe = async () => {
const { setCookies } = await withRequest({}, () => login("ada", "lovelace"));
const sid = setCookies[0]!.split(";")[0]!.slice("tcm_sid=".length);
const who = await withRequest({ cookies: { tcm_sid: decodeURIComponent(sid) }, headers: { "x-client": "test" } }, () => me());
assert.equal(who.value, "ada via test");
};
export const testWrongPassword = async () => {
await assert.rejects(withRequest({}, () => login("ada", "nope")), (e: { status?: number }) => e.status === 401);
};
`
used by