UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

172 lines (153 loc) 4.85 kB
/* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ import { omit } from "lodash"; import { PostgreSQL } from "./types"; import { callback2 } from "smc-util/async-utils"; import { query } from "./query"; import debug from "debug"; const L = debug("hub:project-queries"); import { DUMMY_SECRET } from "smc-webapp/project/settings/const"; import { DatastoreConfig } from "smc-webapp/project/settings/types"; export async function project_has_network_access( db: PostgreSQL, project_id: string ): Promise<boolean> { let x; try { x = await callback2(db.get_project, { project_id, columns: ["users", "settings"], }); } catch (err) { // error probably means there is no such project or project_id is badly formatted. return false; } if (x.settings != null && x.settings.network) { return true; } if (x.users != null) { for (const account_id in x.users) { if ( x.users[account_id] != null && x.users[account_id].upgrades != null && x.users[account_id].upgrades.network ) { return true; } } } return false; } // get/set/del datastore configurations in addons interface GetDSOpts { db: PostgreSQL; account_id: string; project_id: string; } async function get_datastore( opts: GetDSOpts ): Promise<{ [key: string]: DatastoreConfig }> { const { db, account_id, project_id } = opts; const q: { users: any; addons?: any } = await query({ db, table: "projects", select: ["addons", "users"], where: { project_id }, one: true, }); // this access test is absolutely critial to have! (only project queries set access_check to false) if (q.users[account_id] == null) throw Error(`access denied`); return q.addons?.datastore; } export async function project_datastore_set( db: PostgreSQL, account_id: string, project_id: string, config: any ): Promise<void> { // L("project_datastore_set", config); if (config.name == null) throw Error("configuration 'name' is not defined"); if (typeof config.type !== "string") throw Error( "configuration 'type' is not defined (must be 'gcs', 'sshfs', ...)" ); // check data from user for (const [key, val] of Object.entries(config)) { if (typeof val !== "string" && typeof val !== "boolean") { throw new Error(`Invalid value -- '${key}' is not a valid type`); } if (typeof val === "string" && val.length > 100000) { throw new Error(`Invalid value -- '${key}' is too long`); } } const old_name = config.__old_name; const conf_new = omit(config, "name", "secret", "__old_name"); // this is implicitly a test if the user has access to modify this -- don't catch it const ds_prev = await get_datastore({ db, account_id, project_id }); // there is a situation where datastore is renamed, i.e. "name" is a new one, // while the previous secret is stored under a different key. So, if __old_name // is set, we pick that one instead. const prev_name = old_name != null ? old_name : config.name; // if a user wants to update the settings, they don't need to have the secret. // an empty value or the dummy text signals to keep the secret as it is... if ( ds_prev != null && ds_prev[prev_name] != null && (config.secret === DUMMY_SECRET || config.secret === "") ) { conf_new.secret = ds_prev[prev_name].secret; } else { conf_new.secret = Buffer.from(config.secret ?? "").toString("base64"); } await query({ db, query: "UPDATE projects", where: { "project_id = $::UUID": project_id }, jsonb_merge: { addons: { datastore: { [config.name]: conf_new } } }, }); } export async function project_datastore_del( db: PostgreSQL, account_id: string, project_id: string, name: string ): Promise<void> { L("project_datastore_del", name); if (typeof name !== "string" || name.length == 0) { throw Error("Datastore name not properly set."); } // this is implicitly a test if the user has access to modify this -- don't catch it const ds = await get_datastore({ db, account_id, project_id }); delete ds[name]; await query({ db, query: "UPDATE projects", where: { "project_id = $::UUID": project_id }, jsonb_set: { addons: { datastore: ds } }, }); } export async function project_datastore_get( db: PostgreSQL, account_id: string, project_id: string ): Promise<any> { try { const ds = await get_datastore({ db, account_id, project_id, }); if (ds != null) { for (const [k, v] of Object.entries(ds)) { ds[k] = omit(v, "secret") as any; } } return { addons: { datastore: ds }, }; } catch (err) { return { type: "error", error: `${err}` }; } }