Conversation
…dation - Implemented in-memory rate limiting for authentication requests to mitigate brute force attacks, with stricter limits for sensitive endpoints. - Added redirect URL validation to prevent open redirects, allowing only specified hosts. - Improved logging for rate limit exceedances during development. - Cleaned up old rate limit entries periodically to optimize memory usage. - Updated the OTP form to remove unnecessary API_URL declaration.
- Changed references from AUTH_BASE_URL and BETTER_AUTH_URL to BASE_URL for consistency in cookie domain handling. - Updated comments to clarify the purpose of BASE_URL in relation to OAuth callbacks and cookie domains.
| // BASE_URL must contain the production domain (e.g., api.trycomp.ai) | ||
| // so getCookieDomain() can derive the cross-subdomain cookie domain. | ||
| baseURL: | ||
| process.env.BASE_URL || | ||
| process.env.AUTH_BASE_URL || | ||
| process.env.BETTER_AUTH_URL || | ||
| 'http://localhost:3000', |
There was a problem hiding this comment.
🔴 Bug: BASE_URL semantic conflict breaks auth configuration. Throughout the codebase, BASE_URL refers to the API server URL (defaulting to http://localhost:3333 in .env.example and 5+ other locations), but betterAuth.baseURL historically expects the app URL (port 3000) for OAuth callback routing. Adding BASE_URL as highest priority silently changes baseURL from the app URL to the API URL, which can break OAuth sign-in flows. Additionally, the JSDoc at lines 128-134 still says to use the app URL (e.g., https://app.trycomp.ai), directly contradicting the new inline comment at line 139 that says api.trycomp.ai. Finally, the NestJS startup validator in better-auth.config.ts was not updated to check BASE_URL, so a deployment using only BASE_URL will crash at startup.
Extended reasoning...
What the bug is
This PR adds process.env.BASE_URL as the highest-priority value for better-auth's baseURL configuration (line 142), getCookieDomain() (line 22), and validateSecurityConfig() (line 112). However, BASE_URL has an established semantic meaning throughout the codebase as the API server URL, not the app URL.
Evidence of the semantic conflict
Multiple files in the codebase consistently use BASE_URL to mean the API URL at port 3333:
apps/api/.env.exampleline 1:BASE_URL="http://localhost:3333"apps/api/src/trigger/integration-platform/run-connection-checks.ts:89:const apiUrl = process.env.BASE_URL || 'http://localhost:3333'apps/api/src/trigger/integration-platform/sync-employees-schedule.ts:5:const API_BASE_URL = process.env.BASE_URL || 'http://localhost:3333'apps/api/src/integration-platform/controllers/admin-integrations.controller.ts:124: usesBASE_URLfor API callbacks at port 3333
Meanwhile, the default fallback in auth.server.ts line 145 is still http://localhost:3000 (the app port), and the JSDoc at lines 128-134 (not updated by this PR) still says: "Set AUTH_BASE_URL to the app's URL (e.g., http://localhost:3000 in dev)" and "In production, use the app's public URL (e.g., https://app.trycomp.ai)".
Step-by-step proof of the issue
- A developer clones the repo and uses
apps/api/.env.example, which setsBASE_URL="http://localhost:3333". - Before this PR,
baseURLwould resolve viaAUTH_BASE_URL || BETTER_AUTH_URL || 'http://localhost:3000'— pointing to the app. - After this PR,
baseURLresolves viaBASE_URL || AUTH_BASE_URL || BETTER_AUTH_URL || 'http://localhost:3000'— nowhttp://localhost:3333(the API) takes priority. - better-auth uses
baseURLto generate OAuth callback URLs. These now point to port 3333 (API) instead of port 3000 (app), bypassing the app's auth proxy. - In production, if
BASE_URL=https://api.trycomp.ai, OAuth callbacks would point toapi.trycomp.aiinstead ofapp.trycomp.ai, potentially breaking OAuth sign-in.
Note: The better-auth.config.ts NestJS validator (line 13 comment) does say "AUTH_BASE_URL should point to the API itself", suggesting an architectural shift is underway. However, this PR doesn't complete that migration — the JSDoc is stale, and the intent is ambiguous.
Documentation contradiction
The JSDoc at lines 128-134 says:
Set AUTH_BASE_URL to the app's URL (e.g., http://localhost:3000 in dev)
In production, use the app's public URL (e.g., https://app.trycomp.ai)
The new inline comment at lines 139-140 says:
BASE_URL must contain the production domain (e.g., api.trycomp.ai)
These directly contradict each other. The PR should update the JSDoc to reflect the new architecture.
NestJS validator inconsistency
The NestJS config validator in apps/api/src/config/better-auth.config.ts (line 24) checks process.env.AUTH_BASE_URL || process.env.BETTER_AUTH_URL but does NOT check process.env.BASE_URL. If a deployment sets only BASE_URL (following this PR's new guidance), the API will crash at startup with: "AUTH_BASE_URL or BETTER_AUTH_URL environment variable is required" — even though auth.server.ts would happily use BASE_URL.
How to fix
- Update the JSDoc at lines 128-134 to match the new inline comment, clarifying that
baseURLshould now point to the API. - Update
better-auth.config.tsline 24 to also checkprocess.env.BASE_URL:const url = process.env.BASE_URL || process.env.AUTH_BASE_URL || process.env.BETTER_AUTH_URL. - Verify that OAuth callback URLs work correctly when
baseURLpoints to the API rather than the app, or document the architectural change.
| process.env.BASE_URL || process.env.AUTH_BASE_URL || process.env.BETTER_AUTH_URL || ''; | ||
| if (baseUrl.includes('localhost')) { | ||
| console.warn( | ||
| 'SECURITY WARNING: AUTH_BASE_URL contains "localhost" in production. ' + | ||
| 'SECURITY WARNING: BASE_URL contains "localhost" in production. ' + | ||
| 'This may cause issues with OAuth callbacks and cookies.', | ||
| ); |
There was a problem hiding this comment.
🟡 Nit: The security warning on line 116 always says BASE_URL contains "localhost" but the resolved value may actually come from AUTH_BASE_URL or BETTER_AUTH_URL via the fallback chain. A developer seeing this warning in production could check BASE_URL, find it unset, and be confused about the source of the issue. Consider dynamically identifying which env var matched.
Extended reasoning...
What the bug is
In validateSecurityConfig() (lines 112-117), the code resolves baseUrl using a fallback chain:
const baseUrl = process.env.BASE_URL || process.env.AUTH_BASE_URL || process.env.BETTER_AUTH_URL || '';However, the warning message on line 116 is hardcoded to always reference BASE_URL:
SECURITY WARNING: BASE_URL contains "localhost" in production.
Step-by-step proof
- Suppose
BASE_URLis not set in the environment. - Suppose
AUTH_BASE_URLis set tohttp://localhost:3000. - The
||chain resolvesbaseUrlto"http://localhost:3000"(fromAUTH_BASE_URL). baseUrl.includes('localhost')evaluates totrue.- The warning fires:
SECURITY WARNING: BASE_URL contains "localhost" in production. - A developer reads this, checks
BASE_URL, finds it is not set, and is confused — the actual culprit isAUTH_BASE_URL.
Why existing code doesn't prevent it
The warning message is a simple hardcoded string. There is no logic to determine which environment variable in the fallback chain actually provided the value that matched "localhost". Notably, the old code had a similar issue (it hardcoded AUTH_BASE_URL even when BETTER_AUTH_URL was the source), and this PR changed the label to BASE_URL without addressing the underlying pattern.
Impact
This is low-impact since the warning is a console.warn only visible to operators in a production misconfiguration scenario. The functional behavior (detecting localhost in production) is correct — only the env var name in the message is potentially misleading. However, the whole purpose of a security warning is to help operators quickly diagnose and fix configuration issues, and a misleading message works against that goal.
How to fix
Dynamically identify which env var matched, for example:
const envVarName = process.env.BASE_URL ? 'BASE_URL'
: process.env.AUTH_BASE_URL ? 'AUTH_BASE_URL'
: 'BETTER_AUTH_URL';
console.warn(`SECURITY WARNING: ${envVarName} contains "localhost" in production. ...`);Alternatively, the message could mention all three env vars generically (e.g., "The resolved base URL contains localhost").
|
🎉 This PR is included in version 3.1.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
What does this PR do?
Visual Demo (For contributors especially)
A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).
Video Demo (if applicable):
Image Demo (if applicable):
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Checklist