@drax/identity-back 0.0.31 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/config/IdentityConfig.js +2 -3
  2. package/dist/factory/RoleServiceFactory.js +8 -7
  3. package/dist/factory/TenantServiceFactory.js +8 -7
  4. package/dist/factory/UserApiKeyServiceFactory.js +24 -0
  5. package/dist/factory/UserServiceFactory.js +8 -7
  6. package/dist/graphql/resolvers/user-api-key.resolvers.js +89 -0
  7. package/dist/graphql/resolvers/user.resolvers.js +7 -2
  8. package/dist/graphql/types/userApiKey.graphql +33 -0
  9. package/dist/index.js +4 -2
  10. package/dist/interfaces/IUserApiKeyRepository.js +1 -0
  11. package/dist/middleware/apiKeyMiddleware.js +30 -0
  12. package/dist/middleware/rbacMiddleware.js +0 -1
  13. package/dist/models/UserApiKeyModel.js +44 -0
  14. package/dist/permissions/IdentityPermissions.js +6 -0
  15. package/dist/rbac/Rbac.js +8 -0
  16. package/dist/repository/mongo/UserApiKeyMongoRepository.js +82 -0
  17. package/dist/repository/mongo/UserMongoRepository.js +3 -3
  18. package/dist/repository/sqlite/RoleSqliteRepository.js +16 -18
  19. package/dist/repository/sqlite/TenantSqliteRepository.js +13 -11
  20. package/dist/repository/sqlite/UserApiKeySqliteRepository.js +147 -0
  21. package/dist/repository/sqlite/UserSqliteRepository.js +29 -26
  22. package/dist/routes/UserApiKeyRoutes.js +119 -0
  23. package/dist/routes/UserRoutes.js +5 -1
  24. package/dist/services/UserApiKeyService.js +65 -0
  25. package/dist/setup/LoadIdentityConfigFromEnv.js +3 -3
  26. package/dist/utils/AuthUtils.js +10 -0
  27. package/dist/zod/UserApiKeyZod.js +9 -0
  28. package/package.json +7 -6
  29. package/src/config/IdentityConfig.ts +4 -3
  30. package/src/factory/RoleServiceFactory.ts +11 -11
  31. package/src/factory/TenantServiceFactory.ts +11 -11
  32. package/src/factory/UserApiKeyServiceFactory.ts +30 -0
  33. package/src/factory/UserServiceFactory.ts +8 -7
  34. package/src/graphql/resolvers/tenant.resolvers.ts +0 -1
  35. package/src/graphql/resolvers/user-api-key.resolvers.ts +94 -0
  36. package/src/graphql/resolvers/user.resolvers.ts +9 -2
  37. package/src/graphql/types/userApiKey.graphql +33 -0
  38. package/src/index.ts +10 -0
  39. package/src/interfaces/IUserApiKeyRepository.ts +8 -0
  40. package/src/middleware/apiKeyMiddleware.ts +35 -0
  41. package/src/middleware/rbacMiddleware.ts +1 -2
  42. package/src/models/UserApiKeyModel.ts +59 -0
  43. package/src/permissions/IdentityPermissions.ts +7 -0
  44. package/src/rbac/Rbac.ts +13 -2
  45. package/src/repository/mongo/UserApiKeyMongoRepository.ts +114 -0
  46. package/src/repository/mongo/UserMongoRepository.ts +3 -3
  47. package/src/repository/sqlite/RoleSqliteRepository.ts +28 -20
  48. package/src/repository/sqlite/TenantSqliteRepository.ts +25 -11
  49. package/src/repository/sqlite/UserApiKeySqliteRepository.ts +197 -0
  50. package/src/repository/sqlite/UserSqliteRepository.ts +37 -27
  51. package/src/routes/UserApiKeyRoutes.ts +128 -0
  52. package/src/routes/UserRoutes.ts +5 -1
  53. package/src/services/UserApiKeyService.ts +86 -0
  54. package/src/setup/LoadIdentityConfigFromEnv.ts +5 -3
  55. package/src/utils/AuthUtils.ts +11 -0
  56. package/src/zod/UserApiKeyZod.ts +15 -0
  57. package/test/data-obj/apikey/root-mongo-user-apikey.ts +10 -0
  58. package/test/data-obj/roles/admin-mongo-role.ts +0 -3
  59. package/test/initializers/RoleMongoInitializer.ts +1 -0
  60. package/test/initializers/RoleSqliteInitializer.ts +1 -0
  61. package/test/initializers/UserMongoInitializer.ts +18 -0
  62. package/test/repository/mongo/user-apikey-mongo-repository.test.ts +73 -0
  63. package/tsconfig.tsbuildinfo +1 -1
  64. package/types/factory/RoleServiceFactory.d.ts.map +1 -1
  65. package/types/factory/TenantServiceFactory.d.ts.map +1 -1
  66. package/types/factory/UserApiKeyServiceFactory.d.ts.map +1 -0
  67. package/types/factory/UserServiceFactory.d.ts.map +1 -1
  68. package/types/graphql/resolvers/tenant.resolvers.d.ts.map +1 -1
  69. package/types/graphql/resolvers/user-api-key.resolvers.d.ts.map +1 -0
  70. package/types/graphql/resolvers/user.resolvers.d.ts.map +1 -1
  71. package/types/index.d.ts.map +1 -1
  72. package/types/interfaces/IUserApiKeyRepository.d.ts.map +1 -0
  73. package/types/middleware/apiKeyMiddleware.d.ts.map +1 -0
  74. package/types/middleware/rbacMiddleware.d.ts.map +1 -1
  75. package/types/models/UserApiKeyModel.d.ts.map +1 -0
  76. package/types/rbac/Rbac.d.ts +15 -0
  77. package/types/rbac/Rbac.d.ts.map +1 -1
  78. package/types/repository/mongo/UserApiKeyMongoRepository.d.ts.map +1 -0
  79. package/types/repository/sqlite/RoleSqliteRepository.d.ts.map +1 -1
  80. package/types/repository/sqlite/TenantSqliteRepository.d.ts.map +1 -1
  81. package/types/repository/sqlite/UserApiKeySqliteRepository.d.ts +19 -0
  82. package/types/repository/sqlite/UserApiKeySqliteRepository.d.ts.map +1 -0
  83. package/types/repository/sqlite/UserSqliteRepository.d.ts.map +1 -1
  84. package/types/routes/UserApiKeyRoutes.d.ts.map +1 -0
  85. package/types/routes/UserRoutes.d.ts.map +1 -1
  86. package/types/services/UserApiKeyService.d.ts.map +1 -0
  87. package/types/setup/LoadIdentityConfigFromEnv.d.ts.map +1 -1
  88. package/types/utils/AuthUtils.d.ts.map +1 -1
  89. package/types/zod/UserApiKeyZod.d.ts +16 -0
  90. package/types/zod/UserApiKeyZod.d.ts.map +1 -0
  91. package/src/utils/DbSetupUtils.ts +0 -41
  92. package/types/config/IdentityConfig.d.ts +0 -13
  93. package/types/config/IdentityConfig.d.ts.map +0 -1
  94. package/types/graphql/resolvers/role.resolvers.d.ts +0 -52
  95. package/types/graphql/resolvers/tenant.resolvers.d.ts +0 -49
  96. package/types/graphql/resolvers/user.resolvers.d.ts +0 -67
  97. package/types/utils/DbSetupUtils.d.ts.map +0 -1
