wabe 0.6.9 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -32
- package/bucket/b.txt +1 -0
- package/dev/index.ts +215 -0
- package/dist/authentication/Session.d.ts +4 -1
- package/dist/authentication/interface.d.ts +16 -0
- package/dist/email/interface.d.ts +1 -1
- package/dist/graphql/resolvers.d.ts +4 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +8713 -8867
- package/dist/server/index.d.ts +4 -2
- package/dist/utils/crypto.d.ts +7 -0
- package/dist/utils/helper.d.ts +4 -1
- package/generated/schema.graphql +16 -14
- package/generated/wabe.ts +4 -4
- package/package.json +15 -15
- package/src/authentication/OTP.test.ts +69 -0
- package/src/authentication/OTP.ts +66 -0
- package/src/authentication/Session.test.ts +665 -0
- package/src/authentication/Session.ts +529 -0
- package/src/authentication/defaultAuthentication.ts +214 -0
- package/src/authentication/index.ts +3 -0
- package/src/authentication/interface.ts +157 -0
- package/src/authentication/oauth/GitHub.test.ts +105 -0
- package/src/authentication/oauth/GitHub.ts +133 -0
- package/src/authentication/oauth/Google.test.ts +105 -0
- package/src/authentication/oauth/Google.ts +110 -0
- package/src/authentication/oauth/Oauth2Client.test.ts +225 -0
- package/src/authentication/oauth/Oauth2Client.ts +140 -0
- package/src/authentication/oauth/index.ts +2 -0
- package/src/authentication/oauth/utils.test.ts +35 -0
- package/src/authentication/oauth/utils.ts +28 -0
- package/src/authentication/providers/EmailOTP.test.ts +138 -0
- package/src/authentication/providers/EmailOTP.ts +93 -0
- package/src/authentication/providers/EmailPassword.test.ts +187 -0
- package/src/authentication/providers/EmailPassword.ts +130 -0
- package/src/authentication/providers/EmailPasswordSRP.test.ts +206 -0
- package/src/authentication/providers/EmailPasswordSRP.ts +184 -0
- package/src/authentication/providers/GitHub.ts +30 -0
- package/src/authentication/providers/Google.ts +30 -0
- package/src/authentication/providers/OAuth.test.ts +185 -0
- package/src/authentication/providers/OAuth.ts +112 -0
- package/src/authentication/providers/PhonePassword.test.ts +187 -0
- package/src/authentication/providers/PhonePassword.ts +129 -0
- package/src/authentication/providers/QRCodeOTP.test.ts +79 -0
- package/src/authentication/providers/QRCodeOTP.ts +65 -0
- package/src/authentication/providers/index.ts +6 -0
- package/src/authentication/resolvers/refreshResolver.test.ts +37 -0
- package/src/authentication/resolvers/refreshResolver.ts +20 -0
- package/src/authentication/resolvers/signInWithResolver.inte.test.ts +59 -0
- package/src/authentication/resolvers/signInWithResolver.test.ts +307 -0
- package/src/authentication/resolvers/signInWithResolver.ts +102 -0
- package/src/authentication/resolvers/signOutResolver.test.ts +41 -0
- package/src/authentication/resolvers/signOutResolver.ts +22 -0
- package/src/authentication/resolvers/signUpWithResolver.test.ts +186 -0
- package/src/authentication/resolvers/signUpWithResolver.ts +69 -0
- package/src/authentication/resolvers/verifyChallenge.test.ts +136 -0
- package/src/authentication/resolvers/verifyChallenge.ts +69 -0
- package/src/authentication/roles.test.ts +59 -0
- package/src/authentication/roles.ts +40 -0
- package/src/authentication/utils.test.ts +99 -0
- package/src/authentication/utils.ts +43 -0
- package/src/cache/InMemoryCache.test.ts +62 -0
- package/src/cache/InMemoryCache.ts +45 -0
- package/src/cron/index.test.ts +17 -0
- package/src/cron/index.ts +46 -0
- package/src/database/DatabaseController.test.ts +625 -0
- package/src/database/DatabaseController.ts +983 -0
- package/src/database/index.test.ts +1230 -0
- package/src/database/index.ts +9 -0
- package/src/database/interface.ts +312 -0
- package/src/email/DevAdapter.ts +8 -0
- package/src/email/EmailController.test.ts +29 -0
- package/src/email/EmailController.ts +13 -0
- package/src/email/index.ts +2 -0
- package/src/email/interface.ts +36 -0
- package/src/email/templates/sendOtpCode.ts +120 -0
- package/src/file/FileController.ts +28 -0
- package/src/file/FileDevAdapter.ts +54 -0
- package/src/file/hookDeleteFile.ts +27 -0
- package/src/file/hookReadFile.ts +70 -0
- package/src/file/hookUploadFile.ts +53 -0
- package/src/file/index.test.ts +979 -0
- package/src/file/index.ts +2 -0
- package/src/file/interface.ts +42 -0
- package/src/graphql/GraphQLSchema.test.ts +4399 -0
- package/src/graphql/GraphQLSchema.ts +928 -0
- package/src/graphql/index.ts +2 -0
- package/src/graphql/parseGraphqlSchema.ts +94 -0
- package/src/graphql/parser.test.ts +217 -0
- package/src/graphql/parser.ts +566 -0
- package/src/graphql/pointerAndRelationFunction.ts +200 -0
- package/src/graphql/resolvers.ts +467 -0
- package/src/graphql/tests/aggregation.test.ts +1123 -0
- package/src/graphql/tests/e2e.test.ts +596 -0
- package/src/graphql/tests/scalars.test.ts +250 -0
- package/src/graphql/types.ts +219 -0
- package/src/hooks/HookObject.test.ts +122 -0
- package/src/hooks/HookObject.ts +168 -0
- package/src/hooks/authentication.ts +76 -0
- package/src/hooks/createUser.test.ts +77 -0
- package/src/hooks/createUser.ts +10 -0
- package/src/hooks/defaultFields.test.ts +187 -0
- package/src/hooks/defaultFields.ts +40 -0
- package/src/hooks/deleteSession.test.ts +181 -0
- package/src/hooks/deleteSession.ts +20 -0
- package/src/hooks/hashFieldHook.test.ts +163 -0
- package/src/hooks/hashFieldHook.ts +97 -0
- package/src/hooks/index.test.ts +207 -0
- package/src/hooks/index.ts +430 -0
- package/src/hooks/permissions.test.ts +424 -0
- package/src/hooks/permissions.ts +113 -0
- package/src/hooks/protected.test.ts +551 -0
- package/src/hooks/protected.ts +72 -0
- package/src/hooks/searchableFields.test.ts +166 -0
- package/src/hooks/searchableFields.ts +98 -0
- package/src/hooks/session.test.ts +138 -0
- package/src/hooks/session.ts +78 -0
- package/src/hooks/setEmail.test.ts +216 -0
- package/src/hooks/setEmail.ts +35 -0
- package/src/hooks/setupAcl.test.ts +589 -0
- package/src/hooks/setupAcl.ts +29 -0
- package/src/index.ts +9 -0
- package/src/schema/Schema.test.ts +484 -0
- package/src/schema/Schema.ts +795 -0
- package/src/schema/defaultResolvers.ts +94 -0
- package/src/schema/index.ts +1 -0
- package/src/schema/resolvers/meResolver.test.ts +62 -0
- package/src/schema/resolvers/meResolver.ts +14 -0
- package/src/schema/resolvers/newFile.ts +0 -0
- package/src/schema/resolvers/resetPassword.test.ts +345 -0
- package/src/schema/resolvers/resetPassword.ts +64 -0
- package/src/schema/resolvers/sendEmail.test.ts +118 -0
- package/src/schema/resolvers/sendEmail.ts +21 -0
- package/src/schema/resolvers/sendOtpCode.test.ts +153 -0
- package/src/schema/resolvers/sendOtpCode.ts +52 -0
- package/src/security.test.ts +3461 -0
- package/src/server/defaultSessionHandler.test.ts +66 -0
- package/src/server/defaultSessionHandler.ts +115 -0
- package/src/server/generateCodegen.ts +476 -0
- package/src/server/index.test.ts +552 -0
- package/src/server/index.ts +354 -0
- package/src/server/interface.ts +11 -0
- package/src/server/routes/authHandler.ts +187 -0
- package/src/server/routes/index.ts +40 -0
- package/src/utils/crypto.test.ts +41 -0
- package/src/utils/crypto.ts +121 -0
- package/src/utils/export.ts +13 -0
- package/src/utils/helper.ts +195 -0
- package/src/utils/index.test.ts +11 -0
- package/src/utils/index.ts +201 -0
- package/src/utils/preload.ts +8 -0
- package/src/utils/testHelper.ts +117 -0
- package/tsconfig.json +32 -0
- package/bunfig.toml +0 -4
- package/dist/ai/index.d.ts +0 -1
- package/dist/ai/interface.d.ts +0 -9
- /package/dist/server/{defaultHandlers.d.ts → defaultSessionHandler.d.ts} +0 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach, mock, spyOn } from 'bun:test'
|
|
2
|
+
import { _getPermissionPropertiesOfAClass, _checkCLP } from './permissions'
|
|
3
|
+
import { HookObject } from './HookObject'
|
|
4
|
+
import { OperationType } from '.'
|
|
5
|
+
import type { WabeContext } from '../server/interface'
|
|
6
|
+
import * as permissions from './permissions'
|
|
7
|
+
import { RoleEnum } from '../../generated/wabe'
|
|
8
|
+
|
|
9
|
+
describe('Permissions', () => {
|
|
10
|
+
describe('Class Level Permissions', () => {
|
|
11
|
+
const mockGetObject = mock(() => {})
|
|
12
|
+
|
|
13
|
+
const controllers = {
|
|
14
|
+
database: {
|
|
15
|
+
getObject: mockGetObject,
|
|
16
|
+
},
|
|
17
|
+
} as any
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
mockGetObject.mockClear()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const config = {
|
|
24
|
+
schema: {
|
|
25
|
+
classes: [
|
|
26
|
+
{
|
|
27
|
+
name: 'TestClass',
|
|
28
|
+
fields: {
|
|
29
|
+
field1: { type: 'String' },
|
|
30
|
+
},
|
|
31
|
+
permissions: {
|
|
32
|
+
read: {
|
|
33
|
+
requireAuthentication: true,
|
|
34
|
+
authorizedRoles: [RoleEnum.Admin],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'TestClass2',
|
|
40
|
+
fields: {
|
|
41
|
+
field2: { type: 'String' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'TestClass3',
|
|
46
|
+
fields: {
|
|
47
|
+
field2: { type: 'String' },
|
|
48
|
+
},
|
|
49
|
+
permissions: {
|
|
50
|
+
read: {
|
|
51
|
+
requireAuthentication: true,
|
|
52
|
+
authorizedRoles: ['everyone'],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'TestClass4',
|
|
58
|
+
fields: {
|
|
59
|
+
field2: { type: 'String' },
|
|
60
|
+
},
|
|
61
|
+
permissions: {
|
|
62
|
+
read: {
|
|
63
|
+
requireAuthentication: true,
|
|
64
|
+
authorizedRoles: [],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'TestClass5',
|
|
70
|
+
fields: {
|
|
71
|
+
field2: { type: 'String' },
|
|
72
|
+
},
|
|
73
|
+
permissions: {},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
} as any
|
|
78
|
+
|
|
79
|
+
const context = { wabe: { config } } as any
|
|
80
|
+
|
|
81
|
+
it('should throw an error if authorized roles is empty and the user is not root', () => {
|
|
82
|
+
mockGetObject.mockResolvedValue({
|
|
83
|
+
id: 'sessionId',
|
|
84
|
+
user: { id: 'userId' },
|
|
85
|
+
} as never)
|
|
86
|
+
|
|
87
|
+
const context: WabeContext<any> = {
|
|
88
|
+
sessionId: 'sessionId',
|
|
89
|
+
user: {
|
|
90
|
+
id: 'userId',
|
|
91
|
+
role: {
|
|
92
|
+
id: 'roleId',
|
|
93
|
+
name: 'Admin',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
isRoot: false,
|
|
97
|
+
wabe: { controllers, config } as any,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const obj = new HookObject({
|
|
101
|
+
className: 'TestClass4',
|
|
102
|
+
context,
|
|
103
|
+
object: {
|
|
104
|
+
id: 'id',
|
|
105
|
+
},
|
|
106
|
+
operationType: OperationType.BeforeRead,
|
|
107
|
+
select: {},
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(_checkCLP(obj, OperationType.BeforeRead)).rejects.toThrow(
|
|
111
|
+
'Permission denied to read class TestClass4',
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should throw an error if operation is undefined and user is not root', () => {
|
|
116
|
+
mockGetObject.mockResolvedValue({
|
|
117
|
+
id: 'sessionId',
|
|
118
|
+
user: { id: 'userId' },
|
|
119
|
+
} as never)
|
|
120
|
+
|
|
121
|
+
const context: WabeContext<any> = {
|
|
122
|
+
sessionId: 'sessionId',
|
|
123
|
+
user: {
|
|
124
|
+
id: 'userId',
|
|
125
|
+
role: {
|
|
126
|
+
id: 'roleId',
|
|
127
|
+
name: 'Admin',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
isRoot: false,
|
|
131
|
+
wabe: { controllers, config } as any,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const obj = new HookObject({
|
|
135
|
+
className: 'TestClass5',
|
|
136
|
+
context,
|
|
137
|
+
object: {
|
|
138
|
+
id: 'id',
|
|
139
|
+
},
|
|
140
|
+
operationType: OperationType.BeforeRead,
|
|
141
|
+
select: {},
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
expect(_checkCLP(obj, OperationType.BeforeRead)).rejects.toThrow(
|
|
145
|
+
'Permission denied to read class TestClass5',
|
|
146
|
+
)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should get the permission for a given className', async () => {
|
|
150
|
+
const permission = await _getPermissionPropertiesOfAClass({
|
|
151
|
+
className: 'TestClass',
|
|
152
|
+
operation: 'read',
|
|
153
|
+
context,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
expect(permission).toEqual({
|
|
157
|
+
requireAuthentication: true,
|
|
158
|
+
authorizedRoles: [RoleEnum.Admin],
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const permission2 = await _getPermissionPropertiesOfAClass({
|
|
162
|
+
className: 'TestClass2',
|
|
163
|
+
operation: 'read',
|
|
164
|
+
context,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
expect(permission2).toBeUndefined()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('should throw permission denied if no session id is provided but class require authentication', () => {
|
|
171
|
+
const context: WabeContext<any> = {
|
|
172
|
+
sessionId: '',
|
|
173
|
+
user: {},
|
|
174
|
+
isRoot: false,
|
|
175
|
+
wabe: { controllers, config } as any,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const obj = new HookObject({
|
|
179
|
+
className: 'TestClass',
|
|
180
|
+
context,
|
|
181
|
+
object: {
|
|
182
|
+
id: 'id',
|
|
183
|
+
},
|
|
184
|
+
operationType: OperationType.BeforeRead,
|
|
185
|
+
select: {},
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
expect(_checkCLP(obj, OperationType.BeforeRead)).rejects.toThrow(
|
|
189
|
+
'Permission denied to read class TestClass',
|
|
190
|
+
)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should not throw permission denied if authorized roles is everyone', () => {
|
|
194
|
+
mockGetObject.mockResolvedValue({
|
|
195
|
+
id: 'sessionId',
|
|
196
|
+
user: { id: 'userId' },
|
|
197
|
+
} as never)
|
|
198
|
+
|
|
199
|
+
const context: WabeContext<any> = {
|
|
200
|
+
sessionId: 'sessionId',
|
|
201
|
+
user: {
|
|
202
|
+
id: 'userId',
|
|
203
|
+
role: {
|
|
204
|
+
id: 'roleId',
|
|
205
|
+
name: 'Role',
|
|
206
|
+
} as any,
|
|
207
|
+
} as any,
|
|
208
|
+
isRoot: false,
|
|
209
|
+
wabe: {
|
|
210
|
+
controllers,
|
|
211
|
+
config,
|
|
212
|
+
} as any,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const obj = new HookObject({
|
|
216
|
+
className: 'TestClass3',
|
|
217
|
+
context,
|
|
218
|
+
object: {
|
|
219
|
+
id: 'id',
|
|
220
|
+
},
|
|
221
|
+
operationType: OperationType.BeforeRead,
|
|
222
|
+
select: {},
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
expect(_checkCLP(obj, OperationType.BeforeRead)).resolves
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should throw permission denied if authorized roles is everyone but requireAuthentication is true and client is anonymous', () => {
|
|
229
|
+
mockGetObject.mockResolvedValue({
|
|
230
|
+
id: 'sessionId',
|
|
231
|
+
user: { id: 'userId' },
|
|
232
|
+
} as never)
|
|
233
|
+
|
|
234
|
+
const context: WabeContext<any> = {
|
|
235
|
+
sessionId: undefined,
|
|
236
|
+
user: undefined,
|
|
237
|
+
isRoot: false,
|
|
238
|
+
wabe: {
|
|
239
|
+
controllers,
|
|
240
|
+
config,
|
|
241
|
+
} as any,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const obj = new HookObject({
|
|
245
|
+
className: 'TestClass3',
|
|
246
|
+
context,
|
|
247
|
+
object: {
|
|
248
|
+
id: 'id',
|
|
249
|
+
},
|
|
250
|
+
operationType: OperationType.BeforeRead,
|
|
251
|
+
select: {},
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
expect(_checkCLP(obj, OperationType.BeforeRead)).rejects.toThrow(
|
|
255
|
+
'Permission denied to read class TestClass3',
|
|
256
|
+
)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should throw permission denied if role is not an authorized role', () => {
|
|
260
|
+
mockGetObject.mockResolvedValue({
|
|
261
|
+
id: 'sessionId',
|
|
262
|
+
user: { id: 'userId' },
|
|
263
|
+
} as never)
|
|
264
|
+
|
|
265
|
+
const context: WabeContext<any> = {
|
|
266
|
+
sessionId: 'sessionId',
|
|
267
|
+
user: {
|
|
268
|
+
id: 'userId',
|
|
269
|
+
role: {
|
|
270
|
+
id: 'roleId',
|
|
271
|
+
name: 'Role',
|
|
272
|
+
} as any,
|
|
273
|
+
} as any,
|
|
274
|
+
isRoot: false,
|
|
275
|
+
wabe: {
|
|
276
|
+
controllers,
|
|
277
|
+
config,
|
|
278
|
+
} as any,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const obj = new HookObject({
|
|
282
|
+
className: 'TestClass',
|
|
283
|
+
context,
|
|
284
|
+
object: {
|
|
285
|
+
id: 'id',
|
|
286
|
+
},
|
|
287
|
+
operationType: OperationType.BeforeRead,
|
|
288
|
+
select: {},
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
expect(_checkCLP(obj, OperationType.BeforeRead)).rejects.toThrow(
|
|
292
|
+
'Permission denied to read class TestClass',
|
|
293
|
+
)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('should not throw permission denied if valid session id is provided', () => {
|
|
297
|
+
mockGetObject.mockResolvedValue({
|
|
298
|
+
id: 'sessionId',
|
|
299
|
+
user: { id: 'userId' },
|
|
300
|
+
} as never)
|
|
301
|
+
|
|
302
|
+
const context: WabeContext<any> = {
|
|
303
|
+
sessionId: 'sessionId',
|
|
304
|
+
user: {
|
|
305
|
+
id: 'userId',
|
|
306
|
+
role: {
|
|
307
|
+
id: 'roleId',
|
|
308
|
+
name: 'Admin',
|
|
309
|
+
} as any,
|
|
310
|
+
} as any,
|
|
311
|
+
isRoot: false,
|
|
312
|
+
wabe: {
|
|
313
|
+
controllers,
|
|
314
|
+
config,
|
|
315
|
+
} as any,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const obj = new HookObject({
|
|
319
|
+
className: 'TestClass',
|
|
320
|
+
context,
|
|
321
|
+
object: { id: 'id' },
|
|
322
|
+
operationType: OperationType.BeforeRead,
|
|
323
|
+
select: {},
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
expect(_checkCLP(obj, OperationType.BeforeRead)).resolves
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('should not throw permission denied if client is root', () => {
|
|
330
|
+
const context: WabeContext<any> = {
|
|
331
|
+
sessionId: '',
|
|
332
|
+
user: {
|
|
333
|
+
id: '',
|
|
334
|
+
} as any,
|
|
335
|
+
isRoot: true,
|
|
336
|
+
wabe: { controllers, config } as any,
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const obj = new HookObject({
|
|
340
|
+
className: 'TestClass',
|
|
341
|
+
context,
|
|
342
|
+
object: { id: 'id' },
|
|
343
|
+
operationType: OperationType.BeforeRead,
|
|
344
|
+
select: {},
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
expect(_checkCLP(obj, OperationType.BeforeRead)).resolves
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('should call _checkPermission on beforeRead', () => {
|
|
351
|
+
const spyBeforeRead = spyOn(
|
|
352
|
+
permissions,
|
|
353
|
+
'defaultCheckPermissionOnRead',
|
|
354
|
+
).mockResolvedValue()
|
|
355
|
+
|
|
356
|
+
permissions.defaultCheckPermissionOnRead({} as never)
|
|
357
|
+
|
|
358
|
+
expect(spyBeforeRead).toHaveBeenCalledTimes(1)
|
|
359
|
+
expect(spyBeforeRead).toHaveBeenCalledWith({})
|
|
360
|
+
|
|
361
|
+
spyBeforeRead.mockRestore()
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('should call _checkPermission on beforeCreate', () => {
|
|
365
|
+
const spyBeforeCreate = spyOn(
|
|
366
|
+
permissions,
|
|
367
|
+
'defaultCheckPermissionOnCreate',
|
|
368
|
+
).mockResolvedValue()
|
|
369
|
+
|
|
370
|
+
permissions.defaultCheckPermissionOnCreate({
|
|
371
|
+
sessionId: 'sessionId',
|
|
372
|
+
user: { id: 'userId' },
|
|
373
|
+
} as never)
|
|
374
|
+
|
|
375
|
+
expect(spyBeforeCreate).toHaveBeenCalledTimes(1)
|
|
376
|
+
expect(spyBeforeCreate).toHaveBeenCalledWith({
|
|
377
|
+
sessionId: 'sessionId',
|
|
378
|
+
user: { id: 'userId' },
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
spyBeforeCreate.mockRestore()
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('should call _checkPermission on beforeUpdate', () => {
|
|
385
|
+
const spyBeforeUpdate = spyOn(
|
|
386
|
+
permissions,
|
|
387
|
+
'defaultCheckPermissionOnUpdate',
|
|
388
|
+
).mockResolvedValue()
|
|
389
|
+
|
|
390
|
+
permissions.defaultCheckPermissionOnUpdate({
|
|
391
|
+
sessionId: 'sessionId',
|
|
392
|
+
user: { id: 'userId' },
|
|
393
|
+
} as never)
|
|
394
|
+
|
|
395
|
+
expect(spyBeforeUpdate).toHaveBeenCalledTimes(1)
|
|
396
|
+
expect(spyBeforeUpdate).toHaveBeenCalledWith({
|
|
397
|
+
sessionId: 'sessionId',
|
|
398
|
+
user: { id: 'userId' },
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
spyBeforeUpdate.mockRestore()
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('should call _checkPermission on beforeDelete', () => {
|
|
405
|
+
const spyBeforeDelete = spyOn(
|
|
406
|
+
permissions,
|
|
407
|
+
'defaultCheckPermissionOnDelete',
|
|
408
|
+
).mockResolvedValue()
|
|
409
|
+
|
|
410
|
+
permissions.defaultCheckPermissionOnDelete({
|
|
411
|
+
sessionId: 'sessionId',
|
|
412
|
+
user: { id: 'userId' },
|
|
413
|
+
} as never)
|
|
414
|
+
|
|
415
|
+
expect(spyBeforeDelete).toHaveBeenCalledTimes(1)
|
|
416
|
+
expect(spyBeforeDelete).toHaveBeenCalledWith({
|
|
417
|
+
sessionId: 'sessionId',
|
|
418
|
+
user: { id: 'userId' },
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
spyBeforeDelete.mockRestore()
|
|
422
|
+
})
|
|
423
|
+
})
|
|
424
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { PermissionsOperations } from '../schema'
|
|
2
|
+
import type { WabeContext } from '../server/interface'
|
|
3
|
+
import type { HookObject } from './HookObject'
|
|
4
|
+
import { OperationType } from './index'
|
|
5
|
+
|
|
6
|
+
const convertOperationTypeToPermission = (operationType: OperationType) => {
|
|
7
|
+
const template: Record<OperationType, PermissionsOperations> = {
|
|
8
|
+
[OperationType.BeforeCreate]: 'create',
|
|
9
|
+
[OperationType.AfterCreate]: 'create',
|
|
10
|
+
[OperationType.BeforeRead]: 'read',
|
|
11
|
+
[OperationType.AfterRead]: 'read',
|
|
12
|
+
[OperationType.BeforeDelete]: 'delete',
|
|
13
|
+
[OperationType.AfterDelete]: 'delete',
|
|
14
|
+
[OperationType.BeforeUpdate]: 'update',
|
|
15
|
+
[OperationType.AfterUpdate]: 'update',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return template[operationType]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const _getPermissionPropertiesOfAClass = ({
|
|
22
|
+
className,
|
|
23
|
+
operation,
|
|
24
|
+
context,
|
|
25
|
+
}: {
|
|
26
|
+
className: string
|
|
27
|
+
operation: PermissionsOperations
|
|
28
|
+
context: WabeContext<any>
|
|
29
|
+
}) => {
|
|
30
|
+
const wabeClass = context.wabe.config.schema?.classes?.find(
|
|
31
|
+
(c) => c.name === className,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if (!wabeClass) throw new Error(`Class ${className} not found in schema`)
|
|
35
|
+
|
|
36
|
+
const permission = wabeClass.permissions?.[operation]
|
|
37
|
+
|
|
38
|
+
return permission
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const _checkCLP = async (
|
|
42
|
+
object: HookObject<any, any>,
|
|
43
|
+
operationType: OperationType,
|
|
44
|
+
) => {
|
|
45
|
+
if (object.context.isRoot) return
|
|
46
|
+
|
|
47
|
+
const permissionOperation = convertOperationTypeToPermission(operationType)
|
|
48
|
+
|
|
49
|
+
if (!permissionOperation) throw new Error('Bad operation type provided')
|
|
50
|
+
|
|
51
|
+
const permissionProperties = await _getPermissionPropertiesOfAClass({
|
|
52
|
+
className: object.className,
|
|
53
|
+
operation: permissionOperation,
|
|
54
|
+
context: object.context,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// If no permission is defined by default we throw an error, Zero trust principle
|
|
58
|
+
if (!permissionProperties)
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Permission denied to ${permissionOperation} class ${object.className}`,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const sessionId = object.context.sessionId
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
!permissionProperties.requireAuthentication &&
|
|
67
|
+
!permissionProperties.authorizedRoles
|
|
68
|
+
)
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
// User is not corrected but requireAuthentication is on true
|
|
72
|
+
if (!sessionId || !object.getUser())
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Permission denied to ${permissionOperation} class ${object.className}`,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if (permissionProperties.authorizedRoles?.includes('everyone')) return
|
|
78
|
+
|
|
79
|
+
// authorizedRoles is empty
|
|
80
|
+
if (
|
|
81
|
+
permissionProperties.authorizedRoles?.length === 0 ||
|
|
82
|
+
!permissionProperties
|
|
83
|
+
)
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Permission denied to ${permissionOperation} class ${object.className}`,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
const roleName = object.context.user?.role?.name
|
|
89
|
+
|
|
90
|
+
// No role name found
|
|
91
|
+
if (!roleName)
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Permission denied to ${permissionOperation} class ${object.className}`,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
// The role of the user is not included in the authorizedRoles
|
|
97
|
+
if (!permissionProperties.authorizedRoles?.includes(roleName))
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Permission denied to ${permissionOperation} class ${object.className}`,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const defaultCheckPermissionOnRead = (object: HookObject<any, any>) =>
|
|
104
|
+
_checkCLP(object, OperationType.BeforeRead)
|
|
105
|
+
|
|
106
|
+
export const defaultCheckPermissionOnCreate = (object: HookObject<any, any>) =>
|
|
107
|
+
_checkCLP(object, OperationType.BeforeCreate)
|
|
108
|
+
|
|
109
|
+
export const defaultCheckPermissionOnUpdate = (object: HookObject<any, any>) =>
|
|
110
|
+
_checkCLP(object, OperationType.BeforeUpdate)
|
|
111
|
+
|
|
112
|
+
export const defaultCheckPermissionOnDelete = (object: HookObject<any, any>) =>
|
|
113
|
+
_checkCLP(object, OperationType.BeforeDelete)
|