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/project-queries.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { omit } from "lodash";
7
import { PostgreSQL } from "./types";
8
import { callback2 } from "@cocalc/util/async-utils";
9
import { query } from "./query";
10
import debug from "debug";
11
const L = debug("hub:project-queries");
12
import { DUMMY_SECRET } from "@cocalc/util/consts";
13
import { DatastoreConfig } from "@cocalc/util/types";
14
15
export async function project_has_network_access(
16
db: PostgreSQL,
17
project_id: string
18
): Promise<boolean> {
19
let x;
20
try {
21
x = await callback2(db.get_project, {
22
project_id,
23
columns: ["users", "settings"],
24
});
25
} catch (err) {
26
// error probably means there is no such project or project_id is badly formatted.
27
return false;
28
}
29
if (x.settings != null && x.settings.network) {
30
return true;
31
}
32
if (x.users != null) {
33
for (const account_id in x.users) {
34
if (
35
x.users[account_id] != null &&
36
x.users[account_id].upgrades != null &&
37
x.users[account_id].upgrades.network
38
) {
39
return true;
40
}
41
}
42
}
43
return false;
44
}
45
46
// get/set/del datastore configurations in addons
47
48
interface GetDSOpts {
49
db: PostgreSQL;
50
account_id: string;
51
project_id: string;
52
}
53
54
async function get_datastore(
55
opts: GetDSOpts
56
): Promise<{ [key: string]: DatastoreConfig }> {
57
const { db, account_id, project_id } = opts;
58
const q: { users: any; addons?: any } = await query({
59
db,
60
table: "projects",
61
select: ["addons", "users"],
62
where: { project_id },
63
one: true,
64
});
65
66
// this access test is absolutely critial to have! (only project queries set access_check to false)
67
if (q.users[account_id] == null) throw Error(`access denied`);
68
69
return q.addons?.datastore;
70
}
71
72
export async function project_datastore_set(
73
db: PostgreSQL,
74
account_id: string,
75
project_id: string,
76
config: any
77
): Promise<void> {
78
// L("project_datastore_set", config);
79
80
if (config.name == null) throw Error("configuration 'name' is not defined");
81
if (typeof config.type !== "string")
82
throw Error(
83
"configuration 'type' is not defined (must be 'gcs', 'sshfs', ...)"
84
);
85
86
// check data from user
87
for (const [key, val] of Object.entries(config)) {
88
if (typeof val !== "string" && typeof val !== "boolean") {
89
throw new Error(`Invalid value -- '${key}' is not a valid type`);
90
}
91
if (typeof val === "string" && val.length > 100000) {
92
throw new Error(`Invalid value -- '${key}' is too long`);
93
}
94
}
95
96
const old_name = config.__old_name;
97
const conf_new = omit(config, "name", "secret", "__old_name");
98
99
// this is implicitly a test if the user has access to modify this -- don't catch it
100
const ds_prev = await get_datastore({ db, account_id, project_id });
101
102
// there is a situation where datastore is renamed, i.e. "name" is a new one,
103
// while the previous secret is stored under a different key. So, if __old_name
104
// is set, we pick that one instead.
105
const prev_name = old_name != null ? old_name : config.name;
106
107
// if a user wants to update the settings, they don't need to have the secret.
108
// an empty value or the dummy text signals to keep the secret as it is...
109
if (
110
ds_prev != null &&
111
ds_prev[prev_name] != null &&
112
(config.secret === DUMMY_SECRET || config.secret === "")
113
) {
114
conf_new.secret = ds_prev[prev_name].secret;
115
} else {
116
conf_new.secret = Buffer.from(config.secret ?? "").toString("base64");
117
}
118
119
await query({
120
db,
121
query: "UPDATE projects",
122
where: { "project_id = $::UUID": project_id },
123
jsonb_merge: { addons: { datastore: { [config.name]: conf_new } } },
124
});
125
}
126
127
export async function project_datastore_del(
128
db: PostgreSQL,
129
account_id: string,
130
project_id: string,
131
name: string
132
): Promise<void> {
133
L("project_datastore_del", name);
134
if (typeof name !== "string" || name.length == 0) {
135
throw Error("Datastore name not properly set.");
136
}
137
138
// this is implicitly a test if the user has access to modify this -- don't catch it
139
const ds = await get_datastore({ db, account_id, project_id });
140
delete ds[name];
141
await query({
142
db,
143
query: "UPDATE projects",
144
where: { "project_id = $::UUID": project_id },
145
jsonb_set: { addons: { datastore: ds } },
146
});
147
}
148
149
export async function project_datastore_get(
150
db: PostgreSQL,
151
account_id: string,
152
project_id: string
153
): Promise<any> {
154
try {
155
const ds = await get_datastore({
156
db,
157
account_id,
158
project_id,
159
});
160
if (ds != null) {
161
for (const [k, v] of Object.entries(ds)) {
162
ds[k] = omit(v, "secret") as any;
163
}
164
}
165
return {
166
addons: { datastore: ds },
167
};
168
} catch (err) {
169
return { type: "error", error: `${err}` };
170
}
171
}
172
173