@flowerforce/flowerbase 1.1.2-beta.9 → 1.2.1-beta.10

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +28 -3
  3. package/dist/auth/controller.d.ts.map +1 -1
  4. package/dist/auth/controller.js +57 -3
  5. package/dist/auth/plugins/jwt.d.ts.map +1 -1
  6. package/dist/auth/plugins/jwt.js +49 -3
  7. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  8. package/dist/auth/providers/custom-function/controller.js +19 -3
  9. package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
  10. package/dist/auth/providers/local-userpass/controller.js +125 -71
  11. package/dist/auth/providers/local-userpass/dtos.d.ts +11 -2
  12. package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
  13. package/dist/auth/utils.d.ts +53 -14
  14. package/dist/auth/utils.d.ts.map +1 -1
  15. package/dist/auth/utils.js +46 -63
  16. package/dist/constants.d.ts +13 -0
  17. package/dist/constants.d.ts.map +1 -1
  18. package/dist/constants.js +14 -2
  19. package/dist/features/functions/controller.d.ts.map +1 -1
  20. package/dist/features/functions/controller.js +52 -11
  21. package/dist/features/functions/dtos.d.ts +3 -0
  22. package/dist/features/functions/dtos.d.ts.map +1 -1
  23. package/dist/features/functions/interface.d.ts +3 -0
  24. package/dist/features/functions/interface.d.ts.map +1 -1
  25. package/dist/features/functions/utils.d.ts +2 -1
  26. package/dist/features/functions/utils.d.ts.map +1 -1
  27. package/dist/features/functions/utils.js +19 -7
  28. package/dist/features/rules/utils.d.ts.map +1 -1
  29. package/dist/features/rules/utils.js +11 -2
  30. package/dist/features/triggers/index.d.ts.map +1 -1
  31. package/dist/features/triggers/index.js +48 -7
  32. package/dist/features/triggers/utils.d.ts.map +1 -1
  33. package/dist/features/triggers/utils.js +118 -27
  34. package/dist/index.d.ts +8 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +57 -21
  37. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  38. package/dist/services/mongodb-atlas/index.js +605 -478
  39. package/dist/services/mongodb-atlas/model.d.ts +2 -1
  40. package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
  41. package/dist/services/mongodb-atlas/utils.d.ts +9 -2
  42. package/dist/services/mongodb-atlas/utils.d.ts.map +1 -1
  43. package/dist/services/mongodb-atlas/utils.js +113 -23
  44. package/dist/shared/handleUserRegistration.d.ts.map +1 -1
  45. package/dist/shared/handleUserRegistration.js +1 -0
  46. package/dist/shared/models/handleUserRegistration.model.d.ts +6 -2
  47. package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
  48. package/dist/utils/context/helpers.d.ts +7 -6
  49. package/dist/utils/context/helpers.d.ts.map +1 -1
  50. package/dist/utils/context/helpers.js +3 -0
  51. package/dist/utils/context/index.d.ts +1 -1
  52. package/dist/utils/context/index.d.ts.map +1 -1
  53. package/dist/utils/context/index.js +176 -5
  54. package/dist/utils/context/interface.d.ts +1 -1
  55. package/dist/utils/context/interface.d.ts.map +1 -1
  56. package/dist/utils/crypto/index.d.ts +1 -0
  57. package/dist/utils/crypto/index.d.ts.map +1 -1
  58. package/dist/utils/crypto/index.js +6 -2
  59. package/dist/utils/initializer/exposeRoutes.d.ts.map +1 -1
  60. package/dist/utils/initializer/exposeRoutes.js +11 -4
  61. package/dist/utils/initializer/registerPlugins.d.ts +3 -1
  62. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  63. package/dist/utils/initializer/registerPlugins.js +9 -6
  64. package/dist/utils/roles/helpers.js +11 -3
  65. package/dist/utils/roles/machines/commonValidators.d.ts.map +1 -1
  66. package/dist/utils/roles/machines/commonValidators.js +10 -6
  67. package/dist/utils/roles/machines/read/B/validators.d.ts +4 -0
  68. package/dist/utils/roles/machines/read/B/validators.d.ts.map +1 -0
  69. package/dist/utils/roles/machines/read/B/validators.js +8 -0
  70. package/dist/utils/roles/machines/read/C/index.d.ts.map +1 -1
  71. package/dist/utils/roles/machines/read/C/index.js +10 -7
  72. package/dist/utils/roles/machines/read/C/validators.d.ts +5 -0
  73. package/dist/utils/roles/machines/read/C/validators.d.ts.map +1 -0
  74. package/dist/utils/roles/machines/read/C/validators.js +29 -0
  75. package/dist/utils/roles/machines/read/D/index.d.ts.map +1 -1
  76. package/dist/utils/roles/machines/read/D/index.js +13 -11
  77. package/dist/utils/rules.d.ts +1 -1
  78. package/dist/utils/rules.d.ts.map +1 -1
  79. package/dist/utils/rules.js +26 -17
  80. package/jest.config.ts +2 -12
  81. package/jest.setup.ts +28 -0
  82. package/package.json +1 -2
  83. package/src/auth/controller.ts +70 -4
  84. package/src/auth/plugins/jwt.test.ts +93 -0
  85. package/src/auth/plugins/jwt.ts +62 -3
  86. package/src/auth/providers/custom-function/controller.ts +22 -5
  87. package/src/auth/providers/local-userpass/controller.ts +168 -96
  88. package/src/auth/providers/local-userpass/dtos.ts +13 -2
  89. package/src/auth/utils.ts +51 -86
  90. package/src/constants.ts +16 -3
  91. package/src/fastify.d.ts +32 -15
  92. package/src/features/functions/controller.ts +81 -14
  93. package/src/features/functions/dtos.ts +3 -0
  94. package/src/features/functions/interface.ts +3 -0
  95. package/src/features/functions/utils.ts +29 -8
  96. package/src/features/rules/utils.ts +11 -2
  97. package/src/features/triggers/index.ts +43 -1
  98. package/src/features/triggers/utils.ts +146 -38
  99. package/src/index.ts +69 -20
  100. package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
  101. package/src/services/mongodb-atlas/__tests__/utils.test.ts +141 -0
  102. package/src/services/mongodb-atlas/index.ts +241 -90
  103. package/src/services/mongodb-atlas/model.ts +15 -2
  104. package/src/services/mongodb-atlas/utils.ts +158 -22
  105. package/src/shared/handleUserRegistration.ts +3 -3
  106. package/src/shared/models/handleUserRegistration.model.ts +8 -3
  107. package/src/types/fastify-raw-body.d.ts +22 -0
  108. package/src/utils/__tests__/STEP_B_STATES.test.ts +1 -1
  109. package/src/utils/__tests__/STEP_C_STATES.test.ts +1 -1
  110. package/src/utils/__tests__/STEP_D_STATES.test.ts +2 -2
  111. package/src/utils/__tests__/checkIsValidFieldNameFn.test.ts +9 -4
  112. package/src/utils/__tests__/registerPlugins.test.ts +16 -1
  113. package/src/utils/context/helpers.ts +3 -0
  114. package/src/utils/context/index.ts +238 -13
  115. package/src/utils/context/interface.ts +1 -1
  116. package/src/utils/crypto/index.ts +5 -1
  117. package/src/utils/initializer/exposeRoutes.ts +15 -8
  118. package/src/utils/initializer/registerPlugins.ts +15 -7
  119. package/src/utils/roles/helpers.ts +23 -5
  120. package/src/utils/roles/machines/commonValidators.ts +10 -5
  121. package/src/utils/roles/machines/read/B/validators.ts +8 -0
  122. package/src/utils/roles/machines/read/C/index.ts +11 -7
  123. package/src/utils/roles/machines/read/C/validators.ts +21 -0
  124. package/src/utils/roles/machines/read/D/index.ts +22 -12
  125. package/src/utils/rules.ts +31 -22
  126. package/tsconfig.spec.json +7 -0
