This is an automated email from the ASF dual-hosted git repository. EmryZhang pushed a commit to branch release/casbin-js-runtime-fixes-20261107 in repository https://gitbox.apache.org/repos/asf/casbin-casbin.js.git
commit 09eb698df5de357b4042fd260daa092095d80369 Author: Zhangyanrui123 <[email protected]> AuthorDate: Sat Nov 7 23:31:20 2026 +0800 fix: discard stale permission reloads --- src/Authorizer.ts | 11 ++++- src/__test__/release-regressions.test.ts | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/Authorizer.ts b/src/Authorizer.ts index 09e9b02..c8af016 100644 --- a/src/Authorizer.ts +++ b/src/Authorizer.ts @@ -259,7 +259,7 @@ export class Authorizer { // The permission of the former identity must not stay in use while the // new one is loading, otherwise a slow request would keep the old rights. // Bumping the version also invalidates an enforcer that is still being built. - this.stateVersion++; + const version = ++this.stateVersion; this.enforcer = undefined; this.permission = undefined; this.enforcerPromise = undefined; @@ -269,12 +269,15 @@ export class Authorizer { let config: PermissionSource; if (cached === null) { config = await this.getEnforcerDataFromSvr(); + if (version !== this.stateVersion) { + return; + } Cache.saveToLocalStorage(key, typeof config === 'string' ? config : JSON.stringify(config), this.cacheExpiredTime); } else { config = cached; } // Another identity was requested while this one was loading. - if (this.user !== user) { + if (version !== this.stateVersion) { return; } await this.initEnforcer(config); @@ -302,6 +305,10 @@ export class Authorizer { const key = this.cookieKey ?? DEFAULT_COOKIE_KEY; const permission = Cookies.get(key); if (permission === undefined) { + this.stateVersion++; + this.permission = undefined; + this.enforcer = undefined; + this.enforcerPromise = undefined; console.warn(`WARNING: No cookie named "${key}" was found.`); return false; } diff --git a/src/__test__/release-regressions.test.ts b/src/__test__/release-regressions.test.ts new file mode 100644 index 0000000..8c018e5 --- /dev/null +++ b/src/__test__/release-regressions.test.ts @@ -0,0 +1,70 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ + +import Cookies from 'js-cookie'; +import { Authorizer } from '../Authorizer'; +import { basicModelStr, rbacModelStr } from './models'; + +const permission = (object: string) => ({ m: basicModelStr, p: [['p', 'alice', object, 'read']] }); + +test('an older request for the same user cannot replace a newer permission or cache', async () => { + localStorage.clear(); + const endpoint = 'https://casbin.test/request-order'; + const authorizer = new Authorizer('auto', { endpoint }); + let resolveOld!: (value: ReturnType<typeof permission>) => void; + authorizer.getEnforcerDataFromSvr = () => + new Promise((resolve) => { + resolveOld = resolve; + }); + const oldLoad = authorizer.setUser('alice'); + authorizer.getEnforcerDataFromSvr = () => Promise.resolve(permission('new-data')); + await authorizer.refresh(); + resolveOld(permission('old-data')); + await oldLoad; + expect(await authorizer.can('read', 'new-data')).toBe(true); + expect(await authorizer.can('read', 'old-data')).toBe(false); + const cached = new Authorizer('auto', { endpoint }); + cached.getEnforcerDataFromSvr = () => Promise.reject(new Error('expected the new cache')); + await cached.setUser('alice'); + expect(await cached.can('read', 'new-data')).toBe(true); + expect(await cached.can('read', 'old-data')).toBe(false); +}); + +test.each(['map', 'model'])('removing a %s permission cookie clears the previous permission', async (shape) => { + const data = + shape === 'map' + ? { read: ['data1'] } + : { + m: rbacModelStr, + p: [['p', 'admin', 'data1', 'read']], + g: [['g', 'alice', 'admin']], + }; + Cookies.set('access_perm', JSON.stringify(data)); + const authorizer = new Authorizer('cookies'); + await authorizer.setUser('alice'); + expect(await authorizer.can('read', 'data1')).toBe(true); + Cookies.remove('access_perm'); + const warning = jest.spyOn(console, 'warn').mockImplementation(() => undefined); + try { + expect(authorizer.loadPermissionFromCookie()).toBe(false); + expect(await authorizer.can('read', 'data1')).toBe(false); + } finally { + warning.mockRestore(); + } +}); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
