mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-10 22:01:40 +00:00
fix: announce only the revokes that can have a socket to drop
Every access-token revoke broadcast an eviction cluster-wide, including the read-URL tokens `revokeReadUrl` retires constantly. A scoped token is refused at the handshake, so none of those broadcasts could ever reach a connection. The announcement now goes out only for a token the handshake would have admitted: full access, no app in the chain. `revokeOwnAccessToken` refuses full-access tokens outright, so that whole path is silent; a revoke by raw uuid says nothing about the token and still announces, which costs a no-op broadcast rather than leaving a revoked socket up. Fails without it: the test revokes a scoped token and a full-access one through the same entry point and counts the announcements.
This commit is contained in:
1 parent
5b9c43c58e
commit
751f9316ae
2 files changed
+43
-6
No files matched your search
@@ -2296,6 +2296,34 @@ describe('AuthService (integration)', () => {
|
||||
expect(rows).toHaveLength(0);
|
||||
});
|
||||
|
||||
// Only a full-access token is admitted to a socket, so only its revoke
|
||||
// is worth a cluster-wide eviction broadcast.
|
||||
it('announces a revoked full-access token, and no other kind', async () => {
|
||||
const user = await makeUser();
|
||||
const actor = makeActor({
|
||||
user: { id: user.id, uuid: user.uuid, username: user.username },
|
||||
});
|
||||
const emit = vi.spyOn(server.clients.event, 'emit');
|
||||
const announced = () =>
|
||||
emit.mock.calls.filter(
|
||||
(c) => c[0] === 'auth.access-token.revoked',
|
||||
).length;
|
||||
|
||||
const scoped = await authService.createAccessToken(actor, [
|
||||
[`user:${user.uuid}:email:read`],
|
||||
]);
|
||||
await authService.revokeAccessToken(actor, scoped);
|
||||
expect(announced()).toBe(0);
|
||||
|
||||
const full = await authService.createAccessToken(actor, [
|
||||
[FULL_API_ACCESS],
|
||||
]);
|
||||
await authService.revokeAccessToken(actor, full);
|
||||
expect(announced()).toBe(1);
|
||||
|
||||
emit.mockRestore();
|
||||
});
|
||||
|
||||
it('revokeAccessToken rejects with 404 when the token belongs to another user', async () => {
|
||||
const u1 = await makeUser();
|
||||
const u2 = await makeUser();
|
||||
|
||||
@@ -1885,6 +1885,8 @@ export class AuthService extends PuterService {
|
||||
let tokenUid: string;
|
||||
let issuerUuidFromJwt: string | undefined;
|
||||
let sessionUidFromJwt: string | undefined;
|
||||
// A uuid says nothing about the token; assume it held one.
|
||||
let couldHoldSocket = true;
|
||||
const isJwt = /^[\w-]+\.[\w-]+\.[\w-]+$/.test(tokenOrUuid.trim());
|
||||
if (isJwt) {
|
||||
const decoded = this.services.token.verify<AccessTokenPayload>(
|
||||
@@ -1899,6 +1901,7 @@ export class AuthService extends PuterService {
|
||||
tokenUid = decoded.token_uid;
|
||||
issuerUuidFromJwt = decoded.user_uid;
|
||||
sessionUidFromJwt = decoded.session_uid;
|
||||
couldHoldSocket = !decoded.app_uid && decoded.full_access === true;
|
||||
} else {
|
||||
tokenUid = tokenOrUuid;
|
||||
}
|
||||
@@ -1935,6 +1938,7 @@ export class AuthService extends PuterService {
|
||||
tokenUid,
|
||||
sessionUidFromJwt,
|
||||
sessionRow,
|
||||
{ couldHoldSocket },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2003,10 +2007,12 @@ export class AuthService extends PuterService {
|
||||
);
|
||||
}
|
||||
|
||||
// Full access is refused above, so nothing here ever held a socket.
|
||||
await this.#revokeAccessTokenTail(
|
||||
decoded.token_uid,
|
||||
decoded.session_uid,
|
||||
null,
|
||||
{ couldHoldSocket: false },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2052,6 +2058,7 @@ export class AuthService extends PuterService {
|
||||
tokenUid: string,
|
||||
sessionUidFromJwt: string | undefined,
|
||||
sessionRow: SessionRow | null,
|
||||
opts: { couldHoldSocket?: boolean } = {},
|
||||
): Promise<void> {
|
||||
await this.#dropAccessTokenGrants(tokenUid);
|
||||
|
||||
@@ -2068,12 +2075,14 @@ export class AuthService extends PuterService {
|
||||
if (row) await this.stores.session.removeByUuid(row.uuid);
|
||||
}
|
||||
|
||||
// `revoked_at` is otherwise only read at the next handshake.
|
||||
this.clients.event?.emit(
|
||||
'auth.access-token.revoked',
|
||||
{ token_uid: tokenUid },
|
||||
{},
|
||||
);
|
||||
// Only a token the handshake admits has a socket to drop.
|
||||
if (opts.couldHoldSocket !== false) {
|
||||
this.clients.event?.emit(
|
||||
'auth.access-token.revoked',
|
||||
{ token_uid: tokenUid },
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// -- Internals ---------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user