feat: implement Redis session management and user authentication flow
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import type {
|
||||
GameSessionInfo,
|
||||
GatewaySessionConfig,
|
||||
GatewaySessionInfo,
|
||||
GatewaySessionService,
|
||||
SessionRevocationOptions,
|
||||
} from './sessionService.js';
|
||||
import type { UserRecord } from './userRepository.js';
|
||||
|
||||
interface StoredSession {
|
||||
info: GatewaySessionInfo;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
interface StoredGameSession {
|
||||
info: GameSessionInfo;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
const buildGameKey = (profile: string, gameToken: string): string => `${profile}:${gameToken}`;
|
||||
|
||||
// 세션 TTL 동작을 테스트할 수 있도록 메모리 기반 구현을 제공한다.
|
||||
export class InMemoryGatewaySessionService implements GatewaySessionService {
|
||||
private readonly sessions = new Map<string, StoredSession>();
|
||||
private readonly gameSessions = new Map<string, StoredGameSession>();
|
||||
private readonly sessionGames = new Map<string, Set<string>>();
|
||||
private readonly sessionTtlMs: number;
|
||||
private readonly gameSessionTtlMs: number;
|
||||
|
||||
constructor(config: GatewaySessionConfig) {
|
||||
this.sessionTtlMs = config.sessionTtlSeconds * 1000;
|
||||
this.gameSessionTtlMs = config.gameSessionTtlSeconds * 1000;
|
||||
}
|
||||
|
||||
async createSession(user: UserRecord): Promise<GatewaySessionInfo> {
|
||||
const sessionToken = randomUUID();
|
||||
const info: GatewaySessionInfo = {
|
||||
sessionToken,
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
issuedAt: new Date().toISOString(),
|
||||
};
|
||||
this.sessions.set(sessionToken, {
|
||||
info,
|
||||
expiresAt: Date.now() + this.sessionTtlMs,
|
||||
});
|
||||
return info;
|
||||
}
|
||||
|
||||
async getSession(sessionToken: string): Promise<GatewaySessionInfo | null> {
|
||||
const stored = this.sessions.get(sessionToken);
|
||||
if (!stored) {
|
||||
return null;
|
||||
}
|
||||
if (Date.now() > stored.expiresAt) {
|
||||
this.sessions.delete(sessionToken);
|
||||
this.sessionGames.delete(sessionToken);
|
||||
return null;
|
||||
}
|
||||
return stored.info;
|
||||
}
|
||||
|
||||
async revokeSession(
|
||||
sessionToken: string,
|
||||
options: SessionRevocationOptions = { revokeGames: true }
|
||||
): Promise<void> {
|
||||
if (options.revokeGames ?? true) {
|
||||
const gameKeys = this.sessionGames.get(sessionToken);
|
||||
if (gameKeys) {
|
||||
for (const key of gameKeys) {
|
||||
this.gameSessions.delete(key);
|
||||
}
|
||||
}
|
||||
this.sessionGames.delete(sessionToken);
|
||||
}
|
||||
this.sessions.delete(sessionToken);
|
||||
}
|
||||
|
||||
async createGameSession(
|
||||
sessionToken: string,
|
||||
profile: string
|
||||
): Promise<GameSessionInfo | null> {
|
||||
const session = await this.getSession(sessionToken);
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
const gameToken = randomUUID();
|
||||
const info: GameSessionInfo = {
|
||||
profile,
|
||||
gameToken,
|
||||
sessionToken,
|
||||
userId: session.userId,
|
||||
username: session.username,
|
||||
displayName: session.displayName,
|
||||
issuedAt: new Date().toISOString(),
|
||||
};
|
||||
const key = buildGameKey(profile, gameToken);
|
||||
this.gameSessions.set(key, {
|
||||
info,
|
||||
expiresAt: Date.now() + this.gameSessionTtlMs,
|
||||
});
|
||||
const set = this.sessionGames.get(sessionToken) ?? new Set<string>();
|
||||
set.add(key);
|
||||
this.sessionGames.set(sessionToken, set);
|
||||
return info;
|
||||
}
|
||||
|
||||
async getGameSession(profile: string, gameToken: string): Promise<GameSessionInfo | null> {
|
||||
const key = buildGameKey(profile, gameToken);
|
||||
const stored = this.gameSessions.get(key);
|
||||
if (!stored) {
|
||||
return null;
|
||||
}
|
||||
if (Date.now() > stored.expiresAt) {
|
||||
this.gameSessions.delete(key);
|
||||
return null;
|
||||
}
|
||||
return stored.info;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user