@@ -4,27 +4,35 @@ import {UUID} from "crypto";
4
4
  import sqlite from "better-sqlite3";
5
5
  import {randomUUID} from "node:crypto";
6
6
  import {IDraxPaginateResult, IDraxPaginateOptions} from "@drax/common-share";
7
- import {SqliteErrorToValidationError} from "@drax/common-back";
7
+ import {SqliteErrorToValidationError, SqliteTableBuilder} from "@drax/common-back";
8
+ import type {SqliteTableField} from "@drax/common-back";
9
+
10
+
11
+ const tableFields: SqliteTableField[] = [
12
+ {name: "name", type: "TEXT", unique: false, primary: false},
13
+ {name: "createdAt", type: "TEXT", unique: false, primary: false},
14
+ {name: "updatedAt", type: "TEXT", unique: false, primary: false},
15
+ ]
8
16
 
9
- const tenantTableSQL: string = `
10
- CREATE TABLE IF NOT EXISTS tenants
11
- (
12
- id TEXT PRIMARY KEY,
13
- name TEXT
14
- );
15
- `;
16
17
 
17
18
  class TenantSqliteRepository implements ITenantRepository{
18
19
 
19
20
  private db: any;
21
+ private dataBaseFile: string;
20
22
 
21
- constructor(DATABASE:string, verbose:boolean = false) {
22
- this.db = new sqlite(DATABASE, {verbose: verbose ? console.log : null});
23
+ constructor(dataBaseFile:string, verbose:boolean = false) {
24
+ this.dataBaseFile = dataBaseFile;
25
+ this.db = new sqlite(this.dataBaseFile, {verbose: verbose ? console.log : null});
23
26
  this.table()
24
27
  }
25
28
 
26
29
  table() {
27
- this.db.exec(tenantTableSQL);
30
+ const builder = new SqliteTableBuilder(
31
+ this.dataBaseFile,
32
+ 'tenants',
33
+ tableFields,
34
+ false);
35
+ builder.build('id')
28
36
  }
29
37
 
30
38
 
@@ -36,6 +44,7 @@ class TenantSqliteRepository implements ITenantRepository{
36
44
  tenantData.id = randomUUID()
37
45
  }
38
46
 
47
+ tenantData.createdAt = (new Date().toISOString())
39
48
 
40
49
  const fields = Object.keys(tenantData)
41
50
  .map(field => `${field}`)
@@ -66,10 +75,15 @@ class TenantSqliteRepository implements ITenantRepository{
66
75
 
67
76
  async update(id: string, tenantData: ITenantBase): Promise<ITenant> {
68
77
  try{
78
+
79
+ tenantData.updatedAt = (new Date().toISOString())
80
+
69
81
  const setClauses = Object.keys(tenantData)
70
82
  .map(field => `${field} = @${field}`)
71
83
  .join(', ');
84
+
72
85
  tenantData.id = id
86
+
73
87
  const stmt = this.db.prepare( `UPDATE tenants SET ${setClauses} WHERE id = @id `);
74
88
  stmt.run(tenantData);
75
89
  return this.findById(id)
@@ -0,0 +1,197 @@
1
+ import {IUserApiKey, IUserApiKeyBase} from '@drax/identity-share'
2
+ import {IUserApiKeyRepository} from '../../interfaces/IUserApiKeyRepository'
3
+ import {UUID} from "crypto";
4
+ import sqlite from "better-sqlite3";
5
+ import {randomUUID} from "node:crypto";
6
+ import {IDraxPaginateResult, IDraxPaginateOptions} from "@drax/common-share";
7
+ import {SqliteErrorToValidationError, SqliteTableBuilder} from "@drax/common-back";
8
+ import type {SqliteTableField} from "@drax/common-back";
9
+ import UserSqliteRepository from "./UserSqliteRepository.js";
10
+
11
+
12
+ const tableFields: SqliteTableField[] = [
13
+ {name: "secret", type: "TEXT", unique: true, primary: false},
14
+ {name: "name", type: "TEXT", unique: false, primary: false},
15
+ {name: "user", type: "TEXT", unique: false, primary: false},
16
+ {name: "ipv4", type: "TEXT", unique: false, primary: false},
17
+ {name: "ipv6", type: "TEXT", unique: false, primary: false},
18
+ {name: "createdAt", type: "TEXT", unique: false, primary: false}
19
+ ]
20
+
21
+ class UserApiKeySqliteRepository implements IUserApiKeyRepository {
22
+
23
+ private db: any;
24
+ private dataBaseFile: string;
25
+ private userRepository: UserSqliteRepository;
26
+
27
+ constructor(dataBaseFile: string, verbose: boolean = false) {
28
+ this.dataBaseFile = dataBaseFile
29
+ this.userRepository = new UserSqliteRepository(dataBaseFile, verbose)
30
+ this.db = new sqlite(dataBaseFile, {verbose: verbose ? console.log : null});
31
+ this.table()
32
+ }
33
+
34
+ async findUserById(id: string) {
35
+ return await this.userRepository.findById(id)
36
+ }
37
+
38
+ table() {
39
+ const builder = new SqliteTableBuilder(
40
+ this.dataBaseFile,
41
+ 'user_api_keys',
42
+ tableFields,
43
+ false);
44
+ builder.build('id')
45
+ }
46
+
47
+
48
+ async create(userApiKeyData: IUserApiKeyBase): Promise<IUserApiKey> {
49
+ try {
50
+
51
+ if (!userApiKeyData.id) {
52
+ userApiKeyData.id = randomUUID()
53
+ }
54
+
55
+ if (userApiKeyData.ipv4 && Array.isArray(userApiKeyData.ipv4) && userApiKeyData.ipv4.length > 0) {
56
+ userApiKeyData.ipv4 = userApiKeyData.ipv4.join(',')
57
+ }else{
58
+ userApiKeyData.ipv4 = ""
59
+ }
60
+
61
+ if (userApiKeyData.ipv6 && Array.isArray(userApiKeyData.ipv6) && userApiKeyData.ipv6.length > 0) {
62
+ userApiKeyData.ipv6 = userApiKeyData.ipv6.join(',')
63
+ }else{
64
+ userApiKeyData.ipv6 = ""
65
+ }
66
+
67
+ userApiKeyData.createdAt = (new Date().toISOString())
68
+
69
+ const fields = Object.keys(userApiKeyData)
70
+ .map(field => `${field}`)
71
+ .join(', ');
72
+
73
+ const values = Object.keys(userApiKeyData)
74
+ .map(field => `@${field}`)
75
+ .join(', ');
76
+
77
+ const stmt = this.db.prepare(`INSERT INTO user_api_keys (${fields})
78
+ VALUES (${values})`);
79
+ stmt.run(userApiKeyData)
80
+ return this.findById(userApiKeyData.id as UUID)
81
+ } catch (e) {
82
+ console.log(e)
83
+ throw SqliteErrorToValidationError(e, userApiKeyData)
84
+ }
85
+ }
86
+
87
+ async findById(id: string): Promise<IUserApiKey | null> {
88
+ const userApiKey = this.db.prepare('SELECT * FROM user_api_keys WHERE id = ?').get(id);
89
+ userApiKey.ipv4 = userApiKey.ipv4 != "" ? userApiKey.ipv4.split(',') : []
90
+ userApiKey.ipv6 = userApiKey.ipv6 != "" ? userApiKey.ipv6.split(',') : []
91
+ userApiKey.user = await this.findUserById(userApiKey.user)
92
+ return userApiKey
93
+ }
94
+
95
+ async findBySecret(secret: string): Promise<IUserApiKey | null> {
96
+ const userApiKey = this.db.prepare('SELECT * FROM user_api_keys WHERE secret = ?').get(secret);
97
+ userApiKey.ipv4 = userApiKey.ipv4 != "" ? userApiKey.ipv4.split(',') : []
98
+ userApiKey.ipv6 = userApiKey.ipv6 != "" ? userApiKey.ipv6.split(',') : []
99
+ userApiKey.user = await this.findUserById(userApiKey.user)
100
+ return userApiKey
101
+ }
102
+
103
+ async update(id: string, userApiKeyData: IUserApiKeyBase): Promise<IUserApiKey> {
104
+ try {
105
+
106
+ if (userApiKeyData.ipv4 && Array.isArray(userApiKeyData.ipv4) && userApiKeyData.ipv4.length > 0) {
107
+ userApiKeyData.ipv4 = userApiKeyData.ipv4.join(',')
108
+ }else{
109
+ userApiKeyData.ipv4 = ""
110
+ }
111
+
112
+ if (userApiKeyData.ipv6 && Array.isArray(userApiKeyData.ipv6) && userApiKeyData.ipv6.length > 0) {
113
+ userApiKeyData.ipv6 = userApiKeyData.ipv6.join(',')
114
+ }else{
115
+ userApiKeyData.ipv6 = ""
116
+ }
117
+
118
+ delete userApiKeyData.secret
119
+
120
+ const setClauses = Object.keys(userApiKeyData)
121
+ .map(field => `${field} = @${field}`)
122
+ .join(', ');
123
+
124
+ userApiKeyData.id = id
125
+
126
+ const stmt = this.db.prepare(`UPDATE user_api_keys
127
+ SET ${setClauses}
128
+ WHERE id = @id `);
129
+ stmt.run(userApiKeyData);
130
+ return this.findById(id)
131
+ } catch (e) {
132
+ console.log(e)
133
+ throw SqliteErrorToValidationError(e, userApiKeyData)
134
+ }
135
+
136
+ }
137
+
138
+ async delete(id: string): Promise<boolean> {
139
+ const stmt = this.db.prepare('DELETE FROM user_api_keys WHERE id = ?');
140
+ stmt.run(id);
141
+ return true
142
+ }
143
+
144
+
145
+ async paginate({
146
+ page = 1,
147
+ limit = 5,
148
+ orderBy = '',
149
+ orderDesc = false,
150
+ search = '',
151
+ filters = []
152
+ }: IDraxPaginateOptions): Promise<IDraxPaginateResult<IUserApiKey>> {
153
+ const offset = page > 1 ? (page - 1) * limit : 0
154
+
155
+ let where = ""
156
+ if (search) {
157
+ where = ` WHERE name LIKE '%${search}%'`
158
+ }
159
+
160
+ let whereFilters= []
161
+ if(filters && filters.length > 0 ){
162
+ where = where ? ` AND ` : ` WHERE `
163
+ for(const filter of filters){
164
+ if(['eq','$eq'].includes(filter.operator)){
165
+ whereFilters.push(` ${filter.field} = '${filter.value}' `)
166
+ }
167
+ if(['ne','$ne'].includes(filter.operator)){
168
+ whereFilters.push(` ${filter.field} != '${filter.value}' `)
169
+ }
170
+ if(['in','$in'].includes(filter.operator)){
171
+ whereFilters.push(` ${filter.field} LIKE '%${filter.value}%' `)
172
+ }
173
+ }
174
+ where += whereFilters.join(" AND ")
175
+ }
176
+
177
+ const rCount = this.db.prepare('SELECT COUNT(*) as count FROM user_api_keys' + where).get();
178
+ const userApiKeys = this.db.prepare('SELECT * FROM user_api_keys ' + where + ' LIMIT ? OFFSET ?').all([limit, offset]);
179
+
180
+ for (const userApiKey of userApiKeys) {
181
+ userApiKey.ipv4 = userApiKey.ipv4 != "" ? userApiKey.ipv4.split(',') : []
182
+ userApiKey.ipv6 = userApiKey.ipv6 != "" ? userApiKey.ipv6.split(',') : []
183
+ userApiKey.user = await this.findUserById(userApiKey.user)
184
+ }
185
+
186
+ return {
187
+ page: page,
188
+ limit: limit,
189
+ total: rCount.count,
190
+ items: userApiKeys
191
+ }
192
+ }
193
+
194
+
195
+ }
196
+
197
+ export default UserApiKeySqliteRepository
@@ -4,42 +4,48 @@ import {randomUUID} from "node:crypto";
4
4
  import {UUID} from "crypto";
5
5
  import {IUserRepository} from "../../interfaces/IUserRepository";
6
6
  import {IDraxPaginateResult, IDraxPaginateOptions} from "@drax/common-share";
7
- import { SqliteErrorToValidationError, ValidationError} from "@drax/common-back";
7
+ import {SqliteErrorToValidationError, SqliteTableBuilder, ValidationError} from "@drax/common-back";
8
+ import type {SqliteTableField} from "@drax/common-back";
8
9
  import RoleSqliteRepository from "./RoleSqliteRepository.js";
9
10
  import TenantSqliteRepository from "./TenantSqliteRepository.js";
10
11
 
11
-
12
- const userTableSQL: string = `
13
- CREATE TABLE IF NOT EXISTS users
14
- (
15
- id TEXT PRIMARY KEY,
16
- name TEXT,
17
- username TEXT UNIQUE,
18
- active INTEGER,
19
- password TEXT,
20
- email TEXT UNIQUE,
21
- phone TEXT,
22
- role TEXT,
23
- tenant TEXT,
24
- groups TEXT,
25
- avatar TEXT
26
- );
27
- `;
12
+ const tableFields: SqliteTableField[] = [
13
+ {name: "name", type: "TEXT", unique: false, primary: false},
14
+ {name: "username", type: "TEXT", unique: true, primary: false},
15
+ {name: "active", type: "INTEGER", unique: false, primary: false},
16
+ {name: "active", type: "INTEGER", unique: false, primary: false},
17
+ {name: "password", type: "TEXT", unique: false, primary: false},
18
+ {name: "email", type: "TEXT", unique: true, primary: false},
19
+ {name: "phone", type: "TEXT", unique: false, primary: false},
20
+ {name: "role", type: "TEXT", unique: false, primary: false},
21
+ {name: "tenant", type: "TEXT", unique: false, primary: false},
22
+ {name: "groups", type: "TEXT", unique: false, primary: false},
23
+ {name: "avatar", type: "TEXT", unique: false, primary: false},
24
+ {name: "createdAt", type: "TEXT", unique: false, primary: false},
25
+ {name: "updatedAt", type: "TEXT", unique: false, primary: false}
26
+ ]
28
27
 
29
28
  class UserSqliteRepository implements IUserRepository {
30
29
  private db: any;
31
30
  private roleRepository: RoleSqliteRepository;
32
31
  private tenantRepository: TenantSqliteRepository;
32
+ private dataBaseFile: string;
33
33
 
34
- constructor(DATABASE: string, verbose: boolean = false) {
35
- this.db = new sqlite(DATABASE, {verbose: verbose ? console.log : null});
36
- this.roleRepository = new RoleSqliteRepository(DATABASE, verbose)
37
- this.tenantRepository = new TenantSqliteRepository(DATABASE, verbose)
34
+ constructor(dataBaseFile: string, verbose: boolean = false) {
35
+ this.dataBaseFile = dataBaseFile
36
+ this.db = new sqlite(dataBaseFile, {verbose: verbose ? console.log : null});
37
+ this.roleRepository = new RoleSqliteRepository(dataBaseFile, verbose)
38
+ this.tenantRepository = new TenantSqliteRepository(dataBaseFile, verbose)
38
39
  this.table()
39
40
  }
40
41
 
41
42
  table() {
42
- this.db.exec(userTableSQL);
43
+ const builder = new SqliteTableBuilder(
44
+ this.dataBaseFile,
45
+ 'users',
46
+ tableFields,
47
+ false);
48
+ builder.build('id')
43
49
  }
44
50
 
45
51
  normalizeData(userData: IUserCreate | IUserUpdate): void {
@@ -58,6 +64,8 @@ class UserSqliteRepository implements IUserRepository {
58
64
  throw new ValidationError([{field: 'role', reason: 'validation.notfound', value: userData.role}])
59
65
  }
60
66
 
67
+ userData.createdAt = (new Date().toISOString())
68
+
61
69
  this.normalizeData(userData)
62
70
 
63
71
  try {
@@ -86,6 +94,8 @@ class UserSqliteRepository implements IUserRepository {
86
94
  throw new ValidationError([{field: 'role', reason: 'validation.notfound', value: userData.role}])
87
95
  }
88
96
 
97
+ userData.updatedAt = (new Date().toISOString())
98
+
89
99
  this.normalizeData(userData)
90
100
 
91
101
  const setClauses = Object.keys(userData)
@@ -153,20 +163,20 @@ class UserSqliteRepository implements IUserRepository {
153
163
  if(filters && filters.length > 0 ){
154
164
  where = where ? ` AND ` : ` WHERE `
155
165
  for(const filter of filters){
156
- if(filter.operator === '$eq'){
166
+ if(['eq','$eq'].includes(filter.operator)){
157
167
  whereFilters.push(` ${filter.field} = '${filter.value}' `)
158
168
  }
159
- if(filter.operator === '$ne'){
169
+ if(['ne','$ne'].includes(filter.operator)){
160
170
  whereFilters.push(` ${filter.field} != '${filter.value}' `)
161
171
  }
162
- if(filter.operator === '$in'){
172
+ if(['in','$in'].includes(filter.operator)){
163
173
  whereFilters.push(` ${filter.field} LIKE '%${filter.value}%' `)
164
174
  }
165
175
  }
166
176
  where += whereFilters.join(" AND ")
167
177
  }
168
178
 
169
- // console.log("paginate where ", where, "search", search, "filters", filters, "whereFilters", whereFilters)
179
+ console.log("paginate where ", where, "search", search, "filters", filters, "whereFilters", whereFilters)
170
180
 
171
181
  const rCount = this.db.prepare('SELECT COUNT(*) as count FROM users' + where).get();
172
182
 
@@ -0,0 +1,128 @@
1
+ import UserApiKeyServiceFactory from "../factory/UserApiKeyServiceFactory.js";
2
+ import type {IUserApiKey} from "@drax/identity-share";
3
+ import {ValidationError} from "@drax/common-back";
4
+ import {IdentityPermissions} from "../permissions/IdentityPermissions.js";
5
+ import UnauthorizedError from "../errors/UnauthorizedError.js";
6
+ import {IDraxPaginateResult} from "@drax/common-share";
7
+
8
+
9
+ async function UserApiKeyRoutes(fastify, options) {
10
+
11
+ fastify.get('/api/user-api-keys', async (request, reply): Promise<IDraxPaginateResult<IUserApiKey>> => {
12
+
13
+ try {
14
+ request.rbac.assertAuthenticated()
15
+
16
+ request.rbac.assertOrPermissions([
17
+ IdentityPermissions.ViewUserApiKey,
18
+ IdentityPermissions.ViewMyUserApiKey
19
+ ])
20
+
21
+ const filters = []
22
+
23
+ if(!request.rbac.hasPermission(IdentityPermissions.ViewUserApiKey)){
24
+ filters.push({field: "user", operator: "eq", value: request.rbac.authUser.id})
25
+ }
26
+
27
+ const page = request.query.page
28
+ const limit = request.query.limit
29
+ const orderBy = request.query.orderBy
30
+ const orderDesc = request.query.orderDesc
31
+ const search = request.query.search
32
+ const userApiKeyService = UserApiKeyServiceFactory()
33
+
34
+
35
+ let paginateResult = await userApiKeyService.paginate({page, limit, orderBy, orderDesc, search, filters})
36
+ return paginateResult
37
+ } catch (e) {
38
+ console.log("/api/user-api-keys",e)
39
+ if (e instanceof ValidationError) {
40
+ reply.statusCode = e.statusCode
41
+ reply.send({error: e.message, inputErrors: e.errors})
42
+ } else if (e instanceof UnauthorizedError) {
43
+ reply.statusCode = e.statusCode
44
+ reply.send({error: e.message})
45
+ } else {
46
+ reply.statusCode = 500
47
+ reply.send({error: 'error.server'})
48
+ }
49
+ }
50
+ })
51
+
52
+ fastify.post('/api/user-api-keys', async (request, reply): Promise<IUserApiKey> => {
53
+ try {
54
+ request.rbac.assertPermission(IdentityPermissions.CreateUser)
55
+ const payload = request.body
56
+ payload.user = request.rbac.authUser.id
57
+
58
+ const userApiKeyService = UserApiKeyServiceFactory()
59
+
60
+ let userApiKey = await userApiKeyService.create(payload)
61
+ return userApiKey
62
+ } catch (e) {
63
+ if (e instanceof ValidationError) {
64
+ reply.statusCode = e.statusCode
65
+ reply.send({error: e.message, inputErrors: e.errors})
66
+ } else if (e instanceof UnauthorizedError) {
67
+ reply.statusCode = e.statusCode
68
+ reply.send({error: e.message})
69
+ } else {
70
+ reply.statusCode = 500
71
+ reply.send({error: 'error.server'})
72
+ }
73
+ }
74
+
75
+ })
76
+
77
+ fastify.put('/api/user-api-keys/:id', async (request, reply): Promise<IUserApiKey> => {
78
+ try {
79
+ request.rbac.assertPermission(IdentityPermissions.UpdateUser)
80
+ const id = request.params.id
81
+ const payload = request.body
82
+ const userApiKeyService = UserApiKeyServiceFactory()
83
+ let userApiKey = await userApiKeyService.update(id, payload)
84
+ return userApiKey
85
+ } catch (e) {
86
+ if (e instanceof ValidationError) {
87
+ reply.statusCode = e.statusCode
88
+ reply.send({error: e.message, inputErrors: e.errors})
89
+ }
90
+ if (e instanceof UnauthorizedError) {
91
+ reply.statusCode = e.statusCode
92
+ reply.send({error: e.message})
93
+ } else if (e instanceof UnauthorizedError) {
94
+ reply.statusCode = e.statusCode
95
+ reply.send({error: e.message})
96
+ } else {
97
+ reply.statusCode = 500
98
+ reply.send({error: 'error.server'})
99
+ }
100
+ }
101
+ })
102
+
103
+ fastify.delete('/api/user-api-keys/:id', async (request, reply): Promise<any> => {
104
+ try {
105
+ request.rbac.assertPermission(IdentityPermissions.DeleteUser)
106
+ const id = request.params.id
107
+ const userApiKeyService = UserApiKeyServiceFactory()
108
+ let r = await userApiKeyService.delete(id)
109
+ return r
110
+ } catch (e) {
111
+ if (e instanceof ValidationError) {
112
+ reply.statusCode = e.statusCode
113
+ reply.send({error: e.message, inputErrors: e.errors})
114
+ } else if (e instanceof UnauthorizedError) {
115
+ reply.statusCode = e.statusCode
116
+ reply.send({error: e.message})
117
+ } else {
118
+ reply.statusCode = 500
119
+ reply.send({error: 'error.server'})
120
+ }
121
+ }
122
+ })
123
+
124
+
125
+ }
126
+
127
+ export default UserApiKeyRoutes;
128
+ export {UserApiKeyRoutes}
@@ -32,6 +32,7 @@ async function UserRoutes(fastify, options) {
32
32
  if (request.authUser) {
33
33
  const userService = UserServiceFactory()
34
34
  let user = await userService.findById(request.authUser.id)
35
+ user.password = undefined
35
36
  delete user.password
36
37
  return user
37
38
  } else {
@@ -69,9 +70,12 @@ async function UserRoutes(fastify, options) {
69
70
  filters.push({field: 'tenant', operator: '$eq', value: request.rbac.getAuthUser.tenantId})
70
71
  }
71
72
  let paginateResult = await userService.paginate({page, limit, orderBy, orderDesc, search, filters})
73
+ for(let item of paginateResult.items){
74
+ item.password = undefined
75
+ delete item.password
76
+ }
72
77
  return paginateResult
73
78
  } catch (e) {
74
- console.log("/api/users",e)
75
79
  if (e instanceof ValidationError) {
76
80
  reply.statusCode = e.statusCode
77
81
  reply.send({error: e.message, inputErrors: e.errors})
@@ -0,0 +1,86 @@
1
+ import {IUserApiKeyRepository} from "../interfaces/IUserApiKeyRepository";
2
+ import {DraxConfig, ValidationError, ZodErrorToValidationError} from "@drax/common-back"
3
+ import {userApiKeySchema} from "../zod/UserApiKeyZod.js";
4
+ import {ZodError} from "zod";
5
+ import {IUserApiKeyBase, IUserApiKey} from "@drax/identity-share";
6
+ import {IDraxPaginateOptions, IDraxPaginateResult} from "@drax/common-share";
7
+ import crypto from "node:crypto";
8
+ import AuthUtils from "../utils/AuthUtils.js";
9
+ import IdentityConfig from "../config/IdentityConfig.js";
10
+
11
+ class UserApiKeyService {
12
+
13
+ _repository: IUserApiKeyRepository
14
+
15
+ constructor(userApiKeyRepostitory: IUserApiKeyRepository) {
16
+ this._repository = userApiKeyRepostitory
17
+ console.log("UserApiKeyService constructor")
18
+ }
19
+
20
+ async create(userApiKeyData: IUserApiKeyBase): Promise<IUserApiKey> {
21
+ try {
22
+ userApiKeyData.name = userApiKeyData?.name?.trim()
23
+ const secret = crypto.randomUUID()
24
+ const APIKEY_SECRET = DraxConfig.getOrLoad(IdentityConfig.ApiKeySecret)
25
+ userApiKeyData.secret = AuthUtils.generateHMAC(APIKEY_SECRET, secret)
26
+ await userApiKeySchema.parseAsync(userApiKeyData)
27
+ const userApiKey = await this._repository.create(userApiKeyData)
28
+ userApiKey.secret = secret
29
+ return userApiKey
30
+ } catch (e) {
31
+ if (e instanceof ZodError) {
32
+ throw ZodErrorToValidationError(e, userApiKeyData)
33
+ }
34
+ throw e
35
+ }
36
+ }
37
+
38
+ async update(id: string, userApiKeyData: IUserApiKeyBase) {
39
+ try {
40
+ userApiKeyData.name = userApiKeyData?.name?.trim()
41
+ delete userApiKeyData.secret
42
+ await userApiKeySchema.parseAsync(userApiKeyData)
43
+ const userApiKey = await this._repository.update(id, userApiKeyData)
44
+ return userApiKey
45
+ } catch (e) {
46
+ if (e instanceof ZodError) {
47
+ throw ZodErrorToValidationError(e, userApiKeyData)
48
+ }
49
+ throw e
50
+ }
51
+ }
52
+
53
+ async delete(id: string): Promise<boolean> {
54
+ const currentUserApiKey = await this.findById(id)
55
+ const deletedUserApiKey = await this._repository.delete(id);
56
+ return deletedUserApiKey;
57
+ }
58
+
59
+ async findById(id: string): Promise<IUserApiKey | null> {
60
+ const userApiKey: IUserApiKey = await this._repository.findById(id);
61
+ return userApiKey
62
+ }
63
+
64
+ async findBySecret(secret: string): Promise<IUserApiKey | null> {
65
+ const APIKEY_SECRET = DraxConfig.getOrLoad(IdentityConfig.ApiKeySecret)
66
+ const hashedSecret = AuthUtils.generateHMAC(APIKEY_SECRET, secret)
67
+ const userApiKey: IUserApiKey = await this._repository.findBySecret(hashedSecret);
68
+ return userApiKey
69
+ }
70
+
71
+
72
+ async paginate({
73
+ page= 1,
74
+ limit= 5,
75
+ orderBy= '',
76
+ orderDesc= false,
77
+ search= '',
78
+ filters= []} : IDraxPaginateOptions): Promise<IDraxPaginateResult<IUserApiKey>>{
79
+ const pagination = await this._repository.paginate({page, limit, orderBy, orderDesc, search, filters});
80
+ return pagination;
81
+ }
82
+
83
+
84
+ }
85
+
86
+ export default UserApiKeyService
@@ -2,12 +2,14 @@ import {DraxConfig} from "@drax/common-back";
2
2
  import IdentityConfig from "../config/IdentityConfig.js";
3
3
 
4
4
  function LoadIdentityConfigFromEnv() {
5
- DraxConfig.set(IdentityConfig.DbEngine, process.env[IdentityConfig.DbEngine])
6
- DraxConfig.set(IdentityConfig.SqliteDbFile, process.env[IdentityConfig.SqliteDbFile])
7
- DraxConfig.set(IdentityConfig.MongoDbUri, process.env[IdentityConfig.MongoDbUri])
5
+
8
6
  DraxConfig.set(IdentityConfig.JwtSecret, process.env[IdentityConfig.JwtSecret])
9
7
  DraxConfig.set(IdentityConfig.JwtExpiration, process.env[IdentityConfig.JwtExpiration])
10
8
  DraxConfig.set(IdentityConfig.JwtIssuer, process.env[IdentityConfig.JwtIssuer])
9
+ DraxConfig.set(IdentityConfig.ApiKeySecret, process.env[IdentityConfig.ApiKeySecret])
10
+
11
+ DraxConfig.set(IdentityConfig.RbacCacheTTL, process.env[IdentityConfig.RbacCacheTTL])
12
+ DraxConfig.set(IdentityConfig.AvatarDir, process.env[IdentityConfig.AvatarDir])
11
13
  }
12
14
 
13
15
  export default LoadIdentityConfigFromEnv
@@ -2,6 +2,7 @@ import bcryptjs from "bcryptjs";
2
2
  import jsonwebtoken, {SignOptions, VerifyOptions} from "jsonwebtoken";
3
3
  import {DraxConfig} from "@drax/common-back";
4
4
  import IdentityConfig from "../config/IdentityConfig.js";
5
+ import crypto from "crypto";
5
6
 
6
7
  class AuthUtils{
7
8
 
@@ -67,6 +68,16 @@ class AuthUtils{
67
68
 
68
69
  return token
69
70
  }
71
+
72
+ static generateHMAC(secret: string, apikey: string) {
73
+ // Crear un objeto HMAC utilizando el algoritmo SHA-256 y el secreto
74
+ const hmac = crypto.createHmac('sha256', secret);
75
+ // Actualizar el HMAC con la apikey
76
+ hmac.update(apikey);
77
+ // Generar el hash en formato hexadecimal
78
+ return hmac.digest('hex');
79
+ }
70
80
  }
71
81
 
72
82
  export default AuthUtils
83
+ export {AuthUtils}
@@ -0,0 +1,15 @@
1
+ import {array, object, string} from "zod"
2
+
3
+ const userApiKeySchema = object({
4
+ name: string({ required_error: "validation.required" })
5
+ .min(1, "validation.required"),
6
+ ipv4: array(string().ip({version: "v4", message: 'validation.invalidIpv4'})),
7
+ ipv6: array(string().ip({version: "v6", message: 'validation.invalidIpv6'})),
8
+
9
+
10
+ })
11
+
12
+
13
+ export default userApiKeySchema
14
+
15
+ export {userApiKeySchema}