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

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 (103) 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/services/mongodb-atlas/utils.d.ts.map +1 -1
  48. package/dist/services/mongodb-atlas/utils.js +3 -1
  49. package/dist/shared/handleUserRegistration.d.ts.map +1 -1
  50. package/dist/shared/handleUserRegistration.js +66 -1
  51. package/dist/shared/models/handleUserRegistration.model.d.ts +2 -1
  52. package/dist/shared/models/handleUserRegistration.model.d.ts.map +1 -1
  53. package/dist/shared/models/handleUserRegistration.model.js +1 -0
  54. package/dist/utils/context/helpers.d.ts +6 -6
  55. package/dist/utils/context/helpers.d.ts.map +1 -1
  56. package/dist/utils/context/index.d.ts +1 -1
  57. package/dist/utils/context/index.d.ts.map +1 -1
  58. package/dist/utils/context/index.js +176 -9
  59. package/dist/utils/context/interface.d.ts +1 -1
  60. package/dist/utils/context/interface.d.ts.map +1 -1
  61. package/dist/utils/crypto/index.d.ts +1 -0
  62. package/dist/utils/crypto/index.d.ts.map +1 -1
  63. package/dist/utils/crypto/index.js +6 -2
  64. package/dist/utils/initializer/exposeRoutes.js +1 -1
  65. package/dist/utils/initializer/registerPlugins.d.ts.map +1 -1
  66. package/dist/utils/initializer/registerPlugins.js +12 -4
  67. package/dist/utils/roles/helpers.js +2 -1
  68. package/dist/utils/rules-matcher/utils.d.ts.map +1 -1
  69. package/dist/utils/rules-matcher/utils.js +3 -0
  70. package/package.json +1 -2
  71. package/src/auth/controller.ts +71 -5
  72. package/src/auth/plugins/jwt.test.ts +93 -0
  73. package/src/auth/plugins/jwt.ts +67 -8
  74. package/src/auth/providers/anon-user/controller.ts +91 -0
  75. package/src/auth/providers/anon-user/dtos.ts +10 -0
  76. package/src/auth/providers/custom-function/controller.ts +40 -31
  77. package/src/auth/providers/custom-function/dtos.ts +5 -1
  78. package/src/auth/providers/local-userpass/controller.ts +211 -101
  79. package/src/auth/providers/local-userpass/dtos.ts +20 -2
  80. package/src/auth/utils.ts +66 -83
  81. package/src/constants.ts +14 -2
  82. package/src/features/functions/controller.ts +42 -12
  83. package/src/features/functions/dtos.ts +3 -0
  84. package/src/features/functions/interface.ts +3 -0
  85. package/src/features/functions/utils.ts +29 -8
  86. package/src/features/triggers/index.ts +44 -1
  87. package/src/features/triggers/interface.ts +1 -0
  88. package/src/features/triggers/utils.ts +89 -37
  89. package/src/index.ts +49 -13
  90. package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +95 -0
  91. package/src/services/mongodb-atlas/index.ts +665 -567
  92. package/src/services/mongodb-atlas/model.ts +16 -3
  93. package/src/services/mongodb-atlas/utils.ts +3 -0
  94. package/src/shared/handleUserRegistration.ts +83 -2
  95. package/src/shared/models/handleUserRegistration.model.ts +2 -1
  96. package/src/utils/__tests__/registerPlugins.test.ts +5 -1
  97. package/src/utils/context/index.ts +238 -18
  98. package/src/utils/context/interface.ts +1 -1
  99. package/src/utils/crypto/index.ts +5 -1
  100. package/src/utils/initializer/exposeRoutes.ts +1 -1
  101. package/src/utils/initializer/registerPlugins.ts +8 -0
  102. package/src/utils/roles/helpers.ts +3 -2
  103. package/src/utils/rules-matcher/utils.ts +3 -0
@@ -1,42 +1,122 @@
1
- import sendGrid from '@sendgrid/mail'
2
1
  import { FastifyInstance } from 'fastify'
3
- import { AUTH_CONFIG, DB_NAME } from '../../../constants'
4
- import { services } from '../../../services'
2
+ import { ObjectId } from 'mongodb'
3
+ import { AUTH_CONFIG, DB_NAME, DEFAULT_CONFIG } from '../../../constants'
5
4
  import handleUserRegistration from '../../../shared/handleUserRegistration'
