@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
@@ -0,0 +1,91 @@
1
+ import { FastifyInstance } from 'fastify'
2
+ import { AUTH_CONFIG, DB_NAME, DEFAULT_CONFIG } from '../../../constants'
3
+ import { hashToken } from '../../../utils/crypto'
4
+ import { PROVIDER } from '../../../shared/models/handleUserRegistration.model'
5
+ import { AUTH_ENDPOINTS } from '../../utils'
6
+ import { LoginDto } from './dtos'
7
+
8
+ /**
9
+ * Controller for handling anonymous user login.
10
+ * @testable
11
+ * @param {FastifyInstance} app - The Fastify instance.
12
+ */
13
+ export async function anonUserController(app: FastifyInstance) {
14
+ const db = app.mongo.client.db(DB_NAME)
15
+ const { authCollection, refreshTokensCollection, providers } = AUTH_CONFIG
16
+ const refreshTokenTtlMs = DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000
17
+ const anonUserTtlSeconds = DEFAULT_CONFIG.ANON_USER_TTL_SECONDS
18
+
19
+ try {
20
+ await db.collection(authCollection!).createIndex(
21
+ { createdAt: 1 },
22
+ {
23
+ expireAfterSeconds: anonUserTtlSeconds,
24
+ partialFilterExpression: { 'identities.provider_type': PROVIDER.ANON_USER }
25
+ }
26
+ )
27
+ } catch (error) {
28
+ console.error('Failed to ensure anonymous user TTL index', error)
29
+ }
30
+
31
+ app.post<LoginDto>(
32
+ AUTH_ENDPOINTS.LOGIN,
33
+ async function () {
34
+ const anonProvider = providers?.['anon-user']
35
+ if (anonProvider?.disabled) {
36
+ throw new Error('Anonymous authentication disabled')
37
+ }
38
+
39
+ const now = new Date()
40
+ const insertResult = await db.collection(authCollection!).insertOne({
41
+ status: 'confirmed',
42
+ createdAt: now,
43
+ custom_data: {},
44
+ identities: [
45
+ {
46
+ provider_type: PROVIDER.ANON_USER,
47
+ provider_data: {}
48
+ }
49
+ ]
50
+ })
51
+
52
+ const userId = insertResult.insertedId
53
+ await db.collection(authCollection!).updateOne(
54
+ { _id: userId },
55
+ {
56
+ $set: {
57
+ identities: [
58
+ {
59
+ id: userId.toString(),
60
+ provider_id: userId.toString(),
61
+ provider_type: PROVIDER.ANON_USER,
62
+ provider_data: {}
63
+ }
64
+ ]
65
+ }
66
+ }
67
+ )
68
+
69
+ const currentUserData = {
70
+ _id: userId,
71
+ user_data: {}
72
+ }
73
+ const refreshToken = this.createRefreshToken(currentUserData)
74
+ const refreshTokenHash = hashToken(refreshToken)
75
+ await db.collection(refreshTokensCollection).insertOne({
76
+ userId,
77
+ tokenHash: refreshTokenHash,
78
+ createdAt: now,
79
+ expiresAt: new Date(Date.now() + refreshTokenTtlMs),
80
+ revokedAt: null
81
+ })
82
+
83
+ return {
84
+ access_token: this.createAccessToken(currentUserData),
85
+ refresh_token: refreshToken,
86
+ device_id: '',
87
+ user_id: userId.toString()
88
+ }
89
+ }
90
+ )
91
+ }
@@ -0,0 +1,10 @@
1
+ export type LoginSuccessDto = {
2
+ access_token: string
3
+ device_id: string
4
+ refresh_token: string
5
+ user_id: string
6
+ }
7
+
8
+ export interface LoginDto {
9
+ Reply: LoginSuccessDto
10
+ }
@@ -1,16 +1,10 @@
1
1
  import { FastifyInstance } from 'fastify'
2
- import { AUTH_CONFIG } from '../../../constants'
3
- import handleUserRegistration from '../../../shared/handleUserRegistration'
4
- import { PROVIDER } from '../../../shared/models/handleUserRegistration.model'
2
+ import { AUTH_CONFIG, DB_NAME, DEFAULT_CONFIG } from '../../../constants'
5
3
  import { StateManager } from '../../../state'
6
4
  import { GenerateContext } from '../../../utils/context'
