@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.
- package/dist/config/IdentityConfig.js +2 -3
- package/dist/factory/RoleServiceFactory.js +8 -7
- package/dist/factory/TenantServiceFactory.js +8 -7
- package/dist/factory/UserApiKeyServiceFactory.js +24 -0
- package/dist/factory/UserServiceFactory.js +8 -7
- package/dist/graphql/resolvers/user-api-key.resolvers.js +89 -0
- package/dist/graphql/resolvers/user.resolvers.js +7 -2
- package/dist/graphql/types/userApiKey.graphql +33 -0
- package/dist/index.js +4 -2
- package/dist/interfaces/IUserApiKeyRepository.js +1 -0
- package/dist/middleware/apiKeyMiddleware.js +30 -0
- package/dist/middleware/rbacMiddleware.js +0 -1
- package/dist/models/UserApiKeyModel.js +44 -0
- package/dist/permissions/IdentityPermissions.js +6 -0
- package/dist/rbac/Rbac.js +8 -0
- package/dist/repository/mongo/UserApiKeyMongoRepository.js +82 -0
- package/dist/repository/mongo/UserMongoRepository.js +3 -3
- package/dist/repository/sqlite/RoleSqliteRepository.js +16 -18
- package/dist/repository/sqlite/TenantSqliteRepository.js +13 -11
- package/dist/repository/sqlite/UserApiKeySqliteRepository.js +147 -0
- package/dist/repository/sqlite/UserSqliteRepository.js +29 -26
- package/dist/routes/UserApiKeyRoutes.js +119 -0
- package/dist/routes/UserRoutes.js +5 -1
- package/dist/services/UserApiKeyService.js +65 -0
- package/dist/setup/LoadIdentityConfigFromEnv.js +3 -3
- package/dist/utils/AuthUtils.js +10 -0
- package/dist/zod/UserApiKeyZod.js +9 -0
- package/package.json +7 -6
- package/src/config/IdentityConfig.ts +4 -3
- package/src/factory/RoleServiceFactory.ts +11 -11
- package/src/factory/TenantServiceFactory.ts +11 -11
- package/src/factory/UserApiKeyServiceFactory.ts +30 -0
- package/src/factory/UserServiceFactory.ts +8 -7
- package/src/graphql/resolvers/tenant.resolvers.ts +0 -1
- package/src/graphql/resolvers/user-api-key.resolvers.ts +94 -0
- package/src/graphql/resolvers/user.resolvers.ts +9 -2
- package/src/graphql/types/userApiKey.graphql +33 -0
- package/src/index.ts +10 -0
- package/src/interfaces/IUserApiKeyRepository.ts +8 -0
- package/src/middleware/apiKeyMiddleware.ts +35 -0
- package/src/middleware/rbacMiddleware.ts +1 -2
- package/src/models/UserApiKeyModel.ts +59 -0
- package/src/permissions/IdentityPermissions.ts +7 -0
- package/src/rbac/Rbac.ts +13 -2
- package/src/repository/mongo/UserApiKeyMongoRepository.ts +114 -0
- package/src/repository/mongo/UserMongoRepository.ts +3 -3
- package/src/repository/sqlite/RoleSqliteRepository.ts +28 -20
- package/src/repository/sqlite/TenantSqliteRepository.ts +25 -11
- package/src/repository/sqlite/UserApiKeySqliteRepository.ts +197 -0
- package/src/repository/sqlite/UserSqliteRepository.ts +37 -27
- package/src/routes/UserApiKeyRoutes.ts +128 -0
- package/src/routes/UserRoutes.ts +5 -1
- package/src/services/UserApiKeyService.ts +86 -0
- package/src/setup/LoadIdentityConfigFromEnv.ts +5 -3
- package/src/utils/AuthUtils.ts +11 -0
- package/src/zod/UserApiKeyZod.ts +15 -0
- package/test/data-obj/apikey/root-mongo-user-apikey.ts +10 -0
- package/test/data-obj/roles/admin-mongo-role.ts +0 -3
- package/test/initializers/RoleMongoInitializer.ts +1 -0
- package/test/initializers/RoleSqliteInitializer.ts +1 -0
- package/test/initializers/UserMongoInitializer.ts +18 -0
- package/test/repository/mongo/user-apikey-mongo-repository.test.ts +73 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/types/factory/RoleServiceFactory.d.ts.map +1 -1
- package/types/factory/TenantServiceFactory.d.ts.map +1 -1
- package/types/factory/UserApiKeyServiceFactory.d.ts.map +1 -0
- package/types/factory/UserServiceFactory.d.ts.map +1 -1
- package/types/graphql/resolvers/tenant.resolvers.d.ts.map +1 -1
- package/types/graphql/resolvers/user-api-key.resolvers.d.ts.map +1 -0
- package/types/graphql/resolvers/user.resolvers.d.ts.map +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/interfaces/IUserApiKeyRepository.d.ts.map +1 -0
- package/types/middleware/apiKeyMiddleware.d.ts.map +1 -0
- package/types/middleware/rbacMiddleware.d.ts.map +1 -1
- package/types/models/UserApiKeyModel.d.ts.map +1 -0
- package/types/rbac/Rbac.d.ts +15 -0
- package/types/rbac/Rbac.d.ts.map +1 -1
- package/types/repository/mongo/UserApiKeyMongoRepository.d.ts.map +1 -0
- package/types/repository/sqlite/RoleSqliteRepository.d.ts.map +1 -1
- package/types/repository/sqlite/TenantSqliteRepository.d.ts.map +1 -1
- package/types/repository/sqlite/UserApiKeySqliteRepository.d.ts +19 -0
- package/types/repository/sqlite/UserApiKeySqliteRepository.d.ts.map +1 -0
- package/types/repository/sqlite/UserSqliteRepository.d.ts.map +1 -1
- package/types/routes/UserApiKeyRoutes.d.ts.map +1 -0
- package/types/routes/UserRoutes.d.ts.map +1 -1
- package/types/services/UserApiKeyService.d.ts.map +1 -0
- package/types/setup/LoadIdentityConfigFromEnv.d.ts.map +1 -1
- package/types/utils/AuthUtils.d.ts.map +1 -1
- package/types/zod/UserApiKeyZod.d.ts +16 -0
- package/types/zod/UserApiKeyZod.d.ts.map +1 -0
- package/src/utils/DbSetupUtils.ts +0 -41
- package/types/config/IdentityConfig.d.ts +0 -13
- package/types/config/IdentityConfig.d.ts.map +0 -1
- package/types/graphql/resolvers/role.resolvers.d.ts +0 -52
- package/types/graphql/resolvers/tenant.resolvers.d.ts +0 -49
- package/types/graphql/resolvers/user.resolvers.d.ts +0 -67
- package/types/utils/DbSetupUtils.d.ts.map +0 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import UserApiKeyMongoRepository from "../repository/mongo/UserApiKeyMongoRepository.js";
|
|
2
|
+
import UserApiKeyService from "../services/UserApiKeyService.js";
|
|
3
|
+
import UserApiKeySqliteRepository from "../repository/sqlite/UserApiKeySqliteRepository.js";
|
|
4
|
+
import {IUserApiKeyRepository} from "../interfaces/IUserApiKeyRepository";
|
|
5
|
+
import {COMMON, CommonConfig, DraxConfig} from "@drax/common-back";
|
|
6
|
+
|
|
7
|
+
let userService: UserApiKeyService
|
|
8
|
+
|
|
9
|
+
const UserApiKeyServiceFactory = (verbose: boolean = false): UserApiKeyService => {
|
|
10
|
+
if (!userService) {
|
|
11
|
+
let userRepository: IUserApiKeyRepository
|
|
12
|
+
switch (DraxConfig.getOrLoad(CommonConfig.DbEngine)) {
|
|
13
|
+
case COMMON.DB_ENGINES.MONGODB:
|
|
14
|
+
userRepository = new UserApiKeyMongoRepository()
|
|
15
|
+
break;
|
|
16
|
+
case COMMON.DB_ENGINES.SQLITE:
|
|
17
|
+
const dbFile = DraxConfig.getOrLoad(CommonConfig.SqliteDbFile)
|
|
18
|
+
userRepository = new UserApiKeySqliteRepository(dbFile, verbose)
|
|
19
|
+
break;
|
|
20
|
+
default:
|
|
21
|
+
throw new Error("DraxConfig.DB_ENGINE must be one of " + Object.values(COMMON.DB_ENGINES).join(", "));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
userService = new UserApiKeyService(userRepository)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return userService
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default UserApiKeyServiceFactory
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import UserMongoRepository from "../repository/mongo/UserMongoRepository.js";
|
|
2
2
|
import UserService from "../services/UserService.js";
|
|
3
3
|
import UserSqliteRepository from "../repository/sqlite/UserSqliteRepository.js";
|
|
4
|
-
import {DbEngine, DbSetupUtils} from "../utils/DbSetupUtils.js";
|
|
5
4
|
import {IUserRepository} from "../interfaces/IUserRepository";
|
|
5
|
+
import {COMMON, CommonConfig, DraxConfig} from "@drax/common-back";
|
|
6
6
|
|
|
7
7
|
let userService: UserService
|
|
8
8
|
|
|
9
9
|
const UserServiceFactory = (verbose:boolean = false) : UserService => {
|
|
10
10
|
if(!userService){
|
|
11
11
|
let userRepository: IUserRepository
|
|
12
|
-
switch (
|
|
13
|
-
case
|
|
14
|
-
console.log("UserServiceFactory DB ENGINE MONGODB")
|
|
12
|
+
switch (DraxConfig.getOrLoad(CommonConfig.DbEngine)) {
|
|
13
|
+
case COMMON.DB_ENGINES.MONGODB:
|
|
15
14
|
userRepository = new UserMongoRepository()
|
|
16
15
|
break;
|
|
17
|
-
case
|
|
18
|
-
|
|
19
|
-
userRepository = new UserSqliteRepository(
|
|
16
|
+
case COMMON.DB_ENGINES.SQLITE:
|
|
17
|
+
const dbFile = DraxConfig.getOrLoad(CommonConfig.SqliteDbFile)
|
|
18
|
+
userRepository = new UserSqliteRepository(dbFile,verbose)
|
|
20
19
|
break;
|
|
20
|
+
default:
|
|
21
|
+
throw new Error("DraxConfig.DB_ENGINE must be one of " + Object.values(COMMON.DB_ENGINES).join(", "));
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
userService = new UserService(userRepository)
|
|
@@ -2,7 +2,6 @@ import TenantServiceFactory from "../../factory/TenantServiceFactory.js";
|
|
|
2
2
|
import {IdentityPermissions} from "../../permissions/IdentityPermissions.js";
|
|
3
3
|
import {ValidationError, ValidationErrorToGraphQLError} from "@drax/common-back";
|
|
4
4
|
import {GraphQLError} from "graphql";
|
|
5
|
-
import {PermissionService} from "../../services/PermissionService.js";
|
|
6
5
|
import UnauthorizedError from "../../errors/UnauthorizedError.js";
|
|
7
6
|
|
|
8
7
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import UserApiKeyServiceFactory from "../../factory/UserApiKeyServiceFactory.js";
|
|
2
|
+
import {IdentityPermissions} from "../../permissions/IdentityPermissions.js";
|
|
3
|
+
import {ValidationError, ValidationErrorToGraphQLError} from "@drax/common-back";
|
|
4
|
+
import {GraphQLError} from "graphql";
|
|
5
|
+
import UnauthorizedError from "../../errors/UnauthorizedError.js";
|
|
6
|
+
import * as crypto from "node:crypto";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
Query: {
|
|
11
|
+
paginateUserApiKey: async (_, {options= {page:1, limit:5, orderBy:"", orderDesc:false, search:"", filters: []} }, {rbac, authUser}) => {
|
|
12
|
+
try {
|
|
13
|
+
rbac.assertAuthenticated()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
rbac.assertOrPermissions([
|
|
17
|
+
IdentityPermissions.ViewUserApiKey,
|
|
18
|
+
IdentityPermissions.ViewMyUserApiKey
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
if(!Array.isArray(options.filters)){
|
|
22
|
+
options.filters = []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if(!rbac.hasPermission(IdentityPermissions.ViewUserApiKey)){
|
|
26
|
+
options.filters.push({field: "user", operator: "eq", value: rbac.authUser.id})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const userApiKeyService = UserApiKeyServiceFactory()
|
|
30
|
+
return await userApiKeyService.paginate(options)
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error("paginateUserApiKey",e)
|
|
33
|
+
if (e instanceof UnauthorizedError) {
|
|
34
|
+
throw new GraphQLError(e.message)
|
|
35
|
+
}
|
|
36
|
+
throw new GraphQLError('error.server')
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
Mutation: {
|
|
41
|
+
createUserApiKey: async (_, {input}, {rbac}) => {
|
|
42
|
+
try {
|
|
43
|
+
rbac.assertPermission(IdentityPermissions.CreateUserApiKey)
|
|
44
|
+
input.user = rbac.authUser.id
|
|
45
|
+
input.secret = crypto.randomUUID()
|
|
46
|
+
const userApiKeyService = UserApiKeyServiceFactory(true)
|
|
47
|
+
return await userApiKeyService.create(input)
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error("createUserApiKey",e)
|
|
50
|
+
if (e instanceof ValidationError) {
|
|
51
|
+
throw ValidationErrorToGraphQLError(e)
|
|
52
|
+
}
|
|
53
|
+
if (e instanceof UnauthorizedError) {
|
|
54
|
+
throw new GraphQLError(e.message)
|
|
55
|
+
}
|
|
56
|
+
throw new GraphQLError('error.server')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
},
|
|
60
|
+
updateUserApiKey: async (_, {id, input}, {rbac}) => {
|
|
61
|
+
try {
|
|
62
|
+
rbac.assertPermission(IdentityPermissions.UpdateUserApiKey)
|
|
63
|
+
const userApiKeyService = UserApiKeyServiceFactory()
|
|
64
|
+
return await userApiKeyService.update(id, input)
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error("updateUserApiKey",e)
|
|
67
|
+
if (e instanceof ValidationError) {
|
|
68
|
+
throw ValidationErrorToGraphQLError(e)
|
|
69
|
+
}
|
|
70
|
+
if (e instanceof UnauthorizedError) {
|
|
71
|
+
throw new GraphQLError(e.message)
|
|
72
|
+
}
|
|
73
|
+
throw new GraphQLError('error.server')
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
deleteUserApiKey: async (_, {id}, {rbac}) => {
|
|
77
|
+
try {
|
|
78
|
+
rbac.assertPermission(IdentityPermissions.DeleteUserApiKey)
|
|
79
|
+
const userApiKeyService = UserApiKeyServiceFactory()
|
|
80
|
+
return await userApiKeyService.delete(id)
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error("deleteUserApiKey",e)
|
|
83
|
+
if (e instanceof ValidationError) {
|
|
84
|
+
throw ValidationErrorToGraphQLError(e)
|
|
85
|
+
}
|
|
86
|
+
if (e instanceof UnauthorizedError) {
|
|
87
|
+
throw new GraphQLError(e.message)
|
|
88
|
+
}
|
|
89
|
+
throw new GraphQLError('error.server')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -20,7 +20,7 @@ export default {
|
|
|
20
20
|
if (authUser) {
|
|
21
21
|
let userService = UserServiceFactory()
|
|
22
22
|
let user = await userService.findById(authUser.id)
|
|
23
|
-
|
|
23
|
+
user.password = undefined
|
|
24
24
|
return user
|
|
25
25
|
}
|
|
26
26
|
throw new UnauthorizedError()
|
|
@@ -34,7 +34,9 @@ export default {
|
|
|
34
34
|
try {
|
|
35
35
|
rbac.assertPermission(IdentityPermissions.ViewUser)
|
|
36
36
|
let userService = UserServiceFactory()
|
|
37
|
-
|
|
37
|
+
let user = await userService.findById(id)
|
|
38
|
+
user.password = undefined
|
|
39
|
+
return user
|
|
38
40
|
} catch (e) {
|
|
39
41
|
if (e instanceof UnauthorizedError) {
|
|
40
42
|
throw new GraphQLError(e.message)
|
|
@@ -47,6 +49,11 @@ export default {
|
|
|
47
49
|
try {
|
|
48
50
|
rbac.assertPermission(IdentityPermissions.ViewUser)
|
|
49
51
|
let userService = UserServiceFactory()
|
|
52
|
+
|
|
53
|
+
if(!options.filters){
|
|
54
|
+
options.filters = []
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
if (rbac.getAuthUser.tenantId) {
|
|
51
58
|
options.filters.push({field: 'tenant', operator: '$eq', value: rbac.getAuthUser.tenantId})
|
|
52
59
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type UserApiKey {
|
|
2
|
+
id: ID!
|
|
3
|
+
name: String
|
|
4
|
+
secret: String
|
|
5
|
+
ipv4: [String]
|
|
6
|
+
ipv6: [String]
|
|
7
|
+
user: User
|
|
8
|
+
createdAt: Date
|
|
9
|
+
updatedAt: Date
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type UserApiKeyPaginated{
|
|
13
|
+
total: Int
|
|
14
|
+
page: Int
|
|
15
|
+
limit: Int
|
|
16
|
+
items: [UserApiKey]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Query{
|
|
20
|
+
paginateUserApiKey(options: PaginateOptions): UserApiKeyPaginated
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
input UserApiKeyInput{
|
|
24
|
+
name: String
|
|
25
|
+
ipv4: [String]
|
|
26
|
+
ipv6: [String]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type Mutation{
|
|
30
|
+
createUserApiKey(input: UserApiKeyInput): UserApiKey
|
|
31
|
+
updateUserApiKey(id: ID!, input: UserApiKeyInput): UserApiKey
|
|
32
|
+
deleteUserApiKey(id: ID!): Boolean
|
|
33
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,18 +2,24 @@ import GraphqlMerge from "./graphql/index.js"
|
|
|
2
2
|
import UserServiceFactory from "./factory/UserServiceFactory.js";
|
|
3
3
|
import RoleServiceFactory from "./factory/RoleServiceFactory.js";
|
|
4
4
|
import TenantServiceFactory from "./factory/TenantServiceFactory.js";
|
|
5
|
+
|
|
5
6
|
import RoleService from "./services/RoleService.js";
|
|
6
7
|
import UserService from "./services/UserService.js";
|
|
7
8
|
import TenantService from "./services/TenantService.js";
|
|
8
9
|
import PermissionService from "./services/PermissionService.js";
|
|
10
|
+
|
|
9
11
|
import Rbac from "./rbac/Rbac.js";
|
|
12
|
+
|
|
10
13
|
import {UserRoutes} from "./routes/UserRoutes.js";
|
|
11
14
|
import {UserAvatarRoutes} from "./routes/UserAvatarRoutes.js";
|
|
12
15
|
import {RoleRoutes} from "./routes/RoleRoutes.js";
|
|
13
16
|
import {TenantRoutes} from "./routes/TenantRoutes.js";
|
|
17
|
+
import {UserApiKeyRoutes} from "./routes/UserApiKeyRoutes.js";
|
|
18
|
+
|
|
14
19
|
import AuthUtils from "./utils/AuthUtils.js";
|
|
15
20
|
import {jwtMiddleware} from "./middleware/jwtMiddleware.js";
|
|
16
21
|
import {rbacMiddleware} from "./middleware/rbacMiddleware.js";
|
|
22
|
+
import {apiKeyMiddleware} from "./middleware/apiKeyMiddleware.js";
|
|
17
23
|
|
|
18
24
|
import IdentityPermissions from "./permissions/IdentityPermissions.js";
|
|
19
25
|
import IdentityConfig from "./config/IdentityConfig.js";
|
|
@@ -29,6 +35,7 @@ import RecoveryUserPassword from "./setup/RecoveryUserPassword.js";
|
|
|
29
35
|
import type {IRoleRepository} from "./interfaces/IRoleRepository";
|
|
30
36
|
import type {ITenantRepository} from "./interfaces/ITenantRepository";
|
|
31
37
|
import type {IUserRepository} from "./interfaces/IUserRepository";
|
|
38
|
+
import type {IUserApiKeyRepository} from "./interfaces/IUserApiKeyRepository";
|
|
32
39
|
|
|
33
40
|
|
|
34
41
|
const graphqlMergeResult = await GraphqlMerge()
|
|
@@ -40,6 +47,7 @@ export type {
|
|
|
40
47
|
IRoleRepository,
|
|
41
48
|
ITenantRepository,
|
|
42
49
|
IUserRepository,
|
|
50
|
+
IUserApiKeyRepository
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
export {
|
|
@@ -64,12 +72,14 @@ export {
|
|
|
64
72
|
RoleRoutes,
|
|
65
73
|
TenantRoutes,
|
|
66
74
|
UserAvatarRoutes,
|
|
75
|
+
UserApiKeyRoutes,
|
|
67
76
|
|
|
68
77
|
AuthUtils,
|
|
69
78
|
|
|
70
79
|
//API MIDDLEWARE
|
|
71
80
|
jwtMiddleware,
|
|
72
81
|
rbacMiddleware,
|
|
82
|
+
apiKeyMiddleware,
|
|
73
83
|
|
|
74
84
|
//Permissions
|
|
75
85
|
IdentityPermissions,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import {IUserApiKey, IUserApiKeyBase} from '@drax/identity-share'
|
|
2
|
+
import {IDraxCrud} from "@drax/common-share";
|
|
3
|
+
|
|
4
|
+
interface IUserApiKeyRepository extends IDraxCrud<IUserApiKey, IUserApiKeyBase, IUserApiKeyBase>{
|
|
5
|
+
findBySecret(username: string): Promise<IUserApiKey | null>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export {IUserApiKeyRepository}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {IUserApiKey} from "@drax/identity-share";
|
|
2
|
+
import {DraxCache, DraxConfig} from "@drax/common-back";
|
|
3
|
+
import UserApiKeyServiceFactory from "../factory/UserApiKeyServiceFactory.js";
|
|
4
|
+
import IdentityConfig from "../config/IdentityConfig.js";
|
|
5
|
+
|
|
6
|
+
const cacheTTL = DraxConfig.getOrLoad(IdentityConfig.ApiKeyCacheTTL) ? parseInt(DraxConfig.getOrLoad(IdentityConfig.ApiKeyCacheTTL)) : 10000;
|
|
7
|
+
const draxCache = new DraxCache<IUserApiKey>(cacheTTL);
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async function userApiKeyLoader(k):Promise<IUserApiKey | null> {
|
|
11
|
+
const userApiKeyService = UserApiKeyServiceFactory()
|
|
12
|
+
const userApiKey: IUserApiKey = await userApiKeyService.findBySecret(k)
|
|
13
|
+
return userApiKey
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function apiKeyMiddleware (request, reply) {
|
|
17
|
+
try{
|
|
18
|
+
const apiKey = request.headers['x-api-key']
|
|
19
|
+
if(apiKey){
|
|
20
|
+
const userApiKey = await draxCache.getOrLoad(apiKey, userApiKeyLoader)
|
|
21
|
+
if(userApiKey && userApiKey.user){
|
|
22
|
+
request.authUser = userApiKey.user
|
|
23
|
+
request.authUser.roleId = userApiKey.user.role.id
|
|
24
|
+
request.authUser.tenantId = userApiKey.user.tenant.id
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return
|
|
28
|
+
}catch (e) {
|
|
29
|
+
console.error(e)
|
|
30
|
+
reply.code(500).send({ error: 'APIKEY ERROR' });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default apiKeyMiddleware;
|
|
35
|
+
export {apiKeyMiddleware}
|
|
@@ -16,10 +16,9 @@ async function roleLoader(k):Promise<IRole | null> {
|
|
|
16
16
|
|
|
17
17
|
async function rbacMiddleware (request, reply) {
|
|
18
18
|
try{
|
|
19
|
-
//console.log("rbacMiddleware authUser",request.authUser)
|
|
20
19
|
if(request.authUser as IJwtUser){
|
|
21
20
|
const authUser = request.authUser
|
|
22
|
-
const cacheKey= authUser.roleId
|
|
21
|
+
const cacheKey = authUser.roleId
|
|
23
22
|
const role = await draxCache.getOrLoad(cacheKey, roleLoader)
|
|
24
23
|
const rbac = new Rbac(authUser,role)
|
|
25
24
|
request.rbac = rbac
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {mongoose, MongooseSoftDelete} from '@drax/common-back';
|
|
2
|
+
import {IUserApiKey} from "@drax/identity-share";
|
|
3
|
+
import uniqueValidator from 'mongoose-unique-validator';
|
|
4
|
+
import mongoosePaginate from 'mongoose-paginate-v2'
|
|
5
|
+
import {PaginateModel} from "mongoose";
|
|
6
|
+
|
|
7
|
+
// Defining user Mongoose Schema
|
|
8
|
+
const UserApiKeySchema = new mongoose.Schema<IUserApiKey>({
|
|
9
|
+
name: {
|
|
10
|
+
type: String,
|
|
11
|
+
unique: false,
|
|
12
|
+
required: true,
|
|
13
|
+
index: false,
|
|
14
|
+
},
|
|
15
|
+
secret: {
|
|
16
|
+
type: String,
|
|
17
|
+
unique: false,
|
|
18
|
+
required: true,
|
|
19
|
+
index: true,
|
|
20
|
+
},
|
|
21
|
+
user: {
|
|
22
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
23
|
+
ref: 'User',
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
ipv4: [{
|
|
27
|
+
type: String,
|
|
28
|
+
unique: false,
|
|
29
|
+
required: false,
|
|
30
|
+
index: false,
|
|
31
|
+
}],
|
|
32
|
+
ipv6: [{
|
|
33
|
+
type: String,
|
|
34
|
+
unique: false,
|
|
35
|
+
required: false,
|
|
36
|
+
index: false,
|
|
37
|
+
}],
|
|
38
|
+
}, {timestamps: true});
|
|
39
|
+
|
|
40
|
+
UserApiKeySchema.set('toJSON', {getters: true});
|
|
41
|
+
|
|
42
|
+
UserApiKeySchema.plugin(uniqueValidator, {message: 'validation.unique'});
|
|
43
|
+
|
|
44
|
+
UserApiKeySchema.plugin(MongooseSoftDelete);
|
|
45
|
+
UserApiKeySchema.plugin(mongoosePaginate);
|
|
46
|
+
|
|
47
|
+
const MODEL_NAME = 'UserApiKey';
|
|
48
|
+
const COLLECTION_NAME = 'userApiKeys';
|
|
49
|
+
|
|
50
|
+
const UserApiKeyModel = mongoose.model<IUserApiKey,PaginateModel<IUserApiKey>>(MODEL_NAME, UserApiKeySchema,COLLECTION_NAME);
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
UserApiKeySchema,
|
|
55
|
+
UserApiKeyModel
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default UserApiKeyModel
|
|
59
|
+
|
|
@@ -7,6 +7,13 @@ enum IdentityPermissions {
|
|
|
7
7
|
ViewUser = "user:view",
|
|
8
8
|
ManageUser = "user:manage",
|
|
9
9
|
|
|
10
|
+
CreateUserApiKey = "userApiKey:create",
|
|
11
|
+
UpdateUserApiKey = "userApiKey:update",
|
|
12
|
+
DeleteUserApiKey = "userApiKey:delete",
|
|
13
|
+
ViewUserApiKey = "userApiKey:view",
|
|
14
|
+
ViewMyUserApiKey = "userApiKey:myView",
|
|
15
|
+
ManageUserApiKey = "userApiKey:manage",
|
|
16
|
+
|
|
10
17
|
CreateRole = "role:create",
|
|
11
18
|
UpdateRole = "role:update",
|
|
12
19
|
DeleteRole = "role:delete",
|
package/src/rbac/Rbac.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {IJwtUser, IRole} from "@drax/identity-share";
|
|
2
2
|
import UnauthorizedError from "../errors/UnauthorizedError.js";
|
|
3
3
|
|
|
4
|
-
class Rbac{
|
|
4
|
+
class Rbac {
|
|
5
5
|
private role: IRole;
|
|
6
6
|
private authUser: IJwtUser;
|
|
7
7
|
|
|
@@ -27,11 +27,22 @@ class Rbac{
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
assertPermission(requiredPermission: string) {
|
|
30
|
-
if(!this.hasPermission(requiredPermission)){
|
|
30
|
+
if (!this.hasPermission(requiredPermission)) {
|
|
31
31
|
throw new UnauthorizedError()
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
assertOrPermissions(requiredPermissions: string[]) {
|
|
36
|
+
|
|
37
|
+
for(let requiredPermission of requiredPermissions){
|
|
38
|
+
if (this.hasPermission(requiredPermission)) {
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw new UnauthorizedError()
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
assertAuthenticated() {
|
|
36
47
|
if (!this.authUser) {
|
|
37
48
|
throw new UnauthorizedError()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {UserApiKeyModel} from "../../models/UserApiKeyModel.js";
|
|
2
|
+
import {
|
|
3
|
+
mongoose,
|
|
4
|
+
MongooseErrorToValidationError,
|
|
5
|
+
MongoServerErrorToValidationError,
|
|
6
|
+
ValidationError
|
|
7
|
+
} from "@drax/common-back"
|
|
8
|
+
import type {IUserApiKey, IUserApiKeyBase, IUserApiKeySoftDelete} from "@drax/identity-share";
|
|
9
|
+
import {DeleteResult, MongoServerError} from "mongodb";
|
|
10
|
+
import {PaginateResult} from "mongoose";
|
|
11
|
+
import {IDraxPaginateOptions, IDraxPaginateResult} from "@drax/common-share";
|
|
12
|
+
import {IUserApiKeyRepository} from "../../interfaces/IUserApiKeyRepository";
|
|
13
|
+
|
|
14
|
+
class UserMongoRepository implements IUserApiKeyRepository {
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async create(data: IUserApiKeyBase): Promise<IUserApiKey> {
|
|
21
|
+
try {
|
|
22
|
+
|
|
23
|
+
const userApiKey: mongoose.HydratedDocument<IUserApiKey> = new UserApiKeyModel(data)
|
|
24
|
+
await userApiKey.save()
|
|
25
|
+
await userApiKey.populate({path: 'user', populate: {path: 'tenant role'} })
|
|
26
|
+
return userApiKey
|
|
27
|
+
} catch (e) {
|
|
28
|
+
if (e instanceof mongoose.Error.ValidationError) {
|
|
29
|
+
throw MongooseErrorToValidationError(e)
|
|
30
|
+
}
|
|
31
|
+
throw e
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async update(id: string, data: IUserApiKeyBase): Promise<IUserApiKey> {
|
|
37
|
+
try {
|
|
38
|
+
delete data.secret
|
|
39
|
+
const userApiKey: mongoose.HydratedDocument<IUserApiKey> = await UserApiKeyModel.findOneAndUpdate({_id: id}, data, {new: true}).populate({path: 'user', populate: {path: 'tenant role'} }).exec()
|
|
40
|
+
return userApiKey
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (e instanceof mongoose.Error.ValidationError) {
|
|
43
|
+
throw MongooseErrorToValidationError(e)
|
|
44
|
+
}
|
|
45
|
+
if (e instanceof MongoServerError || e.name === 'MongoServerError') {
|
|
46
|
+
throw MongoServerErrorToValidationError(e)
|
|
47
|
+
}
|
|
48
|
+
throw e
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async delete(id: string): Promise<boolean> {
|
|
53
|
+
const userApiKey: mongoose.HydratedDocument<IUserApiKeySoftDelete> = await UserApiKeyModel.findById(id)
|
|
54
|
+
userApiKey.softDelete()
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async findById(id: string): Promise<IUserApiKey> {
|
|
59
|
+
const userApiKey: mongoose.HydratedDocument<IUserApiKey> = await UserApiKeyModel.findById(id).populate({path: 'user', populate: {path: 'tenant role'} }).exec()
|
|
60
|
+
return userApiKey
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async findBySecret(secret: string): Promise<IUserApiKey> {
|
|
64
|
+
const userApiKey: mongoose.HydratedDocument<IUserApiKey> = await UserApiKeyModel.findOne({secret: secret}).populate({path: 'user', populate: {path: 'tenant role'}}).exec()
|
|
65
|
+
return userApiKey
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async paginate({
|
|
69
|
+
page = 1,
|
|
70
|
+
limit = 5,
|
|
71
|
+
orderBy = '',
|
|
72
|
+
orderDesc = false,
|
|
73
|
+
search = '',
|
|
74
|
+
filters = []
|
|
75
|
+
}: IDraxPaginateOptions): Promise<IDraxPaginateResult<IUserApiKey>> {
|
|
76
|
+
|
|
77
|
+
const query = {
|
|
78
|
+
deleted: false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (search) {
|
|
82
|
+
query['$or'] = [
|
|
83
|
+
{name: new RegExp(search, 'i')},
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if(filters){
|
|
88
|
+
for(const filter of filters){
|
|
89
|
+
if(['eq','$eq'].includes(filter.operator)){
|
|
90
|
+
query[filter.field] = {$eq: filter.value}
|
|
91
|
+
}
|
|
92
|
+
if(['ne','$ne'].includes(filter.operator)){
|
|
93
|
+
query[filter.field] = {$ne: filter.value}
|
|
94
|
+
}
|
|
95
|
+
if(['in','$in'].includes(filter.operator)){
|
|
96
|
+
query[filter.field] = {$in: filter.value}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const options = {populate: ['user', 'user.tenant', 'user.role'], page: page, limit: limit}
|
|
102
|
+
|
|
103
|
+
const userApiKeyPaginated: PaginateResult<IUserApiKey> = await UserApiKeyModel.paginate(query, options)
|
|
104
|
+
return {
|
|
105
|
+
page: page,
|
|
106
|
+
limit: limit,
|
|
107
|
+
total: userApiKeyPaginated.totalDocs,
|
|
108
|
+
items: userApiKeyPaginated.docs
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default UserMongoRepository
|
|
@@ -86,13 +86,13 @@ class UserMongoRepository implements IUserRepository {
|
|
|
86
86
|
|
|
87
87
|
if(filters){
|
|
88
88
|
for(const filter of filters){
|
|
89
|
-
if(
|
|
89
|
+
if(['eq','$eq'].includes(filter.operator)){
|
|
90
90
|
query[filter.field] = {$eq: filter.value}
|
|
91
91
|
}
|
|
92
|
-
if(
|
|
92
|
+
if(['ne','$ne'].includes(filter.operator)){
|
|
93
93
|
query[filter.field] = {$ne: filter.value}
|
|
94
94
|
}
|
|
95
|
-
if(
|
|
95
|
+
if(['in','$in'].includes(filter.operator)){
|
|
96
96
|
query[filter.field] = {$in: filter.value}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -4,31 +4,36 @@ import sqlite from "better-sqlite3";
|
|
|
4
4
|
import {randomUUID} from "node:crypto";
|
|
5
5
|
import {IDraxPaginateResult, IDraxPaginateOptions} from "@drax/common-share";
|
|
6
6
|
import {IRole, IRoleBase} from "@drax/identity-share";
|
|
7
|
-
import {SqliteErrorToValidationError} from "@drax/common-back";
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
19
|
-
`;
|
|
7
|
+
import {SqliteErrorToValidationError, SqliteTableBuilder, SqliteTableField} from "@drax/common-back";
|
|
8
|
+
|
|
9
|
+
const tableFields: SqliteTableField[] = [
|
|
10
|
+
{name: "name", type: "TEXT", unique: true, primary: false},
|
|
11
|
+
{name: "permissions", type: "TEXT", unique: false, primary: false},
|
|
12
|
+
{name: "childRoles", type: "TEXT", unique: false, primary: false},
|
|
13
|
+
{name: "readonly", type: "INTEGER", unique: false, primary: false},
|
|
14
|
+
{name: "createdAt", type: "TEXT", unique: false, primary: false},
|
|
15
|
+
{name: "updatedAt", type: "TEXT", unique: false, primary: false},
|
|
16
|
+
]
|
|
17
|
+
|
|
20
18
|
|
|
21
19
|
class RoleSqliteRepository implements IRoleRepository{
|
|
22
20
|
|
|
23
21
|
private db: any;
|
|
22
|
+
private dataBaseFile: string;
|
|
24
23
|
|
|
25
|
-
constructor(
|
|
26
|
-
this.
|
|
24
|
+
constructor(dataBaseFile:string, verbose:boolean = false) {
|
|
25
|
+
this.dataBaseFile = dataBaseFile;
|
|
26
|
+
this.db = new sqlite(dataBaseFile, {verbose: verbose ? console.log : null});
|
|
27
27
|
this.table()
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
table() {
|
|
31
|
-
|
|
31
|
+
const builder = new SqliteTableBuilder(
|
|
32
|
+
this.dataBaseFile,
|
|
33
|
+
'roles',
|
|
34
|
+
tableFields,
|
|
35
|
+
false);
|
|
36
|
+
builder.build('id')
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
normalizeData(roleData: IRoleBase){
|
|
@@ -52,7 +57,7 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
this.normalizeData(roleData)
|
|
55
|
-
|
|
60
|
+
roleData.createdAt = (new Date().toISOString())
|
|
56
61
|
|
|
57
62
|
const fields = Object.keys(roleData)
|
|
58
63
|
.map(field => `${field}`)
|
|
@@ -62,9 +67,6 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
62
67
|
.map(field => `@${field}`)
|
|
63
68
|
.join(', ');
|
|
64
69
|
|
|
65
|
-
/* console.log("fields", fields)
|
|
66
|
-
console.log("values",values)
|
|
67
|
-
console.log("userData",roleData)*/
|
|
68
70
|
|
|
69
71
|
const stmt = this.db.prepare(`INSERT INTO roles (${fields}) VALUES (${values})`);
|
|
70
72
|
stmt.run(roleData)
|
|
@@ -80,12 +82,18 @@ class RoleSqliteRepository implements IRoleRepository{
|
|
|
80
82
|
async update(id: string, roleData: IRoleBase): Promise<IRole> {
|
|
81
83
|
try{
|
|
82
84
|
this.normalizeData(roleData)
|
|
85
|
+
roleData.updatedAt = (new Date().toISOString())
|
|
86
|
+
|
|
83
87
|
const setClauses = Object.keys(roleData)
|
|
84
88
|
.map(field => `${field} = @${field}`)
|
|
85
89
|
.join(', ');
|
|
90
|
+
|
|
86
91
|
roleData.id = id
|
|
92
|
+
|
|
87
93
|
const stmt = this.db.prepare( `UPDATE roles SET ${setClauses} WHERE id = @id `);
|
|
94
|
+
|
|
88
95
|
stmt.run(roleData);
|
|
96
|
+
|
|
89
97
|
return this.findById(id)
|
|
90
98
|
}catch (e){
|
|
91
99
|
console.log(e)
|