/
authorization.ts
242 lines (206 loc) · 7.92 KB
/
authorization.ts
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import { getRemoteDataAsync, localText, notifyError } from "../base";
import { getRemoteData } from "./scriptdata-compat";
import { UserDefinition } from "./userdefinition";
const andOrRegex = /[|&]/;
/**
* Contains permission related functions.
*
* ## Note
* We use a namespace here both for compatibility and for allowing users to override
* these functions easily in ES modules environment, which is normally hard to do.
*/
export namespace Authorization {
/**
* Checks if the current user has the permission specified.
* This should only be used for UI purposes and it is strongly recommended to check permissions server side.
*
* > Please prefer the `hasPermissionAsync` variant as this may block the UI thread if the `UserData` script is not already loaded.
* @param permission Permission key. It may contain logical operators like A&B|C.
* @returns `false` for "null or undefined", true for "*", `IsLoggedIn` for "?". For other permissions,
* if the user has the permission or if the user has the `IsAdmin` flag (super admin) `true`, otherwise `false`.
*/
export function hasPermission(permission: string) {
if (permission == null)
return false;
if (permission == "*")
return true;
// normally in server side empty permission would return false
// here we are more tolerant for compatibility reasons and
// as it is less risky
if (permission == "" || permission == "?")
return !!Authorization.isLoggedIn;
var ud = Authorization.userDefinition;
if (!ud)
return false;
if (ud.IsAdmin)
return true;
return isPermissionInSet(ud.Permissions, permission);
}
/**
* Checks if the current user has the permission specified.
* This should only be used for UI purposes and it is strongly recommended to check permissions server side.
*
* @param permission Permission key. It may contain logical operators like A&B|C.
* @returns `false` for "null or undefined", true for "*", `IsLoggedIn` for "?". For other permissions,
* if the user has the permission or if the user has the `IsAdmin` flag (super admin) `true`, otherwise `false`.
*/
export async function hasPermissionAsync(permission: string): Promise<boolean> {
if (permission == null)
return false;
if (permission == "*")
return true;
if (permission == "" || permission == "?")
return await Authorization.isLoggedInAsync;
var ud = await Authorization.userDefinitionAsync;
if (!ud)
return false;
if (ud.IsAdmin)
return true;
return isPermissionInSet(ud.Permissions, permission);
}
/**
* Checks if the hashset contains the specified permission, also handling logical "|" and "&" operators
* @param permissionSet Set of permissions
* @param permission Permission key or a permission expression containing & | operators
* @returns true if set contains permission
*/
export function isPermissionInSet(permissionSet: { [key: string]: boolean }, permission: string) {
if (!permissionSet || permission == null)
return false;
if (permissionSet[permission])
return true;
if (!andOrRegex.test(permission))
return false;
var orParts = permission.split('|');
for (var r of orParts) {
if (!r.length)
continue;
var andParts = r.split('&');
let anyFalse = false;
for (var n of andParts) {
if (!n || !permissionSet[n]) {
anyFalse = true;
break;
}
}
if (!anyFalse)
return true;
}
return false;
}
/**
* Throws an error if the current user does not have the specified permission.
* Prefer `await validatePermissionAsync()` as this one might block the UI if the `UserData`
* is not already loaded.
* @param permission Permission key. It may contain logical operators like A&B|C.
*/
export function validatePermission(permission: string) {
if (!hasPermission(permission)) {
notifyError(localText("Authorization.AccessDenied"));
throw new Error(localText("Authorization.AccessDenied"));
}
}
/**
* Throws an error if the current user does not have the specified permission.
* @param permission Permission key. It may contain logical operators like A&B|C.
* @example
* await Authorization.validatePermissionAsync("A&B|C");
*/
export async function validatePermissionAsync(permission: string): Promise<void> {
if (!(await hasPermissionAsync(permission))) {
notifyError(localText("Authorization.AccessDenied"));
throw new Error(localText("Authorization.AccessDenied"));
}
}
}
export declare namespace Authorization {
/**
* Checks if the current user is logged in. Prefer `isLoggedInAsync` as this one might block the UI if the `UserData`
* is not already loaded.
* @returns `true` if the user is logged in, `false` otherwise.
* @example
* if (Authorization.isLoggedIn) {
* // do something
* }
*/
export let isLoggedIn: boolean;
/**
* Checks if the current user is logged in.
* @returns `true` if the user is logged in, `false` otherwise.
* @example
* if (await Authorization.isLoggedInAsync) {
* // do something
* }
*/
export let isLoggedInAsync: Promise<boolean>;
/** Returns the username for currently logged user. Prefer `usernameAsync` as this one might block the UI if the `UserData`
* is not already loaded.
* @returns Username for currently logged user.
* @example
* if (Authorization.username) {
* // do something
* }
*/
export let username: string;
/** Returns the username for currently logged user.
* @returns Username for currently logged user.
* @example
* if (await Authorization.usernameAsync) {
* // do something
* }
*/
export let usernameAsync: Promise<string>;
/** Returns the user data for currently logged user. Prefer `userDefinitionAsync` as this one might block the UI if the `UserData`
* is not already loaded.
* @returns User data for currently logged user.
* @example
* if (Authorization.userDefinition.IsAdmin) {
* // do something
* }
*/
export let userDefinition: UserDefinition;
/** Returns the user data for currently logged user.
* @returns User data for currently logged user.
* @example
* if ((await Authorization.userDefinitionAsync).IsAdmin) {
* // do something
* }
*/
export let userDefinitionAsync: Promise<UserDefinition>;
}
Object.defineProperty(Authorization, "isLoggedIn", {
get: function () {
return !!(Authorization.userDefinition?.Username);
},
configurable: true
});
Object.defineProperty(Authorization, "isLoggedInAsync", {
get: async function () {
return !!((await Authorization.userDefinitionAsync)?.Username);
},
configurable: true
});
Object.defineProperty(Authorization, "userDefinition", {
get: function () {
return getRemoteData<UserDefinition>("UserData");
},
configurable: true
});
Object.defineProperty(Authorization, "userDefinitionAsync", {
get: async function () {
return await getRemoteDataAsync<UserDefinition>("UserData");
},
configurable: true
});
Object.defineProperty(Authorization, "username", {
get: function () {
return Authorization.userDefinition?.Username;
},
configurable: true
});
Object.defineProperty(Authorization, "usernameAsync", {
get: async function () {
return (await Authorization.userDefinitionAsync)?.Username;
},
configurable: true
});