@volcanicminds/backend 0.2.38 → 0.2.40

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 (39) hide show
  1. package/dist/index.js +52 -3
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/api/auth/controller/auth.js +33 -17
  4. package/dist/lib/api/auth/controller/auth.js.map +1 -1
  5. package/dist/lib/api/auth/routes.js +29 -0
  6. package/dist/lib/api/auth/routes.js.map +1 -1
  7. package/dist/lib/api/token/controller/token.js +114 -0
  8. package/dist/lib/api/token/controller/token.js.map +1 -0
  9. package/dist/lib/api/token/routes.js +170 -0
  10. package/dist/lib/api/token/routes.js.map +1 -0
  11. package/dist/lib/api/users/controller/user.js +1 -1
  12. package/dist/lib/api/users/controller/user.js.map +1 -1
  13. package/dist/lib/hooks/onRequest.js +28 -10
  14. package/dist/lib/hooks/onRequest.js.map +1 -1
  15. package/dist/lib/middleware/isAdmin.js +1 -1
  16. package/dist/lib/middleware/isAdmin.js.map +1 -1
  17. package/dist/lib/middleware/isAuthenticated.js +1 -1
  18. package/dist/lib/middleware/isAuthenticated.js.map +1 -1
  19. package/dist/lib/schemas/auth.js +2 -0
  20. package/dist/lib/schemas/auth.js.map +1 -0
  21. package/dist/lib/schemas/common.js +33 -0
  22. package/dist/lib/schemas/common.js.map +1 -0
  23. package/dist/lib/schemas/token.js +39 -0
  24. package/dist/lib/schemas/token.js.map +1 -0
  25. package/index.d.ts +1 -0
  26. package/index.ts +57 -5
  27. package/lib/api/auth/controller/auth.ts +34 -15
  28. package/lib/api/auth/routes.ts +29 -0
  29. package/lib/api/token/controller/token.ts +99 -0
  30. package/lib/api/token/routes.ts +168 -0
  31. package/lib/api/users/controller/user.ts +1 -1
  32. package/lib/hooks/onRequest.ts +32 -12
  33. package/lib/middleware/isAdmin.ts +1 -1
  34. package/lib/middleware/isAuthenticated.ts +1 -1
  35. package/lib/schemas/auth.ts +0 -0
  36. package/lib/schemas/common.ts +31 -0
  37. package/lib/schemas/token.ts +37 -0
  38. package/package.json +1 -1
  39. package/types/global.d.ts +32 -5
@@ -34,6 +34,7 @@ module.exports = {
34
34
  type: 'object',
35
35
  properties: {
36
36
  id: { type: 'string' },
37
+ _id: { type: 'string' },
37
38
  externalId: { type: 'string' },
38
39
  username: { type: 'string' },
39
40
  email: { type: 'string' },
@@ -71,6 +72,32 @@ module.exports = {
71
72
  }
72
73
  }
73
74
  },
