@forklaunch/implementation-iam-base 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/LICENSE +21 -0
- package/__test__/schemaEquality.test.ts +293 -0
- package/index.ts +8 -0
- package/package.json +48 -0
- package/schemas/organization.schema.ts +28 -0
- package/schemas/permission.schema.ts +28 -0
- package/schemas/role.schema.ts +28 -0
- package/schemas/typebox/organization.schema.ts +49 -0
- package/schemas/typebox/permission.schema.ts +30 -0
- package/schemas/typebox/role.schema.ts +36 -0
- package/schemas/typebox/user.schema.ts +52 -0
- package/schemas/user.schema.ts +28 -0
- package/schemas/zod/organization.schema.ts +49 -0
- package/schemas/zod/permission.schema.ts +30 -0
- package/schemas/zod/role.schema.ts +36 -0
- package/schemas/zod/user.schema.ts +52 -0
- package/services/organization.service.ts +146 -0
- package/services/permission.service.ts +349 -0
- package/services/role.service.ts +182 -0
- package/services/user.service.ts +225 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateRoleDto,
|
|
3
|
+
RoleDto,
|
|
4
|
+
RoleService,
|
|
5
|
+
UpdateRoleDto
|
|
6
|
+
} from '@forklaunch/interfaces-iam';
|
|
7
|
+
import {
|
|
8
|
+
MetricsDefinition,
|
|
9
|
+
OpenTelemetryCollector
|
|
10
|
+
} from '@forklaunch/core/http';
|
|
11
|
+
import { EntityManager } from '@mikro-orm/core';
|
|
12
|
+
|
|
13
|
+
import { IdDto, IdsDto, InstanceTypeRecord } from '@forklaunch/common';
|
|
14
|
+
import {
|
|
15
|
+
InternalDtoMapper,
|
|
16
|
+
RequestDtoMapperConstructor,
|
|
17
|
+
ResponseDtoMapperConstructor,
|
|
18
|
+
transformIntoInternalDtoMapper
|
|
19
|
+
} from '@forklaunch/core/dtoMapper';
|
|
20
|
+
import { MapNestedDtoArraysToCollections } from '@forklaunch/core/services';
|
|
21
|
+
import { AnySchemaValidator } from '@forklaunch/validator';
|
|
22
|
+
|
|
23
|
+
export class BaseRoleService<
|
|
24
|
+
SchemaValidator extends AnySchemaValidator,
|
|
25
|
+
Metrics extends MetricsDefinition = MetricsDefinition,
|
|
26
|
+
Dto extends {
|
|
27
|
+
RoleDtoMapper: RoleDto;
|
|
28
|
+
CreateRoleDtoMapper: CreateRoleDto;
|
|
29
|
+
UpdateRoleDtoMapper: UpdateRoleDto;
|
|
30
|
+
} = {
|
|
31
|
+
RoleDtoMapper: RoleDto;
|
|
32
|
+
CreateRoleDtoMapper: CreateRoleDto;
|
|
33
|
+
UpdateRoleDtoMapper: UpdateRoleDto;
|
|
34
|
+
},
|
|
35
|
+
Entities extends {
|
|
36
|
+
RoleDtoMapper: MapNestedDtoArraysToCollections<RoleDto, 'permissions'>;
|
|
37
|
+
CreateRoleDtoMapper: MapNestedDtoArraysToCollections<
|
|
38
|
+
RoleDto,
|
|
39
|
+
'permissions'
|
|
40
|
+
>;
|
|
41
|
+
UpdateRoleDtoMapper: MapNestedDtoArraysToCollections<
|
|
42
|
+
RoleDto,
|
|
43
|
+
'permissions'
|
|
44
|
+
>;
|
|
45
|
+
} = {
|
|
46
|
+
RoleDtoMapper: MapNestedDtoArraysToCollections<RoleDto, 'permissions'>;
|
|
47
|
+
CreateRoleDtoMapper: MapNestedDtoArraysToCollections<
|
|
48
|
+
RoleDto,
|
|
49
|
+
'permissions'
|
|
50
|
+
>;
|
|
51
|
+
UpdateRoleDtoMapper: MapNestedDtoArraysToCollections<
|
|
52
|
+
RoleDto,
|
|
53
|
+
'permissions'
|
|
54
|
+
>;
|
|
55
|
+
}
|
|
56
|
+
> implements RoleService
|
|
57
|
+
{
|
|
58
|
+
#dtoMappers: InternalDtoMapper<
|
|
59
|
+
InstanceTypeRecord<typeof this.dtoMappers>,
|
|
60
|
+
Entities,
|
|
61
|
+
Dto
|
|
62
|
+
>;
|
|
63
|
+
|
|
64
|
+
constructor(
|
|
65
|
+
public em: EntityManager,
|
|
66
|
+
protected openTelemetryCollector: OpenTelemetryCollector<Metrics>,
|
|
67
|
+
protected schemaValidator: SchemaValidator,
|
|
68
|
+
protected dtoMappers: {
|
|
69
|
+
RoleDtoMapper: ResponseDtoMapperConstructor<
|
|
70
|
+
SchemaValidator,
|
|
71
|
+
Dto['RoleDtoMapper'],
|
|
72
|
+
Entities['RoleDtoMapper']
|
|
73
|
+
>;
|
|
74
|
+
CreateRoleDtoMapper: RequestDtoMapperConstructor<
|
|
75
|
+
SchemaValidator,
|
|
76
|
+
Dto['CreateRoleDtoMapper'],
|
|
77
|
+
Entities['CreateRoleDtoMapper']
|
|
78
|
+
>;
|
|
79
|
+
UpdateRoleDtoMapper: RequestDtoMapperConstructor<
|
|
80
|
+
SchemaValidator,
|
|
81
|
+
Dto['UpdateRoleDtoMapper'],
|
|
82
|
+
Entities['UpdateRoleDtoMapper']
|
|
83
|
+
>;
|
|
84
|
+
}
|
|
85
|
+
) {
|
|
86
|
+
this.#dtoMappers = transformIntoInternalDtoMapper(
|
|
87
|
+
dtoMappers,
|
|
88
|
+
schemaValidator
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async createRole(
|
|
93
|
+
roleDto: Dto['CreateRoleDtoMapper'],
|
|
94
|
+
em?: EntityManager
|
|
95
|
+
): Promise<Dto['RoleDtoMapper']> {
|
|
96
|
+
// TODO: Think about removing static method here, since we need specific args
|
|
97
|
+
const role =
|
|
98
|
+
this.#dtoMappers.CreateRoleDtoMapper.deserializeDtoToEntity(roleDto);
|
|
99
|
+
await (em ?? this.em).transactional((em) => em.persist(role));
|
|
100
|
+
return this.#dtoMappers.RoleDtoMapper.serializeEntityToDto(
|
|
101
|
+
role as Entities['RoleDtoMapper']
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async createBatchRoles(
|
|
106
|
+
roleDtos: Dto['CreateRoleDtoMapper'][],
|
|
107
|
+
em?: EntityManager
|
|
108
|
+
): Promise<Dto['RoleDtoMapper'][]> {
|
|
109
|
+
const roles = await Promise.all(
|
|
110
|
+
roleDtos.map(async (roleDto) =>
|
|
111
|
+
this.#dtoMappers.CreateRoleDtoMapper.deserializeDtoToEntity(roleDto)
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
await (em ?? this.em).transactional((em) => em.persist(roles));
|
|
115
|
+
return roles.map((role) =>
|
|
116
|
+
this.#dtoMappers.RoleDtoMapper.serializeEntityToDto(
|
|
117
|
+
role as Entities['RoleDtoMapper']
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getRole(idDto: IdDto, em?: EntityManager): Promise<RoleDto> {
|
|
123
|
+
const role = await (em ?? this.em).findOneOrFail('Role', idDto, {
|
|
124
|
+
populate: ['id', '*']
|
|
125
|
+
});
|
|
126
|
+
return this.#dtoMappers.RoleDtoMapper.serializeEntityToDto(
|
|
127
|
+
role as Entities['RoleDtoMapper']
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async getBatchRoles(idsDto: IdsDto, em?: EntityManager): Promise<RoleDto[]> {
|
|
132
|
+
return (
|
|
133
|
+
await (em ?? this.em).find('Role', idsDto, {
|
|
134
|
+
populate: ['id', '*']
|
|
135
|
+
})
|
|
136
|
+
).map((role) =>
|
|
137
|
+
this.#dtoMappers.RoleDtoMapper.serializeEntityToDto(
|
|
138
|
+
role as Entities['RoleDtoMapper']
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async updateRole(
|
|
144
|
+
roleDto: Dto['UpdateRoleDtoMapper'],
|
|
145
|
+
em?: EntityManager
|
|
146
|
+
): Promise<Dto['RoleDtoMapper']> {
|
|
147
|
+
let role = this.#dtoMappers.UpdateRoleDtoMapper.deserializeDtoToEntity(
|
|
148
|
+
roleDto
|
|
149
|
+
) as Entities['RoleDtoMapper'];
|
|
150
|
+
await (em ?? this.em).transactional(async (em) => {
|
|
151
|
+
role = (await em.upsert('Role', role)) as Entities['RoleDtoMapper'];
|
|
152
|
+
});
|
|
153
|
+
return this.#dtoMappers.RoleDtoMapper.serializeEntityToDto(role);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async updateBatchRoles(
|
|
157
|
+
roleDtos: Dto['UpdateRoleDtoMapper'][],
|
|
158
|
+
em?: EntityManager
|
|
159
|
+
): Promise<Dto['RoleDtoMapper'][]> {
|
|
160
|
+
let roles = await Promise.all(
|
|
161
|
+
roleDtos.map(async (roleDto) =>
|
|
162
|
+
this.#dtoMappers.UpdateRoleDtoMapper.deserializeDtoToEntity(roleDto)
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
await (em ?? this.em).transactional(async (em) => {
|
|
166
|
+
roles = await em.upsertMany('Role', roles);
|
|
167
|
+
});
|
|
168
|
+
return roles.map((role) =>
|
|
169
|
+
this.#dtoMappers.RoleDtoMapper.serializeEntityToDto(
|
|
170
|
+
role as Entities['RoleDtoMapper']
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async deleteRole(idDto: IdDto, em?: EntityManager): Promise<void> {
|
|
176
|
+
await (em ?? this.em).nativeDelete('Role', idDto);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async deleteBatchRoles(idsDto: IdsDto, em?: EntityManager): Promise<void> {
|
|
180
|
+
await (em ?? this.em).nativeDelete('Role', { id: { $in: idsDto.ids } });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateUserDto,
|
|
3
|
+
RoleService,
|
|
4
|
+
UpdateUserDto,
|
|
5
|
+
UserDto,
|
|
6
|
+
UserService
|
|
7
|
+
} from '@forklaunch/interfaces-iam';
|
|
8
|
+
import {
|
|
9
|
+
MetricsDefinition,
|
|
10
|
+
OpenTelemetryCollector
|
|
11
|
+
} from '@forklaunch/core/http';
|
|
12
|
+
|
|
13
|
+
import { OrganizationService } from '@forklaunch/interfaces-iam';
|
|
14
|
+
import { IdDto, IdsDto, InstanceTypeRecord } from '@forklaunch/common';
|
|
15
|
+
import {
|
|
16
|
+
InternalDtoMapper,
|
|
17
|
+
RequestDtoMapperConstructor,
|
|
18
|
+
ResponseDtoMapperConstructor,
|
|
19
|
+
transformIntoInternalDtoMapper
|
|
20
|
+
} from '@forklaunch/core/dtoMapper';
|
|
21
|
+
import { MapNestedDtoArraysToCollections } from '@forklaunch/core/services';
|
|
22
|
+
import { AnySchemaValidator } from '@forklaunch/validator';
|
|
23
|
+
import { EntityManager } from '@mikro-orm/core';
|
|
24
|
+
|
|
25
|
+
export class BaseUserService<
|
|
26
|
+
SchemaValidator extends AnySchemaValidator,
|
|
27
|
+
OrganizationStatus,
|
|
28
|
+
Metrics extends MetricsDefinition = MetricsDefinition,
|
|
29
|
+
Dto extends {
|
|
30
|
+
UserDtoMapper: UserDto;
|
|
31
|
+
CreateUserDtoMapper: CreateUserDto;
|
|
32
|
+
UpdateUserDtoMapper: UpdateUserDto;
|
|
33
|
+
} = {
|
|
34
|
+
UserDtoMapper: UserDto;
|
|
35
|
+
CreateUserDtoMapper: CreateUserDto;
|
|
36
|
+
UpdateUserDtoMapper: UpdateUserDto;
|
|
37
|
+
},
|
|
38
|
+
Entities extends {
|
|
39
|
+
UserDtoMapper: MapNestedDtoArraysToCollections<UserDto, 'roles'>;
|
|
40
|
+
CreateUserDtoMapper: MapNestedDtoArraysToCollections<UserDto, 'roles'>;
|
|
41
|
+
UpdateUserDtoMapper: MapNestedDtoArraysToCollections<UserDto, 'roles'>;
|
|
42
|
+
} = {
|
|
43
|
+
UserDtoMapper: MapNestedDtoArraysToCollections<UserDto, 'roles'>;
|
|
44
|
+
CreateUserDtoMapper: MapNestedDtoArraysToCollections<UserDto, 'roles'>;
|
|
45
|
+
UpdateUserDtoMapper: MapNestedDtoArraysToCollections<UserDto, 'roles'>;
|
|
46
|
+
}
|
|
47
|
+
> implements UserService
|
|
48
|
+
{
|
|
49
|
+
#dtoMappers: InternalDtoMapper<
|
|
50
|
+
InstanceTypeRecord<typeof this.dtoMappers>,
|
|
51
|
+
Entities,
|
|
52
|
+
Dto
|
|
53
|
+
>;
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
public em: EntityManager,
|
|
57
|
+
protected passwordEncryptionPublicKeyPath: string,
|
|
58
|
+
protected roleServiceFactory: () => RoleService,
|
|
59
|
+
protected organizationServiceFactory: () => OrganizationService<OrganizationStatus>,
|
|
60
|
+
protected openTelemetryCollector: OpenTelemetryCollector<Metrics>,
|
|
61
|
+
protected schemaValidator: SchemaValidator,
|
|
62
|
+
protected dtoMappers: {
|
|
63
|
+
UserDtoMapper: ResponseDtoMapperConstructor<
|
|
64
|
+
SchemaValidator,
|
|
65
|
+
Dto['UserDtoMapper'],
|
|
66
|
+
Entities['UserDtoMapper']
|
|
67
|
+
>;
|
|
68
|
+
CreateUserDtoMapper: RequestDtoMapperConstructor<
|
|
69
|
+
SchemaValidator,
|
|
70
|
+
Dto['CreateUserDtoMapper'],
|
|
71
|
+
Entities['CreateUserDtoMapper'],
|
|
72
|
+
(
|
|
73
|
+
dto: never,
|
|
74
|
+
passwordEncryptionPublicKeyPath: string
|
|
75
|
+
) => Entities['UpdateUserDtoMapper']
|
|
76
|
+
>;
|
|
77
|
+
UpdateUserDtoMapper: RequestDtoMapperConstructor<
|
|
78
|
+
SchemaValidator,
|
|
79
|
+
Dto['UpdateUserDtoMapper'],
|
|
80
|
+
Entities['UpdateUserDtoMapper'],
|
|
81
|
+
(
|
|
82
|
+
dto: never,
|
|
83
|
+
passwordEncryptionPublicKeyPath: string
|
|
84
|
+
) => Entities['UpdateUserDtoMapper']
|
|
85
|
+
>;
|
|
86
|
+
}
|
|
87
|
+
) {
|
|
88
|
+
this.#dtoMappers = transformIntoInternalDtoMapper(
|
|
89
|
+
dtoMappers,
|
|
90
|
+
schemaValidator
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async createUser(
|
|
95
|
+
userDto: Dto['CreateUserDtoMapper'],
|
|
96
|
+
em?: EntityManager
|
|
97
|
+
): Promise<Dto['UserDtoMapper']> {
|
|
98
|
+
const user =
|
|
99
|
+
await this.#dtoMappers.CreateUserDtoMapper.deserializeDtoToEntity(
|
|
100
|
+
userDto,
|
|
101
|
+
this.passwordEncryptionPublicKeyPath
|
|
102
|
+
);
|
|
103
|
+
((await em) ?? this.em).transactional(async (em) => {
|
|
104
|
+
await em.persist(user);
|
|
105
|
+
});
|
|
106
|
+
return this.#dtoMappers.UserDtoMapper.serializeEntityToDto(user);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async createBatchUsers(
|
|
110
|
+
userDtos: Dto['CreateUserDtoMapper'][],
|
|
111
|
+
em?: EntityManager
|
|
112
|
+
): Promise<Dto['UserDtoMapper'][]> {
|
|
113
|
+
const users = await Promise.all(
|
|
114
|
+
userDtos.map(async (createUserDto) =>
|
|
115
|
+
this.#dtoMappers.CreateUserDtoMapper.deserializeDtoToEntity(
|
|
116
|
+
createUserDto,
|
|
117
|
+
this.passwordEncryptionPublicKeyPath
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
await (em ?? this.em).transactional(async (em) => {
|
|
122
|
+
await em.persist(users);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return users.map((user) =>
|
|
126
|
+
this.#dtoMappers.UserDtoMapper.serializeEntityToDto(user)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getUser(
|
|
131
|
+
idDto: IdDto,
|
|
132
|
+
em?: EntityManager
|
|
133
|
+
): Promise<Dto['UserDtoMapper']> {
|
|
134
|
+
const user = await (em ?? this.em).findOneOrFail('User', idDto, {
|
|
135
|
+
populate: ['id', '*']
|
|
136
|
+
});
|
|
137
|
+
return this.#dtoMappers.UserDtoMapper.serializeEntityToDto(
|
|
138
|
+
user as Entities['UserDtoMapper']
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async getBatchUsers(
|
|
143
|
+
idsDto: IdsDto,
|
|
144
|
+
em?: EntityManager
|
|
145
|
+
): Promise<Dto['UserDtoMapper'][]> {
|
|
146
|
+
return (
|
|
147
|
+
await (em ?? this.em).find('User', idsDto, {
|
|
148
|
+
populate: ['id', '*']
|
|
149
|
+
})
|
|
150
|
+
).map((user) =>
|
|
151
|
+
this.#dtoMappers.UserDtoMapper.serializeEntityToDto(
|
|
152
|
+
user as Entities['UserDtoMapper']
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async updateUser(
|
|
158
|
+
userDto: Dto['UpdateUserDtoMapper'],
|
|
159
|
+
em?: EntityManager
|
|
160
|
+
): Promise<Dto['UserDtoMapper']> {
|
|
161
|
+
let user = this.#dtoMappers.UpdateUserDtoMapper.deserializeDtoToEntity(
|
|
162
|
+
userDto,
|
|
163
|
+
this.passwordEncryptionPublicKeyPath
|
|
164
|
+
);
|
|
165
|
+
await (em ?? this.em).transactional(async (localEm) => {
|
|
166
|
+
user = await localEm.upsert(user);
|
|
167
|
+
});
|
|
168
|
+
return this.#dtoMappers.UserDtoMapper.serializeEntityToDto(user);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async updateBatchUsers(
|
|
172
|
+
userDtos: UpdateUserDto[],
|
|
173
|
+
em?: EntityManager
|
|
174
|
+
): Promise<Dto['UserDtoMapper'][]> {
|
|
175
|
+
let users = await Promise.all(
|
|
176
|
+
userDtos.map(async (updateUserDto) =>
|
|
177
|
+
this.#dtoMappers.UpdateUserDtoMapper.deserializeDtoToEntity(
|
|
178
|
+
updateUserDto,
|
|
179
|
+
this.passwordEncryptionPublicKeyPath
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
await (em ?? this.em).transactional(async (localEm) => {
|
|
184
|
+
users = await localEm.upsertMany(users);
|
|
185
|
+
});
|
|
186
|
+
return users.map((user) =>
|
|
187
|
+
this.#dtoMappers.UserDtoMapper.serializeEntityToDto(user)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async deleteUser(idDto: IdDto, em?: EntityManager): Promise<void> {
|
|
192
|
+
const entityManager = em || this.em;
|
|
193
|
+
await entityManager.nativeDelete('User', idDto);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async deleteBatchUsers(idsDto: IdsDto, em?: EntityManager): Promise<void> {
|
|
197
|
+
const entityManager = em || this.em;
|
|
198
|
+
await entityManager.nativeDelete('User', idsDto);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async verifyHasRole(idDto: IdDto, roleId: string): Promise<void> {
|
|
202
|
+
const user = await this.getUser(idDto);
|
|
203
|
+
if (
|
|
204
|
+
user.roles.filter((role) => {
|
|
205
|
+
return roleId == role.id;
|
|
206
|
+
}).length === 0
|
|
207
|
+
) {
|
|
208
|
+
throw new Error(`User ${idDto.id} does not have role ${roleId}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async verifyHasPermission(idDto: IdDto, permissionId: string): Promise<void> {
|
|
213
|
+
const user = await this.getUser(idDto);
|
|
214
|
+
if (
|
|
215
|
+
user.roles
|
|
216
|
+
.map((role) => role.permissions.map((permission) => permission.id))
|
|
217
|
+
.flat()
|
|
218
|
+
.filter((id) => id == permissionId).length === 0
|
|
219
|
+
) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`User ${idDto.id} does not have permission ${permissionId}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|