@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.
- package/README.md +0 -7
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/api/auth/controller/auth.js +136 -17
- package/dist/lib/api/auth/controller/auth.js.map +1 -1
- package/dist/lib/api/auth/routes.js +107 -13
- package/dist/lib/api/auth/routes.js.map +1 -1
- package/dist/lib/api/health/routes.js +2 -2
- package/dist/lib/api/health/routes.js.map +1 -1
- package/dist/lib/api/users/routes.js +12 -14
- package/dist/lib/api/users/routes.js.map +1 -1
- package/dist/lib/hooks/onRequest.js +21 -17
- package/dist/lib/hooks/onRequest.js.map +1 -1
- package/dist/lib/loader/plugins.js +0 -1
- package/dist/lib/loader/plugins.js.map +1 -1
- package/dist/lib/loader/router.js +66 -35
- package/dist/lib/loader/router.js.map +1 -1
- package/dist/lib/loader/schemas.js +8 -3
- package/dist/lib/loader/schemas.js.map +1 -1
- package/dist/lib/middleware/isAdmin.js +5 -4
- package/dist/lib/middleware/isAdmin.js.map +1 -1
- package/dist/lib/middleware/isAuthenticated.js +7 -6
- package/dist/lib/middleware/isAuthenticated.js.map +1 -1
- package/dist/lib/middleware/postAuth.js +19 -0
- package/dist/lib/middleware/postAuth.js.map +1 -0
- package/dist/lib/middleware/preAuth.js +17 -0
- package/dist/lib/middleware/preAuth.js.map +1 -0
- package/dist/lib/util/generate.js +10 -0
- package/dist/lib/util/generate.js.map +1 -0
- package/dist/lib/util/regexp.js +13 -13
- package/dist/lib/util/regexp.js.map +1 -1
- package/index.d.ts +2 -1
- package/index.ts +50 -2
- package/lib/api/auth/controller/auth.ts +118 -23
- package/lib/api/auth/routes.ts +107 -14
- package/lib/api/health/routes.ts +2 -2
- package/lib/api/users/routes.ts +12 -14
- package/lib/hooks/onRequest.ts +21 -27
- package/lib/loader/plugins.ts +0 -1
- package/lib/loader/router.ts +71 -34
- package/lib/loader/schemas.ts +7 -3
- package/lib/middleware/isAdmin.ts +3 -3
- package/lib/middleware/isAuthenticated.ts +5 -5
- package/lib/middleware/postAuth.ts +5 -0
- package/lib/middleware/preAuth.ts +3 -0
- package/lib/util/generate.ts +6 -0
- package/lib/util/regexp.ts +34 -32
- package/package.json +1 -1
- package/types/global.d.ts +15 -1
- package/dist/lib/api/auth/controller/password.js +0 -23
- package/dist/lib/api/auth/controller/password.js.map +0 -1
- package/dist/lib/middleware/example.js +0 -13
- package/dist/lib/middleware/example.js.map +0 -1
- package/lib/api/auth/controller/password.ts +0 -21
- package/lib/middleware/example.ts +0 -12
|
@@ -1,31 +1,126 @@
|
|
|
1
1
|
import { FastifyReply, FastifyRequest } from 'fastify'
|
|
2
|
-
import
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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.
|
|
24
|
-
|
|
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
|
|
28
|
-
|
|
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
|
-
|
|
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
|
}
|
package/lib/api/auth/routes.ts
CHANGED
|
@@ -1,20 +1,111 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
config: {
|
|
3
|
-
title: '
|
|
4
|
-
description: '
|
|
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: '
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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: '
|
|
44
|
-
path: '/
|
|
136
|
+
method: 'POST',
|
|
137
|
+
path: '/invalidate-tokens',
|
|
45
138
|
roles: [],
|
|
46
|
-
handler: 'auth.
|
|
139
|
+
handler: 'auth.invalidateTokens',
|
|
47
140
|
middlewares: ['global.isAuthenticated'],
|
|
48
141
|
config: {
|
|
49
|
-
title: '
|
|
50
|
-
description: '
|
|
142
|
+
title: 'Invalidate all tokens',
|
|
143
|
+
description: 'Invalidate all tokens',
|
|
51
144
|
response: {
|
|
52
145
|
200: {
|
|
53
146
|
description: 'Default response',
|
package/lib/api/health/routes.ts
CHANGED
package/lib/api/users/routes.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
config: {
|
|
3
|
-
title: 'User
|
|
4
|
-
description: 'User
|
|
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: '
|
|
29
|
-
|
|
24
|
+
id: { type: 'string' },
|
|
25
|
+
externalId: { type: 'string' },
|
|
26
|
+
username: { type: 'string' },
|
|
30
27
|
email: { type: 'string' },
|
|
31
|
-
|
|
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',
|
package/lib/hooks/onRequest.ts
CHANGED
|
@@ -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 = () => (
|
|
10
|
-
req.hasRole = (r: Role) => (
|
|
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
|
-
|
|
18
|
+
let user: AuthenticatedUser = {} as AuthenticatedUser
|
|
19
19
|
try {
|
|
20
20
|
const tokenData = reply.server.jwt.verify(token)
|
|
21
|
-
user
|
|
22
|
-
user
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
}
|
package/lib/loader/plugins.ts
CHANGED
package/lib/loader/router.ts
CHANGED
|
@@ -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:
|
|
35
|
+
roles: rs = []
|
|
39
36
|
} = route
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
54
|
-
deprecated = defaultConfig.deprecated
|
|
55
|
-
tags = defaultConfig.tags
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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:
|
|
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
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
192
|
+
...midds,
|
|
156
193
|
config: {
|
|
157
194
|
requiredRoles: roles || []
|
|
158
195
|
},
|
package/lib/loader/schemas.ts
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
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}`)
|