Skip to content

fix(auth): stabilize oauth redirect/session handling and api origin - #131

Merged
LucasSantana-Dev merged 6 commits into
mainfrom
fix/login-oauth-session-origin
Mar 10, 2026
Merged

LucasSantana-Dev merged 6 commits into
mainfrom
fix/login-oauth-session-origin

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented Mar 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • make OAuth redirect URI resolution deterministic across auth start/callback
  • harden session adapter behavior for Redis-backed sessions
  • enforce same-origin frontend API base handling for login/auth flows
  • update auth/session route and middleware tests

Test plan

  • npm run test --workspace=packages/backend -- tests/integration/routes/auth.test.ts tests/integration/api.test.ts tests/unit/middleware/session.test.ts tests/unit/middleware/sessionRedisAdapter.test.ts

Summary by CodeRabbit

  • Bug Fixes

    • Fixed OAuth session persistence failures with improved Redis compatibility.
    • Improved OAuth callback redirect URI consistency across authentication flow.
    • Enhanced E2E test stability with deterministic element locators and proper route cleanup.
  • Documentation

    • Updated autoplay recommendations description with anti-repeat filtering details.
    • Added WEBAPP_REDIRECT_URI environment variable guidance with examples.

@vercel

vercel Bot commented Mar 10, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lucky Ready Ready Preview, Comment Mar 10, 2026 3:58am

@coderabbitai

coderabbitai Bot commented Mar 10, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@LucasSantana-Dev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 1 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0fcf8ae8-e13e-44cb-96eb-dffc2aadc57f

📥 Commits

Reviewing files that changed from the base of the PR and between 09d6a4d and 74f9336.

📒 Files selected for processing (4)
  • packages/backend/src/middleware/session.ts
  • packages/backend/tests/unit/middleware/sessionRedisAdapter.test.ts
  • packages/backend/tests/unit/middleware/sessionResilientStore.test.ts
  • packages/frontend/src/services/api.test.ts
📝 Walkthrough

Walkthrough

This PR implements OAuth session persistence using a resilient Redis adapter for ioredis compatibility, introduces context-aware OAuth redirect URI handling with forwarded-header support, adds smart frontend API base inference for same-origin requests on specific hosts, configures production trust-proxy settings, and improves E2E test resilience with deterministic role-based locators.

Changes

Cohort / File(s) Summary
OAuth Session Persistence
packages/backend/src/middleware/session.ts, packages/backend/src/types/session.d.ts
Introduces Redis-backed session storage with resilience layer; adds ioredis adapter wrapper (createConnectRedisClientAdapter), ResilientSessionStore with fallback to file/memory stores on errors, Redis client configuration with lazy connect and retry strategy, and local fallback creation. Adds oauthRedirectUri optional property to SessionData interface.
OAuth Redirect URI Handling
packages/backend/src/utils/oauthRedirectUri.ts, packages/backend/src/routes/auth.ts, packages/backend/src/routes/authCallback.ts, packages/backend/src/services/DiscordOAuthService.ts
New utility for context-aware OAuth redirect URI construction with layered fallbacks (session stored URI, environment variable, forwarded headers, production canonical); normalizes legacy callback paths. Routes store and retrieve redirect URI from session. DiscordOAuthService.exchangeCodeForToken now accepts optional redirectUri parameter. Adds /auth/callback route binding alongside existing /api/auth/callback.
Backend Middleware & Config
packages/backend/src/middleware/index.ts, nginx/nginx.conf
Sets trust proxy flag in production. Implements dynamic static path selection preferring monorepo public path with fallback to local public. Nginx now uses mapped X-Forwarded-Proto header via new $proxy_x_forwarded_proto variable for consistent forwarding behavior.
Frontend API Base Inference
packages/frontend/src/services/apiBase.ts, packages/frontend/src/services/apiBase.test.ts, packages/frontend/src/services/api.ts
New inferApiBase function determines API endpoint using same-origin /api for specific managed hosts, special handling for home server domain, and fallback to /api. Frontend API service now normalizes base URL by removing trailing slashes and uses globalThis for window detection in 401 handling.
Test Enhancements
packages/backend/tests/setup.ts, packages/backend/tests/unit/middleware/index.test.ts, packages/backend/tests/unit/middleware/session.test.ts, packages/backend/tests/unit/middleware/sessionRedisAdapter.test.ts, packages/backend/tests/unit/utils/oauthRedirectUri.test.ts
Structured express-session mock with MockStore/MockMemoryStore classes. Added unit tests for production trust proxy behavior, Redis adapter interface verification, Redis adapter operations (set/del/mGet/scanIterator), and OAuth redirect URI precedence/normalization logic with environment and forwarded header scenarios.
Integration Tests
packages/backend/tests/integration/api.test.ts, packages/backend/tests/integration/routes/auth.test.ts
Enhanced OAuth callback test to verify exchangeCodeForToken receives two arguments with callback URL. Refactored auth tests with helper wrappers (getDiscordOAuthMock, getSessionServiceMock, mockSuccessfulOAuthFlow); added tests for forwarded host redirect URI derivation, production canonical callback, and parameterized test sets for /api/auth/callback and /auth/callback paths.
E2E Test Improvements
packages/frontend/tests/e2e/dashboard-page.spec.ts, packages/frontend/tests/e2e/servers-page.spec.ts, packages/frontend/tests/e2e/track-history-page.spec.ts
Replaced text-based locators with role-based heading locators (getByRole) for deterministic element selection. Added explicit route unroute cleanup in servers page test. Improved test resilience against DOM structure changes.
Documentation
CHANGELOG.md, README.md
Added changelog entries documenting OAuth session persistence with connect-redis v9 adapter, same-origin /api for specific hosts, OAuth redirect URI reuse with forwarded-host fallback, and E2E stability improvements. Updated README with WEBAPP_REDIRECT_URI configuration guidance and autoplay anti-repeat filtering description; clarified frontend base URL behavior for same-origin and API host mapping scenarios.

Sequence Diagram

