mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
import { getEnvVariable, getNodeEnvironment } from '@hexclave/shared/dist/utils/env';
|
|
import './polyfills';
|
|
|
|
import { HexclaveAssertionError } from '@hexclave/shared/dist/utils/errors';
|
|
import { wait } from '@hexclave/shared/dist/utils/promises';
|
|
import type { NextRequest } from 'next/server';
|
|
import { NextResponse } from 'next/server';
|
|
|
|
const corsAllowedRequestHeaders = [
|
|
// General
|
|
'authorization',
|
|
'content-type',
|
|
'x-stack-project-id',
|
|
'x-stack-override-error-status',
|
|
'x-stack-random-nonce', // used to forcefully disable some caches
|
|
'x-stack-client-version',
|
|
'x-stack-disable-artificial-development-delay',
|
|
|
|
// Project auth
|
|
'x-stack-request-type',
|
|
'x-stack-publishable-client-key',
|
|
'x-stack-secret-server-key',
|
|
'x-stack-super-secret-admin-key',
|
|
'x-stack-admin-access-token',
|
|
|
|
// User auth
|
|
'x-stack-refresh-token',
|
|
'x-stack-access-token',
|
|
];
|
|
|
|
const corsAllowedResponseHeaders = [
|
|
'content-type',
|
|
'x-stack-actual-status',
|
|
'x-stack-known-error',
|
|
];
|
|
|
|
// Hexclave rebrand: every `x-stack-*` header is dual-accepted under its `x-hexclave-*` equivalent.
|
|
// Derive the alias names so the CORS allowlists never drift.
|
|
function withHexclaveHeaderAliases(headers: string[]): string[] {
|
|
return headers.flatMap((header) => header.startsWith('x-stack-')
|
|
? [header, `x-hexclave-${header.slice('x-stack-'.length)}`]
|
|
: [header]);
|
|
}
|
|
const corsAllowedRequestHeadersWithAliases = withHexclaveHeaderAliases(corsAllowedRequestHeaders);
|
|
const corsAllowedResponseHeadersWithAliases = withHexclaveHeaderAliases(corsAllowedResponseHeaders);
|
|
|
|
export async function proxy(request: NextRequest) {
|
|
const delay = Number.parseInt(getEnvVariable('STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS', '0'));
|
|
if (delay) {
|
|
if (getNodeEnvironment().includes('production')) {
|
|
throw new HexclaveAssertionError('STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS is only allowed in development');
|
|
}
|
|
if (!request.headers.get('x-stack-disable-artificial-development-delay')) {
|
|
await wait(delay);
|
|
}
|
|
}
|
|
|
|
const url = new URL(request.url);
|
|
const isApiRequest = url.pathname.startsWith('/api/');
|
|
|
|
// Hexclave rebrand: dual-accept request headers — copy each `x-hexclave-*` onto its `x-stack-*`
|
|
// equivalent so downstream API routes that read `x-stack-*` keep working unchanged. The new form
|
|
// wins when both are present.
|
|
const newRequestHeaders = new Headers(request.headers);
|
|
for (const [name, value] of request.headers) {
|
|
if (name.startsWith('x-hexclave-')) {
|
|
newRequestHeaders.set(`x-stack-${name.slice('x-hexclave-'.length)}`, value);
|
|
}
|
|
}
|
|
|
|
// default headers
|
|
const responseInit = {
|
|
request: {
|
|
headers: newRequestHeaders,
|
|
},
|
|
headers: {
|
|
// CORS headers
|
|
...(!isApiRequest ? {} : {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
"Access-Control-Allow-Headers": corsAllowedRequestHeadersWithAliases.join(', '),
|
|
"Access-Control-Expose-Headers": corsAllowedResponseHeadersWithAliases.join(', '),
|
|
}),
|
|
},
|
|
};
|
|
|
|
// we want to allow preflight requests to pass through
|
|
// even if the API route does not implement OPTIONS
|
|
if (request.method === 'OPTIONS' && isApiRequest) {
|
|
return new Response(null, responseInit);
|
|
}
|
|
|
|
return NextResponse.next(responseInit);
|
|
}
|
|
|
|
export const config = {
|
|
matcher: '/:path*',
|
|
};
|