auth.test.js
3,712 bytes
| 1 | import request from "supertest"; |
|---|---|
| 2 | import { afterEach, describe, expect, it } from "vitest"; |
| 3 | import { makeHarness } from "./helpers.js"; |
| 4 | |
| 5 | describe("auth", () => { |
| 6 | let harness; |
| 7 | afterEach(() => harness.close()); |
| 8 | |
| 9 | it("does not create owner sessions for anonymous home page visits", async () => { |
| 10 | harness = makeHarness(); |
| 11 | const page = await harness.agent.get("/").expect(200); |
| 12 | expect(page.headers["set-cookie"]).toBeUndefined(); |
| 13 | expect(harness.store.sessionCount()).toBe(0); |
| 14 | }); |
| 15 | |
| 16 | it("does not persist sessions for repeated OAuth starts without completed authentication", async () => { |
| 17 | harness = makeHarness(); |
| 18 | for (let attempt = 0; attempt < 35; attempt += 1) { |
| 19 | await request(harness.app).get("/auth/github").expect(302); |
| 20 | } |
| 21 | expect(harness.store.sessionCount()).toBe(0); |
| 22 | }); |
| 23 | |
| 24 | it("uses a verified GitHub OAuth state and then asks for selected repository access", async () => { |
| 25 | harness = makeHarness(); |
| 26 | const start = await harness.agent.get("/auth/github").expect(302); |
| 27 | const state = new URL(start.headers.location).searchParams.get("state"); |
| 28 | await harness.agent.get("/auth/github/callback?code=ok&state=wrong").expect(400); |
| 29 | await harness.agent.get(`/auth/github/callback?code=ok&state=${state}`).expect(302); |
| 30 | const install = await harness.agent.get("/github/install").expect(302); |
| 31 | const installState = new URL(install.headers.location).searchParams.get("state"); |
| 32 | await harness.agent.get("/github/installed?installation_id=333&state=wrong").expect(400); |
| 33 | await harness.agent.get(`/github/installed?installation_id=333&state=${installState}`).expect(302); |
| 34 | }); |
| 35 | |
| 36 | it("rejects an OAuth state after its ten minute lifetime", async () => { |
| 37 | harness = makeHarness(); |
| 38 | const start = await harness.agent.get("/auth/github").expect(302); |
| 39 | const state = new URL(start.headers.location).searchParams.get("state"); |
| 40 | harness.setNow("2026-07-23T12:11:00.000Z"); |
| 41 | await harness.agent.get(`/auth/github/callback?code=ok&state=${state}`).expect(400); |
| 42 | }); |
| 43 | |
| 44 | it("refreshes an expiring GitHub App user token before accessing repositories", async () => { |
| 45 | harness = makeHarness(); |
| 46 | const start = await harness.agent.get("/auth/github").expect(302); |
| 47 | const state = new URL(start.headers.location).searchParams.get("state"); |
| 48 | await harness.agent.get(`/auth/github/callback?code=ok&state=${state}`).expect(302); |
| 49 | harness.store.updateOwnerTokens(77, { |
| 50 | accessToken: "expired-token", |
| 51 | refreshToken: "refresh-me", |
| 52 | expiresAt: "2026-07-23T11:59:00.000Z", |
| 53 | }); |
| 54 | const install = await harness.agent.get("/github/install").expect(302); |
| 55 | const installState = new URL(install.headers.location).searchParams.get("state"); |
| 56 | await harness.agent.get(`/github/installed?installation_id=333&state=${installState}`).expect(302); |
| 57 | await harness.agent.get("/").expect(200); |
| 58 | expect(harness.calls.refreshes).toEqual(["refresh-me"]); |
| 59 | expect(harness.calls.repositoryTokens).toContain("refreshed-token"); |
| 60 | }); |
| 61 | |
| 62 | it("signs the owner out and removes the session", async () => { |
| 63 | harness = makeHarness(); |
| 64 | const start = await harness.agent.get("/auth/github").expect(302); |
| 65 | const state = new URL(start.headers.location).searchParams.get("state"); |
| 66 | await harness.agent.get(`/auth/github/callback?code=ok&state=${state}`).expect(302); |
| 67 | |
| 68 | const response = await harness.agent.post("/auth/logout").expect(302); |
| 69 | expect(response.headers.location).toBe("/"); |
| 70 | expect(harness.store.sessionCount()).toBe(0); |
| 71 | |
| 72 | const page = await harness.agent.get("/").expect(200); |
| 73 | expect(page.text).toContain("Continue with GitHub"); |
| 74 | expect(page.text).not.toContain('action="/auth/logout"'); |
| 75 | }); |
| 76 | }); |
| 77 | |