package/src/auth/utils.ts CHANGED
@@ -8,19 +8,45 @@ export const LOGIN_SCHEMA = {
8
8
  body: {
9
9
  type: 'object',
10
10
  properties: {
11
- username: { type: 'string' },
12
- password: { type: 'string' }
11
+ username: {
12
+ type: 'string',
13
+ pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
14
+ minLength: 3,
15
+ maxLength: 254
16
+ },
17
+ password: { type: 'string', minLength: 8, maxLength: 128 }
13
18
  },
14
19
  required: ['username', 'password']
15
20
  }
16
21
  }
17
22
 
18
- export const RESET_SCHEMA = {
23
+ export const RESET_SEND_SCHEMA = {
19
24
  body: {
20
25
  type: 'object',
21
26
  properties: {
22
- email: { type: 'string' },
23
- password: { type: 'string' }
27
+ email: {
28
+ type: 'string',
29
+ pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
30
+ minLength: 3,
31
+ maxLength: 254
32
+ }
33
+ },
34
+ required: ['email']
35
+ }
36
+ }
37
+
38
+ export const RESET_CALL_SCHEMA = {
39
+ body: {
40
+ type: 'object',
41
+ properties: {
42
+ email: {
43
+ type: 'string',
44
+ pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
45
+ minLength: 3,
46
+ maxLength: 254
47
+ },
48
+ password: { type: 'string', minLength: 8, maxLength: 128 },
49
+ arguments: { type: 'array' }
24
50
  },
25
51
  required: ['email', 'password']
26
52
  }
@@ -30,7 +56,7 @@ export const CONFIRM_RESET_SCHEMA = {
30
56
  body: {
31
57
  type: 'object',
32
58
  properties: {
33
- password: { type: 'string' },
59
+ password: { type: 'string', minLength: 8, maxLength: 128 },
34
60
  token: { type: 'string' },
35
61
  tokenId: { type: 'string' }
36
62
  },
@@ -38,12 +64,19 @@ export const CONFIRM_RESET_SCHEMA = {
38
64
  }
39
65
  }
40
66
 
67
+ export const RESET_SCHEMA = RESET_SEND_SCHEMA
68
+
41
69
  export const REGISTRATION_SCHEMA = {
42
70
  body: {
43
71
  type: 'object',
44
72
  properties: {
45
- email: { type: 'string' },
46
- password: { type: 'string' }
73
+ email: {
74
+ type: 'string',
75
+ pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
76
+ minLength: 3,
77
+ maxLength: 254
78
+ },
79
+ password: { type: 'string', minLength: 8, maxLength: 128 }
47
80
  },
48
81
  required: ['email', 'password']
49
82
  }
@@ -54,7 +87,8 @@ export enum AUTH_ENDPOINTS {
54
87
  REGISTRATION = '/register',
55
88
  PROFILE = '/profile',
56
89
  SESSION = '/session',
57
- RESET = '/reset/call',
90
+ RESET = '/reset/send',
91
+ RESET_CALL = '/reset/call',
58
92
  CONFIRM_RESET = "/reset",
59
93
  FIRST_USER = '/setup/first-user'
60
94
  }
@@ -62,7 +96,8 @@ export enum AUTH_ENDPOINTS {
62
96
  export enum AUTH_ERRORS {
63
97
  INVALID_CREDENTIALS = 'Invalid credentials',
64
98
  INVALID_TOKEN = 'Invalid refresh token provided',
65
- INVALID_RESET_PARAMS = 'Invalid token or tokenId provided'
99
+ INVALID_RESET_PARAMS = 'Invalid token or tokenId provided',
100
+ MISSING_RESET_FUNCTION = 'Missing reset function'
66
101
  }
67
102
 
68
103
  export interface AuthConfig {
@@ -99,11 +134,6 @@ export interface Config {
99
134
  resetPasswordUrl: string
100
135
  runConfirmationFunction: boolean
101
136
  runResetFunction: boolean
102
- mailConfig: {
103
- from: string
104
- subject: string
105
- mailToken: string
106
- }
107
137
  }
108
138
 
109
139
  export interface CustomUserDataConfig {
@@ -115,13 +145,16 @@ export interface CustomUserDataConfig {
115
145
  on_user_creation_function_name: string
116
146
  }
117
147
 
148
+ const resolveAppPath = () =>
149
+ process.env.FLOWERBASE_APP_PATH ?? require.main?.path ?? process.cwd()
150
+
118
151
 
119
152
  /**
120
153
  * > Loads the auth config json file
121
154
  * @testable
122
155
  */
123
156
  export const loadAuthConfig = (): AuthConfig => {
124
- const authPath = path.join(require.main!.path, 'auth/providers.json')
157
+ const authPath = path.join(resolveAppPath(), 'auth/providers.json')
125
158
  return JSON.parse(fs.readFileSync(authPath, 'utf-8'))
126
159
  }
127
160
 
@@ -130,79 +163,11 @@ export const loadAuthConfig = (): AuthConfig => {
130
163
  * @testable
131
164
  */
132
165
  export const loadCustomUserData = (): CustomUserDataConfig => {
133
- const userDataPath = path.join(require.main!.path, 'auth/custom_user_data.json')
166
+ const userDataPath = path.join(resolveAppPath(), 'auth/custom_user_data.json')
134
167
  return JSON.parse(fs.readFileSync(userDataPath, 'utf-8'))
135
168
  }
136
169
 
137
- export const getMailConfig = (
138
- resetPasswordConfig: Config,
139
- token: string,
140
- tokenId: string
141
- ) => {
142
- const { mailConfig, resetPasswordUrl } = resetPasswordConfig
143
- const ENV_PREFIX = 'ENV'
144
- const { from, subject, mailToken } = mailConfig
145
-
146
- const [fromPrefix, fromPath] = from.split('.')
147
-
148
- if (!fromPath) {
149
- throw new Error(`Invalid fromPath: ${fromPath}`)
150
- }
151
-
152
- const currentSender = (fromPrefix === ENV_PREFIX ? process.env[fromPath] : from) ?? ''
153
- const [subjectPrefix, subjectPath] = subject.split('.')
154
-
155
- if (!subjectPath) {
156
- throw new Error(`Invalid subjectPath: ${subjectPath}`)
157
- }
158
-
159
- const currentSubject =
160
- (subjectPrefix === ENV_PREFIX ? process.env[subjectPath] : subject) ?? ''
161
- const [mailTokenPrefix, mailTokenPath] = mailToken.split('.')
162
-
163
- if (!mailTokenPath) {
164
- throw new Error(`Invalid mailTokenPath: ${mailTokenPath}`)
165
- }
166
-
167
- const currentMailToken =
168
- (mailTokenPrefix === 'ENV' ? process.env[mailTokenPath] : mailToken) ?? ''
169
-
170
- const link = `${resetPasswordUrl}/${token}/${tokenId}`
171
- const body = `<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; text-align: center; padding: 20px;">
172
- <table width="100%" cellspacing="0" cellpadding="0">
173
- <tr>
174
- <td align="center">
175
- <table width="600" cellspacing="0" cellpadding="0" style="background: #ffffff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
176
- <tr>
177
- <td align="center">
178
- <h2>Password Reset Request</h2>
179
- <p>If you requested a password reset, click the button below to reset your password.</p>
180
- <p>If you did not request this, please ignore this email.</p>
181
- <p>
182
- <a href="${link}" style="display: inline-block; padding: 12px 20px; font-size: 16px; color: #ffffff; background: #007bff; text-decoration: none; border-radius: 5px;">Reset Password</a>
183
- </p>
184
- <p style="margin-top: 20px; font-size: 12px; color: #777;">If the button does not work, copy and paste the following link into your browser:</p>
185
- <p style="font-size: 12px; color: #777;">${link}</p>
186
- </td>
187
- </tr>
188
- </table>
189
- </td>
190
- </tr>
191
- </table>
192
- </body>`
193
- return {
194
- from: currentSender ?? '',
195
- subject: currentSubject,
196
- mailToken: currentMailToken,
197
- body
198
- }
199
- }
200
-
201
-
202
-
203
-
204
-
205
170
  export const generatePassword = (length = 20) => {
206
171
  const bytes = crypto.randomBytes(length);
207
172
  return Array.from(bytes, (b) => CHARSET[b % CHARSET.length]).join("");
208
- }
173
+ }
package/src/constants.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { loadAuthConfig, loadCustomUserData } from './auth/utils'
2
+ import { ALLOWED_METHODS } from './'
2
3
 
3
4
  const {
4
5
  database_name,
@@ -15,7 +16,18 @@ export const DEFAULT_CONFIG = {
15
16
  API_VERSION: process.env.API_VERSION || 'v2.0',
16
17
  HTTPS_SCHEMA: process.env.HTTPS_SCHEMA || 'https',
17
18
  HOST: process.env.HOST || '0.0.0.0',
18
- ENABLE_LOGGER: process.env.ENABLE_LOGGER
19
+ ENABLE_LOGGER: process.env.ENABLE_LOGGER,
20
+ RESET_PASSWORD_TTL_SECONDS: Number(process.env.RESET_PASSWORD_TTL_SECONDS) || 3600,
21
+ AUTH_RATE_LIMIT_WINDOW_MS: Number(process.env.AUTH_RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000,
22
+ AUTH_LOGIN_MAX_ATTEMPTS: Number(process.env.AUTH_LOGIN_MAX_ATTEMPTS) || 10,
23
+ AUTH_RESET_MAX_ATTEMPTS: Number(process.env.AUTH_RESET_MAX_ATTEMPTS) || 5,
24
+ REFRESH_TOKEN_TTL_DAYS: Number(process.env.REFRESH_TOKEN_TTL_DAYS) || 60,
25
+ SWAGGER_UI_USER: process.env.SWAGGER_UI_USER || '',
26
+ SWAGGER_UI_PASSWORD: process.env.SWAGGER_UI_PASSWORD || '',
27
+ CORS_OPTIONS: {
28
+ origin: "*",
29
+ methods: ["GET", "POST", "PUT", "DELETE"] as ALLOWED_METHODS[]
30
+ }
19
31
  }
20
32
  export const API_VERSION = `/api/client/${DEFAULT_CONFIG.API_VERSION}`
21
33
  export const HTTPS_SCHEMA = DEFAULT_CONFIG.HTTPS_SCHEMA
@@ -25,7 +37,8 @@ export const DB_NAME = database_name
25
37
  export const AUTH_CONFIG = {
26
38
  authCollection: auth_collection,
27
39
  userCollection: collection_name,
28
- resetPasswordCollection: 'reset-password-requests',
40
+ resetPasswordCollection: 'reset_password_requests',
41
+ refreshTokensCollection: 'auth_refresh_tokens',
29
42
  resetPasswordConfig: configuration['local-userpass']?.config,
30
43
  user_id_field,
31
44
  on_user_creation_function_name,
@@ -39,4 +52,4 @@ export const AUTH_CONFIG = {
39
52
  export const S3_CONFIG = {
40
53
  ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID,
41
54
  SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY
42
- }
55
+ }
package/src/fastify.d.ts CHANGED
@@ -1,7 +1,33 @@
1
- import { FastifyRequest as FastifyRequestType } from 'fastify'
1
+ import { FastifyRequest as FastifyRequestType, FastifyReply } from 'fastify'
2
2
 
3
3
  type User = Record<string, unknown>
4
4
  type UserData = Record<string, unknown>
5
+ type JwtUserData = UserData
6
+
7
+ type JwtAccessPayload = {
8
+ typ: 'access'
9
+ id: string
10
+ user_data: JwtUserData
11
+ data: JwtUserData
12
+ custom_data?: JwtUserData
13
+ }
14
+
15
+ type JwtRefreshPayload = {
16
+ typ: 'refresh'
17
+ baas_id: string
18
+ }
19
+
20
+ type JwtPayload = JwtAccessPayload | JwtRefreshPayload
21
+
22
+ type JwtAccessUser = JwtAccessPayload & {
23
+ sub: string
24
+ }
25
+
26
+ type JwtRefreshUser = JwtRefreshPayload & {
27
+ sub: string
28
+ }
29
+
30
+ type JwtUser = JwtAccessUser | JwtRefreshUser
5
31
 
6
32
  declare module 'fastify' {
7
33
  interface FastifyInstance {
@@ -9,20 +35,11 @@ declare module 'fastify' {
9
35
  createAccessToken(user: User): string
10
36
  createRefreshToken(user: User): string
11
37
  }
38
+ }
12
39
 
13
- interface FastifyRequest {
14
- user:
15
- | {
16
- id: string
17
- typ: 'refresh'
18
- sub: string
19
- user_data: UserData
20
- user: User
21
- }
22
- | {
23
- typ: 'access'
24
- user_data: UserData
25
- id: string
26
- }
40
+ declare module '@fastify/jwt' {
41
+ interface FastifyJWT {
42
+ payload: JwtPayload
43
+ user: JwtUser
27
44
  }
28
45
  }
@@ -1,5 +1,6 @@
1
1
  import { ObjectId } from 'bson'
2
- import { DEFAULT_CONFIG } from '../../constants'
2
+ import { ChangeStream, Document } from 'mongodb';
3
+ import type { FastifyRequest } from 'fastify'
3
4
  import { services } from '../../services'
4
5
  import { StateManager } from '../../state'
5
6
  import { GenerateContext } from '../../utils/context'
@@ -7,6 +8,32 @@ import { Base64Function, FunctionCallBase64Dto, FunctionCallDto } from './dtos'
7
8
  import { FunctionController } from './interface'
8
9
  import { executeQuery } from './utils'
9
10
 
11
+ const normalizeUser = (payload: Record<string, any> | undefined) => {
12
+ if (!payload) return undefined
13
+ const nestedUser =
14
+ payload.data ?? payload.user_data ?? payload.custom_data ?? payload
15
+ const flattened =
16
+ typeof nestedUser === 'object' && nestedUser !== null ? nestedUser : {}
17
+
18
+ return {
19
+ ...payload,
20
+ ...flattened,
21
+ custom_data: payload.custom_data ?? flattened,
22
+ user_data: payload.user_data ?? flattened,
23
+ data: payload.data ?? flattened
24
+ }
25
+ }
26
+
27
+ const getRequestUser = (req: FastifyRequest) => {
28
+ const candidate = req.user as Record<string, any> | undefined
29
+ return normalizeUser(candidate)
30
+ }
31
+
32
+ const logFunctionCall = (method: string, user: Record<string, any> | undefined, args: unknown[]) => {
33
+ if (process.env.DEBUG_FUNCTIONS !== 'true') return
34
+ console.log('[functions-debug]', method, user ? { id: user.id, role: user.role, email: user.email } : 'no-user', args)
35
+ }
36
+
10
37
  /**
11
38
  * > Creates a pre handler for every query
12
39
  * @param app -> the fastify instance
@@ -19,8 +46,13 @@ export const functionsController: FunctionController = async (
19
46
  ) => {
20
47
  app.addHook('preHandler', app.jwtAuthentication)
21
48
 
49
+ const streams = {} as Record<string, ChangeStream<Document, Document>>
50
+
22
51
  app.post<{ Body: FunctionCallDto }>('/call', async (req, res) => {
23
- const { user } = req
52
+ const user = getRequestUser(req)
53
+ if (!user || user.typ !== 'access') {
54
+ throw new Error('Access token required')
55
+ }
24
56
  const { name: method, arguments: args } = req.body
25
57
 
26
58
  if ('service' in req.body) {
@@ -29,16 +61,31 @@ export const functionsController: FunctionController = async (
29
61
  if (!serviceFn) {
30
62
  throw new Error(`Service "${req.body.service}" does not exist`)
31
63
  }
32
- const [{ database, collection, query, update, document, documents, pipeline = [] }] = args
64
+ const [{
65
+ database,
66
+ collection,
67
+ query,
68
+ filter,
69
+ update,
70
+ options,
71
+ returnNewDocument,
72
+ document,
73
+ documents,
74
+ pipeline = []
75
+ }] = args
33
76
 
34
77
  const currentMethod = serviceFn(app, { rules, user })
35
78
  .db(database)
36
79
  .collection(collection)[method]
37
80
 
81
+ logFunctionCall(`service:${req.body.service}:${method}`, user, args)
38
82
  const operatorsByType = await executeQuery({
39
83
  currentMethod,
40
84
  query,
85
+ filter,
41
86
  update,
87
+ options,
88
+ returnNewDocument,
42
89
  document,
43
90
  documents,
44
91
  pipeline,
@@ -57,6 +104,7 @@ export const functionsController: FunctionController = async (
57
104
  throw new Error(`Function "${req.body.name}" is private`)
58
105
  }
59
106
 
107
+ logFunctionCall(`function:${method}`, user, args)
60
108
  const result = await GenerateContext({
61
109
  args: req.body.arguments,
62
110
  app,
@@ -72,7 +120,11 @@ export const functionsController: FunctionController = async (
72
120
  app.get<{
73
121
  Querystring: FunctionCallBase64Dto
74
122
  }>('/call', async (req, res) => {
75
- const { query, user } = req
123
+ const { query } = req
124
+ const user = getRequestUser(req)
125
+ if (!user || user.typ !== 'access') {
126
+ throw new Error('Access token required')
127
+ }
76
128
  const { baas_request, stitch_request } = query
77
129
 
78
130
  const config: Base64Function = JSON.parse(
@@ -88,28 +140,43 @@ export const functionsController: FunctionController = async (
88
140
  'Cache-Control': 'no-cache',
89
141
  'Connection': 'keep-alive',
90
142
  "access-control-allow-credentials": "true",
91
- "access-control-allow-origin": `${DEFAULT_CONFIG.HTTPS_SCHEMA}://${req.headers.host}`,
143
+ "access-control-allow-origin": '*',
92
144
  "access-control-allow-headers": "X-Stitch-Location, X-Baas-Location, Location",
93
- }
145
+ };
94
146
 
95
147
  res.raw.writeHead(200, headers)
96
148
  res.raw.flushHeaders();
97
149
 
98
- const changeStream = await services['mongodb-atlas'](app, {
150
+ const requestKey = baas_request || stitch_request
151
+
152
+ if (!requestKey) return
153
+
154
+ const changeStream = streams[requestKey]
155
+
156
+ if (changeStream) {
157
+ changeStream.on('change', (change) => {
158
+ res.raw.write(`data: ${JSON.stringify(change)}\n\n`);
159
+ });
160
+
161
+ req.raw.on('close', () => {
162
+ console.log("change stream closed");
163
+ changeStream?.close?.();
164
+ delete streams[requestKey]
165
+ });
166
+ return
167
+ }
168
+
169
+ streams[requestKey] = await services['mongodb-atlas'](app, {
99
170
  user,
100
171
  rules
101
172
  })
102
173
  .db(database)
103
174
  .collection(collection)
104
- .watch([], { fullDocument: 'whenAvailable' })
175
+ .watch([], { fullDocument: 'whenAvailable' });
105
176
 
106
- changeStream.on('change', (change) => {
107
- res.raw.write(`data: ${JSON.stringify(change)}\n\n`);
108
- });
109
177
 
110
- req.raw.on('close', () => {
111
- console.log("change stream closed")
112
- changeStream.close();
178
+ streams[requestKey].on('change', (change) => {
179
+ res.raw.write(`data: ${JSON.stringify(change)}\n\n`);
113
180
  });
114
181
  })
115
182
  }
@@ -23,8 +23,11 @@ export type FunctionCallBase64Dto = {
23
23
  type ArgumentsData = Arguments<{
24
24
  database: string
25
25
  collection: string
26
+ filter?: Document
26
27
  query: Parameters<GetOperatorsFunction>
27
28
  update: Document
29
+ options?: Document
30
+ returnNewDocument?: boolean
28
31
  document: Document
29
32
  documents: Document[]
30
33
  pipeline?: Document[]
@@ -24,6 +24,9 @@ export type ExecuteQueryParams = {
24
24
  currentMethod: ReturnType<GetOperatorsFunction>[keyof ReturnType<GetOperatorsFunction>]
25
25
  query: Parameters<GetOperatorsFunction>
26
26
  update: Document
27
+ filter?: Document
28
+ options?: Document
29
+ returnNewDocument?: boolean
27
30
  document: Document
28
31
  documents: Document[]
29
32
  pipeline: Document[]
@@ -44,29 +44,51 @@ export const executeQuery = async ({
44
44
  currentMethod,
45
45
  query,
46
46
  update,
47
+ filter,
48
+ options,
49
+ returnNewDocument,
47
50
  document,
48
51
  documents,
49
52
  pipeline,
50
53
  isClient = false
51
54
  }: ExecuteQueryParams) => {
55
+ const resolvedQuery =
56
+ typeof query !== 'undefined'
57
+ ? query
58
+ : typeof filter !== 'undefined'
59
+ ? filter
60
+ : {}
61
+ const resolvedUpdate = typeof update !== 'undefined' ? update : {}
62
+ const resolvedOptions =
63
+ typeof options !== 'undefined'
64
+ ? options
65
+ : typeof returnNewDocument === 'boolean'
66
+ ? { returnDocument: returnNewDocument ? 'after' : 'before' }
67
+ : undefined
52
68
  return {
53
69
  find: async () =>
54
70
  await (currentMethod as ReturnType<GetOperatorsFunction>['find'])(
55
- EJSON.deserialize(query)
71
+ EJSON.deserialize(resolvedQuery)
56
72
  ).toArray(),
57
73
  findOne: () =>
58
74
  (currentMethod as ReturnType<GetOperatorsFunction>['findOne'])(
59
- EJSON.deserialize(query)
75
+ EJSON.deserialize(resolvedQuery)
60
76
  ),
61
77
  deleteOne: () =>
62
78
  (currentMethod as ReturnType<GetOperatorsFunction>['deleteOne'])(
63
- EJSON.deserialize(query)
79
+ EJSON.deserialize(resolvedQuery)
64
80
  ),
65
81
  insertOne: () =>
66
82
  (currentMethod as ReturnType<GetOperatorsFunction>['insertOne'])(
67
83
  EJSON.deserialize(document)
68
84
  ),
69
- updateOne: () => currentMethod(EJSON.deserialize(query), EJSON.deserialize(update)),
85
+ updateOne: () => currentMethod(EJSON.deserialize(resolvedQuery), EJSON.deserialize(resolvedUpdate)),
86
+ findOneAndUpdate: () =>
87
+ (currentMethod as ReturnType<GetOperatorsFunction>['findOneAndUpdate'])(
88
+ EJSON.deserialize(resolvedQuery),
89
+ EJSON.deserialize(resolvedUpdate),
90
+ resolvedOptions ? EJSON.deserialize(resolvedOptions) : undefined
91
+ ),
70
92
  aggregate: async () =>
71
93
  (await (currentMethod as ReturnType<GetOperatorsFunction>['aggregate'])(
72
94
  EJSON.deserialize(pipeline),
@@ -79,13 +101,12 @@ export const executeQuery = async ({
79
101
  ),
80
102
  updateMany: () =>
81
103
  (currentMethod as ReturnType<GetOperatorsFunction>['updateMany'])(
82
- EJSON.deserialize(query),
83
- EJSON.deserialize(update)
104
+ EJSON.deserialize(resolvedQuery),
105
+ EJSON.deserialize(resolvedUpdate)
84
106
  ),
85
107
  deleteMany: () =>
86
108
  (currentMethod as ReturnType<GetOperatorsFunction>['deleteMany'])(
87
- EJSON.deserialize(query)
109
+ EJSON.deserialize(resolvedQuery)
88
110
  )
89
111
  }
90
112
  }
91
-
@@ -5,11 +5,20 @@ import { Rules, RulesConfig } from './interface'
5
5
 
6
6
  export const loadRules = async (rootDir = process.cwd()): Promise<Rules> => {
7
7
  const rulesRoot = path.join(rootDir, 'data_sources', 'mongodb-atlas')
8
- const files = fs.readdirSync(rulesRoot, { recursive: true }) as string[]
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
+ const files = recursivelyCollectFiles(rulesRoot)
9
18
  const rulesFiles = files.filter((x) => (x as string).endsWith('rules.json'))
10
19
 
11
20
  const rulesByCollection = rulesFiles.reduce((acc, rulesFile) => {
12
- const filePath = path.join(rulesRoot, rulesFile)
21
+ const filePath = rulesFile
13
22
  const collectionRules = readJsonContent(filePath) as RulesConfig
14
23
  acc[collectionRules.collection] = collectionRules
15
24
 
@@ -1,3 +1,4 @@
1
+ import { AUTH_CONFIG, DB_NAME } from '../../constants'
1
2
  import { services } from '../../services'
2
3
  import { Function, Functions } from '../functions/interface'
3
4
  import { ActivateTriggersParams } from './dtos'
@@ -17,7 +18,48 @@ export const activateTriggers = async ({
17
18
  }: ActivateTriggersParams) => {
18
19
  console.log('START ACTIVATION TRIGGERS')
19
20
  try {
20
- for await (const trigger of triggersList) {
21
+ const triggersToActivate = [...triggersList]
22
+ if (AUTH_CONFIG.on_user_creation_function_name) {
23
+ const alreadyDeclared = triggersToActivate.some(
24
+ (trigger) =>
25
+ trigger.content.type === 'AUTHENTICATION' &&
26
+ trigger.content.event_processors?.FUNCTION?.config?.function_name ===
27
+ AUTH_CONFIG.on_user_creation_function_name
28
+ )
29
+ if (!alreadyDeclared) {
30
+ triggersToActivate.push({
31
+ fileName: '__auto_on_user_creation_trigger__.json',
32
+ content: {
33
+ name: 'onUserCreation',
34
+ type: 'AUTHENTICATION',
35
+ disabled: false,
36
+ config: {
37
+ collection: AUTH_CONFIG.authCollection ?? 'auth_users',
38
+ database: DB_NAME,
39
+ full_document: true,
40
+ full_document_before_change: false,
41
+ match: {},
42
+ operation_types: ['insert', 'update', 'replace'],
43
+ project: {},
44
+ service_name: 'mongodb-atlas',
45
+ skip_catchup_events: false,
46
+ tolerate_resume_errors: false,
47
+ unordered: false,
48
+ schedule: ''
49
+ },
50
+ event_processors: {
51
+ FUNCTION: {
52
+ config: {
53
+ function_name: AUTH_CONFIG.on_user_creation_function_name
54
+ }
55
+ }
56
+ }
57
+ }
58
+ })
59
+ }
60
+ }
61
+
62
+ for await (const trigger of triggersToActivate) {
21
63
  const { content } = trigger
22
64
  const { type, config, event_processors } = content
23
65