fix(hooks): useHasPermission must distinguish ALLOWED from ROOM_OWNER

The permission map shipped over the wire carries both
PermissionSetting.ALLOWED (value 1) and PermissionSetting.ROOM_OWNER
(value 2). Server-side, `Habbo.hasPermission(key)` calls
`Rank.hasPermission(key, isRoomOwner=false)`, whose implementation
at Rank.java:120 is:

  setting == ALLOWED || (setting == ROOM_OWNER && isRoomOwner)

So a permission whose rank value is ROOM_OWNER is only granted when
the caller is the active room owner — Habbo.hasPermission(key) with
the default `false` therefore returns false for ROOM_OWNER entries.

The previous useHasPermission implementation (`> 0`) treated
ROOM_OWNER as unconditionally true, which would let a UI gate light
up even when the server would refuse the action. Real example from
the default seed: `acc_closedice_room` is ROOM_OWNER for rank_1..6
and ALLOWED only for rank_7 — under `> 0` the predicate was true for
every rank, diverging from the server behaviour.

Tighten useHasPermission to `=== 1` (ALLOWED only). For the genuine
"this is a ROOM_OWNER permission, combine with room session"
scenarios, code reaches for usePermissionValue(key) and checks
`=== 2 && roomSession.isRoomOwner` explicitly.

None of the 11 migrated consumers are affected by the tightening:
the keys they use (acc_supporttool / acc_anyroomowner /
acc_catalogfurni / acc_calendar_force / acc_staff_pick /
acc_ambassador) are all ALLOWED-only in the default seed.

Test refresh:
- useHasPermission('acc_supporttool') (value 1) stays true.
- useHasPermission('acc_anyroomowner') with value 2 in the mock
  flips from true to false — the new contract.
- Other cases unchanged.

Verification: yarn typecheck clean, yarn lint:hooks clean, yarn test
214/214.
This commit is contained in:
simoleo89
2026-05-19 19:45:19 +02:00
parent c7e258e3d1
commit 989b132c6a
2 changed files with 39 additions and 15 deletions
+14 -5
View File
@@ -214,16 +214,25 @@ describe('useHasPermission + usePermissionValue + useUserPermissions', () =>
});
});
it('useHasPermission returns true for any non-zero value, false for absent/zero', () =>
it('useHasPermission returns true only for ALLOWED (value 1), false for ROOM_OWNER/absent/zero', () =>
{
permissionsSnapshot = new Map([
[ 'acc_supporttool', 1 ],
[ 'acc_anyroomowner', 2 ],
[ 'acc_closedice_room', 0 ]
[ 'acc_supporttool', 1 ], // ALLOWED
[ 'acc_anyroomowner', 2 ], // ROOM_OWNER — requires room ownership at call time
[ 'acc_closedice_room', 0 ] // DISALLOWED (shouldn't reach the client, but defensive)
]);
// ALLOWED → true. Matches Habbo.hasPermission(key) which calls
// Rank.hasPermission(key, false) → only ALLOWED short-circuits.
expect(renderHook(() => useHasPermission('acc_supporttool')).result.current).toBe(true);
expect(renderHook(() => useHasPermission('acc_anyroomowner')).result.current).toBe(true);
// ROOM_OWNER → false. The server-side check requires the
// caller to pass isRoomOwner=true, which the client doesn't
// have ambiently. Code that needs to combine this with the
// active room session should call usePermissionValue(key) and
// check === 2 alongside roomSession.isRoomOwner.
expect(renderHook(() => useHasPermission('acc_anyroomowner')).result.current).toBe(false);
expect(renderHook(() => useHasPermission('acc_closedice_room')).result.current).toBe(false);
expect(renderHook(() => useHasPermission('acc_unknown_key')).result.current).toBe(false);
});