6
5
  import { PROVIDER } from '../../../shared/models/handleUserRegistration.model'
7
6
  import { StateManager } from '../../../state'
8
7
  import { GenerateContext } from '../../../utils/context'
9
- import { comparePassword, generateToken, hashPassword } from '../../../utils/crypto'
8
+ import { comparePassword, generateToken, hashPassword, hashToken } from '../../../utils/crypto'
10
9
  import {
11
10
  AUTH_ENDPOINTS,
12
11
  AUTH_ERRORS,
13
12
  CONFIRM_RESET_SCHEMA,
14
- getMailConfig,
13
+ CONFIRM_USER_SCHEMA,
15
14
  LOGIN_SCHEMA,
16
15
  REGISTRATION_SCHEMA,
17
- RESET_SCHEMA
16
+ RESET_CALL_SCHEMA,
17
+ RESET_SEND_SCHEMA
18
18
  } from '../../utils'
19
19
  import {
20
20
  ConfirmResetPasswordDto,
21
+ ConfirmUserDto,
21
22
  LoginDto,
22
23
  RegistrationDto,
23
- ResetPasswordDto
24
+ ResetPasswordCallDto,
25
+ ResetPasswordSendDto
24
26
  } from './dtos'
27
+
28
+ const rateLimitStore = new Map<string, number[]>()
29
+
30
+ const isRateLimited = (key: string, maxAttempts: number, windowMs: number) => {
31
+ const now = Date.now()
32
+ const existing = rateLimitStore.get(key) ?? []
33
+ const recent = existing.filter((timestamp) => now - timestamp < windowMs)
34
+ recent.push(now)
35
+ rateLimitStore.set(key, recent)
36
+ return recent.length > maxAttempts
37
+ }
25
38
  /**
26
39
  * Controller for handling local user registration and login.
27
40
  * @testable
28
41
  * @param {FastifyInstance} app - The Fastify instance.
29
42
  */