7
- import {
8
- AUTH_ENDPOINTS,
9
- generatePassword,
10
- } from '../../utils'
11
- import {
12
- LoginDto
13
- } from './dtos'
5
+ import { hashToken } from '../../../utils/crypto'
6
+ import { AUTH_ENDPOINTS } from '../../utils'
7
+ import { LoginDto } from './dtos'
14
8
  import { LOGIN_SCHEMA } from './schema'
15
9
 
16
10
  /**
@@ -22,6 +16,9 @@ export async function customFunctionController(app: FastifyInstance) {
22
16
 
23
17
  const functionsList = StateManager.select('functions')
24
18
  const services = StateManager.select('services')
19
+ const db = app.mongo.client.db(DB_NAME)
20
+ const { authCollection, refreshTokensCollection } = AUTH_CONFIG
21
+ const refreshTokenTtlMs = DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS * 24 * 60 * 60 * 1000
25
22
 
26
23
  /**
27
24
  * Endpoint for user login.
@@ -35,7 +32,7 @@ export async function customFunctionController(app: FastifyInstance) {
35
32
  {
36
33
  schema: LOGIN_SCHEMA
37
34
  },
38
- async function (req) {
35
+ async function (req, reply) {
39
36
  const { providers } = AUTH_CONFIG
40
37
  const authFunctionName = providers["custom-function"].authFunctionName
41
38
 
@@ -53,7 +50,8 @@ export async function customFunctionController(app: FastifyInstance) {
53
50
  id
54
51
  } = req
55
52
 
56
- const res = await GenerateContext({
53
+ type CustomFunctionAuthResult = { id?: string }
54
+ const authResult = await GenerateContext({
57
55
  args: [
58
56
  req.body
59
57
  ],
@@ -72,30 +70,41 @@ export async function customFunctionController(app: FastifyInstance) {
72
70
  ip,
73
71
  id
74
72
  }
75
- })
73
+ }) as CustomFunctionAuthResult
76
74
 
77
75
 
78
- if (res.id) {
79
- const user = await handleUserRegistration(app, { run_as_system: true, skipUserCheck: true, provider: PROVIDER.CUSTOM_FUNCTION })({ email: res.id, password: generatePassword() })
80
- if (!user?.insertedId) {
81
- throw new Error('Failed to register custom user')
82
- }
76
+ if (!authResult.id) {
77
+ reply.code(401).send({ message: 'Unauthorized' })
78
+ return
79
+ }
83
80
 
84
- const currentUserData = {
85
- _id: user.insertedId,
86
- user_data: {
87
- _id: user.insertedId
88
- }
89
- }
90
- return {
91
- access_token: this.createAccessToken(currentUserData),
92
- refresh_token: this.createRefreshToken(currentUserData),
93
- device_id: '',
94
- user_id: user.insertedId.toString()
95
- }
81
+ const authUser = await db.collection(authCollection!).findOne({ email: authResult.id })
82
+ if (!authUser) {
83
+ reply.code(401).send({ message: 'Unauthorized' })
84
+ return
96
85
  }
97
86
 
98
- throw new Error("Authentication Failed")
87
+ const currentUserData = {
88
+ _id: authUser._id,
89
+ user_data: {
90
+ _id: authUser._id
91
+ }
92
+ }
93
+ const refreshToken = this.createRefreshToken(currentUserData)
94
+ const refreshTokenHash = hashToken(refreshToken)
95
+ await db.collection(refreshTokensCollection).insertOne({
96
+ userId: authUser._id,
97
+ tokenHash: refreshTokenHash,
98
+ createdAt: new Date(),
99
+ expiresAt: new Date(Date.now() + refreshTokenTtlMs),
100
+ revokedAt: null
101
+ })
102
+ return {
103
+ access_token: this.createAccessToken(currentUserData),
104
+ refresh_token: refreshToken,
105
+ device_id: '',
106
+ user_id: authUser._id.toString()
107
+ }
99
108
  }
100
109
  )
101
110
 
@@ -10,7 +10,11 @@ export type LoginSuccessDto = {
10
10
  user_id: string
11
11
  }
12
12
 
13
+ export type LoginErrorDto = {
14
+ message: string
15
+ }
16
+
13
17
  export interface LoginDto {
14
18
  Body: LoginUserDto
15
- Reply: LoginSuccessDto
19
+ Reply: LoginSuccessDto | LoginErrorDto
16
20
  }
@@ -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
+ }