@volcanicminds/backend 0.2.27 → 0.2.29
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 +26 -18
- package/TODO.md +0 -1
- package/dist/index.js +46 -20
- 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/config/plugins.js +24 -0
- package/dist/lib/config/plugins.js.map +1 -0
- package/dist/lib/hooks/onRequest.js +13 -9
- package/dist/lib/hooks/onRequest.js.map +1 -1
- package/dist/lib/loader/plugins.js +23 -0
- package/dist/lib/loader/plugins.js.map +1 -0
- 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 +57 -33
- 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/config/plugins.ts +24 -0
- package/lib/hooks/onRequest.ts +13 -19
- package/lib/loader/plugins.ts +22 -0
- 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
package/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ dotenv.config()
|
|
|
6
6
|
import yn from './lib/util/yn'
|
|
7
7
|
import logger from './lib/util/logger'
|
|
8
8
|
import * as mark from './lib/util/mark'
|
|
9
|
+
import * as loaderPlugins from './lib/loader/plugins'
|
|
9
10
|
import * as loaderRoles from './lib/loader/roles'
|
|
10
11
|
import * as loaderRouter from './lib/loader/router'
|
|
11
12
|
import * as loaderHooks from './lib/loader/hooks'
|
|
@@ -26,6 +27,7 @@ import fastifyApollo, { fastifyApolloDrainPlugin } from '@as-integrations/fastif
|
|
|
26
27
|
import { myContextFunction, MyContext } from './lib/apollo/context'
|
|
27
28
|
import resolvers from './lib/apollo/resolvers'
|
|
28
29
|
import typeDefs from './lib/apollo/type-defs'
|
|
30
|
+
import { UserManagement } from './types/global'
|
|
29
31
|
|
|
30
32
|
global.log = logger
|
|
31
33
|
|
|
@@ -131,7 +133,7 @@ async function addFastifySwagger(fastify: FastifyInstance) {
|
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
const start = async () => {
|
|
136
|
+
const start = async (decorators) => {
|
|
135
137
|
const begin = new Date().getTime()
|
|
136
138
|
mark.print(logger)
|
|
137
139
|
global.roles = loaderRoles.load()
|
|
@@ -140,41 +142,16 @@ const start = async () => {
|
|
|
140
142
|
const fastify = await Fastify(opts)
|
|
141
143
|
|
|
142
144
|
const { HOST: host = '0.0.0.0', PORT: port = '2230', GRAPHQL } = process.env
|
|
143
|
-
const {
|
|
145
|
+
const { JWT_SECRET, JWT_EXPIRES_IN = '15d' } = process.env
|
|
144
146
|
|
|
145
147
|
const loadApollo = yn(GRAPHQL, false)
|
|
146
|
-
const
|
|
147
|
-
const addPluginHelmet = yn(SRV_HELMET, false)
|
|
148
|
-
const addPluginRateLimit = yn(SRV_RATELIMIT, false)
|
|
149
|
-
const addPluginCompress = yn(SRV_COMPRESS, false)
|
|
150
|
-
|
|
151
|
-
log.t && log.trace(`Attach Apollo Server ${loadApollo}`)
|
|
152
|
-
log.t && log.trace(`Add plugin CORS: ${addPluginCors}`)
|
|
153
|
-
log.t && log.trace(`Add plugin HELMET: ${!loadApollo ? addPluginHelmet : 'Not usable with Apollo'}`)
|
|
154
|
-
log.t && log.trace(`Add plugin COMPRESS: ${addPluginCompress}`)
|
|
155
|
-
log.t && log.trace(`Add plugin RATELIMIT: ${addPluginRateLimit}`)
|
|
148
|
+
const plugins = loaderPlugins.load()
|
|
156
149
|
|
|
157
150
|
// Helmet is not usable with Apollo Server
|
|
158
|
-
!loadApollo &&
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
origin: '*',
|
|
163
|
-
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'],
|
|
164
|
-
maxAge: 31536000,
|
|
165
|
-
credentials: true,
|
|
166
|
-
allowedHeaders: [
|
|
167
|
-
'Content-Type',
|
|
168
|
-
'Content-Length',
|
|
169
|
-
'Authorization',
|
|
170
|
-
'Origin',
|
|
171
|
-
'v-total',
|
|
172
|
-
'v-count',
|
|
173
|
-
'v-page',
|
|
174
|
-
'v-pageSize'
|
|
175
|
-
]
|
|
176
|
-
}))
|
|
177
|
-
addPluginCompress && (await fastify.register(compress))
|
|
151
|
+
!loadApollo && plugins?.helmet && (await fastify.register(helmet))
|
|
152
|
+
plugins?.rateLimit && (await fastify.register(rateLimit))
|
|
153
|
+
plugins?.cors && (await fastify.register(cors, plugins.cors || {}))
|
|
154
|
+
plugins?.compress && (await fastify.register(compress))
|
|
178
155
|
|
|
179
156
|
// JWT Validator
|
|
180
157
|
log.t && log.trace(`Add JWT - expiresIn: ${JWT_EXPIRES_IN}`)
|
|
@@ -188,6 +165,52 @@ const start = async () => {
|
|
|
188
165
|
await addApolloRouting(fastify, apollo)
|
|
189
166
|
await addFastifyRouting(fastify)
|
|
190
167
|
|
|
168
|
+
// defaults
|
|
169
|
+
decorators = {
|
|
170
|
+
userManager: {
|
|
171
|
+
createUser(data: any) {
|
|
172
|
+
throw Error('Not implemented')
|
|
173
|
+
},
|
|
174
|
+
resetExternalId(data: any) {
|
|
175
|
+
throw Error('Not implemented')
|
|
176
|
+
},
|
|
177
|
+
updateUserById(id: string, user: any) {
|
|
178
|
+
throw Error('Not implemented')
|
|
179
|
+
},
|
|
180
|
+
retrieveUserById(id: string) {
|
|
181
|
+
throw Error('Not implemented')
|
|
182
|
+
},
|
|
183
|
+
retrieveUserByEmail(email: string) {
|
|
184
|
+
throw Error('Not implemented')
|
|
185
|
+
},
|
|
186
|
+
retrieveUserByExternalId(externalId: string) {
|
|
187
|
+
throw Error('Not implemented')
|
|
188
|
+
},
|
|
189
|
+
retrieveUserByPassword(email: string, password: string) {
|
|
190
|
+
throw Error('Not implemented')
|
|
191
|
+
},
|
|
192
|
+
changePassword(email: string, password: string, oldPassword: string) {
|
|
193
|
+
throw Error('Not implemented')
|
|
194
|
+
},
|
|
195
|
+
enableUserById(id: string) {
|
|
196
|
+
throw Error('Not implemented')
|
|
197
|
+
},
|
|
198
|
+
disableUserById(id: string) {
|
|
199
|
+
throw Error('Not implemented')
|
|
200
|
+
},
|
|
201
|
+
isValidUser(data: any) {
|
|
202
|
+
throw Error('Not implemented')
|
|
203
|
+
}
|
|
204
|
+
} as UserManagement,
|
|
205
|
+
...decorators
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await Promise.all(
|
|
209
|
+
Object.keys(decorators || {}).map(async (key) => {
|
|
210
|
+
await fastify.decorate(key, decorators[key])
|
|
211
|
+
})
|
|
212
|
+
)
|
|
213
|
+
|
|
191
214
|
await fastify
|
|
192
215
|
.listen({
|
|
193
216
|
port: Number(port),
|
|
@@ -216,7 +239,8 @@ export {
|
|
|
216
239
|
Roles,
|
|
217
240
|
Route,
|
|
218
241
|
RouteConfig,
|
|
219
|
-
ConfiguredRoute
|
|
242
|
+
ConfiguredRoute,
|
|
243
|
+
UserManagement
|
|
220
244
|
} from './types/global'
|
|
221
245
|
|
|
222
246
|
/**
|
|
@@ -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',
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = [
|
|
4
|
+
{
|
|
5
|
+
name: 'cors',
|
|
6
|
+
enable: false,
|
|
7
|
+
options: {}
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
name: 'rateLimit',
|
|
11
|
+
enable: false,
|
|
12
|
+
options: {}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'helmet',
|
|
16
|
+
enable: false,
|
|
17
|
+
options: {}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'compress',
|
|
21
|
+
enable: false,
|
|
22
|
+
options: {}
|
|
23
|
+
}
|
|
24
|
+
]
|
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
|
|
@@ -43,13 +37,13 @@ module.exports = async (req, reply) => {
|
|
|
43
37
|
|
|
44
38
|
if (req.routeConfig.requiredRoles?.length > 0) {
|
|
45
39
|
const { method = '', url = '', requiredRoles } = req.routeConfig
|
|
46
|
-
const userRoles: string[] = req.user?.roles?.map((
|
|
40
|
+
const userRoles: string[] = req.user?.roles?.map((code) => code) || []
|
|
47
41
|
const resolvedRoles = userRoles.length > 0 ? requiredRoles.filter((r) => userRoles.includes(r.code)) : []
|
|
48
42
|
|
|
49
43
|
if (!resolvedRoles.length) {
|
|
50
44
|
log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${url}`)
|
|
51
45
|
return reply
|
|
52
|
-
.
|
|
46
|
+
.status(403)
|
|
53
47
|
.send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to call this route' })
|
|
54
48
|
}
|
|
55
49
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { config } from 'dotenv'
|
|
2
|
+
|
|
3
|
+
const glob = require('glob')
|
|
4
|
+
|
|
5
|
+
export function load() {
|
|
6
|
+
const plugins: any = {}
|
|
7
|
+
|
|
8
|
+
const patterns = [`${__dirname}/../config/plugins.{ts,js}`, `${process.cwd()}/src/config/plugins.{ts,js}`]
|
|
9
|
+
patterns.forEach((pattern) => {
|
|
10
|
+
log.t && log.trace('Looking for ' + pattern)
|
|
11
|
+
glob.sync(pattern).forEach((f: string) => {
|
|
12
|
+
const configPlugins = require(f)
|
|
13
|
+
configPlugins.forEach((plugin) => {
|
|
14
|
+
plugins[plugin.name] = plugin.enable ? plugin.options : false
|
|
15
|
+
log.t && log.trace(`* Plugin ${plugin.name} ${plugin.enable ? 'enabled' : 'disabled'}`)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
const enabledPulgins = Object.keys(plugins).filter((p) => !!plugins[p])
|
|
20
|
+
log.d && log.debug(`Plugins loaded: ${enabledPulgins.length > 0 ? enabledPulgins.join(', ') : 0}`)
|
|
21
|
+
return plugins
|
|
22
|
+
}
|