@flowerforce/flowerbase 1.2.1-beta.2 → 1.2.1-beta.20

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 (97) hide show
  1. package/README.md +37 -6
  2. package/dist/auth/controller.d.ts.map +1 -1
  3. package/dist/auth/controller.js +55 -4
  4. package/dist/auth/plugins/jwt.d.ts.map +1 -1
  5. package/dist/auth/plugins/jwt.js +52 -6
  6. package/dist/auth/providers/anon-user/controller.d.ts +8 -0
  7. package/dist/auth/providers/anon-user/controller.d.ts.map +1 -0
  8. package/dist/auth/providers/anon-user/controller.js +90 -0
  9. package/dist/auth/providers/anon-user/dtos.d.ts +10 -0
  10. package/dist/auth/providers/anon-user/dtos.d.ts.map +1 -0
  11. package/dist/auth/providers/anon-user/dtos.js +2 -0
  12. package/dist/auth/providers/custom-function/controller.d.ts.map +1 -1
  13. package/dist/auth/providers/custom-function/controller.js +35 -25
  14. package/dist/auth/providers/custom-function/dtos.d.ts +4 -1
  15. package/dist/auth/providers/custom-function/dtos.d.ts.map +1 -1
  16. package/dist/auth/providers/local-userpass/controller.d.ts.map +1 -1
  17. package/dist/auth/providers/local-userpass/controller.js +159 -73
  18. package/dist/auth/providers/local-userpass/dtos.d.ts +17 -2
  19. package/dist/auth/providers/local-userpass/dtos.d.ts.map +1 -1
  20. package/dist/auth/utils.d.ts +76 -14
  21. package/dist/auth/utils.d.ts.map +1 -1
  22. package/dist/auth/utils.js +55 -61
  23. package/dist/constants.d.ts +12 -0
  24. package/dist/constants.d.ts.map +1 -1
  25. package/dist/constants.js +16 -4
  26. package/dist/features/functions/controller.d.ts.map +1 -1
  27. package/dist/features/functions/controller.js +31 -12
  28. package/dist/features/functions/dtos.d.ts +3 -0
  29. package/dist/features/functions/dtos.d.ts.map +1 -1
  30. package/dist/features/functions/interface.d.ts +3 -0
  31. package/dist/features/functions/interface.d.ts.map +1 -1
  32. package/dist/features/functions/utils.d.ts +3 -2
  33. package/dist/features/functions/utils.d.ts.map +1 -1
  34. package/dist/features/functions/utils.js +19 -7
  35. package/dist/features/triggers/index.d.ts.map +1 -1
  36. package/dist/features/triggers/index.js +49 -7
  37. package/dist/features/triggers/interface.d.ts +1 -0
  38. package/dist/features/triggers/interface.d.ts.map +1 -1
  39. package/dist/features/triggers/utils.d.ts.map +1 -1
  40. package/dist/features/triggers/utils.js +67 -26
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +48 -13
  43. package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
  44. package/dist/services/mongodb-atlas/index.js +72 -2
  45. package/dist/services/mongodb-atlas/model.d.ts +3 -2
  46. package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
  47. package/dist/shared/handleUserRegistration.d.ts.map +1 -1
  48. package/dist/shared/handleUserRegistration.js +66 -1
  49. package/dist/shared/models/handleUserRegistration.model.d.ts +2 -1
  50. package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
  51. package/dist/shared/models/handleUserRegistration.model.js +1 -0
  52. package/dist/utils/context/helpers.d.ts +6 -6
  53. package/dist/utils/context/helpers.d.ts.map +1 -1
  54. package/dist/utils/context/index.d.ts +1 -1
  55. package/dist/utils/context/index.d.ts.map +1 -1
  56. package/dist/utils/context/index.js +176 -9
  57. package/dist/utils/context/interface.d.ts +1 -1
  58. package/dist/utils/context/interface.d.ts.map +1 -1
  59. package/dist/utils/crypto/index.d.ts +1 -0
  60. package/dist/utils/crypto/index.d.ts.map +1 -1
  61. package/dist/utils/crypto/index.js +6 -2
  62. package/dist/utils/initializer/exposeRoutes.js +1 -1
  63. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  64. package/dist/utils/initializer/registerPlugins.js +12 -4
  65. package/dist/utils/roles/helpers.js +2 -1
  66. package/package.json +1 -2
  67. package/src/auth/controller.ts +71 -5
  68. package/src/auth/plugins/jwt.test.ts +93 -0
  69. package/src/auth/plugins/jwt.ts +67 -8
  70. package/src/auth/providers/anon-user/controller.ts +91 -0
  71. package/src/auth/providers/anon-user/dtos.ts +10 -0
  72. package/src/auth/providers/custom-function/controller.ts +40 -31
  73. package/src/auth/providers/custom-function/dtos.ts +5 -1
  74. package/src/auth/providers/local-userpass/controller.ts +211 -101
  75. package/src/auth/providers/local-userpass/dtos.ts +20 -2
  76. package/src/auth/utils.ts +66 -83
  77. package/src/constants.ts +14 -2
  78. package/src/features/functions/controller.ts +42 -12
  79. package/src/features/functions/dtos.ts +3 -0
  80. package/src/features/functions/interface.ts +3 -0
  81. package/src/features/functions/utils.ts +29 -8
  82. package/src/features/triggers/index.ts +44 -1
  83. package/src/features/triggers/interface.ts +1 -0
  84. package/src/features/triggers/utils.ts +89 -37
  85. package/src/index.ts +49 -13
  86. package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
  87. package/src/services/mongodb-atlas/index.ts +100 -2
  88. package/src/services/mongodb-atlas/model.ts +16 -3
  89. package/src/shared/handleUserRegistration.ts +83 -2
  90. package/src/shared/models/handleUserRegistration.model.ts +2 -1
  91. package/src/utils/__tests__/registerPlugins.test.ts +5 -1
  92. package/src/utils/context/index.ts +238 -18
  93. package/src/utils/context/interface.ts +1 -1
  94. package/src/utils/crypto/index.ts +5 -1
  95. package/src/utils/initializer/exposeRoutes.ts +1 -1
  96. package/src/utils/initializer/registerPlugins.ts +8 -0
  97. package/src/utils/roles/helpers.ts +3 -2
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,30 @@ export const CONFIRM_RESET_SCHEMA = {
38
64
  }
