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]

Reply via email to