30
43
  export async function localUserPassController(app: FastifyInstance) {
31
- const functionsList = StateManager.select('functions')
32
-
33
- const {
34
- authCollection,
35
- userCollection,
36
- user_id_field,
37
- on_user_creation_function_name
38
- } = AUTH_CONFIG
44
+ const { authCollection, userCollection, user_id_field } = AUTH_CONFIG
45
+ const { resetPasswordCollection } = AUTH_CONFIG
46
+ const { refreshTokensCollection } = AUTH_CONFIG
39
47
  const db = app.mongo.client.db(DB_NAME)
48
+ const resetPasswordTtlSeconds = DEFAULT_CONFIG.RESET_PASSWORD_TTL_SECONDS
49
+ const rateLimitWindowMs = DEFAULT_CONFIG.AUTH_RATE_LIMIT_WINDOW_MS
50
+ const loginMaxAttempts = DEFAULT_CONFIG.AUTH_LOGIN_MAX_ATTEMPTS
51
+ const registerMaxAttempts = DEFAULT_CONFIG.AUTH_REGISTER_MAX_ATTEMPTS
52
+ const resetMaxAttempts = DEFAULT_CONFIG.AUTH_RESET_MAX_ATTEMPTS
53
+ const refreshTokenTtlMs = DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000
54
+
55
+ try {
56
+ await db.collection(resetPasswordCollection).createIndex(
57
+ { createdAt: 1 },
58
+ { expireAfterSeconds: resetPasswordTtlSeconds }
59
+ )
60
+ } catch (error) {
61
+ console.error('Failed to ensure reset password TTL index', error)
62
+ }
63
+
64
+ try {
65
+ await db.collection(refreshTokensCollection).createIndex(
66
+ { expiresAt: 1 },
67
+ { expireAfterSeconds: 0 }
68
+ )
69
+ } catch (error) {
70
+ console.error('Failed to ensure refresh token TTL index', error)
71
+ }
72
+ const handleResetPasswordRequest = async (
73
+ email: string,
74
+ password?: string,
75
+ extraArguments?: unknown[]
76
+ ) => {
77
+ const { resetPasswordConfig } = AUTH_CONFIG
78
+ const authUser = await db.collection(authCollection!).findOne({
79
+ email
80
+ })
81
+
82
+ if (!authUser) {
83
+ return
84
+ }
85
+
86
+ const token = generateToken()
87
+ const tokenId = generateToken()
88
+
89
+ await db
90
+ ?.collection(resetPasswordCollection)
91
+ .updateOne(
92
+ { email },
93
+ { $set: { token, tokenId, email, createdAt: new Date() } },
94
+ { upsert: true }
95
+ )
96
+
97
+ if (!resetPasswordConfig.runResetFunction && !resetPasswordConfig.resetFunctionName) {
98
+ throw new Error(AUTH_ERRORS.MISSING_RESET_FUNCTION)
99
+ }
100
+
101
+ if (resetPasswordConfig.runResetFunction && resetPasswordConfig.resetFunctionName) {
102
+ const functionsList = StateManager.select('functions')
103
+ const services = StateManager.select('services')
104
+ const currentFunction = functionsList[resetPasswordConfig.resetFunctionName]
105
+ const baseArgs = { token, tokenId, email, password, username: email }
106
+ const args = Array.isArray(extraArguments) ? [baseArgs, ...extraArguments] : [baseArgs]
107
+ await GenerateContext({
108
+ args,
109
+ app,
110
+ rules: {},
111
+ user: {},
112
+ currentFunction,
113
+ functionsList,
114
+ services
115
+ })
116
+ return
117
+ }
118
+
119
+ }
40
120
 
41
121
  /**
42
122
  * Endpoint for user registration.
@@ -52,6 +132,11 @@ export async function localUserPassController(app: FastifyInstance) {
52
132
  schema: REGISTRATION_SCHEMA
53
133
  },
54
134
  async (req, res) => {
135
+ const key = `register:${req.ip}`
136
+ if (isRateLimited(key, registerMaxAttempts, rateLimitWindowMs)) {
137
+ res.status(429).send({ message: 'Too many requests' })
138
+ return
139
+ }
55
140
 
56
141
  const result = await handleUserRegistration(app, { run_as_system: true, provider: PROVIDER.LOCAL_USERPASS })({ email: req.body.email.toLowerCase(), password: req.body.password })
57
142
 
@@ -65,6 +150,50 @@ export async function localUserPassController(app: FastifyInstance) {
65
150
  }
66
151
  )
67
152
 
153
+ /**
154
+ * Endpoint for confirming a user registration.
155
+ *
156
+ * @route {POST} /confirm
157
+ * @param {ConfirmUserDto} req - The request object with confirmation data.
158
+ * @returns {Promise<Object>} A promise resolving with confirmation status.
159
+ */
160
+ app.post<ConfirmUserDto>(
161
+ AUTH_ENDPOINTS.CONFIRM,
162
+ {
163
+ schema: CONFIRM_USER_SCHEMA
164
+ },
165
+ async (req, res) => {
166
+ const key = `confirm:${req.ip}`
167
+ if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
168
+ res.status(429).send({ message: 'Too many requests' })
169
+ return
170
+ }
171
+
172
+ const existing = await db.collection(authCollection!).findOne({
173
+ confirmationToken: req.body.token,
174
+ confirmationTokenId: req.body.tokenId
175
+ }) as { _id: ObjectId; status?: string } | null
176
+
177
+ if (!existing) {
178
+ res.status(500)
179
+ throw new Error(AUTH_ERRORS.INVALID_TOKEN)
180
+ }
181
+
182
+ if (existing.status !== 'confirmed') {
183
+ await db.collection(authCollection!).updateOne(
184
+ { _id: existing._id },
185
+ {
186
+ $set: { status: 'confirmed' },
187
+ $unset: { confirmationToken: '', confirmationTokenId: '' }
188
+ }
189
+ )
190
+ }
191
+
192
+ res.status(200)
193
+ return { status: 'confirmed' }
194
+ }
195
+ )
196
+
68
197
  /**
69
198
  * Endpoint for user login.
70
199
  *
@@ -77,7 +206,12 @@ export async function localUserPassController(app: FastifyInstance) {
77
206
  {
78
207
  schema: LOGIN_SCHEMA
79
208
  },
80
- async function (req) {
209
+ async function (req, res) {
210
+ const key = `login:${req.ip}`
211
+ if (isRateLimited(key, loginMaxAttempts, rateLimitWindowMs)) {
212
+ res.status(429).send({ message: 'Too many requests' })
213
+ return
214
+ }
81
215
  const authUser = await db.collection(authCollection!).findOne({
82
216
  email: req.body.username
83
217
  })
@@ -110,45 +244,23 @@ export async function localUserPassController(app: FastifyInstance) {
110
244
  id: authUser._id.toString()
111
245
  }
112
246
 
113
- if (authUser && authUser.status === 'pending') {
114
- try {
115
- await db?.collection(authCollection!).updateOne(
116
- { _id: authUser._id },
117
- {
118
- $set: {
119
- status: 'confirmed'
120
- }
121
- }
122
- )
123
- } catch (error) {
124
- console.log('>>> 🚀 ~ localUserPassController ~ error:', error)
125
- }
247
+ if (authUser && authUser.status !== 'confirmed') {
248
+ throw new Error(AUTH_ERRORS.USER_NOT_CONFIRMED)
126
249
  }
127
250
 
128
- if (
129
- authUser &&
130
- authUser.status === 'pending' &&
131
- on_user_creation_function_name &&
132
- functionsList[on_user_creation_function_name]
133
- ) {
134
- try {
135
- await GenerateContext({
136
- args: [userWithCustomData],
137
- app,
138
- rules: {},
139
- user: userWithCustomData,
140
- currentFunction: functionsList[on_user_creation_function_name],
141
- functionsList,
142
- services
143
- })
144
- } catch (error) {
145
- console.log('localUserPassController - /login - GenerateContext - CATCH:', error)
146
- }
147
- }
251
+ const refreshToken = this.createRefreshToken(userWithCustomData)
252
+ const refreshTokenHash = hashToken(refreshToken)
253
+ await db.collection(refreshTokensCollection).insertOne({
254
+ userId: authUser._id,
255
+ tokenHash: refreshTokenHash,
256
+ createdAt: new Date(),
257
+ expiresAt: new Date(Date.now() + refreshTokenTtlMs),
258
+ revokedAt: null
259
+ })
148
260
 
149
261
  return {
150
262
  access_token: this.createAccessToken(userWithCustomData),
151
- refresh_token: this.createRefreshToken(userWithCustomData),
263
+ refresh_token: refreshToken,
152
264
  device_id: '',
153
265
  user_id: authUser._id.toString()
154
266
  }
@@ -158,65 +270,49 @@ export async function localUserPassController(app: FastifyInstance) {
158
270
  /**
159
271
  * Endpoint for reset password.
160
272
  *
161
- * @route {POST} /reset/call
273
+ * @route {POST} /reset/send
162
274
  * @param {ResetPasswordDto} req - The request object with th reset request.
163
275
  * @returns {Promise<void>}
164
276
  */
