@budibase/worker 1.0.199 → 1.0.200-alpha.2

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.
Files changed (40) hide show
  1. package/Dockerfile +3 -0
  2. package/nodemon.json +1 -1
  3. package/package.json +56 -46
  4. package/scripts/dev/manage.js +2 -0
  5. package/scripts/jestSetup.js +9 -0
  6. package/scripts/load/users.js +4 -4
  7. package/src/api/controllers/global/auth.ts +21 -14
  8. package/src/api/controllers/global/configs.js +126 -2
  9. package/src/api/controllers/global/roles.js +3 -3
  10. package/src/api/controllers/global/self.js +12 -3
  11. package/src/api/controllers/global/users.ts +48 -88
  12. package/src/api/routes/global/self.js +2 -2
  13. package/src/api/routes/global/users.js +3 -3
  14. package/src/api/routes/tests/auth.spec.js +38 -16
  15. package/src/api/routes/tests/configs.spec.js +223 -7
  16. package/src/api/routes/tests/email.spec.js +8 -7
  17. package/src/api/routes/tests/realEmail.spec.js +5 -5
  18. package/src/api/routes/tests/self.spec.js +58 -0
  19. package/src/api/routes/tests/users.spec.js +299 -13
  20. package/src/api/routes/validation/index.ts +1 -0
  21. package/src/api/{utilities/validation.js → routes/validation/users.ts} +4 -4
  22. package/src/index.ts +2 -0
  23. package/src/sdk/index.ts +1 -0
  24. package/src/sdk/users/events.ts +161 -0
  25. package/src/sdk/users/index.ts +1 -0
  26. package/src/sdk/users/users.ts +201 -0
  27. package/src/{api/routes/tests/utilities → tests}/TestConfiguration.js +68 -111
  28. package/src/tests/controllers.js +7 -0
  29. package/src/tests/index.js +12 -0
  30. package/src/tests/mocks/email.js +10 -0
  31. package/src/tests/mocks/index.js +5 -0
  32. package/src/tests/structures/configs.js +76 -0
  33. package/src/tests/structures/index.js +12 -0
  34. package/src/tests/structures/users.ts +28 -0
  35. package/tsconfig.build.json +25 -0
  36. package/tsconfig.json +21 -15
  37. package/src/api/routes/tests/utilities/controllers.js +0 -7
  38. package/src/api/routes/tests/utilities/index.js +0 -41
  39. package/src/api/routes/tests/utilities/structures.js +0 -2
  40. package/src/api/utilities/index.js +0 -33
@@ -0,0 +1,58 @@
1
+ jest.mock("nodemailer")
2
+ const { config, request } = require("../../../tests")
3
+ const { events } = require("@budibase/backend-core")
4
+
5
+ describe("/api/global/self", () => {
6
+
7
+ beforeAll(async () => {
8
+ await config.beforeAll()
9
+ })
10
+
11
+ afterAll(async () => {
12
+ await config.afterAll()
13
+ })
14
+
15
+ beforeEach(() => {
16
+ jest.clearAllMocks()
17
+ })
18
+
19
+ const updateSelf = async (user) => {
20
+ const res = await request
21
+ .post(`/api/global/self`)
22
+ .send(user)
23
+ .set(config.defaultHeaders())
24
+ .expect("Content-Type", /json/)
25
+ .expect(200)
26
+ return res
27
+ }
28
+
29
+ describe("update", () => {
30
+
31
+ it("should update self", async () => {
32
+ const user = await config.createUser()
33
+
34
+ delete user.password
35
+ const res = await updateSelf(user)
36
+
37
+ expect(res.body._id).toBe(user._id)
38
+ expect(events.user.updated).toBeCalledTimes(1)
39
+ expect(events.user.updated).toBeCalledWith(user)
40
+ expect(events.user.passwordUpdated).not.toBeCalled()
41
+ })
42
+
43
+ it("should update password", async () => {
44
+ const user = await config.createUser()
45
+ const password = "newPassword"
46
+ user.password = password
47
+
48
+ const res = await updateSelf(user)
49
+
50
+ delete user.password
51
+ expect(res.body._id).toBe(user._id)
52
+ expect(events.user.updated).toBeCalledTimes(1)
53
+ expect(events.user.updated).toBeCalledWith(user)
54
+ expect(events.user.passwordUpdated).toBeCalledTimes(1)
55
+ expect(events.user.passwordUpdated).toBeCalledWith(user)
56
+ })
57
+ })
58
+ })
@@ -1,20 +1,19 @@
1
1
  jest.mock("nodemailer")
