Skip to content

feat: implement MTA hooks for quota enforcement#82

Open
jzunigax2 wants to merge 2 commits into
masterfrom
feat/mta-hooks-read-only-quota
Open

feat: implement MTA hooks for quota enforcement#82
jzunigax2 wants to merge 2 commits into
masterfrom
feat/mta-hooks-read-only-quota

Conversation

@jzunigax2

@jzunigax2 jzunigax2 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
  • Added MTA hooks module with authentication guard, controller, and service.
  • Implemented handleRcpt method in MtaHooksService to manage recipient quota checks.
  • Introduced getUserUsage method in BridgeClient for fetching user storage usage.
  • Updated .env.template to include MTA hooks credentials.
  • Added unit tests for MTA hooks functionality and authentication guard to ensure reliability.

Summary by CodeRabbit

  • New Features

    • Added MTA “rcpt” hook endpoint with Basic Auth protection.
    • Enforced recipient quota checks using Bridge usage data, returning a clear SMTP rejection when over quota.
    • Added configuration support for MTA hook credentials, including an env template update.
  • Bug Fixes

    • Improved safety and robustness: fail-open behavior when lookups fail, constant-time credential comparison, and handling of missing/invalid recipient size or multi-recipient inputs.
  • Tests

    • Expanded unit and snapshot coverage for quota handling, auth guard edge cases, and usage retrieval.

@jzunigax2 jzunigax2 self-assigned this Jul 6, 2026
@jzunigax2 jzunigax2 marked this pull request as ready for review July 9, 2026 01:01
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b340d12-a49b-4442-9301-50888f3c31cb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an MTA Hooks feature with a guarded NestJS RCPT endpoint that resolves recipients, checks Bridge mailbox usage against quota, and returns accept or SMTP rejection responses. It also adds protocol types, configuration, Bridge usage access, application wiring, and tests.

Changes

MTA Hooks feature

Layer / File(s) Summary
Protocol types and configuration
src/modules/mta-hooks/mta-hooks.types.ts, src/config/configuration.ts, .env.template
Defines MTA hook request and response shapes and adds mtaHooks username and secret configuration from environment variables.
Bridge usage lookup
src/modules/infrastructure/bridge/bridge.service.ts, src/modules/infrastructure/bridge/bridge.service.spec.ts
Adds getUserUsage(userUuid), which signs a gateway JWT, requests user usage, parses successful JSON, and throws BridgeApiError for non-200 responses.
Basic authentication guard
src/modules/mta-hooks/mta-hooks-auth.guard.ts, src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts
Adds Basic-auth validation with non-empty configured credentials, constant-time comparisons, malformed-header handling, and tests for valid, invalid, and empty-secret cases.
RCPT quota enforcement
src/modules/mta-hooks/mta-hooks.service.ts, src/modules/mta-hooks/mta-hooks.service.spec.ts
Adds recipient resolution, declared-size parsing, quota comparison, over-quota SMTP rejection, fail-open error handling, and coverage for recipient and lookup behaviors.
Controller and module wiring
src/modules/mta-hooks/mta-hooks.controller.ts, src/modules/mta-hooks/mta-hooks.module.ts, src/app.module.ts
Adds the guarded POST /mta-hooks/rcpt endpoint, wires Account and Bridge dependencies, and registers MtaHooksModule in AppModule.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Stalwart
  participant MtaHooksController
  participant MtaHooksAuthGuard
  participant MtaHooksService
  participant AccountService
  participant BridgeClient

  Stalwart->>MtaHooksController: POST /mta-hooks/rcpt
  MtaHooksController->>MtaHooksAuthGuard: canActivate(request)
  MtaHooksAuthGuard-->>MtaHooksController: allowed or UnauthorizedException
  MtaHooksController->>MtaHooksService: handleRcpt(request)
  MtaHooksService->>AccountService: findUserIdByAddress(recipient)
  AccountService-->>MtaHooksService: userUuid
  MtaHooksService->>BridgeClient: getUserUsage(userUuid)
  BridgeClient-->>MtaHooksService: usage and quota
  MtaHooksService-->>MtaHooksController: accept or reject response
  MtaHooksController-->>Stalwart: MtaHookResponse
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding MTA hooks for quota enforcement.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mta-hooks-read-only-quota

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
.env.template (1)

33-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

dotenv-linter flags key ordering.

MTA_HOOKS_SECRET should appear before MTA_HOOKS_USERNAME per the linter's alphabetical ordering rule.