39
65
  }
40
66
 
67
+ export const CONFIRM_USER_SCHEMA = {
68
+ body: {
69
+ type: 'object',
70
+ properties: {
71
+ token: { type: 'string' },
72
+ tokenId: { type: 'string' }
73
+ },
74
+ required: ['token', 'tokenId']
75
+ }
76
+ }
77
+
78
+ export const RESET_SCHEMA = RESET_SEND_SCHEMA
79
+
41
80
  export const REGISTRATION_SCHEMA = {
42
81
  body: {
43
82
  type: 'object',
44
83
  properties: {
45
- email: { type: 'string' },
46
- password: { type: 'string' }
84
+ email: {
85
+ type: 'string',
86
+ pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
87
+ minLength: 3,
88
+ maxLength: 254
89
+ },
90
+ password: { type: 'string', minLength: 8, maxLength: 128 }
47
91
  },
48
92
  required: ['email', 'password']
49
93
  }
@@ -52,9 +96,11 @@ export const REGISTRATION_SCHEMA = {
52
96
  export enum AUTH_ENDPOINTS {
53
97
  LOGIN = '/login',
54
98
  REGISTRATION = '/register',
99
+ CONFIRM = '/confirm',
55
100
  PROFILE = '/profile',
56
101
  SESSION = '/session',
57
- RESET = '/reset/call',
102
+ RESET = '/reset/send',
103
+ RESET_CALL = '/reset/call',
58
104
  CONFIRM_RESET = "/reset",
59
105
  FIRST_USER = '/setup/first-user'
60
106
  }
@@ -62,7 +108,9 @@ export enum AUTH_ENDPOINTS {
62
108
  export enum AUTH_ERRORS {
63
109
  INVALID_CREDENTIALS = 'Invalid credentials',
64
110
  INVALID_TOKEN = 'Invalid refresh token provided',
65
- INVALID_RESET_PARAMS = 'Invalid token or tokenId provided'
111
+ INVALID_RESET_PARAMS = 'Invalid token or tokenId provided',
112
+ MISSING_RESET_FUNCTION = 'Missing reset function',
113
+ USER_NOT_CONFIRMED = 'User not confirmed'
66
114
  }
67
115
 
68
116
  export interface AuthConfig {
@@ -70,6 +118,7 @@ export interface AuthConfig {
70
118
  'api-key': ApiKey
71
119
  'local-userpass': LocalUserpass
72
120
  'custom-function': CustomFunction
121
+ 'anon-user'?: AnonUser
73
122
  }
74
123
 
75
124
  interface ApiKey {
@@ -93,17 +142,19 @@ interface CustomFunction {
93
142
  }
94
143
  }
95
144
 
145
+ export interface AnonUser {
146
+ name: "anon-user"
147
+ type: "anon-user"
148
+ disabled: boolean
149
+ }
150
+
96
151
  export interface Config {
97
152
  autoConfirm: boolean
153
+ confirmationFunctionName?: string
98
154
  resetFunctionName: string
99
155
  resetPasswordUrl: string
100
156
  runConfirmationFunction: boolean
101
157
  runResetFunction: boolean
102
- mailConfig: {
103
- from: string
104
- subject: string
105
- mailToken: string
106
- }
107
158
  }
108
159
 
109
160
  export interface CustomUserDataConfig {
@@ -137,74 +188,6 @@ export const loadCustomUserData = (): CustomUserDataConfig => {
137
188
  return JSON.parse(fs.readFileSync(userDataPath, 'utf-8'))
138
189
  }
139
190
 
140
- export const getMailConfig = (
141
- resetPasswordConfig: Config,
142
- token: string,
143
- tokenId: string
144
- ) => {
145
- const { mailConfig, resetPasswordUrl } = resetPasswordConfig
146
- const ENV_PREFIX = 'ENV'
147
- const { from, subject, mailToken } = mailConfig
148
-
149
- const [fromPrefix, fromPath] = from.split('.')
150
-
151
- if (!fromPath) {
152
- throw new Error(`Invalid fromPath: ${fromPath}`)
153
- }
154
-
155
- const currentSender = (fromPrefix === ENV_PREFIX ? process.env[fromPath] : from) ?? ''
156
- const [subjectPrefix, subjectPath] = subject.split('.')
157
-
158
- if (!subjectPath) {
159
- throw new Error(`Invalid subjectPath: ${subjectPath}`)
160
- }
161
-
162
- const currentSubject =
163
- (subjectPrefix === ENV_PREFIX ? process.env[subjectPath] : subject) ?? ''
164
- const [mailTokenPrefix, mailTokenPath] = mailToken.split('.')
165
-
166
- if (!mailTokenPath) {
167
- throw new Error(`Invalid mailTokenPath: ${mailTokenPath}`)
168
- }
169
-
170
- const currentMailToken =
171
- (mailTokenPrefix === 'ENV' ? process.env[mailTokenPath] : mailToken) ?? ''
172
-
173
- const link = `${resetPasswordUrl}/${token}/${tokenId}`
174
- const body = `<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; text-align: center; padding: 20px;">
175
- <table width="100%" cellspacing="0" cellpadding="0">
176
- <tr>
177
- <td align="center">
178
- <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);">
179
- <tr>
180
- <td align="center">
181
- <h2>Password Reset Request</h2>
182
- <p>If you requested a password reset, click the button below to reset your password.</p>
183
- <p>If you did not request this, please ignore this email.</p>
184
- <p>
185
- <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>
186
- </p>
187
- <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>
188
- <p style="font-size: 12px; color: #777;">${link}</p>
189
- </td>
190
- </tr>
191
- </table>
192
- </td>
193
- </tr>
194
- </table>
195
- </body>`
196
- return {
197
- from: currentSender ?? '',
198
- subject: currentSubject,
199
- mailToken: currentMailToken,
200
- body
201
- }
202
- }
203
-
204
-
205
-
206
-
207
-
208
191
  export const generatePassword = (length = 20) => {
209
192
  const bytes = crypto.randomBytes(length);
210
193
  return Array.from(bytes, (b) => CHARSET[b % CHARSET.length]).join("");
package/src/constants.ts CHANGED
@@ -17,6 +17,15 @@ export const DEFAULT_CONFIG = {
17
17
  HTTPS_SCHEMA: process.env.HTTPS_SCHEMA || 'https',
18
18
  HOST: process.env.HOST || '0.0.0.0',
19
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_REGISTER_MAX_ATTEMPTS: Number(process.env.AUTH_REGISTER_MAX_ATTEMPTS) || 5,
24
+ AUTH_RESET_MAX_ATTEMPTS: Number(process.env.AUTH_RESET_MAX_ATTEMPTS) || 5,
25
+ REFRESH_TOKEN_TTL_DAYS: Number(process.env.REFRESH_TOKEN_TTL_DAYS) || 60,
26
+ ANON_USER_TTL_SECONDS: Number(process.env.ANON_USER_TTL_SECONDS) || 3 * 60 * 60,
27
+ SWAGGER_UI_USER: process.env.SWAGGER_UI_USER || '',
28
+ SWAGGER_UI_PASSWORD: process.env.SWAGGER_UI_PASSWORD || '',
20
29
  CORS_OPTIONS: {
21
30
  origin: "*",
22
31
  methods: ["GET", "POST", "PUT", "DELETE"] as ALLOWED_METHODS[]
@@ -30,12 +39,15 @@ export const DB_NAME = database_name
30
39
  export const AUTH_CONFIG = {
31
40
  authCollection: auth_collection,
32
41
  userCollection: collection_name,
33
- resetPasswordCollection: 'reset-password-requests',
42
+ resetPasswordCollection: 'reset_password_requests',
43
+ refreshTokensCollection: 'auth_refresh_tokens',
34
44
  resetPasswordConfig: configuration['local-userpass']?.config,
45
+ localUserpassConfig: configuration['local-userpass']?.config,
35
46
  user_id_field,
36
47
  on_user_creation_function_name,
37
48
  providers: {
38
- "custom-function": configuration['custom-function']?.config
49
+ "custom-function": configuration['custom-function']?.config,
50
+ "anon-user": configuration['anon-user']
39
51
  }
40
52
  }
41
53
 
@@ -34,6 +34,13 @@ const logFunctionCall = (method: string, user: Record<string, any> | undefined,
34
34
  console.log('[functions-debug]', method, user ? { id: user.id, role: user.role, email: user.email } : 'no-user', args)
35
35
  }
36
36
 
37
+ const formatFunctionExecutionError = (error: unknown) => {
38
+ const err = error as { message?: string; name?: string }
39
+ const message = typeof err?.message === 'string' ? err.message : String(error)
40
+ const name = typeof err?.name === 'string' ? err.name : 'Error'
41
+ return JSON.stringify({ message, name })
42
+ }
43
+
37
44
  /**
38
45
  * > Creates a pre handler for every query
39
46
  * @param app -> the fastify instance
@@ -61,7 +68,18 @@ export const functionsController: FunctionController = async (
61
68
  if (!serviceFn) {
62
69
  throw new Error(`Service "${req.body.service}" does not exist`)
63
70
  }
64
- const [{ database, collection, query, update, document, documents, pipeline = [] }] = args
71
+ const [{
72
+ database,
73
+ collection,
74
+ query,
75
+ filter,
76
+ update,
77
+ options,
78
+ returnNewDocument,
79
+ document,
80
+ documents,
81
+ pipeline = []
82
+ }] = args
65
83
 
66
84
  const currentMethod = serviceFn(app, { rules, user })
67
85
  .db(database)
@@ -71,7 +89,10 @@ export const functionsController: FunctionController = async (
71
89
  const operatorsByType = await executeQuery({
72
90
  currentMethod,
73
91
  query,
92
+ filter,
74
93
  update,
94
+ options,
95
+ returnNewDocument,
75
96
  document,
76
97
  documents,
77
98
  pipeline,
@@ -91,17 +112,26 @@ export const functionsController: FunctionController = async (
91
112
  }
92
113
 
93
114
  logFunctionCall(`function:${method}`, user, args)
94
- const result = await GenerateContext({
95
- args: req.body.arguments,
96
- app,
97
- rules,
98
- user: { ...user, _id: new ObjectId(user.id) },
99
- currentFunction,
100
- functionsList,
101
- services
102
- })
103
- res.type('application/json')
104
- return JSON.stringify(result)
115
+ try {
116
+ const result = await GenerateContext({
117
+ args: req.body.arguments,
118
+ app,
119
+ rules,
120
+ user: { ...user, _id: new ObjectId(user.id) },
121
+ currentFunction,
122
+ functionsList,
123
+ services
124
+ })
125
+ res.type('application/json')
126
+ return JSON.stringify(result)
127
+ } catch (error) {
128
+ res.status(500)
129
+ res.type('application/json')
130
+ return JSON.stringify({
131
+ error: formatFunctionExecutionError(error),
132
+ error_code: 'FunctionExecutionError'
133
+ })
134
+ }
105
135
  })
106
136
  app.get<{
107
137
  Querystring: FunctionCallBase64Dto
@@ -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
-
@@ -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,49 @@ 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
+ isAutoTrigger: true,
38
+ collection: AUTH_CONFIG.authCollection ?? 'auth_users',
39
+ database: DB_NAME,
40
+ full_document: true,
41
+ full_document_before_change: false,
42
+ match: {},
43
+ operation_types: ['insert', 'update', 'replace'],
44
+ project: {},
45
+ service_name: 'mongodb-atlas',
46
+ skip_catchup_events: false,
47
+ tolerate_resume_errors: false,
48
+ unordered: false,
49
+ schedule: ''
50
+ },
51
+ event_processors: {
52
+ FUNCTION: {
53
+ config: {
54
+ function_name: AUTH_CONFIG.on_user_creation_function_name
55
+ }
56
+ }
57
+ }
58
+ }
59
+ })
60
+ }
61
+ }
62
+
63
+ for await (const trigger of triggersToActivate) {
21
64
  const { content } = trigger
22
65
  const { type, config, event_processors } = content
23
66
 
@@ -21,6 +21,7 @@ type Config = {
21
21
  database: string
22
22
  full_document: boolean
23
23
  full_document_before_change: boolean
24
+ isAutoTrigger?: boolean
24
25
  match: Record<string, unknown>
25
26
  operation_types: string[]
26
27
  project: Record<string, unknown>
@@ -110,55 +110,107 @@ const handleAuthenticationTrigger = async ({
110
110
  services,
111
111
  app
112
112
  }: HandlerParams) => {
113
- const { database } = config
113
+ const { database, isAutoTrigger } = config
114
+ const authCollection = AUTH_CONFIG.authCollection ?? 'auth_users'
115
+ const collection = app.mongo.client.db(database || DB_NAME).collection(authCollection)
114
116
  const pipeline = [
115
117
  {
116
118
  $match: {
117
- operationType: { $in: ['insert'] }
119
+ operationType: { $in: ['insert', 'update', 'replace'] }
118
120
  }
119
121
  }
120
122
  ]
121
- const changeStream = app.mongo.client
122
- .db(database || DB_NAME)
123
- .collection(AUTH_CONFIG.authCollection)
124
- .watch(pipeline, {
125
- fullDocument: 'whenAvailable'
126
- })
123
+ const changeStream = collection.watch(pipeline, {
124
+ fullDocument: 'whenAvailable'
125
+ })
127
126
  changeStream.on('error', (error) => {
128
127
  if (shouldIgnoreStreamError(error)) return
129
128
  console.error('Authentication trigger change stream error', error)
130
129
  })
131
130
  changeStream.on('change', async function (change) {
132
- const document = change['fullDocument' as keyof typeof change] as Record<
133
- string,
134
- string
135
- > //TODO -> define user type
136
-
137
- if (document) {
138
- delete document.password
139
-
140
- const currentUser = { ...document }
141
- delete currentUser.password
142
- await GenerateContext({
143
- args: [{
144
- user: {
145
- ...currentUser,
146
- id: currentUser._id.toString(),
147
- data: {
148
- _id: currentUser._id.toString(),
149
- email: currentUser.email
150
- }
151
- }
152
- }],
153
- app,
154
- rules: StateManager.select("rules"),
155
- user: {}, // TODO from currentUser ??
156
- currentFunction: triggerHandler,
157
- functionsList,
158
- services,
159
- runAsSystem: true
160
- })
131
+ const operationType = change['operationType' as keyof typeof change] as string | undefined
132
+ const documentKey = change['documentKey' as keyof typeof change] as
133
+ | { _id?: unknown }
134
+ | undefined
135
+ const fullDocument = change['fullDocument' as keyof typeof change] as
136
+ | Record<string, unknown>
137
+ | null
138
+ if (!documentKey?._id) {
139
+ return
161
140
  }
141
+
142
+ const updateDescription = change[
143
+ 'updateDescription' as keyof typeof change
144
+ ] as { updatedFields?: Record<string, unknown> } | undefined
145
+ const updatedStatus = updateDescription?.updatedFields?.status
146
+ let confirmedCandidate = false
147
+ let confirmedDocument =
148
+ fullDocument as Record<string, unknown> | null
149
+
150
+ if (operationType === 'update') {
151
+ if (updatedStatus === 'confirmed') {
152
+ confirmedCandidate = true
153
+ } else if (updatedStatus === undefined) {
154
+ const fetched = await collection.findOne({
155
+ _id: documentKey._id
156
+ }) as Record<string, unknown> | null
157
+ confirmedDocument = fetched ?? confirmedDocument
158
+ confirmedCandidate = (confirmedDocument as { status?: string } | null)?.status === 'confirmed'
159
+ }
160
+ } else {
161
+ confirmedCandidate = (confirmedDocument as { status?: string } | null)?.status === 'confirmed'
162
+ }
163
+
164
+ if (!confirmedCandidate) {
165
+ return
166
+ }
167
+
168
+ const updateResult = await collection.findOneAndUpdate(
169
+ {
170
+ _id: documentKey._id,
171
+ status: 'confirmed',
172
+ on_user_creation_triggered_at: { $exists: false }
173
+ },
174
+ {
175
+ $set: {
176
+ on_user_creation_triggered_at: new Date()
177
+ }
178
+ },
179
+ {
180
+ returnDocument: 'after'
181
+ }
182
+ )
183
+
184
+ const document =
185
+ (updateResult?.value as Record<string, unknown> | null) ?? confirmedDocument
186
+ if (!document) {
187
+ return
188
+ }
189
+
190
+ delete (document as { password?: unknown }).password
191
+
192
+ const currentUser = { ...document }
193
+ delete (currentUser as { password?: unknown }).password
194
+
195
+ const userData = {
196
+ ...currentUser,
197
+ id: (currentUser as { _id: { toString: () => string } })._id.toString(),
198
+ data: {
199
+ _id: (currentUser as { _id: { toString: () => string } })._id.toString(),
200
+ email: (currentUser as { email?: string }).email
201
+ }
202
+ }
203
+ // TODO change va ripulito
204
+ await GenerateContext({
205
+ args: isAutoTrigger ? [userData] : [{ user: userData /*, ...change */ }],
206
+ app,
207
+ rules: StateManager.select("rules"),
208
+ user: {}, // TODO from currentUser ??
209
+ currentFunction: triggerHandler,
210
+ functionsList,
211
+ services,
212
+ runAsSystem: true
213
+ })
162
214
  })
163
215
  registerOnClose(
164
216
  app,