Files
nocobase-mcp/test/auth-service.test.ts
T

106 lines
3.8 KiB
TypeScript

import assert from "node:assert/strict";
import { existsSync, mkdtempSync, statSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { NocoBaseAuthService } from "../src/auth-service.js";
import { loadStoredCredentials, saveCredentials } from "../src/credentials.js";
import type { OAuthCredentials } from "../src/oauth.js";
const oauthCredentials: OAuthCredentials = {
type: "oauth",
issuer: "https://justconn.top/api",
clientId: "desktop-client",
accessToken: "private-access-token",
refreshToken: "private-refresh-token",
tokenType: "Bearer",
scope: "api offline_access",
expiresAt: 901_000,
};
test("returns a browser link immediately, then verifies and saves OAuth credentials in the background", async () => {
const directory = mkdtempSync(join(tmpdir(), "nocobase-mcp-auth-service-"));
const path = join(directory, "private", "credentials.json");
let resolveCredentials: (credentials: OAuthCredentials) => void = () => undefined;
const credentials = new Promise<OAuthCredentials>((resolve) => {
resolveCredentials = resolve;
});
let verified = 0;
let reloaded = 0;
const service = new NocoBaseAuthService({
credentialsFile: path,
now: () => 1_000,
startLogin: async () => ({
prompt: {
opened: false,
url: "https://justconn.top/idpOAuth/device?user_code=ABCD-EFGH",
userCode: "ABCD-EFGH",
expiresIn: 600,
},
credentials,
}),
verifyCredentials: async (value) => {
assert.equal(value.accessToken, oauthCredentials.accessToken);
verified += 1;
},
onCredentialsSaved: () => {
reloaded += 1;
},
});
const started = await service.start();
assert.deepEqual(started, {
status: "authorization_pending",
login_url: "https://justconn.top/idpOAuth/device?user_code=ABCD-EFGH",
user_code: "ABCD-EFGH",
expires_in_seconds: 600,
browser_opened: false,
next_action:
"Send login_url and user_code to the user. After approval, poll nocobase_login_status until authenticated.",
});
assert.doesNotMatch(JSON.stringify(started), /private-access-token|private-refresh-token/);
assert.equal(existsSync(path), false);
assert.equal((await service.status()).status, "authorization_pending");
resolveCredentials(oauthCredentials);
for (let attempt = 0; attempt < 10 && !existsSync(path); attempt += 1) {
await new Promise<void>((resolve) => setImmediate(resolve));
}
assert.equal(verified, 1);
assert.equal(reloaded, 1);
assert.equal((await service.status()).status, "authenticated");
const stored = loadStoredCredentials(path);
assert.equal(stored.type, "oauth");
if (process.platform !== "win32") assert.equal(statSync(path).mode & 0o777, 0o600);
});
test("reuses stored login unless a fresh authorization is explicitly requested", async () => {
const directory = mkdtempSync(join(tmpdir(), "nocobase-mcp-auth-force-"));
const path = join(directory, "private", "credentials.json");
saveCredentials({ authorizeKey: "personal-key" }, path);
let starts = 0;
const service = new NocoBaseAuthService({
credentialsFile: path,
startLogin: async () => {
starts += 1;
return {
prompt: {
opened: true,
url: "https://justconn.top/idpOAuth/device?user_code=FRESH-CODE",
userCode: "FRESH-CODE",
expiresIn: 600,
},
credentials: new Promise<OAuthCredentials>(() => undefined),
};
},
});
assert.equal((await service.start()).status, "authenticated");
assert.equal(starts, 0);
assert.equal((await service.start({ force: true })).status, "authorization_pending");
assert.equal(starts, 1);
assert.equal((await service.start({ force: true })).status, "authorization_pending");
assert.equal(starts, 1);
});