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/settings/server-settings.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2021 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import LRU from "lru-cache";
7
8
import getLogger from "@cocalc/backend/logger";
9
import getPool from "@cocalc/database/pool";
10
import type { PostgreSQL } from "@cocalc/database/postgres/types";
11
import { PassportStrategyDB } from "@cocalc/database/settings/auth-sso-types";
12
import { callback2 as cb2 } from "@cocalc/util/async-utils";
13
import { SERVER_SETTINGS_ENV_PREFIX } from "@cocalc/util/consts";
14
import { EXTRAS } from "@cocalc/util/db-schema/site-settings-extras";
15
import {
16
AllSiteSettingsKeys,
17
AllSiteSettingsCached as ServerSettings,
18
} from "@cocalc/util/db-schema/types";
19
import { site_settings_conf as CONF } from "@cocalc/util/schema";
20
export type { ServerSettings };
21
22
const L = getLogger("server:server-settings");
23
24
// We're just using this to cache this result for a **few seconds**.
25
const CACHE_TIME_SECONDS = process.env.NODE_ENV == "development" ? 3 : 15;
26
type CacheKeys = "server-settings" | "passports";
27
// TODO add something for the passports data type?
28
const cache = new LRU<CacheKeys, ServerSettings | PassportStrategyDB[]>({
29
max: 10,
30
ttl: 1000 * CACHE_TIME_SECONDS,
31
});
32
const KEY: CacheKeys = "server-settings";
33
34
export function resetServerSettingsCache() {
35
cache.clear();
36
}
37
38
export function getPassportsCached(): PassportStrategyDB[] | undefined {
39
return cache.get("passports") as PassportStrategyDB[] | undefined;
40
}
41
42
export function setPassportsCached(val: PassportStrategyDB[]) {
43
return cache.set("passports", val);
44
}
45
46
export async function getServerSettings(): Promise<ServerSettings> {
47
if (cache.has(KEY)) {
48
return cache.get(KEY)! as ServerSettings; // can't be null
49
}
50
const pool = getPool();
51
const { rows } = await pool.query("SELECT name, value FROM server_settings");
52
53
const settings: ServerSettings = { _timestamp: Date.now() };
54
55
const raw: { [key in AllSiteSettingsKeys]?: string } = {};
56
for (const row of rows) {
57
raw[row.name] = row.value;
58
}
59
60
// process values, including any post-processing.
61
for (const row of rows) {
62
const { name, value } = row;
63
const spec = CONF[name] ?? EXTRAS[name];
64
// we only process values we know
65
if (spec == null) continue;
66
const toVal = spec.to_val;
67
settings[name] = toVal != null ? toVal(value, raw) : value;
68
}
69
// set default values for missing keys
70
for (const config of [EXTRAS, CONF]) {
71
for (const key in config) {
72
if (settings[key] == null) {
73
const spec = config[key];
74
settings[key] =
75
spec?.to_val != null ? spec.to_val(spec.default, raw) : spec.default;
76
}
77
}
78
}
79
80
cache.set(KEY, settings);
81
return settings;
82
}
83
84
/*
85
This stores environment variables for server settings in the DB to make the life of an admin easier.
86
e.g. COCALC_SETTING_DNS, COCALC_SETTING_EMAIL_SMTP_SERVER, COCALC_SETTING_EMAIL_SMTP_PASSWORD, ...
87
Loaded once at startup, right after configuring the db schema, see hub/hub.ts.
88
*/
89
export async function load_server_settings_from_env(
90
db: PostgreSQL,
91
): Promise<void> {
92
const PREFIX = SERVER_SETTINGS_ENV_PREFIX;
93
L.debug("load_server_settings_from_env variables prefixed by ", PREFIX);
94
// reset all readonly values
95
await db.async_query({
96
query: "UPDATE server_settings",
97
set: { readonly: false },
98
where: ["1=1"], // otherwise there is an exception about not restricting the query
99
});
100
// now, check if there are any we know of
101
for (const config of [EXTRAS, CONF]) {
102
for (const key in config) {
103
const envvar = `${PREFIX}_${key.toUpperCase()}`;
104
const envval = process.env[envvar];
105
if (envval == null) continue;
106
// ATTN do not expose the value, could be a password
107
L.debug(`picking up $${envvar} and saving it in the database`);
108
109
// check validity
110
const valid = (CONF[key] ?? EXTRAS[key])?.valid;
111
if (valid != null) {
112
if (Array.isArray(valid) && !valid.includes(envval)) {
113
throw new Error(
114
`The value of $${envvar} is invalid. allowed are ${valid}.`,
115
);
116
} else if (typeof valid == "function" && !valid(envval)) {
117
throw new Error(
118
`The validation function rejected the value of $${envvar}.`,
119
);
120
}
121
}
122
123
await cb2(db.set_server_setting, {
124
name: key,
125
value: envval,
126
readonly: true,
127
});
128
}
129
}
130
}
131
132