@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,11 +1,18 @@
1
1
  import fastifyJwt from '@fastify/jwt'
2
2
  import fp from 'fastify-plugin'
3
3
  import { Document, ObjectId, WithId } from 'mongodb'
4
+ import { AUTH_CONFIG, DB_NAME, DEFAULT_CONFIG } from '../../constants'
4
5
 
5
6
  type Options = {
6
7
  secret: string
7
8
  }
8
9
 
10
+ type JwtAccessWithTimestamp = {
11
+ typ: 'access'
12
+ sub: string
13
+ iat?: number
14
+ }
15
+
9
16
  /**
10
17
  * This module is a Fastify plugin that sets up JWT-based authentication and token creation.
11
18
  * It registers JWT authentication, and provides methods to create access and refresh tokens.
@@ -25,20 +32,72 @@ export default fp(async function (fastify, opts: Options) {
25
32
  try {
26
33
  await request.jwtVerify()
27
34
  } catch (err) {
28
- // TODO: handle error
29
- reply.send(err)
35
+ fastify.log.warn({ err }, 'JWT authentication failed')
36
+ reply.code(401).send({ message: 'Unauthorized' })
37
+ return
38
+ }
39
+
40
+ if (request.user?.typ !== 'access') {
41
+ return
42
+ }
43
+
44
+ const db = fastify.mongo?.client?.db(DB_NAME)
45
+ if (!db) {
46
+ fastify.log.warn('Mongo client unavailable while checking logout state')
47
+ return
48
+ }
49
+
50
+ if (!request.user.sub) {
51
+ reply.code(401).send({ message: 'Unauthorized' })
52
+ return
53
+ }
54
+
55
+ let authUser
56
+ try {
57
+ authUser = await db
58
+ .collection<Document>(AUTH_CONFIG.authCollection)
59
+ .findOne({ _id: new ObjectId(request.user.sub) })
60
+ } catch (err) {
61
+ fastify.log.warn({ err }, 'Failed to lookup user during JWT authentication')
62
+ reply.code(401).send({ message: 'Unauthorized' })
63
+ return
64
+ }
65
+
66
+ if (!authUser) {
67
+ reply.code(401).send({ message: 'Unauthorized' })
68
+ return
69
+ }
70
+
71
+ const lastLogoutAt = authUser.lastLogoutAt ? new Date(authUser.lastLogoutAt) : null
72
+ const accessUser = request.user as JwtAccessWithTimestamp
73
+ const rawIssuedAt = accessUser.iat
74
+ const issuedAt =
75
+ typeof rawIssuedAt === 'number'
76
+ ? rawIssuedAt
77
+ : typeof rawIssuedAt === 'string'
78
+ ? Number(rawIssuedAt)
79
+ : undefined
80
+ if (
81
+ lastLogoutAt &&
82
+ !Number.isNaN(lastLogoutAt.getTime()) &&
83
+ typeof issuedAt === 'number' &&
84
+ !Number.isNaN(issuedAt) &&
85
+ lastLogoutAt.getTime() >= issuedAt * 1000
86
+ ) {
87
+ reply.code(401).send({ message: 'Unauthorized' })
88
+ return
30
89
  }
31
90
  })
32
91
 
33
92
  fastify.decorate('createAccessToken', function (user: WithId<Document>) {
34
93
  const id = user._id.toString()
35
- const userDataId = user.user_data._id.toString()
94
+ // const userDataId = user.user_data._id.toString()
36
95
 
37
96
  const user_data = {
38
- _id: userDataId,
39
- id: userDataId,
97
+ ...user.user_data,
98
+ _id: id,
99
+ id: id,
40
100
  email: user.email,
41
- ...user.user_data
42
101
  }
43
102
 
44
103
  return this.jwt.sign(
@@ -52,7 +111,7 @@ export default fp(async function (fastify, opts: Options) {
52
111
  {
53
112
  iss: BAAS_ID,
54
113
  jti: BAAS_ID,
55
- sub: user._id.toJSON(),
114
+ sub: id,
56
115
  expiresIn: '300m'
57
116
  }
58
117
  )
@@ -66,7 +125,7 @@ export default fp(async function (fastify, opts: Options) {
66
125
  },
67
126
  {
68
127
  sub: user._id.toJSON(),
69
- expiresIn: '60d'
128
+ expiresIn: `${DEFAULT_CONFIG.REFRESH_TOKEN_TTL_DAYS}d`
70
129
  }
71
130
  )
72
131
  })
@@ -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
  }