Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/conat/persist/auth.ts
1710 views
1
import { SERVICE } from "./util";
2
import { ConatError } from "@cocalc/conat/core/client";
3
import { normalize } from "path";
4
5
export const MAX_PATH_LENGTH = 4000;
6
7
export function getUserId(subject: string, service = SERVICE): string {
8
if (
9
subject.startsWith(`${service}.account-`) ||
10
subject.startsWith(`${service}.project-`)
11
) {
12
// note that project and account have the same number of letters
13
return subject.slice(
14
`${service}.account-`.length,
15
`${service}.account-`.length + 36,
16
);
17
}
18
return "";
19
}
20
21
export function assertHasWritePermission({
22
subject,
23
path,
24
service = SERVICE,
25
}: {
26
// Subject definitely has one of the following forms, or we would never
27
// see this message:
28
// ${service}.account-${account_id}.> or
29
// ${service}.project-${project_id}.> or
30
// ${service}.hub.>
31
// ${service}.SOMETHING-WRONG
32
// A user is only allowed to write to a subject if they have rights
33
// to the given project, account or are a hub.
34
// The path can a priori be any string. However, here's what's allowed
35
// accounts/[account_id]/any...thing
36
// projects/[project_id]/any...thing
37
// hub/any...thing <- only hub can write to this.
38
// Also, we don't allow malicious paths, which means by definition that
39
// normalize(path) != path.
40
// This is to avoid accidentally writing a file to different project, which
41
// would be very bad.
42
subject: string;
43
path: string;
44
service?: string;
45
}) {
46
if (path != normalize(path)) {
47
throw Error(`permission denied: path '${path}' is not normalized`);
48
}
49
if (path.length > MAX_PATH_LENGTH) {
50
throw new ConatError(
51
`permission denied: path (of length ${path.length}) is too long (limit is '${MAX_PATH_LENGTH}' characters)`,
52
{ code: 403 },
53
);
54
}
55
if (path.startsWith("/") || path.endsWith("/")) {
56
throw new ConatError(
57
`permission denied: path '${path}' must not start or end with '/'`,
58
{ code: 403 },
59
);
60
}
61
const v = subject.split(".");
62
if (v[0] != service) {
63
throw Error(
64
`bug -- first segment of subject must be '${service}' -- subject='${subject}'`,
65
);
66
}
67
const s = v[1];
68
if (s == "hub") {
69
// hub user can write to any path
70
return;
71
}
72
for (const cls of ["account", "project"]) {
73
if (s.startsWith(cls + "-")) {
74
const user_id = getUserId(subject, service);
75
const base = cls + "s/" + user_id + "/";
76
if (path.startsWith(base)) {
77
// permissions granted
78
return;
79
} else {
80
throw new ConatError(
81
`permission denied: subject '${subject}' does not grant write permission to path='${path}' since it is not under '${base}'`,
82
{ code: 403 },
83
);
84
}
85
}
86
}
87
throw new ConatError(`invalid subject: '${subject}'`, { code: 403 });
88
}
89
90