@volcanicminds/backend 0.8.2 → 0.9.0

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.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volcanicminds/backend",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "codename": "turin",
5
5
  "license": "MIT",
6
6
  "description": "The volcanic (minds) backend",
@@ -51,43 +51,43 @@
51
51
  "upgrade-pkg": "yarn npm-upgrade"
52
52
  },
53
53
  "dependencies": {
54
- "@apollo/server": "^4.10.0",
54
+ "@apollo/server": "^4.11.0",
55
55
  "@as-integrations/fastify": "^2.1.1",
56
- "@fastify/compress": "^7.0.0",
56
+ "@fastify/compress": "^7.0.3",
57
57
  "@fastify/cors": "^9.0.1",
58
58
  "@fastify/helmet": "^11.1.1",
59
- "@fastify/jwt": "^8.0.0",
60
- "@fastify/multipart": "^8.1.0",
59
+ "@fastify/jwt": "^8.0.1",
60
+ "@fastify/multipart": "^8.3.0",
61
61
  "@fastify/rate-limit": "^9.1.0",
62
62
  "@fastify/schedule": "^4.1.1",
63
- "@fastify/swagger": "^8.14.0",
64
- "@fastify/swagger-ui": "^3.0.0",
65
- "@types/node": "^20.11.20",
66
- "dayjs": "^1.11.10",
63
+ "@fastify/swagger": "^8.15.0",
64
+ "@fastify/swagger-ui": "^3.1.0",
65
+ "@types/node": "^20.16.2",
66
+ "dayjs": "^1.11.13",
67
67
  "dotenv": "^16.4.5",
68
- "fastify": "^4.26.1",
68
+ "fastify": "^4.28.1",
69
69
  "fastify-raw-body": "^4.3.0",
70
- "glob": "^10.3.10",
71
- "graphql": "^16.8.1",
70
+ "glob": "^10.4.5",
71
+ "graphql": "^16.9.0",
72
72
  "i18n": "^0.15.1",
73
73
  "lodash": "^4.17.21",
74
- "nanoid": "^5.0.6",
75
- "object-sizeof": "^2.6.4",
76
- "pino": "^8.19.0",
77
- "pino-pretty": "^10.3.1",
74
+ "nanoid": "^5.0.7",
75
+ "object-sizeof": "^2.6.5",
76
+ "pino": "^9.3.2",
77
+ "pino-pretty": "^11.2.2",
78
78
  "root-require": "^0.3.1",
79
- "semver": "^7.6.0",
79
+ "semver": "^7.6.3",
80
80
  "toad-scheduler": "^3.0.1"
81
81
  },
82
82
  "devDependencies": {
83
- "@types/mocha": "^10.0.6",
83
+ "@types/mocha": "^10.0.7",
84
84
  "cross-env": "^7.0.3",
85
85
  "expect": "^29.7.0",
86
- "mocha": "^10.3.0",
87
- "nodemon": "^3.1.0",
86
+ "mocha": "^10.7.3",
87
+ "nodemon": "^3.1.4",
88
88
  "npm-upgrade": "^3.1.0",
89
89
  "ts-node": "^10.9.2",
90
- "typescript": "^5.3.3"
90
+ "typescript": "^5.5.4"
91
91
  },
