Skip to content

feat: add hosted database password update webhook endpoint#1690

Merged
gugu merged 2 commits intomainfrom
hosted-db-password-webhook
Mar 26, 2026
Merged

feat: add hosted database password update webhook endpoint#1690
gugu merged 2 commits intomainfrom
hosted-db-password-webhook

Conversation

@gugu
Copy link
Copy Markdown
Contributor

@gugu gugu commented Mar 25, 2026

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.

Summary by CodeRabbit

  • New Features

    • New authenticated API to update passwords for hosted database connections.
    • Clients can submit company, database name, and new password to update credentials.
  • API Changes

    • Connection creation response simplified to return a single connectionId field.

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>
@gugu gugu requested review from Artuomka and Copilot March 25, 2026 21:49
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Adds a hosted-connection password update feature: new DTO, use-case interface and implementation, controller endpoint, module/provider registration, and a UseCaseType enum token; also adjusts related create-connection response types.

Changes

Cohort / File(s) Summary
Token & Enum Registration
backend/src/common/data-injection.tokens.ts
Added SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD to UseCaseType.
DTOs & Responses
backend/src/microservices/saas-microservice/data-structures/update-hosted-connection-password.dto.ts, backend/src/microservices/saas-microservice/data-structures/common-responce.ds.ts
Added UpdateHostedConnectionPasswordDto (validated, documented) and new CreatedConnectionResponse (connectionId).
Controller
backend/src/microservices/saas-microservice/saas.controller.ts
Injected new IUpdateHostedConnectionPassword use-case; added POST /connection/hosted/password endpoint; adjusted create endpoint response type to CreatedConnectionResponse.
Module & Routing
backend/src/microservices/saas-microservice/saas.module.ts
Registered UpdateHostedConnectionPasswordUseCase provider and added authenticated route saas/connection/hosted/password.
Use-Case Interfaces
backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts
Updated create use-case return type to CreatedConnectionResponse; added IUpdateHostedConnectionPassword interface.
Use-Case Implementations
backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts, backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.ts
Added request-scoped UpdateHostedConnectionPasswordUseCase that validates company, finds connection, updates password, and persists; modified create use-case to return {connectionId} and adjusted related calls/formatting.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Controller as SaasController
    participant UseCase as UpdateHostedConnectionPasswordUseCase
    participant CompanyRepo as CompanyInfoRepository
    participant ConnRepo as ConnectionRepository

    Client->>Controller: POST /connection/hosted/password (UpdateHostedConnectionPasswordDto)
    Controller->>UseCase: execute(updatePasswordData)
    UseCase->>CompanyRepo: findCompanyInfoByCompanyIdWithoutConnections(companyId)
    CompanyRepo-->>UseCase: Company | null
    alt Company not found
        UseCase-->>Controller: throw NotFoundException
        Controller-->>Client: 404
    else Company found
        UseCase->>ConnRepo: find({where:{company:{id}}})
        ConnRepo-->>UseCase: [connections]
        alt No matching connection
            UseCase-->>Controller: throw NotFoundException
            Controller-->>Client: 404
        else Matching connection
            UseCase->>UseCase: set connection.password = password
            UseCase->>ConnRepo: saveUpdatedConnection(connection)
            ConnRepo-->>UseCase: success
            UseCase-->>Controller: {success:true}
            Controller-->>Client: 200 {success:true}
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Backend ceadr refactoring #1680: Modifies the "create connection for hosted DB" use-case and return contract — closely related to the create-use-case adjustments in this PR.

Poem

🐰 I hopped through routes and DTO fields bright,
Found company roots in the data night,
A password replaced with a careful tap,
Saved to the connection — no rabbit trap!
A tiny hop, success: true!

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Security Check ⚠️ Warning The new password update endpoint lacks authentication controls, allowing unauthorized password changes for any company via an unauthenticated POST request. Add the route to authenticateToken middleware, implement @Auth/@UseGuards decorators, and validate requester authorization for the specific company before allowing password updates.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: adding a hosted database password update webhook endpoint. The title is specific, concise, and clearly describes the primary feature introduced in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hosted-db-password-webhook

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new SaaS webhook endpoint that allows rocketadmin-saas to notify the backend when a hosted database password is reset, so the backend can update stored connection credentials.

Changes:

  • Added POST /saas/connection/hosted/password endpoint wired through SaasController + SaasModule.
  • Introduced UpdateHostedConnectionPasswordUseCase and its DTO/interface wiring.
  • Registered a new DI token SAAS_UPDATE_HOSTED_CONNECTION_PASSWORD.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts Implements the password update use case (currently has a lookup issue with encrypted fields).
backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts Adds the use-case interface for password updates.
backend/src/microservices/saas-microservice/saas.module.ts Registers the new use case provider and includes the new route in SaaS auth middleware routing.
backend/src/microservices/saas-microservice/saas.controller.ts Adds the new webhook endpoint handler and injects the use case.
backend/src/microservices/saas-microservice/data-structures/update-hosted-connection-password.dto.ts Defines request payload for the password update webhook.
backend/src/common/data-injection.tokens.ts Adds a new UseCaseType token for DI.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +14 to +20
@ApiProperty({
description: 'Database name',
example: 'my_database',
})
@IsNotEmpty()
@IsString()
databaseName: string;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using databaseName as the identifier for the hosted connection is ambiguous (a company can have multiple connections with the same DB name) and, because the database column is encrypted at rest, it can’t be used for direct DB lookups. Consider including a hostedDatabaseId/connectionId in this DTO (consistent with DeleteConnectionForHostedDbDto) and using that to target the exact connection.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +33
const connection = await this._dbContext.connectionRepository.findOne({
where: { company: { id: companyId }, database: databaseName },
});
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConnectionEntity.database is stored encrypted (see ConnectionEntity @BeforeInsert/@BeforeUpdate), so querying with where: { database: databaseName } will not match any rows (encryption uses random salt/IV). This will cause this endpoint to consistently return CONNECTION_NOT_FOUND. Use a lookup that doesn’t rely on encrypted fields (e.g., accept hostedDatabaseId/connection id and query by id + companyId, or fetch all connections for the company and match connection.database after entities are loaded/decrypted).

Suggested change
const connection = await this._dbContext.connectionRepository.findOne({
where: { company: { id: companyId }, database: databaseName },
});
const companyConnections = await this._dbContext.connectionRepository.find({
where: { company: { id: companyId } },
});
const connection = companyConnections.find((conn) => conn.database === databaseName);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts`:
- Around line 31-36: The findOne call in
update-hosted-connection-password.use.case.ts uses where: { company: { id:
companyId }, database: databaseName } which will never match because the
database column is stored encrypted and only decrypted in `@AfterLoad`; instead,
fetch the connection by company (e.g., use connectionRepository.findOne({ where:
{ company: { id: companyId } } }) or implement/call the custom repo method
pattern used in delete-connection-for-hosted-db.use.case.ts), then compare the
decrypted connection.database to the plaintext databaseName in code and throw
NotFoundException if it doesn’t match; alternatively add or reuse a repository
method that performs encrypted-field-aware lookup and call that from this use
case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cefe6840-70d1-4507-9d79-1dae84753232

📥 Commits

Reviewing files that changed from the base of the PR and between 333512e and c86f66a.

📒 Files selected for processing (6)
  • backend/src/common/data-injection.tokens.ts
  • backend/src/microservices/saas-microservice/data-structures/update-hosted-connection-password.dto.ts
  • backend/src/microservices/saas-microservice/saas.controller.ts
  • backend/src/microservices/saas-microservice/saas.module.ts
  • backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts
  • backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts

…ionId from create endpoint

The database column is stored encrypted and only decrypted in @afterload,
so querying by plaintext value would never match. Now fetches connections
by company and filters by decrypted database name in code. Also simplifies
create hosted connection response to return just the connectionId.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/src/microservices/saas-microservice/saas.controller.ts (1)

317-328: Consider using PUT or PATCH instead of POST for update operations.

The endpoint updates an existing resource's password, which semantically aligns better with PUT or PATCH HTTP methods. POST typically indicates resource creation. Additionally, consider changing the @ApiResponse status to 200 for updates rather than 201.

♻️ Suggested change
 `@ApiOperation`({ summary: 'Update password of hosted database connection' })
 `@ApiBody`({ type: UpdateHostedConnectionPasswordDto })
 `@ApiResponse`({
-  status: 201,
+  status: 200,
   type: SuccessResponse,
 })
-@Post('/connection/hosted/password')
+@Put('/connection/hosted/password')
 async updateHostedConnectionPassword(
   `@Body`() updatePasswordData: UpdateHostedConnectionPasswordDto,
 ): Promise<SuccessResponse> {
   return await this.updateHostedConnectionPasswordUseCase.execute(updatePasswordData);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/microservices/saas-microservice/saas.controller.ts` around lines
