harden implementations

This commit is contained in:
seavor 2026-04-12 15:21:29 -05:00
parent c3ae4cffd6
commit 559a3ff1f4
25 changed files with 240 additions and 37 deletions

View file

@ -24,7 +24,9 @@ export * from './ping';
export * from './register';
export * from './removeFromList';
export * from './replayDeleteMatch';
export * from './replayGetCode';
export * from './replayList';
export * from './replayModifyMatch';
export * from './replaySubmitCode';
export * from './requestPasswordSalt';
export * from './updateStatus';

View file

@ -44,7 +44,8 @@ export function login(options: WebSocketConnectOptions, passwordSalt?: string):
SessionPersistence.updateBuddyList(buddyList);
SessionPersistence.updateIgnoreList(ignoreList);
SessionPersistence.updateUser(userInfo);
SessionPersistence.loginSuccessful(loginConfig);
const { password: _password, ...safeConfig } = loginConfig;
SessionPersistence.loginSuccessful(safeConfig);
listUsers();
listRooms();

View file

@ -0,0 +1,10 @@
import { BackendService } from '../../services/BackendService';
export function replayGetCode(gameId: number, onCodeReceived: (code: string) => void): void {
BackendService.sendSessionCommand('Command_ReplayGetCode', { gameId }, {
responseName: 'Response_ReplayGetCode',
onSuccess: (response) => {
onCodeReceived(response.replayCode);
},
});
}

View file

@ -0,0 +1,12 @@
import { BackendService } from '../../services/BackendService';
export function replaySubmitCode(
replayCode: string,
onSuccess?: () => void,
onError?: (responseCode: number) => void,
): void {
BackendService.sendSessionCommand('Command_ReplaySubmitCode', { replayCode }, {
onSuccess,
onError,
});
}

View file

@ -163,6 +163,22 @@ describe('login', () => {
expect(SessionIndexMocks.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGED_IN, 'Logged in.');
});
it('onSuccess does NOT pass plaintext password to loginSuccessful', () => {
login({ userName: 'alice', password: 'secret' } as any);
const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } };
invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp });
const calledWith = (SessionPersistence.loginSuccessful as jest.Mock).mock.calls[0][0];
expect(calledWith).not.toHaveProperty('password');
});
it('onSuccess passes hashedPassword to loginSuccessful when salt is used', () => {
login({ userName: 'alice', password: 'pw' } as any, 'salt');
const loginResp = { buddyList: [], ignoreList: [], userInfo: { name: 'alice' } };
invokeOnSuccess(loginResp, { responseCode: 0, '.Response_Login.ext': loginResp });
const calledWith = (SessionPersistence.loginSuccessful as jest.Mock).mock.calls[0][0];
expect(calledWith).toHaveProperty('hashedPassword', 'hashed_pw');
});
it('onResponseCode RespClientUpdateRequired calls onLoginError', () => {
login({ userName: 'alice', password: 'pw' } as any);
invokeResponseCode(1);

View file

@ -423,3 +423,52 @@ describe('removeFromList / removeFromBuddyList / removeFromIgnoreList', () => {
expect(SessionPersistence.removeFromList).toHaveBeenCalledWith('buddy', 'alice');
});
});
describe('replayGetCode', () => {
const { replayGetCode } = jest.requireActual('./replayGetCode');
beforeEach(() => jest.clearAllMocks());
it('sends Command_ReplayGetCode with gameId and responseName', () => {
replayGetCode(42, jest.fn());
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
'Command_ReplayGetCode',
{ gameId: 42 },
expect.objectContaining({ responseName: 'Response_ReplayGetCode' })
);
});
it('calls onCodeReceived with replayCode on success', () => {
const onCodeReceived = jest.fn();
replayGetCode(42, onCodeReceived);
invokeOnSuccess({ replayCode: 'abc123-xyz' });
expect(onCodeReceived).toHaveBeenCalledWith('abc123-xyz');
});
});
describe('replaySubmitCode', () => {
const { replaySubmitCode } = jest.requireActual('./replaySubmitCode');
beforeEach(() => jest.clearAllMocks());
it('sends Command_ReplaySubmitCode with replayCode', () => {
replaySubmitCode('42-abc123');
expect(BackendService.sendSessionCommand).toHaveBeenCalledWith(
'Command_ReplaySubmitCode',
{ replayCode: '42-abc123' },
expect.any(Object)
);
});
it('forwards onSuccess callback', () => {
const onSuccess = jest.fn();
replaySubmitCode('42-abc123', onSuccess);
invokeOnSuccess();
expect(onSuccess).toHaveBeenCalled();
});
it('forwards onError callback', () => {
const onError = jest.fn();
replaySubmitCode('42-abc123', undefined, onError);
invokeCallback('onError', 404);
expect(onError).toHaveBeenCalledWith(404);
});
});