Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/src/common/data-injection.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export enum UseCaseType {
SAAS_GET_USERS_COUNT_IN_COMPANY = 'SAAS_GET_USERS_COUNT_IN_COMPANY',
FREEZE_CONNECTIONS_IN_COMPANY = 'FREEZE_CONNECTIONS_IN_COMPANY',
UNFREEZE_CONNECTIONS_IN_COMPANY = 'UNFREEZE_CONNECTIONS_IN_COMPANY',
SAAS_REGISTER_USER_WITH_SAML = 'SAAS_REGISTER_USER_WITH_SAML',

INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export class RegisterUserDs {
isActive: boolean;
name: string;
role?: UserRoleEnum;
samlNameId?: string;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum ExternalRegistrationProviderEnum {
GOOGLE = 'GOOGLE',
GITHUB = 'GITHUB',
SAML = 'SAML',
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const userCustomRepositoryExtension: IUserRepository = {
async findOneUserByEmail(
email: string,
externalRegistrationProvider: ExternalRegistrationProviderEnum = null,
samlNameId: string = null,
): Promise<UserEntity | null> {
const userQb = this.createQueryBuilder('user').where('user.email = :userEmail', {
userEmail: email?.toLowerCase(),
Expand All @@ -56,6 +57,9 @@ export const userCustomRepositoryExtension: IUserRepository = {
externalRegistrationProvider: externalRegistrationProvider,
});
}
if (samlNameId && externalRegistrationProvider === ExternalRegistrationProviderEnum.SAML) {
userQb.andWhere('user.samlNameId = :samlNameId', { samlNameId: samlNameId });
}
return userQb.getOne();
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface IUserRepository {
findOneUserByEmail(
email: string,
externalRegistrationProvider?: ExternalRegistrationProviderEnum,
samlNameId?: string,
): Promise<UserEntity>;

findUserWithConnections(userId: string): Promise<UserEntity>;
Expand Down
3 changes: 3 additions & 0 deletions backend/src/entities/user/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ export class UserEntity {
})
externalRegistrationProvider: ExternalRegistrationProviderEnum;

@Column({ default: null })
samlNameId: string;

@Column({ default: true })
showTestConnections: boolean;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';

export class SaasSAMLUserRegisterDS {
@ApiProperty()
email: string;

@ApiProperty()
name: string;

@ApiProperty()
companyId: string;

@ApiProperty()
samlConfigId: string;

@ApiProperty()
samlNameId: string;

@ApiProperty({ required: false })
samlAttributes?: Record<string, any>;
}
30 changes: 29 additions & 1 deletion backend/src/microservices/saas-microservice/saas.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { SaasUsualUserRegisterDS } from '../../entities/user/application/data-st
import { FoundUserDto } from '../../entities/user/dto/found-user.dto.js';
import { ExternalRegistrationProviderEnum } from '../../entities/user/enums/external-registration-provider.enum.js';
import { UserEntity } from '../../entities/user/user.entity.js';
import { InTransactionEnum } from '../../enums/in-transaction.enum.js';
import { SentryInterceptor } from '../../interceptors/sentry.interceptor.js';
import { SuccessResponse } from './data-structures/common-responce.ds.js';
import { RegisterCompanyWebhookDS } from './data-structures/register-company.ds.js';
import { RegisteredCompanyDS } from './data-structures/registered-company.ds.js';
import { SaasRegisterUserWithGithub } from './data-structures/saas-register-user-with-github.js';
import { SaasSAMLUserRegisterDS } from './data-structures/saas-saml-user-register.ds.js';
import { SaasRegisterUserWithGoogleDS } from './data-structures/sass-register-user-with-google.js';
import {
ICompanyRegistration,
Expand All @@ -23,9 +25,9 @@ import {
ISaaSGetUsersCountInCompany,
ISaasGetUsersInfosByEmail,
ISaasRegisterUser,
ISaasSAMLRegisterUser,
ISuspendUsers,
} from './use-cases/saas-use-cases.interface.js';
import { InTransactionEnum } from '../../enums/in-transaction.enum.js';

@UseInterceptors(SentryInterceptor)
@Controller('saas')
Expand All @@ -48,6 +50,8 @@ export class SaasController {
private readonly loginUserWithGoogleUseCase: ILoginUserWithGoogle,
@Inject(UseCaseType.SAAS_LOGIN_USER_WITH_GITHUB)
private readonly loginUserWithGithubUseCase: ILoginUserWithGitHub,
@Inject(UseCaseType.SAAS_REGISTER_USER_WITH_SAML)
private readonly registerUserWithSamlUseCase: ISaasSAMLRegisterUser,
@Inject(UseCaseType.SAAS_SUSPEND_USERS)
private readonly suspendUsersUseCase: ISuspendUsers,
@Inject(UseCaseType.SAAS_GET_COMPANY_INFO_BY_USER_ID)
Expand Down Expand Up @@ -203,4 +207,28 @@ export class SaasController {
async unfreezeConnectionsInCompany(@Body('companyIds') companyIds: Array<string>) {
return await this.unfreezeConnectionsInCompanyUseCase.execute({ companyIds });
}

@ApiOperation({ summary: 'Register user with SAML' })
@ApiBody({ type: SaasSAMLUserRegisterDS })
@ApiResponse({
status: 201,
})
@Post('user/saml/login')
async registerUserWithSaml(
@Body('email') email: string,
@Body('name') name: string,
@Body('companyId') companyId: string,
@Body('samlConfigId') samlConfigId: string,
@Body('samlNameId') samlNameId: string,
@Body('samlAttributes') samlAttributes: Record<string, any>,
): Promise<UserEntity> {
return await this.registerUserWithSamlUseCase.execute({
email,
name,
companyId,
samlConfigId,
samlNameId,
samlAttributes
});
}
}
6 changes: 6 additions & 0 deletions backend/src/microservices/saas-microservice/saas.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SaasUsualRegisterUseCase } from './use-cases/saas-usual-register-user.u
import { SuspendUsersUseCase } from './use-cases/suspend-users.use.case.js';
import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connections-in-company-use.case.js';
import { SaasRegisterDemoUserAccountUseCase } from './use-cases/register-demo-user-account.use.case.js';
import { SaaSRegisterUserWIthSamlUseCase } from './use-cases/register-user-with-saml-use.case.js';

@Module({
imports: [],
Expand Down Expand Up @@ -71,6 +72,10 @@ import { SaasRegisterDemoUserAccountUseCase } from './use-cases/register-demo-us
provide: UseCaseType.SAAS_DEMO_USER_REGISTRATION,
useClass: SaasRegisterDemoUserAccountUseCase,
},
{
provide: UseCaseType.SAAS_REGISTER_USER_WITH_SAML,
useClass: SaaSRegisterUserWIthSamlUseCase,
},
],
controllers: [SaasController],
exports: [],
Expand All @@ -91,6 +96,7 @@ export class SaasModule {
{ path: 'saas/company/:companyId/users/count', method: RequestMethod.GET },
{ path: 'saas/company/freeze-connections', method: RequestMethod.PUT },
{ path: 'saas/company/unfreeze-connections', method: RequestMethod.PUT },
{ path: 'saas/user/saml/login', method: RequestMethod.POST },
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import AbstractUseCase from '../../../common/abstract-use.case.js';
import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js';
import { BaseType } from '../../../common/data-injection.tokens.js';
import { RegisterUserDs } from '../../../entities/user/application/data-structures/register-user-ds.js';
import { ExternalRegistrationProviderEnum } from '../../../entities/user/enums/external-registration-provider.enum.js';
import { UserRoleEnum } from '../../../entities/user/enums/user-role.enum.js';
import { UserEntity } from '../../../entities/user/user.entity.js';
import { Messages } from '../../../exceptions/text/messages.js';
import { SaasSAMLUserRegisterDS } from '../data-structures/saas-saml-user-register.ds.js';

@Injectable()
export class SaaSRegisterUserWIthSamlUseCase extends AbstractUseCase<SaasSAMLUserRegisterDS, UserEntity> {
constructor(
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
) {
super();
}

public async implementation(inputData: SaasSAMLUserRegisterDS): Promise<UserEntity> {
const { email, name, samlNameId, companyId } = inputData;
const foundUser = await this._dbContext.userRepository.findOneUserByEmail(
email,
ExternalRegistrationProviderEnum.SAML,
samlNameId,
);
if (foundUser) {
return foundUser;
}

const userData: RegisterUserDs = {
email: email,
password: null,
isActive: true,
name: name ? name : null,
gclidValue: null,
};

const savedUser = await this._dbContext.userRepository.saveRegisteringUser(
userData,
ExternalRegistrationProviderEnum.SAML,
);

const foundCompanyInfo = await this._dbContext.companyInfoRepository.findOne({ where: { id: companyId } });
if (!foundCompanyInfo) {
throw new NotFoundException(Messages.COMPANY_NOT_FOUND);
}

savedUser.company = foundCompanyInfo;
savedUser.samlNameId = samlNameId;
savedUser.role = UserRoleEnum.USER;

return await this._dbContext.userRepository.saveUserEntity(savedUser);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { GetUsersInfosByEmailDS } from '../data-structures/get-users-infos-by-em
import { RegisterCompanyWebhookDS } from '../data-structures/register-company.ds.js';
import { RegisteredCompanyDS } from '../data-structures/registered-company.ds.js';
import { SaasRegisterUserWithGithub } from '../data-structures/saas-register-user-with-github.js';
import { SaasSAMLUserRegisterDS } from '../data-structures/saas-saml-user-register.ds.js';
import { SaasRegisterUserWithGoogleDS } from '../data-structures/sass-register-user-with-google.js';
import { SuspendUsersDS } from '../data-structures/suspend-users.ds.js';

Expand Down Expand Up @@ -57,3 +58,7 @@ export interface ISaaSGetUsersCountInCompany {
export interface IFreezeConnectionsInCompany {
execute(inputData: FreezeConnectionsInCompanyDS): Promise<SuccessResponse>;
}

export interface ISaasSAMLRegisterUser {
execute(userData: SaasSAMLUserRegisterDS): Promise<UserEntity>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddSamlPropertiesToUserEntity1748002305012 implements MigrationInterface {
name = 'AddSamlPropertiesToUserEntity1748002305012';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user" ADD "samlNameId" character varying`);
await queryRunner.query(
`ALTER TYPE "public"."user_externalregistrationprovider_enum" RENAME TO "user_externalregistrationprovider_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."user_externalregistrationprovider_enum" AS ENUM('GOOGLE', 'GITHUB', 'SAML')`,
);
await queryRunner.query(
`ALTER TABLE "user" ALTER COLUMN "externalRegistrationProvider" TYPE "public"."user_externalregistrationprovider_enum" USING "externalRegistrationProvider"::"text"::"public"."user_externalregistrationprovider_enum"`,
);
await queryRunner.query(`DROP TYPE "public"."user_externalregistrationprovider_enum_old"`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."user_externalregistrationprovider_enum_old" AS ENUM('GOOGLE', 'GITHUB')`,
);
await queryRunner.query(
`ALTER TABLE "user" ALTER COLUMN "externalRegistrationProvider" TYPE "public"."user_externalregistrationprovider_enum_old" USING "externalRegistrationProvider"::"text"::"public"."user_externalregistrationprovider_enum_old"`,
);
await queryRunner.query(`DROP TYPE "public"."user_externalregistrationprovider_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."user_externalregistrationprovider_enum_old" RENAME TO "user_externalregistrationprovider_enum"`,
);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "samlNameId"`);
}
}
Loading