insite-users-server 2.3.5 → 2.5.0
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/LICENSE +1 -1
- package/README.md +373 -4
- package/dist/index.js +3 -3
- package/dist/index.js.map +4 -4
- package/package.json +54 -47
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,13 +1,382 @@
|
|
|
1
|
-
#
|
|
1
|
+
# insite-users-server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/insite-users-server)
|
|
4
|
+
|
|
5
|
+
Server-side core for users, organizations, roles, sessions, and avatars in the inSite stack. In-memory domain models backed by MongoDB with change streams for real-time sync, role-based abilities, and hierarchical org structure.
|
|
6
|
+
|
|
7
|
+
Part of [inSite](../../README.md) — uses [insite-db](../db/README.md) and [insite-common](../common/README.md). WebSocket integration via [insite-users-server-ws](../users-server-ws/README.md).
|
|
2
8
|
|
|
3
9
|
## Installation
|
|
4
10
|
|
|
5
11
|
```sh
|
|
6
|
-
|
|
12
|
+
npm install insite-users-server
|
|
7
13
|
```
|
|
8
14
|
|
|
9
|
-
Or
|
|
15
|
+
Or:
|
|
10
16
|
|
|
11
17
|
```sh
|
|
12
|
-
|
|
18
|
+
bun add insite-users-server
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
- **Users** — in-memory `Map<string, User>` with `byEmail`, `bySessionId`, `sorted`; login/logout, password hashing (Argon2), root user auto-creation
|
|
24
|
+
- **Orgs** — hierarchical organizations with owners (users or orgs), `slaveOrgs`, `null` org for users without org
|
|
25
|
+
- **Roles** — role hierarchy with `involves`, merged abilities, `root` role
|
|
26
|
+
- **Sessions** — TTL (7 days), `isOnline`, prolong, `uid()` for session IDs
|
|
27
|
+
- **Avatars** — WebP storage in `users.avatars` collection, base64 save
|
|
28
|
+
- **Abilities** — schema-driven permissions via `AbilitiesMap`, merge from roles, params (number/items)
|
|
29
|
+
|
|
30
|
+
## Setup
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Users } from "insite-users-server";
|
|
34
|
+
import type { Collections } from "insite-db";
|
|
35
|
+
import type { AbilitiesSchema } from "insite-common";
|
|
36
|
+
|
|
37
|
+
const users = await Users.init(collections, {
|
|
38
|
+
abilities: yourAbilitiesSchema,
|
|
39
|
+
// optional: indexes, schema, initialRoot, roles, orgs, sessions, avatars, collection
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// or with constructor:
|
|
43
|
+
const usersInstance = new Users(collections, options);
|
|
44
|
+
await usersInstance.whenReady();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Options<AS>:**
|
|
48
|
+
|
|
49
|
+
| Option | Type | Description |
|
|
50
|
+
|--------|------|-------------|
|
|
51
|
+
| `abilities` | `AS` | Abilities schema from [insite-common](../common/README.md) |
|
|
52
|
+
| `indexes` | `CollectionIndexes` | Additional MongoDB indexes for users |
|
|
53
|
+
| `schema` | `CollectionSchema` | Custom schema extensions for users |
|
|
54
|
+
| `initialRoot` | `Partial<UserDoc>` | Props for auto-created root user |
|
|
55
|
+
| `roles` | `RolesOptions` | Roles collection config |
|
|
56
|
+
| `orgs` | `OrgsOptions` | Orgs collection config |
|
|
57
|
+
| `sessions` | `SessionsOptions` | Sessions collection config |
|
|
58
|
+
| `avatars` | `AvatarsOptions` | Avatars collection config |
|
|
59
|
+
| `collection` | `CollectionOptions` | Base collection options |
|
|
60
|
+
|
|
61
|
+
**Events:** `user-create`, `user-is-online`, `user-permissions-change`, `session-delete`
|
|
62
|
+
|
|
63
|
+
## Public API
|
|
64
|
+
|
|
65
|
+
### Exports
|
|
66
|
+
|
|
67
|
+
| Export | Type | Description |
|
|
68
|
+
|--------|------|-------------|
|
|
69
|
+
| `Users` | class | Main entry, extends `Map<string, User>` |
|
|
70
|
+
| `User` | class | User entity |
|
|
71
|
+
| `UserDoc`, `Options`, `AvatarDoc` | types | User-related types |
|
|
72
|
+
| `Org`, `OrgDoc`, `Orgs` | types | Org-related |
|
|
73
|
+
| `Role`, `RoleDoc`, `Roles` | types | Role-related |
|
|
74
|
+
| `Session`, `SessionDoc`, `Sessions` | types | Session-related |
|
|
75
|
+
| `AbilitiesMap`, `AbilityBoolean`, `AbilityParam`, `AbilityParamItems`, `AbilityParamNumber`, `AbilityWithParams`, `GenericAbilities` | types | Abilities |
|
|
76
|
+
| `PermissionError`, `SubordinationError`, `UnauthorizedError` | classes | User errors |
|
|
77
|
+
| `RolesError`, `AbilityError` | classes | Role/ability errors |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### Users
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
class Users<AS extends AbilitiesSchema> extends Map<string, User<AS>>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Method | Description |
|
|
88
|
+
|--------|-------------|
|
|
89
|
+
| `constructor(collections, options)` | Creates instance, starts async init |
|
|
90
|
+
| `static init(collections, options)` | Factory, returns `Promise<Users>` |
|
|
91
|
+
| `whenReady()` | Returns `Promise<Users>` for init completion |
|
|
92
|
+
| `load(userDoc)` | Loads user from doc into map |
|
|
93
|
+
| `create(newUser)` | Creates user, returns `_id` |
|
|
94
|
+
| `updateUser(_id, updates, byUser?)` | Updates user; `byUser` restricts role changes by subordination |
|
|
95
|
+
| `changePassword(_id, newPassword)` | Hashes and updates password, invalidates sessions |
|
|
96
|
+
| `login(email, password, sessionProps)` | Verifies credentials, creates session |
|
|
97
|
+
| `logout(session)` | Deletes session |
|
|
98
|
+
| `deleteUser(_id)` | Deletes user from collection |
|
|
99
|
+
| `sortIds(ids)` | Sorts ids by user/org display order |
|
|
100
|
+
| `replace(fromId)` | Runs replace handlers (e.g. merge user) |
|
|
101
|
+
|
|
102
|
+
| Property | Type | Description |
|
|
103
|
+
|----------|------|-------------|
|
|
104
|
+
| `collection` | `WatchedCollection<UserDoc>` | Users MongoDB collection |
|
|
105
|
+
| `roles` | `Roles<AS>` | Roles manager |
|
|
106
|
+
| `sessions` | `Sessions<AS>` | Sessions manager |
|
|
107
|
+
| `orgs` | `Orgs<AS>` | Orgs manager |
|
|
108
|
+
| `avatars` | `Avatars` | Avatars manager |
|
|
109
|
+
| `abilities` | `AbilitiesMap<AS>` | Abilities schema map |
|
|
110
|
+
| `byEmail` | `Map<string, User<AS>>` | Lookup by email |
|
|
111
|
+
| `bySessionId` | `Map<string, User<AS>>` | Lookup by session id |
|
|
112
|
+
| `sorted` | `User<AS>[]` | Users sorted by last name |
|
|
113
|
+
| `replaceMap` | `Map<string, string>` | Map fromId → toId for replace |
|
|
114
|
+
| `replaceHandlers` | `Set<(fromId, toId) => void \| Promise<void>>` | Handlers for `replace()` |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### User
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
class User<AS extends AbilitiesSchema>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| Property | Type | Description |
|
|
125
|
+
|----------|------|-------------|
|
|
126
|
+
| `_id` | `string` | User id |
|
|
127
|
+
| `email` | `string` | Email |
|
|
128
|
+
| `name` | `{ first, middle, last }` | Name parts |
|
|
129
|
+
| `initials` | `string` | First letters of first/last name |
|
|
130
|
+
| `displayLabel` | `string` | Display name (first + last or email) |
|
|
131
|
+
| `org` | `Org<AS>` | Organization |
|
|
132
|
+
| `job` | `string` | Job title |
|
|
133
|
+
| `avatar` | `string \| null` | Avatar timestamp id |
|
|
134
|
+
| `avatarUrl` | `string \| null` | Full avatar URL |
|
|
135
|
+
| `roles` | `Set<Role<AS>>` | All roles (own + inherited) |
|
|
136
|
+
| `ownRoles` | `Role<AS>[]` | Direct roles |
|
|
137
|
+
| `ownRoleIds` | `string[]` | Direct role ids |
|
|
138
|
+
| `slaveRoles` | `Set<Role<AS>>` | Inherited roles |
|
|
139
|
+
| `slaveRoleIds` | `string[]` | Inherited role ids |
|
|
140
|
+
| `abilities` | `Abilities<AS>` | Merged abilities from roles |
|
|
141
|
+
| `slaveOrgs` | `Set<Org<AS>>` | Orgs user owns |
|
|
142
|
+
| `slaveUsers` | `Set<User<AS>>` | Users in subordination |
|
|
143
|
+
| `slaves` | `Set<Org<AS> \| User<AS>>` | Union of slaveOrgs and slaveUsers |
|
|
144
|
+
| `slaveIds` | `string[]` | Ids of slaves |
|
|
145
|
+
| `permissiveIds` | `string[]` | Ids user can act on (org, self, slaves) |
|
|
146
|
+
| `sessions` | `Set<Session<AS>>` | Active sessions |
|
|
147
|
+
| `isOnline` | `boolean` | Has at least one online session |
|
|
148
|
+
| `isRoot` | `boolean` | Has root role |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### Orgs
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
class Orgs<AS extends AbilitiesSchema> extends Map<string, Org<AS>>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| Method | Description |
|
|
159
|
+
|--------|-------------|
|
|
160
|
+
| `create(newOrg)` | Creates org, returns `_id` |
|
|
161
|
+
| `updateOrg(_id, updates, byUser?)` | Updates org; `byUser` restricts owner changes |
|
|
162
|
+
| `deleteOrg(org)` | Removes org from owners, deletes org |
|
|
163
|
+
| `load(orgDoc)` | Loads org from doc |
|
|
164
|
+
| `replace(fromId)` | Runs replace handlers |
|
|
165
|
+
|
|
166
|
+
| Property | Type | Description |
|
|
167
|
+
|----------|------|-------------|
|
|
168
|
+
| `collection` | `WatchedCollection<OrgDoc>` | Orgs collection |
|
|
169
|
+
| `null` | `Org<AS>` | Virtual org for users without org |
|
|
170
|
+
| `sorted` | `Org<AS>[]` | Orgs in hierarchy order |
|
|
171
|
+
| `replaceMap` | `Map<string, string>` | Replace mapping |
|
|
172
|
+
| `replaceHandlers` | `Set<(fromId, toId) => void \| Promise<void>>` | Replace handlers |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### Org
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
class Org<AS extends AbilitiesSchema>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
| Property | Type | Description |
|
|
183
|
+
|----------|------|-------------|
|
|
184
|
+
| `_id` | `string` | Org id |
|
|
185
|
+
| `title` | `string` | Org title |
|
|
186
|
+
| `initials` | `string` | First letter of title |
|
|
187
|
+
| `displayLabel` | `string` | Same as title |
|
|
188
|
+
| `ownerOrgs` | `Set<Org<AS>>` | Parent orgs |
|
|
189
|
+
| `ownerUsers` | `Set<User<AS>>` | Owner users |
|
|
190
|
+
| `users` | `Set<User<AS>>` | Users in this org |
|
|
191
|
+
| `slaveOrgs` | `Set<Org<AS>>` | Child orgs |
|
|
192
|
+
| `ownerIds` | `string[]` | Owner ids (users or orgs) |
|
|
193
|
+
|
|
194
|
+
| Method | Description |
|
|
195
|
+
|--------|-------------|
|
|
196
|
+
| `delete()` | Deletes org via Orgs |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### Roles
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
class Roles<AS extends AbilitiesSchema> extends Map<string, Role<AS>>
|
|
13
204
|
```
|
|
205
|
+
|
|
206
|
+
| Method | Description |
|
|
207
|
+
|--------|-------------|
|
|
208
|
+
| `create(newRole)` | Creates role, returns `_id` |
|
|
209
|
+
| `updateRole(roleId, updates)` | Updates role (excludes abilities) |
|
|
210
|
+
| `deleteRole(role)` | Removes from involves, deletes role |
|
|
211
|
+
| `setAbility(roleId, abilityLongId, set)` | Enables/disables ability |
|
|
212
|
+
| `setAbilityParam(roleId, abilityLongId, paramId, value)` | Sets ability param |
|
|
213
|
+
| `cleanUpIds(ids)` | Removes redundant role ids (e.g. if A involves B, removes A when B requested) |
|
|
214
|
+
| `load(roleDoc)` | Loads role from doc |
|
|
215
|
+
|
|
216
|
+
| Property | Type | Description |
|
|
217
|
+
|----------|------|-------------|
|
|
218
|
+
| `collection` | `WatchedCollection<RoleDoc>` | Roles collection |
|
|
219
|
+
| `root` | `Role<AS>` | Root role |
|
|
220
|
+
| `sorted` | `Role<AS>[]` | Roles in hierarchy order |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### Role
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
class Role<AS extends AbilitiesSchema>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
| Property | Type | Description |
|
|
231
|
+
|----------|------|-------------|
|
|
232
|
+
| `_id` | `string` | Role id |
|
|
233
|
+
| `ownInvolveIds` | `string[]` | Direct role ids this involves |
|
|
234
|
+
| `ownAbilities` | `Abilities<AS>` | Direct abilities |
|
|
235
|
+
| `title` | `string` | Role title |
|
|
236
|
+
| `displayTitle` | `string` | Title or id |
|
|
237
|
+
| `ownInvolves` | `Role<AS>[]` | Direct involved roles |
|
|
238
|
+
| `involves` | `Set<Role<AS>>` | All involved roles (transitive) |
|
|
239
|
+
| `abilities` | `Abilities<AS>` | Merged abilities |
|
|
240
|
+
| `inheritedAbilities` | `Abilities<AS>` | From involved roles |
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### Sessions
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
class Sessions<AS extends AbilitiesSchema> extends Map<string, Session<AS>>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
| Method | Description |
|
|
251
|
+
|--------|-------------|
|
|
252
|
+
| `uid()` | Generates session id (ObjectId~base64url) |
|
|
253
|
+
| `create(user, props)` | Creates session for user |
|
|
254
|
+
| `destroySession(_id)` | Deletes session |
|
|
255
|
+
| `load(sessionDoc)` | Loads session from doc |
|
|
256
|
+
|
|
257
|
+
| Property | Type | Description |
|
|
258
|
+
|----------|------|-------------|
|
|
259
|
+
| `collection` | `WatchedCollection<SessionDoc>` | Sessions collection (TTL 7 days) |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### Session
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
class Session<AS extends AbilitiesSchema>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
| Property | Type | Description |
|
|
270
|
+
|----------|------|-------------|
|
|
271
|
+
| `_id` | `string` | Session id |
|
|
272
|
+
| `user` | `User<AS>` | User |
|
|
273
|
+
| `isOnline` | `boolean` | Online flag |
|
|
274
|
+
| `userAgent` | `string` | User agent |
|
|
275
|
+
| `remoteAddress` | `string` | IP |
|
|
276
|
+
| `createdAt` | `number` | Timestamp |
|
|
277
|
+
| `prolongedAt` | `number` | Last prolong timestamp |
|
|
278
|
+
| `expiresAt` | `Date` | Expiry |
|
|
279
|
+
|
|
280
|
+
| Method | Description |
|
|
281
|
+
|--------|-------------|
|
|
282
|
+
| `prolong(updates)` | Extends expiry, updates fields |
|
|
283
|
+
| `online()` | Sets isOnline true |
|
|
284
|
+
| `offline()` | Sets isOnline false |
|
|
285
|
+
| `delete()` | Removes from map and collection |
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### Avatars
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
class Avatars
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
| Method | Description |
|
|
296
|
+
|--------|-------------|
|
|
297
|
+
| `save(_id, type, data)` | Saves base64 data (expects `image/webp`), updates user.avatar |
|
|
298
|
+
| `deleteAvatar(_id)` | Deletes avatar, clears user.avatar |
|
|
299
|
+
|
|
300
|
+
| Property | Type | Description |
|
|
301
|
+
|----------|------|-------------|
|
|
302
|
+
| `collection` | `WatchedCollection<AvatarDoc>` | Avatars collection |
|
|
303
|
+
| `TYPES_ACCEPTED` | `string[]` | `["image/webp"]` |
|
|
304
|
+
| `MAX_SIZE` | `number` | 512 KB |
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### AbilitiesMap
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
class AbilitiesMap<AS extends AbilitiesSchema> extends Map<string, AbilitySchema>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
| Method | Description |
|
|
315
|
+
|--------|-------------|
|
|
316
|
+
| `get(longId, parent?)` | Gets ability schema by long id (e.g. `"users.edit"`) |
|
|
317
|
+
| `getParamSchema(longId, paramId)` | Gets param schema |
|
|
318
|
+
| `getDefaultParam(longId, paramId, max?)` | Default value for param |
|
|
319
|
+
| `getDefaultAbility(schema, max?)` | Default ability object |
|
|
320
|
+
| `getDefaultAbilities(schemas?, max?)` | Full default abilities |
|
|
321
|
+
| `getSchemeFor(abilities)` | Returns schema filtered to given abilities |
|
|
322
|
+
| `getParam(abilities, longId, paramId)` | Gets param value or default |
|
|
323
|
+
| `setParam(abilities, longId, paramId, value)` | Sets param, returns abilities |
|
|
324
|
+
| `isParamFits(longId, paramId, value, referenceValue?)` | Validates param |
|
|
325
|
+
| `adjustParam(paramSchema, value)` | Clamps/validates param value |
|
|
326
|
+
| `adjust(abilities, parent?)` | Normalizes abilities to schema |
|
|
327
|
+
| `merge(target, source, parent?)` | Merges source into target |
|
|
328
|
+
| `hasAbility(abilities, longId)` | Checks if ability is set |
|
|
329
|
+
| `setAbility(target, longId)` | Enables ability |
|
|
330
|
+
| `unsetAbility(target, longId)` | Disables ability |
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
### Errors
|
|
335
|
+
|
|
336
|
+
| Class | Constructor | `name` |
|
|
337
|
+
|-------|-------------|--------|
|
|
338
|
+
| `UnauthorizedError` | `(payload?)` | `"UnauthorizedError"` |
|
|
339
|
+
| `SubordinationError` | `(payload?)` or `(type, payload?)` or `(type, ids, payload?)` — type: `"_id" \| "permissiveIds" \| "slaveIds"` | `"SubordinationError"` |
|
|
340
|
+
| `PermissionError` | `(payload?)` | `"PermissionError"` |
|
|
341
|
+
| `RolesError` | `(roleId, payload?)` or `(roleIds[], payload?)` | `"RolesError"` |
|
|
342
|
+
| `AbilityError` | `(payload?)` or `(longId, payload?)` | `"AbilityError"` |
|
|
343
|
+
|
|
344
|
+
All extend `Err` from `@nesvet/n` with `code` and `payload`.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Types Reference
|
|
349
|
+
|
|
350
|
+
| Type | Description |
|
|
351
|
+
|------|-------------|
|
|
352
|
+
| `UserDoc` | `{ _id, email, password, roles, name, org, job, avatar?, meta, createdAt }` |
|
|
353
|
+
| `NewUser` | UserDoc minus _id, createdAt; name partial |
|
|
354
|
+
| `Options<AS>` | Users init options |
|
|
355
|
+
| `OrgDoc` | `{ _id, title, note, owners, meta, createdAt }` |
|
|
356
|
+
| `NewOrg` | OrgDoc minus _id, createdAt |
|
|
357
|
+
| `OrgsOptions` | Orgs config |
|
|
358
|
+
| `SessionDoc` | `{ _id, user, userAgent, remoteAddress, isOnline, meta, createdAt, prolongedAt, expiresAt }` |
|
|
359
|
+
| `SessionsOptions` | Sessions config |
|
|
360
|
+
| `RoleDoc` | `{ _id, involves, abilities, title, description, meta, createdAt }` |
|
|
361
|
+
| `NewRole` | RoleDoc minus abilities, createdAt |
|
|
362
|
+
| `RolesOptions` | Roles config |
|
|
363
|
+
| `AvatarDoc` | `{ _id, type, size, ts, data, meta }` |
|
|
364
|
+
| `AvatarsOptions` | Avatars config |
|
|
365
|
+
| `AbilityParam` | `AbilityParamItems \| AbilityParamNumber` |
|
|
366
|
+
| `AbilityParamItems` | `string[]` |
|
|
367
|
+
| `AbilityParamNumber` | `number` |
|
|
368
|
+
| `AbilityWithParams` | `Record<string, AbilityParam>` |
|
|
369
|
+
| `AbilityObject` | `Record<string, Ability \| AbilityParam>` |
|
|
370
|
+
| `AbilityBoolean` | `true` |
|
|
371
|
+
| `Ability` | `AbilityBoolean \| AbilityObject` |
|
|
372
|
+
| `GenericAbilities` | `Record<string, Ability>` |
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Related
|
|
377
|
+
|
|
378
|
+
- [insite-db](../db/README.md), [insite-common](../common/README.md), [insite-users-server-ws](../users-server-ws/README.md)
|
|
379
|
+
|
|
380
|
+
## License
|
|
381
|
+
|
|
382
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
`);n!==Q.get(this)&&(Q.set(this,n),this.#e.users.isInited&&this.#e.update())}}delete(){this.#e.delete(this._id),Q.delete(this),this.#e.update()}};var c={_id:/^[\dA-Fa-f]{24}$/,email:/^[^\s@]{1,63}@[^\s@]{1,63}\.[^\s@]{1,63}$/,argon2:/^\$argon2(?:i|d|id)\$v=\d+\$m=\d+,t=\d+,p=\d+(?:\$[\d+/A-Za-z]+){2}$/,sessionId:/^(?:[\dA-Fa-f]{16,32}\$[\dA-Fa-f]{16,32}|[0-9a-f]{24}~[A-Za-z0-9_-]{43})$/,role:/^[\d_a-z-]{1,64}$/,ip:/^(?:\d{1,3}\.){3}\d{1,3}$/};var I={required:["_id","involves","abilities","title","description","meta","createdAt"],properties:{_id:{bsonType:"string",pattern:c.role.source,not:{enum:["root"]}},involves:{bsonType:"array",uniqueItems:!0,items:{bsonType:"string",pattern:c.role.source,not:{enum:["root"]}},additionalItems:!1},abilities:{bsonType:"object"},title:{bsonType:"string",maxLength:256},description:{bsonType:"string",maxLength:10240},meta:{bsonType:"object"},createdAt:{bsonType:"number"}},additionalProperties:!1};var _e=[[{involves:1}]];function Se(){throw new Error("Direct usage of roles collection.deleteOne or .deleteMany is forbidden. Use roles.deleteRole instead.")}var G=class extends Map{constructor(e,t={}){super();let{root:i,...s}=t;this.users=e,this.collections=e.collections,this.abilities=e.abilities,this.root=new D(this,{...i,_id:"root",involves:[],abilities:this.abilities.getDefaultAbilities(),title:"Root",description:"Root",meta:{},createdAt:Date.now()}),this.initOptions=s}users;collections;collection;abilities;root;initOptions;async init(){if(!this.users.isInited){let{schema:e,indexes:t,collection:i}=this.initOptions;e&&(e.required&&h(e.required,I.required),e.properties&&u(e,Object.keys(I.properties)));let s={...I,...e,required:[...I.required,...e?.required??[]],properties:{...I.properties,...e?.properties}};this.collection=Object.assign(await this.collections.ensure("roles",{...i,schema:s}),{deleteOne:Se,deleteMany:Se}),await this.collection.ensureIndexes([..._e,...t??[]]),await this.#e();for await(let o of this.collection.find())this.abilities.adjust(o.abilities)&&await this.collection.updateOne({_id:o._id},{$set:{abilities:o.abilities}}),this.load(o);this.update(),this.collection.onChange(this.#t),delete this.initOptions}}async#e(){await this.collection.updateMany({meta:{$exists:!1}},{$set:{meta:{}}})}#t=e=>{switch(e.operationType){case"insert":{new D(this,e.fullDocument);break}case"replace":this.get(e.documentKey._id)?.update(e.fullDocument);break;case"update":this.get(e.documentKey._id)?.update(e.updateDescription.updatedFields);break;case"delete":this.get(e.documentKey._id)?.delete()}};load(e){new D(this,e)}resolve(e,t){t=[e,...t];for(let i of e.ownInvolveIds){let s=this.get(i);if(s){for(let o of t)me(o.involves,s),this.abilities.merge(o.inheritedAbilities,s.abilities);s!==e&&this.resolve(s,t)}}}sorted=[];#i(e,t){return t.involves.size-e.involves.size||(e.title&&t.title?e.title>t.title?1:e.title<t.title?-1:0:e.title?-1:t.title||e._id>t._id?1:e._id<t._id?-1:0)}update(){let e=[...this.values()];this.root.ownInvolveIds=[];for(let s of e)s!==this.root&&!e.some(o=>o.ownInvolveIds.includes(s._id))&&this.root.ownInvolveIds.push(s._id),s.involves.clear(),s.abilities=structuredClone(s.ownAbilities),s.inheritedAbilities={};this.root.involves.add(this.root),this.resolve(this.root,[]);for(let s of e)s.ownInvolves=J(this,s.ownInvolveIds,!0).sort(this.#i).reverse();let t=[this.root];for(let s=0;s<t.length;s++){let o=t[s];for(let n of o.ownInvolves){let l=t.indexOf(n);~l&&(t.splice(l,1),s--),t.splice(s+1,0,n)}}let i=0;for(let s of t)this.abilities.merge(s.abilities,s.inheritedAbilities),s._o=i++;this.sorted=t,this.users.isInited&&this.users.update(!0)}async create({_id:e,involves:t,title:i,description:s,...o}){return await this.collection.insertOne({_id:e,involves:t||[],abilities:{},title:i??"",description:s??"",meta:{},...u(o,["abilities"]),createdAt:Date.now()}),e}async updateRole(e,t){let i=this.get(e);if(!i)return!1;if(t.involves){h(t.involves,[e]);for(let s of t.involves)this.get(s)?.involves.has(i)&&h(t.involves,[s]);t.involves=this.cleanUpIds(t.involves)}return await this.collection.updateOne({_id:e},{$set:t}),!0}deleteRole(e){let t=typeof e=="string"?e:e._id;return this.collection.bulkWrite([{updateMany:{filter:{involves:t},update:{$pullAll:{involves:[t]}}}},{deleteOne:{filter:{_id:t}}}])}cleanUpIds(e){let t=q(this.sorted,J(this,e,!0)),i=[];for(let s of t)t.every(o=>o===s||!o.involves.has(s))&&i.push(s._id);return i}async setAbility(e,t,i){let s=this.get(e);return s?(await this.collection.updateOne({_id:e},{$set:{abilities:i?this.abilities.setAbility(s.ownAbilities,t):this.abilities.unsetAbility(s.ownAbilities,t)}}),!0):!1}async setAbilityParam(e,t,i,s){let o=this.get(e);return o?(await this.collection.updateOne({_id:e},{$set:{abilities:this.abilities.setParam(o.ownAbilities,t,i,s)}}),!0):!1}};var ee=class extends f{constructor(e){super("Unauthorized","unauthorized",e)}name="UnauthorizedError"},te=class extends f{constructor(e,t,i){switch(arguments.length){case 1:i=e,e=void 0;break;case 2:i=t,t=void 0}super("Insubordination attempt","subordination",{...e&&{type:e},...t&&{ids:Array.isArray(t)?t:[t]},...i})}name="SubordinationError"},ie=class extends f{constructor(e){super("Insufficient permissions","permissions",e)}name="PermissionError"};import{hash as ve,verify as ke}from"@node-rs/argon2";import Me from"eventemitter3";import{newObjectIdString as Ue}from"insite-db";import{newObjectIdString as De}from"insite-db";var se=new WeakMap,m=class{constructor(e,t){this.orgs=e,this._id=t._id,e.set(this._id,this),this.update(t)}_id;title="";initials="";displayLabel="";_o=0;orgs;isOrg=!0;ownerOrgs=new Set;ownerUsers=new Set;users=new Set;slaveOrgs=new Set;ownerIds=[];update({_id:e,title:t,owners:i,...s}){if(Object.assign(this,s),t!==void 0&&(this.title=t,this.initials=t?.[0]?.toUpperCase()??"",this.displayLabel=t),i){this.ownerIds=i;let o=this.ownerIds.join(",");o!==se.get(this)&&(se.set(this,o),this.orgs.users.isInited&&this.orgs.update())}}async delete(){this.orgs.delete(this._id),se.delete(this),await this.orgs.replace(this._id),this.orgs.update()}};var j={required:["_id","title","note","owners","meta","createdAt"],properties:{_id:{bsonType:"string",pattern:c._id.source},title:{bsonType:"string",maxLength:512},note:{bsonType:"string",maxLength:131072},owners:{bsonType:"array",uniqueItems:!0,items:{bsonType:"string",pattern:c._id.source},additionalItems:!1},meta:{bsonType:"object"},createdAt:{bsonType:"number"}},additionalProperties:!1};var Ie=[[{owners:1}]];function xe(){throw new Error("Direct usage of orgs collection.deleteOne or .deleteMany is forbidden. Use orgs.deleteOrg instead.")}var L=class extends Map{constructor(e,t={}){super(),this.users=e,this.collections=e.collections,this.preinitOptions=t}users;collections;preinitOptions;collection;null;#e=!1;async preinit(){if(!this.#e){this.#e=!0;let{schema:e,indexes:t,null:i,collection:s}=this.preinitOptions;e&&(e.required&&h(e.required,j.required),e.properties&&u(e,Object.keys(j.properties)));let o={...j,...e,required:[...j.required,...e?.required??[]],properties:{...j.properties,...e?.properties}};this.collection=Object.assign(await this.collections.ensure("orgs",{...s,schema:o}),{deleteOne:xe,deleteMany:xe}),await this.collection.ensureIndexes([...Ie,...t??[]]),this.null=Object.assign(new m(this,{...i,_id:"",title:"",note:"",owners:[],meta:{},createdAt:Date.now()}),{_id:null,ownerIds:new C,ownerOrgs:new _,ownerUsers:new _,slaveOrgs:new _}),await this.#t();for await(let n of this.collection.find())this.load(n);delete this.preinitOptions}}async#t(){await this.collection.updateMany({meta:{$exists:!1}},{$set:{meta:{}}})}init(){this.users.isInited||(this.update(),this.collection.onChange(this.#i))}#i=e=>{switch(e.operationType){case"insert":{new m(this,e.fullDocument);break}case"replace":this.get(e.documentKey._id)?.update(e.fullDocument);break;case"update":this.get(e.documentKey._id)?.update(e.updateDescription.updatedFields);break;case"delete":this.get(e.documentKey._id)?.delete()}};load(e){new m(this,e)}#o(e,t){t=[e,...t];for(let i of e.ownerOrgs){this.#o(i,t);for(let s of t)i.slaveOrgs.add(s)}}sorted=[];update(){let e=[...this.values()],t=[];for(let i of e){i.ownerOrgs.clear(),i.ownerUsers.clear(),i.slaveOrgs.clear();for(let s of i.ownerIds){let o=this.get(s);if(o)i.ownerOrgs.add(o);else{let n=this.users.get(s);n&&i.ownerUsers.add(n)}}!i.ownerOrgs.size&&i._id&&t.push(i)}for(let i of e)e.some(s=>s.ownerOrgs.has(i))||this.#o(i,[]);t.sort(this.#s);for(let i of e)be(i.slaveOrgs,this.#s);for(let i=0;i<t.length;i++){let s=t[i];for(let o of[...s.slaveOrgs].reverse())if(o.ownerOrgs.has(s)){let n=t.indexOf(o);~n&&(t.splice(n,1),i--),t.splice(i+1,0,o)}}for(let i=0,{length:s}=t;i<s;)t[i]._o=i++;this.sorted=t,this.users.isInited&&this.users.update()}#s(e,t){return t.slaveOrgs.size-e.slaveOrgs.size||(e.title>t.title?1:e.title<t.title?-1:0)}async create({title:e,note:t,...i}){let s=De();return await this.collection.insertOne({_id:s,title:e??"",note:t??"",meta:{},...u(i,["_id"]),createdAt:Date.now()}),s}async updateOrg(e,t,i){let s=this.get(e);return s?(t.owners&&(t.owners=this.users.sortIds(ge(i?k(s.ownerIds,i.slaveIds):[],k(t.owners,[e,...O(s.slaveOrgs)])))),await this.collection.updateOne({_id:e},{$set:t}),!0):!1}deleteOrg(e){let t=typeof e=="string"?e:e._id;return this.collection.bulkWrite([{updateMany:{filter:{owners:t},update:{$pullAll:{owners:[t]}}}},{deleteOne:{filter:{_id:t}}}])}replaceMap=new Map;replaceHandlers=new Set;async replace(e){let t=this.replaceMap.get(e)??null;t&&this.replaceMap.delete(e);for(let i of this.replaceHandlers)await i(e,t)}};import{randomBytes as je}from"node:crypto";import{newObjectIdString as Re}from"insite-db";var R={required:["_id","user","remoteAddress","isOnline","meta","createdAt","prolongedAt","expiresAt"],properties:{_id:{bsonType:"string",pattern:c.sessionId.source},user:{bsonType:"string",pattern:c._id.source},userAgent:{bsonType:"string",maxLength:4096},remoteAddress:{bsonType:"string",pattern:c.ip.source},isOnline:{bsonType:"bool"},meta:{bsonType:"object"},createdAt:{bsonType:"number"},prolongedAt:{bsonType:"number"},expiresAt:{bsonType:"date"}}};var $=class{constructor(e,t){let i=e.users.get(t.user);i?(this.#e=e,this._id=t._id,this.#e.set(this._id,this),this.user=i,this.user.sessions.add(this),this.#e.users.bySessionId.set(this._id,this.user),this.update(t)):e.collection.deleteOne({_id:t._id})}_id;user;isOnline=!1;userAgent;remoteAddress;createdAt;prolongedAt;expiresAt;#e;update({_id:e,user:t,isOnline:i,...s}){i!==void 0&&this.isOnline!==i&&(this.isOnline=i,this.#e.users.isInited&&this.user.isOnline!==this.isOnline&&this.user.updateIsOnlineDebounced()),Object.assign(this,s)}prolong({_id:e,user:t,...i}){let s=Date.now();return i.prolongedAt=s,i.expiresAt=new Date(s+this.#e.collection.expireAfterSeconds*1e3),this.update(i),this.#e.collection.updateOne({_id:this._id},{$set:i})}async online(){if(!this.isOnline){let e={isOnline:!0};this.update(e),await this.#e.collection.updateOne({_id:this._id},{$set:e})}}async offline(){if(this.isOnline){let e={isOnline:!1};this.update(e),await this.#e.collection.updateOne({_id:this._id},{$set:e})}}delete(){this.#e.delete(this._id),this.user.sessions.delete(this),this.#e.users.bySessionId.delete(this._id),this.#e.users.emit("session-delete",this),this.isOnline&&this.user.updateIsOnline()}};var oe=3600*24*7,$e=[[{user:1}],[{expiresAt:1},{expireAfterSeconds:oe}]],B=class extends Map{constructor(e,t={}){super(),this.users=e,this.collections=e.collections,this.initOptions=t}users;collections;initOptions;collection;async init(){if(!this.users.isInited){let{schema:e,indexes:t,collection:i}=this.initOptions;e&&(e.required&&h(e.required,R.required),e.properties&&u(e,Object.keys(R.properties)));let s={...R,...e,required:[...R.required,...e?.required??[]],properties:{...R.properties,...e?.properties}};this.collection=Object.assign(await this.collections.ensure("sessions",{...i,schema:s}),{expireAfterSeconds:oe}),await this.collection.ensureIndexes([...$e,...t??[]]),await this.#e();for await(let o of this.collection.find())this.load(o);this.collection.onChange(this.#t),delete this.initOptions}}async#e(){await this.collection.bulkWrite([{updateMany:{filter:{meta:{$exists:!1}},update:{$set:{meta:{}}}}},{updateMany:{filter:{isOnline:!0},update:{$set:{isOnline:!1}}}}])}#t=e=>{switch(e.operationType){case"insert":this.has(e.documentKey._id)||new $(this,e.fullDocument),this.users.get(e.fullDocument.user)?.trimSessions();break;case"replace":this.get(e.documentKey._id)?.update(e.fullDocument);break;case"update":this.get(e.documentKey._id)?.update(e.updateDescription.updatedFields);break;case"delete":this.get(e.documentKey._id)?.delete()}};uid(){return`${Re()}~${je(32).toString("base64url")}`}load(e){new $(this,e)}async create(e,t){let i=Date.now(),s={userAgent:"",remoteAddress:"127.0.0.1",isOnline:!0,...t,_id:this.uid(),user:e._id,meta:{},createdAt:i,prolongedAt:i,expiresAt:new Date(i+oe*1e3)},o=new $(this,s);return await this.collection.insertOne(s),o}async destroySession(e){let{deletedCount:t}=await this.collection.deleteOne({_id:e});return!!t}};import{Binary as Te}from"insite-db";var we={required:["_id","type","size","ts","data","meta"],properties:{_id:{bsonType:"string",pattern:c._id.source},type:{bsonType:"string",maxLength:256},size:{bsonType:"number"},ts:{bsonType:"string",maxLength:10},data:{bsonType:"binData"},meta:{bsonType:"object"}},additionalProperties:!1};var z=class{constructor(e,t={}){this.#e=e,this.collections=e.collections,this.initOptions=t}TYPES_ACCEPTED=["image/webp"];MAX_SIZE=1024*512;#e;collections;collection;initOptions;async init(){if(!this.#e.isInited){let{collection:e}=this.initOptions;this.collection=await this.collections.ensure("users.avatars",{...e,schema:we}),await this.#t()}}async#t(){await this.collection.updateMany({meta:{$exists:!1}},{$set:{meta:{}}})}async save(e,t,i){let s=Te.createFromBase64(i.slice(i.indexOf(","))),o=Date.now().toString(36);await Promise.all([this.collection.replaceOne({_id:e},{type:t,size:s.length(),ts:o,data:s,meta:{}},{upsert:!0}),this.#e.collection.updateOne({_id:e},{$set:{avatar:o}})])}async deleteAvatar(e){let[{deletedCount:t}]=await Promise.all([this.collection.deleteOne({_id:e}),this.#e.collection.updateOne({_id:e},{$set:{avatar:null}})]);return!!t}};var T={required:["_id","email","password","roles","name","org","job","meta","createdAt"],properties:{_id:{bsonType:"string",pattern:c._id.source},email:{bsonType:"string",pattern:c.email.source},password:{bsonType:"string",pattern:c.argon2.source},roles:{bsonType:"array",uniqueItems:!0,items:{bsonType:"string",pattern:c.role.source},additionalItems:!1},name:{bsonType:"object",required:["first","middle","last"],properties:{first:{bsonType:"string",maxLength:512},middle:{bsonType:"string",maxLength:512},last:{bsonType:"string",maxLength:512}},additionalProperties:!1},org:{oneOf:[{bsonType:"string",pattern:c._id.source},{bsonType:"null"}]},job:{bsonType:"string",maxLength:512},avatar:{oneOf:[{bsonType:"string",maxLength:10},{bsonType:"null"}]},meta:{bsonType:"object"},createdAt:{bsonType:"number"}},additionalProperties:!1};var re=new WeakMap,ne=new WeakMap,Ee=process.env.INSITE_STATIC_URL??"",P=class{constructor(e,t){this.#e=e,this._id=t._id,e.set(this._id,this),this.update(t),e.isInited&&e.emit("user-create",this)}_id;email;name={first:"",middle:"",last:""};initials="";displayLabel="";org;job="";avatar=null;avatarUrl=null;#e;isUser=!0;isRoot;roles=new Set;ownRoles=[];ownRoleIds=[];slaveRoles=new Set;slaveRoleIds=[];abilities={};slaveOrgs=new Set;slaveUsers=new Set;slaves=new Set;slaveIds=[];permissiveIds=[];sessions=new Set;isOnline=!1;update({_id:e,name:t,email:i,roles:s,org:o,avatar:n,...l}){Object.assign(this,l);let a;i&&this.email!==i&&(this.#e.byEmail.delete(this.email),this.email=i,this.#e.byEmail.set(i,this),a=!0);let d,g;t&&(this.name.first!==t.first||this.name.middle!==t.middle||this.name.last!==t.last)&&(g=this.name.last!==t.last,this.name={first:t.first??"",middle:t.middle??"",last:t.last??""},this.initials=((t.first?.[0]??"")+(t.last?.[0]??"")||this.email[0]).toUpperCase(),d=!0),(a||d)&&(this.displayLabel=`${this.name.first} ${this.name.last}`.trim()||this.name.middle||this.email);let A;if(s){this.ownRoleIds=s;let w=this.ownRoleIds.join(",");w!==re.get(this)&&(re.set(this,w),this.#e.isInited&&(this.updateRoles(),A=!0))}let p;o!==void 0&&this.org?._id!==o&&(this.org?.users.delete(this),this.org=o&&this.#e.orgs.get(o)||this.#e.orgs.null,this.org.users.add(this),p=!0),n!==void 0&&this.avatar!==n&&(this.avatar=n,this.avatarUrl=n&&`${Ee}/avatars/${this._id}-${n}.webp`),this.#e.isInited&&(A||p?(this.updatePermissions(),g&&(this.#e.isSortRequired=!0),this.#e.update()):g&&this.#e.sort())}updateRoles(){this.ownRoles=[],this.roles.clear(),this.slaveRoles.clear(),U(this.abilities);for(let e of this.ownRoleIds){let t=this.#e.roles.get(e);if(t){this.ownRoles.push(t),this.roles.add(t);for(let i of t.involves)this.roles.add(i),this.slaveRoles.add(i);this.slaveRoleIds=O(this.slaveRoles),this.#e.abilities.merge(this.abilities,t.abilities)}}this.roles.has(this.#e.roles.root)&&(this.isRoot=!0),this.trimSessions()}updatePermissions(){this.slaveOrgs.clear(),this.slaveUsers.clear(),this.slaves.clear();let e=new Set;for(let s of this.#e.orgs.sorted)if((s.ownerUsers.has(this)||this.isRoot)&&!this.slaveOrgs.has(s)){this.slaveOrgs.add(s),this.slaves.add(s),e.add(s._id);for(let o of s.slaveOrgs)this.slaveOrgs.add(o),this.slaves.add(o),e.add(o._id)}for(let s of new Set([this.org,...this.slaveOrgs,this.#e.orgs.null]))for(let o of s.users)o!==this&&(ue(this.slaveRoles,o.ownRoles)||!o.ownRoles.length)&&(this.slaveUsers.add(o),this.slaves.add(o),e.add(o._id));this.slaveIds=[...e];let t=[];if(this.org._id){t.push(this.org._id);for(let s of this.org.slaveOrgs)this.slaveOrgs.has(s)||t.push(s._id)}this.permissiveIds=[...t,this._id,...e];let i=[this.permissiveIds.join(","),this.ownRoleIds.join(","),JSON.stringify(this.abilities),this.slaveRoleIds.join(",")].join(`
|
|
3
|
-
`);i!==
|
|
1
|
+
import{Err as se}from"@nesvet/n";var P=class extends se{constructor(e,t){typeof e=="object"&&(t=e,e=void 0),super("User lacks ability","lacksability",{...e&&{ability:e},...t})}name="AbilityError"};import{add as oe,assignPath as re,deletePath as ne,empty as ae,getPath as B,intersection as le,pick as ce,setPath as de}from"@nesvet/n";function x(l){return typeof l=="object"}function D(l){return typeof l=="number"}function I(l){return Array.isArray(l)}var F=new Map,S=class extends Map{constructor(e){super();for(let t of e)delete t.top,this.#e.push(t);(function t(i,s,o){for(let r of i){if(F.set(r,s),r.level=o,!s||r.top){if(r.longId=r._id,this.has(r._id))throw new Error(`Top-level ability duplicate: ${r._id}`);this.set(r._id,r)}else r.longId=s?`${s.longId}.${r._id}`:r._id;r.abilities&&t.call(this,r.abilities,r,o+1)}}).call(this,e,null,0)}#e=[];get(e,t){let i=e.split("."),s=t??super.get(i.shift());if(s&&i.length){for(let o of i)if(s=s?.abilities?.find(({_id:r,top:a})=>!a&&r===o),!s)break}return s}getSchema=this.get;getParamSchema(e,t){return this.get(e)?.params?.find(({_id:i})=>i===t)}getDefaultParam(e,t,i){(t===void 0||typeof t=="boolean")&&(i=t);let s=typeof e=="string"?this.getParamSchema(e,t):e;if(s)switch(s.type){case"number":return i?s.max??1/0:s.min??0;case"items":return i?s.items.map(({_id:o})=>o):[]}}getDefaultAbility(e,t=!e){if(typeof e=="string"&&(e=this.get(e)),e){if(e.abilities||e.params){let i={};if(e.params)for(let s of e.params)i[s._id]=this.getDefaultParam(s,t);if(t&&e.abilities)for(let s of e.abilities)s.top||(i[s._id]=this.getDefaultAbility(s,t));return i}return!0}}getDefaultAbilities(e,t=!e){let i=(e?(Array.isArray(e)?e:[e]).map(o=>typeof o=="string"?this.get(o):o).filter(Boolean):[...this.values()]).sort((o,r)=>o.level-r.level),s=new Map;for(let o of i)(function r(a){let n=F.get(a);n&&(r(n),s.has(n)||s.set(n,!1))})(o),t?(function r(a){if(s.has(a)||s.set(a,!0),a.abilities)for(let n of a.abilities)n.top&&r(n)})(o):s.has(o)||s.set(o,!1);return re({},Object.fromEntries([...s].map(([o,r])=>[o.longId,this.getDefaultAbility(o,r)])))}getSchemeFor(e){return e=this.adjust(structuredClone(e)),(function t(i,s){for(let o=0;o<i.length;o++){let r=i[o],a=r.top?e[r._id]:s[r._id];if(a){if(r.params)for(let n of r.params){let d=a[n._id];switch(n.type){case"number":n.max=d;break;case"items":n.items=n.items.filter(({_id:A})=>d.includes(A))}}r.abilities&&(t(r.abilities,a),r.abilities.length||delete r.abilities)}else i.splice(o--,1)}return i})(structuredClone(this.#e),e)}getParam(e,t,i){if(this.getParamSchema(t,i)){let s=B(e,`${t}.${i}`);return this.isParamFits(t,i,s)?s:this.getDefaultParam(t,i)}}setParam(e,t,i,s){let o=this.getParamSchema(t,i);if(!o)throw new Error(`No such ability param: ${t}.${i}`);return de(e,`${t}.${i}`,this.adjustParam(o,s)),e}isParamFits(e,t,i,s){if(i!==void 0){let o=this.getParamSchema(e,t);if(o)switch(o.type){case"number":return D(i)&&(o.min===void 0||i>=o.min)&&(s===void 0?o.max===void 0||i<=o.max:i<=s);case"items":return I(i)&&i.length===new Set(i).size&&(s===void 0?i.every(r=>o.items.find(({_id:a})=>a===r)):i.every(r=>s.includes(r)))}}return!1}adjustParam(e,t){return e.type==="number"?D(t)?Math.max(Math.min(t,e.max??1/0),e.min??0):e.min??0:I(t)?le(e.items.map(i=>i._id),t):[]}adjust(e,t){let i={};for(let[s,o]of Object.entries(e)){let r=this.get(s,t);if(r)if(r.params||r.abilities){let a={};if(r.params)if(x(o))for(let n of r.params)a[n._id]=this.adjustParam(n,o[n._id]);else for(let n of r.params)a[n._id]=this.getDefaultParam(n);r.abilities&&x(o)&&Object.assign(a,this.adjust(ce(o,r.abilities.filter(n=>!n.top).map(n=>n._id)),r)),i[s]=a}else i[s]=!0}return Object.assign(ae(e),i)}merge(e,t,i){i||(this.adjust(e),t=this.adjust(structuredClone(t)));for(let[s,o]of Object.entries(t)){let r=this.get(s,i);if(r&&r.isInheritable!==!1)if(s in e&&(r.params||r.abilities)){let a=e[s],n=o;if(r.params){for(let d of r.params)if(d.isInheritable!==!1)switch(d.type){case"number":a[d._id]=Math.max(a[d._id],n[d._id]);break;case"items":oe(a[d._id],...n[d._id])}}r.abilities&&this.merge(a,n,r)}else e[s]=o}return e}hasAbility(e,t){return!!B(e,t)}setAbility(e,t){let i=typeof t=="string"?this.get(t):t;if(!i)throw new Error(`No such ability: ${t}`);return this.merge(e,this.getDefaultAbilities(i)),e}unsetAbility(e,t){let i=typeof t=="string"?this.get(t):t;if(!i)throw new Error(`No such ability: ${t}`);return(function s(o){if(ne(e,o.longId),o.abilities)for(let r of o.abilities)s(r)})(i),e}};import{Err as pe}from"@nesvet/n";var R=class extends pe{constructor(e,t){super(`User lacks role${Array.isArray(e)?"s":""}`,"lacksroles",{roles:Array.isArray(e)?e:[e],...t})}name="RolesError"};import{deleteProps as H,getAll as Z,intersection as he,reAdd as me,removeAll as T}from"@nesvet/n";var U=new WeakMap,m=class{constructor(e,t){this.#e=e,this._id=t._id,e.set(this._id,this),this.update(t)}#e;_id;ownInvolveIds;ownAbilities;title="";displayTitle="";_o=0;ownInvolves=[];involves=new Set;abilities={};inheritedAbilities={};update({_id:e,involves:t,abilities:i,title:s,...o}){if(Object.assign(this,o),t&&(this.ownInvolveIds=t),i&&(this.ownAbilities=i),s!==void 0&&(this.title=s,this.displayTitle=s||this._id),t||i){let r=[this.ownInvolveIds.join(","),JSON.stringify(this.ownAbilities)].join(`
|
|
2
|
+
`);r!==U.get(this)&&(U.set(this,r),this.#e.users.isInited&&this.#e.update())}}delete(){this.#e.delete(this._id),U.delete(this),this.#e.update()}};var c={_id:/^[\dA-Fa-f]{24}$/,email:/^[^\s@]{1,63}@[^\s@]{1,63}\.[^\s@]{1,63}$/,argon2:/^\$argon2(?:i|d|id)\$v=\d+\$m=\d+,t=\d+,p=\d+(?:\$[\d+/A-Za-z]+){2}$/,sessionId:/^(?:[\dA-Fa-f]{16,32}\$[\dA-Fa-f]{16,32}|[0-9a-f]{24}~[A-Za-z0-9_-]{43})$/,role:/^[\d_a-z-]{1,64}$/,ip:/^(?:\d{1,3}\.){3}\d{1,3}$/};var u={required:["_id","involves","abilities","title","description","meta","createdAt"],properties:{_id:{bsonType:"string",pattern:c.role.source,not:{enum:["root"]}},involves:{bsonType:"array",uniqueItems:!0,items:{bsonType:"string",pattern:c.role.source,not:{enum:["root"]}},additionalItems:!1},abilities:{bsonType:"object"},title:{bsonType:"string",maxLength:256},description:{bsonType:"string",maxLength:10240},meta:{bsonType:"object"},createdAt:{bsonType:"number"}},additionalProperties:!1};var ue=[[{involves:1}]];function J(){throw new Error("Direct usage of roles collection.deleteOne or .deleteMany is forbidden. Use roles.deleteRole instead.")}var w=class extends Map{constructor(e,t={}){super();let{root:i,...s}=t;this.users=e,this.collections=e.collections,this.abilities=e.abilities,this.root=new m(this,{...i,_id:"root",involves:[],abilities:this.abilities.getDefaultAbilities(),title:"Root",description:"Root",meta:{},createdAt:Date.now()}),this.initOptions=s}users;collections;collection;abilities;root;initOptions;async init(){if(!this.users.isInited){let{schema:e,indexes:t,collection:i}=this.initOptions;e&&(e.required&&T(e.required,u.required),e.properties&&H(e,Object.keys(u.properties)));let s={...u,...e,required:[...u.required,...e?.required??[]],properties:{...u.properties,...e?.properties}};this.collection=Object.assign(await this.collections.ensure("roles",{...i,schema:s}),{deleteOne:J,deleteMany:J}),await this.collection.ensureIndexes([...ue,...t??[]]),await this.#e();for await(let o of this.collection.find())this.abilities.adjust(o.abilities)&&await this.collection.updateOne({_id:o._id},{$set:{abilities:o.abilities}}),this.load(o);this.update(),this.collection.onChange(this.#t),delete this.initOptions}}async#e(){await this.collection.updateMany({meta:{$exists:!1}},{$set:{meta:{}}})}#t=e=>{switch(e.operationType){case"insert":{new m(this,e.fullDocument);break}case"replace":this.get(e.documentKey._id)?.update(e.fullDocument);break;case"update":this.get(e.documentKey._id)?.update(e.updateDescription.updatedFields);break;case"delete":this.get(e.documentKey._id)?.delete()}};load(e){new m(this,e)}resolve(e,t){t=[e,...t];for(let i of e.ownInvolveIds){let s=this.get(i);if(s){for(let o of t)me(o.involves,s),this.abilities.merge(o.inheritedAbilities,s.abilities);s!==e&&this.resolve(s,t)}}}sorted=[];#i(e,t){return t.involves.size-e.involves.size||(e.title&&t.title?e.title>t.title?1:e.title<t.title?-1:0:e.title?-1:t.title||e._id>t._id?1:e._id<t._id?-1:0)}update(){let e=[...this.values()];this.root.ownInvolveIds=[];for(let s of e)s!==this.root&&!e.some(o=>o.ownInvolveIds.includes(s._id))&&this.root.ownInvolveIds.push(s._id),s.involves.clear(),s.abilities=structuredClone(s.ownAbilities),s.inheritedAbilities={};this.root.involves.add(this.root),this.resolve(this.root,[]);for(let s of e)s.ownInvolves=Z(this,s.ownInvolveIds,!0).sort(this.#i).reverse();let t=[this.root];for(let s=0;s<t.length;s++){let o=t[s];for(let r of o.ownInvolves){let a=t.indexOf(r);~a&&(t.splice(a,1),s--),t.splice(s+1,0,r)}}let i=0;for(let s of t)this.abilities.merge(s.abilities,s.inheritedAbilities),s._o=i++;this.sorted=t,this.users.isInited&&this.users.update(!0)}async create({_id:e,involves:t,title:i,description:s,...o}){return await this.collection.insertOne({_id:e,involves:t||[],abilities:{},title:i??"",description:s??"",meta:{},...H(o,["abilities"]),createdAt:Date.now()}),e}async updateRole(e,t){let i=this.get(e);if(!i)return!1;if(t.involves){T(t.involves,[e]);for(let s of t.involves)this.get(s)?.involves.has(i)&&T(t.involves,[s]);t.involves=this.cleanUpIds(t.involves)}return await this.collection.updateOne({_id:e},{$set:t}),!0}deleteRole(e){let t=typeof e=="string"?e:e._id;return this.collection.bulkWrite([{updateMany:{filter:{involves:t},update:{$pullAll:{involves:[t]}}}},{deleteOne:{filter:{_id:t}}}])}cleanUpIds(e){let t=he(this.sorted,Z(this,e,!0)),i=[];for(let s of t)t.every(o=>o===s||!o.involves.has(s))&&i.push(s._id);return i}async setAbility(e,t,i){let s=this.get(e);return s?(await this.collection.updateOne({_id:e},{$set:{abilities:i?this.abilities.setAbility(s.ownAbilities,t):this.abilities.unsetAbility(s.ownAbilities,t)}}),!0):!1}async setAbilityParam(e,t,i,s){let o=this.get(e);return o?(await this.collection.updateOne({_id:e},{$set:{abilities:this.abilities.setParam(o.ownAbilities,t,i,s)}}),!0):!1}};import{Err as E}from"@nesvet/n";var C=class extends E{constructor(e){super("Unauthorized","unauthorized",e)}name="UnauthorizedError"},j=class extends E{constructor(e,t,i){switch(arguments.length){case 1:i=e,e=void 0;break;case 2:i=t,t=void 0}super("Insubordination attempt","subordination",{...e&&{type:e},...t&&{ids:Array.isArray(t)?t:[t]},...i})}name="SubordinationError"},$=class extends E{constructor(e){super("Insufficient permissions","permissions",e)}name="PermissionError"};import{hash as te,verify as je}from"@node-rs/argon2";import $e from"eventemitter3";import{_ids as Ee,deleteProps as ie,removeAll as Me,StatefulPromise as ke,without as qe}from"@nesvet/n";import{newObjectIdString as Ge}from"insite-db";import{_ids as fe,deleteProps as X,EmptyArray as be,EmptySet as k,removeAll as ye,sort as ge,union as Ae,without as Y}from"@nesvet/n";import{newObjectIdString as Se}from"insite-db";var M=new WeakMap,p=class{constructor(e,t){this.orgs=e,this._id=t._id,e.set(this._id,this),this.update(t)}_id;title="";initials="";displayLabel="";_o=0;orgs;isOrg=!0;ownerOrgs=new Set;ownerUsers=new Set;users=new Set;slaveOrgs=new Set;ownerIds=[];update({_id:e,title:t,owners:i,...s}){if(Object.assign(this,s),t!==void 0&&(this.title=t,this.initials=t?.[0]?.toUpperCase()??"",this.displayLabel=t),i){this.ownerIds=i;let o=this.ownerIds.join(",");o!==M.get(this)&&(M.set(this,o),this.orgs.users.isInited&&this.orgs.update())}}async delete(){this.orgs.delete(this._id),M.delete(this),await this.orgs.replace(this._id),this.orgs.update()}};var f={required:["_id","title","note","owners","meta","createdAt"],properties:{_id:{bsonType:"string",pattern:c._id.source},title:{bsonType:"string",maxLength:512},note:{bsonType:"string",maxLength:131072},owners:{bsonType:"array",uniqueItems:!0,items:{bsonType:"string",pattern:c._id.source},additionalItems:!1},meta:{bsonType:"object"},createdAt:{bsonType:"number"}},additionalProperties:!1};var we=[[{owners:1}]];function Q(){throw new Error("Direct usage of orgs collection.deleteOne or .deleteMany is forbidden. Use orgs.deleteOrg instead.")}var v=class extends Map{constructor(e,t={}){super(),this.users=e,this.collections=e.collections,this.preinitOptions=t}users;collections;preinitOptions;collection;null;#e=!1;async preinit(){if(!this.#e){this.#e=!0;let{schema:e,indexes:t,null:i,collection:s}=this.preinitOptions;e&&(e.required&&ye(e.required,f.required),e.properties&&X(e,Object.keys(f.properties)));let o={...f,...e,required:[...f.required,...e?.required??[]],properties:{...f.properties,...e?.properties}};this.collection=Object.assign(await this.collections.ensure("orgs",{...s,schema:o}),{deleteOne:Q,deleteMany:Q}),await this.collection.ensureIndexes([...we,...t??[]]),this.null=Object.assign(new p(this,{...i,_id:"",title:"",note:"",owners:[],meta:{},createdAt:Date.now()}),{_id:null,ownerIds:new be,ownerOrgs:new k,ownerUsers:new k,slaveOrgs:new k}),await this.#t();for await(let r of this.collection.find())this.load(r);delete this.preinitOptions}}async#t(){await this.collection.updateMany({meta:{$exists:!1}},{$set:{meta:{}}})}init(){this.users.isInited||(this.update(),this.collection.onChange(this.#i))}#i=e=>{switch(e.operationType){case"insert":{new p(this,e.fullDocument);break}case"replace":this.get(e.documentKey._id)?.update(e.fullDocument);break;case"update":this.get(e.documentKey._id)?.update(e.updateDescription.updatedFields);break;case"delete":this.get(e.documentKey._id)?.delete()}};load(e){new p(this,e)}#o(e,t){t=[e,...t];for(let i of e.ownerOrgs){this.#o(i,t);for(let s of t)i.slaveOrgs.add(s)}}sorted=[];update(){let e=[...this.values()],t=[];for(let i of e){i.ownerOrgs.clear(),i.ownerUsers.clear(),i.slaveOrgs.clear();for(let s of i.ownerIds){let o=this.get(s);if(o)i.ownerOrgs.add(o);else{let r=this.users.get(s);r&&i.ownerUsers.add(r)}}!i.ownerOrgs.size&&i._id&&t.push(i)}for(let i of e)e.some(s=>s.ownerOrgs.has(i))||this.#o(i,[]);t.sort(this.#s);for(let i of e)ge(i.slaveOrgs,this.#s);for(let i=0;i<t.length;i++){let s=t[i];for(let o of[...s.slaveOrgs].reverse())if(o.ownerOrgs.has(s)){let r=t.indexOf(o);~r&&(t.splice(r,1),i--),t.splice(i+1,0,o)}}for(let i=0,{length:s}=t;i<s;)t[i]._o=i++;this.sorted=t,this.users.isInited&&this.users.update()}#s(e,t){return t.slaveOrgs.size-e.slaveOrgs.size||(e.title>t.title?1:e.title<t.title?-1:0)}async create({title:e,note:t,...i}){let s=Se();return await this.collection.insertOne({_id:s,title:e??"",note:t??"",meta:{},...X(i,["_id"]),createdAt:Date.now()}),s}async updateOrg(e,t,i){let s=this.get(e);return s?(t.owners&&(t.owners=this.users.sortIds(Ae(i?Y(s.ownerIds,i.slaveIds):[],Y(t.owners,[e,...fe(s.slaveOrgs)])))),await this.collection.updateOne({_id:e},{$set:t}),!0):!1}deleteOrg(e){let t=typeof e=="string"?e:e._id;return this.collection.bulkWrite([{updateMany:{filter:{owners:t},update:{$pullAll:{owners:[t]}}}},{deleteOne:{filter:{_id:t}}}])}replaceMap=new Map;replaceHandlers=new Set;async replace(e){let t=this.replaceMap.get(e)??null;t&&this.replaceMap.delete(e);for(let i of this.replaceHandlers)await i(e,t)}};import{randomBytes as ve}from"node:crypto";import{deleteProps as Oe,removeAll as _e}from"@nesvet/n";import{newObjectIdString as Pe}from"insite-db";var b={required:["_id","user","remoteAddress","isOnline","meta","createdAt","prolongedAt","expiresAt"],properties:{_id:{bsonType:"string",pattern:c.sessionId.source},user:{bsonType:"string",pattern:c._id.source},userAgent:{bsonType:"string",maxLength:4096},remoteAddress:{bsonType:"string",pattern:c.ip.source},isOnline:{bsonType:"bool"},meta:{bsonType:"object"},createdAt:{bsonType:"number"},prolongedAt:{bsonType:"number"},expiresAt:{bsonType:"date"}}};var y=class{constructor(e,t){let i=e.users.get(t.user);i?(this.#e=e,this._id=t._id,this.#e.set(this._id,this),this.user=i,this.user.sessions.add(this),this.#e.users.bySessionId.set(this._id,this.user),this.update(t)):e.collection.deleteOne({_id:t._id})}_id;user;isOnline=!1;userAgent;remoteAddress;createdAt;prolongedAt;expiresAt;#e;update({_id:e,user:t,isOnline:i,...s}){i!==void 0&&this.isOnline!==i&&(this.isOnline=i,this.#e.users.isInited&&this.user.isOnline!==this.isOnline&&this.user.updateIsOnlineDebounced()),Object.assign(this,s)}prolong({_id:e,user:t,...i}){let s=Date.now();return i.prolongedAt=s,i.expiresAt=new Date(s+this.#e.collection.expireAfterSeconds*1e3),this.update(i),this.#e.collection.updateOne({_id:this._id},{$set:i})}async online(){if(!this.isOnline){let e={isOnline:!0};this.update(e),await this.#e.collection.updateOne({_id:this._id},{$set:e})}}async offline(){if(this.isOnline){let e={isOnline:!1};this.update(e),await this.#e.collection.updateOne({_id:this._id},{$set:e})}}delete(){this.#e.delete(this._id),this.user.sessions.delete(this),this.#e.users.bySessionId.delete(this._id),this.#e.users.emit("session-delete",this),this.isOnline&&this.user.updateIsOnline()}};var q=3600*24*7,xe=[[{user:1}],[{expiresAt:1},{expireAfterSeconds:q}]],O=class extends Map{constructor(e,t={}){super(),this.users=e,this.collections=e.collections,this.initOptions=t}users;collections;initOptions;collection;async init(){if(!this.users.isInited){let{schema:e,indexes:t,collection:i}=this.initOptions;e&&(e.required&&_e(e.required,b.required),e.properties&&Oe(e,Object.keys(b.properties)));let s={...b,...e,required:[...b.required,...e?.required??[]],properties:{...b.properties,...e?.properties}};this.collection=Object.assign(await this.collections.ensure("sessions",{...i,schema:s}),{expireAfterSeconds:q}),await this.collection.ensureIndexes([...xe,...t??[]]),await this.#e();for await(let o of this.collection.find())this.load(o);this.collection.onChange(this.#t),delete this.initOptions}}async#e(){await this.collection.bulkWrite([{updateMany:{filter:{meta:{$exists:!1}},update:{$set:{meta:{}}}}},{updateMany:{filter:{isOnline:!0},update:{$set:{isOnline:!1}}}}])}#t=e=>{switch(e.operationType){case"insert":this.has(e.documentKey._id)||new y(this,e.fullDocument),this.users.get(e.fullDocument.user)?.trimSessions();break;case"replace":this.get(e.documentKey._id)?.update(e.fullDocument);break;case"update":this.get(e.documentKey._id)?.update(e.updateDescription.updatedFields);break;case"delete":this.get(e.documentKey._id)?.delete()}};uid(){return`${Pe()}~${ve(32).toString("base64url")}`}load(e){new y(this,e)}async create(e,t){let i=Date.now(),s={userAgent:"",remoteAddress:"127.0.0.1",isOnline:!0,...t,_id:this.uid(),user:e._id,meta:{},createdAt:i,prolongedAt:i,expiresAt:new Date(i+q*1e3)},o=new y(this,s);return await this.collection.insertOne(s),o}async destroySession(e){let{deletedCount:t}=await this.collection.deleteOne({_id:e});return!!t}};import{Binary as De}from"insite-db";var V={required:["_id","type","size","ts","data","meta"],properties:{_id:{bsonType:"string",pattern:c._id.source},type:{bsonType:"string",maxLength:256},size:{bsonType:"number"},ts:{bsonType:"string",maxLength:10},data:{bsonType:"binData"},meta:{bsonType:"object"}},additionalProperties:!1};var _=class{constructor(e,t={}){this.#e=e,this.collections=e.collections,this.initOptions=t}TYPES_ACCEPTED=["image/webp"];MAX_SIZE=1024*512;#e;collections;collection;initOptions;async init(){if(!this.#e.isInited){let{collection:e}=this.initOptions;this.collection=await this.collections.ensure("users.avatars",{...e,schema:V}),await this.#t()}}async#t(){await this.collection.updateMany({meta:{$exists:!1}},{$set:{meta:{}}})}async save(e,t,i){let s=De.createFromBase64(i.slice(i.indexOf(","))),o=Date.now().toString(36);await Promise.all([this.collection.replaceOne({_id:e},{type:t,size:s.length(),ts:o,data:s,meta:{}},{upsert:!0}),this.#e.collection.updateOne({_id:e},{$set:{avatar:o}})])}async deleteAvatar(e){let[{deletedCount:t}]=await Promise.all([this.collection.deleteOne({_id:e}),this.#e.collection.updateOne({_id:e},{$set:{avatar:null}})]);return!!t}};var g={required:["_id","email","password","roles","name","org","job","meta","createdAt"],properties:{_id:{bsonType:"string",pattern:c._id.source},email:{bsonType:"string",pattern:c.email.source},password:{bsonType:"string",pattern:c.argon2.source},roles:{bsonType:"array",uniqueItems:!0,items:{bsonType:"string",pattern:c.role.source},additionalItems:!1},name:{bsonType:"object",required:["first","middle","last"],properties:{first:{bsonType:"string",maxLength:512},middle:{bsonType:"string",maxLength:512},last:{bsonType:"string",maxLength:512}},additionalProperties:!1},org:{oneOf:[{bsonType:"string",pattern:c._id.source},{bsonType:"null"}]},job:{bsonType:"string",maxLength:512},avatar:{oneOf:[{bsonType:"string",maxLength:10},{bsonType:"null"}]},meta:{bsonType:"object"},createdAt:{bsonType:"number"}},additionalProperties:!1};import{_ids as ee,debounce as Ie,empty as Re,hasAny as Ue,removeOne as Te}from"@nesvet/n";var G=new WeakMap,N=new WeakMap,Ce=process.env.INSITE_STATIC_URL??"",h=class{constructor(e,t){this.#e=e,this._id=t._id,e.set(this._id,this),this.update(t),e.isInited&&e.emit("user-create",this)}_id;email;name={first:"",middle:"",last:""};initials="";displayLabel="";org;job="";avatar=null;avatarUrl=null;#e;isUser=!0;isRoot;roles=new Set;ownRoles=[];ownRoleIds=[];slaveRoles=new Set;slaveRoleIds=[];abilities={};slaveOrgs=new Set;slaveUsers=new Set;slaves=new Set;slaveIds=[];permissiveIds=[];sessions=new Set;isOnline=!1;update({_id:e,name:t,email:i,roles:s,org:o,avatar:r,...a}){Object.assign(this,a);let n;i&&this.email!==i&&(this.#e.byEmail.delete(this.email),this.email=i,this.#e.byEmail.set(i,this),n=!0);let d,A;t&&(this.name.first!==t.first||this.name.middle!==t.middle||this.name.last!==t.last)&&(A=this.name.last!==t.last,this.name={first:t.first??"",middle:t.middle??"",last:t.last??""},this.initials=((t.first?.[0]??"")+(t.last?.[0]??"")||this.email[0]).toUpperCase(),d=!0),(n||d)&&(this.displayLabel=`${this.name.first} ${this.name.last}`.trim()||this.name.middle||this.email);let W;if(s){this.ownRoleIds=s;let K=this.ownRoleIds.join(",");K!==G.get(this)&&(G.set(this,K),this.#e.isInited&&(this.updateRoles(),W=!0))}let z;o!==void 0&&this.org?._id!==o&&(this.org?.users.delete(this),this.org=o&&this.#e.orgs.get(o)||this.#e.orgs.null,this.org.users.add(this),z=!0),r!==void 0&&this.avatar!==r&&(this.avatar=r,this.avatarUrl=r&&`${Ce}/avatars/${this._id}-${r}.webp`),this.#e.isInited&&(W||z?(this.updatePermissions(),A&&(this.#e.isSortRequired=!0),this.#e.update()):A&&this.#e.sort())}updateRoles(){this.ownRoles=[],this.roles.clear(),this.slaveRoles.clear(),Re(this.abilities);for(let e of this.ownRoleIds){let t=this.#e.roles.get(e);if(t){this.ownRoles.push(t),this.roles.add(t);for(let i of t.involves)this.roles.add(i),this.slaveRoles.add(i);this.slaveRoleIds=ee(this.slaveRoles),this.#e.abilities.merge(this.abilities,t.abilities)}}this.roles.has(this.#e.roles.root)&&(this.isRoot=!0),this.trimSessions()}updatePermissions(){this.slaveOrgs.clear(),this.slaveUsers.clear(),this.slaves.clear();let e=new Set;for(let s of this.#e.orgs.sorted)if((s.ownerUsers.has(this)||this.isRoot)&&!this.slaveOrgs.has(s)){this.slaveOrgs.add(s),this.slaves.add(s),e.add(s._id);for(let o of s.slaveOrgs)this.slaveOrgs.add(o),this.slaves.add(o),e.add(o._id)}for(let s of new Set([this.org,...this.slaveOrgs,this.#e.orgs.null]))for(let o of s.users)o!==this&&(Ue(this.slaveRoles,o.ownRoles)||!o.ownRoles.length)&&(this.slaveUsers.add(o),this.slaves.add(o),e.add(o._id));this.slaveIds=[...e];let t=[];if(this.org._id){t.push(this.org._id);for(let s of this.org.slaveOrgs)this.slaveOrgs.has(s)||t.push(s._id)}this.permissiveIds=[...t,this._id,...e];let i=[this.permissiveIds.join(","),this.ownRoleIds.join(","),JSON.stringify(this.abilities),this.slaveRoleIds.join(",")].join(`
|
|
3
|
+
`);i!==N.get(this)&&(N.set(this,i),this.#e.isInited&&this.#e.emit("user-permissions-change",this))}trimSessions(){let e=this.abilities.login?.sessionsLimit??0;if(this.sessions.size>e)return this.#e.sessions.collection.deleteMany({_id:{$in:ee([...this.sessions].sort((t,i)=>t.expiresAt.valueOf()-i.expiresAt.valueOf()).slice(0,this.sessions.size-e))}})}updateIsOnline(){let e=!1;for(let t of this.sessions)if(t.isOnline){e=!0;break}this.isOnline!==e&&(this.isOnline=e,this.#e.emit("user-is-online",this))}updateIsOnlineDebounced=Ie(()=>this.updateIsOnline(),500);async delete(){this.org.users.delete(this),this.#e.delete(this._id),this.#e.byEmail.delete(this.email),Te(this.#e.sorted,this),G.delete(this),N.delete(this),await this.#e.replace(this._id),this.#e.update()}};var Ne=[[{email:1},{unique:!0}],[{org:1}]],L=class l extends Map{constructor(e,t){super();let i=new $e;this.#e=i,this._events=i._events,this._eventsCount=i._eventsCount,this.emit=i.emit,this.on=i.on,this.once=i.once,this.addListener=i.addListener,this.off=i.off,this.removeListener=i.removeListener,this.collections=e,this.abilities=new S(t.abilities),this.initOptions=t,this.init()}#e;_events;_eventsCount;emit;on(e,t,i){return this.#e.on(e,t,i),this}once;addListener;off;removeListener;initOptions;collections;abilities;collection;roles;sessions;orgs;avatars;byEmail=new Map;bySessionId=new Map;sorted=[];isSortRequired=!0;#t=!1;async preinit(){if(!this.#t){this.#t=!0;let{indexes:e,schema:t,initialRoot:i,collection:s}=this.initOptions;t&&(t.required&&Me(t.required,g.required),t.properties&&ie(t,Object.keys(g.properties)));let o={...g,...t,required:[...g.required,...t?.required??[]],properties:{...g.properties,...t?.properties}};this.collection=await this.collections.ensure("users",{...s,schema:o}),await this.collection.ensureIndexes([...Ne,...e??[]]),await this.collection.countDocuments({roles:"root"})||await this.create({email:process.env.INSITE_ROOT_EMAIL??"insite@root.email",password:process.env.INSITE_ROOT_PASSWORD??"inSiteRootPassword",name:{first:"Root"},job:"Root",...i,roles:["root"],org:null,meta:{}}),await this.#i();for await(let r of this.collection.find())this.load(r)}}async#i(){await this.collection.updateMany({meta:{$exists:!1}},{$set:{meta:{}}})}isInited=!1;async init(){if(!this.isInited){let{roles:e,orgs:t,sessions:i,avatars:s,collection:{quiet:o}={}}=this.initOptions;this.roles=new w(this,{...e,collection:{quiet:o,...e?.collection}}),this.orgs=new v(this,{...t,collection:{quiet:o,...t?.collection}}),this.sessions=new O(this,{...i,collection:{quiet:o,...i?.collection}}),this.avatars=new _(this,{...s,collection:{quiet:o,...s?.collection}}),await this.roles.init(),await this.orgs.preinit(),await this.preinit(),this.orgs.init(),await this.avatars.init(),this.update(!0),this.collection.onChange(this.#o),await this.sessions.init(),delete this.initOptions,this.isInited=!0,this.#s.resolve(this)}}#o=e=>{switch(e.operationType){case"insert":new h(this,e.fullDocument);break;case"replace":this.get(e.documentKey._id)?.update(e.fullDocument);break;case"update":this.get(e.documentKey._id)?.update(e.updateDescription.updatedFields);break;case"delete":this.get(e.documentKey._id)?.delete()}};load(e){new h(this,e)}sort(){this.isSortRequired=!1,this.sorted=[...this.values()].sort((e,t)=>e.name.last>t.name.last?1:e.name.last<t.name.last?-1:0)}update(e){if(e)for(let t of this.values())t.updateRoles();for(let t of this.values())t.updatePermissions();this.isSortRequired&&this.sort()}#s=new ke;whenReady(){return this.#s}async create({email:e,password:t,roles:i,name:s,org:o,job:r,...a}){if(this.byEmail.has(e))throw new Error("User exists");let n=Ge();return await this.collection.insertOne({_id:n,email:e,password:await te(t),roles:this.isInited?this.roles.cleanUpIds(i):i,name:{first:s.first??"",middle:s.middle??"",last:s.last??""},org:o??null,job:r??"",meta:{},...ie(a,["_id"]),createdAt:Date.now()}),n}async updateUser(e,t,i){let s=this.get(e);return s?(t.roles&&(t.roles=s.isRoot?["root"]:this.roles.cleanUpIds(i?qe(s.ownRoleIds,i.slaveRoleIds).concat(t.roles):t.roles)),await this.collection.updateOne({_id:e},{$set:t}),!0):!1}async changePassword(e,t){if(!this.has(e))throw new Error("usernotfound");if(!t)throw new Error("emptypasswordisnotallowed");await Promise.all([this.collection.updateOne({_id:e},{$set:{password:await te(t)}}),this.sessions.collection.deleteMany({user:e})])}async login(e,t,i){let s=this.byEmail.get(e);if(!s)throw new Error("usernotfound");if(!await je(s.password,t))throw new Error("incorrectpassword");if(!s.abilities.login)throw new Error("userisnotabletologin");return this.sessions.create(s,i)}logout(e){return e.delete(),this.sessions.collection.deleteOne({_id:e._id})}#r(e,t){return e instanceof h&&t instanceof h?e.name.last&&t.name.last?e.name.last>t.name.last?1:e.name.last<t.name.last?-1:0:!e.name.last&&!t.name.last?e.displayLabel>t.displayLabel?1:e.displayLabel<t.displayLabel?-1:0:e.name.last?-1:1:e instanceof p&&t instanceof p?e.title>t.title?1:e.title<t.title?-1:0:e instanceof p?-1:1}sortIds(e){return Ee(e.map(t=>this.get(t)||this.orgs.get(t)).filter(Boolean).sort(this.#r))}replaceMap=new Map;replaceHandlers=new Set;async replace(e){let t=this.replaceMap.get(e)??null;t&&this.replaceMap.delete(e);for(let i of this.replaceHandlers)await i(e,t)}async deleteUser(e){let{deletedCount:t}=await this.collection.deleteOne({_id:e});return!!t}static init(e,t){return new l(e,t).whenReady()}};export{P as AbilityError,$ as PermissionError,R as RolesError,j as SubordinationError,C as UnauthorizedError,L as Users};
|
|
4
4
|
//# sourceMappingURL=index.js.map
|