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