🔧 Fix key ordering
 # MTA hooks (RCPT)
-MTA_HOOKS_USERNAME=stalwart
 MTA_HOOKS_SECRET=
+MTA_HOOKS_USERNAME=stalwart
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.template around lines 33 - 36, The dotenv linter is flagging the MTA
hooks entries for incorrect alphabetical ordering. Reorder the variables in the
.env.template section so MTA_HOOKS_SECRET appears before MTA_HOOKS_USERNAME,
keeping the existing values unchanged and preserving the surrounding comment and
section structure.

Source: Linters/SAST tools

src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts (1)

65-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test case for empty-secret credentials.

This would guard against the empty-secret bypass (see comment on the guard). When the configured secret is non-empty, sending stalwart: (empty password) should reject.

♻️ Suggested additional test
+  it('when the secret is empty, then throws Unauthorized', () => {
+    const context = contextWithAuth(basic(username, ''));
+
+    expect(() => guard.canActivate(context)).toThrow(UnauthorizedException);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts` around lines 65 - 71, Add
a spec in mta-hooks-auth.guard.spec for the empty-secret bypass case by
extending the existing Basic auth parsing tests around guard.canActivate and
contextWithAuth. Verify that when the configured secret is non-empty, a Basic
credential decoding to a username with an empty password such as stalwart: is
rejected with UnauthorizedException, alongside the existing no-colon test. Use
the guard’s credential validation path in AuthGuard to locate the behavior being
exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/config/configuration.ts`:
- Around line 57-60: The MTA hooks Basic auth check in configuration handling
currently allows an empty secret, which can let `stalwart:` pass as valid
credentials. Update the logic around `mtaHooks` in `configuration.ts` and the
related startup/request guard so `MTA_HOOKS_SECRET` must be non-empty: fail
startup or reject access when the secret resolves to an empty string, and ensure
the check uses the `MTA_HOOKS_SECRET`/`mtaHooks` values consistently.

In `@src/modules/mta-hooks/mta-hooks-auth.guard.ts`:
- Around line 16-20: The auth guard in MTAHooksAuthGuard is accepting an empty
secret because ConfigService.getOrThrow only rejects undefined, not ''. Update
the constructor to validate that both mtaHooks.username and mtaHooks.secret are
non-empty strings before storing them, and fail fast if the secret is missing or
blank. Use the existing MTAHooksAuthGuard constructor and
expectedSecret/expectedUsername fields to locate the change, and keep the
validation aligned with the mtaHooks configuration contract.

---

Nitpick comments:
In @.env.template:
- Around line 33-36: The dotenv linter is flagging the MTA hooks entries for
incorrect alphabetical ordering. Reorder the variables in the .env.template
section so MTA_HOOKS_SECRET appears before MTA_HOOKS_USERNAME, keeping the
existing values unchanged and preserving the surrounding comment and section
structure.

In `@src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts`:
- Around line 65-71: Add a spec in mta-hooks-auth.guard.spec for the
empty-secret bypass case by extending the existing Basic auth parsing tests
around guard.canActivate and contextWithAuth. Verify that when the configured
secret is non-empty, a Basic credential decoding to a username with an empty
password such as stalwart: is rejected with UnauthorizedException, alongside the
existing no-colon test. Use the guard’s credential validation path in AuthGuard
to locate the behavior being exercised.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26106eeb-7d02-47e8-93d7-20a9ea34f049

📥 Commits

Reviewing files that changed from the base of the PR and between 5c57964 and cede383.

📒 Files selected for processing (12)
  • .env.template
  • src/app.module.ts
  • src/config/configuration.ts
  • src/modules/infrastructure/bridge/bridge.service.spec.ts
  • src/modules/infrastructure/bridge/bridge.service.ts
  • src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts
  • src/modules/mta-hooks/mta-hooks-auth.guard.ts
  • src/modules/mta-hooks/mta-hooks.controller.ts
  • src/modules/mta-hooks/mta-hooks.module.ts
  • src/modules/mta-hooks/mta-hooks.service.spec.ts
  • src/modules/mta-hooks/mta-hooks.service.ts
  • src/modules/mta-hooks/mta-hooks.types.ts

Comment thread src/config/configuration.ts
Comment thread src/modules/mta-hooks/mta-hooks-auth.guard.ts
- Added MTA hooks module with authentication guard, controller, and service.
- Implemented `handleRcpt` method in `MtaHooksService` to manage recipient quota checks.
- Introduced `getUserUsage` method in `BridgeClient` for fetching user storage usage.
- Updated `.env.template` to include MTA hooks credentials.
- Added unit tests for MTA hooks functionality and authentication guard to ensure reliability.
…uard

- Added unit tests to verify that the MtaHooksAuthGuard throws UnauthorizedException when the secret is empty.
- Enhanced the guard's constructor to throw an error if the configured secret is empty, ensuring proper configuration validation.
@jzunigax2 jzunigax2 force-pushed the feat/mta-hooks-read-only-quota branch from cede383 to 5ffcffe Compare July 10, 2026 17:02
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts (1)

22-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider renaming basic helper parameters to avoid shadowing.

The username and secret parameters shadow the module-level constants of the same name (lines 7-8). While functionally correct, this can cause confusion during future edits.

♻️ Optional rename
-  const basic = (username: string, secret: string): string =>
-    `Basic ${Buffer.from(`${username}:${secret}`).toString('base64')}`;
+  const basic = (user: string, pass: string): string =>
+    `Basic ${Buffer.from(`${user}:${pass}`).toString('base64')}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts` around lines 22 - 23,
Rename the parameters of the basic helper to avoid shadowing the module-level
username and secret constants, and update the template expression to use the new
parameter names while preserving the generated Basic authentication header.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.env.template:
- Around line 35-37: Replace the empty MTA_HOOKS_SECRET value in the deployment
template with an explicit placeholder, and ensure the startup configuration
validation rejects the placeholder or any missing secret before enabling RCPT
hooks. Update the relevant MTA hooks configuration/validation symbols and
document that operators must replace the placeholder before deployment.

---

Nitpick comments:
In `@src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts`:
- Around line 22-23: Rename the parameters of the basic helper to avoid
shadowing the module-level username and secret constants, and update the
template expression to use the new parameter names while preserving the
generated Basic authentication header.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f80a3a76-8d40-4ce1-a2b2-3641fd5400d2

📥 Commits

Reviewing files that changed from the base of the PR and between cede383 and 5ffcffe.

📒 Files selected for processing (12)
  • .env.template
  • src/app.module.ts
  • src/config/configuration.ts
  • src/modules/infrastructure/bridge/bridge.service.spec.ts
  • src/modules/infrastructure/bridge/bridge.service.ts
  • src/modules/mta-hooks/mta-hooks-auth.guard.spec.ts
  • src/modules/mta-hooks/mta-hooks-auth.guard.ts
  • src/modules/mta-hooks/mta-hooks.controller.ts
  • src/modules/mta-hooks/mta-hooks.module.ts
  • src/modules/mta-hooks/mta-hooks.service.spec.ts
  • src/modules/mta-hooks/mta-hooks.service.ts
  • src/modules/mta-hooks/mta-hooks.types.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/modules/mta-hooks/mta-hooks.module.ts
  • src/config/configuration.ts
  • src/app.module.ts
  • src/modules/mta-hooks/mta-hooks.types.ts
  • src/modules/mta-hooks/mta-hooks.service.ts
  • src/modules/mta-hooks/mta-hooks.controller.ts
  • src/modules/mta-hooks/mta-hooks-auth.guard.ts
  • src/modules/infrastructure/bridge/bridge.service.ts
  • src/modules/mta-hooks/mta-hooks.service.spec.ts
  • src/modules/infrastructure/bridge/bridge.service.spec.ts

Comment thread .env.template
Comment on lines +35 to +37
# MTA hooks (RCPT)
MTA_HOOKS_USERNAME=stalwart
MTA_HOOKS_SECRET=

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not leave the MTA hooks secret empty in the deployment template.

With MTA_HOOKS_SECRET= and the configuration fallback to '', deployments that copy this file unchanged will have unusable RCPT authentication because the guard requires non-empty credentials. Use an explicit placeholder and validate the secret at startup, or document that it must be replaced before deployment.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 37-37: [UnorderedKey] The MTA_HOOKS_SECRET key should go before the MTA_HOOKS_USERNAME key

(UnorderedKey)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.template around lines 35 - 37, Replace the empty MTA_HOOKS_SECRET value
in the deployment template with an explicit placeholder, and ensure the startup
configuration validation rejects the placeholder or any missing secret before
enabling RCPT hooks. Update the relevant MTA hooks configuration/validation
symbols and document that operators must replace the placeholder before
deployment.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jzunigax2 jzunigax2 requested a review from xabg2 July 10, 2026 20:12
@jzunigax2 jzunigax2 added the enhancement New feature or request label Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant