CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/database/postgres/load-sso-conf.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Client } from "pg";
7
import getLogger from "@cocalc/backend/logger";
8
import { PostgreSQL } from "@cocalc/database/postgres/types";
9
import { lstat, readFile , realpath } from "fs/promises";
10
11
const L = getLogger("auth:sso:import-sso-configuration").debug;
12
13
// The path to the file. In actual use, this is a K8S secret exported as a file to /secrets/sso/sso.json
14
// content of that file: "{ [strategy name]: {conf: {…}, info: {…}}, […] : { … } | null, … }"
15
// further details are describe in src/packages/server/auth/sso/types.ts
16
const SSO_JSON = process.env.COCALC_SSO_CONFIG;
17
18
// This function imports the SSO configuration from a file into the database.
19
// If a key points to "null", the entry is deleted.
20
// This runs only once during startup, called by the hub's auth.ts.
21
export async function loadSSOConf(db: PostgreSQL): Promise<void> {
22
if (SSO_JSON == null) {
23
L("No SSO configuration file specified via $COCALC_SSO_CONFIG.");
24
return;
25
}
26
27
// test if the path at SSO_JSON is a regular file and is readable
28
try {
29
// the file could be a symlink, we have to resolve it
30
const ssofn = await realpath(SSO_JSON)
31
const stats = await lstat(ssofn);
32
if (!stats.isFile()) {
33
L(`SSO configuration file ${SSO_JSON} is not a regular file`);
34
return;
35
}
36
} catch (err) {
37
L(`SSO configuration file ${SSO_JSON} does not exist or is not readable`);
38
return;
39
}
40
await load(db);
41
}
42
43
async function load(db: PostgreSQL) {
44
if (SSO_JSON == null) {
45
throw new Error("SSO_JSON is not defined, should never happen");
46
}
47
// load the json data stored in the file SSO_JSON
48
L(`Loading SSO configuration from '${SSO_JSON}'`);
49
50
const client = db._client();
51
if (client == null) {
52
L(`no database client available -- skipping SSO configuration`);
53
return;
54
}
55
56
// throws upon JSON parsing errors
57
const data = JSON.parse(await readFile(SSO_JSON, "utf8"));
58
59
try {
60
await client.query("BEGIN");
61
for (const strategy in data) {
62
const val = data[strategy];
63
if (val == null) {
64
await deleteSSO(client, strategy);
65
} else {
66
await upsertSSO(client, strategy, val);
67
}
68
}
69
await client.query("COMMIT");
70
} catch (err) {
71
L(`ROLLBACK -- err=${err}`);
72
await client.query("ROLLBACK");
73
}
74
}
75
76
const deleteQuery = `
77
DELETE FROM passport_settings
78
WHERE strategy = $1`;
79
80
async function deleteSSO(client: Client, strategy: string) {
81
L(`Deleting SSO configuration for ${strategy}`);
82
await client.query(deleteQuery, [strategy]);
83
}
84
85
const upsertQuery = `
86
INSERT INTO passport_settings (strategy, conf, info)
87
VALUES ($1, $2, $3)
88
ON CONFLICT (strategy) DO UPDATE SET conf = $2, info = $3`;
89
90
async function upsertSSO(
91
client: Client,
92
strategy: string,
93
val: { conf: object; info: object }
94
) {
95
const { conf, info } = val;
96
L(`Updating SSO configuration for ${strategy}:`, { conf, info });
97
await client.query(upsertQuery, [strategy, conf, info]);
98
}
99
100