fix(game-api): revoke token after prestart deletion

This commit is contained in:
2026-07-31 17:42:03 +00:00
parent 21d204c749
commit 596d01b6f4
6 changed files with 78 additions and 2 deletions
+12
View File
@@ -6,6 +6,7 @@ import { isValid, parseISO } from 'date-fns';
interface RedisClientLike {
get(key: string): Promise<string | null>;
set(key: string, value: string, options?: { EX?: number; NX?: boolean }): Promise<string | null>;
del?(key: string): Promise<number>;
}
const ACCESS_TOKEN_PREFIX = 'ga_';
@@ -72,6 +73,17 @@ export class RedisAccessTokenStore {
}
}
async revoke(accessToken: string): Promise<boolean> {
if (!RedisAccessTokenStore.isAccessToken(accessToken)) {
return false;
}
if (!this.client.del) {
throw new Error('Redis client does not support access token revocation.');
}
const key = buildAccessKey(this.profileName, accessToken);
return (await this.client.del(key)) > 0;
}
async markGatewayTokenUsed(sessionId: string, ttlSeconds: number): Promise<boolean> {
if (ttlSeconds <= 0) {
return false;
+3
View File
@@ -83,6 +83,7 @@ export interface GameApiContext {
uploadPath: string;
uploadPublicUrl: string | null;
auth: GameSessionTokenPayload | null;
accessToken?: string;
accessTokenStore: RedisAccessTokenStore;
flushStore: FlushStore;
gameTokenSecret: string;
@@ -100,6 +101,7 @@ export const createGameApiContext = (options: {
uploadPath: string;
uploadPublicUrl: string | null;
auth: GameSessionTokenPayload | null;
accessToken?: string;
accessTokenStore: RedisAccessTokenStore;
flushStore: FlushStore;
gameTokenSecret: string;
@@ -117,6 +119,7 @@ export const createGameApiContext = (options: {
uploadPath: options.uploadPath,
uploadPublicUrl: options.uploadPublicUrl,
auth: options.auth,
...(options.accessToken ? { accessToken: options.accessToken } : {}),
accessTokenStore: options.accessTokenStore,
flushStore: options.flushStore,
gameTokenSecret: options.gameTokenSecret,
+3
View File
@@ -86,6 +86,9 @@ const requestImmediateAction = async (
if (!result.ok) {
throw new TRPCError({ code: 'BAD_REQUEST', message: result.reason });
}
if (action === 'dieOnPrestart' && ctx.accessToken) {
await ctx.accessTokenStore.revoke(ctx.accessToken);
}
return { ok: true };
} catch (error) {
if (
+1
View File
@@ -196,6 +196,7 @@ export const createGameApiServer = async () => {
uploadPath: config.uploadPath,
uploadPublicUrl: config.uploadPublicUrl,
auth,
...(auth && token ? { accessToken: token } : {}),
accessTokenStore,
flushStore,
gameTokenSecret: config.gameTokenSecret,