security: RBAC cache cross-instance invalidation + force re-login on role/perm change (#57)
- shrink roleDefaults cache TTL from 60s to 10s (safety-net staleness bound) - publish/subscribe on capakraken:rbac-invalidate so peer instances drop their local role-defaults cache on mutation (ioredis pub/sub; lazy init so idle test files don't open connections) - after updateUserRole/setUserPermissions/resetUserPermissions: delete all ActiveSession rows for that user so the next request re-auths via tRPC's jti check, and invalidate the role-defaults cache - tests: peer-instance invalidation via FakeRedis pub/sub fan-out; mutation side-effects assert session deletion + cache invalidation on each path Without this, demoted admins kept their JWT valid until expiry and peer instances kept serving stale role defaults for up to the TTL window. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -49,12 +49,20 @@ vi.mock("otpauth", () => {
|
||||
const createCaller = createCallerFactory(userRouter);
|
||||
|
||||
function createAdminCaller(db: Record<string, unknown>) {
|
||||
// Provide a no-op activeSession stub by default — some mutation paths
|
||||
// (setPermissions / resetPermissions / updateRole, see ticket #57) now
|
||||
// invalidate active sessions to force a re-login on privilege changes.
|
||||
// Individual tests can override by passing their own `activeSession` key.
|
||||
const dbWithDefaults = {
|
||||
activeSession: { deleteMany: vi.fn().mockResolvedValue({ count: 0 }) },
|
||||
...db,
|
||||
};
|
||||
return createCaller({
|
||||
session: {
|
||||
user: { email: "admin@example.com", name: "Admin", image: null },
|
||||
expires: "2099-01-01T00:00:00.000Z",
|
||||
},
|
||||
db: db as never,
|
||||
db: dbWithDefaults as never,
|
||||
dbUser: {
|
||||
id: "user_admin",
|
||||
systemRole: SystemRole.ADMIN,
|
||||
|
||||
Reference in New Issue
Block a user