sequenceDiagram
    participant Client as User/Browser
    participant Backend as Backend Server
    participant Session as Session Store<br/>(Redis/Fallback)
    participant OAuth as Discord OAuth
    participant TokenSvc as Token Exchange

    Client->>Backend: GET /api/auth/discord (initiate OAuth)
    Backend->>Backend: Compute redirect URI<br/>(from forwarded headers/<br/>env/production default)
    Backend->>Session: Store oauthRedirectUri in session
    Session-->>Backend: Session saved
    Backend-->>Client: Redirect to Discord OAuth URL
    
    Client->>OAuth: Authorize at Discord
    OAuth-->>Client: Redirect to callback with code
    
    Client->>Backend: GET /api/auth/callback?code=...
    Backend->>Session: Retrieve oauthRedirectUri<br/>from session
    Session-->>Backend: oauthRedirectUri (or fallback)
    Backend->>TokenSvc: Exchange code for token<br/>with stored redirectUri
    TokenSvc-->>Backend: Access token response
    Backend->>Session: setSession with user info
    Session-->>Backend: Session updated
    Backend-->>Client: Redirect to authenticated state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • PR #125: Adds VITE_API_BASE_URL support and frontend API base normalization, directly related to the new inferApiBase function and API endpoint resolution logic in this PR.
  • PR #126: Modifies backend dynamic CORS origin handling and frontend API base inference for host-based routing, complementary to the context-aware OAuth redirect URI and API base selection changes.
  • PR #127: Changes frontend API base inference for lucassantana.tech hosts, overlapping with the new host-aware inferApiBase logic for same-origin /api requests.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: OAuth redirect URI stabilization, session handling improvements, and API origin fixes across the codebase.

✏️ 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/login-oauth-session-origin

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.

@netlify

netlify Bot commented Mar 10, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for regal-bunny-0c8efe ready!

Name Link
🔨 Latest commit 74f9336
🔍 Latest deploy log https://app.netlify.com/projects/regal-bunny-0c8efe/deploys/69af969fb32cb30008b402d8
😎 Deploy Preview https://deploy-preview-131--regal-bunny-0c8efe.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

github-actions Bot commented Mar 10, 2026 •

Copy link
Copy Markdown

Size Change: +42 B (+0.01%)

Total Size: 291 kB

Filename Size Change
packages/frontend/dist/assets/AutoMessages-BGYtjSoJ.js 0 B -1.56 kB (removed) 🏆
packages/frontend/dist/assets/AutoMessages-CMgS6A5U.js 1.55 kB +1.55 kB (new file) 🆕
packages/frontend/dist/assets/AutoMod-C8xhDPfJ.js 2.73 kB +2.73 kB (new file) 🆕
packages/frontend/dist/assets/AutoMod-FJBRLEVI.js 0 B -2.73 kB (removed) 🏆
packages/frontend/dist/assets/badge-B4cZUzaj.js 0 B -453 B (removed) 🏆
packages/frontend/dist/assets/badge-BSzIfNL_.js 451 B +451 B (new file) 🆕
packages/frontend/dist/assets/Card-BkEuAEQs.js 378 B +378 B (new file) 🆕
packages/frontend/dist/assets/Card-BrIFAMmT.js 0 B -379 B (removed) 🏆
packages/frontend/dist/assets/CommandsConfig-9DyEn9YO.js 1.42 kB +1.42 kB (new file) 🆕
packages/frontend/dist/assets/CommandsConfig-LUbwGfjY.js 0 B -1.43 kB (removed) 🏆
packages/frontend/dist/assets/Config-B6SeqPtf.js 0 B -1.61 kB (removed) 🏆
packages/frontend/dist/assets/Config-Cy7pzW6r.js 1.61 kB +1.61 kB (new file) 🆕
packages/frontend/dist/assets/CustomCommands-qKtmfnQF.js 0 B -2.1 kB (removed) 🏆
packages/frontend/dist/assets/CustomCommands-yT0WH2rG.js 2.1 kB +2.1 kB (new file) 🆕
packages/frontend/dist/assets/DashboardOverview-DYXUKauZ.js 0 B -2.93 kB (removed) 🏆
packages/frontend/dist/assets/DashboardOverview-XHXxJ9Q-.js 2.93 kB +2.93 kB (new file) 🆕
packages/frontend/dist/assets/Features-D4OCXHWA.js 2.39 kB +2.39 kB (new file) 🆕
packages/frontend/dist/assets/Features-DYlv53jk.js 0 B -2.39 kB (removed) 🏆
packages/frontend/dist/assets/index-D9yyAzoM.js 0 B -67 kB (removed) 🏆
packages/frontend/dist/assets/index-DtgzK-rw.js 67.1 kB +67.1 kB (new file) 🆕
packages/frontend/dist/assets/input-BIBeHpt5.js 424 B +424 B (new file) 🆕
packages/frontend/dist/assets/input-jQCPYx24.js 0 B -429 B (removed) 🏆
packages/frontend/dist/assets/label-BtI5qtgh.js 441 B +441 B (new file) 🆕
packages/frontend/dist/assets/label-CbBuRbzV.js 0 B -441 B (removed) 🏆
packages/frontend/dist/assets/LastFm-C6DSRF4P.js 1.52 kB +1.52 kB (new file) 🆕
packages/frontend/dist/assets/LastFm-Cg7eFcVi.js 0 B -1.52 kB (removed) 🏆
packages/frontend/dist/assets/Login-C8ApiiBt.js 0 B -2.22 kB (removed) 🏆
packages/frontend/dist/assets/Login-CyEdqIEv.js 2.23 kB +2.23 kB (new file) 🆕
packages/frontend/dist/assets/Lyrics-B8dOl9x3.js 0 B -1.3 kB (removed) 🏆
packages/frontend/dist/assets/Lyrics-Cgj-ILor.js 1.3 kB +1.3 kB (new file) 🆕
packages/frontend/dist/assets/Moderation-CyO4oF_2.js 3.75 kB +3.75 kB (new file) 🆕
packages/frontend/dist/assets/Moderation-IV3RA0ph.js 0 B -3.75 kB (removed) 🏆
packages/frontend/dist/assets/ModerationConfig-DGmc9jMg.js 0 B -1.93 kB (removed) 🏆
packages/frontend/dist/assets/ModerationConfig-mUBG9RyO.js 1.93 kB +1.93 kB (new file) 🆕
packages/frontend/dist/assets/Music-Cn10Yn1W.js 0 B -6.08 kB (removed) 🏆
packages/frontend/dist/assets/Music-D_6_novU.js 6.08 kB +6.08 kB (new file) 🆕
packages/frontend/dist/assets/MusicConfig-DjtYin2c.js 1.62 kB +1.62 kB (new file) 🆕
packages/frontend/dist/assets/MusicConfig-IddHclvI.js 0 B -1.62 kB (removed) 🏆
packages/frontend/dist/assets/select-Cc-hOp73.js 1.19 kB +1.19 kB (new file) 🆕
packages/frontend/dist/assets/select-CvrCF0jT.js 0 B -1.19 kB (removed) 🏆
packages/frontend/dist/assets/ServerLogs-BT9nKyXL.js 2.82 kB +2.82 kB (new file) 🆕
packages/frontend/dist/assets/ServerLogs-dnMNBxPb.js 0 B -2.82 kB (removed) 🏆
packages/frontend/dist/assets/ServerSettings-Bx22dsba.js 0 B -1.9 kB (removed) 🏆
packages/frontend/dist/assets/ServerSettings-DTQEdHEp.js 1.89 kB +1.89 kB (new file) 🆕
packages/frontend/dist/assets/ServersPage-AmZDWDFs.js 0 B -2.4 kB (removed) 🏆
packages/frontend/dist/assets/ServersPage-B2gVdZYc.js 2.4 kB +2.4 kB (new file) 🆕
packages/frontend/dist/assets/Skeleton-1qcU1i2h.js 227 B +227 B (new file) 🆕
packages/frontend/dist/assets/Skeleton-CXKoTiGM.js 0 B -227 B (removed) 🏆
packages/frontend/dist/assets/switch-Dj-GgJeq.js 0 B -515 B (removed) 🏆
packages/frontend/dist/assets/switch-REl4l9kI.js 514 B +514 B (new file) 🆕
packages/frontend/dist/assets/TrackHistory-BSBej7TJ.js 1.84 kB +1.84 kB (new file) 🆕
packages/frontend/dist/assets/TrackHistory-sZ6gthg8.js 0 B -1.84 kB (removed) 🏆
packages/frontend/dist/assets/TwitchNotifications-B3uB9o6z.js 0 B -1.57 kB (removed) 🏆
packages/frontend/dist/assets/TwitchNotifications-Cn0zWvqs.js 1.57 kB +1.57 kB (new file) 🆕
packages/frontend/dist/assets/useGuildSelection-4GpN0txK.js 294 B +294 B (new file) 🆕
packages/frontend/dist/assets/useGuildSelection-CJsIjWbs.js 0 B -295 B (removed) 🏆
ℹ️ View Unchanged
Filename Size
packages/frontend/dist/assets/index-DLqvhx7-.css 13 kB
packages/frontend/dist/assets/usePageMetadata-Dudc5JrX.js 287 B
packages/frontend/dist/assets/vendor-forms-C1Gy4syj.js 21.1 kB
packages/frontend/dist/assets/vendor-radix-BTf9AecN.js 33.5 kB
packages/frontend/dist/assets/vendor-react-Bu1Oeml_.js 17.3 kB
packages/frontend/dist/assets/vendor-state-Szs8I3iE.js 25 kB
packages/frontend/dist/assets/vendor-ui-DSWwm_-g.js 64.2 kB
packages/frontend/dist/assets/zod-CLxBCFgw.js 1.58 kB

