Skip to content

fix(api): derive SIWE allowed domains from CORS origins#207

Merged
FSM1 merged 4 commits into
mainfrom
fix/siwe-domain-cors-origins
Feb 26, 2026
Merged

fix(api): derive SIWE allowed domains from CORS origins#207
FSM1 merged 4 commits into
mainfrom
fix/siwe-domain-cors-origins

Conversation

@FSM1

@FSM1 FSM1 commented Feb 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replace single SIWE_DOMAIN env var with dynamic domain list derived from CORS_ALLOWED_ORIGINS
  • SIWE domain validation now accepts any non-wildcard origin from the CORS config, enabling wallet auth from localhost against staging/production APIs
  • Remove SIWE_DOMAIN from .env.example, deploy workflow, and all call sites
  • Add VITE_PINATA_GATEWAY_URL to web .env.example (was missing)

Motivation

When running the web app locally (localhost:5173) pointed at the staging API, wallet login fails with 401 because the SIWE message domain (localhost:5173) doesn't match the configured SIWE_DOMAIN (app-staging.cipherbox.cc). Since CORS already defines the trusted origin list, SIWE should reuse it rather than requiring a separate config.

Test plan

  • SIWE service unit tests pass (17/17), including new multi-domain test
  • Wallet login from localhost:5173 against staging API
  • Wallet login from app-staging.cipherbox.cc
  • Wallet linking flow still works

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Wallet verification now accepts multiple allowed domains derived from CORS/WEB_APP_URL instead of a single static domain.
  • Chores

    • Removed SIWE_DOMAIN from example envs and staging deploy configuration.
    • Removed commented Google OAuth client ID from web example.
  • Tests

    • Updated SIWE/wallet verification tests to cover multi-domain acceptance and error cases.

Replace single SIWE_DOMAIN env var with dynamic domain list derived from
CORS_ALLOWED_ORIGINS. This allows wallet auth from any trusted origin
(e.g. localhost during dev against staging API) without separate config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 6f5ec9ce41a3
@coderabbitai

coderabbitai Bot commented Feb 26, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@FSM1 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 56 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 98640ab and 9f967d0.

📒 Files selected for processing (9)
  • .github/workflows/deploy-staging.yml
  • .github/workflows/e2e.yml
  • apps/api/.env.example
  • apps/api/src/auth/auth.service.ts
  • apps/api/src/auth/controllers/identity.controller.spec.ts
  • apps/api/src/auth/controllers/identity.controller.ts
  • apps/api/src/auth/services/siwe.service.spec.ts
  • apps/api/src/auth/services/siwe.service.ts
  • apps/api/src/main.ts
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: deriving SIWE allowed domains from CORS origins instead of using a hardcoded SIWE_DOMAIN variable.
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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/siwe-domain-cors-origins

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 and usage tips.

@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 (2)
apps/web/.env.example (1)

7-8: Consider whether this change belongs in this PR.

Adding VITE_PINATA_GATEWAY_URL appears unrelated to the main PR objective of deriving SIWE allowed domains from CORS origins. While the PR summary mentions this as a previously missing variable, bundling unrelated configuration changes can make the PR harder to review and understand.

