99 lines
4.8 KiB
JavaScript
99 lines
4.8 KiB
JavaScript
// Clean per-tenant installer for saltcorn-idp on the Postgres multi-tenant
|
|
// instance. Registers + enables the plugin in each named tenant schema and runs
|
|
// its onLoad (creating the _idp_* tables + bootstrapping keys), using Saltcorn's
|
|
// supported Plugin.loadAndSaveNewPlugin API inside runWithTenant -- replacing the
|
|
// old manual "INSERT INTO <tenant>._sc_plugins" SQL hack.
|
|
//
|
|
// The CLI `install-plugin -t <tenant> -d <dir>` cannot do this: a local (-d)
|
|
// plugin is "unsafe" on a non-root tenant, so loadAndSaveNewPlugin returns
|
|
// before the upsert unless allowUnsafeOnTenantsWithoutConfigSetting (its 5th arg)
|
|
// is set, and the CLI never passes it. This script passes it.
|
|
//
|
|
// Usage (from project root, PG env sourced -- see installIdpTenant.sh):
|
|
// node idp/scripts/installIdpTenant.js t1 t2 (or '*' for all tenants)
|
|
|
|
const { createRequire } = require("node:module");
|
|
const path = require("node:path");
|
|
|
|
// Re-root @saltcorn/* against the Saltcorn checkout's node_modules. This file
|
|
// lives at idp/scripts/, so up 2 = project root, then saltcorn/packages/...
|
|
const scRequire = createRequire(path.join(__dirname, "..", "..", "saltcorn", "packages", "saltcorn-data", "package.json"));
|
|
|
|
const Plugin = scRequire("@saltcorn/data/models/plugin");
|
|
const db = scRequire("@saltcorn/data/db");
|
|
const { init_multi_tenant, getRootState } = scRequire("@saltcorn/data/db/state");
|
|
const { getAllTenants } = scRequire("@saltcorn/admin-models/models/tenant");
|
|
|
|
const PLUGIN_NAME = "saltcorn-idp";
|
|
const IDP_DIR = path.resolve(__dirname, "..");
|
|
|
|
|
|
const installInto = async (tenant) => {
|
|
await db.runWithTenant(tenant, async () => {
|
|
await db.withTransaction(async () => {
|
|
// Remove any prior rows (including the old manual-hack row and earlier
|
|
// installs) so we converge on exactly one _sc_plugins row -- no
|
|
// duplicate source of truth.
|
|
await db.deleteWhere("_sc_plugins", { name: PLUGIN_NAME });
|
|
const plugin = new Plugin({ name: PLUGIN_NAME, source: "local", location: IDP_DIR, configuration: {} });
|
|
await Plugin.loadAndSaveNewPlugin(plugin, true, false);
|
|
});
|
|
// Verify against the NEWEST table so a stale Phase-3 row can't pass.
|
|
const row = await db.selectMaybeOne("_sc_plugins", { name: PLUGIN_NAME });
|
|
const svc = await db.query(
|
|
"SELECT 1 FROM information_schema.tables WHERE table_schema = $1 AND table_name = '_idp_ldap_service'",
|
|
[db.getTenantSchema()]
|
|
);
|
|
const hasSvc = svc && svc.rows && svc.rows.length > 0;
|
|
// eslint-disable-next-line no-console
|
|
console.log(`[installIdpTenant] ${tenant}: _sc_plugins=${row ? "yes" : "NO"} _idp_ldap_service=${hasSvc ? "yes" : "NO"}`);
|
|
if (!row || !hasSvc) {
|
|
throw new Error("install verification failed for tenant " + tenant + " (onLoad did not run)");
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
const main = async () => {
|
|
await Plugin.loadAllPlugins();
|
|
|
|
// Resolve the target tenants (from the public _sc_tenants list).
|
|
let tenants = process.argv.slice(2);
|
|
if (tenants.length === 0 || (tenants.length === 1 && tenants[0] === "*")) {
|
|
const all = await db.runWithTenant(db.connectObj.default_schema, getAllTenants);
|
|
tenants = (all || []).map((t) => (typeof t === "string" ? t : t.subdomain)).filter(Boolean);
|
|
}
|
|
if (!tenants || tenants.length === 0) {
|
|
// eslint-disable-next-line no-console
|
|
console.error("[installIdpTenant] no tenants to install into");
|
|
process.exit(1);
|
|
}
|
|
|
|
// Initialize per-tenant State (so getState() resolves inside runWithTenant)
|
|
// without running migrations. This also runs each tenant's existing plugins'
|
|
// onLoad, which is itself idempotent.
|
|
await init_multi_tenant(Plugin.loadAllPlugins, true, tenants);
|
|
|
|
// Permit installing this LOCAL plugin into tenant schemas. In this Saltcorn
|
|
// build loadAndSaveNewPlugin skips any non-"npm" plugin on a non-root tenant
|
|
// BEFORE the allowUnsafe arg is consulted; the supported lever is this
|
|
// root-only config -- the intended setting for a multi-tenant deployment that
|
|
// offers the IdP plugin to its tenants.
|
|
await getRootState().setConfig("tenants_unsafe_plugins", true);
|
|
|
|
// eslint-disable-next-line no-console
|
|
console.log("[installIdpTenant] installing " + PLUGIN_NAME + " into: " + tenants.join(", "));
|
|
for (const t of tenants) {
|
|
await installInto(t);
|
|
}
|
|
// eslint-disable-next-line no-console
|
|
console.log("[installIdpTenant] done");
|
|
process.exit(0);
|
|
};
|
|
|
|
|
|
main().catch((e) => {
|
|
// eslint-disable-next-line no-console
|
|
console.error("[installIdpTenant] ERROR:", e && (e.stack || e.message || e));
|
|
process.exit(1);
|
|
});
|