Phase B: encrypted archive export bundles (age), power-level-gated export command, encryption-state linter
This commit is contained in:
parent
a7ad1f059f
commit
699b9412c1
22 changed files with 2325 additions and 9 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { ConfigError, loadConfig, parseRoomMappings, toGroupFileMapping } from "./config.js";
|
||||
import { ConfigError, loadConfig, parseExportConfig, parseRoomMappings, toGroupFileMapping } from "./config.js";
|
||||
|
||||
const WS = "11111111-1111-4111-8111-111111111111";
|
||||
const GROUP = "22222222-2222-4222-8222-222222222222";
|
||||
|
|
@ -27,8 +27,7 @@ describe("parseRoomMappings", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("loadConfig", () => {
|
||||
const env = {
|
||||
const env = {
|
||||
DATABASE_URL: "postgres://bot@db/hub",
|
||||
MATRIX_HOMESERVER_URL: "https://matrix.example.org",
|
||||
MATRIX_USER: "archive-bot",
|
||||
|
|
@ -39,8 +38,10 @@ describe("loadConfig", () => {
|
|||
FILES_OCID_PASSWORD: "pw2",
|
||||
ARCHIVE_BOT_HEALTH_PORT: "9100",
|
||||
MATRIX_MEDIA_REDIRECT_HOSTS: " R2.example.com ,",
|
||||
};
|
||||
};
|
||||
void env;
|
||||
|
||||
describe("loadConfig", () => {
|
||||
it("parses the full env contract", () => {
|
||||
const config = loadConfig(env);
|
||||
expect(config.healthPort).toBe(9100);
|
||||
|
|
@ -54,3 +55,51 @@ describe("loadConfig", () => {
|
|||
expect(() => loadConfig({ ...env, FILES_OCID_WEBDAV_URL: "ftp://x" })).toThrow(/http/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseExportConfig (phase-B encrypted exports)", () => {
|
||||
const R = "age1" + "q".repeat(58);
|
||||
const base = {
|
||||
ARCHIVE_EXPORT_AGE_RECIPIENTS: R,
|
||||
ARCHIVE_EXPORT_R2_ACCESS_KEY: "k",
|
||||
ARCHIVE_EXPORT_R2_SECRET_KEY: "s",
|
||||
};
|
||||
|
||||
it("is null when nothing is configured", () => {
|
||||
expect(parseExportConfig({})).toBeNull();
|
||||
});
|
||||
|
||||
it("is null when explicitly disabled, even with credentials present", () => {
|
||||
expect(parseExportConfig({ ...base, ARCHIVE_EXPORT_ENABLED: "false" })).toBeNull();
|
||||
});
|
||||
|
||||
it("parses recipients, R2 credentials and defaults", () => {
|
||||
const cfg = parseExportConfig(base);
|
||||
expect(cfg).toMatchObject({
|
||||
enabled: true,
|
||||
ageRecipients: [R],
|
||||
every: 500,
|
||||
r2: { region: "auto", bucket: "hub-matrix-media" },
|
||||
});
|
||||
});
|
||||
|
||||
it("de-duplicates recipients and honours ARCHIVE_EXPORT_EVERY", () => {
|
||||
const cfg = parseExportConfig({ ...base, ARCHIVE_EXPORT_AGE_RECIPIENTS: `${R}, ${R}`, ARCHIVE_EXPORT_EVERY: "50" });
|
||||
expect(cfg?.ageRecipients).toEqual([R]);
|
||||
expect(cfg?.every).toBe(50);
|
||||
});
|
||||
|
||||
it("rejects malformed recipients and missing credentials", () => {
|
||||
expect(() => parseExportConfig({ ...base, ARCHIVE_EXPORT_AGE_RECIPIENTS: "AGE-SECRET-KEY-1X" })).toThrow(ConfigError);
|
||||
expect(() => parseExportConfig({ ARCHIVE_EXPORT_AGE_RECIPIENTS: R })).toThrow(/ACCESS_KEY/);
|
||||
expect(() => parseExportConfig({ ...base, ARCHIVE_EXPORT_EVERY: "-1" })).toThrow(ConfigError);
|
||||
expect(() => parseExportConfig({ ...base, ARCHIVE_EXPORT_R2_ENDPOINT: "http://insecure" })).toThrow(ConfigError);
|
||||
});
|
||||
|
||||
it("wires into loadConfig", () => {
|
||||
const cfg = loadConfig({ ...env, ...base });
|
||||
expect(cfg.export).not.toBeNull();
|
||||
expect(cfg.export?.ageRecipients).toEqual([R]);
|
||||
// and stays null in the default case
|
||||
expect(loadConfig(env).export).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue