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