Additionally, the specific gateway URL (sapphire-fashionable-antelope-504.mypinata.cloud) looks like a project-specific Pinata gateway. Confirm whether:

  1. This specific URL should be in the example file, or if a placeholder/generic format would be more appropriate
  2. This gateway URL is intended to be public/shared across environments
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/.env.example` around lines 7 - 8, The VITE_PINATA_GATEWAY_URL
addition is unrelated to the SIWE/CORS change; remove or convert it to a generic
placeholder in the env example to keep the PR focused: replace the concrete
value "sapphire-fashionable-antelope-504.mypinata.cloud" with a clear
placeholder like "<YOUR_PINATA_GATEWAY_URL>" (referencing the variable name
VITE_PINATA_GATEWAY_URL) or move this change into a separate follow-up PR and
update any docs mentioning VITE_PINATA_GATEWAY_URL accordingly; ensure the
example does not expose a project-specific gateway URL and confirm whether the
gateway should be public before adding a real URL.
apps/api/src/auth/controllers/identity.controller.ts (1)

256-273: Extract domain derivation logic to avoid duplication.

This domain extraction logic is duplicated verbatim in auth.service.ts (lines 421-437). Consider extracting this into a shared helper method in SiweService or a configuration utility to follow DRY principles and ensure consistent behavior across both call sites.

♻️ Suggested approach: Add helper to SiweService
// In siwe.service.ts, add:
+  /**
+   * Derive allowed SIWE domains from CORS origins configuration.
+   * Filters wildcards and extracts host:port from URLs.
+   */
+  static deriveAllowedDomains(
+    corsOrigins: string | undefined,
+    webAppUrl: string | undefined
+  ): string[] {
+    const rawOrigins = corsOrigins || webAppUrl;
+    if (!rawOrigins) {
+      return ['localhost:5173', 'localhost:4173', 'localhost'];
+    }
+    return rawOrigins
+      .split(',')
+      .map((o) => o.trim())
+      .filter((o) => !o.includes('*'))
+      .map((o) => {
+        try {
+          return new URL(o).host;
+        } catch {
+          return o;
+        }
+      });
+  }

Then in both controller and service:

-    const rawOrigins =
-      this.configService.get<string>('CORS_ALLOWED_ORIGINS') ||
-      this.configService.get<string>('WEB_APP_URL');
-    const allowedDomains = rawOrigins
-      ? rawOrigins
-          .split(',')
-          .map((o) => o.trim())
-          .filter((o) => !o.includes('*'))
-          .map((o) => {
-            try {
-              const url = new URL(o);
-              return url.host;
-            } catch {
-              return o;
-            }
-          })
-      : ['localhost:5173', 'localhost:4173', 'localhost'];
+    const allowedDomains = SiweService.deriveAllowedDomains(
+      this.configService.get<string>('CORS_ALLOWED_ORIGINS'),
+      this.configService.get<string>('WEB_APP_URL')
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/auth/controllers/identity.controller.ts` around lines 256 - 273,
The domain-extraction block is duplicated; extract it into a single helper
(e.g., parseAllowedDomains or deriveAllowedDomains) on SiweService or a small
config utility, implement the logic there (read
CORS_ALLOWED_ORIGINS/WEB_APP_URL, split by comma, trim, filter out wildcards,
try new URL() to return url.host or fallback to the original token, and preserve
the same default array ['localhost:5173','localhost:4173','localhost']), then
replace the inline logic in identity.controller.ts and auth.service.ts to call
that helper (importing the new function or SiweService method) so both call
sites reuse the same implementation and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/auth/controllers/identity.controller.ts`:
- Around line 260-273: When parsing CORS_ALLOWED_ORIGINS into allowedDomains,
handle the edge case where filtering out wildcards yields an empty array: after
computing allowedDomains from rawOrigins (variable names: rawOrigins,
allowedDomains, CORS_ALLOWED_ORIGINS), check if allowedDomains.length === 0 and
then emit a clear warning (e.g., console.warn or the module logger) that
rawOrigins contained only wildcard entries and wildcard domains were ignored,
include the rawOrigins value in the message, and then set allowedDomains to the
same safe fallback used when rawOrigins is falsy
(['localhost:5173','localhost:4173','localhost']) so SIWE verification
(SiweService.verifySiweMessage) does not silently reject all requests without
context.

---

Nitpick comments:
In `@apps/api/src/auth/controllers/identity.controller.ts`:
- Around line 256-273: The domain-extraction block is duplicated; extract it
into a single helper (e.g., parseAllowedDomains or deriveAllowedDomains) on
SiweService or a small config utility, implement the logic there (read
CORS_ALLOWED_ORIGINS/WEB_APP_URL, split by comma, trim, filter out wildcards,
try new URL() to return url.host or fallback to the original token, and preserve
the same default array ['localhost:5173','localhost:4173','localhost']), then
replace the inline logic in identity.controller.ts and auth.service.ts to call
that helper (importing the new function or SiweService method) so both call
sites reuse the same implementation and behavior.

In `@apps/web/.env.example`:
- Around line 7-8: The VITE_PINATA_GATEWAY_URL addition is unrelated to the
SIWE/CORS change; remove or convert it to a generic placeholder in the env
example to keep the PR focused: replace the concrete value
"sapphire-fashionable-antelope-504.mypinata.cloud" with a clear placeholder like
"<YOUR_PINATA_GATEWAY_URL>" (referencing the variable name
VITE_PINATA_GATEWAY_URL) or move this change into a separate follow-up PR and
update any docs mentioning VITE_PINATA_GATEWAY_URL accordingly; ensure the
example does not expose a project-specific gateway URL and confirm whether the
gateway should be public before adding a real URL.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9070136 and 498d08d.

📒 Files selected for processing (8)
  • .github/workflows/deploy-staging.yml
  • apps/api/.env.example
  • apps/api/src/auth/auth.service.ts
  • apps/api/src/auth/controllers/identity.controller.spec.ts
  • apps/api/src/auth/controllers/identity.controller.ts
  • apps/api/src/auth/services/siwe.service.spec.ts
  • apps/api/src/auth/services/siwe.service.ts
  • apps/web/.env.example
💤 Files with no reviewable changes (2)
  • apps/api/.env.example
  • .github/workflows/deploy-staging.yml

Comment thread apps/api/src/auth/controllers/identity.controller.ts Outdated
FSM1 and others added 3 commits February 26, 2026 22:47
WEB_APP_URL was a legacy single-origin fallback superseded by
CORS_ALLOWED_ORIGINS. Remove all references to consolidate on a
single env var for trusted origins (CORS + SIWE domain validation).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: a2f44c18551c
SiweService now injects ConfigService and resolves allowed domains from
CORS_ALLOWED_ORIGINS internally. Callers no longer need to build and
pass the domain list — verifySiweMessage handles it as an implementation
detail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: d681ad360619
@FSM1 FSM1 enabled auto-merge (squash) February 26, 2026 22:09
@FSM1 FSM1 merged commit 4723063 into main Feb 26, 2026
14 checks passed
@FSM1 FSM1 deleted the fix/siwe-domain-cors-origins branch March 4, 2026 01:16
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