@flowerforce/flowerbase 1.7.5 → 1.7.6-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/README.md +125 -1
- package/dist/auth/controller.d.ts.map +1 -1
- package/dist/auth/controller.js +11 -10
- package/dist/auth/plugins/jwt.js +1 -1
- package/dist/auth/providers/anon-user/controller.js +1 -1
- package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
- package/dist/auth/providers/custom-function/controller.js +28 -7
- package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
- package/dist/auth/providers/local-userpass/controller.js +15 -14
- package/dist/auth/utils.d.ts +1 -0
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +14 -3
- 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/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 +5 -1
- package/dist/features/triggers/utils.js +3 -3
- 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/monitoring/routes/users.d.ts.map +1 -1
- package/dist/monitoring/routes/users.js +7 -6
- package/dist/monitoring/utils.d.ts.map +1 -1
- package/dist/monitoring/utils.js +5 -4
- package/dist/services/api/index.d.ts +4 -0
- package/dist/services/api/index.d.ts.map +1 -1
- package/dist/services/api/utils.d.ts +1 -0
- package/dist/services/api/utils.d.ts.map +1 -1
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +9 -7
- package/dist/services/mongodb-atlas/model.d.ts +2 -1
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/shared/handleUserDeletion.js +1 -1
- package/dist/shared/handleUserRegistration.js +2 -2
- package/dist/utils/context/helpers.d.ts +12 -0
- package/dist/utils/context/helpers.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/exposeRoutes.js +1 -1
- 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/dist/utils/rules-matcher/interface.d.ts +5 -1
- package/dist/utils/rules-matcher/interface.d.ts.map +1 -1
- package/dist/utils/rules-matcher/interface.js +2 -0
- package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
- package/dist/utils/rules-matcher/utils.js +51 -16
- package/package.json +4 -2
- package/src/auth/__tests__/controller.test.ts +1 -0
- package/src/auth/controller.ts +12 -11
- package/src/auth/plugins/jwt.ts +2 -2
- package/src/auth/providers/anon-user/__tests__/controller.test.ts +1 -0
- package/src/auth/providers/anon-user/controller.ts +2 -2
- package/src/auth/providers/custom-function/controller.ts +29 -8
- package/src/auth/providers/local-userpass/controller.ts +16 -15
- package/src/auth/utils.ts +1 -0
- package/src/constants.ts +14 -4
- package/src/features/encryption/interface.ts +46 -0
- package/src/features/encryption/utils.ts +22 -0
- package/src/features/rules/utils.ts +1 -11
- package/src/features/triggers/__tests__/index.test.ts +1 -0
- package/src/features/triggers/index.ts +6 -2
- package/src/features/triggers/utils.ts +4 -4
- package/src/index.ts +10 -2
- package/src/monitoring/plugin.ts +33 -0
- package/src/monitoring/routes/users.ts +8 -7
- package/src/monitoring/ui.collections.js +7 -10
- package/src/monitoring/ui.css +383 -1
- package/src/monitoring/ui.endpoints.js +5 -10
- package/src/monitoring/ui.events.js +4 -6
- 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 +239 -3
- package/src/monitoring/ui.triggers.js +2 -3
- package/src/monitoring/ui.users.js +5 -9
- package/src/monitoring/utils.ts +6 -5
- package/src/services/mongodb-atlas/index.ts +10 -13
- package/src/services/mongodb-atlas/model.ts +3 -1
- package/src/shared/handleUserDeletion.ts +2 -2
- package/src/shared/handleUserRegistration.ts +3 -3
- package/src/types/fastify-raw-body.d.ts +0 -9
- package/src/utils/__tests__/mongodbCSFLE.test.ts +105 -0
- package/src/utils/__tests__/operators.test.ts +24 -0
- package/src/utils/__tests__/rule.test.ts +39 -0
- package/src/utils/__tests__/rulesMatcherInterfaces.test.ts +2 -0
- package/src/utils/index.ts +12 -1
- package/src/utils/initializer/exposeRoutes.ts +2 -2
- package/src/utils/initializer/mongodbCSFLE.ts +224 -0
- package/src/utils/initializer/registerPlugins.ts +45 -10
- package/src/utils/rules-matcher/interface.ts +5 -1
- package/src/utils/rules-matcher/utils.ts +78 -32
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ObjectId } from 'bson'
|
|
2
2
|
import { FastifyInstance } from 'fastify'
|
|
3
|
-
import { AUTH_CONFIG,
|
|
3
|
+
import { AUTH_CONFIG, AUTH_DB_NAME, DEFAULT_CONFIG } from '../../../constants'
|
|
4
4
|
import { PROVIDER } from '../../../shared/models/handleUserRegistration.model'
|
|
5
5
|
import { hashToken } from '../../../utils/crypto'
|
|
6
6
|
import { AUTH_ENDPOINTS } from '../../utils'
|
|
@@ -12,7 +12,7 @@ import { LoginDto } from './dtos'
|
|
|
12
12
|
* @param {FastifyInstance} app - The Fastify instance.
|
|
13
13
|
*/
|
|
14
14
|
export async function anonUserController(app: FastifyInstance) {
|
|
15
|
-
const db = app.mongo.client.db(
|
|
15
|
+
const db = app.mongo.client.db(AUTH_DB_NAME)
|
|
16
16
|
const { authCollection, refreshTokensCollection } = AUTH_CONFIG
|
|
17
17
|
const refreshTokenTtlMs = DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000
|
|
18
18
|
const anonUserTtlSeconds = DEFAULT_CONFIG.ANON_USER_TTL_SECONDS
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FastifyInstance } from 'fastify'
|
|
2
|
-
import {
|
|
2
|
+
import { ObjectId } from 'mongodb'
|
|
3
|
+
import { AUTH_CONFIG, AUTH_DB_NAME, DB_NAME, DEFAULT_CONFIG } from '../../../constants'
|
|
3
4
|
import { StateManager } from '../../../state'
|
|
4
5
|
import { GenerateContext } from '../../../utils/context'
|
|
5
6
|
import { hashToken } from '../../../utils/crypto'
|
|
@@ -16,7 +17,8 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
16
17
|
|
|
17
18
|
const functionsList = StateManager.select('functions')
|
|
18
19
|
const services = StateManager.select('services')
|
|
19
|
-
const
|
|
20
|
+
const authDb = app.mongo.client.db(AUTH_DB_NAME)
|
|
21
|
+
const customUserDb = app.mongo.client.db(DB_NAME)
|
|
20
22
|
const { authCollection, refreshTokensCollection, userCollection, user_id_field } = AUTH_CONFIG
|
|
21
23
|
const refreshTokenTtlMs = DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000
|
|
22
24
|
|
|
@@ -54,7 +56,7 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
54
56
|
id
|
|
55
57
|
} = req
|
|
56
58
|
|
|
57
|
-
type CustomFunctionAuthResult = { id?: string }
|
|
59
|
+
type CustomFunctionAuthResult = { id?: string; email?: string }
|
|
58
60
|
const authResult = await GenerateContext({
|
|
59
61
|
args: [
|
|
60
62
|
req.body
|
|
@@ -83,15 +85,34 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
83
85
|
return
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
const
|
|
88
|
+
const email = authResult.email ?? authResult.id
|
|
89
|
+
let authUser = await authDb.collection(authCollection!).findOne({ email })
|
|
87
90
|
if (!authUser) {
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
const authUserId = new ObjectId()
|
|
92
|
+
await authDb.collection(authCollection!).insertOne({
|
|
93
|
+
_id: authUserId,
|
|
94
|
+
email,
|
|
95
|
+
status: 'confirmed',
|
|
96
|
+
createdAt: new Date(),
|
|
97
|
+
custom_data: {},
|
|
98
|
+
identities: [
|
|
99
|
+
{
|
|
100
|
+
id: authResult.id.toString(),
|
|
101
|
+
provider_id: authResult.id.toString(),
|
|
102
|
+
provider_type: 'custom-function',
|
|
103
|
+
provider_data: { email }
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
authUser = {
|
|
108
|
+
_id: authUserId,
|
|
109
|
+
email
|
|
110
|
+
}
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
const user =
|
|
93
114
|
user_id_field && userCollection
|
|
94
|
-
? await
|
|
115
|
+
? await customUserDb
|
|
95
116
|
.collection(userCollection)
|
|
96
117
|
.findOne({ [user_id_field]: authUser._id.toString() })
|
|
97
118
|
: {}
|
|
@@ -109,7 +130,7 @@ export async function customFunctionController(app: FastifyInstance) {
|
|
|
109
130
|
}
|
|
110
131
|
const refreshToken = this.createRefreshToken(currentUserData)
|
|
111
132
|
const refreshTokenHash = hashToken(refreshToken)
|
|
112
|
-
await
|
|
133
|
+
await authDb.collection(refreshTokensCollection).insertOne({
|
|
113
134
|
userId: authUser._id,
|
|
114
135
|
tokenHash: refreshTokenHash,
|
|
115
136
|
createdAt: new Date(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FastifyInstance } from 'fastify'
|
|
2
2
|
import { ObjectId } from 'mongodb'
|
|
3
|
-
import { AUTH_CONFIG, DB_NAME, DEFAULT_CONFIG } from '../../../constants'
|
|
3
|
+
import { AUTH_CONFIG, AUTH_DB_NAME, DB_NAME, DEFAULT_CONFIG } from '../../../constants'
|
|
4
4
|
import handleUserRegistration from '../../../shared/handleUserRegistration'
|
|
5
5
|
import { PROVIDER } from '../../../shared/models/handleUserRegistration.model'
|
|
6
6
|
import { StateManager } from '../../../state'
|
|
@@ -44,7 +44,8 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
44
44
|
const { authCollection, userCollection, user_id_field } = AUTH_CONFIG
|
|
45
45
|
const { resetPasswordCollection } = AUTH_CONFIG
|
|
46
46
|
const { refreshTokensCollection } = AUTH_CONFIG
|
|
47
|
-
const
|
|
47
|
+
const authDb = app.mongo.client.db(AUTH_DB_NAME)
|
|
48
|
+
const customUserDb = app.mongo.client.db(DB_NAME)
|
|
48
49
|
const resetPasswordTtlSeconds = DEFAULT_CONFIG.RESET_PASSWORD_TTL_SECONDS
|
|
49
50
|
const rateLimitWindowMs = DEFAULT_CONFIG.AUTH_RATE_LIMIT_WINDOW_MS
|
|
50
51
|
const loginMaxAttempts = DEFAULT_CONFIG.AUTH_LOGIN_MAX_ATTEMPTS
|
|
@@ -54,7 +55,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
54
55
|
const resolveLocalUserpassProvider = () => AUTH_CONFIG.authProviders?.['local-userpass']
|
|
55
56
|
|
|
56
57
|
try {
|
|
57
|
-
await
|
|
58
|
+
await authDb.collection(resetPasswordCollection).createIndex(
|
|
58
59
|
{ createdAt: 1 },
|
|
59
60
|
{ expireAfterSeconds: resetPasswordTtlSeconds }
|
|
60
61
|
)
|
|
@@ -63,7 +64,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
try {
|
|
66
|
-
await
|
|
67
|
+
await authDb.collection(refreshTokensCollection).createIndex(
|
|
67
68
|
{ expiresAt: 1 },
|
|
68
69
|
{ expireAfterSeconds: 0 }
|
|
69
70
|
)
|
|
@@ -76,7 +77,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
76
77
|
extraArguments?: unknown[]
|
|
77
78
|
) => {
|
|
78
79
|
const { resetPasswordConfig } = AUTH_CONFIG
|
|
79
|
-
const authUser = await
|
|
80
|
+
const authUser = await authDb.collection(authCollection!).findOne({
|
|
80
81
|
email
|
|
81
82
|
})
|
|
82
83
|
|
|
@@ -87,7 +88,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
87
88
|
const token = generateToken()
|
|
88
89
|
const tokenId = generateToken()
|
|
89
90
|
|
|
90
|
-
await
|
|
91
|
+
await authDb
|
|
91
92
|
?.collection(resetPasswordCollection)
|
|
92
93
|
.updateOne(
|
|
93
94
|
{ email },
|
|
@@ -194,7 +195,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
194
195
|
return
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
const existing = await
|
|
198
|
+
const existing = await authDb.collection(authCollection!).findOne({
|
|
198
199
|
confirmationToken: req.body.token,
|
|
199
200
|
confirmationTokenId: req.body.tokenId
|
|
200
201
|
}) as { _id: ObjectId; status?: string } | null
|
|
@@ -205,7 +206,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
if (existing.status !== 'confirmed') {
|
|
208
|
-
await
|
|
209
|
+
await authDb.collection(authCollection!).updateOne(
|
|
209
210
|
{ _id: existing._id },
|
|
210
211
|
{
|
|
211
212
|
$set: { status: 'confirmed' },
|
|
@@ -241,7 +242,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
241
242
|
res.status(429).send({ message: 'Too many requests' })
|
|
242
243
|
return
|
|
243
244
|
}
|
|
244
|
-
const authUser = await
|
|
245
|
+
const authUser = await authDb.collection(authCollection!).findOne({
|
|
245
246
|
email: req.body.username
|
|
246
247
|
})
|
|
247
248
|
|
|
@@ -260,7 +261,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
260
261
|
|
|
261
262
|
const user =
|
|
262
263
|
user_id_field && userCollection
|
|
263
|
-
? await
|
|
264
|
+
? await customUserDb
|
|
264
265
|
.collection(userCollection)
|
|
265
266
|
.findOne({ [user_id_field]: authUser._id.toString() })
|
|
266
267
|
: {}
|
|
@@ -284,7 +285,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
284
285
|
|
|
285
286
|
const refreshToken = this.createRefreshToken(userWithCustomData)
|
|
286
287
|
const refreshTokenHash = hashToken(refreshToken)
|
|
287
|
-
await
|
|
288
|
+
await authDb.collection(refreshTokensCollection).insertOne({
|
|
288
289
|
userId: authUser._id,
|
|
289
290
|
tokenHash: refreshTokenHash,
|
|
290
291
|
createdAt: new Date(),
|
|
@@ -382,7 +383,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
382
383
|
}
|
|
383
384
|
const { token, tokenId, password } = req.body
|
|
384
385
|
|
|
385
|
-
const resetRequest = await
|
|
386
|
+
const resetRequest = await authDb
|
|
386
387
|
?.collection(resetPasswordCollection)
|
|
387
388
|
.findOne({ token, tokenId })
|
|
388
389
|
|
|
@@ -396,11 +397,11 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
396
397
|
Date.now() - createdAt.getTime() > resetPasswordTtlSeconds * 1000
|
|
397
398
|
|
|
398
399
|
if (isExpired) {
|
|
399
|
-
await
|
|
400
|
+
await authDb?.collection(resetPasswordCollection).deleteOne({ _id: resetRequest._id })
|
|
400
401
|
throw new Error(AUTH_ERRORS.INVALID_RESET_PARAMS)
|
|
401
402
|
}
|
|
402
403
|
const hashedPassword = await hashPassword(password)
|
|
403
|
-
await
|
|
404
|
+
await authDb.collection(authCollection!).updateOne(
|
|
404
405
|
{ email: resetRequest.email },
|
|
405
406
|
{
|
|
406
407
|
$set: {
|
|
@@ -409,7 +410,7 @@ export async function localUserPassController(app: FastifyInstance) {
|
|
|
409
410
|
}
|
|
410
411
|
)
|
|
411
412
|
|
|
412
|
-
await
|
|
413
|
+
await authDb?.collection(resetPasswordCollection).deleteOne({ _id: resetRequest._id })
|
|
413
414
|
}
|
|
414
415
|
)
|
|
415
416
|
}
|
package/src/auth/utils.ts
CHANGED
package/src/constants.ts
CHANGED
|
@@ -14,12 +14,12 @@ const monitEnabled = typeof monitEnabledEnv === 'string'
|
|
|
14
14
|
: false
|
|
15
15
|
|
|
16
16
|
const {
|
|
17
|
-
database_name,
|
|
17
|
+
database_name = 'main',
|
|
18
18
|
collection_name = 'users',
|
|
19
19
|
user_id_field = 'id',
|
|
20
20
|
on_user_creation_function_name
|
|
21
21
|
} = loadCustomUserData()
|
|
22
|
-
const { auth_collection = 'auth_users', ...configuration } = loadAuthConfig()
|
|
22
|
+
const { auth_collection = 'auth_users', auth_database, ...configuration } = loadAuthConfig()
|
|
23
23
|
|
|
24
24
|
export const DEFAULT_CONFIG = {
|
|
25
25
|
PORT: Number(process.env.PORT) || 3000,
|
|
@@ -57,11 +57,16 @@ 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}`
|
|
63
67
|
export const HTTPS_SCHEMA = DEFAULT_CONFIG.HTTPS_SCHEMA
|
|
64
68
|
export const DB_NAME = database_name
|
|
69
|
+
export const AUTH_DB_NAME = auth_database ?? database_name
|
|
65
70
|
|
|
66
71
|
type AuthProviders = Record<string, { disabled?: boolean; config?: unknown }>
|
|
67
72
|
// TODO spostare nell'oggetto providers anche le altre configurazioni
|
|
@@ -81,9 +86,14 @@ export const AUTH_CONFIG = {
|
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
|
|
86
89
|
export const S3_CONFIG = {
|
|
87
90
|
ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID,
|
|
88
91
|
SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY
|
|
89
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,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,
|
|
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(
|
|
@@ -36,7 +40,7 @@ export const activateTriggers = async ({
|
|
|
36
40
|
config: {
|
|
37
41
|
isAutoTrigger: true,
|
|
38
42
|
collection: AUTH_CONFIG.authCollection ?? 'auth_users',
|
|
39
|
-
database:
|
|
43
|
+
database: AUTH_DB_NAME,
|
|
40
44
|
full_document: true,
|
|
41
45
|
full_document_before_change: false,
|
|
42
46
|
match: {},
|
|
@@ -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, 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,11 +246,11 @@ 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 ||
|
|
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 = {
|
|
253
|
-
database: database ||
|
|
253
|
+
database: database || AUTH_DB_NAME,
|
|
254
254
|
collection: authCollection,
|
|
255
255
|
operationTypes: normalizedOps,
|
|
256
256
|
providers: providerFilter,
|
|
@@ -652,7 +652,7 @@ const handleDataBaseTrigger = async ({
|
|
|
652
652
|
|
|
653
653
|
const normalizedOperations = normalizeOperationTypes(operation_types)
|
|
654
654
|
|
|
655
|
-
const collection = app.mongo.client.db(database).collection(collectionName)
|
|
655
|
+
const collection = app.mongo[CHANGESTREAM].client.db(database).collection(collectionName)
|
|
656
656
|
const pipeline = [
|
|
657
657
|
{
|
|
658
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 {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FastifyInstance } from 'fastify'
|
|
2
2
|
import { ObjectId } from 'mongodb'
|
|
3
3
|
import { loadAuthConfig, loadCustomUserData, PASSWORD_RULES } from '../../auth/utils'
|
|
4
|
-
import { AUTH_CONFIG, DB_NAME } from '../../constants'
|
|
4
|
+
import { AUTH_CONFIG, AUTH_DB_NAME, DB_NAME } from '../../constants'
|
|
5
5
|
import handleUserRegistration from '../../shared/handleUserRegistration'
|
|
6
6
|
import { PROVIDER } from '../../shared/models/handleUserRegistration.model'
|
|
7
7
|
import { hashPassword } from '../../utils/crypto'
|
|
@@ -47,7 +47,8 @@ export const registerUserRoutes = (app: FastifyInstance, deps: UserRoutesDeps) =
|
|
|
47
47
|
const resolvedAuthLimit = Math.min(Number.isFinite(parsedAuthLimit) && parsedAuthLimit > 0 ? parsedAuthLimit : 100, 500)
|
|
48
48
|
const resolvedCustomLimit = Math.min(Number.isFinite(parsedCustomLimit) && parsedCustomLimit > 0 ? parsedCustomLimit : 25, 500)
|
|
49
49
|
const resolvedCustomPage = Math.max(Number.isFinite(parsedPage) && parsedPage > 0 ? parsedPage : 1, 1)
|
|
50
|
-
const
|
|
50
|
+
const authDb = app.mongo.client.db(AUTH_DB_NAME)
|
|
51
|
+
const customDb = app.mongo.client.db(DB_NAME)
|
|
51
52
|
const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
|
|
52
53
|
const userCollection = AUTH_CONFIG.userCollection
|
|
53
54
|
|
|
@@ -69,7 +70,7 @@ export const registerUserRoutes = (app: FastifyInstance, deps: UserRoutesDeps) =
|
|
|
69
70
|
]
|
|
70
71
|
}
|
|
71
72
|
: {}
|
|
72
|
-
const authItems = await
|
|
73
|
+
const authItems = await authDb
|
|
73
74
|
.collection(authCollection)
|
|
74
75
|
.find(authFilter)
|
|
75
76
|
.sort({ createdAt: -1, _id: -1 })
|
|
@@ -94,11 +95,11 @@ export const registerUserRoutes = (app: FastifyInstance, deps: UserRoutesDeps) =
|
|
|
94
95
|
]
|
|
95
96
|
}
|
|
96
97
|
: {}
|
|
97
|
-
const total = await
|
|
98
|
+
const total = await customDb.collection(userCollection).countDocuments(customFilter)
|
|
98
99
|
const totalPages = Math.max(1, Math.ceil(total / Math.max(resolvedCustomLimit, 1)))
|
|
99
100
|
const page = Math.min(resolvedCustomPage, totalPages)
|
|
100
101
|
const skip = Math.max(0, (page - 1) * resolvedCustomLimit)
|
|
101
|
-
const customItems = await
|
|
102
|
+
const customItems = await customDb
|
|
102
103
|
.collection(userCollection)
|
|
103
104
|
.find(customFilter)
|
|
104
105
|
.sort({ createdAt: -1, _id: -1 })
|
|
@@ -193,7 +194,7 @@ export const registerUserRoutes = (app: FastifyInstance, deps: UserRoutesDeps) =
|
|
|
193
194
|
return { error: passwordError }
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
const db = app.mongo.client.db(
|
|
197
|
+
const db = app.mongo.client.db(AUTH_DB_NAME)
|
|
197
198
|
const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
|
|
198
199
|
const selector: Record<string, unknown> = {}
|
|
199
200
|
|
|
@@ -231,7 +232,7 @@ export const registerUserRoutes = (app: FastifyInstance, deps: UserRoutesDeps) =
|
|
|
231
232
|
app.patch(`${prefix}/api/users/:id/status`, async (req, reply) => {
|
|
232
233
|
const params = req.params as { id: string }
|
|
233
234
|
const body = req.body as { disabled?: boolean; status?: string; email?: string }
|
|
234
|
-
const db = app.mongo.client.db(
|
|
235
|
+
const db = app.mongo.client.db(AUTH_DB_NAME)
|
|
235
236
|
const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
|
|
236
237
|
const selector: Record<string, unknown> = {}
|
|
237
238
|
|
|
@@ -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 = () => {
|