Skip to content

Commit c86f66a

Browse files
guguclaude
andcommitted
feat: add hosted database password update webhook endpoint
Add POST /saas/connection/hosted/password endpoint that rocketadmin-saas calls when a hosted database password is reset, to update the stored connection credentials in the backend. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 333512e commit c86f66a

6 files changed

Lines changed: 106 additions & 5 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export enum UseCaseType {
116116
SAAS_REGISTER_USER_WITH_SAML = 'SAAS_REGISTER_USER_WITH_SAML',
117117
SAAS_CREATE_CONNECTION_FOR_HOSTED_DB = 'SAAS_CREATE_CONNECTION_FOR_HOSTED_DB',
118118
SAAS_DELETE_CONNECTION_FOR_HOSTED_DB = 'SAAS_DELETE_CONNECTION_FOR_HOSTED_DB',
119+
SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD = 'SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD',
119120

120121
INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
121122
VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP = 'VERIFY_INVITE_USER_IN_COMPANY_AND_CONNECTION_GROUP',
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
3+
4+
export class UpdateHostedConnectionPasswordDto {
5+
@ApiProperty({
6+
description: 'Company ID',
7+
example: '123e4567-e89b-12d3-a456-426614174000',
8+
})
9+
@IsNotEmpty()
10+
@IsString()
11+
@IsUUID()
12+
companyId: string;
13+
14+
@ApiProperty({
15+
description: 'Database name',
16+
example: 'my_database',
17+
})
18+
@IsNotEmpty()
19+
@IsString()
20+
databaseName: string;
21+
22+
@ApiProperty({
23+
description: 'New database password',
24+
example: 'new_secure_password',
25+
})
26+
@IsNotEmpty()
27+
@IsString()
28+
password: string;
29+
}

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { SkipThrottle } from '@nestjs/throttler';
1616
import { UseCaseType } from '../../common/data-injection.tokens.js';
1717
import { Timeout } from '../../decorators/timeout.decorator.js';
1818
import { CompanyInfoEntity } from '../../entities/company-info/company-info.entity.js';
19+
import { CreatedConnectionDTO } from '../../entities/connection/application/dto/created-connection.dto.js';
1920
import { SaasUsualUserRegisterDS } from '../../entities/user/application/data-structures/usual-register-user.ds.js';
2021
import { FoundUserDto } from '../../entities/user/dto/found-user.dto.js';
2122
import { ExternalRegistrationProviderEnum } from '../../entities/user/enums/external-registration-provider.enum.js';
@@ -24,11 +25,14 @@ import { InTransactionEnum } from '../../enums/in-transaction.enum.js';
2425
import { Messages } from '../../exceptions/text/messages.js';
2526
import { SentryInterceptor } from '../../interceptors/sentry.interceptor.js';
2627
import { SuccessResponse } from './data-structures/common-responce.ds.js';
28+
import { CreateConnectionForHostedDbDto } from './data-structures/create-connecttion-for-selfhosted-db.dto.js';
29+
import { DeleteConnectionForHostedDbDto } from './data-structures/delete-connection-for-hosted-db.dto.js';
2730
import { RegisterCompanyWebhookDS } from './data-structures/register-company.ds.js';
2831
import { RegisteredCompanyDS } from './data-structures/registered-company.ds.js';
2932
import { SaasRegisterUserWithGithub } from './data-structures/saas-register-user-with-github.js';
3033
import { SaasSAMLUserRegisterDS } from './data-structures/saas-saml-user-register.ds.js';
3134
import { SaasRegisterUserWithGoogleDS } from './data-structures/sass-register-user-with-google.js';
35+
import { UpdateHostedConnectionPasswordDto } from './data-structures/update-hosted-connection-password.dto.js';
3236
import {
3337
ICompanyRegistration,
3438
ICreateConnectionForHostedDb,
@@ -45,10 +49,8 @@ import {
4549
ISaasSAMLRegisterUser,
4650
ISuspendUsers,
4751
ISuspendUsersOverLimit,
52+
IUpdateHostedConnectionPassword,
4853
} from './use-cases/saas-use-cases.interface.js';
49-
import { CreatedConnectionDTO } from '../../entities/connection/application/dto/created-connection.dto.js';
50-
import { CreateConnectionForHostedDbDto } from './data-structures/create-connecttion-for-selfhosted-db.dto.js';
51-
import { DeleteConnectionForHostedDbDto } from './data-structures/delete-connection-for-hosted-db.dto.js';
5254

5355
@UseInterceptors(SentryInterceptor)
5456
@SkipThrottle()
@@ -91,6 +93,8 @@ export class SaasController {
9193
private readonly createConnectionForHostedDbUseCase: ICreateConnectionForHostedDb,
9294
@Inject(UseCaseType.SAAS_DELETE_CONNECTION_FOR_HOSTED_DB)
9395
private readonly deleteConnectionForHostedDbUseCase: IDeleteConnectionForHostedDb,
96+
@Inject(UseCaseType.SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD)
97+
private readonly updateHostedConnectionPasswordUseCase: IUpdateHostedConnectionPassword,
9498
) {}
9599

96100
@ApiOperation({ summary: 'Company registered webhook' })
@@ -309,4 +313,17 @@ export class SaasController {
309313
): Promise<CreatedConnectionDTO> {
310314
return await this.deleteConnectionForHostedDbUseCase.execute(deleteConnectionData);
311315
}
316+
317+
@ApiOperation({ summary: 'Update password of hosted database connection' })
318+
@ApiBody({ type: UpdateHostedConnectionPasswordDto })
319+
@ApiResponse({
320+
status: 201,
321+
type: SuccessResponse,
322+
})
323+
@Post('/connection/hosted/password')
324+
async updateHostedConnectionPassword(
325+
@Body() updatePasswordData: UpdateHostedConnectionPasswordDto,
326+
): Promise<SuccessResponse> {
327+
return await this.updateHostedConnectionPasswordUseCase.execute(updatePasswordData);
328+
}
312329
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { UserEntity } from '../../entities/user/user.entity.js';
77
import { SignInAuditEntity } from '../../entities/user-sign-in-audit/sign-in-audit.entity.js';
88
import { SignInAuditService } from '../../entities/user-sign-in-audit/sign-in-audit.service.js';
99
import { SaasController } from './saas.controller.js';
10+
import { CreateConnectionForHostedDbUseCase } from './use-cases/create-connection-for-hosted-db.use.case.js';
11+
import { DeleteConnectionForHostedDbUseCase } from './use-cases/delete-connection-for-hosted-db.use.case.js';
1012
import { FreezeConnectionsInCompanyUseCase } from './use-cases/freeze-connections-in-company.use.case.js';
1113
import { GetFullCompanyInfoByUserIdUseCase } from './use-cases/get-full-company-info-by-user-id.use.case.js';
1214
import { GetUserInfoUseCase } from './use-cases/get-user-info.use.case.js';
@@ -20,9 +22,8 @@ import { SaaSRegisterUserWIthSamlUseCase } from './use-cases/register-user-with-
2022
import { SaasUsualRegisterUseCase } from './use-cases/saas-usual-register-user.use.case.js';
2123
import { SuspendUsersUseCase } from './use-cases/suspend-users.use.case.js';
2224
import { SuspendUsersOverLimitUseCase } from './use-cases/suspend-users-over-limit.use.case.js';
23-
import { CreateConnectionForHostedDbUseCase } from './use-cases/create-connection-for-hosted-db.use.case.js';
24-
import { DeleteConnectionForHostedDbUseCase } from './use-cases/delete-connection-for-hosted-db.use.case.js';
2525
import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connections-in-company-use.case.js';
26+
import { UpdateHostedConnectionPasswordUseCase } from './use-cases/update-hosted-connection-password.use.case.js';
2627

2728
@Module({
2829
imports: [TypeOrmModule.forFeature([SignInAuditEntity, UserEntity])],
@@ -95,6 +96,10 @@ import { UnFreezeConnectionsInCompanyUseCase } from './use-cases/unfreeze-connec
9596
provide: UseCaseType.SAAS_DELETE_CONNECTION_FOR_HOSTED_DB,
9697
useClass: DeleteConnectionForHostedDbUseCase,
9798
},
99+
{
100+
provide: UseCaseType.SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD,
101+
useClass: UpdateHostedConnectionPasswordUseCase,
102+
},
98103
SignInAuditService,
99104
],
100105
controllers: [SaasController],
@@ -120,6 +125,7 @@ export class SaasModule {
120125
{ path: 'saas/user/saml/login', method: RequestMethod.POST },
121126
{ path: 'saas/connection/hosted', method: RequestMethod.POST },
122127
{ path: 'saas/connection/hosted/delete', method: RequestMethod.POST },
128+
{ path: 'saas/connection/hosted/password', method: RequestMethod.POST },
123129
);
124130
}
125131
}

backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { SaasRegisterUserWithGithub } from '../data-structures/saas-register-use
1717
import { SaasSAMLUserRegisterDS } from '../data-structures/saas-saml-user-register.ds.js';
1818
import { SaasRegisterUserWithGoogleDS } from '../data-structures/sass-register-user-with-google.js';
1919
import { SuspendUsersDS } from '../data-structures/suspend-users.ds.js';
20+
import { UpdateHostedConnectionPasswordDto } from '../data-structures/update-hosted-connection-password.dto.js';
2021

2122
export interface ICompanyRegistration {
2223
execute(inputData: RegisterCompanyWebhookDS): Promise<RegisteredCompanyDS>;
@@ -77,3 +78,7 @@ export interface ICreateConnectionForHostedDb {
7778
export interface IDeleteConnectionForHostedDb {
7879
execute(inputData: DeleteConnectionForHostedDbDto): Promise<CreatedConnectionDTO>;
7980
}
81+
82+
export interface IUpdateHostedConnectionPassword {
83+
execute(inputData: UpdateHostedConnectionPasswordDto): Promise<SuccessResponse>;
84+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Inject, Injectable, NotFoundException, Scope } 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 { Messages } from '../../../exceptions/text/messages.js';
6+
import { SuccessResponse } from '../data-structures/common-responce.ds.js';
7+
import { UpdateHostedConnectionPasswordDto } from '../data-structures/update-hosted-connection-password.dto.js';
8+
import { IUpdateHostedConnectionPassword } from './saas-use-cases.interface.js';
9+
10+
@Injectable({ scope: Scope.REQUEST })
11+
export class UpdateHostedConnectionPasswordUseCase
12+
extends AbstractUseCase<UpdateHostedConnectionPasswordDto, SuccessResponse>
13+
implements IUpdateHostedConnectionPassword
14+
{
15+
constructor(
16+
@Inject(BaseType.GLOBAL_DB_CONTEXT)
17+
protected _dbContext: IGlobalDatabaseContext,
18+
) {
19+
super();
20+
}
21+
22+
protected async implementation(inputData: UpdateHostedConnectionPasswordDto): Promise<SuccessResponse> {
23+
const { companyId, databaseName, password } = inputData;
24+
25+
const foundCompany =
26+
await this._dbContext.companyInfoRepository.findCompanyInfoByCompanyIdWithoutConnections(companyId);
27+
if (!foundCompany) {
28+
throw new NotFoundException(Messages.COMPANY_NOT_FOUND);
29+
}
30+
31+
const connection = await this._dbContext.connectionRepository.findOne({
32+
where: { company: { id: companyId }, database: databaseName },
33+
});
34+
if (!connection) {
35+
throw new NotFoundException(Messages.CONNECTION_NOT_FOUND);
36+
}
37+
38+
connection.password = password;
39+
await this._dbContext.connectionRepository.saveUpdatedConnection(connection);
40+
41+
return { success: true };
42+
}
43+
}

0 commit comments

Comments
 (0)