compressed-size-action

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/backend/tests/integration/api.test.ts (1)

80-92: ⚠️ Potential issue | 🟡 Minor

This integration test still skips the stateful half of the flow.

It calls /api/auth/callback directly, so it never proves that /api/auth/discord stored req.session.oauthRedirectUri and that the callback reused the same value. Please drive the request through the start route first and carry the returned cookie into the callback assertion.

Based on learnings "Add integration tests where appropriate" and "Test behavior, not implementation details".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/tests/integration/api.test.ts` around lines 80 - 92, The
test currently hits /api/auth/callback directly and skips the stateful start of
the flow; update the test to first request GET /api/auth/discord, capture the
Set-Cookie/session returned, then call GET /api/auth/callback with that cookie
and the query code (MOCK_AUTH_CODE) so the request.session.oauthRedirectUri set
by the discord start route is preserved; assert the redirect contains
authenticated=true and that mockDiscordOAuth.exchangeCodeForToken was called
with MOCK_AUTH_CODE and a redirect URI containing '/api/auth/callback' (or the
captured redirect value) to verify the session-stored redirectUri was reused.
packages/backend/src/routes/auth.ts (1)

23-43: ⚠️ Potential issue | 🟠 Major

Fail the OAuth start request when the session cannot be saved.

req.session.save() logs errors but still resolves, so this route can redirect to Discord with an oauthRedirectUri that was never persisted. On the callback, req.session.oauthRedirectUri may be missing and the token exchange can use a different redirect URI than the authorize step.

Suggested fix
                 req.session.oauthInitiated = true
                 req.session.oauthRedirectUri = getOAuthRedirectUri(req)

-                await new Promise<void>((resolve) => {
+                await new Promise<void>((resolve, reject) => {
                     req.session.save((err) => {
                         if (err) {
                             errorLog({
                                 message: 'Error saving session on OAuth init:',
                                 error: err,
                             })
+                            reject(err)
+                            return
                         } else {
                             debugLog({
                                 message: 'Session initialized for OAuth',
                                 data: { sessionId: req.sessionID },
                             })
                         }
                         resolve()
                     })
                 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/routes/auth.ts` around lines 23 - 43, The route
currently always proceeds even when req.session.save(err) fails, which can lead
to an unset oauthRedirectUri; update the save logic in the OAuth init flow to
treat save errors as fatal: in the Promise passed to req.session.save (and
around getOAuthRedirectUri / req.session.oauthRedirectUri usage) reject or
return an HTTP error (e.g., 500) when err is non-null, log the error via
errorLog and do not continue to compute clientId or redirectUri/redirect to
Discord; only resolve/continue and call debugLog when save succeeds so the
downstream token exchange can rely on req.session.oauthRedirectUri being
persisted.
🧹 Nitpick comments (2)
packages/frontend/src/services/apiBase.test.ts (1)

14-37: Good test coverage, consider adding edge cases.

The parameterized tests cover the key scenarios. Consider adding test cases for:

  • Apex homeserver domain (luk-homeserver.com.br)
  • Localhost behavior (if relevant for local dev)
Optional: additional test cases
         {
             hostname: 'panel.luk-homeserver.com.br',
             expected: 'https://api.luk-homeserver.com.br/api',
         },
