@icazemier/gibbons-mongodb 1.0.0-beta.1
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 +21 -0
- package/README.md +554 -0
- package/build/cjs/bin/cli.d.ts +2 -0
- package/build/cjs/bin/cli.js +52 -0
- package/build/cjs/bin/init.d.ts +17 -0
- package/build/cjs/bin/init.js +29 -0
- package/build/cjs/config.d.ts +22 -0
- package/build/cjs/config.js +35 -0
- package/build/cjs/gibbons-mongo-db.d.ts +593 -0
- package/build/cjs/gibbons-mongo-db.js +699 -0
- package/build/cjs/index.d.ts +6 -0
- package/build/cjs/index.js +22 -0
- package/build/cjs/interfaces/config.d.ts +42 -0
- package/build/cjs/interfaces/config.js +2 -0
- package/build/cjs/interfaces/gibbon-group.d.ts +21 -0
- package/build/cjs/interfaces/gibbon-group.js +2 -0
- package/build/cjs/interfaces/gibbon-like.d.ts +8 -0
- package/build/cjs/interfaces/gibbon-like.js +2 -0
- package/build/cjs/interfaces/gibbon-permission.d.ts +11 -0
- package/build/cjs/interfaces/gibbon-permission.js +2 -0
- package/build/cjs/interfaces/gibbon-user.d.ts +12 -0
- package/build/cjs/interfaces/gibbon-user.js +2 -0
- package/build/cjs/interfaces/index.d.ts +6 -0
- package/build/cjs/interfaces/index.js +22 -0
- package/build/cjs/interfaces/permissions-resource.d.ts +15 -0
- package/build/cjs/interfaces/permissions-resource.js +2 -0
- package/build/cjs/models/gibbon-group.d.ts +133 -0
- package/build/cjs/models/gibbon-group.js +389 -0
- package/build/cjs/models/gibbon-model.d.ts +69 -0
- package/build/cjs/models/gibbon-model.js +78 -0
- package/build/cjs/models/gibbon-permission.d.ts +71 -0
- package/build/cjs/models/gibbon-permission.js +154 -0
- package/build/cjs/models/gibbon-user.d.ts +122 -0
- package/build/cjs/models/gibbon-user.js +380 -0
- package/build/cjs/models/index.d.ts +4 -0
- package/build/cjs/models/index.js +20 -0
- package/build/cjs/package.json +3 -0
- package/build/cjs/seeder.d.ts +56 -0
- package/build/cjs/seeder.js +118 -0
- package/build/cjs/utils.d.ts +31 -0
- package/build/cjs/utils.js +38 -0
- package/build/esm/bin/cli.d.ts +2 -0
- package/build/esm/bin/cli.js +47 -0
- package/build/esm/bin/init.d.ts +17 -0
- package/build/esm/bin/init.js +25 -0
- package/build/esm/config.d.ts +22 -0
- package/build/esm/config.js +31 -0
- package/build/esm/gibbons-mongo-db.d.ts +593 -0
- package/build/esm/gibbons-mongo-db.js +700 -0
- package/build/esm/index.d.ts +6 -0
- package/build/esm/index.js +6 -0
- package/build/esm/interfaces/config.d.ts +42 -0
- package/build/esm/interfaces/config.js +1 -0
- package/build/esm/interfaces/gibbon-group.d.ts +21 -0
- package/build/esm/interfaces/gibbon-group.js +1 -0
- package/build/esm/interfaces/gibbon-like.d.ts +8 -0
- package/build/esm/interfaces/gibbon-like.js +1 -0
- package/build/esm/interfaces/gibbon-permission.d.ts +11 -0
- package/build/esm/interfaces/gibbon-permission.js +1 -0
- package/build/esm/interfaces/gibbon-user.d.ts +12 -0
- package/build/esm/interfaces/gibbon-user.js +1 -0
- package/build/esm/interfaces/index.d.ts +6 -0
- package/build/esm/interfaces/index.js +6 -0
- package/build/esm/interfaces/permissions-resource.d.ts +15 -0
- package/build/esm/interfaces/permissions-resource.js +1 -0
- package/build/esm/models/gibbon-group.d.ts +133 -0
- package/build/esm/models/gibbon-group.js +321 -0
- package/build/esm/models/gibbon-model.d.ts +69 -0
- package/build/esm/models/gibbon-model.js +76 -0
- package/build/esm/models/gibbon-permission.d.ts +71 -0
- package/build/esm/models/gibbon-permission.js +134 -0
- package/build/esm/models/gibbon-user.d.ts +122 -0
- package/build/esm/models/gibbon-user.js +302 -0
- package/build/esm/models/index.d.ts +4 -0
- package/build/esm/models/index.js +4 -0
- package/build/esm/package.json +3 -0
- package/build/esm/seeder.d.ts +56 -0
- package/build/esm/seeder.js +116 -0
- package/build/esm/utils.d.ts +31 -0
- package/build/esm/utils.js +34 -0
- package/package.json +115 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Ivo Cazemier
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/icazemier/gibbons/master/gibbons.png" width="200" />
|
|
2
|
+
|
|
3
|
+
# Gibbons for MongoDB
|
|
4
|
+
|
|
5
|
+
A high-performance library to manage user groups and user permissions in [MongoDB](https://www.mongodb.com/) using bitwise operations with [Gibbons](https://github.com/icazemier/gibbons).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ⚡ **Bitwise efficiency** - Store and query thousands of permissions/groups using minimal space
|
|
10
|
+
- 🔍 **Fast queries** - Leverage MongoDB's bitwise operators for lightning-fast permission checks
|
|
11
|
+
- 🎯 **Type-safe** - Full TypeScript support with comprehensive type definitions
|
|
12
|
+
- 📦 **Easy setup** - CLI tool for database initialization
|
|
13
|
+
- 🔄 **Flexible** - Works with existing user collections
|
|
14
|
+
|
|
15
|
+
## What this is NOT
|
|
16
|
+
|
|
17
|
+
- An ORM (Object-Relational Mapper)
|
|
18
|
+
- A complete auth/authentication solution
|
|
19
|
+
- A replacement for your existing user management system
|
|
20
|
+
|
|
21
|
+
## Quick Example
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { GibbonsMongoDb, ConfigLoader } from "@icazemier/gibbons-mongodb";
|
|
25
|
+
|
|
26
|
+
// Initialize
|
|
27
|
+
const config = await ConfigLoader.load();
|
|
28
|
+
const gibbonsDb = new GibbonsMongoDb("mongodb://localhost:27017", config);
|
|
29
|
+
await gibbonsDb.initialize();
|
|
30
|
+
|
|
31
|
+
// Allocate permissions
|
|
32
|
+
const editPerm = await gibbonsDb.allocatePermission({ name: "posts.edit" });
|
|
33
|
+
const deletePerm = await gibbonsDb.allocatePermission({ name: "posts.delete" });
|
|
34
|
+
|
|
35
|
+
// Create a group with permissions
|
|
36
|
+
const adminGroup = await gibbonsDb.allocateGroup({ name: "Admins" });
|
|
37
|
+
await gibbonsDb.subscribePermissionsToGroups(
|
|
38
|
+
[adminGroup.gibbonGroupPosition],
|
|
39
|
+
[editPerm.gibbonPermissionPosition, deletePerm.gibbonPermissionPosition]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Create user and assign to group
|
|
43
|
+
const user = await gibbonsDb.createUser({
|
|
44
|
+
name: "John",
|
|
45
|
+
email: "john@example.com"
|
|
46
|
+
});
|
|
47
|
+
await gibbonsDb.subscribeUsersToGroups(
|
|
48
|
+
{ _id: user._id },
|
|
49
|
+
[adminGroup.gibbonGroupPosition]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Validate permissions
|
|
53
|
+
const hasEdit = gibbonsDb.validateUserPermissionsForAnyPermissions(
|
|
54
|
+
user.permissionsGibbon,
|
|
55
|
+
[editPerm.gibbonPermissionPosition]
|
|
56
|
+
);
|
|
57
|
+
console.log("User can edit:", hasEdit); // true
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## How It Works
|
|
61
|
+
|
|
62
|
+
Gibbons uses MongoDB's `Binary` data type to store bitwise masks. Each bit represents a group or permission position, allowing you to:
|
|
63
|
+
|
|
64
|
+
- Store thousands of permissions in a few bytes
|
|
65
|
+
- Use MongoDB's `$bitsAnySet`, `$bitsAllSet` operators for fast queries
|
|
66
|
+
- Aggregate permissions from multiple groups automatically
|
|
67
|
+
|
|
68
|
+
Example MongoDB query:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Gibbon } from "@icazemier/gibbons";
|
|
72
|
+
|
|
73
|
+
const gibbon = Gibbon.create(256).setPosition(1).setPosition(3);
|
|
74
|
+
const cursor = mongoClient
|
|
75
|
+
.db("mydb")
|
|
76
|
+
.collection("users")
|
|
77
|
+
.find({
|
|
78
|
+
groupsGibbon: {
|
|
79
|
+
$bitsAnySet: gibbon.toBuffer(),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
# Installation
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install @icazemier/gibbons-mongodb
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
# Setup
|
|
91
|
+
|
|
92
|
+
## 1. Configuration File
|
|
93
|
+
|
|
94
|
+
Create a configuration file that Gibbons can discover. We use [cosmiconfig](https://github.com/davidtheclark/cosmiconfig#readme), so you can use any of these formats:
|
|
95
|
+
|
|
96
|
+
- `.gibbons-mongodbrc.json` (recommended)
|
|
97
|
+
- `.gibbons-mongodbrc.yaml`
|
|
98
|
+
- `gibbons-mongodb` property in `package.json`
|
|
99
|
+
- And more (see cosmiconfig docs)
|
|
100
|
+
|
|
101
|
+
### Configuration Structure
|
|
102
|
+
|
|
103
|
+
**⚠️ IMPORTANT:** Config settings affect how data is stored. Changing these on a live system can break existing data!
|
|
104
|
+
|
|
105
|
+
Example `.gibbons-mongodbrc.json`:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"permissionByteLength": 256,
|
|
110
|
+
"groupByteLength": 256,
|
|
111
|
+
"mongoDbMutationConcurrency": 10,
|
|
112
|
+
"dbStructure": {
|
|
113
|
+
"user": {
|
|
114
|
+
"dbName": "myapp",
|
|
115
|
+
"collectionName": "users"
|
|
116
|
+
},
|
|
117
|
+
"group": {
|
|
118
|
+
"dbName": "myapp",
|
|
119
|
+
"collectionName": "groups"
|
|
120
|
+
},
|
|
121
|
+
"permission": {
|
|
122
|
+
"dbName": "myapp",
|
|
123
|
+
"collectionName": "permissions"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Configuration Options
|
|
130
|
+
|
|
131
|
+
| Option | Type | Description |
|
|
132
|
+
| ----------------------------- | ------ | --------------------------------------------------------------------------- |
|
|
133
|
+
| `permissionByteLength` | number | Bytes for permission Gibbon (e.g., 256 = 2,048 possible permissions) |
|
|
134
|
+
| `groupByteLength` | number | Bytes for group Gibbon (e.g., 256 = 2,048 possible groups) |
|
|
135
|
+
| `mongoDbMutationConcurrency` | number | Concurrency limit for bulk operations |
|
|
136
|
+
| `dbStructure.user` | object | User collection config - can point to existing collection |
|
|
137
|
+
| `dbStructure.group` | object | Group collection config - will be created/managed by Gibbons |
|
|
138
|
+
| `dbStructure.permission` | object | Permission collection config - will be created/managed by Gibbons |
|
|
139
|
+
|
|
140
|
+
**Note:** The user collection can be an existing one. Gibbons adds `groupsGibbon` and `permissionsGibbon` fields without affecting other fields.
|
|
141
|
+
|
|
142
|
+
## 2. Initialize Database
|
|
143
|
+
|
|
144
|
+
Run the CLI tool to populate groups and permissions collections:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Using default config
|
|
148
|
+
npx gibbons-mongodb init --uri mongodb://localhost:27017
|
|
149
|
+
|
|
150
|
+
# Using custom config file
|
|
151
|
+
npx gibbons-mongodb init --uri mongodb://localhost:27017 --config ./my-config.json
|
|
152
|
+
|
|
153
|
+
# Or use the alias
|
|
154
|
+
npx @icazemier/gibbons-mongodb init -u mongodb://localhost:27017
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This creates:
|
|
158
|
+
- `permissionByteLength * 8` non-allocated permission slots
|
|
159
|
+
- `groupByteLength * 8` non-allocated group slots
|
|
160
|
+
|
|
161
|
+
For 256 bytes each, that's **2,048 groups** and **2,048 permissions** ready to allocate!
|
|
162
|
+
|
|
163
|
+
# Usage
|
|
164
|
+
|
|
165
|
+
## Initialize the Library
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { GibbonsMongoDb, ConfigLoader } from "@icazemier/gibbons-mongodb";
|
|
169
|
+
|
|
170
|
+
// Load config (searches for .gibbons-mongodbrc files)
|
|
171
|
+
const config = await ConfigLoader.load();
|
|
172
|
+
|
|
173
|
+
// Or load from specific file
|
|
174
|
+
const config = await ConfigLoader.load("gibbons-mongodb", "./custom-config.json");
|
|
175
|
+
|
|
176
|
+
// Create instance
|
|
177
|
+
const gibbonsDb = new GibbonsMongoDb("mongodb://localhost:27017", config);
|
|
178
|
+
|
|
179
|
+
// Initialize (connects to MongoDB and sets up collections)
|
|
180
|
+
await gibbonsDb.initialize();
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Managing Permissions
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// Allocate new permissions
|
|
187
|
+
const createPost = await gibbonsDb.allocatePermission({
|
|
188
|
+
name: "posts.create",
|
|
189
|
+
description: "Create new blog posts",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const editPost = await gibbonsDb.allocatePermission({
|
|
193
|
+
name: "posts.edit",
|
|
194
|
+
description: "Edit any blog post",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const deletePost = await gibbonsDb.allocatePermission({
|
|
198
|
+
name: "posts.delete",
|
|
199
|
+
description: "Delete any blog post",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
console.log(createPost.gibbonPermissionPosition); // e.g., 1
|
|
203
|
+
console.log(createPost.gibbonIsAllocated); // true
|
|
204
|
+
|
|
205
|
+
// Update permission metadata
|
|
206
|
+
await gibbonsDb.updatePermissionMetadata(createPost.gibbonPermissionPosition, {
|
|
207
|
+
description: "Create and publish blog posts",
|
|
208
|
+
module: "blog",
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// List all allocated permissions
|
|
212
|
+
const permissionsCursor = gibbonsDb.findAllAllocatedPermissions();
|
|
213
|
+
for await (const perm of permissionsCursor) {
|
|
214
|
+
console.log(perm.name, perm.gibbonPermissionPosition);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Deallocate permissions (removes from groups and users)
|
|
218
|
+
await gibbonsDb.deallocatePermissions([deletePost.gibbonPermissionPosition]);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Managing Groups
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// Allocate new groups
|
|
225
|
+
const admins = await gibbonsDb.allocateGroup({
|
|
226
|
+
name: "Admins",
|
|
227
|
+
description: "Full system access",
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const editors = await gibbonsDb.allocateGroup({
|
|
231
|
+
name: "Editors",
|
|
232
|
+
description: "Content editors",
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Assign permissions to groups
|
|
236
|
+
await gibbonsDb.subscribePermissionsToGroups(
|
|
237
|
+
[admins.gibbonGroupPosition],
|
|
238
|
+
[createPost.gibbonPermissionPosition, editPost.gibbonPermissionPosition, deletePost.gibbonPermissionPosition]
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
await gibbonsDb.subscribePermissionsToGroups(
|
|
242
|
+
[editors.gibbonGroupPosition],
|
|
243
|
+
[createPost.gibbonPermissionPosition, editPost.gibbonPermissionPosition]
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Update group metadata
|
|
247
|
+
await gibbonsDb.updateGroupMetadata(admins.gibbonGroupPosition, {
|
|
248
|
+
color: "#FF0000",
|
|
249
|
+
priority: 1,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Find groups by permissions
|
|
253
|
+
const groupsWithDelete = gibbonsDb.findGroupsByPermissions([deletePost.gibbonPermissionPosition]);
|
|
254
|
+
for await (const group of groupsWithDelete) {
|
|
255
|
+
console.log(`${group.name} can delete posts`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Remove permissions from groups
|
|
259
|
+
await gibbonsDb.unsubscribePermissionsFromGroups(
|
|
260
|
+
[editors.gibbonGroupPosition],
|
|
261
|
+
[createPost.gibbonPermissionPosition]
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Deallocate groups (removes from users)
|
|
265
|
+
await gibbonsDb.deallocateGroups([editors.gibbonGroupPosition]);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Managing Users
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// Create users
|
|
272
|
+
const user1 = await gibbonsDb.createUser({
|
|
273
|
+
name: "Alice",
|
|
274
|
+
email: "alice@example.com",
|
|
275
|
+
username: "alice",
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const user2 = await gibbonsDb.createUser({
|
|
279
|
+
name: "Bob",
|
|
280
|
+
email: "bob@example.com",
|
|
281
|
+
username: "bob",
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Assign users to groups
|
|
285
|
+
await gibbonsDb.subscribeUsersToGroups(
|
|
286
|
+
{ email: "alice@example.com" },
|
|
287
|
+
[admins.gibbonGroupPosition]
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
await gibbonsDb.subscribeUsersToGroups(
|
|
291
|
+
{ _id: user2._id },
|
|
292
|
+
[editors.gibbonGroupPosition]
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// Find users by groups
|
|
296
|
+
const adminUsers = gibbonsDb.findUsersByGroups([admins.gibbonGroupPosition]);
|
|
297
|
+
for await (const user of adminUsers) {
|
|
298
|
+
console.log(`${user.name} is an admin`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Find users by permissions
|
|
302
|
+
const usersWhoCanEdit = gibbonsDb.findUsersByPermissions([editPost.gibbonPermissionPosition]);
|
|
303
|
+
|
|
304
|
+
// Find users with custom filter
|
|
305
|
+
const activeUsers = gibbonsDb.findUsers({
|
|
306
|
+
status: "active",
|
|
307
|
+
createdAt: { $gte: new Date("2024-01-01") },
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Remove users from groups
|
|
311
|
+
await gibbonsDb.unsubscribeUsersFromGroups(
|
|
312
|
+
{ email: "bob@example.com" },
|
|
313
|
+
[editors.gibbonGroupPosition]
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Delete users
|
|
317
|
+
const deletedCount = await gibbonsDb.removeUser({ email: "bob@example.com" });
|
|
318
|
+
console.log(`Deleted ${deletedCount} user(s)`);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Permission Validation
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// Fetch user with populated gibbons
|
|
325
|
+
const user = await gibbonsDb.findUsers({ email: "alice@example.com" }).next();
|
|
326
|
+
|
|
327
|
+
if (user) {
|
|
328
|
+
// Check if user has ALL specified permissions
|
|
329
|
+
const canEditAndDelete = gibbonsDb.validateUserPermissionsForAllPermissions(
|
|
330
|
+
user.permissionsGibbon,
|
|
331
|
+
[editPost.gibbonPermissionPosition, deletePost.gibbonPermissionPosition]
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Check if user has ANY of the specified permissions
|
|
335
|
+
const canModify = gibbonsDb.validateUserPermissionsForAnyPermissions(
|
|
336
|
+
user.permissionsGibbon,
|
|
337
|
+
[editPost.gibbonPermissionPosition, deletePost.gibbonPermissionPosition]
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// Check if user has ALL specified groups
|
|
341
|
+
const isAdmin = gibbonsDb.validateUserGroupsForAllGroups(
|
|
342
|
+
user.groupsGibbon,
|
|
343
|
+
[admins.gibbonGroupPosition]
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Check if user has ANY of the specified groups
|
|
347
|
+
const isStaff = gibbonsDb.validateUserGroupsForAnyGroups(
|
|
348
|
+
user.groupsGibbon,
|
|
349
|
+
[admins.gibbonGroupPosition, editors.gibbonGroupPosition]
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
console.log({
|
|
353
|
+
canEditAndDelete,
|
|
354
|
+
canModify,
|
|
355
|
+
isAdmin,
|
|
356
|
+
isStaff,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Database Validation
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// Verify groups are allocated before using them
|
|
365
|
+
const groupsValid = await gibbonsDb.validateAllocatedGroups([1, 2, 3]);
|
|
366
|
+
if (!groupsValid) {
|
|
367
|
+
throw new Error("Some groups are not allocated");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Verify permissions are allocated
|
|
371
|
+
const permsValid = await gibbonsDb.validateAllocatedPermissions([5, 6, 7]);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Working with Gibbons Directly
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { Gibbon } from "@icazemier/gibbons";
|
|
378
|
+
|
|
379
|
+
// Get aggregated permissions from groups
|
|
380
|
+
const permissionsGibbon = await gibbonsDb.getPermissionsGibbonForGroups([
|
|
381
|
+
admins.gibbonGroupPosition,
|
|
382
|
+
editors.gibbonGroupPosition,
|
|
383
|
+
]);
|
|
384
|
+
|
|
385
|
+
// Get positions as array
|
|
386
|
+
const positions = permissionsGibbon.getPositionsArray();
|
|
387
|
+
console.log("Permission positions:", positions); // e.g., [1, 2, 3, 5]
|
|
388
|
+
|
|
389
|
+
// Manual permission checks
|
|
390
|
+
const hasPermission = permissionsGibbon.isPositionSet(editPost.gibbonPermissionPosition);
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
# API Reference
|
|
394
|
+
|
|
395
|
+
For complete API documentation with examples, see the [TypeScript definitions](./src/index.ts) or check the TSDoc comments in your IDE.
|
|
396
|
+
|
|
397
|
+
## Main Classes
|
|
398
|
+
|
|
399
|
+
- **`GibbonsMongoDb`** - Main class for managing users, groups, and permissions
|
|
400
|
+
- **`MongoDbSeeder`** - Seeds database with pre-allocated groups and permissions
|
|
401
|
+
- **`ConfigLoader`** - Loads configuration from filesystem
|
|
402
|
+
|
|
403
|
+
## Key Methods
|
|
404
|
+
|
|
405
|
+
### GibbonsMongoDb
|
|
406
|
+
|
|
407
|
+
#### Permissions
|
|
408
|
+
- `allocatePermission<T>(data: T)` - Allocate new permission
|
|
409
|
+
- `deallocatePermissions(permissions)` - Deallocate permissions
|
|
410
|
+
- `updatePermissionMetadata(position, data)` - Update permission metadata
|
|
411
|
+
- `findAllAllocatedPermissions()` - List all allocated permissions
|
|
412
|
+
- `validateAllocatedPermissions(permissions)` - Validate permissions exist
|
|
413
|
+
|
|
414
|
+
#### Groups
|
|
415
|
+
- `allocateGroup<T>(data: T)` - Allocate new group
|
|
416
|
+
- `deallocateGroups(groups)` - Deallocate groups
|
|
417
|
+
- `updateGroupMetadata(position, data)` - Update group metadata
|
|
418
|
+
- `findGroups(groups)` - Find specific groups
|
|
419
|
+
- `findGroupsByPermissions(permissions)` - Find groups with permissions
|
|
420
|
+
- `findAllAllocatedGroups()` - List all allocated groups
|
|
421
|
+
- `validateAllocatedGroups(groups)` - Validate groups exist
|
|
422
|
+
|
|
423
|
+
#### Users
|
|
424
|
+
- `createUser<T>(data: T)` - Create new user
|
|
425
|
+
- `removeUser(filter)` - Delete users
|
|
426
|
+
- `findUsers(filter)` - Query users
|
|
427
|
+
- `findUsersByGroups(groups)` - Find users by groups
|
|
428
|
+
- `findUsersByPermissions(permissions)` - Find users by permissions
|
|
429
|
+
|
|
430
|
+
#### Subscriptions
|
|
431
|
+
- `subscribeUsersToGroups(filter, groups)` - Add users to groups
|
|
432
|
+
- `subscribePermissionsToGroups(groups, permissions)` - Add permissions to groups
|
|
433
|
+
- `unsubscribeUsersFromGroups(filter, groups)` - Remove users from groups
|
|
434
|
+
- `unsubscribePermissionsFromGroups(groups, permissions)` - Remove permissions from groups
|
|
435
|
+
|
|
436
|
+
#### Validation
|
|
437
|
+
- `validateUserGroupsForAllGroups(userGroups, groups)` - Check if user has all groups
|
|
438
|
+
- `validateUserGroupsForAnyGroups(userGroups, groups)` - Check if user has any group
|
|
439
|
+
- `validateUserPermissionsForAllPermissions(userPerms, perms)` - Check if user has all permissions
|
|
440
|
+
- `validateUserPermissionsForAnyPermissions(userPerms, perms)` - Check if user has any permission
|
|
441
|
+
|
|
442
|
+
# Advanced Topics
|
|
443
|
+
|
|
444
|
+
## Data Structure
|
|
445
|
+
|
|
446
|
+
### User Document
|
|
447
|
+
```typescript
|
|
448
|
+
{
|
|
449
|
+
_id: ObjectId,
|
|
450
|
+
// Your custom fields
|
|
451
|
+
name: "Alice",
|
|
452
|
+
email: "alice@example.com",
|
|
453
|
+
// Gibbons-managed fields
|
|
454
|
+
groupsGibbon: Binary, // Bitwise mask of group memberships
|
|
455
|
+
permissionsGibbon: Binary, // Aggregated permissions from groups
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Group Document
|
|
460
|
+
```typescript
|
|
461
|
+
{
|
|
462
|
+
_id: ObjectId,
|
|
463
|
+
gibbonGroupPosition: 1, // Unique position (1-based)
|
|
464
|
+
gibbonIsAllocated: true, // Allocation status
|
|
465
|
+
permissionsGibbon: Binary, // Bitwise mask of permissions
|
|
466
|
+
// Your custom fields
|
|
467
|
+
name: "Admins",
|
|
468
|
+
description: "Full access",
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Permission Document
|
|
473
|
+
```typescript
|
|
474
|
+
{
|
|
475
|
+
_id: ObjectId,
|
|
476
|
+
gibbonPermissionPosition: 5, // Unique position (1-based)
|
|
477
|
+
gibbonIsAllocated: true, // Allocation status
|
|
478
|
+
// Your custom fields
|
|
479
|
+
name: "posts.edit",
|
|
480
|
+
description: "Edit posts",
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
## MongoDB Queries
|
|
485
|
+
|
|
486
|
+
You can query directly using MongoDB's bitwise operators:
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
import { Binary } from "mongodb";
|
|
490
|
+
import { Gibbon } from "@icazemier/gibbons";
|
|
491
|
+
|
|
492
|
+
// Find users with specific permissions
|
|
493
|
+
const gibbon = Gibbon.create(256)
|
|
494
|
+
.setPosition(editPost.gibbonPermissionPosition)
|
|
495
|
+
.setPosition(deletePost.gibbonPermissionPosition);
|
|
496
|
+
|
|
497
|
+
const users = await db.collection("users").find({
|
|
498
|
+
permissionsGibbon: {
|
|
499
|
+
$bitsAllSet: new Binary(gibbon.toBuffer()),
|
|
500
|
+
},
|
|
501
|
+
}).toArray();
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## Environment Variables
|
|
505
|
+
|
|
506
|
+
- `GIBBONS_ENCODE_FROM_TO_STRING` - Controls whether Gibbons encodes to UTF-16 string or Buffer (see [Gibbons docs](https://github.com/icazemier/gibbons))
|
|
507
|
+
|
|
508
|
+
# Best Practices
|
|
509
|
+
|
|
510
|
+
1. **Choose appropriate byte lengths** - Calculate based on your maximum expected permissions/groups:
|
|
511
|
+
- 256 bytes = 2,048 items
|
|
512
|
+
- 512 bytes = 4,096 items
|
|
513
|
+
- 1024 bytes = 8,192 items
|
|
514
|
+
|
|
515
|
+
2. **Don't change byte lengths on live systems** - This will corrupt existing data
|
|
516
|
+
|
|
517
|
+
3. **Use meaningful names** - Store descriptive names/descriptions with permissions and groups for easier management
|
|
518
|
+
|
|
519
|
+
4. **Aggregate permissions through groups** - Don't assign permissions directly to users; use groups for better maintainability
|
|
520
|
+
|
|
521
|
+
5. **Validate before operations** - Always check if groups/permissions are allocated before using them
|
|
522
|
+
|
|
523
|
+
6. **Monitor allocation usage** - Keep track of how many slots you've used vs. available
|
|
524
|
+
|
|
525
|
+
7. **Backup before migrations** - Config changes can affect data structure
|
|
526
|
+
|
|
527
|
+
# Troubleshooting
|
|
528
|
+
|
|
529
|
+
## "Could not load config"
|
|
530
|
+
Make sure you have a `.gibbons-mongodbrc.json` (or equivalent) in your project root or run `npx gibbons-mongodb init` with `--config` flag.
|
|
531
|
+
|
|
532
|
+
## "Not able to allocate permission/group"
|
|
533
|
+
All slots are used. Increase `permissionByteLength` or `groupByteLength` in config, reinitialize database.
|
|
534
|
+
|
|
535
|
+
## "Called populateGroupsAndPermissions, but permissions and groups seem to be populated already"
|
|
536
|
+
Database is already initialized. This is expected on subsequent runs.
|
|
537
|
+
|
|
538
|
+
# License
|
|
539
|
+
|
|
540
|
+
MIT
|
|
541
|
+
|
|
542
|
+
# Contributing
|
|
543
|
+
|
|
544
|
+
Issues and pull requests welcome! See [repository](https://github.com/icazemier/gibbons-mongodb) for details.
|
|
545
|
+
|
|
546
|
+
## For Contributors
|
|
547
|
+
|
|
548
|
+
This project uses automated semantic versioning. Please use conventional commits:
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
npm run commit # Interactive commit tool
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
See [SEMANTIC-VERSIONING-QUICKSTART.md](SEMANTIC-VERSIONING-QUICKSTART.md) for details.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
8
|
+
const helpers_1 = require("yargs/helpers");
|
|
9
|
+
const init_js_1 = require("./init.js");
|
|
10
|
+
/**
|
|
11
|
+
* Main CLI entry point using yargs for command parsing and routing.
|
|
12
|
+
*/
|
|
13
|
+
void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
14
|
+
.scriptName('gibbons-mongodb')
|
|
15
|
+
.command('init', 'Populate new groups and permissions collections in your existing MongoDB instance', (yargs) => {
|
|
16
|
+
return yargs
|
|
17
|
+
.options({
|
|
18
|
+
uri: {
|
|
19
|
+
demandOption: true,
|
|
20
|
+
alias: 'u',
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'MongoDB URI (Note: Database name and collections are configured through config)',
|
|
23
|
+
nargs: 1,
|
|
24
|
+
},
|
|
25
|
+
config: {
|
|
26
|
+
demandOption: false,
|
|
27
|
+
alias: 'c',
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Point to custom/own config file',
|
|
30
|
+
nargs: 1,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
.example('$0 init --uri=mongodb://localhost:27017 --config=./someconfig.json', 'Populates groups and permissions in MongoDB for given URI specified by a custom config file');
|
|
34
|
+
}, async (argv) => {
|
|
35
|
+
try {
|
|
36
|
+
await (0, init_js_1.init)(argv);
|
|
37
|
+
console.log('✓ Database initialization completed successfully');
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error('✗ Initialization failed:', error instanceof Error ? error.message : error);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.usage('Usage: $0 <command> [options]')
|
|
46
|
+
.demandCommand(1, 'Expected a command, e.g. `init`')
|
|
47
|
+
.strict()
|
|
48
|
+
.help()
|
|
49
|
+
.alias('help', 'h')
|
|
50
|
+
.version()
|
|
51
|
+
.alias('version', 'v')
|
|
52
|
+
.parse();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command arguments for the init command.
|
|
3
|
+
*/
|
|
4
|
+
export interface InitCommandArgs {
|
|
5
|
+
/** MongoDB connection URI */
|
|
6
|
+
uri: string;
|
|
7
|
+
/** Optional path to custom configuration file */
|
|
8
|
+
config?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Initializes a MongoDB instance with pre-populated groups and permissions.
|
|
12
|
+
* Connects to the database, loads configuration, and runs the seeding process.
|
|
13
|
+
*
|
|
14
|
+
* @param argv - Command-line arguments containing URI and optional config path
|
|
15
|
+
* @throws Error when configuration cannot be loaded or seeding fails
|
|
16
|
+
*/
|
|
17
|
+
export declare const init: (argv: InitCommandArgs) => Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = void 0;
|
|
4
|
+
const mongodb_1 = require("mongodb");
|
|
5
|
+
const config_js_1 = require("../config.js");
|
|
6
|
+
const seeder_js_1 = require("../seeder.js");
|
|
7
|
+
/**
|
|
8
|
+
* Initializes a MongoDB instance with pre-populated groups and permissions.
|
|
9
|
+
* Connects to the database, loads configuration, and runs the seeding process.
|
|
10
|
+
*
|
|
11
|
+
* @param argv - Command-line arguments containing URI and optional config path
|
|
12
|
+
* @throws Error when configuration cannot be loaded or seeding fails
|
|
13
|
+
*/
|
|
14
|
+
const init = async (argv) => {
|
|
15
|
+
const { uri, config: configFile } = argv;
|
|
16
|
+
let mongoClient = null;
|
|
17
|
+
try {
|
|
18
|
+
mongoClient = await new mongodb_1.MongoClient(uri).connect();
|
|
19
|
+
const config = await config_js_1.ConfigLoader.load('gibbons-mongodb', configFile);
|
|
20
|
+
const mongoDbSeeder = new seeder_js_1.MongoDbSeeder(mongoClient, config);
|
|
21
|
+
await mongoDbSeeder.initialize();
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
if (mongoClient) {
|
|
25
|
+
await mongoClient.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
exports.init = init;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare class ConfigLoader {
|
|
2
|
+
/**
|
|
3
|
+
* Load config from disk, looks for `.gibbons-mongodbrc` file by default
|
|
4
|
+
* @see For Usage {@link https://github.com/davidtheclark/cosmiconfig}
|
|
5
|
+
*
|
|
6
|
+
* @param {string} [module="gibbons-mongodb"]
|
|
7
|
+
* @param {string} [filepath]
|
|
8
|
+
* @throws {Error} When no config file could be resolved
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // Load config from default locations (.gibbons-mongodbrc, package.json, etc.)
|
|
13
|
+
* const config = await ConfigLoader.load();
|
|
14
|
+
*
|
|
15
|
+
* // Load config from specific file
|
|
16
|
+
* const config = await ConfigLoader.load('gibbons-mongodb', './my-config.json');
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
static load(module?: string, filepath?: string): Promise<any>;
|
|
22
|
+
}
|