mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Merge dev into update-oauth-docs
This commit is contained in:
commit
fbe3bdb577
@ -9,6 +9,10 @@ import type { NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { SmartRouter } from './smart-router';
|
||||
|
||||
const DEV_RATE_LIMIT_MAX_REQUESTS = 30;
|
||||
const DEV_RATE_LIMIT_WINDOW_MS = 10_000;
|
||||
const devRateLimitTimestamps: number[] = [];
|
||||
|
||||
const corsAllowedRequestHeaders = [
|
||||
// General
|
||||
'content-type',
|
||||
@ -65,6 +69,50 @@ export async function middleware(request: NextRequest) {
|
||||
const url = new URL(request.url);
|
||||
const isApiRequest = url.pathname.startsWith('/api/');
|
||||
|
||||
const corsHeadersInit = isApiRequest ? {
|
||||
// CORS headers
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
||||
"Access-Control-Max-Age": "86400", // 1 day (capped to lower values, eg. 10min, by some browsers)
|
||||
"Access-Control-Allow-Headers": corsAllowedRequestHeaders.join(', '),
|
||||
"Access-Control-Expose-Headers": corsAllowedResponseHeaders.join(', '),
|
||||
} : undefined;
|
||||
|
||||
// ensure our clients can handle 429 responses
|
||||
if (isApiRequest && getNodeEnvironment() === 'development' && request.method !== 'OPTIONS') {
|
||||
const now = Date.now();
|
||||
while (devRateLimitTimestamps.length > 0 && now - devRateLimitTimestamps[0] > DEV_RATE_LIMIT_WINDOW_MS) {
|
||||
devRateLimitTimestamps.shift();
|
||||
}
|
||||
console.log('devRateLimitTimestamps', devRateLimitTimestamps.length);
|
||||
if (devRateLimitTimestamps.length >= DEV_RATE_LIMIT_MAX_REQUESTS) {
|
||||
const waitMs = Math.max(0, DEV_RATE_LIMIT_WINDOW_MS - (now - devRateLimitTimestamps[0]));
|
||||
const retryAfterSeconds = Math.max(1, Math.ceil(waitMs / 1000));
|
||||
|
||||
const response = NextResponse.json({
|
||||
message: 'Artificial development rate limit triggered. Wait before retrying.',
|
||||
}, {
|
||||
status: 429,
|
||||
});
|
||||
|
||||
// since not all firewalls return CORS headers with their 429 responses, 50% chance that we don't set the CORS headers
|
||||
if (Math.random() < 0.5 && corsHeadersInit) {
|
||||
for (const [key, value] of Object.entries(corsHeadersInit)) {
|
||||
response.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.random() < 0.5) {
|
||||
// for debugging, make sure we don't always set the Retry-After header
|
||||
response.headers.set('Retry-After', retryAfterSeconds.toString());
|
||||
}
|
||||
|
||||
return response;
|
||||
} else {
|
||||
devRateLimitTimestamps.push(now);
|
||||
}
|
||||
}
|
||||
|
||||
const newRequestHeaders = new Headers(request.headers);
|
||||
// here we could update the request headers (currently we don't)
|
||||
|
||||
@ -72,14 +120,7 @@ export async function middleware(request: NextRequest) {
|
||||
request: {
|
||||
headers: newRequestHeaders,
|
||||
},
|
||||
headers: {
|
||||
// CORS headers
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
||||
"Access-Control-Max-Age": "86400", // 1 day (capped to lower values, eg. 10min, by some browsers)
|
||||
"Access-Control-Allow-Headers": corsAllowedRequestHeaders.join(', '),
|
||||
"Access-Control-Expose-Headers": corsAllowedResponseHeaders.join(', '),
|
||||
},
|
||||
headers: corsHeadersInit,
|
||||
} as const : undefined;
|
||||
|
||||
// we want to allow preflight requests to pass through
|
||||
|
||||
@ -21,6 +21,7 @@ import { CurrentUserCrud } from './crud/current-user';
|
||||
import { ItemCrud } from './crud/items';
|
||||
import { NotificationPreferenceCrud } from './crud/notification-preferences';
|
||||
import { OAuthProviderCrud } from './crud/oauth-providers';
|
||||
import { CustomerProductsListResponse, ListCustomerProductsOptions } from './crud/products';
|
||||
import { TeamApiKeysCrud, UserApiKeysCrud, teamApiKeysCreateInputSchema, teamApiKeysCreateOutputSchema, userApiKeysCreateInputSchema, userApiKeysCreateOutputSchema } from './crud/project-api-keys';
|
||||
import { ProjectPermissionsCrud } from './crud/project-permissions';
|
||||
import { AdminUserProjectsCrud, ClientProjectsCrud } from './crud/projects';
|
||||
@ -29,7 +30,6 @@ import { TeamInvitationCrud } from './crud/team-invitation';
|
||||
import { TeamMemberProfilesCrud } from './crud/team-member-profiles';
|
||||
import { TeamPermissionsCrud } from './crud/team-permissions';
|
||||
import { TeamsCrud } from './crud/teams';
|
||||
import { CustomerProductsListResponse, ListCustomerProductsOptions } from './crud/products';
|
||||
|
||||
export type ClientInterfaceOptions = {
|
||||
clientVersion: string,
|
||||
@ -156,29 +156,33 @@ export class StackClientInterface {
|
||||
token_endpoint_auth_method: 'client_secret_post',
|
||||
};
|
||||
|
||||
const rawResponse = await this._networkRetryException(
|
||||
async () => await oauth.refreshTokenGrantRequest(
|
||||
const response = await this._networkRetryException(async () => {
|
||||
const rawResponse = await oauth.refreshTokenGrantRequest(
|
||||
as,
|
||||
client,
|
||||
refreshToken.token,
|
||||
)
|
||||
);
|
||||
const response = await this._processResponse(rawResponse);
|
||||
);
|
||||
|
||||
if (response.status === "error") {
|
||||
const error = response.error;
|
||||
if (KnownErrors.RefreshTokenError.isInstance(error)) {
|
||||
return null;
|
||||
const response = await this._processResponse(rawResponse);
|
||||
|
||||
if (response.status === "error") {
|
||||
const error = response.error;
|
||||
if (KnownErrors.RefreshTokenError.isInstance(error)) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!response.data.ok) {
|
||||
const body = await response.data.text();
|
||||
throw new Error(`Failed to send refresh token request: ${response.status} ${body}`);
|
||||
}
|
||||
if (!response.data.ok) {
|
||||
const body = await response.data.text();
|
||||
throw new Error(`Failed to send refresh token request: ${response.status} ${body}`);
|
||||
}
|
||||
|
||||
const result = await oauth.processRefreshTokenResponse(as, client, response.data);
|
||||
return response.data;
|
||||
});
|
||||
if (!response) return null;
|
||||
|
||||
const result = await oauth.processRefreshTokenResponse(as, client, response);
|
||||
if (oauth.isOAuth2Error(result)) {
|
||||
// TODO Handle OAuth 2.0 response body error
|
||||
throw new StackAssertionError("OAuth error", { result });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user