@drax/identity-back 0.0.30 → 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 (107) 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/tenant.graphql +2 -0
  9. package/dist/graphql/types/user.graphql +2 -0
  10. package/dist/graphql/types/userApiKey.graphql +33 -0
  11. package/dist/index.js +4 -2
  12. package/dist/interfaces/IUserApiKeyRepository.js +1 -0
  13. package/dist/middleware/apiKeyMiddleware.js +30 -0
  14. package/dist/middleware/rbacMiddleware.js +0 -1
  15. package/dist/models/TenantModel.js +2 -4
  16. package/dist/models/UserApiKeyModel.js +44 -0
  17. package/dist/permissions/IdentityPermissions.js +6 -0
  18. package/dist/rbac/Rbac.js +8 -0
  19. package/dist/repository/mongo/UserApiKeyMongoRepository.js +82 -0
  20. package/dist/repository/mongo/UserMongoRepository.js +3 -3
  21. package/dist/repository/sqlite/RoleSqliteRepository.js +16 -18
  22. package/dist/repository/sqlite/TenantSqliteRepository.js +13 -11
  23. package/dist/repository/sqlite/UserApiKeySqliteRepository.js +147 -0
  24. package/dist/repository/sqlite/UserSqliteRepository.js +29 -26
  25. package/dist/routes/UserApiKeyRoutes.js +119 -0
  26. package/dist/routes/UserRoutes.js +5 -1
  27. package/dist/services/UserApiKeyService.js +65 -0
  28. package/dist/setup/LoadIdentityConfigFromEnv.js +3 -3
  29. package/dist/utils/AuthUtils.js +10 -0
  30. package/dist/zod/UserApiKeyZod.js +9 -0
  31. package/package.json +7 -6
  32. package/src/config/IdentityConfig.ts +4 -3
  33. package/src/factory/RoleServiceFactory.ts +11 -11
  34. package/src/factory/TenantServiceFactory.ts +11 -11
  35. package/src/factory/UserApiKeyServiceFactory.ts +30 -0
  36. package/src/factory/UserServiceFactory.ts +8 -7
  37. package/src/graphql/resolvers/tenant.resolvers.ts +0 -1
  38. package/src/graphql/resolvers/user-api-key.resolvers.ts +94 -0
  39. package/src/graphql/resolvers/user.resolvers.ts +9 -2
  40. package/src/graphql/types/tenant.graphql +2 -0
  41. package/src/graphql/types/user.graphql +2 -0
  42. package/src/graphql/types/userApiKey.graphql +33 -0
  43. package/src/index.ts +10 -0
  44. package/src/interfaces/IUserApiKeyRepository.ts +8 -0
  45. package/src/middleware/apiKeyMiddleware.ts +35 -0
  46. package/src/middleware/rbacMiddleware.ts +1 -2
  47. package/src/models/TenantModel.ts +2 -4
  48. package/src/models/UserApiKeyModel.ts +59 -0
  49. package/src/permissions/IdentityPermissions.ts +7 -0
  50. package/src/rbac/Rbac.ts +13 -2
  51. package/src/repository/mongo/UserApiKeyMongoRepository.ts +114 -0
  52. package/src/repository/mongo/UserMongoRepository.ts +3 -3
  53. package/src/repository/sqlite/RoleSqliteRepository.ts +28 -20
  54. package/src/repository/sqlite/TenantSqliteRepository.ts +25 -11
  55. package/src/repository/sqlite/UserApiKeySqliteRepository.ts +197 -0
  56. package/src/repository/sqlite/UserSqliteRepository.ts +37 -27
  57. package/src/routes/UserApiKeyRoutes.ts +128 -0
  58. package/src/routes/UserRoutes.ts +5 -1
  59. package/src/services/UserApiKeyService.ts +86 -0
  60. package/src/setup/LoadIdentityConfigFromEnv.ts +5 -3
  61. package/src/utils/AuthUtils.ts +11 -0
  62. package/src/zod/UserApiKeyZod.ts +15 -0
  63. package/test/data-obj/apikey/root-mongo-user-apikey.ts +10 -0
  64. package/test/data-obj/roles/admin-mongo-role.ts +0 -3
  65. package/test/initializers/RoleMongoInitializer.ts +1 -0
  66. package/test/initializers/RoleSqliteInitializer.ts +1 -0
  67. package/test/initializers/UserMongoInitializer.ts +18 -0
  68. package/test/repository/mongo/user-apikey-mongo-repository.test.ts +73 -0
  69. package/tsconfig.tsbuildinfo +1 -1
  70. package/types/factory/RoleServiceFactory.d.ts.map +1 -1
  71. package/types/factory/TenantServiceFactory.d.ts.map +1 -1
  72. package/types/factory/UserApiKeyServiceFactory.d.ts.map +1 -0
  73. package/types/factory/UserServiceFactory.d.ts.map +1 -1
  74. package/types/graphql/resolvers/tenant.resolvers.d.ts.map +1 -1
  75. package/types/graphql/resolvers/user-api-key.resolvers.d.ts.map +1 -0
  76. package/types/graphql/resolvers/user.resolvers.d.ts.map +1 -1
  77. package/types/index.d.ts.map +1 -1
  78. package/types/interfaces/IRoleRepository.d.ts.map +1 -0
  79. package/types/interfaces/ITenantRepository.d.ts.map +1 -0
  80. package/types/interfaces/IUserApiKeyRepository.d.ts.map +1 -0
  81. package/types/interfaces/IUserRepository.d.ts.map +1 -0
  82. package/types/middleware/apiKeyMiddleware.d.ts.map +1 -0
  83. package/types/middleware/rbacMiddleware.d.ts.map +1 -1
  84. package/types/models/TenantModel.d.ts.map +1 -1
  85. package/types/models/UserApiKeyModel.d.ts.map +1 -0
  86. package/types/rbac/Rbac.d.ts +15 -0
  87. package/types/rbac/Rbac.d.ts.map +1 -0
  88. package/types/repository/mongo/UserApiKeyMongoRepository.d.ts.map +1 -0
  89. package/types/repository/sqlite/RoleSqliteRepository.d.ts.map +1 -1
  90. package/types/repository/sqlite/TenantSqliteRepository.d.ts.map +1 -1
  91. package/types/repository/sqlite/UserApiKeySqliteRepository.d.ts +19 -0
  92. package/types/repository/sqlite/UserApiKeySqliteRepository.d.ts.map +1 -0
  93. package/types/repository/sqlite/UserSqliteRepository.d.ts.map +1 -1
  94. package/types/routes/UserApiKeyRoutes.d.ts.map +1 -0
  95. package/types/routes/UserRoutes.d.ts.map +1 -1
  96. package/types/services/UserApiKeyService.d.ts.map +1 -0
  97. package/types/setup/LoadIdentityConfigFromEnv.d.ts.map +1 -1
  98. package/types/utils/AuthUtils.d.ts.map +1 -1
  99. package/types/zod/UserApiKeyZod.d.ts +16 -0
  100. package/types/zod/UserApiKeyZod.d.ts.map +1 -0
  101. package/src/utils/DbSetupUtils.ts +0 -41
  102. package/types/config/IdentityConfig.d.ts +0 -13
  103. package/types/config/IdentityConfig.d.ts.map +0 -1
  104. package/types/graphql/resolvers/role.resolvers.d.ts +0 -52
  105. package/types/graphql/resolvers/tenant.resolvers.d.ts +0 -49
  106. package/types/graphql/resolvers/user.resolvers.d.ts +0 -67
  107. package/types/utils/DbSetupUtils.d.ts.map +0 -1
