import { env } from "cloudflare:workers"; import { beforeAll, beforeEach, describe, expect, it } from "vitest"; import { DEFAULT_PASSWORD, DEFAULT_REDIRECT_URI, ISSUER, SELF, countOAuthTokensForClient, createOAuthClientAsAdmin, getLatestSessionIdForUser, getUserIdByEmail, signInForCookie, signUpAdmin, signUpTestUser, testHeaders, type TestCredential, } from "./helpers"; const countConsents = async (clientId: string): Promise => { const row = await env.DB.prepare("SELECT COUNT(*) AS c FROM oauth_consents WHERE client_id = ?") .bind(clientId) .first<{ c: number }>(); return row?.c ?? 0; }; // User-facing revocation goes through tessera's // `DELETE /api/account/connected-apps/:id`, which deletes the consent // row plus matching access + refresh tokens in one D1 batch. The // plugin's raw `oauth2/{delete,update}-consent` endpoints touch only the // consent row, so a caller holding a session cookie could remove the UI // affordance for revocation while leaving previously-issued bearer // tokens valid until expiry. tessera blocks both raw paths. describe("Raw oauth2/delete-consent + oauth2/update-consent are blocked", () => { const adminCred = { email: "consent-block-admin@example.com", password: DEFAULT_PASSWORD, name: "Consent Block Admin", } satisfies TestCredential; const userCred = { email: "consent-block-user@example.com", password: DEFAULT_PASSWORD, name: "Consent Block User", } satisfies TestCredential; let clientId: string; let userId: string; let consentId: string; beforeAll(async () => { await signUpAdmin(adminCred); await signUpTestUser(userCred); const adminCookie = await signInForCookie(adminCred.email, adminCred.password, "10.40.0.1"); const created = await createOAuthClientAsAdmin(adminCookie, { name: "consent-block-target", redirectUris: [DEFAULT_REDIRECT_URI], }); clientId = created.client_id; userId = await getUserIdByEmail(userCred.email); }); beforeEach(async () => { // Reset consent + tokens between tests so prior runs don't bleed // through. consentId is stamped on each fresh insert to keep the // raw endpoint targets unambiguous. await env.DB.batch([ env.DB.prepare("DELETE FROM oauth_access_tokens WHERE client_id = ?").bind(clientId), env.DB.prepare("DELETE FROM oauth_refresh_tokens WHERE client_id = ?").bind(clientId), env.DB.prepare("DELETE FROM oauth_consents WHERE client_id = ?").bind(clientId), ]); consentId = `consent-${crypto.randomUUID()}`; const sessionId = await getLatestSessionIdForUser(userId); const now = Date.now(); const expiresAt = now + 60_000; const scopes = JSON.stringify(["openid"]); const refreshId = `rt-${consentId}`; const accessId = `at-${consentId}`; await env.DB.batch([ env.DB.prepare( `INSERT INTO oauth_consents (id, client_id, user_id, scopes, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, ).bind(consentId, clientId, userId, scopes, now, now), env.DB.prepare( `INSERT INTO oauth_refresh_tokens (id, token, client_id, session_id, user_id, expires_at, created_at, scopes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, ).bind(refreshId, `tok-${refreshId}`, clientId, sessionId, userId, expiresAt, now, scopes), env.DB.prepare( `INSERT INTO oauth_access_tokens (id, token, client_id, refresh_id, session_id, user_id, expires_at, created_at, scopes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).bind(accessId, `tok-${accessId}`, clientId, refreshId, sessionId, userId, expiresAt, now, scopes), ]); }); it("blocks POST /api/auth/oauth2/delete-consent and leaves consent + tokens intact", async () => { const cookie = await signInForCookie(userCred.email, userCred.password, "10.40.0.2"); const path = "/api/auth/oauth2/delete-consent"; for (const variant of [path, `${path}/`, `${path}//`]) { const res = await SELF.fetch(`${ISSUER}${variant}`, { method: "POST", headers: testHeaders({ cookie }), body: JSON.stringify({ id: consentId }), }); expect(res.status, `expected 404 for ${variant}`).toBe(404); } expect(await countConsents(clientId)).toBe(1); expect(await countOAuthTokensForClient("oauth_access_tokens", clientId)).toBe(1); expect(await countOAuthTokensForClient("oauth_refresh_tokens", clientId)).toBe(1); }); it("blocks POST /api/auth/oauth2/update-consent and leaves consent + tokens intact", async () => { const cookie = await signInForCookie(userCred.email, userCred.password, "10.40.0.3"); const path = "/api/auth/oauth2/update-consent"; for (const variant of [path, `${path}/`, `${path}//`]) { const res = await SELF.fetch(`${ISSUER}${variant}`, { method: "POST", headers: testHeaders({ cookie }), body: JSON.stringify({ id: consentId, update: { scopes: ["openid"] } }), }); expect(res.status, `expected 404 for ${variant}`).toBe(404); } expect(await countConsents(clientId)).toBe(1); expect(await countOAuthTokensForClient("oauth_access_tokens", clientId)).toBe(1); expect(await countOAuthTokensForClient("oauth_refresh_tokens", clientId)).toBe(1); }); it("DELETE /api/account/connected-apps/:id still revokes via the supported path", async () => { const cookie = await signInForCookie(userCred.email, userCred.password, "10.40.0.4"); const res = await SELF.fetch(`${ISSUER}/api/account/connected-apps/${encodeURIComponent(consentId)}`, { method: "DELETE", headers: testHeaders({ cookie }), }); expect(res.status).toBe(204); expect(await countConsents(clientId)).toBe(0); expect(await countOAuthTokensForClient("oauth_access_tokens", clientId)).toBe(0); expect(await countOAuthTokensForClient("oauth_refresh_tokens", clientId)).toBe(0); }); });