feat(files): API tokens in Settings, the Hub folder in Files, a notice before a token runs out
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.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
// @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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user