2
- const setup = require("./utilities")
3
- const sendMailMock = setup.emailMock()
2
+ const { config, request, mocks, structures } = require("../../../tests")
3
+ const sendMailMock = mocks.email.mock()
4
+ const { events } = require("@budibase/backend-core")
4
5
 
5
6
  describe("/api/global/users", () => {
6
- let request = setup.getRequest()
7
- let config = setup.getConfig()
8
- let code
9
7
 
10
8
  beforeAll(async () => {
11
- await config.init()
9
+ await config.beforeAll()
12
10
  })
13
11
 
14
- afterAll(setup.afterAll)
12
+ afterAll(async () => {
13
+ await config.afterAll()
14
+ })
15
15
 
16
- it("should be able to generate an invitation", async () => {
17
- // initially configure settings
16
+ const sendUserInvite = async () => {
18
17
  await config.saveSmtpConfig()
19
18
  await config.saveSettingsConfig()
20
19
  const res = await request
@@ -25,16 +24,26 @@ describe("/api/global/users", () => {
25
24
  .set(config.defaultHeaders())
26
25
  .expect("Content-Type", /json/)
27
26
  .expect(200)
27
+
28
+ const emailCall = sendMailMock.mock.calls[0][0]
29
+ // after this URL there should be a code
30
+ const parts = emailCall.html.split("http://localhost:10000/builder/invite?code=")
31
+ const code = parts[1].split("\"")[0].split("&")[0]
32
+ return { code, res }
33
+ }
34
+
35
+ it("should be able to generate an invitation", async () => {
36
+ const { code, res } = await sendUserInvite()
37
+
28
38
  expect(res.body).toEqual({ message: "Invitation has been sent." })
29
39
  expect(sendMailMock).toHaveBeenCalled()
30
- const emailCall = sendMailMock.mock.calls[0][0]
31
- // after this URL there should be a code
32
- const parts = emailCall.html.split("http://localhost:10000/builder/invite?code=")
33
- code = parts[1].split("\"")[0].split("&")[0]
34
40
  expect(code).toBeDefined()
41
+ expect(events.user.invited).toBeCalledTimes(1)
35
42
  })
36
43
 
37
44
  it("should be able to create new user from invite", async () => {
45
+ const { code } = await sendUserInvite()
46
+
38
47
  const res = await request
39
48
  .post(`/api/global/users/invite/accept`)
40
49
  .send({
@@ -47,5 +56,282 @@ describe("/api/global/users", () => {
47
56
  const user = await config.getUser("invite@test.com")
48
57
  expect(user).toBeDefined()
49
58
  expect(user._id).toEqual(res.body._id)
59
+ expect(events.user.inviteAccepted).toBeCalledTimes(1)
60
+ expect(events.user.inviteAccepted).toBeCalledWith(user)
61
+ })
62
+
63
+ const createUser = async (user) => {
64
+ const existing = await config.getUser(user.email)
65
+ if (existing) {
66
+ await deleteUser(existing._id)
67
+ }
68
+ return saveUser(user)
69
+ }
70
+
71
+ const updateUser = async (user) => {
72
+ const existing = await config.getUser(user.email)
73
+ user._id = existing._id
74
+ return saveUser(user)
75
+ }
76
+
77
+ const saveUser = async (user) => {
78
+ const res = await request
79
+ .post(`/api/global/users`)
80
+ .send(user)
81
+ .set(config.defaultHeaders())
82
+ .expect("Content-Type", /json/)
83
+ .expect(200)
84
+ return res.body
85
+ }
86
+
87
+ const deleteUser = async (email) => {
88
+ const user = await config.getUser(email)
89
+ if (user) {
90
+ await request
91
+ .delete(`/api/global/users/${user._id}`)
92
+ .set(config.defaultHeaders())
93
+ .expect("Content-Type", /json/)
94
+ .expect(200)
95
+ }
96
+ }
97
+
98
+ describe("create", () => {
99
+ it("should be able to create a basic user", async () => {
100
+ jest.clearAllMocks()
101
+ const user = structures.users.user({ email: "basic@test.com" })
102
+ await createUser(user)
103
+
104
+ expect(events.user.created).toBeCalledTimes(1)
105
+ expect(events.user.updated).not.toBeCalled()
106
+ expect(events.user.permissionBuilderAssigned).not.toBeCalled()
107
+ expect(events.user.permissionAdminAssigned).not.toBeCalled()
108
+ })
109
+
110
+ it("should be able to create an admin user", async () => {
111
+ jest.clearAllMocks()
112
+ const user = structures.users.adminUser({ email: "admin@test.com" })
113
+ await createUser(user)
114
+
115
+ expect(events.user.created).toBeCalledTimes(1)
116
+ expect(events.user.updated).not.toBeCalled()
117
+ expect(events.user.permissionBuilderAssigned).not.toBeCalled()
118
+ expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
119
+ })
120
+
121
+ it("should be able to create a builder user", async () => {
122
+ jest.clearAllMocks()
123
+ const user = structures.users.builderUser({ email: "builder@test.com" })
124
+ await createUser(user)
125
+
126
+ expect(events.user.created).toBeCalledTimes(1)
127
+ expect(events.user.updated).not.toBeCalled()
128
+ expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1)
129
+ expect(events.user.permissionAdminAssigned).not.toBeCalled()
130
+ })
131
+
132
+ it("should be able to assign app roles", async () => {
133
+ jest.clearAllMocks()
134
+ const user = structures.users.user({ email: "assign-roles@test.com" })
135
+ user.roles = {
136
+ "app_123": "role1",
137
+ "app_456": "role2",
138
+ }
139
+
140
+ await createUser(user)
141
+ const savedUser = await config.getUser(user.email)
142
+
143
+ expect(events.user.created).toBeCalledTimes(1)
144
+ expect(events.user.updated).not.toBeCalled()
145
+ expect(events.role.assigned).toBeCalledTimes(2)
146
+ expect(events.role.assigned).toBeCalledWith(savedUser, "role1")
147
+ expect(events.role.assigned).toBeCalledWith(savedUser, "role2")
148
+ })
149
+ })
150
+
151
+ describe("update", () => {
152
+ it("should be able to update a basic user", async () => {
153
+ let user = structures.users.user({ email: "basic-update@test.com" })
154
+ await createUser(user)
155
+ jest.clearAllMocks()
156
+
157
+ await updateUser(user)
158
+
159
+ expect(events.user.created).not.toBeCalled()
160
+ expect(events.user.updated).toBeCalledTimes(1)
161
+ expect(events.user.permissionBuilderAssigned).not.toBeCalled()
162
+ expect(events.user.permissionAdminAssigned).not.toBeCalled()
163
+ expect(events.user.passwordForceReset).not.toBeCalled()
164
+ })
165
+
166
+ it("should be able to force reset password", async () => {
167
+ let user = structures.users.user({ email: "basic-password-update@test.com" })
168
+ await createUser(user)
169
+ jest.clearAllMocks()
170
+
171
+ user.forceResetPassword = true
172
+ user.password = "tempPassword"
173
+ await updateUser(user)
174
+
175
+ expect(events.user.created).not.toBeCalled()
176
+ expect(events.user.updated).toBeCalledTimes(1)
177
+ expect(events.user.permissionBuilderAssigned).not.toBeCalled()
178
+ expect(events.user.permissionAdminAssigned).not.toBeCalled()
179
+ expect(events.user.passwordForceReset).toBeCalledTimes(1)
180
+ })
181
+
182
+ it("should be able to update a basic user to an admin user", async () => {
183
+ let user = structures.users.user({ email: "basic-update-admin@test.com" })
184
+ await createUser(user)
185
+ jest.clearAllMocks()
186
+
187
+ await updateUser(structures.users.adminUser(user))
188
+
189
+ expect(events.user.created).not.toBeCalled()
190
+ expect(events.user.updated).toBeCalledTimes(1)
191
+ expect(events.user.permissionBuilderAssigned).not.toBeCalled()
192
+ expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
193
+ })
194
+
195
+ it("should be able to update a basic user to a builder user", async () => {
196
+ let user = structures.users.user({ email: "basic-update-builder@test.com" })
197
+ await createUser(user)
198
+ jest.clearAllMocks()
199
+
200
+ await updateUser(structures.users.builderUser(user))
201
+
202
+ expect(events.user.created).not.toBeCalled()
203
+ expect(events.user.updated).toBeCalledTimes(1)
204
+ expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1)
205
+ expect(events.user.permissionAdminAssigned).not.toBeCalled()
206
+ })
207
+
208
+ it("should be able to update an admin user to a basic user", async () => {
209
+ let user = structures.users.adminUser({ email: "admin-update-basic@test.com" })
210
+ await createUser(user)
211
+ jest.clearAllMocks()
212
+
213
+ user.admin.global = false
214
+ await updateUser(user)
215
+
216
+ expect(events.user.created).not.toBeCalled()
217
+ expect(events.user.updated).toBeCalledTimes(1)
218
+ expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
219
+ expect(events.user.permissionBuilderRemoved).not.toBeCalled()
220
+ })
221
+
222
+ it("should be able to update an builder user to a basic user", async () => {
223
+ let user = structures.users.builderUser({ email: "builder-update-basic@test.com" })
224
+ await createUser(user)
225
+ jest.clearAllMocks()
226
+
227
+ user.builder.global = false
228
+ await updateUser(user)
229
+
230
+ expect(events.user.created).not.toBeCalled()
231
+ expect(events.user.updated).toBeCalledTimes(1)
232
+ expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
233
+ expect(events.user.permissionAdminRemoved).not.toBeCalled()
234
+ })
235
+
236
+ it("should be able to assign app roles", async () => {
237
+ const user = structures.users.user({ email: "assign-roles-update@test.com" })
238
+ await createUser(user)
239
+ jest.clearAllMocks()
240
+
241
+ user.roles = {
242
+ "app_123": "role1",
243
+ "app_456": "role2",
244
+ }
245
+ await updateUser(user)
246
+ const savedUser = await config.getUser(user.email)
247
+
248
+ expect(events.user.created).not.toBeCalled()
249
+ expect(events.user.updated).toBeCalledTimes(1)
250
+ expect(events.role.assigned).toBeCalledTimes(2)
251
+ expect(events.role.assigned).toBeCalledWith(savedUser, "role1")
252
+ expect(events.role.assigned).toBeCalledWith(savedUser, "role2")
253
+ })
254
+
255
+ it("should be able to unassign app roles", async () => {
256
+ const user = structures.users.user({ email: "unassign-roles@test.com" })
257
+ user.roles = {
258
+ "app_123": "role1",
259
+ "app_456": "role2",
260
+ }
261
+ await createUser(user)
262
+ jest.clearAllMocks()
263
+
264
+ user.roles = {}
265
+ await updateUser(user)
266
+ const savedUser = await config.getUser(user.email)
267
+
268
+ expect(events.user.created).not.toBeCalled()
269
+ expect(events.user.updated).toBeCalledTimes(1)
270
+ expect(events.role.unassigned).toBeCalledTimes(2)
271
+ expect(events.role.unassigned).toBeCalledWith(savedUser, "role1")
272
+ expect(events.role.unassigned).toBeCalledWith(savedUser, "role2")
273
+ })
274
+
275
+ it("should be able to update existing app roles", async () => {
276
+ const user = structures.users.user({ email: "update-roles@test.com" })
277
+ user.roles = {
278
+ "app_123": "role1",
279
+ "app_456": "role2",
280
+ }
281
+ await createUser(user)
282
+ jest.clearAllMocks()
283
+
284
+ user.roles = {
285
+ "app_123": "role1",
286
+ "app_456": "role2-edit",
287
+ }
288
+ await updateUser(user)
289
+ const savedUser = await config.getUser(user.email)
290
+
291
+ expect(events.user.created).not.toBeCalled()
292
+ expect(events.user.updated).toBeCalledTimes(1)
293
+ expect(events.role.unassigned).toBeCalledTimes(1)
294
+ expect(events.role.unassigned).toBeCalledWith(savedUser, "role2")
295
+ expect(events.role.assigned).toBeCalledTimes(1)
296
+ expect(events.role.assigned).toBeCalledWith(savedUser, "role2-edit")
297
+ })
298
+ })
299
+
300
+ describe("destroy", () => {
301
+ it("should be able to destroy a basic user", async () => {
302
+ let user = structures.users.user({ email: "destroy@test.com" })
303
+ await createUser(user)
304
+ jest.clearAllMocks()
305
+
306
+ await deleteUser(user.email)
307
+
308
+ expect(events.user.deleted).toBeCalledTimes(1)
309
+ expect(events.user.permissionBuilderRemoved).not.toBeCalled()
310
+ expect(events.user.permissionAdminRemoved).not.toBeCalled()
311
+ })
312
+
313
+ it("should be able to destroy an admin user", async () => {
314
+ let user = structures.users.adminUser({ email: "destroy-admin@test.com" })
315
+ await createUser(user)
316
+ jest.clearAllMocks()
317
+
318
+ await deleteUser(user.email)
319
+
320
+ expect(events.user.deleted).toBeCalledTimes(1)
321
+ expect(events.user.permissionBuilderRemoved).not.toBeCalled()
322
+ expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
323
+ })
324
+
325
+ it("should be able to destroy a builder user", async () => {
326
+ let user = structures.users.builderUser({ email: "destroy-admin@test.com" })
327
+ await createUser(user)
328
+ jest.clearAllMocks()
329
+
330
+ await deleteUser(user.email)
331
+
332
+ expect(events.user.deleted).toBeCalledTimes(1)
333
+ expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1)
334
+ expect(events.user.permissionAdminRemoved).not.toBeCalled()
335
+ })
50
336
  })
51
337
  })
@@ -0,0 +1 @@
1
+ export * as users from "./users"
@@ -1,8 +1,8 @@
1
- const joiValidator = require("../../middleware/joi-validator")
2
- const Joi = require("joi")
1
+ import joiValidator from "../../../middleware/joi-validator"
2
+ import Joi from "joi"
3
3
 
4
- exports.buildUserSaveValidation = (isSelf = false) => {
5
- let schema = {
4
+ export const buildUserSaveValidation = (isSelf = false) => {
5
+ let schema: any = {
6
6
  email: Joi.string().allow(null, ""),
7
7
  password: Joi.string().allow(null, ""),
8
8
  forceResetPassword: Joi.boolean().optional(),
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ const http = require("http")
18
18
  const api = require("./api")
19
19
  const redis = require("./utilities/redis")
20
20
  const Sentry = require("@sentry/node")
21
+ import { events } from "@budibase/backend-core"
21
22
 
22
23
  // this will setup http and https proxies form env variables
23
24
  bootstrap()
@@ -73,6 +74,7 @@ server.on("close", async () => {
73
74
  console.log("Server Closed")
74
75
  }
75
76
  await redis.shutdown()
77
+ await events.shutdown()
76
78
  if (!env.isTest()) {
77
79
  process.exit(errCode)
78
80
  }
@@ -0,0 +1 @@
1
+ export * as users from "./users"
@@ -0,0 +1,161 @@
1
+ import env from "../../environment"
2
+ import { events, accounts, tenancy } from "@budibase/backend-core"
3
+ import { User, UserRoles, CloudAccount } from "@budibase/types"
4
+
5
+ export const handleDeleteEvents = async (user: any) => {
6
+ await events.user.deleted(user)
7
+
8
+ if (isBuilder(user)) {
9
+ await events.user.permissionBuilderRemoved(user)
10
+ }
11
+
12
+ if (isAdmin(user)) {
13
+ await events.user.permissionAdminRemoved(user)
14
+ }
15
+ }
16
+
17
+ const assignAppRoleEvents = async (
18
+ user: User,
19
+ roles: UserRoles,
20
+ existingRoles: UserRoles
21
+ ) => {
22
+ for (const [appId, role] of Object.entries(roles)) {
23
+ // app role in existing is not same as new
24
+ if (!existingRoles || existingRoles[appId] !== role) {
25
+ await events.role.assigned(user, role)
26
+ }
27
+ }
28
+ }
29
+
30
+ const unassignAppRoleEvents = async (
31
+ user: User,
32
+ roles: UserRoles,
33
+ existingRoles: UserRoles
34
+ ) => {
35
+ if (!existingRoles) {
36
+ return
37
+ }
38
+ for (const [appId, role] of Object.entries(existingRoles)) {
39
+ // app role in new is not same as existing
40
+ if (!roles || roles[appId] !== role) {
41
+ await events.role.unassigned(user, role)
42
+ }
43
+ }
44
+ }
45
+
46
+ const handleAppRoleEvents = async (user: any, existingUser: any) => {
47
+ const roles = user.roles
48
+ const existingRoles = existingUser?.roles
49
+
50
+ await assignAppRoleEvents(user, roles, existingRoles)
51
+ await unassignAppRoleEvents(user, roles, existingRoles)
52
+ }
53
+
54
+ export const handleSaveEvents = async (user: any, existingUser: any) => {
55
+ const tenantId = tenancy.getTenantId()
56
+ let tenantAccount: CloudAccount | undefined
57
+ if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
58
+ tenantAccount = await accounts.getAccountByTenantId(tenantId)
59
+ }
60
+ await events.identification.identifyUser(user, tenantAccount)
61
+
62
+ if (existingUser) {
63
+ await events.user.updated(user)
64
+
65
+ if (isRemovingBuilder(user, existingUser)) {
66
+ await events.user.permissionBuilderRemoved(user)
67
+ }
68
+
69
+ if (isRemovingAdmin(user, existingUser)) {
70
+ await events.user.permissionAdminRemoved(user)
71
+ }
72
+
73
+ if (
74
+ !existingUser.forceResetPassword &&
75
+ user.forceResetPassword &&
76
+ user.password
77
+ ) {
78
+ await events.user.passwordForceReset(user)
79
+ }
80
+ } else {
81
+ await events.user.created(user)
82
+ }
83
+
84
+ if (isAddingBuilder(user, existingUser)) {
85
+ await events.user.permissionBuilderAssigned(user)
86
+ }
87
+
88
+ if (isAddingAdmin(user, existingUser)) {
89
+ await events.user.permissionAdminAssigned(user)
90
+ }
91
+
92
+ await handleAppRoleEvents(user, existingUser)
93
+ }
94
+
95
+ const isBuilder = (user: any) => user.builder && user.builder.global
96
+ const isAdmin = (user: any) => user.admin && user.admin.global
97
+
98
+ export const isAddingBuilder = (user: any, existingUser: any) => {
99
+ return isAddingPermission(user, existingUser, isBuilder)
100
+ }
101
+
102
+ export const isRemovingBuilder = (user: any, existingUser: any) => {
103
+ return isRemovingPermission(user, existingUser, isBuilder)
104
+ }
105
+
106
+ const isAddingAdmin = (user: any, existingUser: any) => {
107
+ return isAddingPermission(user, existingUser, isAdmin)
108
+ }
109
+
110
+ const isRemovingAdmin = (user: any, existingUser: any) => {
111
+ return isRemovingPermission(user, existingUser, isAdmin)
112
+ }
113
+
114
+ /**
115
+ * Check if a permission is being added to a new or existing user.
116
+ */
117
+ const isAddingPermission = (
118
+ user: any,
119
+ existingUser: any,
120
+ hasPermission: any
121
+ ) => {
122
+ // new user doesn't have the permission
123
+ if (!hasPermission(user)) {
124
+ return false
125
+ }
126
+
127
+ // existing user has the permission
128
+ if (existingUser && hasPermission(existingUser)) {
129
+ return false
130
+ }
131
+
132
+ // permission is being added
133
+ return true
134
+ }
135
+
136
+ /**
137
+ * Check if a permission is being removed from an existing user.
138
+ */
139
+ const isRemovingPermission = (
140
+ user: any,
141
+ existingUser: any,
142
+ hasPermission: any
143
+ ) => {
144
+ // new user has the permission
145
+ if (hasPermission(user)) {
146
+ return false
147
+ }
148
+
149
+ // no existing user or existing user doesn't have the permission
150
+ if (!existingUser) {
151
+ return false
152
+ }
153
+
154
+ // existing user doesn't have the permission
155
+ if (!hasPermission(existingUser)) {
156
+ return false
157
+ }
158
+
159
+ // permission is being removed
160
+ return true
161
+ }
@@ -0,0 +1 @@
1
+ export * from "./users"