@flowerforce/flowerbase 1.7.6-beta.0 → 1.7.6-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -1
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +3 -8
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +11 -1
- package/dist/features/encryption/interface.d.ts +36 -0
- package/dist/features/encryption/interface.d.ts.map +1 -0
- package/dist/features/encryption/interface.js +2 -0
- package/dist/features/encryption/utils.d.ts +9 -0
- package/dist/features/encryption/utils.d.ts.map +1 -0
- package/dist/features/encryption/utils.js +34 -0
- package/dist/features/functions/controller.d.ts +2 -0
- package/dist/features/functions/controller.d.ts.map +1 -1
- package/dist/features/functions/controller.js +7 -1
- package/dist/features/rules/utils.d.ts.map +1 -1
- package/dist/features/rules/utils.js +1 -11
- package/dist/features/triggers/index.d.ts.map +1 -1
- package/dist/features/triggers/index.js +4 -0
- package/dist/features/triggers/utils.d.ts.map +1 -1
- package/dist/features/triggers/utils.js +30 -38
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/monitoring/plugin.d.ts.map +1 -1
- package/dist/monitoring/plugin.js +31 -0
- package/dist/services/mongodb-atlas/index.d.ts +3 -0
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +97 -17
- package/dist/services/mongodb-atlas/model.d.ts +2 -1
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +14 -3
- package/dist/utils/initializer/mongodbCSFLE.d.ts +69 -0
- package/dist/utils/initializer/mongodbCSFLE.d.ts.map +1 -0
- package/dist/utils/initializer/mongodbCSFLE.js +131 -0
- package/dist/utils/initializer/registerPlugins.d.ts +5 -1
- package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
- package/dist/utils/initializer/registerPlugins.js +27 -5
- package/package.json +4 -2
- package/src/auth/providers/custom-function/controller.ts +4 -10
- package/src/constants.ts +11 -2
- package/src/features/encryption/interface.ts +46 -0
- package/src/features/encryption/utils.ts +22 -0
- package/src/features/functions/__tests__/watch-filter.test.ts +11 -1
- package/src/features/functions/controller.ts +8 -0
- package/src/features/rules/utils.ts +1 -11
- package/src/features/triggers/index.ts +5 -1
- package/src/features/triggers/utils.ts +31 -42
- package/src/index.ts +10 -2
- package/src/monitoring/plugin.ts +33 -0
- package/src/monitoring/ui.collections.js +7 -10
- package/src/monitoring/ui.css +378 -0
- package/src/monitoring/ui.endpoints.js +5 -10
- package/src/monitoring/ui.events.js +2 -4
- package/src/monitoring/ui.functions.js +64 -71
- package/src/monitoring/ui.html +8 -0
- package/src/monitoring/ui.js +189 -0
- package/src/monitoring/ui.shared.js +237 -2
- package/src/monitoring/ui.triggers.js +2 -3
- package/src/monitoring/ui.users.js +5 -9
- package/src/services/mongodb-atlas/__tests__/watch-filter.test.ts +78 -0
- package/src/services/mongodb-atlas/index.ts +102 -19
- package/src/services/mongodb-atlas/model.ts +3 -1
- package/src/types/fastify-raw-body.d.ts +0 -9
- package/src/utils/__tests__/mongodbCSFLE.test.ts +105 -0
- package/src/utils/index.ts +12 -1
- package/src/utils/initializer/mongodbCSFLE.ts +224 -0
- package/src/utils/initializer/registerPlugins.ts +45 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowerforce/flowerbase",
|
|
3
|
-
"version": "1.7.6-beta.
|
|
3
|
+
"version": "1.7.6-beta.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"test": "npx jest",
|
|
16
16
|
"build": "rm -rf dist/ && tsc",
|
|
17
|
-
"start": "node dist/src/index.ts"
|
|
17
|
+
"start": "node dist/src/index.ts",
|
|
18
|
+
"tsc:noemit": "tsc --noEmit"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [],
|
|
20
21
|
"author": "",
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
"@fastify/swagger-ui": "^5.2.3",
|
|
31
32
|
"@fastify/websocket": "^11.2.0",
|
|
32
33
|
"bson": "^6.8.0",
|
|
34
|
+
"codemirror": "^5.65.16",
|
|
33
35
|
"dotenv": "^16.4.7",
|
|
34
36
|
"fastify": "^5.0.0",
|
|
35
37
|
"fastify-plugin": "^5.0.1",
|
|
@@ -32,10 +32,7 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
32
32
|
app.post<LoginDto>(
|
|
33
33
|
AUTH_ENDPOINTS.LOGIN,
|
|
34
34
|
{
|
|
35
|
-
schema: LOGIN_SCHEMA
|
|
36
|
-
errorHandler: (_error, _request, reply) => {
|
|
37
|
-
reply.code(500).send({ message: 'Internal Server Error' })
|
|
38
|
-
}
|
|
35
|
+
schema: LOGIN_SCHEMA
|
|
39
36
|
},
|
|
40
37
|
async function (req, reply) {
|
|
41
38
|
const customFunctionProvider = AUTH_CONFIG.authProviders?.['custom-function']
|
|
@@ -82,6 +79,7 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
82
79
|
}
|
|
83
80
|
}) as CustomFunctionAuthResult
|
|
84
81
|
|
|
82
|
+
|
|
85
83
|
if (!authResult.id) {
|
|
86
84
|
reply.code(401).send({ message: 'Unauthorized' })
|
|
87
85
|
return
|
|
@@ -130,7 +128,6 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
130
128
|
...(user || {})
|
|
131
129
|
}
|
|
132
130
|
}
|
|
133
|
-
|
|
134
131
|
const refreshToken = this.createRefreshToken(currentUserData)
|
|
135
132
|
const refreshTokenHash = hashToken(refreshToken)
|
|
136
133
|
await authDb.collection(refreshTokensCollection).insertOne({
|
|
@@ -140,15 +137,12 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
140
137
|
expiresAt: new Date(Date.now() + refreshTokenTtlMs),
|
|
141
138
|
revokedAt: null
|
|
142
139
|
})
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const responsePayload = {
|
|
146
|
-
access_token: accessToken,
|
|
140
|
+
return {
|
|
141
|
+
access_token: this.createAccessToken(currentUserData),
|
|
147
142
|
refresh_token: refreshToken,
|
|
148
143
|
device_id: '',
|
|
149
144
|
user_id: authUser._id.toString()
|
|
150
145
|
}
|
|
151
|
-
reply.code(200).send(responsePayload)
|
|
152
146
|
}
|
|
153
147
|
)
|
|
154
148
|
|
package/src/constants.ts
CHANGED
|
@@ -57,6 +57,10 @@ export const DEFAULT_CONFIG = {
|
|
|
57
57
|
CORS_OPTIONS: {
|
|
58
58
|
origin: "*",
|
|
59
59
|
methods: ["GET", "POST", "PUT", "DELETE"] as ALLOWED_METHODS[]
|
|
60
|
+
},
|
|
61
|
+
MONGODB_ENCRYPTION_CONFIG: {
|
|
62
|
+
keyVaultDb: "encryption",
|
|
63
|
+
keyVaultCollection: "__keyVault"
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
export const API_VERSION = `/api/client/${DEFAULT_CONFIG.API_VERSION}`
|
|
@@ -82,9 +86,14 @@ export const AUTH_CONFIG = {
|
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
87
89
|
export const S3_CONFIG = {
|
|
88
90
|
ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID,
|
|
89
91
|
SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY
|
|
90
92
|
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Name of the MongoDB client to use for change streams.
|
|
96
|
+
* This may be a separate instance because streams do not work
|
|
97
|
+
* when the main client has auto encryption enabled.
|
|
98
|
+
*/
|
|
99
|
+
export const CHANGESTREAM = "changestream"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { UUID } from "mongodb"
|
|
2
|
+
|
|
3
|
+
export type EncryptionSchemaProperty =
|
|
4
|
+
| EncryptionSchema
|
|
5
|
+
| {
|
|
6
|
+
encrypt: {
|
|
7
|
+
algorithm: string
|
|
8
|
+
bsonType: string
|
|
9
|
+
keyAlias?: string
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type EncryptionSchema = {
|
|
14
|
+
bsonType: "object"
|
|
15
|
+
properties: Record<string, EncryptionSchemaProperty>
|
|
16
|
+
encryptMetadata?: {
|
|
17
|
+
keyAlias: string
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export type MappedEncryptionSchemaProperty =
|
|
23
|
+
| MappedEncryptionSchema
|
|
24
|
+
| {
|
|
25
|
+
encrypt: {
|
|
26
|
+
algorithm: string
|
|
27
|
+
bsonType: string
|
|
28
|
+
keyId?: [UUID]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type MappedEncryptionSchema = {
|
|
33
|
+
bsonType: "object"
|
|
34
|
+
properties: Record<string, MappedEncryptionSchemaProperty>
|
|
35
|
+
encryptMetadata?: {
|
|
36
|
+
keyId: [UUID]
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type EncryptionSchemaFile = {
|
|
41
|
+
database: string
|
|
42
|
+
collection: string
|
|
43
|
+
schema: EncryptionSchema
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type EncryptionSchemas = Record<string, EncryptionSchema>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { readJsonContent, recursivelyCollectFiles } from "../../utils"
|
|
3
|
+
import { EncryptionSchemaFile, EncryptionSchemas } from "./interface"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @experimental
|
|
7
|
+
* Schemas used for Client-Side Level Encryption configuration.
|
|
8
|
+
*
|
|
9
|
+
* **Important:** These schemas do not perform JSON validation.
|
|
10
|
+
*/
|
|
11
|
+
export const loadEncryptionSchemas = async (rootDir = process.cwd()): Promise<EncryptionSchemas> => {
|
|
12
|
+
const schemasRoot = path.join(rootDir, 'data_sources', 'mongodb-atlas')
|
|
13
|
+
|
|
14
|
+
const files = recursivelyCollectFiles(schemasRoot)
|
|
15
|
+
const schemaFiles = files.filter((x) => x.endsWith('encryption.json'))
|
|
16
|
+
|
|
17
|
+
return schemaFiles.reduce((acc, filePath) => {
|
|
18
|
+
const { collection, database, schema } = readJsonContent(filePath) as EncryptionSchemaFile
|
|
19
|
+
acc[`${database}.${collection}`] = schema
|
|
20
|
+
return acc
|
|
21
|
+
}, {} as EncryptionSchemas)
|
|
22
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
mapWatchFilterToChangeStreamMatch,
|
|
4
|
+
mapWatchFilterToDocumentQuery,
|
|
5
|
+
shouldSkipReadabilityLookupForChange
|
|
6
|
+
} from '../controller'
|
|
3
7
|
|
|
4
8
|
describe('watch filter mapping', () => {
|
|
5
9
|
it('keeps change-event fields untouched and prefixes only document fields', () => {
|
|
@@ -113,4 +117,10 @@ describe('watch filter mapping', () => {
|
|
|
113
117
|
expect(documentQuery._id).toEqual(id)
|
|
114
118
|
expect(documentQuery.operationType).toBeUndefined()
|
|
115
119
|
})
|
|
120
|
+
|
|
121
|
+
it('skips readability lookup only for delete change events', () => {
|
|
122
|
+
expect(shouldSkipReadabilityLookupForChange({ operationType: 'delete' } as any)).toBe(true)
|
|
123
|
+
expect(shouldSkipReadabilityLookupForChange({ operationType: 'update' } as any)).toBe(false)
|
|
124
|
+
expect(shouldSkipReadabilityLookupForChange({ operationType: 'insert' } as any)).toBe(false)
|
|
125
|
+
})
|
|
116
126
|
})
|
|
@@ -315,6 +315,9 @@ const isReadableDocumentResult = (value: unknown) =>
|
|
|
315
315
|
!Array.isArray(value) &&
|
|
316
316
|
Object.keys(value as Record<string, unknown>).length > 0
|
|
317
317
|
|
|
318
|
+
export const shouldSkipReadabilityLookupForChange = (change: Document) =>
|
|
319
|
+
change.operationType === 'delete'
|
|
320
|
+
|
|
318
321
|
/**
|
|
319
322
|
* > Creates a pre handler for every query
|
|
320
323
|
* @param app -> the fastify instance
|
|
@@ -524,6 +527,11 @@ export const functionsController: FunctionController = async (
|
|
|
524
527
|
(change as { fullDocument?: { _id?: unknown } })?.fullDocument?._id
|
|
525
528
|
if (typeof docId === 'undefined') return
|
|
526
529
|
|
|
530
|
+
if (shouldSkipReadabilityLookupForChange(change)) {
|
|
531
|
+
subscriberRes.write(`data: ${serializeEjson(change)}\n\n`)
|
|
532
|
+
return
|
|
533
|
+
}
|
|
534
|
+
|
|
527
535
|
const readQuery = subscriber.documentFilter
|
|
528
536
|
? ({ $and: [subscriber.documentFilter, { _id: docId }] } as Document)
|
|
529
537
|
: ({ _id: docId } as Document)
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
1
|
import path from 'node:path'
|
|
3
|
-
import { readJsonContent } from '../../utils'
|
|
2
|
+
import { readJsonContent, recursivelyCollectFiles } from '../../utils'
|
|
4
3
|
import { Rules, RulesConfig } from './interface'
|
|
5
4
|
|
|
6
5
|
export const loadRules = async (rootDir = process.cwd()): Promise<Rules> => {
|
|
7
6
|
const rulesRoot = path.join(rootDir, 'data_sources', 'mongodb-atlas')
|
|
8
|
-
const recursivelyCollectFiles = (dir: string): string[] => {
|
|
9
|
-
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
10
|
-
const fullPath = path.join(dir, entry.name)
|
|
11
|
-
if (entry.isDirectory()) {
|
|
12
|
-
return recursivelyCollectFiles(fullPath)
|
|
13
|
-
}
|
|
14
|
-
return entry.isFile() ? [fullPath] : []
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
7
|
const files = recursivelyCollectFiles(rulesRoot)
|
|
18
8
|
const rulesFiles = files.filter((x) => (x as string).endsWith('rules.json'))
|
|
19
9
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AUTH_CONFIG, AUTH_DB_NAME } from '../../constants'
|
|
1
|
+
import { AUTH_CONFIG, AUTH_DB_NAME, CHANGESTREAM } from '../../constants'
|
|
2
2
|
import { services } from '../../services'
|
|
3
3
|
import { Function, Functions } from '../functions/interface'
|
|
4
4
|
import { ActivateTriggersParams } from './dtos'
|
|
@@ -18,6 +18,10 @@ export const activateTriggers = async ({
|
|
|
18
18
|
}: ActivateTriggersParams) => {
|
|
19
19
|
console.log('START ACTIVATION TRIGGERS')
|
|
20
20
|
try {
|
|
21
|
+
// Ensure the changestream MongoDB client exist, or use the main client
|
|
22
|
+
if (!fastify.mongo[CHANGESTREAM]) {
|
|
23
|
+
fastify.mongo[CHANGESTREAM] = fastify.mongo
|
|
24
|
+
}
|
|
21
25
|
const triggersToActivate = [...triggersList]
|
|
22
26
|
if (AUTH_CONFIG.on_user_creation_function_name) {
|
|
23
27
|
const alreadyDeclared = triggersToActivate.some(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import cron from 'node-cron'
|
|
4
|
-
import { AUTH_CONFIG, AUTH_DB_NAME, DB_NAME } from '../../constants'
|
|
4
|
+
import { AUTH_CONFIG, AUTH_DB_NAME, DB_NAME, CHANGESTREAM } from '../../constants'
|
|
5
5
|
import { createEventId, sanitize } from '../../monitoring/utils'
|
|
6
6
|
import { StateManager } from '../../state'
|
|
7
7
|
import { readJsonContent } from '../../utils'
|
|
@@ -246,7 +246,7 @@ const handleAuthenticationTrigger = async ({
|
|
|
246
246
|
const { database, isAutoTrigger, operation_types = [], operation_type } = config
|
|
247
247
|
const providerFilter = normalizeProviders(config.providers ?? [])
|
|
248
248
|
const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
|
|
249
|
-
const collection = app.mongo.client.db(database || AUTH_DB_NAME).collection(authCollection)
|
|
249
|
+
const collection = app.mongo[CHANGESTREAM].client.db(database || AUTH_DB_NAME).collection(authCollection)
|
|
250
250
|
const operationCandidates = operation_type ? mapOpInverse[operation_type] : operation_types
|
|
251
251
|
const normalizedOps = normalizeOperationTypes(operationCandidates)
|
|
252
252
|
const baseMeta = {
|
|
@@ -277,17 +277,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
277
277
|
changeStream.on('error', (error) => {
|
|
278
278
|
if (shouldIgnoreStreamError(error)) return
|
|
279
279
|
console.error('Authentication trigger change stream error', error)
|
|
280
|
-
emitTriggerEvent({
|
|
281
|
-
status: 'error',
|
|
282
|
-
triggerName,
|
|
283
|
-
triggerType,
|
|
284
|
-
functionName,
|
|
285
|
-
meta: {
|
|
286
|
-
...baseMeta,
|
|
287
|
-
event: 'CHANGE_STREAM'
|
|
288
|
-
},
|
|
289
|
-
error
|
|
290
|
-
})
|
|
291
280
|
})
|
|
292
281
|
changeStream.on('change', async function (change) {
|
|
293
282
|
const operationType = change['operationType' as keyof typeof change] as
|
|
@@ -376,6 +365,13 @@ const handleAuthenticationTrigger = async ({
|
|
|
376
365
|
updateDescription
|
|
377
366
|
}
|
|
378
367
|
try {
|
|
368
|
+
emitTriggerEvent({
|
|
369
|
+
status: 'fired',
|
|
370
|
+
triggerName,
|
|
371
|
+
triggerType,
|
|
372
|
+
functionName,
|
|
373
|
+
meta: { ...baseMeta, event: 'LOGOUT' }
|
|
374
|
+
})
|
|
379
375
|
await GenerateContext({
|
|
380
376
|
args: [{ user: userData, ...op }],
|
|
381
377
|
app,
|
|
@@ -387,13 +383,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
387
383
|
services,
|
|
388
384
|
runAsSystem: true
|
|
389
385
|
})
|
|
390
|
-
emitTriggerEvent({
|
|
391
|
-
status: 'fired',
|
|
392
|
-
triggerName,
|
|
393
|
-
triggerType,
|
|
394
|
-
functionName,
|
|
395
|
-
meta: { ...baseMeta, event: 'LOGOUT' }
|
|
396
|
-
})
|
|
397
386
|
} catch (error) {
|
|
398
387
|
emitTriggerEvent({
|
|
399
388
|
status: 'error',
|
|
@@ -428,6 +417,13 @@ const handleAuthenticationTrigger = async ({
|
|
|
428
417
|
updateDescription
|
|
429
418
|
}
|
|
430
419
|
try {
|
|
420
|
+
emitTriggerEvent({
|
|
421
|
+
status: 'fired',
|
|
422
|
+
triggerName,
|
|
423
|
+
triggerType,
|
|
424
|
+
functionName,
|
|
425
|
+
meta: { ...baseMeta, event: 'DELETE' }
|
|
426
|
+
})
|
|
431
427
|
await GenerateContext({
|
|
432
428
|
args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
|
|
433
429
|
app,
|
|
@@ -439,13 +435,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
439
435
|
services,
|
|
440
436
|
runAsSystem: true
|
|
441
437
|
})
|
|
442
|
-
emitTriggerEvent({
|
|
443
|
-
status: 'fired',
|
|
444
|
-
triggerName,
|
|
445
|
-
triggerType,
|
|
446
|
-
functionName,
|
|
447
|
-
meta: { ...baseMeta, event: 'DELETE' }
|
|
448
|
-
})
|
|
449
438
|
} catch (error) {
|
|
450
439
|
emitTriggerEvent({
|
|
451
440
|
status: 'error',
|
|
@@ -482,6 +471,13 @@ const handleAuthenticationTrigger = async ({
|
|
|
482
471
|
updateDescription
|
|
483
472
|
}
|
|
484
473
|
try {
|
|
474
|
+
emitTriggerEvent({
|
|
475
|
+
status: 'fired',
|
|
476
|
+
triggerName,
|
|
477
|
+
triggerType,
|
|
478
|
+
functionName,
|
|
479
|
+
meta: { ...baseMeta, event: 'UPDATE' }
|
|
480
|
+
})
|
|
485
481
|
await GenerateContext({
|
|
486
482
|
args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
|
|
487
483
|
app,
|
|
@@ -493,13 +489,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
493
489
|
services,
|
|
494
490
|
runAsSystem: true
|
|
495
491
|
})
|
|
496
|
-
emitTriggerEvent({
|
|
497
|
-
status: 'fired',
|
|
498
|
-
triggerName,
|
|
499
|
-
triggerType,
|
|
500
|
-
functionName,
|
|
501
|
-
meta: { ...baseMeta, event: 'UPDATE' }
|
|
502
|
-
})
|
|
503
492
|
} catch (error) {
|
|
504
493
|
emitTriggerEvent({
|
|
505
494
|
status: 'error',
|
|
@@ -586,6 +575,13 @@ const handleAuthenticationTrigger = async ({
|
|
|
586
575
|
}
|
|
587
576
|
|
|
588
577
|
try {
|
|
578
|
+
emitTriggerEvent({
|
|
579
|
+
status: 'fired',
|
|
580
|
+
triggerName,
|
|
581
|
+
triggerType,
|
|
582
|
+
functionName,
|
|
583
|
+
meta: { ...baseMeta, event: 'CREATE' }
|
|
584
|
+
})
|
|
589
585
|
await GenerateContext({
|
|
590
586
|
args: isAutoTrigger ? [userData] : [{ user: userData, ...op }],
|
|
591
587
|
app,
|
|
@@ -597,13 +593,6 @@ const handleAuthenticationTrigger = async ({
|
|
|
597
593
|
services,
|
|
598
594
|
runAsSystem: true
|
|
599
595
|
})
|
|
600
|
-
emitTriggerEvent({
|
|
601
|
-
status: 'fired',
|
|
602
|
-
triggerName,
|
|
603
|
-
triggerType,
|
|
604
|
-
functionName,
|
|
605
|
-
meta: { ...baseMeta, event: 'CREATE' }
|
|
606
|
-
})
|
|
607
596
|
} catch (error) {
|
|
608
597
|
emitTriggerEvent({
|
|
609
598
|
status: 'error',
|
|
@@ -663,7 +652,7 @@ const handleDataBaseTrigger = async ({
|
|
|
663
652
|
|
|
664
653
|
const normalizedOperations = normalizeOperationTypes(operation_types)
|
|
665
654
|
|
|
666
|
-
const collection = app.mongo.client.db(database).collection(collectionName)
|
|
655
|
+
const collection = app.mongo[CHANGESTREAM].client.db(database).collection(collectionName)
|
|
667
656
|
const pipeline = [
|
|
668
657
|
{
|
|
669
658
|
$match: {
|
package/src/index.ts
CHANGED
|
@@ -6,12 +6,14 @@ import { loadEndpoints } from './features/endpoints/utils'
|
|
|
6
6
|
import { registerFunctions } from './features/functions'
|
|
7
7
|
import { loadFunctions } from './features/functions/utils'
|
|
8
8
|
import { loadRules } from './features/rules/utils'
|
|
9
|
+
import { loadEncryptionSchemas } from './features/encryption/utils'
|
|
9
10
|
import { activateTriggers } from './features/triggers'
|
|
10
11
|
import { loadTriggers } from './features/triggers/utils'
|
|
11
12
|
import { services } from './services'
|
|
12
13
|
import { StateManager } from './state'
|
|
13
14
|
import { exposeRoutes } from './utils/initializer/exposeRoutes'
|
|
14
15
|
import { registerPlugins } from './utils/initializer/registerPlugins'
|
|
16
|
+
import { type MongoDbEncryptionConfig } from './utils/initializer/mongodbCSFLE'
|
|
15
17
|
export * from './model'
|
|
16
18
|
|
|
17
19
|
|
|
@@ -30,6 +32,7 @@ export type InitializeConfig = {
|
|
|
30
32
|
host?: string
|
|
31
33
|
corsConfig?: CorsConfig
|
|
32
34
|
basePath?: string
|
|
35
|
+
mongodbEncryptionConfig?: MongoDbEncryptionConfig
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -47,7 +50,8 @@ export async function initialize({
|
|
|
47
50
|
port = DEFAULT_CONFIG.PORT,
|
|
48
51
|
mongodbUrl = DEFAULT_CONFIG.MONGODB_URL,
|
|
49
52
|
corsConfig = DEFAULT_CONFIG.CORS_OPTIONS,
|
|
50
|
-
basePath
|
|
53
|
+
basePath,
|
|
54
|
+
mongodbEncryptionConfig
|
|
51
55
|
}: InitializeConfig) {
|
|
52
56
|
if (!jwtSecret || jwtSecret.trim().length === 0) {
|
|
53
57
|
throw new Error('JWT secret missing: set JWT_SECRET or pass jwtSecret to initialize()')
|
|
@@ -77,6 +81,8 @@ export async function initialize({
|
|
|
77
81
|
logInfo("Endpoints LOADED")
|
|
78
82
|
const rulesList = await loadRules(resolvedBasePath)
|
|
79
83
|
logInfo("Rules LOADED")
|
|
84
|
+
const encryptionSchemas = await loadEncryptionSchemas(resolvedBasePath)
|
|
85
|
+
logInfo("Encryption schemas LOADED")
|
|
80
86
|
|
|
81
87
|
const stateConfig = {
|
|
82
88
|
functions: functionsList,
|
|
@@ -152,7 +158,9 @@ export async function initialize({
|
|
|
152
158
|
mongodbUrl,
|
|
153
159
|
jwtSecret,
|
|
154
160
|
functionsList,
|
|
155
|
-
corsConfig
|
|
161
|
+
corsConfig,
|
|
162
|
+
encryptionSchemas,
|
|
163
|
+
mongodbEncryptionConfig
|
|
156
164
|
})
|
|
157
165
|
|
|
158
166
|
logInfo('Plugins registration COMPLETED')
|
package/src/monitoring/plugin.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fastifyWebsocket from '@fastify/websocket'
|
|
|
2
2
|
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'
|
|
3
3
|
import fp from 'fastify-plugin'
|
|
4
4
|
import '@fastify/websocket'
|
|
5
|
+
import fs from 'fs'
|
|
5
6
|
import { DEFAULT_CONFIG } from '../constants'
|
|
6
7
|
import { StateManager } from '../state'
|
|
7
8
|
import { registerCollectionRoutes } from './routes/collections'
|
|
@@ -316,6 +317,38 @@ const createMonitoringPlugin = fp(async (
|
|
|
316
317
|
})
|
|
317
318
|
})
|
|
318
319
|
|
|
320
|
+
const resolveCodeMirrorAsset = (internalPath: string) => {
|
|
321
|
+
try {
|
|
322
|
+
return require.resolve(`codemirror/${internalPath}`)
|
|
323
|
+
} catch {
|
|
324
|
+
return ''
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const codemirrorAssets: Record<string, string> = {
|
|
329
|
+
'codemirror.js': resolveCodeMirrorAsset('lib/codemirror.js'),
|
|
330
|
+
'codemirror.css': resolveCodeMirrorAsset('lib/codemirror.css'),
|
|
331
|
+
'javascript.js': resolveCodeMirrorAsset('mode/javascript/javascript.js'),
|
|
332
|
+
'foldcode.js': resolveCodeMirrorAsset('addon/fold/foldcode.js'),
|
|
333
|
+
'foldgutter.js': resolveCodeMirrorAsset('addon/fold/foldgutter.js'),
|
|
334
|
+
'brace-fold.js': resolveCodeMirrorAsset('addon/fold/brace-fold.js'),
|
|
335
|
+
'comment-fold.js': resolveCodeMirrorAsset('addon/fold/comment-fold.js'),
|
|
336
|
+
'foldgutter.css': resolveCodeMirrorAsset('addon/fold/foldgutter.css')
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
Object.entries(codemirrorAssets).forEach(([assetName, relativePath]) => {
|
|
340
|
+
app.get(`${prefix}/vendor/codemirror/${assetName}`, async (_req, reply) => {
|
|
341
|
+
const assetPath = relativePath || ''
|
|
342
|
+
if (!assetPath || !fs.existsSync(assetPath)) {
|
|
343
|
+
reply.code(404).send(`${assetName} not found`)
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
const asset = fs.readFileSync(assetPath, 'utf8')
|
|
347
|
+
reply.header('Cache-Control', 'no-store')
|
|
348
|
+
reply.type(assetName.endsWith('.css') ? 'text/css' : 'application/javascript').send(asset)
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
|
|
319
352
|
app.get(`${prefix}/ws`, { websocket: true }, (connection) => {
|
|
320
353
|
const socket =
|
|
321
354
|
(connection as {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
collectionTabButtons,
|
|
92
92
|
collectionTabPanels
|
|
93
93
|
} = dom;
|
|
94
|
-
const { api, parseJsonObject, highlightJson, safeStringify } = utils;
|
|
94
|
+
const { api, parseJsonObject, highlightJson, renderJsonViewer, clearJsonViewer, safeStringify } = utils;
|
|
95
95
|
|
|
96
96
|
const TABLE_TRUNCATE_LIMIT = 200;
|
|
97
97
|
|
|
@@ -337,22 +337,21 @@
|
|
|
337
337
|
const highlight = state.collectionResultHighlight;
|
|
338
338
|
if (payload === null || payload === undefined) {
|
|
339
339
|
collectionResult.classList.remove('table-view', 'json-highlight');
|
|
340
|
-
collectionResult
|
|
340
|
+
clearJsonViewer(collectionResult, '');
|
|
341
341
|
return;
|
|
342
342
|
}
|
|
343
343
|
if (state.collectionResultView === 'table') {
|
|
344
|
+
clearJsonViewer(collectionResult, '');
|
|
344
345
|
renderCollectionTable(payload);
|
|
345
346
|
return;
|
|
346
347
|
}
|
|
347
348
|
collectionResult.classList.remove('table-view');
|
|
348
349
|
if (highlight) {
|
|
349
350
|
const text = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2);
|
|
350
|
-
collectionResult
|
|
351
|
-
collectionResult.innerHTML = highlightJson(text || '');
|
|
351
|
+
renderJsonViewer(collectionResult, text || '', { collapsible: true });
|
|
352
352
|
return;
|
|
353
353
|
}
|
|
354
|
-
collectionResult
|
|
355
|
-
collectionResult.textContent = typeof payload === 'string' ? payload : String(payload ?? '');
|
|
354
|
+
clearJsonViewer(collectionResult, typeof payload === 'string' ? payload : String(payload ?? ''));
|
|
356
355
|
};
|
|
357
356
|
|
|
358
357
|
const setCollectionResult = (value, highlight) => {
|
|
@@ -365,12 +364,10 @@
|
|
|
365
364
|
if (!collectionRules) return;
|
|
366
365
|
if (highlight) {
|
|
367
366
|
const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
368
|
-
collectionRules
|
|
369
|
-
collectionRules.innerHTML = highlightJson(text || '');
|
|
367
|
+
renderJsonViewer(collectionRules, text || '', { collapsible: true });
|
|
370
368
|
return;
|
|
371
369
|
}
|
|
372
|
-
collectionRules
|
|
373
|
-
collectionRules.textContent = typeof value === 'string' ? value : String(value ?? '');
|
|
370
|
+
clearJsonViewer(collectionRules, typeof value === 'string' ? value : String(value ?? ''));
|
|
374
371
|
};
|
|
375
372
|
|
|
376
373
|
const updateCollectionModeView = () => {
|