Skip to content

feat(swap-service): lock down service behind shared API key#43

Merged
kaladinlight merged 3 commits into
developfrom
feat/microservice-api-key-auth
Jun 26, 2026
Merged

feat(swap-service): lock down service behind shared API key#43
kaladinlight merged 3 commits into
developfrom
feat/microservice-api-key-auth

Conversation

@kaladinlight

@kaladinlight kaladinlight commented Jun 26, 2026

Copy link
Copy Markdown
Member

Description

Locks swap-service behind a shared service API key so it can only be called by trusted internal callers (public-api, user-service), closing the current state where every swap-service route — including reads of any user's swap history and affiliate fee data — is world-open.

  • Adds an ApiKeyGuard to @shapeshift/shared-utils and registers it globally on swap-service via APP_GUARD. Every Nest route now requires an x-api-key header matching SERVICE_API_KEY (constant-time compare, fails closed, throws at boot if unset). GET /health is a raw Express route and stays open by design.
  • The shared service clients (UserServiceClient/SwapServiceClient/NotificationsServiceClient) now send the key, so existing service-to-service calls keep working. In particular, user-service's SwapServiceClient (referral-fees) authenticates to the now-locked swap-service — so user-service also needs SERVICE_API_KEY set.
  • SERVICE_API_KEY added to swap-service's env schema and documented in the .env.example files.

Scope: swap-service only. user-service and notifications-service are intentionally not guarded here (deferred).

Companion PR (web): public-api becomes the keyed gateway and the browser stops calling swap-service directly — required for this to deploy without breaking the app. Must ship together.

Deploy requirements

  • SERVICE_API_KEY set on swap-service and user-service (same value).
  • public-api's SWAP_SERVICE_API_KEY must match (companion PR).
  • Order: deploy the gateway/web changes before swap-service starts enforcing, or in-flight callers 401.

Not addressed (deferred)

This locks the network, not attribution. The client-forged /swaps attribution hole is only half-closed (the direct browser write path dies); the durable fix is the quote-linked {quoteId, txid} registration migration.

Testing

  • With SERVICE_API_KEY set, requests without a matching x-api-key get 401; requests with it succeed.
  • GET /health works without a key.
  • Service boots fail fast when SERVICE_API_KEY is unset.
  • user-service referral-fees (which calls swap-service) still works with the key configured.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a shared API key requirement for service requests, improving access control across the platform.
    • Service-to-service calls now automatically include the required API key.
  • Bug Fixes

    • Added validation so missing or empty API key settings fail fast during startup.
    • Requests without the correct API key are now rejected consistently.
  • Documentation

    • Updated example environment configuration to include the new API key setting.

Add a shared ApiKeyGuard in @shapeshift/shared-utils and register it globally
on swap-service via APP_GUARD. Every Nest route now requires an x-api-key
header matching SERVICE_API_KEY (constant-time compare, fails closed, throws
at boot if unset). /health is a raw Express route and stays open.

Internal callers carry the key automatically via the shared service clients.
user-service's SwapServiceClient (referral-fees) now authenticates to
swap-service, so user-service also needs SERVICE_API_KEY set.

user-service and notifications-service are not guarded yet (deferred); their
guard wiring was removed from this change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kaladinlight, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 19 minutes and 23 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe8cc35c-d469-4cf6-95b1-bfa020d3b5ba

📥 Commits

Reviewing files that changed from the base of the PR and between 4bf3364 and 7a4dfd9.

📒 Files selected for processing (3)
  • packages/shared-utils/src/api-key.guard.ts
  • packages/shared-utils/src/index.ts
  • packages/shared-utils/src/service-clients.ts
📝 Walkthrough

Walkthrough

The PR adds shared API-key enforcement, requires SERVICE_API_KEY in service configuration, registers the guard in swap-service, and updates shared service clients to send the API key header.

Changes

Shared API-key enforcement

Layer / File(s) Summary
Shared guard and exports
packages/shared-utils/src/api-key.guard.ts, packages/shared-utils/src/index.ts
Defines API_KEY_HEADER, implements ApiKeyGuard, and re-exports both from the shared utilities entrypoint.
Service config and guard wiring
apps/user-service/.env.example, apps/swap-service/.env.example, apps/swap-service/src/env.ts, apps/swap-service/src/app.module.ts
Adds SERVICE_API_KEY to the service env examples, requires it in swap-service env validation, and registers ApiKeyGuard as a global provider.
Outbound client headers
packages/shared-utils/src/service-clients.ts
Imports API_KEY_HEADER and sets the SERVICE_API_KEY header on the shared user, notifications, and swap Axios clients.

Sequence Diagram(s)

sequenceDiagram
  participant "UserServiceClient"
  participant "swap-service"
  participant "ApiKeyGuard"
  "UserServiceClient"->>"swap-service": axios request with x-api-key header
  "swap-service"->>"ApiKeyGuard": canActivate(context)
  "ApiKeyGuard"->>"ApiKeyGuard": compare SERVICE_API_KEY to x-api-key
  "ApiKeyGuard"-->>"swap-service": allow request or throw UnauthorizedException
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop with a key in my pocket of green,
Guarding the swap-house, tidy and keen.
One header to send, one secret to keep,
Then carrots and requests both happilyീപ!

🚥 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 clearly reflects the main change: securing swap-service with a shared API key.
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/microservice-api-key-auth

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

🤖 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 `@apps/swap-service/src/app.module.ts`:
- Around line 12-14: The global ApiKeyGuard is being applied unconditionally
through APP_GUARD in AppModule, which will break existing routes before the
caller rollout lands. Update the AppModule provider setup to gate the APP_GUARD
registration behind a rollout flag, using the existing ApiKeyGuard and AppModule
symbols to keep the guard off until the dependent caller changes are deployed
together. If you choose not to gate it in code, ensure the guard is only enabled
in the same deployment unit as the caller updates.

In `@packages/shared-utils/src/service-clients.ts`:
- Line 18: The shared API key is being forwarded from the client factories in
service-clients.ts to user-service and notifications-service even though those
services are not using auth yet. Update the client setup so the API_KEY_HEADER
is only attached in SwapServiceClient, or make keyed auth explicitly opt-in per
client in the relevant factory methods, and keep the
user-service/notifications-service clients free of SERVICE_API_KEY.
🪄 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: 77dbcf95-0664-4e4d-a124-ab69c14feee1

📥 Commits

Reviewing files that changed from the base of the PR and between bca320a and 4bf3364.

📒 Files selected for processing (7)
  • apps/swap-service/.env.example
  • apps/swap-service/src/app.module.ts
  • apps/swap-service/src/env.ts
  • apps/user-service/.env.example
  • packages/shared-utils/src/api-key.guard.ts
  • packages/shared-utils/src/index.ts
  • packages/shared-utils/src/service-clients.ts

Comment thread apps/swap-service/src/app.module.ts
Comment thread packages/shared-utils/src/service-clients.ts Outdated
kaladinlight and others added 2 commits June 26, 2026 13:48
Per review: UserServiceClient and NotificationsServiceClient were sending
SERVICE_API_KEY to user-service and notifications-service, which aren't
guarded in this change. That needlessly widens the secret's blast radius.
Keep the x-api-key header on SwapServiceClient only — the sole client that
calls the API-key-guarded swap-service.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ients

Import ordering, single-line guard clause, and alphabetized export — no
functional change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kaladinlight kaladinlight merged commit 45af3a2 into develop Jun 26, 2026
2 checks passed
@kaladinlight kaladinlight deleted the feat/microservice-api-key-auth branch June 26, 2026 20:10
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.

1 participant