@@ -1,26 +1,27 @@
1
1
  import sqlite from "better-sqlite3";
2
2
  import { randomUUID } from "node:crypto";
3
- import { SqliteErrorToValidationError } from "@drax/common-back";
4
- const tenantTableSQL = `
5
- CREATE TABLE IF NOT EXISTS tenants
6
- (
7
- id TEXT PRIMARY KEY,
8
- name TEXT
9
- );
10
- `;
3
+ import { SqliteErrorToValidationError, SqliteTableBuilder } from "@drax/common-back";
4
+ const tableFields = [
5
+ { name: "name", type: "TEXT", unique: false, primary: false },
6
+ { name: "createdAt", type: "TEXT", unique: false, primary: false },
7
+ { name: "updatedAt", type: "TEXT", unique: false, primary: false },
8
+ ];
11
9
  class TenantSqliteRepository {
12
- constructor(DATABASE, verbose = false) {
13
- this.db = new sqlite(DATABASE, { verbose: verbose ? console.log : null });
10
+ constructor(dataBaseFile, verbose = false) {
11
+ this.dataBaseFile = dataBaseFile;
12
+ this.db = new sqlite(this.dataBaseFile, { verbose: verbose ? console.log : null });
14
13
  this.table();
15
14
  }
16
15
  table() {
17
- this.db.exec(tenantTableSQL);
16
+ const builder = new SqliteTableBuilder(this.dataBaseFile, 'tenants', tableFields, false);
17
+ builder.build('id');
18
18
  }
19
19
  async create(tenantData) {
20
20
  try {
21
21
  if (!tenantData.id) {
22
22
  tenantData.id = randomUUID();
23
23
  }
24
+ tenantData.createdAt = (new Date().toISOString());
24
25
  const fields = Object.keys(tenantData)
25
26
  .map(field => `${field}`)
26
27
  .join(', ');
@@ -46,6 +47,7 @@ class TenantSqliteRepository {
46
47
  }
47
48
  async update(id, tenantData) {
48
49
  try {
50
+ tenantData.updatedAt = (new Date().toISOString());
49
51
  const setClauses = Object.keys(tenantData)
50
52
  .map(field => `${field} = @${field}`)
51
53
  .join(', ');
@@ -0,0 +1,147 @@
1
+ import sqlite from "better-sqlite3";
2
+ import { randomUUID } from "node:crypto";
3
+ import { SqliteErrorToValidationError, SqliteTableBuilder } from "@drax/common-back";
4
+ import UserSqliteRepository from "./UserSqliteRepository.js";
5
+ const tableFields = [
6
+ { name: "secret", type: "TEXT", unique: true, primary: false },
7
+ { name: "name", type: "TEXT", unique: false, primary: false },
8
+ { name: "user", type: "TEXT", unique: false, primary: false },
9
+ { name: "ipv4", type: "TEXT", unique: false, primary: false },
10
+ { name: "ipv6", type: "TEXT", unique: false, primary: false },
11
+ { name: "createdAt", type: "TEXT", unique: false, primary: false }
12
+ ];
13
+ class UserApiKeySqliteRepository {
14
+ constructor(dataBaseFile, verbose = false) {
15
+ this.dataBaseFile = dataBaseFile;
16
+ this.userRepository = new UserSqliteRepository(dataBaseFile, verbose);
17
+ this.db = new sqlite(dataBaseFile, { verbose: verbose ? console.log : null });
18
+ this.table();
19
+ }
20
+ async findUserById(id) {
21
+ return await this.userRepository.findById(id);
22
+ }
23
+ table() {
24
+ const builder = new SqliteTableBuilder(this.dataBaseFile, 'user_api_keys', tableFields, false);
25
+ builder.build('id');
26
+ }
27
+ async create(userApiKeyData) {
28
+ try {
29
+ if (!userApiKeyData.id) {
30
+ userApiKeyData.id = randomUUID();
31
+ }
32
+ if (userApiKeyData.ipv4 && Array.isArray(userApiKeyData.ipv4) && userApiKeyData.ipv4.length > 0) {
33
+ userApiKeyData.ipv4 = userApiKeyData.ipv4.join(',');
34
+ }
35
+ else {
36
+ userApiKeyData.ipv4 = "";
37
+ }
38
+ if (userApiKeyData.ipv6 && Array.isArray(userApiKeyData.ipv6) && userApiKeyData.ipv6.length > 0) {
39
+ userApiKeyData.ipv6 = userApiKeyData.ipv6.join(',');
40
+ }
41
+ else {
42
+ userApiKeyData.ipv6 = "";
43
+ }
44
+ userApiKeyData.createdAt = (new Date().toISOString());
45
+ const fields = Object.keys(userApiKeyData)
46
+ .map(field => `${field}`)
47
+ .join(', ');
48
+ const values = Object.keys(userApiKeyData)
49
+ .map(field => `@${field}`)
50
+ .join(', ');
51
+ const stmt = this.db.prepare(`INSERT INTO user_api_keys (${fields})
52
+ VALUES (${values})`);
53
+ stmt.run(userApiKeyData);
54
+ return this.findById(userApiKeyData.id);
55
+ }
56
+ catch (e) {
57
+ console.log(e);
58
+ throw SqliteErrorToValidationError(e, userApiKeyData);
59
+ }
60
+ }
61
+ async findById(id) {
62
+ const userApiKey = this.db.prepare('SELECT * FROM user_api_keys WHERE id = ?').get(id);
63
+ userApiKey.ipv4 = userApiKey.ipv4 != "" ? userApiKey.ipv4.split(',') : [];
64
+ userApiKey.ipv6 = userApiKey.ipv6 != "" ? userApiKey.ipv6.split(',') : [];
65
+ userApiKey.user = await this.findUserById(userApiKey.user);
66
+ return userApiKey;
67
+ }
68
+ async findBySecret(secret) {
69
+ const userApiKey = this.db.prepare('SELECT * FROM user_api_keys WHERE secret = ?').get(secret);
70
+ userApiKey.ipv4 = userApiKey.ipv4 != "" ? userApiKey.ipv4.split(',') : [];
71
+ userApiKey.ipv6 = userApiKey.ipv6 != "" ? userApiKey.ipv6.split(',') : [];
72
+ userApiKey.user = await this.findUserById(userApiKey.user);
73
+ return userApiKey;
74
+ }
75
+ async update(id, userApiKeyData) {
76
+ try {
77
+ if (userApiKeyData.ipv4 && Array.isArray(userApiKeyData.ipv4) && userApiKeyData.ipv4.length > 0) {
78
+ userApiKeyData.ipv4 = userApiKeyData.ipv4.join(',');
79
+ }
80
+ else {
81
+ userApiKeyData.ipv4 = "";
82
+ }
83
+ if (userApiKeyData.ipv6 && Array.isArray(userApiKeyData.ipv6) && userApiKeyData.ipv6.length > 0) {
84
+ userApiKeyData.ipv6 = userApiKeyData.ipv6.join(',');
85
+ }
86
+ else {
87
+ userApiKeyData.ipv6 = "";
88
+ }
89
+ delete userApiKeyData.secret;
90
+ const setClauses = Object.keys(userApiKeyData)
91
+ .map(field => `${field} = @${field}`)
92
+ .join(', ');
93
+ userApiKeyData.id = id;
94
+ const stmt = this.db.prepare(`UPDATE user_api_keys
95
+ SET ${setClauses}
96
+ WHERE id = @id `);
97
+ stmt.run(userApiKeyData);
98
+ return this.findById(id);
99
+ }
100
+ catch (e) {
101
+ console.log(e);
102
+ throw SqliteErrorToValidationError(e, userApiKeyData);
103
+ }
104
+ }
105
+ async delete(id) {
106
+ const stmt = this.db.prepare('DELETE FROM user_api_keys WHERE id = ?');
107
+ stmt.run(id);
108
+ return true;
109
+ }
110
+ async paginate({ page = 1, limit = 5, orderBy = '', orderDesc = false, search = '', filters = [] }) {
111
+ const offset = page > 1 ? (page - 1) * limit : 0;
112
+ let where = "";
113
+ if (search) {
114
+ where = ` WHERE name LIKE '%${search}%'`;
115
+ }
116
+ let whereFilters = [];
117
+ if (filters && filters.length > 0) {
118
+ where = where ? ` AND ` : ` WHERE `;
119
+ for (const filter of filters) {
120
+ if (['eq', '$eq'].includes(filter.operator)) {
121
+ whereFilters.push(` ${filter.field} = '${filter.value}' `);
122
+ }
123
+ if (['ne', '$ne'].includes(filter.operator)) {
124
+ whereFilters.push(` ${filter.field} != '${filter.value}' `);
125
+ }
126
+ if (['in', '$in'].includes(filter.operator)) {
127
+ whereFilters.push(` ${filter.field} LIKE '%${filter.value}%' `);
128
+ }
129
+ }
130
+ where += whereFilters.join(" AND ");
131
+ }
132
+ const rCount = this.db.prepare('SELECT COUNT(*) as count FROM user_api_keys' + where).get();
133
+ const userApiKeys = this.db.prepare('SELECT * FROM user_api_keys ' + where + ' LIMIT ? OFFSET ?').all([limit, offset]);
134
+ for (const userApiKey of userApiKeys) {
135
+ userApiKey.ipv4 = userApiKey.ipv4 != "" ? userApiKey.ipv4.split(',') : [];
136
+ userApiKey.ipv6 = userApiKey.ipv6 != "" ? userApiKey.ipv6.split(',') : [];
137
+ userApiKey.user = await this.findUserById(userApiKey.user);
138
+ }
139
+ return {
140
+ page: page,
141
+ limit: limit,
142
+ total: rCount.count,
143
+ items: userApiKeys
144
+ };
145
+ }
146
+ }
147
+ export default UserApiKeySqliteRepository;
@@ -1,33 +1,34 @@
1
1
  import sqlite from "better-sqlite3";
2
2
  import { randomUUID } from "node:crypto";
3
- import { SqliteErrorToValidationError, ValidationError } from "@drax/common-back";
3
+ import { SqliteErrorToValidationError, SqliteTableBuilder, ValidationError } from "@drax/common-back";
4
4
  import RoleSqliteRepository from "./RoleSqliteRepository.js";
5
5
  import TenantSqliteRepository from "./TenantSqliteRepository.js";
6
- const userTableSQL = `
7
- CREATE TABLE IF NOT EXISTS users
8
- (
9
- id TEXT PRIMARY KEY,
10
- name TEXT,
11
- username TEXT UNIQUE,
12
- active INTEGER,
13
- password TEXT,
14
- email TEXT UNIQUE,
15
- phone TEXT,
16
- role TEXT,
17
- tenant TEXT,
18
- groups TEXT,
19
- avatar TEXT
20
- );
21
- `;
6
+ const tableFields = [
7
+ { name: "name", type: "TEXT", unique: false, primary: false },
8
+ { name: "username", type: "TEXT", unique: true, primary: false },
9
+ { name: "active", type: "INTEGER", unique: false, primary: false },
10
+ { name: "active", type: "INTEGER", unique: false, primary: false },
11
+ { name: "password", type: "TEXT", unique: false, primary: false },
12
+ { name: "email", type: "TEXT", unique: true, primary: false },
13
+ { name: "phone", type: "TEXT", unique: false, primary: false },
14
+ { name: "role", type: "TEXT", unique: false, primary: false },
15
+ { name: "tenant", type: "TEXT", unique: false, primary: false },
16
+ { name: "groups", type: "TEXT", unique: false, primary: false },
17
+ { name: "avatar", type: "TEXT", unique: false, primary: false },
18
+ { name: "createdAt", type: "TEXT", unique: false, primary: false },
19
+ { name: "updatedAt", type: "TEXT", unique: false, primary: false }
20
+ ];
22
21
  class UserSqliteRepository {
23
- constructor(DATABASE, verbose = false) {
24
- this.db = new sqlite(DATABASE, { verbose: verbose ? console.log : null });
25
- this.roleRepository = new RoleSqliteRepository(DATABASE, verbose);
26
- this.tenantRepository = new TenantSqliteRepository(DATABASE, verbose);
22
+ constructor(dataBaseFile, verbose = false) {
23
+ this.dataBaseFile = dataBaseFile;
24
+ this.db = new sqlite(dataBaseFile, { verbose: verbose ? console.log : null });
25
+ this.roleRepository = new RoleSqliteRepository(dataBaseFile, verbose);
26
+ this.tenantRepository = new TenantSqliteRepository(dataBaseFile, verbose);
27
27
  this.table();
28
28
  }
29
29
  table() {
30
- this.db.exec(userTableSQL);
30
+ const builder = new SqliteTableBuilder(this.dataBaseFile, 'users', tableFields, false);
31
+ builder.build('id');
31
32
  }
32
33
  normalizeData(userData) {
33
34
  if (userData.groups && Array.isArray(userData.groups)) {
@@ -42,6 +43,7 @@ class UserSqliteRepository {
42
43
  if (!await this.findRoleById(userData.role)) {
43
44
  throw new ValidationError([{ field: 'role', reason: 'validation.notfound', value: userData.role }]);
44
45
  }
46
+ userData.createdAt = (new Date().toISOString());
45
47
  this.normalizeData(userData);
46
48
  try {
47
49
  const fields = Object.keys(userData)
@@ -64,6 +66,7 @@ class UserSqliteRepository {
64
66
  if (!await this.findRoleById(userData.role)) {
65
67
  throw new ValidationError([{ field: 'role', reason: 'validation.notfound', value: userData.role }]);
66
68
  }
69
+ userData.updatedAt = (new Date().toISOString());
67
70
  this.normalizeData(userData);
68
71
  const setClauses = Object.keys(userData)
69
72
  .map(field => `${field} = @${field}`)
@@ -117,19 +120,19 @@ class UserSqliteRepository {
117
120
  if (filters && filters.length > 0) {
118
121
  where = where ? ` AND ` : ` WHERE `;
119
122
  for (const filter of filters) {
120
- if (filter.operator === '$eq') {
123
+ if (['eq', '$eq'].includes(filter.operator)) {
121
124
  whereFilters.push(` ${filter.field} = '${filter.value}' `);
122
125
  }
123
- if (filter.operator === '$ne') {
126
+ if (['ne', '$ne'].includes(filter.operator)) {
124
127
  whereFilters.push(` ${filter.field} != '${filter.value}' `);
125
128
  }
126
- if (filter.operator === '$in') {
129
+ if (['in', '$in'].includes(filter.operator)) {
127
130
  whereFilters.push(` ${filter.field} LIKE '%${filter.value}%' `);
128
131
  }
129
132
  }
130
133
  where += whereFilters.join(" AND ");
131
134
  }
132
- // console.log("paginate where ", where, "search", search, "filters", filters, "whereFilters", whereFilters)
135
+ console.log("paginate where ", where, "search", search, "filters", filters, "whereFilters", whereFilters);
133
136
  const rCount = this.db.prepare('SELECT COUNT(*) as count FROM users' + where).get();
134
137
  const users = this.db.prepare('SELECT * FROM users' + where + ' LIMIT ? OFFSET ?').all([limit, offset]);
135
138
  for (const user of users) {
@@ -0,0 +1,119 @@
1
+ import UserApiKeyServiceFactory from "../factory/UserApiKeyServiceFactory.js";
2
+ import { ValidationError } from "@drax/common-back";
3
+ import { IdentityPermissions } from "../permissions/IdentityPermissions.js";
4
+ import UnauthorizedError from "../errors/UnauthorizedError.js";
5
+ async function UserApiKeyRoutes(fastify, options) {
6
+ fastify.get('/api/user-api-keys', async (request, reply) => {
7
+ try {
8
+ request.rbac.assertAuthenticated();
9
+ request.rbac.assertOrPermissions([
10
+ IdentityPermissions.ViewUserApiKey,
11
+ IdentityPermissions.ViewMyUserApiKey
12
+ ]);
13
+ const filters = [];
14
+ if (!request.rbac.hasPermission(IdentityPermissions.ViewUserApiKey)) {
15
+ filters.push({ field: "user", operator: "eq", value: request.rbac.authUser.id });
16
+ }
17
+ const page = request.query.page;
18
+ const limit = request.query.limit;
19
+ const orderBy = request.query.orderBy;
20
+ const orderDesc = request.query.orderDesc;
21
+ const search = request.query.search;
22
+ const userApiKeyService = UserApiKeyServiceFactory();
23
+ let paginateResult = await userApiKeyService.paginate({ page, limit, orderBy, orderDesc, search, filters });
24
+ return paginateResult;
25
+ }
26
+ catch (e) {
27
+ console.log("/api/user-api-keys", e);
28
+ if (e instanceof ValidationError) {
29
+ reply.statusCode = e.statusCode;
30
+ reply.send({ error: e.message, inputErrors: e.errors });
31
+ }
32
+ else if (e instanceof UnauthorizedError) {
33
+ reply.statusCode = e.statusCode;
34
+ reply.send({ error: e.message });
35
+ }
36
+ else {
37
+ reply.statusCode = 500;
38
+ reply.send({ error: 'error.server' });
39
+ }
40
+ }
41
+ });
42
+ fastify.post('/api/user-api-keys', async (request, reply) => {
43
+ try {
44
+ request.rbac.assertPermission(IdentityPermissions.CreateUser);
45
+ const payload = request.body;
46
+ payload.user = request.rbac.authUser.id;
47
+ const userApiKeyService = UserApiKeyServiceFactory();
48
+ let userApiKey = await userApiKeyService.create(payload);
49
+ return userApiKey;
50
+ }
51
+ catch (e) {
52
+ if (e instanceof ValidationError) {
53
+ reply.statusCode = e.statusCode;
54
+ reply.send({ error: e.message, inputErrors: e.errors });
55
+ }
56
+ else if (e instanceof UnauthorizedError) {
57
+ reply.statusCode = e.statusCode;
58
+ reply.send({ error: e.message });
59
+ }
60
+ else {
61
+ reply.statusCode = 500;
62
+ reply.send({ error: 'error.server' });
63
+ }
64
+ }
65
+ });
66
+ fastify.put('/api/user-api-keys/:id', async (request, reply) => {
67
+ try {
68
+ request.rbac.assertPermission(IdentityPermissions.UpdateUser);
69
+ const id = request.params.id;
70
+ const payload = request.body;
71
+ const userApiKeyService = UserApiKeyServiceFactory();
72
+ let userApiKey = await userApiKeyService.update(id, payload);
73
+ return userApiKey;
74
+ }
75
+ catch (e) {
76
+ if (e instanceof ValidationError) {
77
+ reply.statusCode = e.statusCode;
78
+ reply.send({ error: e.message, inputErrors: e.errors });
79
+ }
80
+ if (e instanceof UnauthorizedError) {
81
+ reply.statusCode = e.statusCode;
82
+ reply.send({ error: e.message });
83
+ }
84
+ else if (e instanceof UnauthorizedError) {
85
+ reply.statusCode = e.statusCode;
86
+ reply.send({ error: e.message });
87
+ }
88
+ else {
89
+ reply.statusCode = 500;
90
+ reply.send({ error: 'error.server' });
91
+ }
92
+ }
93
+ });
94
+ fastify.delete('/api/user-api-keys/:id', async (request, reply) => {
95
+ try {
96
+ request.rbac.assertPermission(IdentityPermissions.DeleteUser);
97
+ const id = request.params.id;
98
+ const userApiKeyService = UserApiKeyServiceFactory();
99
+ let r = await userApiKeyService.delete(id);
100
+ return r;
101
+ }
102
+ catch (e) {
103
+ if (e instanceof ValidationError) {
104
+ reply.statusCode = e.statusCode;
105
+ reply.send({ error: e.message, inputErrors: e.errors });
106
+ }
107
+ else if (e instanceof UnauthorizedError) {
108
+ reply.statusCode = e.statusCode;
109
+ reply.send({ error: e.message });
110
+ }
111
+ else {
112
+ reply.statusCode = 500;
113
+ reply.send({ error: 'error.server' });
114
+ }
115
+ }
116
+ });
117
+ }
118
+ export default UserApiKeyRoutes;
119
+ export { UserApiKeyRoutes };
@@ -26,6 +26,7 @@ async function UserRoutes(fastify, options) {
26
26
  if (request.authUser) {
27
27
  const userService = UserServiceFactory();
28
28
  let user = await userService.findById(request.authUser.id);
29
+ user.password = undefined;
29
30
  delete user.password;
30
31
  return user;
31
32
  }
@@ -62,10 +63,13 @@ async function UserRoutes(fastify, options) {
62
63
  filters.push({ field: 'tenant', operator: '$eq', value: request.rbac.getAuthUser.tenantId });
63
64
  }
64
65
  let paginateResult = await userService.paginate({ page, limit, orderBy, orderDesc, search, filters });
66
+ for (let item of paginateResult.items) {
67
+ item.password = undefined;
68
+ delete item.password;
69
+ }
65
70
  return paginateResult;
66
71
  }
67
72
  catch (e) {
68
- console.log("/api/users", e);
69
73
  if (e instanceof ValidationError) {
70
74
  reply.statusCode = e.statusCode;
71
75
  reply.send({ error: e.message, inputErrors: e.errors });
@@ -0,0 +1,65 @@
1
+ import { DraxConfig, ZodErrorToValidationError } from "@drax/common-back";
2
+ import { userApiKeySchema } from "../zod/UserApiKeyZod.js";
3
+ import { ZodError } from "zod";
4
+ import crypto from "node:crypto";
5
+ import AuthUtils from "../utils/AuthUtils.js";
6
+ import IdentityConfig from "../config/IdentityConfig.js";
7
+ class UserApiKeyService {
8
+ constructor(userApiKeyRepostitory) {
9
+ this._repository = userApiKeyRepostitory;
10
+ console.log("UserApiKeyService constructor");
11
+ }
12
+ async create(userApiKeyData) {
13
+ try {
14
+ userApiKeyData.name = userApiKeyData?.name?.trim();
15
+ const secret = crypto.randomUUID();
16
+ const APIKEY_SECRET = DraxConfig.getOrLoad(IdentityConfig.ApiKeySecret);
17
+ userApiKeyData.secret = AuthUtils.generateHMAC(APIKEY_SECRET, secret);
18
+ await userApiKeySchema.parseAsync(userApiKeyData);
19
+ const userApiKey = await this._repository.create(userApiKeyData);
20
+ userApiKey.secret = secret;
21
+ return userApiKey;
22
+ }
23
+ catch (e) {
24
+ if (e instanceof ZodError) {
25
+ throw ZodErrorToValidationError(e, userApiKeyData);
26
+ }
27
+ throw e;
28
+ }
29
+ }
30
+ async update(id, userApiKeyData) {
31
+ try {
32
+ userApiKeyData.name = userApiKeyData?.name?.trim();
33
+ delete userApiKeyData.secret;
34
+ await userApiKeySchema.parseAsync(userApiKeyData);
35
+ const userApiKey = await this._repository.update(id, userApiKeyData);
36
+ return userApiKey;
37
+ }
38
+ catch (e) {
39
+ if (e instanceof ZodError) {
40
+ throw ZodErrorToValidationError(e, userApiKeyData);
41
+ }
42
+ throw e;
43
+ }
44
+ }
45
+ async delete(id) {
46
+ const currentUserApiKey = await this.findById(id);
47
+ const deletedUserApiKey = await this._repository.delete(id);
48
+ return deletedUserApiKey;
49
+ }
50
+ async findById(id) {
51
+ const userApiKey = await this._repository.findById(id);
52
+ return userApiKey;
53
+ }
54
+ async findBySecret(secret) {
55
+ const APIKEY_SECRET = DraxConfig.getOrLoad(IdentityConfig.ApiKeySecret);
56
+ const hashedSecret = AuthUtils.generateHMAC(APIKEY_SECRET, secret);
57
+ const userApiKey = await this._repository.findBySecret(hashedSecret);
58
+ return userApiKey;
59
+ }
60
+ async paginate({ page = 1, limit = 5, orderBy = '', orderDesc = false, search = '', filters = [] }) {
61
+ const pagination = await this._repository.paginate({ page, limit, orderBy, orderDesc, search, filters });
62
+ return pagination;
63
+ }
64
+ }
65
+ export default UserApiKeyService;
@@ -1,12 +1,12 @@
1
1
  import { DraxConfig } from "@drax/common-back";
2
2
  import IdentityConfig from "../config/IdentityConfig.js";
3
3
  function LoadIdentityConfigFromEnv() {
4
- DraxConfig.set(IdentityConfig.DbEngine, process.env[IdentityConfig.DbEngine]);
5
- DraxConfig.set(IdentityConfig.SqliteDbFile, process.env[IdentityConfig.SqliteDbFile]);
6
- DraxConfig.set(IdentityConfig.MongoDbUri, process.env[IdentityConfig.MongoDbUri]);
7
4
  DraxConfig.set(IdentityConfig.JwtSecret, process.env[IdentityConfig.JwtSecret]);
8
5
  DraxConfig.set(IdentityConfig.JwtExpiration, process.env[IdentityConfig.JwtExpiration]);
9
6
  DraxConfig.set(IdentityConfig.JwtIssuer, process.env[IdentityConfig.JwtIssuer]);
7
+ DraxConfig.set(IdentityConfig.ApiKeySecret, process.env[IdentityConfig.ApiKeySecret]);
8
+ DraxConfig.set(IdentityConfig.RbacCacheTTL, process.env[IdentityConfig.RbacCacheTTL]);
9
+ DraxConfig.set(IdentityConfig.AvatarDir, process.env[IdentityConfig.AvatarDir]);
10
10
  }
11
11
  export default LoadIdentityConfigFromEnv;
12
12
  export { LoadIdentityConfigFromEnv };
@@ -2,6 +2,7 @@ import bcryptjs from "bcryptjs";
2
2
  import jsonwebtoken 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
  class AuthUtils {
6
7
  static verifyToken(token) {
7
8
  const JWT_SECRET = DraxConfig.getOrLoad(IdentityConfig.JwtSecret);
@@ -51,5 +52,14 @@ class AuthUtils {
51
52
  let token = jsonwebtoken.sign(payload, JWT_SECRET, options);
52
53
  return token;
53
54
  }
55
+ static generateHMAC(secret, apikey) {
56
+ // Crear un objeto HMAC utilizando el algoritmo SHA-256 y el secreto
57
+ const hmac = crypto.createHmac('sha256', secret);
58
+ // Actualizar el HMAC con la apikey
59
+ hmac.update(apikey);
60
+ // Generar el hash en formato hexadecimal
61
+ return hmac.digest('hex');
62
+ }
54
63
  }
55
64
  export default AuthUtils;
65
+ export { AuthUtils };
@@ -0,0 +1,9 @@
1
+ import { array, object, string } from "zod";
2
+ const userApiKeySchema = object({
3
+ name: string({ required_error: "validation.required" })
4
+ .min(1, "validation.required"),
5
+ ipv4: array(string().ip({ version: "v4", message: 'validation.invalidIpv4' })),
6
+ ipv6: array(string().ip({ version: "v6", message: 'validation.invalidIpv6' })),
7
+ });
8
+ export default userApiKeySchema;
9
+ export { userApiKeySchema };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.0.30",
6
+ "version": "0.1.0",
7
7
  "description": "Identity module for user management, authentication and authorization.",
8
8
  "main": "dist/index.js",
9
9
  "types": "types/index.d.ts",
@@ -16,7 +16,8 @@
16
16
  "tsc": "tsc -b tsconfig.json",
17
17
  "test": "node --import tsx --test test/**/*",
18
18
  "testMongoRepositoryRole": "node --import tsx --test test/repository/mongo/role*",
19
- "testMongoRepositoryUser": "node --import tsx --test test/repository/mongo/user*",
19
+ "testMongoRepositoryUser": "node --import tsx --test test/repository/mongo/user-mongo*",
20
+ "testMongoRepositoryUserApiKey": "node --import tsx --test test/repository/mongo/user-apikey-mongo*",
20
21
  "testSqliteRepositoryUser": "node --import tsx --test test/repository/sqlite/user*",
21
22
  "testSqliteRepositoryRole": "node --import tsx --test test/repository/sqlite/role*",
22
23
  "testServiceRole": "node --import tsx --test test/service/role*",
@@ -26,9 +27,9 @@
26
27
  "author": "Cristian Incarnato & Drax Team",
27
28
  "license": "ISC",
28
29
  "dependencies": {
29
- "@drax/common-back": "^0.0.30",
30
- "@drax/common-share": "^0.0.30",
31
- "@drax/identity-share": "^0.0.30",
30
+ "@drax/common-back": "^0.1.0",
31
+ "@drax/common-share": "^0.1.0",
32
+ "@drax/identity-share": "^0.1.0",
32
33
  "bcryptjs": "^2.4.3",
33
34
  "express-jwt": "^8.4.1",
34
35
  "graphql": "^16.8.2",
@@ -59,5 +60,5 @@
59
60
  "debug": "0"
60
61
  }
61
62
  },
62
- "gitHead": "b0882d3ace0d0004cbc850e8d86381b9d6432535"
63
+ "gitHead": "bec2a22554155ef5deab5a55245878a4449932ad"
63
64
  }
@@ -1,13 +1,14 @@
1
1
  enum IdentityConfig {
2
2
 
3
- DbEngine = "DRAX_DB_ENGINE",
4
- SqliteDbFile = "DRAX_SQLITE_FILE",
5
- MongoDbUri = "DRAX_MONGO_URI",
3
+
6
4
 
7
5
  JwtSecret = "DRAX_JWT_SECRET",
8
6
  JwtExpiration = "DRAX_JWT_EXPIRATION",
9
7
  JwtIssuer = "DRAX_JWT_ISSUER",
10
8
 
9
+ ApiKeySecret = "DRAX_APIKEY_SECRET",
10
+ ApiKeyCacheTTL = "DRAX_APIKEY_CACHE_TTL",
11
+
11
12
  RbacCacheTTL = "DRAX_RBAC_CACHE_TTL",
12
13
 
13
14
  AvatarDir = "DRAX_AVATAR_DIR",
@@ -1,26 +1,26 @@
1
- import {DraxConfig} from "@drax/common-back"
1
+ import {DraxConfig, CommonConfig, COMMON} from "@drax/common-back"
2
2
  import RoleService from "../services/RoleService.js";
3
3
  import RoleMongoRepository from "../repository/mongo/RoleMongoRepository.js";
4
4
  import RoleSqliteRepository from "../repository/sqlite/RoleSqliteRepository.js";
5
- import {DbSetupUtils, DbEngine} from "../utils/DbSetupUtils.js";
6
5
  import type {IRoleRepository} from "../interfaces/IRoleRepository";
7
6
 
8
7
  let roleService: RoleService
9
8
 
10
- const RoleServiceFactory = (verbose: boolean = false) : RoleService => {
9
+ const RoleServiceFactory = (verbose: boolean = false): RoleService => {
11
10
 
12
- if(!roleService){
11
+ if (!roleService) {
13
12
  let roleRepository: IRoleRepository
14
13
 
15
- switch (DbSetupUtils.getDbEngine()) {
16
- case DbEngine.Mongo:
17
- console.log("RoleServiceFactory DB ENGINE MONGODB")
14
+ switch (DraxConfig.getOrLoad(CommonConfig.DbEngine)) {
15
+ case COMMON.DB_ENGINES.MONGODB:
18
16
  roleRepository = new RoleMongoRepository()
19
17
  break;
20
- case DbEngine.Sqlite:
21
- console.log("RoleServiceFactory DB ENGINE SQLITE")
22
- roleRepository = new RoleSqliteRepository(DbSetupUtils.getDbConfig(), verbose)
18
+ case COMMON.DB_ENGINES.SQLITE:
19
+ const dbFile = DraxConfig.getOrLoad(CommonConfig.SqliteDbFile)
20
+ roleRepository = new RoleSqliteRepository(dbFile, verbose)
23
21
  break;
22
+ default:
23
+ throw new Error("DraxConfig.DB_ENGINE must be one of " + Object.values(COMMON.DB_ENGINES).join(", "));
24
24
  }
25
25
 
26
26
  roleService = new RoleService(roleRepository)
@@ -29,4 +29,4 @@ const RoleServiceFactory = (verbose: boolean = false) : RoleService => {
29
29
  return roleService
30
30
  }
31
31
 
32
- export default RoleServiceFactory
32
+ export default RoleServiceFactory