Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions src/entities/player-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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)))
}
Expand Down
4 changes: 0 additions & 4 deletions src/middleware/current-player-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
}
7 changes: 6 additions & 1 deletion src/middleware/player-auth-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<boolean> {
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
}
31 changes: 20 additions & 11 deletions src/services/api/player-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -43,17 +44,25 @@ export async function findAliasFromIdentifyRequest(
key: APIKey,
service: string,
identifier: string
): Promise<PlayerAlias | null> {
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<PlayerAlias>(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(
Expand Down
9 changes: 6 additions & 3 deletions src/services/api/player-auth-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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, {
Expand Down
6 changes: 3 additions & 3 deletions src/socket/listeners/playerListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -37,6 +37,7 @@ const playerListeners = [
if (alias.service === PlayerAliasService.TALO) {
try {
await validateSessionTokenJWT(
em,
/* v8 ignore next */
data.sessionToken ?? '',
alias,
Expand All @@ -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)
},
Expand Down
4 changes: 2 additions & 2 deletions tests/services/_api/player-api/steamworksIdentify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 () => {
Expand Down