import assert from "node:assert/strict"; import test from "node:test"; import { type AuthorizationPrompt, loginWithBrowser, refreshOAuthCredentials } from "../src/oauth.js"; const discovery = { issuer: "https://justconn.top/api", device_authorization_endpoint: "https://justconn.top/api/idpOAuth/device/auth", registration_endpoint: "https://justconn.top/api/idpOAuth/register", token_endpoint: "https://justconn.top/api/idpOAuth/token", grant_types_supported: [ "authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", ], scopes_supported: ["openid", "offline_access", "api", "mcp"], token_endpoint_auth_methods_supported: ["none", "client_secret_basic"], }; test("uses NocoBase dynamic registration and browser device authorization without exposing tokens", async () => { const calls: Array<{ body: string; method: string; url: string }> = []; let tokenPolls = 0; const fetchMock = (async (input: string | URL | Request, init: RequestInit = {}) => { const url = String(input); const method = init.method ?? "GET"; const body = typeof init.body === "string" || init.body instanceof URLSearchParams ? String(init.body) : ""; calls.push({ body, method, url }); if (url.endsWith("/.well-known/openid-configuration")) return jsonResponse(discovery); if (url.endsWith("/idpOAuth/register")) { return jsonResponse( { client_id: "desktop-client", registration_access_token: "must-not-be-stored", token_endpoint_auth_method: "none", }, 201, ); } if (url.endsWith("/idpOAuth/device/auth")) { return jsonResponse({ device_code: "private-device-code", user_code: "ABCD-EFGH", verification_uri: "https://justconn.top/idpOAuth/device", verification_uri_complete: "https://justconn.top/idpOAuth/device?user_code=ABCD-EFGH", expires_in: 600, interval: 5, }); } if (url.endsWith("/idpOAuth/token")) { tokenPolls += 1; if (tokenPolls === 1) return jsonResponse({ error: "authorization_pending" }, 400); return jsonResponse({ access_token: "oauth-access-token", refresh_token: "oauth-refresh-token", token_type: "Bearer", scope: "api offline_access", expires_in: 900, }); } throw new Error(`Unexpected request: ${method} ${url}`); }) as typeof fetch; let prompt: AuthorizationPrompt | undefined; const sleeps: number[] = []; const credentials = await loginWithBrowser({ fetch: fetchMock, now: () => 1_000, openBrowser: async () => true, onAuthorization: (value) => { prompt = value; }, sleep: async (milliseconds) => { sleeps.push(milliseconds); }, }); assert.equal(credentials.clientId, "desktop-client"); assert.equal(credentials.accessToken, "oauth-access-token"); assert.equal(credentials.refreshToken, "oauth-refresh-token"); assert.equal(credentials.expiresAt, 901_000); assert.doesNotMatch(JSON.stringify(credentials), /must-not-be-stored/); assert.deepEqual(prompt, { opened: true, url: "https://justconn.top/idpOAuth/device?user_code=ABCD-EFGH", userCode: "ABCD-EFGH", expiresIn: 600, }); assert.deepEqual(sleeps, [5_000, 5_000]); const registration = JSON.parse(calls.find((call) => call.url.endsWith("/register"))?.body ?? "{}"); assert.equal(registration.application_type, "native"); assert.equal(registration.token_endpoint_auth_method, "none"); assert.deepEqual(registration.response_types, []); assert.deepEqual(registration.redirect_uris, []); assert.equal(registration.client_secret, undefined); const deviceForm = new URLSearchParams(calls.find((call) => call.url.endsWith("/device/auth"))?.body); assert.equal(deviceForm.get("scope"), "api offline_access"); assert.equal(deviceForm.get("resource"), "https://justconn.top/api/"); }); test("rejects OAuth discovery that points tokens at another origin", async () => { const fetchMock = (async () => jsonResponse({ ...discovery, token_endpoint: "https://evil.example/token" })) as typeof fetch; await assert.rejects( loginWithBrowser({ fetch: fetchMock, openBrowser: async () => true }), /untrusted endpoint/, ); }); test("refreshes a public-client OAuth session and keeps a rotated refresh token optional", async () => { const bodies: string[] = []; const fetchMock = (async (input: string | URL | Request, init: RequestInit = {}) => { const url = String(input); if (url.endsWith("/.well-known/openid-configuration")) return jsonResponse(discovery); bodies.push(String(init.body)); return jsonResponse({ access_token: "new-access", token_type: "Bearer", scope: "api offline_access", expires_in: 600, }); }) as typeof fetch; const refreshed = await refreshOAuthCredentials( { type: "oauth", issuer: "https://justconn.top/api", clientId: "desktop-client", accessToken: "old-access", refreshToken: "old-refresh", tokenType: "Bearer", scope: "api offline_access", expiresAt: 10, }, { fetch: fetchMock, now: () => 2_000 }, ); assert.equal(refreshed.accessToken, "new-access"); assert.equal(refreshed.refreshToken, "old-refresh"); assert.equal(refreshed.expiresAt, 602_000); const form = new URLSearchParams(bodies[0]); assert.equal(form.get("grant_type"), "refresh_token"); assert.equal(form.get("client_id"), "desktop-client"); assert.equal(form.get("refresh_token"), "old-refresh"); }); function jsonResponse(value: unknown, status = 200): Response { return new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" }, }); }