import { createSign, generateKeyPairSync, type KeyObject } from "node:crypto"; import { afterEach, describe, expect, it, vi } from "vitest"; import { clearJwksCacheForTests } from "@/lib/server/auth/jwt"; import { POST } from "@/app/api/auth/password/route"; type TestJwk = JsonWebKey & { kid?: string; alg?: string; use?: string; }; const baseEnv = { ZHINIAN_AUTH_REQUIRED: "1", ZHINIAN_AUTH_BASE_URL: "https://gateway.example.com/auth", ZHINIAN_AUTH_CLIENT_ID: "agentbus-client", ZHINIAN_AUTH_CLIENT_SECRET: "client-secret", ZHINIAN_AUTH_SCOPE: "server", ZHINIAN_AUTH_ISSUER: "https://pig4cloud.com", ZHINIAN_AUTH_SESSION_SECRET: "test-session-secret-with-enough-entropy" }; describe("password auth route AgentBus compatibility", () => { afterEach(() => { vi.unstubAllEnvs(); vi.unstubAllGlobals(); clearJwksCacheForTests(); }); it("encrypts password with the configured AES-CFB key and does not require captcha fields", async () => { for (const [key, value] of Object.entries(baseEnv)) vi.stubEnv(key, value); vi.stubEnv("ZHINIAN_AUTH_PASSWORD_ENC_KEY", "thanks,pig4cloud"); const { publicKey, privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 }); const jwk = publicKey.export({ format: "jwk" }) as TestJwk; jwk.kid = "agentbus-key"; const accessToken = signJwt({ iss: baseEnv.ZHINIAN_AUTH_ISSUER, sub: "subject-42", user_id: "remote-42", username: "user@example.com", client_id: baseEnv.ZHINIAN_AUTH_CLIENT_ID, scope: baseEnv.ZHINIAN_AUTH_SCOPE, exp: Math.floor(Date.now() / 1000) + 600, iat: Math.floor(Date.now() / 1000) - 10, nbf: Math.floor(Date.now() / 1000) - 10 }, privateKey, "agentbus-key"); const seenBodies: URLSearchParams[] = []; vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); if (url.endsWith("/oauth2/jwks")) { return new Response(JSON.stringify({ keys: [jwk] }), { status: 200 }); } if (url.endsWith("/oauth2/token")) { seenBodies.push(new URLSearchParams(String(init?.body))); return new Response(JSON.stringify({ access_token: accessToken, refresh_token: "refresh-token-1", token_type: "bearer", expires_in: "3600" }), { status: 200 }); } return new Response("not found", { status: 404 }); }); const response = await POST(new Request("https://app.example.com/api/auth/password", { method: "POST", body: JSON.stringify({ username: "user@example.com", password: "123456", next: "/create" }) })); expect(response.status).toBe(200); expect(seenBodies).toHaveLength(1); expect(seenBodies[0].get("grant_type")).toBe("password"); expect(seenBodies[0].get("username")).toBe("user@example.com"); expect(seenBodies[0].get("password")).toBe("YehdBPev"); expect(seenBodies[0].has("code")).toBe(false); expect(seenBodies[0].has("randomStr")).toBe(false); }); }); function signJwt(payload: Record, privateKey: KeyObject, kid: string): string { const header = base64UrlJson({ alg: "RS256", typ: "JWT", kid }); const body = base64UrlJson(payload); const signingInput = `${header}.${body}`; const signer = createSign("RSA-SHA256"); signer.update(signingInput); signer.end(); const signature = signer.sign(privateKey).toString("base64url"); return `${signingInput}.${signature}`; } function base64UrlJson(value: unknown): string { return Buffer.from(JSON.stringify(value)).toString("base64url"); }