165
- app.post<ResetPasswordDto>(
277
+ app.post<ResetPasswordSendDto>(
166
278
  AUTH_ENDPOINTS.RESET,
167
279
  {
168
- schema: RESET_SCHEMA
280
+ schema: RESET_SEND_SCHEMA
169
281
  },
170
- async function (req) {
171
- const { resetPasswordCollection, resetPasswordConfig } = AUTH_CONFIG
172
- const email = req.body.email
173
- const authUser = await db.collection(authCollection!).findOne({
174
- email
175
- })
176
-
177
- if (!authUser) {
178
- throw new Error(AUTH_ERRORS.INVALID_CREDENTIALS)
282
+ async function (req, res) {
283
+ const key = `reset:${req.ip}`
284
+ if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
285
+ res.status(429)
286
+ return { message: 'Too many requests' }
179
287
  }
180
-
181
- const token = generateToken()
182
- const tokenId = generateToken()
183
-
184
- await db
185
- ?.collection(resetPasswordCollection)
186
- .updateOne(
187
- { email },
188
- { $set: { token, tokenId, email, createdAt: new Date() } },
189
- { upsert: true }
190
- )
191
-
192
- if (resetPasswordConfig.runResetFunction && resetPasswordConfig.resetFunctionName) {
193
- const functionsList = StateManager.select('functions')
194
- const services = StateManager.select('services')
195
- const currentFunction = functionsList[resetPasswordConfig.resetFunctionName]
196
- await GenerateContext({
197
- args: [{ token, tokenId, email }],
198
- app,
199
- rules: {},
200
- user: {},
201
- currentFunction,
202
- functionsList,
203
- services
204
- })
205
- return
288
+ await handleResetPasswordRequest(req.body.email)
289
+ res.status(202)
290
+ return {
291
+ status: 'ok'
206
292
  }
293
+ }
294
+ )
207
295
 
208
- const { from, subject, mailToken, body } = getMailConfig(
209
- resetPasswordConfig,
210
- token,
211
- tokenId
296
+ app.post<ResetPasswordCallDto>(
297
+ AUTH_ENDPOINTS.RESET_CALL,
298
+ {
299
+ schema: RESET_CALL_SCHEMA
300
+ },
301
+ async function (req, res) {
302
+ const key = `reset:${req.ip}`
303
+ if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
304
+ res.status(429)
305
+ return { message: 'Too many requests' }
306
+ }
307
+ await handleResetPasswordRequest(
308
+ req.body.email,
309
+ req.body.password,
310
+ req.body.arguments
212
311
  )
213
- sendGrid.setApiKey(mailToken)
214
- await sendGrid.send({
215
- to: email,
216
- from,
217
- subject,
218
- html: body
219
- })
312
+ res.status(202)
313
+ return {
314
+ status: 'ok'
315
+ }
220
316
  }
221
317
  )
222
318
 
@@ -232,8 +328,12 @@ export async function localUserPassController(app: FastifyInstance) {
232
328
  {
233
329
  schema: CONFIRM_RESET_SCHEMA
234
330
  },
235
- async function (req) {
236
- const { resetPasswordCollection } = AUTH_CONFIG
331
+ async function (req, res) {
332
+ const key = `reset-confirm:${req.ip}`
333
+ if (isRateLimited(key, resetMaxAttempts, rateLimitWindowMs)) {
334
+ res.status(429)
335
+ return { message: 'Too many requests' }
336
+ }
237
337
  const { token, tokenId, password } = req.body
238
338
 
239
339
  const resetRequest = await db
@@ -243,6 +343,16 @@ export async function localUserPassController(app: FastifyInstance) {
243
343
  if (!resetRequest) {
244
344
  throw new Error(AUTH_ERRORS.INVALID_RESET_PARAMS)
245
345
  }
346
+
347
+ const createdAt = resetRequest.createdAt ? new Date(resetRequest.createdAt) : null
348
+ const isExpired = !createdAt ||
349
+ Number.isNaN(createdAt.getTime()) ||
350
+ Date.now() - createdAt.getTime() > resetPasswordTtlSeconds * 1000
351
+
352
+ if (isExpired) {
353
+ await db?.collection(resetPasswordCollection).deleteOne({ _id: resetRequest._id })
354
+ throw new Error(AUTH_ERRORS.INVALID_RESET_PARAMS)
355
+ }
246
356
  const hashedPassword = await hashPassword(password)
247
357
  await db.collection(authCollection!).updateOne(
248
358
  { email: resetRequest.email },
@@ -15,19 +15,30 @@ export type LoginSuccessDto = {
15
15
  user_id: string
16
16
  }
17
17
 
18
+ export type ErrorResponseDto = {
19
+ message: string
20
+ }
21
+
18
22
  export interface RegistrationDto {
19
23
  Body: RegisterUserDto
20
24
  }
21
25
 
22
26
  export interface LoginDto {
23
27
  Body: LoginUserDto
24
- Reply: LoginSuccessDto
28
+ Reply: LoginSuccessDto | ErrorResponseDto
29
+ }
30
+
31
+ export interface ResetPasswordSendDto {
32
+ Body: {
33
+ email: string
34
+ }
25
35
  }
26
36
 
27
- export interface ResetPasswordDto {
37
+ export interface ResetPasswordCallDto {
28
38
  Body: {
29
39
  email: string
30
40
  password: string
41
+ arguments?: unknown[]
31
42
  }
32
43
  }
33
44
 
@@ -38,3 +49,10 @@ export interface ConfirmResetPasswordDto {
38
49
  password: string
39
50
  }
40
51
  }
52
+
53
+ export interface ConfirmUserDto {
54
+ Body: {
55
+ token: string
56
+ tokenId: string
57
+ }
58
+ }
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