UNPKG

@cocalc/database

Version:

CoCalc: code for working with our PostgreSQL database

99 lines (85 loc) 3.11 kB
/* * This file is part of CoCalc: Copyright © 2022 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ import { Client } from "pg"; import getLogger from "@cocalc/backend/logger"; import { PostgreSQL } from "@cocalc/database/postgres/types"; import { lstat, readFile , realpath } from "fs/promises"; const L = getLogger("auth:sso:import-sso-configuration").debug; // The path to the file. In actual use, this is a K8S secret exported as a file to /secrets/sso/sso.json // content of that file: "{ [strategy name]: {conf: {…}, info: {…}}, […] : { … } | null, … }" // further details are describe in src/packages/server/auth/sso/types.ts const SSO_JSON = process.env.COCALC_SSO_CONFIG; // This function imports the SSO configuration from a file into the database. // If a key points to "null", the entry is deleted. // This runs only once during startup, called by the hub's auth.ts. export async function loadSSOConf(db: PostgreSQL): Promise<void> { if (SSO_JSON == null) { L("No SSO configuration file specified via $COCALC_SSO_CONFIG."); return; } // test if the path at SSO_JSON is a regular file and is readable try { // the file could be a symlink, we have to resolve it const ssofn = await realpath(SSO_JSON) const stats = await lstat(ssofn); if (!stats.isFile()) { L(`SSO configuration file ${SSO_JSON} is not a regular file`); return; } } catch (err) { L(`SSO configuration file ${SSO_JSON} does not exist or is not readable`); return; } await load(db); } async function load(db: PostgreSQL) { if (SSO_JSON == null) { throw new Error("SSO_JSON is not defined, should never happen"); } // load the json data stored in the file SSO_JSON L(`Loading SSO configuration from '${SSO_JSON}'`); const client = db._client(); if (client == null) { L(`no database client available -- skipping SSO configuration`); return; } // throws upon JSON parsing errors const data = JSON.parse(await readFile(SSO_JSON, "utf8")); try { await client.query("BEGIN"); for (const strategy in data) { const val = data[strategy]; if (val == null) { await deleteSSO(client, strategy); } else { await upsertSSO(client, strategy, val); } } await client.query("COMMIT"); } catch (err) { L(`ROLLBACK -- err=${err}`); await client.query("ROLLBACK"); } } const deleteQuery = ` DELETE FROM passport_settings WHERE strategy = $1`; async function deleteSSO(client: Client, strategy: string) { L(`Deleting SSO configuration for ${strategy}`); await client.query(deleteQuery, [strategy]); } const upsertQuery = ` INSERT INTO passport_settings (strategy, conf, info) VALUES ($1, $2, $3) ON CONFLICT (strategy) DO UPDATE SET conf = $2, info = $3`; async function upsertSSO( client: Client, strategy: string, val: { conf: object; info: object } ) { const { conf, info } = val; L(`Updating SSO configuration for ${strategy}:`, { conf, info }); await client.query(upsertQuery, [strategy, conf, info]); }