From 983c6a3c0c360d8bcf5d12c2b601111c577db635 Mon Sep 17 00:00:00 2001 From: tudor <7089284+tudddorrr@users.noreply.github.com> Date: Mon, 25 Aug 2025 19:08:41 +0100 Subject: [PATCH] speed up player identify checks --- src/entities/player-auth.ts | 10 +++--- src/middleware/current-player-middleware.ts | 4 --- src/middleware/player-auth-middleware.ts | 7 ++++- src/services/api/player-api.service.ts | 31 ++++++++++++------- src/services/api/player-auth-api.service.ts | 9 ++++-- src/socket/listeners/playerListeners.ts | 6 ++-- .../player-api/steamworksIdentify.test.ts | 4 +-- 7 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/entities/player-auth.ts b/src/entities/player-auth.ts index 15a4f537..13c5478b 100644 --- a/src/entities/player-auth.ts +++ b/src/entities/player-auth.ts @@ -4,7 +4,6 @@ import { v4 } from 'uuid' import PlayerAlias, { PlayerAliasService } from './player-alias' import { sign } from '../lib/auth/jwt' import { getAuthMiddlewareAliasKey, getAuthMiddlewarePlayerKey } from '../middleware/player-auth-middleware' -import { getAliasFromIdentifyCacheKey } from '../middleware/current-player-middleware' const errorCodes = [ 'INVALID_CREDENTIALS', @@ -74,11 +73,10 @@ export default class PlayerAuth { player: this.player }) - const keysToClear: string[] = [getAuthMiddlewarePlayerKey(this.player.id)] - if (alias) { - keysToClear.push(getAuthMiddlewareAliasKey(alias.id)) - keysToClear.push(getAliasFromIdentifyCacheKey(this.player.game.id, alias.service, alias.identifier)) - } + const keysToClear: string[] = [ + getAuthMiddlewarePlayerKey(this.player.id), + alias ? getAuthMiddlewareAliasKey(alias.id) : null + ].filter((key): key is string => key !== null) await Promise.all(keysToClear.map((key) => em.clearCache(key))) } diff --git a/src/middleware/current-player-middleware.ts b/src/middleware/current-player-middleware.ts index 78e1590f..efb13c43 100644 --- a/src/middleware/current-player-middleware.ts +++ b/src/middleware/current-player-middleware.ts @@ -21,7 +21,3 @@ export default async function currentPlayerMiddleware(ctx: Context, next: Next): await next() } - -export function getAliasFromIdentifyCacheKey(gameId: number, service: string, identifier: string) { - return `identify-${gameId}-${service}-${identifier}` -} diff --git a/src/middleware/player-auth-middleware.ts b/src/middleware/player-auth-middleware.ts index 610fad2f..0cbe8cd8 100644 --- a/src/middleware/player-auth-middleware.ts +++ b/src/middleware/player-auth-middleware.ts @@ -55,6 +55,7 @@ export async function validateAuthSessionToken(ctx: Context, alias: PlayerAlias) try { const valid = await validateSessionTokenJWT( + ctx.em, sessionToken as string, alias, ctx.state.currentPlayerId, @@ -72,11 +73,15 @@ export async function validateAuthSessionToken(ctx: Context, alias: PlayerAlias) } export async function validateSessionTokenJWT( + em: EntityManager, sessionToken: string, alias: PlayerAlias, expectedPlayerId: string, expectedAliasId: number ): Promise { - const payload = await verify<{ playerId: string, aliasId: number }>(sessionToken, alias.player.auth!.sessionKey!) + await em.populate(alias, ['player.auth']) + if (!alias.player.auth) return false + + const payload = await verify<{ playerId: string, aliasId: number }>(sessionToken, alias.player.auth.sessionKey!) return payload.playerId === expectedPlayerId && payload.aliasId === expectedAliasId } diff --git a/src/services/api/player-api.service.ts b/src/services/api/player-api.service.ts index aaeb74b8..419addba 100644 --- a/src/services/api/player-api.service.ts +++ b/src/services/api/player-api.service.ts @@ -13,10 +13,11 @@ import PlayerGameStat from '../../entities/player-game-stat' import checkScope from '../../policies/checkScope' import Integration, { IntegrationType } from '../../entities/integration' import { validateAuthSessionToken } from '../../middleware/player-auth-middleware' -import { getAliasFromIdentifyCacheKey, setCurrentPlayerState } from '../../middleware/current-player-middleware' +import { setCurrentPlayerState } from '../../middleware/current-player-middleware' import { ClickHouseClient } from '@clickhouse/client' import { TraceService } from '../../lib/tracing/trace-service' import { getResultCacheOptions } from '../../lib/perf/getResultCacheOptions' +import { streamCursor } from '../../lib/perf/streamByCursor' async function getRealIdentifier( req: Request, @@ -43,17 +44,25 @@ export async function findAliasFromIdentifyRequest( key: APIKey, service: string, identifier: string -): Promise { - return (req.ctx.em as EntityManager).getRepository(PlayerAlias).findOne({ - service, - identifier: await getRealIdentifier(req, key, service, identifier), - player: { - game: key.game +) { + const em: EntityManager = req.ctx.em + const aliasStream = streamCursor(async (batchSize, after) => { + return em.repo(PlayerAlias).findByCursor({ + service, + identifier: await getRealIdentifier(req, key, service, identifier) + }, { + first: batchSize, + after, + orderBy: { id: 'asc' } + }) + }, 100) + + for await (const alias of aliasStream) { + if (alias.player.game.id === key.game.id) { + return alias } - }, { - ...getResultCacheOptions(getAliasFromIdentifyCacheKey(key.game.id, service, identifier), 30_000), - populate: ['player.auth'] - }) + } + return null } export async function createPlayerFromIdentifyRequest( diff --git a/src/services/api/player-auth-api.service.ts b/src/services/api/player-auth-api.service.ts index 5f86e436..a0fb28b1 100644 --- a/src/services/api/player-auth-api.service.ts +++ b/src/services/api/player-auth-api.service.ts @@ -123,12 +123,15 @@ export default class PlayerAuthAPIService extends APIService { const alias = await findAliasFromIdentifyRequest(req, key, PlayerAliasService.TALO, identifier) if (!alias) return this.handleFailedLogin(req) - const passwordMatches = await bcrypt.compare(password, alias.player.auth!.password) + await em.populate(alias, ['player.auth']) + if (!alias.player.auth) return this.handleFailedLogin(req) + + const passwordMatches = await bcrypt.compare(password, alias.player.auth.password) if (!passwordMatches) this.handleFailedLogin(req) const redis: Redis = req.ctx.redis - if (alias.player.auth!.verificationEnabled) { + if (alias.player.auth.verificationEnabled) { await em.populate(alias.player, ['game']) const code = generateSixDigitCode() @@ -149,7 +152,7 @@ export default class PlayerAuthAPIService extends APIService { } } } else { - const sessionToken = await alias.player.auth!.createSession(em, alias) + const sessionToken = await alias.player.auth.createSession(em, alias) const socketToken = await alias.createSocketToken(redis) createPlayerAuthActivity(req, alias.player, { diff --git a/src/socket/listeners/playerListeners.ts b/src/socket/listeners/playerListeners.ts index 1637e077..9e10303d 100644 --- a/src/socket/listeners/playerListeners.ts +++ b/src/socket/listeners/playerListeners.ts @@ -21,10 +21,10 @@ const playerListeners = [ const token = await socket.redis.get(`socketTokens.${data.playerAliasId}`) let alias: PlayerAlias + const em = RequestContext.getEntityManager() as EntityManager if (token === data.socketToken) { - alias = await RequestContext.getEntityManager()! - .getRepository(PlayerAlias) + alias = await em.repo(PlayerAlias) .findOneOrFail({ id: data.playerAliasId, player: { @@ -37,6 +37,7 @@ const playerListeners = [ if (alias.service === PlayerAliasService.TALO) { try { await validateSessionTokenJWT( + em, /* v8 ignore next */ data.sessionToken ?? '', alias, @@ -63,7 +64,6 @@ const playerListeners = [ conn.playerAliasId = alias.id await sendMessage(conn, 'v1.players.identify.success', alias) - const em = RequestContext.getEntityManager() as EntityManager await alias.player.handleSession(em, true) await alias.player.setPresence(em, socket, alias, true) }, diff --git a/tests/services/_api/player-api/steamworksIdentify.test.ts b/tests/services/_api/player-api/steamworksIdentify.test.ts index e8f8ff04..79c90838 100644 --- a/tests/services/_api/player-api/steamworksIdentify.test.ts +++ b/tests/services/_api/player-api/steamworksIdentify.test.ts @@ -71,7 +71,7 @@ describe('Player API service - identify - steamworks auth', () => { expect(res.body.alias.identifier).toBe(steamId) expect(res.body.alias.player.id).toBe(player.id) - expect(res.body.alias.player.props).toStrictEqual([ + expect(res.body.alias.player.props).toEqual(expect.arrayContaining([ { key: 'META_STEAMWORKS_OWNS_APP_PERMANENTLY', value: 'true' @@ -92,7 +92,7 @@ describe('Player API service - identify - steamworks auth', () => { key: 'META_STEAMWORKS_OWNS_APP_FROM_DATE', value: '2021-08-01T00:00:00.000Z' } - ]) + ])) }) it('should identify a non-existent steamworks player by creating a new player with the write scope', async () => {