Settings gains an API tokens tab: a row per token (name and prefix; each folder with its rights; last use and expiry, "expires soon" marked), revoked from its menu behind a confirm. A new token is the Hub exchange in one click, or folders and rights picked per folder (no Write where an upload cannot land); expiry 30/90 days, 1 year or never (warned). The token shows once in the same dialog, with Copy and a ready curl line. The Hub folder closes the Sources in the Files rail, takes uploads and moved-in files. A token about to run out warns its user once, a week before (the Files GC's sixth pass); the notice opens the tokens tab.
27 lines
1.3 KiB
TypeScript
27 lines
1.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { TOKEN_EXPIRY_DAYS, TOKEN_RIGHTS } from "./tokens";
|
|
|
|
// The token dialog's choices, held to what the backend takes (apitokens.py, routes/tokens.py), read
|
|
// from their source as text — the backend cannot be imported here. A right the server does not know
|
|
// is a 422 for the whole token; an expiry past its ceiling likewise.
|
|
const backend = (file: string) =>
|
|
readFileSync(resolve(__dirname, `../../../../backend/app/${file}`), "utf-8");
|
|
|
|
describe("the token dialog's choices", () => {
|
|
it("are the backend's rights, in its order", () => {
|
|
const body = backend("apitokens.py").match(/^class Right\(StrEnum\):\n([\s\S]*?)\n\n/m)?.[1];
|
|
expect(body, "apitokens.py no longer defines Right").toBeTruthy();
|
|
const rights = [...body!.matchAll(/=\s*"(\w+)"/g)].map((m) => m[1]);
|
|
expect(TOKEN_RIGHTS).toEqual(rights);
|
|
});
|
|
|
|
it("last no longer than the backend allows", () => {
|
|
const max = Number(backend("routes/tokens.py").match(/^_MAX_EXPIRY_DAYS = (\d+)$/m)?.[1]);
|
|
expect(max).toBeGreaterThan(0);
|
|
for (const days of TOKEN_EXPIRY_DAYS) if (days !== null) expect(days).toBeLessThanOrEqual(max);
|
|
});
|
|
});
|