refactor: carry the revocation on outer.* instead of outer.pubsub.*

Eviction runs through `io.in(room).disconnectSockets()`, which goes via the
socket.io Redis adapter a cluster shares, so one node handling the event
disconnects that user on every node in it. Fanning out to siblings had each
of them repeat the same cluster-wide disconnect.

The adapter's Redis is per-cluster, so peer clusters still need the hop --
`outer.*` reaches them and skips the sibling fan-out. The local
`auth.sessions.revoked` goes with it: `outer.*` fires on the emitting node
too, and nothing else subscribed.
This commit is contained in:
Juan Castro committed 2026-10-09 16:24:01 -04:00
1 parent 155341ee6c
commit 8cf07db422
4 files changed
+16 -20

No files matched your search

+6 -3
View File
@@ -706,10 +706,13 @@ export type EventMap = {
* Sessions were revoked for a user. Anything holding one of them open — a
* live socket, say — should drop it: `revoked_at` is otherwise only read on
* the next handshake.
*
* `outer.*` rather than `outer.pubsub.*` on purpose: the eviction runs
* through the socket.io Redis adapter a cluster shares, so one node
* disconnecting covers every node in it — fanning out to siblings would
* just have each of them repeat the same cluster-wide disconnect.
*/
'auth.sessions.revoked': { user_id: number; session_uids: string[] };
/** The same revocation, carried to sibling nodes and peer clusters. */
'outer.pubsub.auth.sessions.revoked': {
'outer.auth.sessions.revoked': {
user_id: number;
session_uids: string[];
};
+1 -6
View File
@@ -732,13 +732,8 @@ export class AuthService extends PuterService {
user_id: revoked.userId,
session_uids: revoked.uuids,
};
this.clients.event?.emit('auth.sessions.revoked', payload, {});
// Whoever holds the socket may not be whoever revoked the session.
this.clients.event?.emit(
'outer.pubsub.auth.sessions.revoked',
payload,
{},
);
this.clients.event?.emit('outer.auth.sessions.revoked', payload, {});
}
/**
@@ -671,10 +671,10 @@ describe('SocketService (live socket.io)', () => {
const socket = await connect({ auth_token: `Bearer ${evicted.token}` });
expect(socket.connected).toBe(true);
// As the fan-out delivers it: the node holding this socket is not
// As a peer cluster delivers it: the node holding this socket is not
// the one that revoked the session.
server.clients.event.emit(
'outer.pubsub.auth.sessions.revoked',
'outer.auth.sessions.revoked',
{ user_id: row!.id, session_uids: ['whatever'] },
{},
);
+7 -9
View File
@@ -834,19 +834,17 @@ export class SocketService extends PuterService {
},
);
// Both channels: the local one for the node that did the revoking,
// the fanned-out one for every other node holding its sockets.
for (const key of [
'auth.sessions.revoked',
'outer.pubsub.auth.sessions.revoked',
] as const) {
this.clients.event.on(key, (_key: string, data: unknown) => {
// Fires on this node and carries to peer clusters; the adapter
// reaches the rest of this one.
this.clients.event.on(
'outer.auth.sessions.revoked',
(_key: string, data: unknown) => {
const { user_id } = data as { user_id: number };
this.#evictUserSockets(user_id).catch((err: unknown) => {
console.error('[socket] session eviction failed', err);
});
});
}
},
);
this.clients.event.on(
'auth.access-token.revoked',