mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 17:46:39 +00:00
rate limit
This commit is contained in:
@@ -108,6 +108,7 @@ jobs:
|
||||
ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||
CF_RATE_LIMIT_BYPASS_TOKEN: ${{ secrets.CF_RATE_LIMIT_BYPASS_TOKEN }}
|
||||
DOCKER_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||
DOCKER_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
ENV: ${{ inputs.target_domain == 'openfront.io' && 'prod' || 'staging' }}
|
||||
|
||||
@@ -66,6 +66,7 @@ jobs:
|
||||
ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||
CF_RATE_LIMIT_BYPASS_TOKEN: ${{ secrets.CF_RATE_LIMIT_BYPASS_TOKEN }}
|
||||
DOCKER_REPO: openfront-prod
|
||||
DOCKER_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
DOMAIN: ${{ vars.DOMAIN }}
|
||||
@@ -124,6 +125,7 @@ jobs:
|
||||
ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||
CF_RATE_LIMIT_BYPASS_TOKEN: ${{ secrets.CF_RATE_LIMIT_BYPASS_TOKEN }}
|
||||
DOCKER_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||
DOCKER_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
DOMAIN: ${{ vars.DOMAIN }}
|
||||
@@ -182,6 +184,7 @@ jobs:
|
||||
ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||
CF_RATE_LIMIT_BYPASS_TOKEN: ${{ secrets.CF_RATE_LIMIT_BYPASS_TOKEN }}
|
||||
DOCKER_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||
DOCKER_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
DOMAIN: ${{ vars.DOMAIN }}
|
||||
@@ -240,6 +243,7 @@ jobs:
|
||||
ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||
CF_RATE_LIMIT_BYPASS_TOKEN: ${{ secrets.CF_RATE_LIMIT_BYPASS_TOKEN }}
|
||||
DOCKER_REPO: ${{ vars.DOCKERHUB_REPO }}
|
||||
DOCKER_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
DOMAIN: ${{ vars.DOMAIN }}
|
||||
|
||||
@@ -176,6 +176,7 @@ R2_ACCESS_KEY=$R2_ACCESS_KEY
|
||||
R2_SECRET_KEY=$R2_SECRET_KEY
|
||||
R2_BUCKET=$R2_BUCKET
|
||||
CF_API_TOKEN=$CF_API_TOKEN
|
||||
CF_RATE_LIMIT_BYPASS_TOKEN=$CF_RATE_LIMIT_BYPASS_TOKEN
|
||||
TURNSTILE_SECRET_KEY=$TURNSTILE_SECRET_KEY
|
||||
API_KEY=$API_KEY
|
||||
DOMAIN=$DOMAIN
|
||||
|
||||
@@ -12,6 +12,7 @@ ADMIN_TOKEN=your_admin_token_here
|
||||
# Cloudflare Configuration
|
||||
CF_ACCOUNT_ID=your_cloudflare_account_id
|
||||
CF_API_TOKEN=your_cloudflare_api_token
|
||||
CF_RATE_LIMIT_BYPASS_TOKEN=your_rate_limit_bypass_token
|
||||
DOMAIN=your-domain.com
|
||||
|
||||
# R2 Configuration
|
||||
|
||||
@@ -61,6 +61,7 @@ export interface ServerConfig {
|
||||
subdomain(): string;
|
||||
cloudflareAccountId(): string;
|
||||
cloudflareApiToken(): string;
|
||||
cloudflareRateLimitBypassToken(): string;
|
||||
cloudflareConfigPath(): string;
|
||||
cloudflareCredsPath(): string;
|
||||
stripePublishableKey(): string;
|
||||
|
||||
@@ -108,6 +108,9 @@ export abstract class DefaultServerConfig implements ServerConfig {
|
||||
cloudflareApiToken(): string {
|
||||
return process.env.CF_API_TOKEN ?? "";
|
||||
}
|
||||
cloudflareRateLimitBypassToken(): string {
|
||||
return process.env.CF_RATE_LIMIT_BYPASS_TOKEN ?? "";
|
||||
}
|
||||
cloudflareConfigPath(): string {
|
||||
return process.env.CF_CONFIG_PATH ?? "";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import z from "zod";
|
||||
import { z } from "zod/v4/classic/external.cjs";
|
||||
import { UserMeResponse, UserMeResponseSchema } from "../core/ApiSchemas";
|
||||
import { ServerConfig } from "../core/configuration/Config";
|
||||
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
|
||||
import {
|
||||
GameID,
|
||||
@@ -11,9 +13,45 @@ import { replacer } from "../core/Util";
|
||||
import { logger } from "./Logger";
|
||||
|
||||
const config = getServerConfigFromServer();
|
||||
const log = logger.child({ component: "Api" });
|
||||
|
||||
const log = logger.child({ component: "Archive" });
|
||||
|
||||
export async function getUserMe(
|
||||
token: string,
|
||||
config: ServerConfig,
|
||||
): Promise<
|
||||
| { type: "success"; response: UserMeResponse }
|
||||
| { type: "error"; message: string }
|
||||
> {
|
||||
try {
|
||||
// Get the user object
|
||||
const response = await fetch(config.jwtIssuer() + "/users/@me", {
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
"x-service-bypass": config.cloudflareRateLimitBypassToken(),
|
||||
},
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
return {
|
||||
type: "error",
|
||||
message: `Failed to fetch user me: ${response.statusText}`,
|
||||
};
|
||||
}
|
||||
const body = await response.json();
|
||||
const result = UserMeResponseSchema.safeParse(body);
|
||||
if (!result.success) {
|
||||
return {
|
||||
type: "error",
|
||||
message: `Invalid response: ${z.prettifyError(result.error)}`,
|
||||
};
|
||||
}
|
||||
return { type: "success", response: result.data };
|
||||
} catch (e) {
|
||||
return {
|
||||
type: "error",
|
||||
message: `Failed to fetch user me: ${e}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
export async function archive(gameRecord: GameRecord) {
|
||||
try {
|
||||
const parsed = GameRecordSchema.safeParse(gameRecord);
|
||||
@@ -30,6 +68,7 @@ export async function archive(gameRecord: GameRecord) {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": config.apiKey(),
|
||||
"x-service-bypass": config.cloudflareRateLimitBypassToken(),
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
@@ -45,7 +84,6 @@ export async function archive(gameRecord: GameRecord) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readGameRecord(
|
||||
gameId: GameID,
|
||||
): Promise<GameRecord | null> {
|
||||
@@ -59,6 +97,7 @@ export async function readGameRecord(
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-service-bypass": config.cloudflareRateLimitBypassToken(),
|
||||
},
|
||||
});
|
||||
const record = await response.json();
|
||||
@@ -76,7 +115,6 @@ export async function readGameRecord(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function finalizeGameRecord(
|
||||
clientRecord: PartialGameRecord,
|
||||
): GameRecord {
|
||||
@@ -1,12 +1,8 @@
|
||||
import { jwtVerify } from "jose";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
TokenPayload,
|
||||
TokenPayloadSchema,
|
||||
UserMeResponse,
|
||||
UserMeResponseSchema,
|
||||
} from "../core/ApiSchemas";
|
||||
import { TokenPayload, TokenPayloadSchema } from "../core/ApiSchemas";
|
||||
import { GameEnv, ServerConfig } from "../core/configuration/Config";
|
||||
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
|
||||
import { PersistentIdSchema } from "../core/Schemas";
|
||||
|
||||
type TokenVerificationResult =
|
||||
@@ -61,40 +57,3 @@ export async function verifyClientToken(
|
||||
return { type: "error", message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserMe(
|
||||
token: string,
|
||||
config: ServerConfig,
|
||||
): Promise<
|
||||
| { type: "success"; response: UserMeResponse }
|
||||
| { type: "error"; message: string }
|
||||
> {
|
||||
try {
|
||||
// Get the user object
|
||||
const response = await fetch(config.jwtIssuer() + "/users/@me", {
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
return {
|
||||
type: "error",
|
||||
message: `Failed to fetch user me: ${response.statusText}`,
|
||||
};
|
||||
}
|
||||
const body = await response.json();
|
||||
const result = UserMeResponseSchema.safeParse(body);
|
||||
if (!result.success) {
|
||||
return {
|
||||
type: "error",
|
||||
message: `Invalid response: ${z.prettifyError(result.error)}`,
|
||||
};
|
||||
}
|
||||
return { type: "success", response: result.data };
|
||||
} catch (e) {
|
||||
return {
|
||||
type: "error",
|
||||
message: `Failed to fetch user me: ${e}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
Turn,
|
||||
} from "../core/Schemas";
|
||||
import { createPartialGameRecord, getClanTag } from "../core/Util";
|
||||
import { archive, finalizeGameRecord } from "./Archive";
|
||||
import { archive, finalizeGameRecord } from "./Api";
|
||||
import { Client } from "./Client";
|
||||
export enum GamePhase {
|
||||
Lobby = "LOBBY",
|
||||
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
} from "../core/Schemas";
|
||||
import { generateID, replacer } from "../core/Util";
|
||||
import { CreateGameInputSchema, GameInputSchema } from "../core/WorkerSchemas";
|
||||
import { archive, finalizeGameRecord } from "./Archive";
|
||||
import { archive, finalizeGameRecord, getUserMe } from "./Api";
|
||||
import { verifyClientToken } from "./Auth";
|
||||
import { Client } from "./Client";
|
||||
import { GameManager } from "./GameManager";
|
||||
import { getUserMe, verifyClientToken } from "./jwt";
|
||||
import { logger } from "./Logger";
|
||||
|
||||
import { GameEnv } from "../core/configuration/Config";
|
||||
|
||||
Reference in New Issue
Block a user