Skip to content
File

Blob: tests/worker/sign-out.test.ts

typescript60 lines
1import { env, exports } from "cloudflare:workers";
2import { beforeAll, describe, expect, it } from "vitest";
3 
4import { makeAuth } from "@/worker/auth";
5 
6import { ISSUER, signInForCookie, testHeaders } from "./helpers";
7 
8const SELF = exports.default;
9 
10const credential = {
11 email: "signout-tester@example.com",
12 password: "correct-horse-battery-staple",
13 name: "Sign-out Tester",
14};
15 
16describe("sign-out clears the session cookie and invalidates the DB row", () => {
17 beforeAll(async () => {
18 const auth = makeAuth(env, { baseURL: ISSUER, issuer: ISSUER });
19 await auth.api.signUpEmail({ body: credential, asResponse: false });
20 });
21 
22 it("Set-Cookie on /api/auth/sign-out has maxAge=0 and the session is gone afterward", async () => {
23 // 1. Sign in to get a real session cookie.
24 const signedInCookie = await signInForCookie(credential.email, credential.password, "10.40.0.1");
25 
26 // 2. The cookie is good — get-session returns the user.
27 const beforeSignOut = await SELF.fetch(`${ISSUER}/api/auth/get-session`, {
28 headers: { cookie: signedInCookie },
29 });
30 expect(beforeSignOut.status).toBe(200);
31 const beforeBody = (await beforeSignOut.json()) as { user?: { email?: string } } | null;
32 expect(beforeBody?.user?.email).toBe(credential.email);
33 
34 // 3. POST /api/auth/sign-out. The response MUST clear the cookie via
35 // `Max-Age=0` (or `Expires` in the past). If this header is absent
36 // or carries `Secure` on an HTTP origin the browser will silently
37 // ignore it — which is exactly the failure mode I'm hunting.
38 const signOutRes = await SELF.fetch(`${ISSUER}/api/auth/sign-out`, {
39 method: "POST",
40 headers: testHeaders({ cookie: signedInCookie }),
41 });
42 expect(signOutRes.status).toBe(200);
43 const signOutSetCookie = signOutRes.headers.get("set-cookie") ?? "";
44 expect(signOutSetCookie).toMatch(/better-auth\.session_token=/);
45 expect(signOutSetCookie.toLowerCase()).toMatch(/max-age=0|expires=/);
46 
47 // 4. Replay the original cookie value: the server must report no
48 // session even though the cookie string is still in our jar. (Real
49 // browsers honour Max-Age=0 by dropping the cookie entirely; we
50 // re-send it explicitly to assert the *server* side is dead too.)
51 const afterSignOut = await SELF.fetch(`${ISSUER}/api/auth/get-session`, {
52 headers: { cookie: signedInCookie },
53 });
54 expect(afterSignOut.status).toBe(200);
55 const afterText = await afterSignOut.text();
56 // Better Auth returns `null` (literal) when the session is gone.
57 expect(afterText.trim()).toBe("null");
58 });
59});