@volcanicminds/backend 0.2.28 → 0.2.30

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 (55) hide show
  1. package/README.md +0 -7
  2. package/dist/index.js +39 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/api/auth/controller/auth.js +136 -17
  5. package/dist/lib/api/auth/controller/auth.js.map +1 -1
  6. package/dist/lib/api/auth/routes.js +107 -13
  7. package/dist/lib/api/auth/routes.js.map +1 -1
  8. package/dist/lib/api/health/routes.js +2 -2
  9. package/dist/lib/api/health/routes.js.map +1 -1
  10. package/dist/lib/api/users/routes.js +12 -14
  11. package/dist/lib/api/users/routes.js.map +1 -1
  12. package/dist/lib/hooks/onRequest.js +21 -17
  13. package/dist/lib/hooks/onRequest.js.map +1 -1
  14. package/dist/lib/loader/plugins.js +0 -1
  15. package/dist/lib/loader/plugins.js.map +1 -1
  16. package/dist/lib/loader/router.js +66 -35
  17. package/dist/lib/loader/router.js.map +1 -1
  18. package/dist/lib/loader/schemas.js +8 -3
  19. package/dist/lib/loader/schemas.js.map +1 -1
  20. package/dist/lib/middleware/isAdmin.js +5 -4
  21. package/dist/lib/middleware/isAdmin.js.map +1 -1
  22. package/dist/lib/middleware/isAuthenticated.js +7 -6
  23. package/dist/lib/middleware/isAuthenticated.js.map +1 -1
  24. package/dist/lib/middleware/postAuth.js +19 -0
  25. package/dist/lib/middleware/postAuth.js.map +1 -0
  26. package/dist/lib/middleware/preAuth.js +17 -0
  27. package/dist/lib/middleware/preAuth.js.map +1 -0
  28. package/dist/lib/util/generate.js +10 -0
  29. package/dist/lib/util/generate.js.map +1 -0
  30. package/dist/lib/util/regexp.js +13 -13
  31. package/dist/lib/util/regexp.js.map +1 -1
  32. package/index.d.ts +2 -1
  33. package/index.ts +50 -2
  34. package/lib/api/auth/controller/auth.ts +118 -23
  35. package/lib/api/auth/routes.ts +107 -14
  36. package/lib/api/health/routes.ts +2 -2
  37. package/lib/api/users/routes.ts +12 -14
  38. package/lib/hooks/onRequest.ts +21 -27
  39. package/lib/loader/plugins.ts +0 -1
  40. package/lib/loader/router.ts +71 -34
  41. package/lib/loader/schemas.ts +7 -3
  42. package/lib/middleware/isAdmin.ts +3 -3
  43. package/lib/middleware/isAuthenticated.ts +5 -5
  44. package/lib/middleware/postAuth.ts +5 -0
  45. package/lib/middleware/preAuth.ts +3 -0
  46. package/lib/util/generate.ts +6 -0
  47. package/lib/util/regexp.ts +34 -32
  48. package/package.json +1 -1
  49. package/types/global.d.ts +15 -1
  50. package/dist/lib/api/auth/controller/password.js +0 -23
  51. package/dist/lib/api/auth/controller/password.js.map +0 -1
  52. package/dist/lib/middleware/example.js +0 -13
  53. package/dist/lib/middleware/example.js.map +0 -1
  54. package/lib/api/auth/controller/password.ts +0 -21
  55. package/lib/middleware/example.ts +0 -12
@@ -1,31 +1,126 @@
1
1
  import { FastifyReply, FastifyRequest } from 'fastify'