75
+ {
76
+ method: 'POST',
77
+ path: '/validate-password',
78
+ roles: [],
79
+ handler: 'auth.validatePassword',
80
+ middlewares: [],
81
+ config: {
82
+ title: 'Validate password',
83
+ description: 'Validate password if valid and usable',
84
+ body: {
85
+ type: 'object',
86
+ properties: {
87
+ password: { type: 'string' }
88
+ }
89
+ },
90
+ response: {
91
+ 200: {
92
+ description: 'Default response',
93
+ type: 'object',
94
+ properties: {
95
+ ok: { type: 'boolean' }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ },
74
101
  {
75
102
  method: 'POST',
76
103
  path: '/change-password',
@@ -203,6 +230,7 @@ module.exports = {
203
230
  type: 'object',
204
231
  properties: {
205
232
  id: { type: 'string' },
233
+ _id: { type: 'string' },
206
234
  externalId: { type: 'string' },
207
235
  username: { type: 'string' },
208
236
  email: { type: 'string' },
@@ -251,6 +279,7 @@ module.exports = {
251
279
  }
252
280
  }
253
281
  },
282
+ body: { $ref: 'blockBodySchema' },
254
283
  response: {
255
284
  200: {
256
285
  description: 'Default response',
@@ -0,0 +1,99 @@
1
+ import { FastifyReply, FastifyRequest } from 'fastify'
2
+
3
+ export async function count(req: FastifyRequest, reply: FastifyReply) {
4
+ return await req.server['tokenManager'].countQuery(req.data())
5
+ }
6
+
7
+ export async function find(req: FastifyRequest, reply: FastifyReply) {
8
+ const { headers, records } = await req.server['tokenManager'].findQuery(req.data())
9
+ return reply.type('application/json').headers(headers).send(records)
10
+ }
11
+
12
+ export async function findOne(req: FastifyRequest, reply: FastifyReply) {
13
+ const { id } = req.parameters()
14
+
15
+ const token = await req.server['tokenManager'].retrieveTokenById(id)
16
+ return token || reply.status(404).send()
17
+ }
18
+
19
+ export async function create(req: FastifyRequest, reply: FastifyReply) {
20
+ const data = req.data()
21
+
22
+ if (!data.name) {
23
+ return reply.status(404).send(Error('Token name not valid'))
24
+ }
25
+
26
+ // public is the default
27
+ const publicRole = global.roles?.public?.code || 'public'
28
+ data.roles = (data.requiredRoles || []).map((r) => global.roles[r]?.code).filter((r) => !!r)
29
+ if (!data.roles.includes(publicRole)) {
30
+ data.roles.push(publicRole)
31
+ }
32
+
33
+ let token = await req.server['tokenManager'].createToken(data)
34
+ if (!token || !token.getId() || !token.externalId) {
35
+ return reply.status(400).send(Error('Token not registered'))
36
+ }
37
+
38
+ const bearerToken = await reply.jwtSign({ sub: token.externalId })
39
+ if (!bearerToken) {
40
+ return reply.status(400).send(Error('Token not signed'))
41
+ }
42
+
43
+ token = await req.server['tokenManager'].updateTokenById(token.getId(), { token: bearerToken })
44
+ return token
45
+ }
46
+
47
+ export async function remove(req: FastifyRequest, reply: FastifyReply) {
48
+ const { id } = req.parameters()
49
+ if (!id) {
50
+ return reply.status(404).send()
51
+ }
52
+
53
+ let token = await req.server['tokenManager'].retrieveTokenById(id)
54
+ if (!token) {
55
+ return reply.status(403).send(Error('Token not found'))
56
+ }
57
+
58
+ token = await req.server['tokenManager'].removeTokenById(id)
59
+ return { ok: true }
60
+ }
61
+
62
+ export async function update(req: FastifyRequest, reply: FastifyReply) {
63
+ const { id } = req.parameters()
64
+ if (!id) {
65
+ return reply.status(404).send()
66
+ }
67
+
68
+ const token = await req.server['tokenManager'].retrieveTokenById(id)
69
+ if (!token || !token.getId()) {
70
+ return reply.status(404).send()
71
+ }
72
+
73
+ const data = req.data() || {}
74
+ return req.server['tokenManager'].updateTokenById(token.getId(), data)
75
+ }
76
+
77
+ export async function block(req: FastifyRequest, reply: FastifyReply) {
78
+ if (!req.hasRole(roles.admin) && !req.hasRole(roles.backoffice)) {
79
+ return reply.status(403).send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to block a user' })
80
+ }
81
+
82
+ const { id: userId } = req.parameters()
83
+ const { reason } = req.data()
84
+
85
+ const user = await req.server['tokenManager'].blockTokenById(userId, reason)
86
+ return { ok: !!user.getId() }
87
+ }
88
+
89
+ export async function unblock(req: FastifyRequest, reply: FastifyReply) {
90
+ if (!req.hasRole(roles.admin) && !req.hasRole(roles.backoffice)) {
91
+ return reply
92
+ .status(403)
93
+ .send({ statusCode: 403, code: 'ROLE_NOT_ALLOWED', message: 'Not allowed to unblock a user' })
94
+ }
95
+
96
+ const { id: userId } = req.parameters()
97
+ const user = await req.server['tokenManager'].unblockTokenById(userId)
98
+ return { ok: !!user.getId() }
99
+ }
@@ -0,0 +1,168 @@
1
+ module.exports = {
2
+ config: {
3
+ title: 'Integration token functions',
4
+ description: 'Integration token functions',
5
+ controller: 'controller',
6
+ tags: ['token'],
7
+ deprecated: false,
8
+ version: false,
9
+ enable: true
10
+ },
11
+ routes: [
12
+ {
13
+ method: 'GET',
14
+ path: '/',
15
+ roles: [roles.admin, roles.backoffice],
16
+ handler: 'token.find',
17
+ middlewares: [],
18
+ config: {
19
+ title: 'Find tokens',
20
+ description: 'Get tokens list',
21
+ query: { $ref: 'getQueryParamsSchema' },
22
+ response: {
23
+ 200: {
24
+ description: 'Default response',
25
+ type: 'array',
26
+ items: { $ref: 'tokenSchema#' }
27
+ }
28
+ }
29
+ }
30
+ },
31
+ {
32
+ method: 'GET',
33
+ path: '/count',
34
+ roles: [roles.admin, roles.backoffice],
35
+ handler: 'token.count',
36
+ middlewares: [],
37
+ config: {
38
+ title: 'Count tokens',
39
+ description: 'Count tokens',
40
+ response: {
41
+ 200: {
42
+ description: 'Default response',
43
+ type: 'number'
44
+ }
45
+ }
46
+ }
47
+ },
48
+ {
49
+ method: 'GET',
50
+ path: '/:id',
51
+ roles: [roles.admin, roles.backoffice],
52
+ handler: 'token.findOne',
53
+ middlewares: [],
54
+ config: {
55
+ title: 'Find token',
56
+ description: 'Get token by id',
57
+ params: { $ref: 'tokenParamsSchema#' },
58
+ response: {
59
+ 200: {
60
+ description: 'Default response',
61
+ $ref: 'tokenSchema#'
62
+ }
63
+ }
64
+ }
65
+ },
66
+ {
67
+ method: 'POST',
68
+ path: '/',
69
+ roles: [roles.admin, roles.backoffice],
70
+ handler: 'token.create',
71
+ middlewares: ['global.isAuthenticated'],
72
+ config: {
73
+ title: 'Create new token',
74
+ description: 'Create a new token',
75
+ body: { $ref: 'tokenBodySchema' },
76
+ response: {
77
+ 200: {
78
+ description: 'Default response',
79
+ $ref: 'tokenSchema#'
80
+ }
81
+ }
82
+ }
83
+ },
84
+ {
85
+ method: 'PUT',
86
+ path: '/:id',
87
+ roles: [roles.admin, roles.backoffice],
88
+ handler: 'token.update',
89
+ middlewares: ['global.isAuthenticated'],
90
+ config: {
91
+ title: 'Update existing token',
92
+ description: 'Update an existing token',
93
+ params: { $ref: 'tokenParamsSchema#' },
94
+ body: { $ref: 'tokenBodySchema' },
95
+ response: {
96
+ 200: {
97
+ description: 'Default response',
98
+ $ref: 'tokenSchema#'
99
+ }
100
+ }
101
+ }
102
+ },
103
+ {
104
+ method: 'DELETE',
105
+ path: '/:id',
106
+ roles: [roles.admin, roles.backoffice],
107
+ handler: 'token.remove',
108
+ middlewares: ['global.isAuthenticated'],
109
+ config: {
110
+ title: 'Unregister existing token (actually disables it)',
111
+ description: 'Unregister an existing token (actually disables it)',
112
+ params: { $ref: 'tokenParamsSchema#' },
113
+ response: {
114
+ 200: {
115
+ description: 'Default response',
116
+ type: 'object',
117
+ properties: {
118
+ ok: { type: 'boolean' }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ },
124
+ {
125
+ method: 'POST',
126
+ path: '/block/:id',
127
+ roles: [roles.admin, roles.backoffice],
128
+ handler: 'token.block',
129
+ middlewares: ['global.isAuthenticated'],
130
+ config: {
131
+ title: 'Block a token by id',
132
+ description: 'Block a token by id',
133
+ params: { $ref: 'tokenParamsSchema#' },
134
+ body: { $ref: 'blockBodySchema' },
135
+ response: {
136
+ 200: {
137
+ description: 'Default response',
138
+ type: 'object',
139
+ properties: {
140
+ ok: { type: 'boolean' }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ },
146
+ {
147
+ method: 'POST',
148
+ path: '/unblock/:id',
149
+ roles: [roles.admin, roles.backoffice],
150
+ handler: 'token.unblock',
151
+ middlewares: ['global.isAuthenticated'],
152
+ config: {
153
+ title: 'Unblock a token by id',
154
+ description: 'Unblock a token by id',
155
+ params: { $ref: 'tokenParamsSchema#' },
156
+ response: {
157
+ 200: {
158
+ description: 'Default response',
159
+ type: 'object',
160
+ properties: {
161
+ ok: { type: 'boolean' }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ ]
168
+ }
@@ -8,5 +8,5 @@ export async function user(req: FastifyRequest, reply: FastifyReply) {
8
8
 
9
9
  export async function isAdmin(req: FastifyRequest, reply: FastifyReply) {
10
10
  const user: AuthenticatedUser | undefined = req.user
11
- reply.send({ isAdmin: user?.id && req.hasRole(roles.admin) })
11
+ reply.send({ isAdmin: user?.getId() && req.hasRole(roles.admin) })
12
12
  }
@@ -1,5 +1,5 @@
1
1
  import { getParams, getData } from '../util/common'
2
- import { AuthenticatedUser, Role } from '../../types/global'
2
+ import { AuthenticatedUser, AuthenticatedToken, Role } from '../../types/global'
3
3
 
4
4
  module.exports = async (req, reply) => {
5
5
  // request enrichment
@@ -11,24 +11,42 @@ module.exports = async (req, reply) => {
11
11
 
12
12
  // authorization check
13
13
  const auth = req.headers?.authorization || ''
14
- const [prefix, token] = auth.split(' ')
14
+ const [prefix, bearerToken] = auth.split(' ')
15
15
  const isRoutePublic = (req.routeConfig.requiredRoles || []).some((role: Role) => role.code === roles.public.code)
16
16
 
17
- if (prefix === 'Bearer' && token != null) {
17
+ if (prefix === 'Bearer' && bearerToken != null) {
18
18
  let user: AuthenticatedUser = {} as AuthenticatedUser
19
+ let token: AuthenticatedToken = {} as AuthenticatedToken
20
+
21
+ console.log('bearer ' + bearerToken)
22
+
19
23
  try {
20
- const tokenData = reply.server.jwt.verify(token)
24
+ const tokenData = reply.server.jwt.verify(bearerToken)
21
25
  user = await req.server['userManager'].retrieveUserByExternalId(tokenData?.sub)
26
+ console.log(user)
22
27
  if (!user) {
28
+ token = await req.server['tokenManager'].retrieveTokenByExternalId(tokenData?.sub)
29
+ console.log(token)
30
+ }
31
+ if (!user && !token) {
23
32
  return reply.status(404).send({ statusCode: 404, code: 'USER_NOT_FOUND', message: 'User not found' })
24
33
  }
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' })
34
+ if (user) {
35
+ const isValid = await req.server['userManager'].isValidUser(user)
36
+ if (!isValid) {
37
+ return reply.status(404).send({ statusCode: 404, code: 'USER_NOT_VALID', message: 'User not valid' })
38
+ }
39
+ // ok, we have the full user here
40
+ req.user = user
41
+ }
42
+ if (token) {
43
+ const isValid = await req.server['tokenManager'].isValidToken(token)
44
+ if (!isValid) {
45
+ return reply.status(404).send({ statusCode: 404, code: 'TOKEN_NOT_VALID', message: 'Token not valid' })
46
+ }
47
+ // ok, we have the full user here
48
+ req.token = token
28
49
  }
29
-
30
- // ok, we have the full user here
31
- req.user = user
32
50
  } catch (error) {
33
51
  if (!isRoutePublic) {
34
52
  throw error
@@ -38,8 +56,10 @@ module.exports = async (req, reply) => {
38
56
 
39
57
  if (req.routeConfig.requiredRoles?.length > 0) {
40
58
  const { method = '', url = '', requiredRoles } = req.routeConfig
41
- const userRoles: string[] = req.user?.roles?.map((code) => code) || [roles.public?.code || 'public']
42
- const resolvedRoles = userRoles.length > 0 ? requiredRoles.filter((r) => userRoles.includes(r.code)) : []
59
+ const authRoles: string[] = ((req.user?.roles || req.token?.roles)?.map((code) => code) as string[]) || [
60
+ roles.public?.code || 'public'
61
+ ]
62
+ const resolvedRoles = authRoles.length > 0 ? requiredRoles.filter((r) => authRoles.includes(r.code)) : []
43
63
 
44
64
  if (!resolvedRoles.length) {
45
65
  log.w && log.warn(`Not allowed to call ${method.toUpperCase()} ${url}`)
@@ -2,7 +2,7 @@ import { FastifyReply, FastifyRequest } from 'fastify'
2
2
 
3
3
  export function preHandler(req: FastifyRequest, res: FastifyReply, done: any) {
4
4
  try {
5
- if (req.user && req.user.id && req.hasRole(roles.admin)) {
5
+ if (req.user && req.user.getId() && req.hasRole(roles.admin)) {
6
6
  return done()
7
7
  }
8
8
 
@@ -2,7 +2,7 @@ import { FastifyReply, FastifyRequest } from 'fastify'
2
2
 
3
3
  export function preHandler(req: FastifyRequest, res: FastifyReply, done: any) {
4
4
  try {
5
- if (!!req.user?.id) {
5
+ if (!!req.user?.getId()) {
6
6
  return done()
7
7
  }
8
8
 
File without changes
@@ -0,0 +1,31 @@
1
+ export const getQueryParamsSchema = {
2
+ $id: 'getQueryParamsSchema',
3
+ type: 'object',
4
+ nullable: true,
5
+ properties: {
6
+ page: {
7
+ type: 'number',
8
+ description: 'Page **number** (default 1)'
9
+ },
10
+ pageSize: {
11
+ type: 'number',
12
+ description: 'Page **size** (default 25)'
13
+ },
14
+ sort: {
15
+ type: 'array',
16
+ description:
17
+ 'Sorting **order** (default ascending).<br/>\
18
+ Otherwise, use the postfix `:desc` or `:asc` (like `&sort=myfield:desc`)',
19
+ items: { type: 'string' }
20
+ }
21
+ }
22
+ }
23
+
24
+ export const blockBodySchema = {
25
+ $id: 'blockBodySchema',
26
+ type: 'object',
27
+ nullable: true,
28
+ properties: {
29
+ reason: { type: 'string' }
30
+ }
31
+ }
@@ -0,0 +1,37 @@
1
+ export const tokenParamsSchema = {
2
+ $id: 'tokenParamsSchema',
3
+ type: 'object',
4
+ nullable: true,
5
+ properties: {
6
+ id: {
7
+ type: 'string',
8
+ description: 'Token id'
9
+ }
10
+ }
11
+ }
12
+
13
+ export const tokenBodySchema = {
14
+ $id: 'tokenBodySchema',
15
+ type: 'object',
16
+ nullable: true,
17
+ properties: {
18
+ name: { type: 'string' },
19
+ description: { type: 'string' },
20
+ requiredRoles: { type: 'array', items: { type: 'string' } }
21
+ }
22
+ }
23
+
24
+ export const tokenSchema = {
25
+ $id: 'tokenSchema',
26
+ type: 'object',
27
+ nullable: true,
28
+ properties: {
29
+ id: { type: 'string' },
30
+ _id: { type: 'string' },
31
+ externalId: { type: 'string' },
32
+ name: { type: 'string' },
33
+ description: { type: 'string' },
34
+ token: { type: 'string' },
35
+ roles: { type: 'array', items: { type: 'string' } }
36
+ }
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volcanicminds/backend",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
4
4
  "codename": "turin",
5
5
  "license": "MIT",
6
6
  "description": "The volcanic (minds) backend",
package/types/global.d.ts CHANGED
@@ -1,12 +1,18 @@
1
1
  import { FastifyRequest, FastifyReply } from 'fastify'
2
2
 
3
3
  export interface AuthenticatedUser {
4
- id: number
4
+ getId(): any
5
5
  username: string
6
6
  email: string
7
7
  roles: Role[]
8
8
  }
9
9
 
10
+ export interface AuthenticatedToken {
11
+ getId(): any
12
+ name: string
13
+ roles: Role[]
14
+ }
15
+
10
16
  export interface Role {
11
17
  code: string
12
18
  name: string
@@ -69,25 +75,45 @@ export interface ConfiguredRoute {
69
75
  }
70
76
 
71
77
  export interface UserManagement {
78
+ isValidUser(data: any): boolean
72
79
  createUser(data: any): any | null
73
80
  resetExternalId(data: any): any | null
74
81
  updateUserById(id: string, user: any): any | null
75
82
  retrieveUserById(id: string): any | null
76
83
  retrieveUserByEmail(email: string): any | null
84
+ retrieveUserByResetPasswordToken(code: string): any | null
85
+ retrieveUserByConfirmationToken(code: string): any | null
86
+ retrieveUserByUsername(username: string): any | null
77
87
  retrieveUserByExternalId(externalId: string): any | null
78
88
  retrieveUserByPassword(email: string, password: string): any | null
79
89
  changePassword(email: string, password: string, oldPassword: string): any | null
80
90
  forgotPassword(email: string): any | null
81
- userConfirmation(user: any)
82
91
  resetPassword(user: any, password: string): any | null
83
- enableUserById(id: string): any | null
84
- disableUserById(id: string): any | null
85
- isValidUser(data: any): boolean
92
+ userConfirmation(user: any)
93
+ blockUserById(id: string, reason: string): any | null
94
+ unblockUserById(id: string): any | null
95
+ countQuery(data: any): any | null
96
+ findQuery(data: any): any | null
97
+ }
98
+
99
+ export interface TokenManagement {
100
+ isValidToken(data: any): boolean
101
+ createToken(data: any): any | null
102
+ resetExternalId(id: string): any | null
103
+ updateTokenById(id: string, token: any): any | null
104
+ retrieveTokenById(id: string): any | null
105
+ retrieveTokenByExternalId(id: string): any | null
106
+ blockTokenById(id: string, reason: string): any | null
107
+ unblockTokenById(id: string): any | null
108
+ countQuery(data: any): any | null
109
+ findQuery(data: any): any | null
110
+ removeTokenById(id: string): any | null
86
111
  }
87
112
 
88
113
  declare module 'fastify' {
89
114
  export interface FastifyRequest {
90
115
  user?: AuthenticatedUser
116
+ token?: AuthenticatedToken
91
117
  startedAt?: Date
92
118
  data(): Data
93
119
  parameters(): Data
@@ -102,6 +128,7 @@ declare module 'fastify' {
102
128
 
103
129
  export interface FastifyRequest extends FastifyRequest {
104
130
  user?: AuthenticatedUser
131
+ token?: AuthenticatedToken
105
132
  startedAt?: Date
106
133
  data(): Data
107
134
  parameters(): Data