-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathauthorization.server.ts
More file actions
113 lines (99 loc) · 3.92 KB
/
authorization.server.ts
File metadata and controls
113 lines (99 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
export type AuthorizationAction = "read" | "write" | string; // Add more actions as needed
const ResourceTypes = ["tasks", "tags", "runs", "batch", "waitpoints", "deployments", "inputStreams", "query"] as const;
export type AuthorizationResources = {
[key in (typeof ResourceTypes)[number]]?: string | string[];
};
export type AuthorizationEntity = {
type: "PUBLIC" | "PRIVATE" | "PUBLIC_JWT";
scopes?: string[];
};
/**
* Checks if the given entity is authorized to perform a specific action on a resource.
*
* @param entity - The entity requesting authorization.
* @param action - The action the entity wants to perform.
* @param resource - The resource on which the action is to be performed.
* @param superScopes - An array of super scopes that can bypass the normal authorization checks.
*
* @example
*
* ```typescript
* import { checkAuthorization } from "./authorization.server";
*
* const entity = {
* type: "PUBLIC",
* scope: ["read:runs:run_1234", "read:tasks"]
* };
*
* checkAuthorization(entity, "read", { runs: "run_1234" }); // Returns true
* checkAuthorization(entity, "read", { runs: "run_5678" }); // Returns false
* checkAuthorization(entity, "read", { tasks: "task_1234" }); // Returns true
* checkAuthorization(entity, "read", { tasks: ["task_5678"] }); // Returns true
* ```
*/
export type AuthorizationResult = { authorized: true } | { authorized: false; reason: string };
/**
* Checks if the given entity is authorized to perform a specific action on a resource.
*/
export function checkAuthorization(
entity: AuthorizationEntity,
action: AuthorizationAction,
resource: AuthorizationResources,
superScopes?: string[]
): AuthorizationResult {
// "PRIVATE" is a secret key and has access to everything
if (entity.type === "PRIVATE") {
return { authorized: true };
}
// "PUBLIC" is a deprecated key and has no access
if (entity.type === "PUBLIC") {
return { authorized: false, reason: "PUBLIC type is deprecated and has no access" };
}
// If the entity has no permissions, deny access
if (!entity.scopes || entity.scopes.length === 0) {
return {
authorized: false,
reason:
"Public Access Token has no permissions. See https://trigger.dev/docs/frontend/overview#authentication for more information.",
};
}
// If the resource object is empty, deny access
if (Object.keys(resource).length === 0) {
return { authorized: false, reason: "Resource object is empty" };
}
// Check for any of the super scopes
if (superScopes && superScopes.length > 0) {
if (superScopes.some((permission) => entity.scopes?.includes(permission))) {
return { authorized: true };
}
}
const filteredResource = Object.keys(resource).reduce((acc, key) => {
if (ResourceTypes.includes(key)) {
acc[key as keyof AuthorizationResources] = resource[key as keyof AuthorizationResources];
}
return acc;
}, {} as AuthorizationResources);
// Check each resource type
for (const [resourceType, resourceValue] of Object.entries(filteredResource)) {
const resourceValues = Array.isArray(resourceValue) ? resourceValue : [resourceValue];
for (const value of resourceValues) {
// Check for specific resource permission
const specificPermission = `${action}:${resourceType}:${value}`;
// Check for general resource type permission
const generalPermission = `${action}:${resourceType}`;
// If any permission matches, return authorized
if (entity.scopes.includes(specificPermission) || entity.scopes.includes(generalPermission)) {
return { authorized: true };
}
}
}
// No matching permissions found
return {
authorized: false,
reason: `Public Access Token is missing required permissions. Token has the following permissions: ${entity.scopes
.map((s) => `'${s}'`)
.join(
", "
)}. See https://trigger.dev/docs/frontend/overview#authentication for more information.`,
};
}