Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORS Configuration for Public API #11512

Merged
merged 6 commits into from
Mar 21, 2025
Merged

CORS Configuration for Public API #11512

merged 6 commits into from
Mar 21, 2025

Conversation

flvndvd
Copy link
Contributor

@flvndvd flvndvd commented Mar 20, 2025

This PR implements proper CORS handling for our public API endpoints (/v1/*) while maintaining existing development environment CORS support.

Context

CORS (Cross-Origin Resource Sharing) is a security feature implemented by browsers that prevents web pages from making requests to a different domain than the one that served the web page. Until now, our public API (/v1/*) has been primarily used by:

  • Backend services (where CORS doesn't apply)
  • Browser extensions via service workers (which are exempt from CORS restrictions)

However, as we're building a new Front extension to have Dust directly in the web application, we've discovered that our API wasn't properly handling CORS. This prevented the extension from making requests directly from browser-based JavaScript code.

Problem

When a web page from front-ext.dust.tt tries to make a request to dust.tt/v1/..., browsers block the request unless the server explicitly allows it through CORS headers. This is a security measure to prevent malicious websites from making unauthorized requests to our API on behalf of authenticated users.

Without proper CORS headers:

  • Requests from our frontend extension were being blocked by browsers
  • The browser's preflight (OPTIONS) requests were failing
  • Authenticated requests (with credentials) weren't possible

Solution

This PR implements proper CORS handling by:

  1. Creating a centralized CORS configuration
  2. Adding necessary CORS headers for both preflight and actual requests
  3. Implementing strict origin validation for security
  4. Maintaining existing development environment setup

Changes

CORS Configuration

  • Created a new config/cors.ts file to centralize CORS configuration
  • Implemented type-safe origin validation using TypeScript's type system
  • Limited allowed origins to only front-ext.dust.tt for production
  • Maintained development environment CORS settings for localhost:3010

Middleware Updates

  • Added specific CORS handling for /v1 routes
  • Separated CORS header logic into dedicated functions:
    • setCorsHeaders() for public API endpoints with origin validation
    • setDevCorsHeaders() for development environment
  • Clear separation between preflight and actual requests
  • Explicit error handling with 403 responses for:
    • Missing origin header
    • Unauthorized origins
    • Unauthorized headers in preflight requests
  • Type-safe response handling

Technical Details

The CORS implementation follows security best practices:

  • No wildcard (*) origins allowed
  • Origin validation using TypeScript type checking
  • Credentials only enabled for validated origins
  • Explicit allowed methods and headers
  • Proper handling of preflight requests

Tests

Risk

Deploy Plan

@flvndvd flvndvd marked this pull request as ready for review March 20, 2025 20:53
return ALLOWED_ORIGINS.includes(origin as AllowedOriginType);
}

export const DEV_ORIGIN = "http://localhost:3010" as const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the front plugin url in dev ? In development it may be nice to accept any origin starting with http://localhost (any port)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, happy to change for a startsWith then.

if (isDevelopment() && origin === DEV_ORIGIN) {
response.headers.set("Access-Control-Allow-Origin", DEV_ORIGIN);
response.headers.set("Access-Control-Allow-Credentials", "true");
} else if (origin && isAllowedOrigin(origin)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

both branches are similar, we can use a single test ( second one with the requested origin )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Merged.

"Access-Control-Allow-Headers",
"Authorization, Content-Type, X-Request-Origin, x-Commit-Hash, X-Dust-Extension-Version"
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

at some point the middleware should return a 403 if the origin is not allowed or if it request a non allowed header in Access-Control-Request-Headers

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤔 ah yeah indeed, when I tried I got a 403, because I was not_authenticated but let me add a proper error handler for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. Let me know if this is what you had in mind.

Copy link
Contributor

@tdraier tdraier left a comment

Choose a reason for hiding this comment

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

just missing the origin.startsWith(DEV_ORIGIN) instead of === , but looks good 👍

Copy link
Contributor

@spolu spolu left a comment

Choose a reason for hiding this comment

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

Let's a few comments. Nitpicky but this is high security risk.

I remember we had cors stuff in next config or our infra? Did you clean them up?

Surprised that there isn't a lib for that? When it comes to security a lib from trusted providers is always better than custom code, did you check?

response: NextResponse,
request: NextRequest
): NextResponse {
const corsResponse = setCorsHeaders(response, request);
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's call it corsErrorResponse

?.toLowerCase();

// If this is a preflight request checking headers.
if (request.method === "OPTIONS" && requestHeaders) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If OPTIONS and no header we should 403 as well instead of blanket 200?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding was that an OPTIONS request without CORS-specific headers is likely a regular HTTP OPTIONS request (checking which HTTP methods are supported). These are valid HTTP requests that should receive a 200 OK with appropriate Allow headers


response.headers.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS"
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we put OPTIONS here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See comment from above.

"content-type",
"x-request-origin",
"x-commit-hash",
"x-dust-extension-version",
Copy link
Contributor

Choose a reason for hiding this comment

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

We also allow a header for hacker one researchers (see our vdp documentation)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, added.

// Cannot use helper functions like isDevelopment() in Edge Runtime middleware since they are not
// bundled. Must check NODE_ENV directly.
const isDevOrigin =
process.env.NODE_ENV === "development" && origin === DEV_ORIGIN;
Copy link
Contributor

Choose a reason for hiding this comment

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

We can get rid of DEV_ORIGIN no? Less code is better for security we just allow all in dev

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@flvndvd
Copy link
Contributor Author

flvndvd commented Mar 21, 2025

I remember we had cors stuff in next config or our infra? Did you clean them up?
As far as I remember we did not have any.

Surprised that there isn't a lib for that? When it comes to security a lib from trusted providers is always better than custom code, did you check?

I did check for existing libraries, all of them are for expressJS, I found only one for NextJs that did not convince me.

Most resources out there will tell you to use the middleware approach for NextJs (example).

Copy link
Contributor

@spolu spolu left a comment

Choose a reason for hiding this comment

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

LGTM

@flvndvd flvndvd merged commit f03c150 into main Mar 21, 2025
6 checks passed
@flvndvd flvndvd deleted the flav/public-api-cors branch March 21, 2025 10:03
flvndvd added a commit that referenced this pull request Mar 21, 2025
@flvndvd flvndvd restored the flav/public-api-cors branch March 21, 2025 13:09
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.

3 participants