@flowerforce/flowerbase 1.7.6-beta.6 → 1.7.6-beta.8
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/dist/features/rules/interface.d.ts +6 -5
- package/dist/features/rules/interface.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +41 -28
- package/dist/utils/roles/helpers.d.ts.map +1 -1
- package/dist/utils/roles/helpers.js +6 -3
- package/dist/utils/roles/machines/fieldPermissions.d.ts.map +1 -1
- package/dist/utils/roles/machines/fieldPermissions.js +7 -0
- package/dist/utils/rules-matcher/interface.d.ts +2 -0
- package/dist/utils/rules-matcher/interface.d.ts.map +1 -1
- package/dist/utils/rules-matcher/interface.js +1 -0
- package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
- package/dist/utils/rules-matcher/utils.js +23 -6
- package/package.json +1 -1
- package/src/features/rules/interface.ts +18 -17
- package/src/features/triggers/__tests__/index.test.ts +6 -4
- package/src/services/mongodb-atlas/__tests__/realmCompatibility.test.ts +205 -7
- package/src/services/mongodb-atlas/__tests__/utils.test.ts +27 -0
- package/src/services/mongodb-atlas/index.ts +294 -180
- package/src/utils/__tests__/checkIsValidFieldNameFn.test.ts +50 -5
- package/src/utils/__tests__/evaluateExpression.test.ts +33 -0
- package/src/utils/__tests__/rule.test.ts +38 -0
- package/src/utils/roles/helpers.ts +10 -5
- package/src/utils/roles/machines/fieldPermissions.ts +7 -0
- package/src/utils/rules-matcher/interface.ts +2 -0
- package/src/utils/rules-matcher/utils.ts +33 -17
- package/src/utils/__tests__/readFileContent.test.ts +0 -35
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb'
|
|
2
2
|
import MongoDbAtlas from '..'
|
|
3
|
-
import {
|
|
3
|
+
import { Rules } from '../../../features/rules/interface'
|
|
4
4
|
|
|
5
5
|
const createAppWithCollection = (collection: Record<string, unknown>) => ({
|
|
6
6
|
mongo: {
|
|
@@ -12,12 +12,12 @@ const createAppWithCollection = (collection: Record<string, unknown>) => ({
|
|
|
12
12
|
}
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
const createRules = (
|
|
15
|
+
const createRules = (overrides: Partial<Rules[keyof Rules]> = {}): Rules => ({
|
|
16
16
|
todos: {
|
|
17
17
|
database: 'db',
|
|
18
18
|
collection: 'todos',
|
|
19
|
-
filters: [],
|
|
20
|
-
roles: [
|
|
19
|
+
filters: overrides.filters ?? [],
|
|
20
|
+
roles: overrides.roles ?? [
|
|
21
21
|
{
|
|
22
22
|
name: 'owner',
|
|
23
23
|
apply_when: {},
|
|
@@ -25,10 +25,10 @@ const createRules = (roleOverrides: Partial<Role> = {}): Rules => ({
|
|
|
25
25
|
delete: true,
|
|
26
26
|
search: true,
|
|
27
27
|
read: true,
|
|
28
|
-
write: true
|
|
29
|
-
...roleOverrides
|
|
28
|
+
write: true
|
|
30
29
|
}
|
|
31
|
-
]
|
|
30
|
+
],
|
|
31
|
+
...overrides
|
|
32
32
|
}
|
|
33
33
|
})
|
|
34
34
|
|
|
@@ -268,6 +268,204 @@ describe('mongodb-atlas Realm compatibility', () => {
|
|
|
268
268
|
expect(findOne).toHaveBeenCalledWith({}, undefined)
|
|
269
269
|
})
|
|
270
270
|
|
|
271
|
+
it('keeps write-only field permissions readable in findOne responses', async () => {
|
|
272
|
+
const doc = {
|
|
273
|
+
_id: new ObjectId(),
|
|
274
|
+
name: 'HQ',
|
|
275
|
+
address: 'Main Street 1'
|
|
276
|
+
}
|
|
277
|
+
const findOne = jest.fn().mockResolvedValue(doc)
|
|
278
|
+
const collection = {
|
|
279
|
+
collectionName: 'todos',
|
|
280
|
+
findOne
|
|
281
|
+
}
|
|
282
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
283
|
+
rules: createRules({
|
|
284
|
+
roles: [
|
|
285
|
+
{
|
|
286
|
+
name: 'internal',
|
|
287
|
+
apply_when: {},
|
|
288
|
+
insert: true,
|
|
289
|
+
delete: true,
|
|
290
|
+
search: true,
|
|
291
|
+
read: true,
|
|
292
|
+
write: true,
|
|
293
|
+
fields: {
|
|
294
|
+
name: { write: true },
|
|
295
|
+
address: { write: true }
|
|
296
|
+
}
|
|
297
|
+
} as any
|
|
298
|
+
]
|
|
299
|
+
}),
|
|
300
|
+
user: { id: 'user-1', custom_data: { type: 'internal' } }
|
|
301
|
+
}).db('db').collection('todos')
|
|
302
|
+
|
|
303
|
+
const result = await operators.findOne({ _id: doc._id })
|
|
304
|
+
|
|
305
|
+
expect(result).toEqual(doc)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('keeps write-only field permissions readable in find responses', async () => {
|
|
309
|
+
const doc = {
|
|
310
|
+
_id: new ObjectId(),
|
|
311
|
+
name: 'HQ',
|
|
312
|
+
address: 'Main Street 1'
|
|
313
|
+
}
|
|
314
|
+
const cursor = {
|
|
315
|
+
toArray: jest.fn().mockResolvedValue([doc]),
|
|
316
|
+
sort: jest.fn(),
|
|
317
|
+
skip: jest.fn(),
|
|
318
|
+
limit: jest.fn()
|
|
319
|
+
}
|
|
320
|
+
const find = jest.fn().mockReturnValue(cursor)
|
|
321
|
+
const collection = {
|
|
322
|
+
collectionName: 'todos',
|
|
323
|
+
find
|
|
324
|
+
}
|
|
325
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
326
|
+
rules: createRules({
|
|
327
|
+
roles: [
|
|
328
|
+
{
|
|
329
|
+
name: 'internal',
|
|
330
|
+
apply_when: {},
|
|
331
|
+
insert: true,
|
|
332
|
+
delete: true,
|
|
333
|
+
search: true,
|
|
334
|
+
read: true,
|
|
335
|
+
write: true,
|
|
336
|
+
fields: {
|
|
337
|
+
name: { write: true },
|
|
338
|
+
address: { write: true }
|
|
339
|
+
}
|
|
340
|
+
} as any
|
|
341
|
+
]
|
|
342
|
+
}),
|
|
343
|
+
user: { id: 'user-1', custom_data: { type: 'internal' } }
|
|
344
|
+
}).db('db').collection('todos')
|
|
345
|
+
|
|
346
|
+
const result = await operators.find({ _id: doc._id }).toArray()
|
|
347
|
+
|
|
348
|
+
expect(result).toEqual([doc])
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('supports Realm-style field access where write implies read without explicit top-level read', async () => {
|
|
352
|
+
const doc = {
|
|
353
|
+
_id: new ObjectId(),
|
|
354
|
+
name: 'HQ',
|
|
355
|
+
address: 'Main Street 1',
|
|
356
|
+
city: 'Rome'
|
|
357
|
+
}
|
|
358
|
+
const findOne = jest.fn().mockResolvedValue(doc)
|
|
359
|
+
const collection = {
|
|
360
|
+
collectionName: 'todos',
|
|
361
|
+
findOne
|
|
362
|
+
}
|
|
363
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
364
|
+
rules: createRules({
|
|
365
|
+
roles: [
|
|
366
|
+
{
|
|
367
|
+
name: 'Internal',
|
|
368
|
+
apply_when: { '%%user.custom_data.type': 'internal' },
|
|
369
|
+
fields: {
|
|
370
|
+
name: { write: true },
|
|
371
|
+
address: { write: true }
|
|
372
|
+
}
|
|
373
|
+
} as any
|
|
374
|
+
]
|
|
375
|
+
}),
|
|
376
|
+
user: { id: 'user-1', custom_data: { type: 'internal' } }
|
|
377
|
+
}).db('db').collection('todos')
|
|
378
|
+
|
|
379
|
+
const result = await operators.findOne({ _id: doc._id })
|
|
380
|
+
|
|
381
|
+
expect(result).toEqual({
|
|
382
|
+
_id: doc._id,
|
|
383
|
+
name: 'HQ',
|
|
384
|
+
address: 'Main Street 1'
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
it('supports Realm-style field access with explicit field-level read without top-level read', async () => {
|
|
389
|
+
const doc = {
|
|
390
|
+
_id: new ObjectId(),
|
|
391
|
+
name: 'HQ',
|
|
392
|
+
address: 'Main Street 1',
|
|
393
|
+
city: 'Rome'
|
|
394
|
+
}
|
|
395
|
+
const findOne = jest.fn().mockResolvedValue(doc)
|
|
396
|
+
const collection = {
|
|
397
|
+
collectionName: 'todos',
|
|
398
|
+
findOne
|
|
399
|
+
}
|
|
400
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
401
|
+
rules: createRules({
|
|
402
|
+
roles: [
|
|
403
|
+
{
|
|
404
|
+
name: 'Internal',
|
|
405
|
+
apply_when: { '%%user.custom_data.type': 'internal' },
|
|
406
|
+
fields: {
|
|
407
|
+
name: { read: true },
|
|
408
|
+
address: { read: true }
|
|
409
|
+
}
|
|
410
|
+
} as any
|
|
411
|
+
]
|
|
412
|
+
}),
|
|
413
|
+
user: { id: 'user-1', custom_data: { type: 'internal' } }
|
|
414
|
+
}).db('db').collection('todos')
|
|
415
|
+
|
|
416
|
+
const result = await operators.findOne({ _id: doc._id })
|
|
417
|
+
|
|
418
|
+
expect(result).toEqual({
|
|
419
|
+
_id: doc._id,
|
|
420
|
+
name: 'HQ',
|
|
421
|
+
address: 'Main Street 1'
|
|
422
|
+
})
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it('keeps top-level read access even when fields only define write rules', async () => {
|
|
426
|
+
const doc = {
|
|
427
|
+
_id: new ObjectId(),
|
|
428
|
+
userId: 'user-1',
|
|
429
|
+
email: 'owner@example.com',
|
|
430
|
+
workspaces: ['workspace-1'],
|
|
431
|
+
avatar: 'owner.png',
|
|
432
|
+
name: 'Owner name',
|
|
433
|
+
tags: ['owner'],
|
|
434
|
+
updatedAt: new Date('2026-03-17T10:00:00.000Z')
|
|
435
|
+
}
|
|
436
|
+
const findOne = jest.fn().mockResolvedValue(doc)
|
|
437
|
+
const collection = {
|
|
438
|
+
collectionName: 'todos',
|
|
439
|
+
findOne
|
|
440
|
+
}
|
|
441
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
442
|
+
rules: createRules({
|
|
443
|
+
roles: [
|
|
444
|
+
{
|
|
445
|
+
name: 'all',
|
|
446
|
+
apply_when: { '%%true': true },
|
|
447
|
+
insert: false,
|
|
448
|
+
delete: false,
|
|
449
|
+
search: true,
|
|
450
|
+
read: true,
|
|
451
|
+
fields: {
|
|
452
|
+
avatar: { write: false },
|
|
453
|
+
name: { write: true },
|
|
454
|
+
tags: { write: false },
|
|
455
|
+
updatedAt: { write: true }
|
|
456
|
+
},
|
|
457
|
+
additional_fields: {}
|
|
458
|
+
} as any
|
|
459
|
+
]
|
|
460
|
+
}),
|
|
461
|
+
user: { id: 'user-1', custom_data: { workspaces: ['workspace-1'] } }
|
|
462
|
+
}).db('db').collection('todos')
|
|
463
|
+
|
|
464
|
+
const result = await operators.findOne({ _id: doc._id })
|
|
465
|
+
|
|
466
|
+
expect(result).toEqual(doc)
|
|
467
|
+
})
|
|
468
|
+
|
|
271
469
|
it('returns insertMany insertedIds as an array', async () => {
|
|
272
470
|
const id0 = new ObjectId()
|
|
273
471
|
const id1 = new ObjectId()
|
|
@@ -66,6 +66,33 @@ describe('MongoDB Atlas aggregate helpers', () => {
|
|
|
66
66
|
expect(hidden).toEqual(expect.arrayContaining(['secret', 'hiddenExtra']))
|
|
67
67
|
expect(hidden).not.toContain('visible')
|
|
68
68
|
})
|
|
69
|
+
|
|
70
|
+
it('does not hide fields that are write-only in the rules config', () => {
|
|
71
|
+
const roles: Role[] = [
|
|
72
|
+
{
|
|
73
|
+
name: 'internal',
|
|
74
|
+
apply_when: {},
|
|
75
|
+
insert: true,
|
|
76
|
+
delete: true,
|
|
77
|
+
search: true,
|
|
78
|
+
read: true,
|
|
79
|
+
write: true,
|
|
80
|
+
fields: {
|
|
81
|
+
name: { write: true },
|
|
82
|
+
address: { write: true },
|
|
83
|
+
secret: { read: false, write: false }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
const hidden = getHiddenFieldsFromRulesConfig({
|
|
89
|
+
roles
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(hidden).toContain('secret')
|
|
93
|
+
expect(hidden).not.toContain('name')
|
|
94
|
+
expect(hidden).not.toContain('address')
|
|
95
|
+
})
|
|
69
96
|
})
|
|
70
97
|
|
|
71
98
|
describe('prependUnsetStage', () => {
|