317 - 328, The endpoint updateHostedConnectionPassword currently uses
`@Post`('/connection/hosted/password') and returns a 201 ApiResponse; change the
HTTP verb to a more appropriate updater such as `@Put` or `@Patch` (choose `@Patch` if
only the password is being modified) and update the `@ApiResponse` status from 201
to 200 (SuccessResponse) accordingly; keep the method name
updateHostedConnectionPassword and the DTO UpdateHostedConnectionPasswordDto
unchanged so only the decorators and response status are adjusted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts`:
- Around line 34-37: The current lookup using companyConnections.find((conn) =>
conn.database === databaseName) can pick the wrong record when multiple
connections share the same database name; change the use case to disambiguate by
requiring and validating a hostname: update the incoming DTO to include
hostname, validate it's present, then replace the find predicate with one that
matches both conn.database === databaseName && conn.host === hostname (or
conn.hostname), and keep throwing
NotFoundException(Messages.CONNECTION_NOT_FOUND) when no exact match is found;
also update any callers/tests to supply the hostname.

---

Nitpick comments:
In `@backend/src/microservices/saas-microservice/saas.controller.ts`:
- Around line 317-328: The endpoint updateHostedConnectionPassword currently
uses `@Post`('/connection/hosted/password') and returns a 201 ApiResponse; change
the HTTP verb to a more appropriate updater such as `@Put` or `@Patch` (choose
`@Patch` if only the password is being modified) and update the `@ApiResponse`
status from 201 to 200 (SuccessResponse) accordingly; keep the method name
updateHostedConnectionPassword and the DTO UpdateHostedConnectionPasswordDto
unchanged so only the decorators and response status are adjusted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2795e3f1-5cdc-4d67-9d04-e5a74a59b4b6

📥 Commits

Reviewing files that changed from the base of the PR and between c86f66a and 6c61cf9.

📒 Files selected for processing (5)
  • backend/src/microservices/saas-microservice/data-structures/common-responce.ds.ts
  • backend/src/microservices/saas-microservice/saas.controller.ts
  • backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.ts
  • backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts
  • backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts
✅ Files skipped from review due to trivial changes (1)
  • backend/src/microservices/saas-microservice/data-structures/common-responce.ds.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts

Comment on lines +34 to +37
const connection = companyConnections.find((conn) => conn.database === databaseName);
if (!connection) {
throw new NotFoundException(Messages.CONNECTION_NOT_FOUND);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's a unique constraint on company+database
echo "=== Checking for unique constraints on connection entity ==="
rg -n "unique|@Unique|@Index" backend/src/entities/connection/connection.entity.ts -A 2 -B 2

echo "=== Checking how hosted connections are created ==="
rg -n "databaseName|database" backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.ts

Repository: rocket-admin/rocketadmin

Length of output: 456


🏁 Script executed:

cat -n backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts | head -50

Repository: rocket-admin/rocketadmin

Length of output: 2199


🏁 Script executed:

cat -n backend/src/entities/connection/connection.entity.ts

Repository: rocket-admin/rocketadmin

Length of output: 10808


🏁 Script executed:

# Check for database migrations or constraints
fd -t f "migration" backend/src --type f | head -10

Repository: rocket-admin/rocketadmin

Length of output: 124


🏁 Script executed:

head -200 backend/src/migrations/1594021894551-InitialMigration.ts | grep -A 5 -B 5 "connection\|unique\|UNIQUE" -i

Repository: rocket-admin/rocketadmin

Length of output: 4151


🏁 Script executed:

# Also check if there are any other migration files that might define this constraint
find backend/src/migrations -type f -name "*.ts" | xargs grep -l "connection\|database" | head -5

Repository: rocket-admin/rocketadmin

Length of output: 373


🏁 Script executed:

# Check for companyId in the connection table and any unique constraints added later
grep -n "companyId\|UNIQUE.*database\|UNIQUE.*company" backend/src/migrations/*.ts

Repository: rocket-admin/rocketadmin

Length of output: 8417


🏁 Script executed:

# Also check the create-connection-for-hosted-db to see the full picture
cat -n backend/src/microservices/saas-microservice/use-cases/create-connection-for-hosted-db.use.case.ts

Repository: rocket-admin/rocketadmin

Length of output: 5350


Potential ambiguity if multiple connections share the same database name.

The lookup uses only databaseName to identify the connection within a company. If a company has multiple connections with the same database value (e.g., pointing to different hosts), Array.find() returns only the first match, potentially updating the wrong connection.

The database schema does not enforce a unique constraint on (companyId, databaseName). Consider either:

  • Adding hostname to the DTO for disambiguation, or
  • Enforcing (companyId, databaseName) as unique at the database level.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts`
around lines 34 - 37, The current lookup using companyConnections.find((conn) =>
conn.database === databaseName) can pick the wrong record when multiple
connections share the same database name; change the use case to disambiguate by
requiring and validating a hostname: update the incoming DTO to include
hostname, validate it's present, then replace the find predicate with one that
matches both conn.database === databaseName && conn.host === hostname (or
conn.hostname), and keep throwing
NotFoundException(Messages.CONNECTION_NOT_FOUND) when no exact match is found;
also update any callers/tests to supply the hostname.

@gugu gugu merged commit 04731c8 into main Mar 26, 2026
18 of 20 checks passed
@gugu gugu deleted the hosted-db-password-webhook branch March 26, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants