fix: keep profile entry tokens out of URLs
This commit is contained in:
@@ -17,6 +17,10 @@
|
||||
"./auth/sanctions": {
|
||||
"types": "./dist/auth/sanctions.d.ts",
|
||||
"default": "./dist/auth/sanctions.js"
|
||||
},
|
||||
"./auth/gameSessionTransfer": {
|
||||
"types": "./dist/auth/gameSessionTransfer.d.ts",
|
||||
"default": "./dist/auth/gameSessionTransfer.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
export const GAME_SESSION_TRANSFER_STORAGE_KEY = 'sammo-pending-game-session';
|
||||
|
||||
export interface GameSessionTransfer {
|
||||
profile: string;
|
||||
gatewayToken: string;
|
||||
}
|
||||
|
||||
export interface GameSessionTransferStorage {
|
||||
getItem(key: string): string | null;
|
||||
setItem(key: string, value: string): void;
|
||||
removeItem(key: string): void;
|
||||
}
|
||||
|
||||
const isGameSessionTransfer = (value: unknown): value is GameSessionTransfer => {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return false;
|
||||
}
|
||||
const candidate = value as Partial<GameSessionTransfer>;
|
||||
return (
|
||||
typeof candidate.profile === 'string' &&
|
||||
candidate.profile.trim().length > 0 &&
|
||||
typeof candidate.gatewayToken === 'string' &&
|
||||
candidate.gatewayToken.trim().length > 0
|
||||
);
|
||||
};
|
||||
|
||||
export const writeGameSessionTransfer = (
|
||||
storage: GameSessionTransferStorage,
|
||||
transfer: GameSessionTransfer
|
||||
): boolean => {
|
||||
try {
|
||||
storage.setItem(GAME_SESSION_TRANSFER_STORAGE_KEY, JSON.stringify(transfer));
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const takeGameSessionTransfer = (storage: GameSessionTransferStorage): GameSessionTransfer | null => {
|
||||
let raw: string | null;
|
||||
try {
|
||||
raw = storage.getItem(GAME_SESSION_TRANSFER_STORAGE_KEY);
|
||||
storage.removeItem(GAME_SESSION_TRANSFER_STORAGE_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
return isGameSessionTransfer(parsed) ? parsed : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
GAME_SESSION_TRANSFER_STORAGE_KEY,
|
||||
takeGameSessionTransfer,
|
||||
writeGameSessionTransfer,
|
||||
type GameSessionTransferStorage,
|
||||
} from '../src/auth/gameSessionTransfer.js';
|
||||
|
||||
const createStorage = (): GameSessionTransferStorage & { values: Map<string, string> } => {
|
||||
const values = new Map<string, string>();
|
||||
return {
|
||||
values,
|
||||
getItem: (key) => values.get(key) ?? null,
|
||||
setItem: (key, value) => values.set(key, value),
|
||||
removeItem: (key) => values.delete(key),
|
||||
};
|
||||
};
|
||||
|
||||
describe('game session transfer', () => {
|
||||
it('stores and consumes a valid transfer exactly once', () => {
|
||||
const storage = createStorage();
|
||||
const transfer = { profile: 'hwe:903', gatewayToken: 'encrypted-token' };
|
||||
|
||||
expect(writeGameSessionTransfer(storage, transfer)).toBe(true);
|
||||
expect(storage.values.has(GAME_SESSION_TRANSFER_STORAGE_KEY)).toBe(true);
|
||||
expect(takeGameSessionTransfer(storage)).toEqual(transfer);
|
||||
expect(takeGameSessionTransfer(storage)).toBeNull();
|
||||
});
|
||||
|
||||
it('removes malformed transfer data without returning it', () => {
|
||||
const storage = createStorage();
|
||||
storage.setItem(GAME_SESSION_TRANSFER_STORAGE_KEY, JSON.stringify({ profile: 'hwe:903' }));
|
||||
|
||||
expect(takeGameSessionTransfer(storage)).toBeNull();
|
||||
expect(storage.values.has(GAME_SESSION_TRANSFER_STORAGE_KEY)).toBe(false);
|
||||
});
|
||||
|
||||
it('reports unavailable storage so callers can use a compatibility fallback', () => {
|
||||
const unavailable: GameSessionTransferStorage = {
|
||||
getItem: () => {
|
||||
throw new Error('blocked');
|
||||
},
|
||||
setItem: () => {
|
||||
throw new Error('blocked');
|
||||
},
|
||||
removeItem: () => {
|
||||
throw new Error('blocked');
|
||||
},
|
||||
};
|
||||
|
||||
expect(writeGameSessionTransfer(unavailable, { profile: 'hwe:903', gatewayToken: 'token' })).toBe(false);
|
||||
expect(takeGameSessionTransfer(unavailable)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ export default defineConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
'auth/gameToken': 'src/auth/gameToken.ts',
|
||||
'auth/gameSessionTransfer': 'src/auth/gameSessionTransfer.ts',
|
||||
'auth/sanctions': 'src/auth/sanctions.ts',
|
||||
},
|
||||
format: 'es',
|
||||
|
||||
Reference in New Issue
Block a user