2
- import { AuthenticatedUser } from '../../../../types/global'
2
+ import * as regExp from '../../../util/regexp'
3
+
4
+ export async function register(req: FastifyRequest, reply: FastifyReply) {
5
+ const { password1: password, password2, ...data } = req.data()
6
+
7
+ if (!data.username) {
8
+ return reply.status(404).send(Error('Username not valid'))
9
+ }
10
+ if (!data.email || !regExp.email.test(data.email)) {
11
+ return reply.status(404).send(Error('Email not valid'))
12
+ }
13
+ if (!password || !regExp.password.test(password)) {
14
+ return reply.status(404).send(Error('Password not valid'))
15
+ }
16
+ if (!password2 || password2 !== password) {
17
+ return reply.status(404).send(Error('Repeated password not match'))
18
+ }
19
+
20
+ // public is the default
21
+ const publicRole = global.roles?.public?.code || 'public'
22
+ data.roles = (data.requiredRoles || []).map((r) => global.roles[r]?.code).filter((r) => !!r)
23
+ if (!data.roles.includes(publicRole)) {
24
+ data.roles.push(publicRole)
25
+ }
26
+
27
+ const user = await req.server['userManager'].createUser({ ...data, password: password })
28
+ if (!user) {
29
+ return reply.status(400).send(Error('User not registered'))
30
+ }
31
+
32
+ return user
33
+ }
34
+
35
+ export async function unregister(req: FastifyRequest, reply: FastifyReply) {
36
+ const { email, password } = req.data()
37
+
38
+ let user = await req.server['userManager'].retrieveUserByPassword(email, password)
39
+ let isValid = await req.server['userManager'].isValidUser(user)
40
+
41
+ if (!isValid) {
42
+ return reply.status(403).send(Error('Wrong credentials'))
43
+ }
44
+
45
+ if (!user.enabled) {
46
+ return reply.status(403).send(Error('User not enabled'))
47
+ }
48
+
49
+ user = await req.server['userManager'].disableUserById(user?.id)
50
+ isValid = await req.server['userManager'].isValidUser(user)
51
+
52
+ if (!isValid) {
53
+ return reply.status(400).send(Error('User not valid'))
54
+ }
55
+
56
+ return { ok: true }
57
+ }
58
+
59
+ export async function changePassword(req: FastifyRequest, reply: FastifyReply) {
60
+ const { email, oldPassword, newPassword1, newPassword2 } = req.data()
61
+
62
+ if (!newPassword1 || !regExp.password.test(newPassword1)) {
63
+ return reply.status(404).send(Error('New password not valid'))
64
+ }
65
+
66
+ if (!newPassword2 || newPassword2 !== newPassword1) {
67
+ return reply.status(404).send(Error('Repeated new password not match'))
68
+ }
69
+
70
+ let user = await req.server['userManager'].retrieveUserByPassword(email, oldPassword)
71
+ let isValid = await req.server['userManager'].isValidUser(user)
72
+
73
+ if (!isValid) {
74
+ return reply.status(403).send(Error('Wrong credentials'))
75
+ }
76
+
77
+ if (!user.enabled) {
78
+ return reply.status(403).send(Error('User not enabled'))
79
+ }
80
+
81
+ user = await req.server['userManager'].changePassword(email, newPassword1, oldPassword)
82
+ isValid = await req.server['userManager'].isValidUser(user)
83
+ return { ok: isValid }
84
+ }
3
85
 
4
86
  export async function login(req: FastifyRequest, reply: FastifyReply) {
5
- const { email = '', password = '' } = req.data()
6
-
7
- // TODO: use UserManagement.find and check password
8
- // demo code here
9
- const username = email.substr(0, email.indexOf('@')) || 'jerry'
10
- const roleList = [username === 'admin' ? roles.admin : username === 'vminds' ? roles.backoffice : roles.public]
11
- const user =
12
- username !== null
13
- ? ({
14
- id: 306, // user id
15
- name: username, // optional
16
- email: email,
17
- roles: roleList
18
- } as AuthenticatedUser)
19
- : null
20
-
21
- // TODO: review if email is important to include in token (for a security purpose)
87
+ const { email, password } = req.data()
88
+
89
+ if (!email || !regExp.email.test(email)) {
90
+ return reply.status(404).send(Error('Email not valid'))
91
+ }
92
+ if (!password || !regExp.password.test(password)) {
93
+ return reply.status(404).send(Error('Password not valid'))
94
+ }
95
+
96
+ const user = await req.server['userManager'].retrieveUserByPassword(email, password)
97
+ const isValid = await req.server['userManager'].isValidUser(user)
98
+
99
+ if (!isValid) {
100
+ return reply.status(403).send(Error('Wrong credentials'))
101
+ }
102
+
103
+ if (!user.enabled) {
104
+ return reply.status(403).send(Error('User not enabled'))
105
+ }
106
+
107
+ // log.trace('User: ' + JSON.stringify(user) + ' ' + roles)
22
108
  // https://www.iana.org/assignments/jwt/jwt.xhtml
23
- const token = user !== null ? await reply.jwtSign({ sub: user.id, name: user.name, email: user.email }) : null
24
- reply.send({ ...user, token: token || null, roles: roleList.map((r) => r.code) })
109
+ const token = user !== null ? await reply.jwtSign({ sub: user.externalId }) : null
110
+ return {
111
+ ...user,
112
+ token: token || null,
113
+ roles: (user.roles || [global.role?.public?.code || 'public']).map((r) => r?.code || r)
114
+ }
25
115
  }