+        {
+            hostname: 'luk-homeserver.com.br',
+            expected: 'https://api.luk-homeserver.com.br/api',
+        },
+        {
+            hostname: 'localhost',
+            expected: '/api',
+        },
     ](
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/services/apiBase.test.ts` around lines 14 - 37, Add
edge-case cases to the parameterized tests that exercise inferApiBase: extend
the test.each in apiBase.test.ts to include a case for the apex homeserver
hostname "luk-homeserver.com.br" with expected
"https://api.luk-homeserver.com.br/api" (matching the existing panel-derived
rule) and a case for local development such as "localhost" (and optionally
"127.0.0.1") with the expected local API behavior (e.g., "/api" or the local
host API URL your inferApiBase implementation returns); ensure these new entries
use the same test title ("infers API base for $hostname") and call
inferApiBase(...) the same way as the other cases so the function name
inferApiBase and the existing test harness are reused.
packages/backend/src/services/DiscordOAuthService.ts (1)

55-70: Make redirectUri required in exchangeCodeForToken.

The callback now computes the exact redirect URI it wants to reuse. Leaving this parameter optional with redirectUri ?? this.getRedirectUri() reopens the mismatch as soon as another caller forgets the second argument.

♻️ Tighten the API contract
     async exchangeCodeForToken(
         code: string,
-        redirectUri?: string,
+        redirectUri: string,
     ): Promise<TokenResponse> {
@@
-                    redirect_uri: redirectUri ?? this.getRedirectUri(),
+                    redirect_uri: redirectUri,
                 }),
             })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/services/DiscordOAuthService.ts` around lines 55 - 70,
exchangeCodeForToken currently accepts an optional redirectUri and falls back to
getRedirectUri(), which risks mismatches; change the method signature of
exchangeCodeForToken to require redirectUri (remove the ?), remove the fallback
use of redirectUri ?? this.getRedirectUri() and pass redirectUri directly into
the URLSearchParams, and update any callers to provide the computed redirect
URI; ensure TokenResponse typing remains unchanged and adjust any tests or
usages referencing getRedirectUri fallback accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nginx/nginx.conf`:
- Around line 3-6: The map definition for proxy_x_forwarded_proto currently
trusts client-supplied $http_x_forwarded_proto; change it so the map uses
$scheme as the default (i.e., always derive proto from nginx's scheme) or, if
you must accept upstream X-Forwarded-Proto, declare trusted proxies by adding
set_real_ip_from entries for your proxy subnets and enable real_ip_header and
real_ip_recursive so nginx only uses X-Forwarded-* from those proxies; also
ensure the backend only trusts X-Forwarded-Proto when coming from the validated
proxy chain. Reference the map block (map $http_x_forwarded_proto
$proxy_x_forwarded_proto) and the directives set_real_ip_from, real_ip_header,
and real_ip_recursive when applying the fix.

In `@packages/backend/src/middleware/session.ts`:
- Around line 194-200: The touch method declares its callback as () => void but
passes it to execute which expects a SessionCallback (error/data arguments),
causing a type mismatch; update touch's signature to accept a SessionCallback
(e.g., callback: SessionCallback = () => {}) and keep the default no-op but
typed correctly, or alternatively adapt the call to wrap the provided zero-arg
callback into a SessionCallback wrapper before calling this.execute('touch',
[sid, sessionData], ...), referencing the touch method, execute method, and
SessionCallback/session.SessionData types to locate and fix the inconsistency.

In `@packages/backend/src/utils/oauthRedirectUri.ts`:
- Around line 3-4: The file defines a hard-coded
CANONICAL_PRODUCTION_REDIRECT_URI which must be removed: read the canonical
backend origin from a validated source (e.g., WEBAPP_BACKEND_URL or the shared
config) instead of using 'https://lucky-api.lucassantana.tech', validate it as a
well-formed URL/origin, and use its origin to build the OAuth callback; update
any code that references CANONICAL_PRODUCTION_REDIRECT_URI (and the logic around
lines referencing 43-53) to throw/fail-fast if the env/shared-config value is
missing or malformed in production-like environments so callbacks never silently
go to the hard-coded host.

In `@packages/backend/tests/integration/routes/auth.test.ts`:
- Around line 145-165: The test "should resolve callback redirect uri from
forwarded host when env is unset" mutates process.env.WEBAPP_REDIRECT_URI but
only restores it conditionally at the end; wrap the request/assertion logic in a
try/finally and restore process.env.WEBAPP_REDIRECT_URI in the finally block so
the original value is always reset (keep references to
process.env.WEBAPP_REDIRECT_URI, mockSuccessfulOAuthFlow(), and the assertion
using getDiscordOAuthMock().exchangeCodeForToken intact).

In `@packages/backend/tests/unit/middleware/index.test.ts`:
- Around line 1-25: The tests mutate process.env.NODE_ENV but don't guarantee
restoration on failure; update the test file so NODE_ENV is always restored by
wrapping each test's mutation in a try/finally or add an afterEach hook that
resets NODE_ENV, referencing the existing tests that call setupMiddleware(app)
and assert app.get('trust proxy') so the originalNodeEnv captured before
mutation is reinstated unconditionally.

In `@packages/frontend/src/services/api.ts`:
- Around line 87-93: The 401 interceptor in services/api.ts currently redirects
using a relative path (globalThis.window.location.assign('/api/auth/discord')),
causing inconsistent OAuth entry points versus the explicit login URL returned
by getDiscordLoginUrl() and NORMALIZED_API_BASE; update the interceptor to build
the redirect using the same base as NORMALIZED_API_BASE (or simply
call/getDiscordLoginUrl() to obtain the full URL) and then call
window.location.assign(...) with that full URL so both manual login and
401-triggered login use the identical OAuth endpoint.

In `@packages/frontend/tests/e2e/servers-page.spec.ts`:
- Around line 113-115: The test registers an extra page.route handler that calls
route.continue(), which bypasses the existing mock installed by
setupMockApiResponses(page); replace route.continue() with route.fallback() in
the anonymous handler (the async route => { ... } callback) so the request is
passed to the next matching handler in the chain, preserving the existing mock
for '/api/guilds' after the artificial delay.

---

Outside diff comments:
In `@packages/backend/src/routes/auth.ts`:
- Around line 23-43: The route currently always proceeds even when
req.session.save(err) fails, which can lead to an unset oauthRedirectUri; update
the save logic in the OAuth init flow to treat save errors as fatal: in the
Promise passed to req.session.save (and around getOAuthRedirectUri /
req.session.oauthRedirectUri usage) reject or return an HTTP error (e.g., 500)
when err is non-null, log the error via errorLog and do not continue to compute
clientId or redirectUri/redirect to Discord; only resolve/continue and call
debugLog when save succeeds so the downstream token exchange can rely on
req.session.oauthRedirectUri being persisted.

In `@packages/backend/tests/integration/api.test.ts`:
- Around line 80-92: The test currently hits /api/auth/callback directly and
skips the stateful start of the flow; update the test to first request GET
/api/auth/discord, capture the Set-Cookie/session returned, then call GET
/api/auth/callback with that cookie and the query code (MOCK_AUTH_CODE) so the
request.session.oauthRedirectUri set by the discord start route is preserved;
assert the redirect contains authenticated=true and that
mockDiscordOAuth.exchangeCodeForToken was called with MOCK_AUTH_CODE and a
redirect URI containing '/api/auth/callback' (or the captured redirect value) to
verify the session-stored redirectUri was reused.

---

Nitpick comments:
In `@packages/backend/src/services/DiscordOAuthService.ts`:
- Around line 55-70: exchangeCodeForToken currently accepts an optional
redirectUri and falls back to getRedirectUri(), which risks mismatches; change
the method signature of exchangeCodeForToken to require redirectUri (remove the
?), remove the fallback use of redirectUri ?? this.getRedirectUri() and pass
redirectUri directly into the URLSearchParams, and update any callers to provide
the computed redirect URI; ensure TokenResponse typing remains unchanged and
adjust any tests or usages referencing getRedirectUri fallback accordingly.

In `@packages/frontend/src/services/apiBase.test.ts`:
- Around line 14-37: Add edge-case cases to the parameterized tests that
exercise inferApiBase: extend the test.each in apiBase.test.ts to include a case
for the apex homeserver hostname "luk-homeserver.com.br" with expected
"https://api.luk-homeserver.com.br/api" (matching the existing panel-derived
rule) and a case for local development such as "localhost" (and optionally
"127.0.0.1") with the expected local API behavior (e.g., "/api" or the local
host API URL your inferApiBase implementation returns); ensure these new entries
use the same test title ("infers API base for $hostname") and call
inferApiBase(...) the same way as the other cases so the function name
inferApiBase and the existing test harness are reused.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0840e42d-f8ea-4862-9586-3e86a53fe713

📥 Commits

Reviewing files that changed from the base of the PR and between 7373227 and 09d6a4d.

📒 Files selected for processing (23)
  • CHANGELOG.md
  • README.md
  • nginx/nginx.conf
  • packages/backend/src/middleware/index.ts
  • packages/backend/src/middleware/session.ts
  • packages/backend/src/routes/auth.ts
  • packages/backend/src/routes/authCallback.ts
  • packages/backend/src/services/DiscordOAuthService.ts
  • packages/backend/src/types/session.d.ts
  • packages/backend/src/utils/oauthRedirectUri.ts
  • packages/backend/tests/integration/api.test.ts
  • packages/backend/tests/integration/routes/auth.test.ts
  • packages/backend/tests/setup.ts
  • packages/backend/tests/unit/middleware/index.test.ts
  • packages/backend/tests/unit/middleware/session.test.ts
  • packages/backend/tests/unit/middleware/sessionRedisAdapter.test.ts
  • packages/backend/tests/unit/utils/oauthRedirectUri.test.ts
  • packages/frontend/src/services/api.ts
  • packages/frontend/src/services/apiBase.test.ts
  • packages/frontend/src/services/apiBase.ts
  • packages/frontend/tests/e2e/dashboard-page.spec.ts
  • packages/frontend/tests/e2e/servers-page.spec.ts
  • packages/frontend/tests/e2e/track-history-page.spec.ts

Comment thread nginx/nginx.conf
Comment on lines +3 to +6
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default $http_x_forwarded_proto;
'' $scheme;
}

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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

nginx best practices X-Forwarded-Proto security trusted proxy

💡 Result:

Core rule

Treat X-Forwarded-Proto (and all X-Forwarded-*) as untrusted client input unless the request arrived from a proxy/load balancer you explicitly trust. X-Forwarded-Proto is only a de-facto convention indicating the client↔proxy scheme. [1]

NGINX best practices (secure “trusted proxy” setup)

  1. Only trust known proxy IPs (never 0.0.0.0/0)

    • Use the Real IP module to declare which upstream hops are trusted to assert “real” client info. [2]
    • Configure only your LB/CDN subnets in set_real_ip_from, select the header, and enable recursive parsing so NGINX finds the last non-trusted address in the chain. [2]
  2. Overwrite (don’t forward) spoofable headers to your upstream

    • When NGINX proxies to an app, explicitly set headers with proxy_set_header (don’t rely on what the client sent). [3]
    • For X-Forwarded-Proto specifically:
      • If NGINX terminates TLS: set it from NGINX’s own view: $scheme.
      • If TLS terminates at the load balancer: have the LB set X-Forwarded-Proto, and ensure NGINX only accepts traffic from the LB (network policy / firewall / private listener), otherwise clients can spoof it.
  3. Prefer the standardized Forwarded header when possible

    • RFC 7239 defines Forwarded: ...; proto=https and is explicit that these fields cannot be relied upon unless proxies are trusted, and that intermediaries should handle/replace existing forwarded info carefully. [4]
  4. Assume attackers will try header-based bypasses

    • OWASP testing guidance explicitly calls out X-Forwarded-For / X-Forwarded-Proto as common header-manipulation targets. [5]
    • Many frameworks can be configured to “trust proxy headers” (e.g., Django’s SECURE_PROXY_SSL_HEADER)—only enable that if only trusted proxies can reach the app/NGINX, otherwise the app may incorrectly treat an HTTP request as HTTPS. [6]

Minimal safe NGINX pattern (example)

# 1) Trust only your proxy/LB/CDN networks for client identity
set_real_ip_from  10.0.0.0/8;        # example: your LB subnet(s)
real_ip_header    X-Forwarded-For;
real_ip_recursive on;

location / {
  proxy_pass http://app;

  # 2) Do not pass through client-supplied forwarding headers; set your own
  proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header Host              $host;
}

Key decision: if TLS is terminated before NGINX, then $scheme will be http between LB→NGINX; in that case you should (a) restrict who can reach NGINX to the LB, and (b) pass the LB’s X-Forwarded-Proto onward only because it’s coming from a trusted source.

Sources: MDN on X-Forwarded-Proto [1]; NGINX Real IP module (set_real_ip_from, real_ip_header, real_ip_recursive) [2]; NGINX proxy module (proxy_set_header, $proxy_add_x_forwarded_for) [3]; RFC 7239 and its security considerations [4]; OWASP testing guide mentioning X-Forwarded-* header manipulation targets [5]; Django docs on trusting X-Forwarded-Proto only when it comes from your proxy [6].


🏁 Script executed:

cat -n nginx/nginx.conf

Repository: LucasSantana-Dev/Lucky

Length of output: 2125


Configure trusted proxy validation or use $scheme instead of client-supplied X-Forwarded-Proto.

This configuration preserves client-provided X-Forwarded-Proto headers without validation. Since nginx.conf lacks set_real_ip_from directives to declare trusted proxies, any client can inject this header. The backend uses it to construct OAuth redirect URIs—an attacker injecting X-Forwarded-Proto: http could force insecure redirects.

Replace the map with:

map $http_x_forwarded_proto $proxy_x_forwarded_proto {
    default $scheme;
}

Or, if nginx sits behind a trusted proxy (Cloudflare, AWS ALB, etc.), add trusted proxy validation:

set_real_ip_from <proxy_subnet>;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

Then configure the app/backend to only trust X-Forwarded-Proto when coming from nginx.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nginx/nginx.conf` around lines 3 - 6, The map definition for
proxy_x_forwarded_proto currently trusts client-supplied
$http_x_forwarded_proto; change it so the map uses $scheme as the default (i.e.,
always derive proto from nginx's scheme) or, if you must accept upstream
X-Forwarded-Proto, declare trusted proxies by adding set_real_ip_from entries
for your proxy subnets and enable real_ip_header and real_ip_recursive so nginx
only uses X-Forwarded-* from those proxies; also ensure the backend only trusts
X-Forwarded-Proto when coming from the validated proxy chain. Reference the map
block (map $http_x_forwarded_proto $proxy_x_forwarded_proto) and the directives
set_real_ip_from, real_ip_header, and real_ip_recursive when applying the fix.

Comment on lines +194 to +200
touch(
sid: string,
sessionData: session.SessionData,
callback: () => void = () => {},
): void {
this.execute('touch', [sid, sessionData], callback as SessionCallback)
}

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

Touch callback signature mismatch.

The touch method's callback type is () => void, but execute calls it with error/data arguments via SessionCallback. While this works at runtime (extra arguments are ignored), the type inconsistency could cause confusion.

Suggested fix for type consistency
     touch(
         sid: string,
         sessionData: session.SessionData,
-        callback: () => void = () => {},
+        callback: (error?: unknown) => void = () => {},
     ): void {
         this.execute('touch', [sid, sessionData], callback as SessionCallback)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
touch(
sid: string,
sessionData: session.SessionData,
callback: () => void = () => {},
): void {
this.execute('touch', [sid, sessionData], callback as SessionCallback)
}
touch(
sid: string,
sessionData: session.SessionData,
callback: (error?: unknown) => void = () => {},
): void {
this.execute('touch', [sid, sessionData], callback as SessionCallback)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/middleware/session.ts` around lines 194 - 200, The touch
method declares its callback as () => void but passes it to execute which
expects a SessionCallback (error/data arguments), causing a type mismatch;
update touch's signature to accept a SessionCallback (e.g., callback:
SessionCallback = () => {}) and keep the default no-op but typed correctly, or
alternatively adapt the call to wrap the provided zero-arg callback into a
SessionCallback wrapper before calling this.execute('touch', [sid, sessionData],
...), referencing the touch method, execute method, and
SessionCallback/session.SessionData types to locate and fix the inconsistency.

Comment on lines +3 to +4
const CANONICAL_PRODUCTION_REDIRECT_URI =
'https://lucky-api.lucassantana.tech/api/auth/callback'

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 | 🟠 Major

Remove the hard-coded production callback origin.

If WEBAPP_BACKEND_URL is missing or malformed in a production-like deployment, this silently sends OAuth callbacks to lucky-api.lucassantana.tech. That can break auth for every non-canonical deployment and route authorization codes to the wrong backend. Fail fast here, or read the canonical backend origin from validated shared config instead of hardcoding a host.

🔧 Safer direction
-const CANONICAL_PRODUCTION_REDIRECT_URI =
-    'https://lucky-api.lucassantana.tech/api/auth/callback'
-
 const getCanonicalProductionRedirectUri = (): string => {
     const backendUrl = process.env.WEBAPP_BACKEND_URL
-    if (!backendUrl) {
-        return CANONICAL_PRODUCTION_REDIRECT_URI
-    }
-
-    try {
-        return new URL('/api/auth/callback', backendUrl).toString()
-    } catch {
-        return CANONICAL_PRODUCTION_REDIRECT_URI
-    }
+    if (!backendUrl) {
+        throw new Error('WEBAPP_BACKEND_URL must be configured in production')
+    }
+
+    return new URL('/api/auth/callback', backendUrl).toString()
 }

As per coding guidelines "Store secrets, ports, and hosts in environment variables (.env, .env.example) and never hardcode them".

Also applies to: 43-53

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/utils/oauthRedirectUri.ts` around lines 3 - 4, The file
defines a hard-coded CANONICAL_PRODUCTION_REDIRECT_URI which must be removed:
read the canonical backend origin from a validated source (e.g.,
WEBAPP_BACKEND_URL or the shared config) instead of using
'https://lucky-api.lucassantana.tech', validate it as a well-formed URL/origin,
and use its origin to build the OAuth callback; update any code that references
CANONICAL_PRODUCTION_REDIRECT_URI (and the logic around lines referencing 43-53)
to throw/fail-fast if the env/shared-config value is missing or malformed in
production-like environments so callbacks never silently go to the hard-coded
host.

Comment on lines +145 to +165
test('should resolve callback redirect uri from forwarded host when env is unset', async () => {
const originalRedirectUri = process.env.WEBAPP_REDIRECT_URI
delete process.env.WEBAPP_REDIRECT_URI

const mockSessionService = sessionService as jest.Mocked<
typeof sessionService
>
mockSessionService.setSession.mockResolvedValue()
mockSuccessfulOAuthFlow()

const response = await request(app)
await request(app)
.get('/api/auth/callback')
.query({ code: MOCK_AUTH_CODE })
.set('Cookie', ['sessionId=callback_session_id'])
.set('x-forwarded-proto', 'https')
.set('x-forwarded-host', 'lucky.lucassantana.tech')
.expect(302)

expect(response.headers.location).toContain('authenticated=true')
expect(mockDiscordOAuth.exchangeCodeForToken).toHaveBeenCalledWith(
expect(getDiscordOAuthMock().exchangeCodeForToken).toHaveBeenCalledWith(
MOCK_AUTH_CODE,
'https://lucky.lucassantana.tech/api/auth/callback',
)
expect(mockDiscordOAuth.getUserInfo).toHaveBeenCalledWith(
MOCK_TOKEN_RESPONSE.access_token,
)
expect(mockSessionService.setSession).toHaveBeenCalled()

if (originalRedirectUri) {
process.env.WEBAPP_REDIRECT_URI = originalRedirectUri
}

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

Restore WEBAPP_REDIRECT_URI in finally here too.

If the request or assertion fails, this test leaves global env state mutated and can poison the rest of the suite.

Suggested fix
         test('should resolve callback redirect uri from forwarded host when env is unset', async () => {
             const originalRedirectUri = process.env.WEBAPP_REDIRECT_URI
             delete process.env.WEBAPP_REDIRECT_URI

-            mockSuccessfulOAuthFlow()
-
-            await request(app)
-                .get('/api/auth/callback')
-                .query({ code: MOCK_AUTH_CODE })
-                .set('x-forwarded-proto', 'https')
-                .set('x-forwarded-host', 'lucky.lucassantana.tech')
-                .expect(302)
-
-            expect(getDiscordOAuthMock().exchangeCodeForToken).toHaveBeenCalledWith(
-                MOCK_AUTH_CODE,
-                'https://lucky.lucassantana.tech/api/auth/callback',
-            )
-
-            if (originalRedirectUri) {
-                process.env.WEBAPP_REDIRECT_URI = originalRedirectUri
-            }
+            try {
+                mockSuccessfulOAuthFlow()
+
+                await request(app)
+                    .get('/api/auth/callback')
+                    .query({ code: MOCK_AUTH_CODE })
+                    .set('x-forwarded-proto', 'https')
+                    .set('x-forwarded-host', 'lucky.lucassantana.tech')
+                    .expect(302)
+
+                expect(
+                    getDiscordOAuthMock().exchangeCodeForToken,
+                ).toHaveBeenCalledWith(
+                    MOCK_AUTH_CODE,
+                    'https://lucky.lucassantana.tech/api/auth/callback',
+                )
+            } finally {
+                if (originalRedirectUri) {
+                    process.env.WEBAPP_REDIRECT_URI = originalRedirectUri
+                } else {
+                    delete process.env.WEBAPP_REDIRECT_URI
+                }
+            }
         })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/tests/integration/routes/auth.test.ts` around lines 145 -
165, The test "should resolve callback redirect uri from forwarded host when env
is unset" mutates process.env.WEBAPP_REDIRECT_URI but only restores it
conditionally at the end; wrap the request/assertion logic in a try/finally and
restore process.env.WEBAPP_REDIRECT_URI in the finally block so the original
value is always reset (keep references to process.env.WEBAPP_REDIRECT_URI,
mockSuccessfulOAuthFlow(), and the assertion using
getDiscordOAuthMock().exchangeCodeForToken intact).

Comment on lines +1 to +25
import { describe, test, expect } from '@jest/globals'
import express from 'express'
import { setupMiddleware } from '../../../src/middleware'

describe('Middleware setup', () => {
test('should enable trust proxy in production', () => {
const originalNodeEnv = process.env.NODE_ENV
process.env.NODE_ENV = 'production'
const app = express()

setupMiddleware(app)

expect(app.get('trust proxy')).toBe(1)
process.env.NODE_ENV = originalNodeEnv
})

test('should not force trust proxy outside production', () => {
const originalNodeEnv = process.env.NODE_ENV
process.env.NODE_ENV = 'test'
const app = express()

setupMiddleware(app)

expect(app.get('trust proxy')).not.toBe(1)
process.env.NODE_ENV = originalNodeEnv

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

Always restore NODE_ENV, even when the test fails.

If setupMiddleware(app) or an assertion throws, the mutated env leaks into later tests. An afterEach or try/finally keeps the suite isolated.

🧪 Safer test isolation
-import { describe, test, expect } from '@jest/globals'
+import { describe, test, expect, afterEach } from '@jest/globals'
 import express from 'express'
 import { setupMiddleware } from '../../../src/middleware'
 
 describe('Middleware setup', () => {
+    const originalNodeEnv = process.env.NODE_ENV
+
+    afterEach(() => {
+        process.env.NODE_ENV = originalNodeEnv
+    })
+
     test('should enable trust proxy in production', () => {
-        const originalNodeEnv = process.env.NODE_ENV
         process.env.NODE_ENV = 'production'
         const app = express()
 
         setupMiddleware(app)
 
         expect(app.get('trust proxy')).toBe(1)
-        process.env.NODE_ENV = originalNodeEnv
     })
 
     test('should not force trust proxy outside production', () => {
-        const originalNodeEnv = process.env.NODE_ENV
         process.env.NODE_ENV = 'test'
         const app = express()
 
         setupMiddleware(app)
 
         expect(app.get('trust proxy')).not.toBe(1)
-        process.env.NODE_ENV = originalNodeEnv
     })
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/tests/unit/middleware/index.test.ts` around lines 1 - 25,
The tests mutate process.env.NODE_ENV but don't guarantee restoration on
failure; update the test file so NODE_ENV is always restored by wrapping each
test's mutation in a try/finally or add an afterEach hook that resets NODE_ENV,
referencing the existing tests that call setupMiddleware(app) and assert
app.get('trust proxy') so the originalNodeEnv captured before mutation is
reinstated unconditionally.

Comment on lines +87 to 93
if (
status === 401 &&
typeof globalThis !== 'undefined' &&
'window' in globalThis
) {
globalThis.window.location.assign('/api/auth/discord')
}

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 | 🟠 Major

Inconsistent OAuth redirect paths may break homeserver deployments.

On homeserver hosts (e.g., panel.luk-homeserver.com.br), NORMALIZED_API_BASE resolves to https://api.luk-homeserver.com.br/api. The explicit login via getDiscordLoginUrl() (line 115) returns the full URL, but this 401 interceptor redirects to the relative path /api/auth/discord, which resolves to the current origin instead.

This creates two different OAuth entry points:

  • Manual login: https://api.luk-homeserver.com.br/api/auth/discord
  • 401 redirect: https://panel.luk-homeserver.com.br/api/auth/discord

Consider using the same base URL for consistency:

Proposed fix
         if (
             status === 401 &&
             typeof globalThis !== 'undefined' &&
             'window' in globalThis
         ) {
-            globalThis.window.location.assign('/api/auth/discord')
+            globalThis.window.location.assign(`${NORMALIZED_API_BASE}/auth/discord`)
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/services/api.ts` around lines 87 - 93, The 401
interceptor in services/api.ts currently redirects using a relative path
(globalThis.window.location.assign('/api/auth/discord')), causing inconsistent
OAuth entry points versus the explicit login URL returned by
getDiscordLoginUrl() and NORMALIZED_API_BASE; update the interceptor to build
the redirect using the same base as NORMALIZED_API_BASE (or simply
call/getDiscordLoginUrl() to obtain the full URL) and then call
window.location.assign(...) with that full URL so both manual login and
401-triggered login use the identical OAuth endpoint.

Comment on lines 113 to 115
await page.route('**/api/guilds', async (route) => {
await page.waitForTimeout(1000)
await new Promise((resolve) => setTimeout(resolve, 1000))
await route.continue()

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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Playwright 1.57, when multiple page.route('**/api/guilds')handlers match the same request, doesroute.continue()go straight to the network whileroute.fallback() passes control to the next matching handler?

💡 Result:

Yes.

  • route.continue() immediately sends the request to the network, and no other matching route handlers will run. [1]
  • route.fallback() defers to the next matching handler in the chain (and only after all applicable handlers does it go to the network). [1]

Source:
[1] Playwright API docs — Route.continue() / Route.fallback() (playwright.dev)

Citations:


Use route.fallback() instead of route.continue() to chain route handlers properly.

The setupMockApiResponses(page) call in beforeEach already registers a handler for /api/guilds. Calling route.continue() sends the request directly to the network and skips the existing mock handler entirely. Use route.fallback() to pass control to the next matching handler in the chain instead.

Suggested fix
         await page.route('**/api/guilds', async (route) => {
             await new Promise((resolve) => setTimeout(resolve, 1000))
-            await route.continue()
+            await route.fallback()
         })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await page.route('**/api/guilds', async (route) => {
await page.waitForTimeout(1000)
await new Promise((resolve) => setTimeout(resolve, 1000))
await route.continue()
await page.route('**/api/guilds', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
await route.fallback()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/tests/e2e/servers-page.spec.ts` around lines 113 - 115, The
test registers an extra page.route handler that calls route.continue(), which
bypasses the existing mock installed by setupMockApiResponses(page); replace
route.continue() with route.fallback() in the anonymous handler (the async route
=> { ... } callback) so the request is passed to the next matching handler in
the chain, preserving the existing mock for '/api/guilds' after the artificial
delay.

@sonarqubecloud

Copy link
Copy Markdown

@sonarqubecloud

Copy link
Copy Markdown

@LucasSantana-Dev
LucasSantana-Dev merged commit 663cc8e into main Mar 10, 2026
16 checks passed
@LucasSantana-Dev
LucasSantana-Dev deleted the fix/login-oauth-session-origin branch March 15, 2026 14:09
LucasSantana-Dev added a commit that referenced this pull request May 13, 2026
…131)

* fix(auth): stabilize oauth redirect uri and e2e selectors

* fix(auth): adapt redis sessions and enforce same-origin api base

* fix(frontend): reduce sonar duplication and harden browser redirect checks

* fix(auth): harden callback canonicalization and proxy/session middleware

* fix(ci): address sonar hotspot and duplication in auth/session changes

* test(auth): increase coverage for session fallback and api client
LucasSantana-Dev added a commit that referenced this pull request Jul 10, 2026
…1761)

Closes the two real gaps found by gap analysis against web-app issue
#131 (everything else already shipped in PR #1609): trigger moved
Monday→Sunday 12:00 UTC (9h BRT) with the week-window + idempotency math
re-anchored coherently, and a 'Novos guias da semana' section fed from
the web-app guides feed (≤3 bullets, omitted when empty, fail-soft on
fetch error — digest always sends). 29/29 spec tests; full bot suite
2637/0. Cross-repo: Criativaria-Projects/web-app#131.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Moves the weekly digest to Sunday 12:00 UTC and adds a “Novos guias da
semana” section powered by the guides RSS feed. Re-anchors the week
window to Sunday to keep idempotency correct and closes gaps in
`Criativaria-Projects/web-app#131`.

- **New Features**
- Trigger now runs on Sunday at 12:00 UTC; week math anchored to Sunday.
- Fetch up to 3 new guides from `CRIATIVARIA_GUIDES_FEED_URL` via
`rss-parser` (last 7 days); render title + link.
- Omit the guides section when empty and fail soft on fetch errors
(digest still sends).

- **Bug Fixes**
- Validate RSS dates; skip undated, unparsable, future, and
out-of-window items.
  - Handle unsorted feeds without dropping newer in-window items.
- Enforce Discord embed limits: truncate titles to 80 chars with
ellipsis and cap field value at 1024 chars; skip only the overlong
bullet and keep later items.

<sup>Written for commit 12b484c.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/LucasSantana-Dev/Lucky/pull/1761?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Weekly digests are now sent on Sundays at 12:00 UTC.
* Digests can include a “New guides this week” section (up to three RSS
items), shown only when available.
* Guide titles are trimmed to fit embed limits, with an overall guides
field size cap.
* **Bug Fixes**
* RSS feed problems no longer block digests; the guides section is
omitted and an error is logged.
* Duplicate weekly digests prevention now aligns with the
Sunday-anchored schedule.
* **Tests**
* Expanded coverage for RSS-guides inclusion/exclusion and fail-soft
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

This branch was successfully deployed

1 active deployment
Preview — 74f9336a Deployed Mar 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant