- Server settings (Settings page): SMTP, AI, publishing, Meta, platform keys and, on the control centre only, Stripe/billing. Stored sealed in data/settings.json and applied over .env in every process; hosted agency workspaces don't see our email, platform keys or billing. - Odoo connection per workspace in Connections (login checked before saving). - provision-tenants.sh marks agency containers with QUORLA_HOSTED_TENANT=true. - docs/wildcard-subdomains.html: one-time setup for automatic agency subdomains. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
2.2 KiB
JavaScript
37 lines
2.2 KiB
JavaScript
// Odoo settings from Connections: login checked before saving, stored encrypted, key never returned, env fallback.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "quorla-odoo-"));
|
|
const FILE = path.join(TMP, "connections.json");
|
|
Object.assign(process.env, { QUORLA_CONNECTIONS_FILE: FILE, DASHBOARD_SECRET: "s".repeat(32), QUORLA_DASHBOARD_DATA: path.join(TMP, "dash"), ODOO_URL: "", ODOO_API_KEY: "" });
|
|
const odoo = await import("../../dashboard/server/odoo.js");
|
|
|
|
test("save checks the login, stores it sealed and hides the key; disconnect clears it", async () => {
|
|
const realFetch = globalThis.fetch;
|
|
const calls = [];
|
|
globalThis.fetch = async (url, init) => {
|
|
const { params } = JSON.parse(init.body);
|
|
calls.push({ url: String(url), params });
|
|
const ok = params.args[2] === "good-key";
|
|
return new Response(JSON.stringify({ result: params.method === "login" ? (ok ? 7 : false) : null }));
|
|
};
|
|
try {
|
|
assert.equal(odoo.settings().configured, false);
|
|
const form = { url: "https://acme.odoo.com/odoo/action-1", db: "acme", username: "ops@acme.test", allowWrites: true };
|
|
await assert.rejects(odoo.saveSettings({ ...form, apiKey: "bad-key" }, "Ana"), /rejected the login/);
|
|
await assert.rejects(odoo.saveSettings({ ...form, apiKey: "" }, "Ana"), /required/);
|
|
const s = await odoo.saveSettings({ ...form, apiKey: "good-key" }, "Ana");
|
|
assert.equal(calls.at(-1).url, "https://acme.odoo.com/jsonrpc", "address trimmed to the site root");
|
|
assert.deepEqual([s.configured, s.source, s.url, s.allowWrites, s.updatedBy], [true, "settings", "https://acme.odoo.com", true, "Ana"]);
|
|
assert.equal(JSON.stringify(s).includes("good-key"), false);
|
|
assert.equal(fs.readFileSync(FILE, "utf8").includes("good-key"), false, "key encrypted at rest");
|
|
const again = await odoo.saveSettings({ ...form, apiKey: "", allowWrites: false }, "Ana");
|
|
assert.equal(again.allowWrites, false, "empty key keeps the stored one");
|
|
assert.equal(odoo.removeSettings().configured, false);
|
|
} finally { globalThis.fetch = realFetch; }
|
|
});
|