-
Notifications
You must be signed in to change notification settings - Fork 139
CORS Configuration for Public API #11512
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const ALLOWED_ORIGINS = ["https://front-ext.dust.tt"] as const; | ||
type AllowedOriginType = (typeof ALLOWED_ORIGINS)[number]; | ||
|
||
export function isAllowedOrigin(origin: string): origin is AllowedOriginType { | ||
return ALLOWED_ORIGINS.includes(origin as AllowedOriginType); | ||
} | ||
|
||
export const ALLOWED_HEADERS = [ | ||
"authorization", | ||
"content-type", | ||
"x-commit-hash", | ||
"x-dust-extension-version", | ||
"x-hackerone-research", | ||
"x-request-origin", | ||
] as const; | ||
type AllowedHeaderType = (typeof ALLOWED_HEADERS)[number]; | ||
|
||
export function isAllowedHeader(header: string): header is AllowedHeaderType { | ||
return ALLOWED_HEADERS.includes(header as AllowedHeaderType); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,12 @@ | ||
import type { NextRequest } from "next/server"; | ||
import { NextResponse } from "next/server"; | ||
|
||
import { | ||
ALLOWED_HEADERS, | ||
isAllowedHeader, | ||
isAllowedOrigin, | ||
} from "@app/config/cors"; | ||
|
||
export function middleware(request: NextRequest) { | ||
const url = request.nextUrl.pathname; | ||
|
||
|
@@ -49,9 +55,94 @@ export function middleware(request: NextRequest) { | |
}); | ||
} | ||
|
||
// Handle CORS only for public API endpoints. | ||
if (url.startsWith("/api/v1")) { | ||
if (request.method === "OPTIONS") { | ||
// Handle preflight request. | ||
const response = new NextResponse(null, { status: 200 }); | ||
return handleCors(response, request); | ||
} | ||
|
||
// Handle actual request. | ||
const response = NextResponse.next(); | ||
return handleCors(response, request); | ||
} | ||
|
||
return NextResponse.next(); | ||
} | ||
|
||
function handleCors( | ||
response: NextResponse, | ||
request: NextRequest | ||
): NextResponse { | ||
const corsResponseError = setCorsHeaders(response, request); | ||
if (corsResponseError) { | ||
// If setCorsHeaders returned a response, it's an error. | ||
return corsResponseError; | ||
} | ||
|
||
return response; | ||
} | ||
|
||
function setCorsHeaders( | ||
response: NextResponse, | ||
request: NextRequest | ||
): NextResponse | undefined { | ||
const origin = request.headers.get("origin"); | ||
const requestHeaders = request.headers | ||
.get("access-control-request-headers") | ||
?.toLowerCase(); | ||
|
||
// If this is a preflight request checking headers. | ||
if (request.method === "OPTIONS" && requestHeaders) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding was that an |
||
const requestedHeaders = requestHeaders.split(",").map((h) => h.trim()); | ||
const hasUnallowedHeader = requestedHeaders.some( | ||
(header) => !isAllowedHeader(header) | ||
); | ||
|
||
if (hasUnallowedHeader) { | ||
return new NextResponse(null, { | ||
status: 403, | ||
statusText: "Forbidden: Unauthorized Headers", | ||
}); | ||
} | ||
} | ||
|
||
// Check origin. | ||
if (!origin) { | ||
return new NextResponse(null, { | ||
status: 403, | ||
statusText: "Forbidden: Missing Origin", | ||
}); | ||
} | ||
|
||
// Check if origin is allowed (prod or dev). | ||
|
||
// Cannot use helper functions like isDevelopment() in Edge Runtime middleware since they are not | ||
// bundled. Must check NODE_ENV directly. | ||
const isDevelopment = process.env.NODE_ENV === "development"; | ||
if (isDevelopment || isAllowedOrigin(origin)) { | ||
response.headers.set("Access-Control-Allow-Origin", origin); | ||
response.headers.set("Access-Control-Allow-Credentials", "true"); | ||
} else { | ||
return new NextResponse(null, { | ||
status: 403, | ||
statusText: "Forbidden: Unauthorized Origin", | ||
}); | ||
} | ||
|
||
response.headers.set( | ||
"Access-Control-Allow-Methods", | ||
"GET, POST, PUT, DELETE, OPTIONS" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we put OPTIONS here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment from above. |
||
); | ||
response.headers.set( | ||
"Access-Control-Allow-Headers", | ||
ALLOWED_HEADERS.join(", ") | ||
); | ||
|
||
return undefined; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated. Let me know if this is what you had in mind. |
||
|
||
export const config = { | ||
matcher: "/:path*", | ||
}; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, added.