fix: enforce rollout gates across backend entry points
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
} from "convex/server";
|
||||
import { v } from "convex/values";
|
||||
import { assetValidator } from "./schema";
|
||||
import { capabilityEnabled, parseCapabilityAllowlist } from "../lib/server/release-gate";
|
||||
|
||||
const MAX_ASSET_CHUNK = 250;
|
||||
const SYNC_LEASE_MS = 5 * 60 * 1000;
|
||||
@@ -39,6 +40,16 @@ function assertBridge(secret: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function assertDeploymentCapability(capability: 'asset_sync'): void {
|
||||
let enabled = false;
|
||||
try {
|
||||
enabled = capabilityEnabled(capability, parseCapabilityAllowlist(process.env.APP_CAPABILITY_ALLOWLIST));
|
||||
} catch {
|
||||
// Invalid backend rollout configuration fails closed.
|
||||
}
|
||||
if (!enabled) throw new Error(`The ${capability} capability is disabled by deployment configuration.`);
|
||||
}
|
||||
|
||||
function constantTimeEqual(a: string, b: string): boolean {
|
||||
let difference = a.length ^ b.length;
|
||||
const length = Math.max(a.length, b.length);
|
||||
@@ -412,6 +423,7 @@ export const bridgeBeginSync = mutationGeneric({
|
||||
args: { ...bridgeArgs, ...characterArgs },
|
||||
handler: async (ctx, args) => {
|
||||
assertBridge(args.bridgeSecret);
|
||||
assertDeploymentCapability('asset_sync');
|
||||
const character = await findCharacter(ctx, args.userId, args.characterId);
|
||||
if (!character) throw new Error("Character not found");
|
||||
const timestamp = now();
|
||||
@@ -446,6 +458,7 @@ export const bridgeStageAssets = mutationGeneric({
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
assertBridge(args.bridgeSecret);
|
||||
assertDeploymentCapability('asset_sync');
|
||||
if (!Number.isInteger(args.index) || args.index < 0 || args.assets.length > MAX_ASSET_CHUNK) {
|
||||
throw new Error("Invalid asset chunk");
|
||||
}
|
||||
@@ -511,6 +524,7 @@ export const bridgeFinishSync = mutationGeneric({
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
assertBridge(args.bridgeSecret);
|
||||
assertDeploymentCapability('asset_sync');
|
||||
const timestamp = now();
|
||||
if (
|
||||
!Number.isFinite(args.syncedAt) ||
|
||||
@@ -574,6 +588,7 @@ export const bridgeFailSync = mutationGeneric({
|
||||
args: { ...bridgeArgs, ...characterArgs, generation: v.string(), error: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
assertBridge(args.bridgeSecret);
|
||||
assertDeploymentCapability('asset_sync');
|
||||
const character = await findCharacter(ctx, args.userId, args.characterId);
|
||||
if (!character || character.syncGeneration !== args.generation) {
|
||||
throw new Error("Synchronization generation is not current");
|
||||
|
||||
Reference in New Issue
Block a user