92
92
  "repository": {
93
93
  "type": "git",
package/index.ts CHANGED
@@ -181,7 +181,22 @@ const start = async (decorators) => {
181
181
  // Helmet is not usable with Apollo Server
182
182
  plugins?.rawBody && (await server.register(rawBody, plugins.rawBody || {}))
183
183
  !loadApollo && plugins?.helmet && (await server.register(helmet, plugins.helmet || {}))
184
- plugins?.rateLimit && (await server.register(rateLimit, plugins.rateLimit || {}))
184
+
185
+ if (plugins?.rateLimit) {
186
+ await server.register(rateLimit, plugins.rateLimit || {})
187
+ server.setNotFoundHandler(
188
+ {
189
+ preHandler: server.rateLimit({
190
+ max: 30,
191
+ timeWindow: 30000
192
+ })
193
+ },
194
+ function (req, reply) {
195
+ reply.code(404).send()
196
+ }
197
+ )
198
+ }
199
+
185
200
  plugins?.multipart && (await server.register(multipart, plugins.multipart || {}))
186
201
  plugins?.cors && (await server.register(cors, plugins.cors || {}))
187
202
  plugins?.compress && (await server.register(compress, plugins.compress || {}))
@@ -5,6 +5,7 @@ module.exports = {
5
5
  enable: true,
6
6
  options: {
7
7
  reset_external_id_on_login: false,
8
- scheduler: false
8
+ scheduler: false,
9
+ embedded_auth: true
9
10
  }
10
11
  }
@@ -1,68 +1,74 @@
1
1
  import { getParams, getData } from '../util/common'
2
2
  import { AuthenticatedUser, AuthenticatedToken, Role } from '../../types/global'
3
3
 
4
+ const { embedded_auth = true } = global.config?.options || {}
5
+
4
6
  module.exports = async (req, reply) => {
5
- // request enrichment
6
7
  log.i && (req.startedAt = new Date())
8
+
9
+ // request enrichment
7
10
  req.data = () => getData(req)
8
11
  req.parameters = () => getParams(req)
9
- req.roles = () => (req.user ? req.user.roles : [roles.public])
10
- req.hasRole = (r: Role) => (req.user ? req.user.roles : [roles.public]).some((role) => role === r?.code)
11
12
 
12
- // authorization check
13
- const auth = req.headers?.authorization || ''
14
- const cfg = req.routeOptions?.config || req.routeConfig || {}
15
- const [prefix, bearerToken] = auth.split(' ')
16
- const isRoutePublic = (cfg.requiredRoles || []).some((role: Role) => role.code === roles.public.code)
13
+ if (embedded_auth) {
14
+ req.roles = () => (req.user ? req.user.roles : [roles.public])
15
+ req.hasRole = (r: Role) => (req.user ? req.user.roles : [roles.public]).some((role) => role === r?.code)
17
16
 
18
- if (prefix === 'Bearer' && bearerToken != null) {
19
- let user: null | AuthenticatedUser = null
20
- let token: null | AuthenticatedToken = null
17
+ // authorization check
18
+ const auth = req.headers?.authorization || ''
19
+ const cfg = req.routeOptions?.config || req.routeConfig || {}
20
+ const [prefix, bearerToken] = auth.split(' ')
21
21
 
22
- try {
23
- const tokenData = reply.server.jwt.verify(bearerToken)
24
- user = await req.server['userManager'].retrieveUserByExternalId(tokenData?.sub)
25
- if (!user && req.server['tokenManager'].isImplemented()) {
26
- token = await req.server['tokenManager'].retrieveTokenByExternalId(tokenData?.sub)
27
- }
28
- if (!user && !token) {
29
- return reply.status(404).send({ statusCode: 404, code: 'USER_NOT_FOUND', message: 'User not found' })
30
- }
31
- if (user) {
32
- const isValid = await req.server['userManager'].isValidUser(user)
33
- if (!isValid) {
34
- return reply.status(404).send({ statusCode: 404, code: 'USER_NOT_VALID', message: 'User not valid' })
22
+ if (prefix === 'Bearer' && bearerToken != null) {
23
+ let user: null | AuthenticatedUser = null
24
+ let token: null | AuthenticatedToken = null
25
+
26
+ try {
27
+ const tokenData = reply.server.jwt.verify(bearerToken)
28
+ user = await req.server['userManager'].retrieveUserByExternalId(tokenData?.sub)
29
+ if (!user && req.server['tokenManager'].isImplemented()) {
30
+ token = await req.server['tokenManager'].retrieveTokenByExternalId(tokenData?.sub)
35
31
  }
36
- // ok, we have the full user here
37
- req.user = user
38
- }
39
- if (token) {
40
- const isValid = await req.server['tokenManager'].isValidToken(token)
41
- if (!isValid) {
42
- return reply.status(404).send({ statusCode: 404, code: 'TOKEN_NOT_VALID', message: 'Token not valid' })
32
+ if (!user && !token) {
33
+ return reply.status(404).send({ statusCode: 404, code: 'USER_NOT_FOUND', message: 'User not found' })
34
+ }
35
+ if (user) {
36
+ const isValid = await req.server['userManager'].isValidUser(user)
37
+ if (!isValid) {
38
+ return reply.status(404).send({ statusCode: 404, code: 'USER_NOT_VALID', message: 'User not valid' })
39
+ }
40
+ // ok, we have the full user here
41
+ req.user = user
42
+ }
43
+ if (token) {
44
+ const isValid = await req.server['tokenManager'].isValidToken(token)
45
+ if (!isValid) {
46
+ return reply.status(404).send({ statusCode: 404, code: 'TOKEN_NOT_VALID', message: 'Token not valid' })
47
+ }
48
+ // ok, we have the full user here
49
+ req.token = token
50
+ }
51
+ } catch (error) {
52
+ const isRoutePublic = (cfg.requiredRoles || []).some((role: Role) => role.code === roles.public.code)
53
+ if (!isRoutePublic) {
54
+ throw error
43
55
  }
44
- // ok, we have the full user here
45
- req.token = token
46
- }
47
- } catch (error) {
48
- if (!isRoutePublic) {
49
- throw error
50
56
  }
51
57
  }
52
- }
53
58
 
54
- if (cfg.requiredRoles?.length > 0) {
55
- const { method = '', url = '', requiredRoles } = cfg
56
- const authRoles: string[] = ((req.user?.roles || req.token?.roles)?.map((code) => code) as string[]) || [
57
- roles.public?.code || 'public'
58
- ]
59
- const resolvedRoles = authRoles.length > 0 ? requiredRoles.filter((r) => authRoles.includes(r.code)) : []
59
+ if (cfg.requiredRoles?.length > 0) {
60
+ const { method = '', url = '', requiredRoles } = cfg
61
+ const authRoles: string[] = ((req.user?.roles || req.token?.roles)?.map((code) => code) as string[]) || [
62
+ roles.public?.code || 'public'
63
+ ]
64
+ const resolvedRoles = authRoles.length > 0 ? requiredRoles.filter((r) => authRoles.includes(r.code)) : []
60
65
 
61
- if (!resolvedRoles.length) {
62
- log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${url}`)
63
- return reply
64
- .status(403)
65
- .send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to call this route' })
66
+ if (!resolvedRoles.length) {
67
+ log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${url}`)
68
+ return reply
69
+ .status(403)
70
+ .send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to call this route' })
71
+ }
66
72
  }
67
73
  }
68
74
  }
@@ -10,6 +10,6 @@ module.exports = async (req, reply) => {
10
10
  extraMessage += `[${reqSize}${replySize} bytes]`
11
11
  }
12
12
 
13
- const message: string = `${req.method} ${req.url} ${reply.statusCode} ${extraMessage}`
14
- reply.statusCode < 300 ? log.info(message) : reply.statusCode < 400 ? log.warn(message) : log.error(message)
13
+ const message = () => `${req.method} ${req.url} ${reply.statusCode} ${extraMessage}`.trim()
14
+ reply.statusCode < 300 ? log.info(message()) : reply.statusCode < 400 ? log.warn(message()) : log.error(message())
15
15
  }
@@ -8,7 +8,8 @@ export function load() {
8
8
  enable: true,
9
9
  options: {
10
10
  reset_external_id_on_login: false,
11
- scheduler: false
11
+ scheduler: false,
12
+ embedded_auth: true
12
13
  }
13
14
  }
14
15
 
@@ -31,9 +31,10 @@ export function load(): ConfiguredRoute[] {
31
31
  method: methodCase,
32
32
  path: pathName = '/',
33
33
  handler,
34
+ roles: rs = [],
34
35
  config = {} as RouteConfig,
35
36
  middlewares = [],
36
- roles: rs = []
37
+ rateLimit
37
38
  } = route
38
39
 
39
40
  const rsp = !rs.length ? [roles.public] : rs
@@ -147,6 +148,7 @@ export function load(): ConfiguredRoute[] {
147
148
  roles: requiredRoles,
148
149
  enable,
149
150
  rawBody,
151
+ rateLimit,
150
152
  base,
151
153
  file: path.join(base, defaultConfig.controller || 'controller', handlerParts[0]),
152
154
  func: handlerParts[1],
@@ -210,8 +212,10 @@ async function loadMiddlewares(base: string, middlewares: string[] = []) {
210
212
  export function apply(server: any, routes: ConfiguredRoute[]): void {
211
213
  log.t && log.trace(`Apply ${routes.length} routes to server with pid ${process.pid}`)
212
214
 
213
- routes.forEach(async ({ handler, method, path, middlewares, roles, enable, rawBody, base, file, func, doc }) => {
214
- if (enable) {
215
+ routes.forEach(async (route) => {
216
+ if (route?.enable) {
217
+ const { handler, method, path, middlewares, roles, rawBody, rateLimit, base, file, func, doc } = route
218
+
215
219
  log.t && log.trace(`* Add path ${method} ${path} on handle ${handler}`)
216
220
  const midds = await loadMiddlewares(base, middlewares)
217
221
 
@@ -222,11 +226,13 @@ export function apply(server: any, routes: ConfiguredRoute[]): void {
222
226
  ...midds,
223
227
  config: {
224
228
  requiredRoles: roles || [],
225
- rawBody: rawBody || false
229
+ rawBody: rawBody || false,
230
+ rateLimit: rateLimit || undefined
226
231
  },
227
- handler: function (req: FastifyRequest, reply: FastifyReply) {
232
+ handler: async function (req: FastifyRequest, reply: FastifyReply) {
228
233
  try {
229
- return require(file)[func](req, reply)
234
+ const module = await import(file)
235
+ return await module[func](req, reply)
230
236
  } catch (err) {
231
237
  log.e && log.error(`Cannot find ${file} or method ${func}: ${err}`)
232
238
  return reply.code(500).send(`Invalid handler ${handler}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volcanicminds/backend",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "codename": "turin",
5
5
  "license": "MIT",
6
6
  "description": "The volcanic (minds) backend",
@@ -51,43 +51,43 @@
51
51
  "upgrade-pkg": "yarn npm-upgrade"
52
52
  },
53
53
  "dependencies": {
54
- "@apollo/server": "^4.10.0",
54
+ "@apollo/server": "^4.11.0",
55
55
  "@as-integrations/fastify": "^2.1.1",
56
- "@fastify/compress": "^7.0.0",
56
+ "@fastify/compress": "^7.0.3",
57
57
  "@fastify/cors": "^9.0.1",
58
58
  "@fastify/helmet": "^11.1.1",
59
- "@fastify/jwt": "^8.0.0",
60
- "@fastify/multipart": "^8.1.0",
59
+ "@fastify/jwt": "^8.0.1",
60
+ "@fastify/multipart": "^8.3.0",
61
61
  "@fastify/rate-limit": "^9.1.0",
62
62
  "@fastify/schedule": "^4.1.1",
63
- "@fastify/swagger": "^8.14.0",
64
- "@fastify/swagger-ui": "^3.0.0",
65
- "@types/node": "^20.11.20",
66
- "dayjs": "^1.11.10",
63
+ "@fastify/swagger": "^8.15.0",
64
+ "@fastify/swagger-ui": "^3.1.0",
65
+ "@types/node": "^20.16.2",
66
+ "dayjs": "^1.11.13",
67
67
  "dotenv": "^16.4.5",
68
- "fastify": "^4.26.1",
68
+ "fastify": "^4.28.1",
69
69
  "fastify-raw-body": "^4.3.0",
70
- "glob": "^10.3.10",
71
- "graphql": "^16.8.1",
70
+ "glob": "^10.4.5",
71
+ "graphql": "^16.9.0",
72
72
  "i18n": "^0.15.1",
73
73
  "lodash": "^4.17.21",
74
- "nanoid": "^5.0.6",
75
- "object-sizeof": "^2.6.4",
76
- "pino": "^8.19.0",
77
- "pino-pretty": "^10.3.1",
74
+ "nanoid": "^5.0.7",
75
+ "object-sizeof": "^2.6.5",
76
+ "pino": "^9.3.2",
77
+ "pino-pretty": "^11.2.2",
78
78
  "root-require": "^0.3.1",
79
- "semver": "^7.6.0",
79
+ "semver": "^7.6.3",
80
80
  "toad-scheduler": "^3.0.1"
81
81
  },
82
82
  "devDependencies": {
83
- "@types/mocha": "^10.0.6",
83
+ "@types/mocha": "^10.0.7",
84
84
  "cross-env": "^7.0.3",
85
85
  "expect": "^29.7.0",
86
- "mocha": "^10.3.0",
87
- "nodemon": "^3.1.0",
86
+ "mocha": "^10.7.3",
87
+ "nodemon": "^3.1.4",
88
88
  "npm-upgrade": "^3.1.0",
89
89
  "ts-node": "^10.9.2",
90
- "typescript": "^5.3.3"
90
+ "typescript": "^5.5.4"
91
91
  },
92
92
  "repository": {
93
93
  "type": "git",
package/types/global.d.ts CHANGED
@@ -48,8 +48,9 @@ export interface Route {
48
48
  path: string
49
49
  handler: string
50
50
  roles: Role[]
51
- config?: RouteConfig
52
51
  middlewares: string[]
52
+ config?: RouteConfig
53
+ rateLimit?: any
53
54
  }
54
55
 
55
56
  export interface GeneralConfig {
@@ -58,6 +59,7 @@ export interface GeneralConfig {
58
59
  options: {
59
60
  reset_external_id_on_login: boolean
60
61
  scheduler: boolean
62
+ embedded_auth: boolean
61
63
  }
62
64
  }
63
65
 
@@ -88,6 +90,7 @@ export interface ConfiguredRoute {
88
90
  path: string
89
91
  handler: any
90
92
  rawBody: boolean
93
+ rateLimit: any
91
94
  file: string
92
95
  func: any
93
96
  base: string