mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 06:32:24 +00:00
fix(socket): use core auth user id for socket state scoping (#1984)
This commit is contained in:
@@ -27,7 +27,9 @@ vi.mock('../../store/channelConnectionsSlice', () => ({
|
||||
upsertChannelConnection: vi.fn((x: unknown) => x),
|
||||
}));
|
||||
vi.mock('../../lib/coreState/store', () => ({
|
||||
getCoreStateSnapshot: vi.fn(() => ({ snapshot: { sessionToken: null } })),
|
||||
getCoreStateSnapshot: vi.fn(() => ({
|
||||
snapshot: { auth: { userId: 'core-user-id' }, sessionToken: null },
|
||||
})),
|
||||
}));
|
||||
class MockMCPTransport {}
|
||||
vi.mock('../../lib/mcp', () => ({ SocketIOMCPTransportImpl: MockMCPTransport }));
|
||||
|
||||
@@ -47,7 +47,9 @@ vi.mock('../../store/connectivitySlice', () => ({
|
||||
|
||||
// Mock coreState
|
||||
vi.mock('../../lib/coreState/store', () => ({
|
||||
getCoreStateSnapshot: vi.fn(() => ({ snapshot: { sessionToken: null } })),
|
||||
getCoreStateSnapshot: vi.fn(() => ({
|
||||
snapshot: { auth: { userId: 'core-user-id' }, sessionToken: null },
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock MCP as a class so `new SocketIOMCPTransportImpl(...)` works at runtime.
|
||||
@@ -98,6 +100,61 @@ describe('socketService — resolveCoreSocketBaseUrl uses getCoreRpcUrl', () =>
|
||||
await pollUntil(() => expect(hoisted.getCoreRpcUrlMock).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('scopes socket state from core auth userId instead of decoding the JWT payload', async () => {
|
||||
const { getCoreStateSnapshot } = await import('../../lib/coreState/store');
|
||||
const { setStatusForUser } = await import('../../store/socketSlice');
|
||||
const setStatusForUserMock = vi.mocked(setStatusForUser);
|
||||
setStatusForUserMock.mockClear();
|
||||
|
||||
vi.mocked(getCoreStateSnapshot).mockReturnValue({
|
||||
snapshot: {
|
||||
auth: { userId: 'core-user-id' },
|
||||
sessionToken: 'header.eyJ1c2VySWQiOiJqd3QtdXNlci1pZCJ9.signature',
|
||||
},
|
||||
} as ReturnType<typeof getCoreStateSnapshot>);
|
||||
|
||||
hoisted.getCoreRpcUrlMock.mockResolvedValue('http://127.0.0.1:7788/rpc');
|
||||
|
||||
const { socketService } = await import('../socketService');
|
||||
socketService.disconnect();
|
||||
socketService.connect('header.eyJ1c2VySWQiOiJqd3QtdXNlci1pZCJ9.signature');
|
||||
|
||||
await pollUntil(() =>
|
||||
expect(setStatusForUserMock).toHaveBeenCalledWith({
|
||||
userId: 'core-user-id',
|
||||
status: 'connecting',
|
||||
})
|
||||
);
|
||||
expect(setStatusForUserMock).not.toHaveBeenCalledWith({
|
||||
userId: 'jwt-user-id',
|
||||
status: 'connecting',
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to pending when the core auth snapshot is not available yet', async () => {
|
||||
const { getCoreStateSnapshot } = await import('../../lib/coreState/store');
|
||||
const { setStatusForUser } = await import('../../store/socketSlice');
|
||||
const setStatusForUserMock = vi.mocked(setStatusForUser);
|
||||
|
||||
vi.mocked(getCoreStateSnapshot).mockReturnValue({
|
||||
snapshot: { sessionToken: 'mock-token' },
|
||||
} as ReturnType<typeof getCoreStateSnapshot>);
|
||||
|
||||
hoisted.getCoreRpcUrlMock.mockResolvedValue('http://127.0.0.1:7788/rpc');
|
||||
|
||||
const { socketService } = await import('../socketService');
|
||||
socketService.disconnect();
|
||||
setStatusForUserMock.mockClear();
|
||||
socketService.connect('mock-token-with-missing-auth');
|
||||
|
||||
await pollUntil(() =>
|
||||
expect(setStatusForUserMock).toHaveBeenCalledWith({
|
||||
userId: '__pending__',
|
||||
status: 'connecting',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('strips /rpc suffix from the resolved RPC URL to derive the socket base', async () => {
|
||||
const { io } = await import('socket.io-client');
|
||||
const ioMock = vi.mocked(io);
|
||||
|
||||
@@ -39,12 +39,6 @@ async function resolveCoreSocketBaseUrl(): Promise<string> {
|
||||
return coreSocketBaseFromRpcUrl(rpcUrl);
|
||||
}
|
||||
|
||||
interface JwtPayload {
|
||||
tgUserId?: string;
|
||||
userId?: string;
|
||||
sub?: string;
|
||||
}
|
||||
|
||||
interface ChannelConnectionUpdatedEvent {
|
||||
channel: ChannelType;
|
||||
authMode: ChannelAuthMode;
|
||||
@@ -99,22 +93,7 @@ function normalizeChannelConnectionUpdatePayload(
|
||||
}
|
||||
|
||||
function getSocketUserId(): string {
|
||||
const token = getCoreStateSnapshot().snapshot.sessionToken;
|
||||
if (!token) return '__pending__';
|
||||
|
||||
try {
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) return '__pending__';
|
||||
|
||||
const payloadBase64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
||||
const payloadJson = atob(payloadBase64);
|
||||
const payload = JSON.parse(payloadJson) as JwtPayload;
|
||||
|
||||
const id = payload.tgUserId || payload.userId || payload.sub;
|
||||
return id || '__pending__';
|
||||
} catch {
|
||||
return '__pending__';
|
||||
}
|
||||
return getCoreStateSnapshot().snapshot?.auth?.userId ?? '__pending__';
|
||||
}
|
||||
|
||||
class SocketService {
|
||||
|
||||
Reference in New Issue
Block a user