Skip to content

Commit 204248b

Browse files
committed
Add SAML user registration support and update user entity
- Introduced SAML user registration endpoint in SaasController. - Added SAML properties to UserEntity and RegisterUserDs. - Updated IUserRepository and user repository implementation to handle SAML. - Created SaasSAMLUserRegisterDS for SAML registration data structure. - Implemented SaaSRegisterUserWIthSamlUseCase for user registration logic. - Updated Docker Compose to include Keycloak for SAML integration. - Added migration to include SAML properties in the user table.
1 parent f1f79a4 commit 204248b

13 files changed

Lines changed: 211 additions & 1 deletion

backend/src/common/data-injection.tokens.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export enum UseCaseType {
108108
SAAS_GET_USERS_COUNT_IN_COMPANY = 'SAAS_GET_USERS_COUNT_IN_COMPANY',
109109
FREEZE_CONNECTIONS_IN_COMPANY = 'FREEZE_CONNECTIONS_IN_COMPANY',
110110
UNFREEZE_CONNECTIONS_IN_COMPANY = 'UNFREEZE_CONNECTIONS_IN_COMPANY',
111+
SAAS_REGISTER_USER_WITH_SAML = 'SAAS_REGISTER_USER_WITH_SAML',
111112

112113
INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
113114
VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',

backend/src/entities/user/application/data-structures/register-user-ds.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export class RegisterUserDs {
77
isActive: boolean;
88
name: string;
99
role?: UserRoleEnum;
10+
samlNameId?: string;
1011
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export enum ExternalRegistrationProviderEnum {
22
GOOGLE = 'GOOGLE',
33
GITHUB = 'GITHUB',
4+
SAML = 'SAML',
45
}

backend/src/entities/user/repository/user-custom-repository-extension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const userCustomRepositoryExtension: IUserRepository = {
4747
async findOneUserByEmail(
4848
email: string,
4949
externalRegistrationProvider: ExternalRegistrationProviderEnum = null,
50+
samlNameId: string = null,
5051
): Promise<UserEntity | null> {
5152
const userQb = this.createQueryBuilder('user').where('user.email = :userEmail', {
5253
userEmail: email?.toLowerCase(),
@@ -56,6 +57,9 @@ export const userCustomRepositoryExtension: IUserRepository = {
5657
externalRegistrationProvider: externalRegistrationProvider,
5758
});
5859
}
60+
if (samlNameId && externalRegistrationProvider === ExternalRegistrationProviderEnum.SAML) {
61+
userQb.andWhere('user.samlNameId = :samlNameId', { samlNameId: samlNameId });
62+
}
5963
return userQb.getOne();
6064
},
6165

backend/src/entities/user/repository/user.repository.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface IUserRepository {
1313
findOneUserByEmail(
1414
email: string,
1515
externalRegistrationProvider?: ExternalRegistrationProviderEnum,
16+
samlNameId?: string,
1617
): Promise<UserEntity>;
1718

1819
findUserWithConnections(userId: string): Promise<UserEntity>;

backend/src/entities/user/user.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ export class UserEntity {
136136
})
137137
externalRegistrationProvider: ExternalRegistrationProviderEnum;
138138

139+
@Column({ default: null })
140+
samlNameId: string;
141+
139142
@Column({ default: true })
140143
showTestConnections: boolean;
141144

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
3+
export class SaasSAMLUserRegisterDS {
4+
@ApiProperty()
5+
email: string;
6+
7+
@ApiProperty()
8+
name: string;
9+
10+
@ApiProperty()
11+
companyId: string;
12+
13+
@ApiProperty()
14+
samlConfigId: string;
15+
16+
@ApiProperty()
17+
samlNameId: string;
18+
19+
@ApiProperty({ required: false })
20+
samlAttributes?: Record<string, any>;
21+
}

backend/src/microservices/saas-microservice/saas.controller.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import { SaasUsualUserRegisterDS } from '../../entities/user/application/data-st
66
import { FoundUserDto } from '../../entities/user/dto/found-user.dto.js';
77
import { ExternalRegistrationProviderEnum } from '../../entities/user/enums/external-registration-provider.enum.js';
88
import { UserEntity } from '../../entities/user/user.entity.js';
9+
import { InTransactionEnum } from '../../enums/in-transaction.enum.js';
910
import { SentryInterceptor } from '../../interceptors/sentry.interceptor.js';
1011
import { SuccessResponse } from './data-structures/common-responce.ds.js';
1112
import { RegisterCompanyWebhookDS } from './data-structures/register-company.ds.js';
1213
import { RegisteredCompanyDS } from './data-structures/registered-company.ds.js';
1314
import { SaasRegisterUserWithGithub } from './data-structures/saas-register-user-with-github.js';
15+
import { SaasSAMLUserRegisterDS } from './data-structures/saas-saml-user-register.ds.js';
1416
import { SaasRegisterUserWithGoogleDS } from './data-structures/sass-register-user-with-google.js';
1517
import {
1618
ICompanyRegistration,
@@ -23,9 +25,9 @@ import {
2325
ISaaSGetUsersCountInCompany,
2426
ISaasGetUsersInfosByEmail,
2527
ISaasRegisterUser,
28+
ISaasSAMLRegisterUser,
2629
ISuspendUsers,
2730
} from './use-cases/saas-use-cases.interface.js';
28-
import { InTransactionEnum } from '../../enums/in-transaction.enum.js';
2931

3032
@UseInterceptors(SentryInterceptor)
3133
@Controller('saas')
@@ -48,6 +50,8 @@ export class SaasController {
4850
private readonly loginUserWithGoogleUseCase: ILoginUserWithGoogle,
4951
@Inject(UseCaseType.SAAS_LOGIN_USER_WITH_GITHUB)
5052
private readonly loginUserWithGithubUseCase: ILoginUserWithGitHub,
53+
@Inject(UseCaseType.SAAS_REGISTER_USER_WITH_SAML)
54+
private readonly registerUserWithSamlUseCase: ISaasSAMLRegisterUser,
5155
@Inject(UseCaseType.SAAS_SUSPEND_USERS)
5256
private readonly suspendUsersUseCase: ISuspendUsers,
5357
@Inject(UseCaseType.SAAS_GET_COMPANY_INFO_BY_USER_ID)
@@ -203,4 +207,28 @@ export class SaasController {
203207
async unfreezeConnectionsInCompany(@Body('companyIds') companyIds: Array<string>) {
204208
return await this.unfreezeConnectionsInCompanyUseCase.execute({ companyIds });
205209
}
210+
211+
@ApiOperation({ summary: 'Register user with SAML' })
212+
@ApiBody({ type: SaasSAMLUserRegisterDS })
213+
@ApiResponse({
214+
status: 201,
215+
})
216+
@Post('user/saml/login')
217+
async registerUserWithSaml(
218+
@Body('email') email: string,
219+
@Body('name') name: string,
220+
@Body('companyId') companyId: string,
221+
@Body('samlConfigId') samlConfigId: string,
222+
@Body('samlNameId') samlNameId: string,
223+
@Body('samlAttributes') samlAttributes: Record<string, any>,
224+
): Promise<UserEntity> {
225+
return await this.registerUserWithSamlUseCase.execute({
226+
email,
227+
name,
228+
companyId,
229+
samlConfigId,
230+
samlNameId,
231+
samlAttributes
232+
});
233+
}
206234
}

backend/src/microservices/saas-microservice/saas.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { SaasUsualRegisterUseCase } from './use-cases/saas-usual-register-user.u
1515
import { SuspendUsersUseCase } from './use-cases/suspend-users.use.case.js';
1616
import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connections-in-company-use.case.js';
1717
import { SaasRegisterDemoUserAccountUseCase } from './use-cases/register-demo-user-account.use.case.js';
18+
import { SaaSRegisterUserWIthSamlUseCase } from './use-cases/register-user-with-saml-use.case.js';
1819

1920
@Module({
2021
imports: [],
@@ -71,6 +72,10 @@ import { SaasRegisterDemoUserAccountUseCase } from './use-cases/register-demo-us
7172
provide: UseCaseType.SAAS_DEMO_USER_REGISTRATION,
7273
useClass: SaasRegisterDemoUserAccountUseCase,
7374
},
75+
{
76+
provide: UseCaseType.SAAS_REGISTER_USER_WITH_SAML,
77+
useClass: SaaSRegisterUserWIthSamlUseCase,
78+
},
7479
],
7580
controllers: [SaasController],
7681
exports: [],
@@ -91,6 +96,7 @@ export class SaasModule {
9196
{ path: 'saas/company/:companyId/users/count', method: RequestMethod.GET },
9297
{ path: 'saas/company/freeze-connections', method: RequestMethod.PUT },
9398
{ path: 'saas/company/unfreeze-connections', method: RequestMethod.PUT },
99+
{ path: 'saas/user/saml/login', method: RequestMethod.POST },
94100
);
95101
}
96102
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { Inject, Injectable } from '@nestjs/common';
2+
import AbstractUseCase from '../../../common/abstract-use.case.js';
3+
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
4+
import { BaseType } from '../../../common/data-injection.tokens.js';
5+
import { ConnectionEntity } from '../../../entities/connection/connection.entity.js';
6+
import { GroupEntity } from '../../../entities/group/group.entity.js';
7+
import { PermissionEntity } from '../../../entities/permission/permission.entity.js';
8+
import { RegisterUserDs } from '../../../entities/user/application/data-structures/register-user-ds.js';
9+
import { ExternalRegistrationProviderEnum } from '../../../entities/user/enums/external-registration-provider.enum.js';
10+
import { UserEntity } from '../../../entities/user/user.entity.js';
11+
import { buildConnectionEntitiesFromTestDtos } from '../../../entities/user/utils/build-connection-entities-from-test-dtos.js';
12+
import { buildDefaultAdminGroups } from '../../../entities/user/utils/build-default-admin-groups.js';
13+
import { buildDefaultAdminPermissions } from '../../../entities/user/utils/build-default-admin-permissions.js';
14+
import { Constants } from '../../../helpers/constants/constants.js';
15+
import { SaasSAMLUserRegisterDS } from '../data-structures/saas-saml-user-register.ds.js';
16+
17+
@Injectable()
18+
export class SaaSRegisterUserWIthSamlUseCase extends AbstractUseCase<SaasSAMLUserRegisterDS, UserEntity> {
19+
constructor(
20+
@Inject(BaseType.GLOBAL_DB_CONTEXT)
21+
protected _dbContext: IGlobalDatabaseContext,
22+
) {
23+
super();
24+
}
25+
26+
public async implementation(inputData: SaasSAMLUserRegisterDS): Promise<UserEntity> {
27+
const { email, name, samlNameId } = inputData;
28+
const foundUser = await this._dbContext.userRepository.findOneUserByEmail(
29+
email,
30+
ExternalRegistrationProviderEnum.SAML,
31+
samlNameId,
32+
);
33+
if (foundUser) {
34+
return foundUser;
35+
}
36+
37+
const userData: RegisterUserDs = {
38+
email: email,
39+
password: null,
40+
isActive: true,
41+
name: name ? name : null,
42+
gclidValue: null,
43+
};
44+
45+
const savedUser = await this._dbContext.userRepository.saveRegisteringUser(
46+
userData,
47+
ExternalRegistrationProviderEnum.SAML,
48+
);
49+
50+
const testConnections = Constants.getTestConnectionsArr();
51+
const testConnectionsEntities = buildConnectionEntitiesFromTestDtos(testConnections);
52+
const createdTestConnections = await Promise.all(
53+
testConnectionsEntities.map(async (connection): Promise<ConnectionEntity> => {
54+
connection.author = savedUser;
55+
return await this._dbContext.connectionRepository.saveNewConnection(connection);
56+
}),
57+
);
58+
const testGroupsEntities = buildDefaultAdminGroups(savedUser, createdTestConnections);
59+
const createdTestGroups = await Promise.all(
60+
testGroupsEntities.map(async (group: GroupEntity) => {
61+
return await this._dbContext.groupRepository.saveNewOrUpdatedGroup(group);
62+
}),
63+
);
64+
const testPermissionsEntities = buildDefaultAdminPermissions(createdTestGroups);
65+
await Promise.all(
66+
testPermissionsEntities.map(async (permission: PermissionEntity) => {
67+
await this._dbContext.permissionRepository.saveNewOrUpdatedPermission(permission);
68+
}),
69+
);
70+
71+
return savedUser;
72+
}
73+
}

0 commit comments

Comments
 (0)