26
116
 
27
- export async function demo(req: FastifyRequest, reply: FastifyReply) {
28
- // JSON.stringify(req.user)
117
+ export async function invalidateTokens(req: FastifyRequest, reply: FastifyReply) {
118
+ let isValid = await req.server['userManager'].isValidUser(req.user)
119
+ if (!isValid) {
120
+ return reply.status(403).send(Error('User not linked'))
121
+ }
29
122
 
30
- reply.send({ ok: req.user })
123
+ const user = await req.server['userManager'].resetExternalId(req.user?.id)
124
+ isValid = await req.server['userManager'].isValidUser(user)
125
+ return { ok: isValid }
31
126
  }
@@ -1,20 +1,111 @@
1
1
  module.exports = {
2
2
  config: {
3
- title: 'User useful functions',
4
- description: 'User useful functions',
3
+ title: 'Authentication functions',
4
+ description: 'Authentication functions',
5
5
  controller: 'controller',
6
6
  tags: ['auth'],
7
-
8
7
  deprecated: false,
9
- version: false
8
+ version: false,
9
+ enable: true
10
10
  },
11
11
  routes: [
12
+ {
13
+ method: 'POST',
14
+ path: '/register',
15
+ roles: [],
16
+ handler: 'auth.register',
17
+ middlewares: ['global.preAuth', 'global.postAuth'],
18
+ config: {
19
+ title: 'Register new user',
20
+ description: 'Register a new user',
21
+ body: {
22
+ type: 'object',
23
+ properties: {
24
+ username: { type: 'string' },
25
+ email: { type: 'string' },
26
+ password1: { type: 'string' },
27
+ password2: { type: 'string' },
28
+ requiredRoles: { type: 'array', items: { type: 'string' } }
29
+ }
30
+ },
31
+ response: {
32
+ 200: {
33
+ description: 'Default response',
34
+ type: 'object',
35
+ properties: {
36
+ id: { type: 'string' },
37
+ externalId: { type: 'string' },
38
+ username: { type: 'string' },
39
+ email: { type: 'string' },
40
+ enabled: { type: 'boolean' },
41
+ roles: { type: 'array', items: { type: 'string' } }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ },
47
+ {
48
+ method: 'POST',
49
+ path: '/unregister',
50
+ roles: [],
51
+ handler: 'auth.unregister',
52
+ middlewares: ['global.preAuth', 'global.postAuth'],
53
+ config: {
54
+ title: 'Unregister existing user',
55
+ description: 'Unregister an existing user',
56
+ body: {
57
+ type: 'object',
58
+ properties: {
59
+ email: { type: 'string' },
60
+ password: { type: 'string' }
61
+ }
62
+ },
63
+ response: {
64
+ 200: {
65
+ description: 'Default response',
66
+ type: 'object',
67
+ properties: {
68
+ ok: { type: 'boolean' }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ },
74
+ {
75
+ method: 'POST',
76
+ path: '/change-password',
77
+ roles: [],
78
+ handler: 'auth.changePassword',
79
+ middlewares: [],
80
+ config: {
81
+ title: 'Change password',
82
+ description: 'Change password for an existing user',
83
+ body: {
84
+ type: 'object',
85
+ properties: {
86
+ email: { type: 'string' },
87
+ oldPassword: { type: 'string' },
88
+ newPassword1: { type: 'string' },
89
+ newPassword2: { type: 'string' }
90
+ }
91
+ },
92
+ response: {
93
+ 200: {
94
+ description: 'Default response',
95
+ type: 'object',
96
+ properties: {
97
+ ok: { type: 'boolean' }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ },
12
103
  {
13
104
  method: 'POST',
14
105
  path: '/login',
15
106
  roles: [],
16
107
  handler: 'auth.login',
17
- middlewares: [],
108
+ middlewares: ['global.preAuth', 'global.postAuth'],
18
109
  config: {
19
110
  title: 'Login',
20
111
  description: 'Basic login authentication',
@@ -30,24 +121,26 @@ module.exports = {
30
121
  description: 'Default response',
31
122
  type: 'object',
32
123
  properties: {
33
- id: { type: 'number' },
34
- name: { type: 'string' },
35
- token: { type: 'string' },
36
- roles: { type: 'array', items: { type: 'string' } }
124
+ id: { type: 'string' },
125
+ externalId: { type: 'string' },
126
+ username: { type: 'string' },
127
+ email: { type: 'string' },
128
+ roles: { type: 'array', items: { type: 'string' } },
129
+ token: { type: 'string' }
37
130
  }
38
131
  }
39
132
  }
40
133
  }
41
134
  },
42
135
  {
43
- method: 'GET',
44
- path: '/demo',
136
+ method: 'POST',
137
+ path: '/invalidate-tokens',
45
138
  roles: [],
46
- handler: 'auth.demo',
139
+ handler: 'auth.invalidateTokens',
47
140
  middlewares: ['global.isAuthenticated'],
48
141
  config: {
49
- title: 'For debug purpose',
50
- description: 'Demo login authentication',
142
+ title: 'Invalidate all tokens',
143
+ description: 'Invalidate all tokens',
51
144
  response: {
52
145
  200: {
53
146
  description: 'Default response',
@@ -1,7 +1,7 @@
1
1
  module.exports = {
2
2
  config: {
3
- title: 'Health useful functions',
4
- description: 'Health useful functions',
3
+ title: 'Health functions',
4
+ description: 'Health functions',
5
5
  controller: 'controller',
6
6
  tags: ['health']
7
7
  },
@@ -1,7 +1,7 @@
1
1
  module.exports = {
2
2
  config: {
3
- title: 'User useful functions',
4
- description: 'User useful functions',
3
+ title: 'User functions',
4
+ description: 'User functions',
5
5
  controller: 'controller',
6
6
  tags: ['users'],
7
7
  version: false
@@ -10,25 +10,27 @@ module.exports = {
10
10
  {
11
11
  method: 'GET',
12
12
  path: '/',
13
- roles: [],
13
+ roles: [roles.public],
14
14
  handler: 'user.user',
15
15
  middlewares: ['global.isAuthenticated'],
16
16
  config: {
17
17
  title: 'Get current user',
18
18
  description: 'Get current user',
19
19
  response: {
20
- 403: {
21
- description: 'Unauthorized',
22
- type: 'string'
23
- },
24
20
  200: {
25
21
  description: 'Default response',
26
22
  type: 'object',
27
23
  properties: {
28
- id: { type: 'number' },
29
- name: { type: 'string' },
24
+ id: { type: 'string' },
25
+ externalId: { type: 'string' },
26
+ username: { type: 'string' },
30
27
  email: { type: 'string' },
31
- roles: { type: 'array', items: { type: 'string' } }
28
+ enabled: { type: 'boolean' },
29
+ enabledAt: { type: 'string' },
30
+ roles: { type: 'array', items: { type: 'string' } },
31
+ createdAt: { type: 'string' },
32
+ version: { type: 'number' },
33
+ updatedAt: { type: 'string' }
32
34
  }
33
35
  }
34
36
  }
@@ -44,10 +46,6 @@ module.exports = {
44
46
  title: 'Check if is an admin',
45
47
  description: 'Check if the current user is an admin',
46
48
  response: {
47
- 403: {
48
- description: 'Unauthorized',
49
- type: 'string'
50
- },
51
49
  200: {
52
50
  description: 'Default response',
53
51
  type: 'object',
@@ -6,8 +6,8 @@ module.exports = async (req, reply) => {
6
6
  log.i && (req.startedAt = new Date())
7
7
  req.data = () => getData(req)
8
8
  req.parameters = () => getParams(req)
9
- req.roles = () => ((req.user && req.user.roles) || []).map((role: Role) => role?.code) || []
10
- req.hasRole = (r: Role) => ((req.user && req.user.roles) || []).some((role: Role) => role?.code === r?.code)
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
11
 
12
12
  // authorization check
13
13
  const auth = req.headers?.authorization || ''
@@ -15,23 +15,17 @@ module.exports = async (req, reply) => {
15
15
  const isRoutePublic = (req.routeConfig.requiredRoles || []).some((role: Role) => role.code === roles.public.code)
16
16
 
17
17
  if (prefix === 'Bearer' && token != null) {
18
- const user: AuthenticatedUser = {} as AuthenticatedUser
18
+ let user: AuthenticatedUser = {} as AuthenticatedUser
19
19
  try {
20
20
  const tokenData = reply.server.jwt.verify(token)
21
- user.id = tokenData.sub
22
- user.name = tokenData.name
23
- user.email = tokenData.email
24
- user.roles = [roles.public, roles.admin]
25
-
26
- // if (global.npmDebugServerStarted) {
27
- // user.id = user.id || 123
28
- // user.name = user.name || 'Jerry Seinfeld'
29
- // user.email = user.email || 'jerry@george.com'
30
- // user.roles = [roles.public, roles.backoffice]
31
- // log.debug('Inject demo user ' + user.id)
32
- // }
33
-
34
- //TODO: recall plugin UserManagement for find user or error
21
+ user = await req.server['userManager'].retrieveUserByExternalId(tokenData?.sub)
22
+ if (!user) {
23
+ return reply.status(404).send({ statusCode: 404, code: 'USER_NOT_FOUND', message: 'User not found' })
24
+ }
25
+ const isValid = await req.server['userManager'].isValidUser(user)
26
+ if (!isValid) {
27
+ return reply.status(404).send({ statusCode: 404, code: 'USER_NOT_VALID', message: 'User not valid' })
28
+ }
35
29
 
36
30
  // ok, we have the full user here
37
31
  req.user = user
@@ -40,18 +34,18 @@ module.exports = async (req, reply) => {
40
34
  throw error
41
35
  }
42
36
  }
37
+ }
43
38
 
44
- if (req.routeConfig.requiredRoles?.length > 0) {
45
- const { method = '', url = '', requiredRoles } = req.routeConfig
46
- const userRoles: string[] = req.user?.roles?.map(({ code }) => code) || []
47
- const resolvedRoles = userRoles.length > 0 ? requiredRoles.filter((r) => userRoles.includes(r.code)) : []
39
+ if (req.routeConfig.requiredRoles?.length > 0) {
40
+ const { method = '', url = '', requiredRoles } = req.routeConfig
41
+ const userRoles: string[] = req.user?.roles?.map((code) => code) || []
42
+ const resolvedRoles = userRoles.length > 0 ? requiredRoles.filter((r) => userRoles.includes(r.code)) : []
48
43
 
49
- if (!resolvedRoles.length) {
50
- log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${url}`)
51
- return reply
52
- .code(403)
53
- .send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to call this route' })
54
- }
44
+ if (!resolvedRoles.length) {
45
+ log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${url}`)
46
+ return reply
47
+ .status(403)
48
+ .send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to call this route' })
55
49
  }
56
50
  }
57
51
  }
@@ -13,7 +13,6 @@ export function load() {
13
13
  configPlugins.forEach((plugin) => {
14
14
  plugins[plugin.name] = plugin.enable ? plugin.options : false
15
15
  log.t && log.trace(`* Plugin ${plugin.name} ${plugin.enable ? 'enabled' : 'disabled'}`)
16
- log.error(plugin)
17
16
  })
18
17
  })
19
18
  })
@@ -1,3 +1,4 @@
1
+ import yn from '../util/yn'
1
2
  import { Route, ConfiguredRoute, RouteConfig } from '../../types/global'
2
3
  import { FastifyReply, FastifyRequest } from 'fastify'
3
4
 
@@ -8,6 +9,7 @@ const methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS']
8
9
  export function load(): ConfiguredRoute[] {
9
10
  const validRoutes: ConfiguredRoute[] = []
10
11
  const patterns = [`${__dirname}/../api/**/routes.{ts,js}`, `${process.cwd()}/src/api/**/routes.{ts,js}`]
12
+ const authMiddlewares = ['global.isAuthenticated', 'global.isAdmin']
11
13
 
12
14
  patterns.forEach((pattern) => {
13
15
  log.t && log.trace('Looking for ' + pattern)
@@ -20,11 +22,6 @@ export function load(): ConfiguredRoute[] {
20
22
  const routesjs = require(f)
21
23
  const { routes = [], config: defaultConfig = {} } = routesjs || {}
22
24
 
23
- // adjust default config
24
- if (!defaultConfig.enable) defaultConfig.enable = true
25
- if (defaultConfig.deprecated == null) defaultConfig.deprecated = false
26
- if (defaultConfig.controller == null) defaultConfig.controller = 'controller'
27
-
28
25
  log.t && log.trace(`* Add ${routes.length} routes from ${file}`)
29
26
 
30
27
  routes.forEach((route: Route, index: number) => {
@@ -35,14 +32,15 @@ export function load(): ConfiguredRoute[] {
35
32
  handler,
36
33
  config = {} as RouteConfig,
37
34
  middlewares = [],
38
- roles: requiredRole = []
35
+ roles: rs = []
39
36
  } = route
40
37
 
41
- if (
42
- !config?.security &&
43
- (requiredRole.some((r) => r.code !== roles.public.code) ||
44
- middlewares.some((m) => m === 'global.isAuthenticated'))
45
- ) {
38
+ const requiredRoles = !rs.length ? [roles.public] : rs
39
+ const reqAuth: boolean =
40
+ middlewares.some((m) => authMiddlewares.includes(m)) ||
41
+ requiredRoles.every((r) => r.code !== roles.public.code)
42
+
43
+ if (!config?.security && reqAuth) {
46
44
  config.security = 'bearer'
47
45
  }
48
46
 
@@ -50,11 +48,11 @@ export function load(): ConfiguredRoute[] {
50
48
  const {
51
49
  title = '',
52
50
  description = '',
53
- enable = defaultConfig.enable || true,
54
- deprecated = defaultConfig.deprecated || false,
55
- tags = defaultConfig.tags || false,
51
+ enable = yn(defaultConfig.enable, true),
52
+ deprecated = yn(defaultConfig.deprecated, false),
53
+ tags = defaultConfig.tags,
56
54
  version = defaultConfig.version || '',
57
- security = defaultConfig.security || undefined,
55
+ security = defaultConfig.security,
58
56
  query,
59
57
  params,
60
58
  body,
@@ -90,25 +88,26 @@ export function load(): ConfiguredRoute[] {
90
88
  }
91
89
  }
92
90
 
93
- if (errors.length == 0) {
94
- enable
95
- ? log.t &&
96
- log.trace(
97
- `* Method [${method}] path ${endpoint} handler ${handler} enabled with ${
98
- middlewares?.length || 0
99
- } middlewares`
100
- )
101
- : log.w && log.warn(`* Method [${method}] path ${endpoint} handler ${handler} disabled. Skip.`)
102
-
91
+ const toAdd = enable && errors.length === 0
92
+ toAdd
93
+ ? log.t &&
94
+ log.trace(
95
+ `* Method [${method}] path ${endpoint} handler ${handler} enabled with ${
96
+ middlewares?.length || 0
97
+ } middlewares`
98
+ )
99
+ : log.w && log.warn(`* Method [${method}] path ${endpoint} handler ${handler} disabled. Skip.`)
100
+
101
+ toAdd &&
103
102
  validRoutes.push({
104
103
  handler,
105
104
  method,
106
105
  path: '/' + endpoint,
107
106
  middlewares,
108
- roles: requiredRole,
107
+ roles: requiredRoles,
109
108
  enable,
110
109
  base,
111
- file: path.join(base, defaultConfig.controller, handlerParts[0]),
110
+ file: path.join(base, defaultConfig.controller || 'controller', handlerParts[0]),
112
111
  func: handlerParts[1],
113
112
  // swagger: doc
114
113
  doc: {
@@ -124,7 +123,6 @@ export function load(): ConfiguredRoute[] {
124
123
  response
125
124
  }
126
125
  })
127
- }
128
126
  })
129
127
  })
130
128
  })
@@ -133,26 +131,65 @@ export function load(): ConfiguredRoute[] {
133
131
  return validRoutes
134
132
  }
135
133
 
136
- function normalizeMiddlewarePath(base: string, middleware: string = '') {
134
+ async function tryToLoadFile(fileName: string) {
135
+ return new Promise((resolve, reject) => {
136
+ try {
137
+ const required = fileName ? require(fileName) : null
138
+ resolve(required)
139
+ } catch (err) {
140
+ reject(err)
141
+ }
142
+ })
143
+ }
144
+
145
+ async function loadMiddleware(base: string, middleware: string = '') {
137
146
  const key = 'global.'
138
- const idx = middleware.indexOf(key)
139
- return idx == 0
140
- ? path.resolve(__dirname + '/../middleware/' + middleware.substring(key.length))
141
- : path.resolve(base + '/middleware/' + middleware)
147
+ const isGlobal = middleware.indexOf(key) > -1
148
+ let required: any = null
149
+
150
+ if (isGlobal) {
151
+ const name = middleware.substring(key.length)
152
+ required = await tryToLoadFile(path.resolve(process.cwd() + '/src/middleware/' + name)).catch(async () => {
153
+ return await tryToLoadFile(path.resolve(__dirname + '/../middleware/' + name))
154
+ })
155
+ } else {
156
+ required = await tryToLoadFile(path.resolve(base + '/middleware/' + middleware))
157
+ }
158
+
159
+ if (!required) {
160
+ log.error(`Middleware ${middleware} not loaded`)
161
+ throw new Error(`Middleware ${middleware} not loaded`)
162
+ }
163
+ return required
142
164
  }
143
165
 
166
+ async function loadMiddlewares(base: string, middlewares: string[] = []) {
167
+ const midds = {}
168
+ await Promise.all(
169
+ middlewares.map(async (m) => {
170
+ const middleware = await loadMiddleware(base, m)
171
+ Object.keys(middleware).map((name) => (midds[name] = [...(midds[name] || []), middleware[name]]))
172
+ })
173
+ )
174
+ // log.debug(base + ' middleware ' + middlewares.length + ' -> ' + Object.keys(midds))
175
+ return midds
176
+ }
177
+
178
+ // preParsing, preValidation, preHandler, preSerialization, ..
179
+
144
180
  export function apply(server: any, routes: ConfiguredRoute[]): void {
145
181
  log.t && log.trace(`Apply ${routes.length} routes to server with pid ${process.pid}`)
146
182
 
147
183
  routes.forEach(async ({ handler, method, path, middlewares, roles, enable, base, file, func, doc }) => {
148
184
  if (enable) {
149
185
  log.t && log.trace(`* Add path ${method} ${path} on handle ${handler}`)
186
+ const midds = await loadMiddlewares(base, middlewares)
150
187
 
151
188
  server.route({
152
189
  method: method,
153
190
  path: path,
154
191
  schema: doc,
155
- preHandler: (middlewares || []).map((m) => require(normalizeMiddlewarePath(base, m))),
192
+ ...midds,
156
193
  config: {
157
194
  requiredRoles: roles || []
158
195
  },
@@ -15,9 +15,13 @@ export function apply(server: any): void {
15
15
  schemaNames.map((name) => {
16
16
  const schema = schemaClass[name]
17
17
  if (schema != null) {
18
- log.trace(`* Schema [${schema.$id}] loaded from ${schemaFileName}`)
19
- server.addSchema(schema)
20
- schemaCount++
18
+ if (schema?.$id) {
19
+ log.trace(`* Schema [${schema.$id}] loaded from ${schemaFileName}`)
20
+ server.addSchema(schema)
21
+ schemaCount++
22
+ } else {
23
+ log.warn(`* Schema [${schema.$id}] not loaded from ${schemaFileName}`)
24
+ }
21
25
  }
22
26
  })
23
27
  })
@@ -1,11 +1,11 @@
1
1
  import { FastifyReply, FastifyRequest } from 'fastify'
2
2
 
3
- const log = global.log
4
- module.exports = (req: FastifyRequest, res: FastifyReply, next: any) => {
3
+ export function preHandler(req: FastifyRequest, res: FastifyReply, done: any) {
5
4
  try {
6
5
  if (req.user && req.user.id && req.hasRole(roles.admin)) {
7
- return next()
6
+ return done()
8
7
  }
8
+
9
9
  throw new Error('User without this privilege')
10
10
  } catch (err) {
11
11
  log.e && log.error(`Upps, something just happened ${err}`)