casbin.js icon indicating copy to clipboard operation
casbin.js copied to clipboard

Fix setPermission() to support enforcer format from Go backend's CasbinJsGetPermissionForUser

Open Copilot opened this issue 4 months ago • 2 comments

Problem

Users integrating casbin.js with Go backends using CasbinJsGetPermissionForUser were unable to use manual mode correctly. The Go function exports permissions in an "enforcer format" containing model (m) and policies (p), but setPermission() only supported a simple action-object format like {"read": ["data1", "data2"], "write": ["data1"]}.

This caused permission checks to return false when they should return true:

const authorizer = new casbinjs.Authorizer('manual');

// Response from Go's CasbinJsGetPermissionForUser contains {m: "...", p: [...]}
authorizer.setPermission(responseFromApi); // Didn't work!

authorizer.can('read', 'data1').then((can) => {
  console.log(can); // false (incorrect!)
});

Solution

Enhanced setPermission() to automatically detect and handle both permission formats:

  1. Auto-detection: Checks if the data contains m (model) or p (policies) keys to identify enforcer format
  2. Smart routing: Uses initEnforcer() for enforcer format, or Permission.load() for simple format
  3. Enhanced manual mode: The can() method now checks if an enforcer is available in manual mode
  4. User support: setUser() now sets the user property in all modes (required for enforcer-based permissions)

Changes

  • Made setPermission() async to support enforcer initialization
  • Added format detection logic to route to appropriate handler
  • Updated can() to check for enforcer in manual mode before falling back to simple permission check
  • Fixed setUser() to work properly in manual mode with enforcer format
  • Added comprehensive tests for both formats
  • Updated README with usage examples for both formats

Usage

const authorizer = new casbinjs.Authorizer('manual');

// Now works with enforcer format from Go backend!
await authorizer.setPermission(responseFromGoAPI);
await authorizer.setUser('alice');

authorizer.can('read', 'data1').then(can => {
  console.log(can); // true (correct!)
});

// Still works with simple format (backward compatible)
await authorizer.setPermission({
  read: ['data1', 'data2'],
  write: ['data1']
});

Breaking Change

⚠️ setPermission() is now async and returns Promise<void> instead of void. Existing code needs to add await:

// Old
authorizer.setPermission(permission);

// New  
await authorizer.setPermission(permission);

Testing

  • Added 3 new tests covering enforcer format support
  • All 15 tests pass
  • Maintains backward compatibility with simple permission format

Fixes #[issue_number]

Original prompt

This section details on the original issue you should resolve

<issue_title>CasbinJsGetPermissionForUser seems to export something casbinjs doesn't understand</issue_title> <issue_description>Hello, I am not sure if this library is still maintained, but I am looking to get the enforcer setup client side.

Versions

  • casbin.js: "^0.5.1"
  • github.com/casbin/casbin/v2 v2.116.0

The API is in go, and the server side model & policy work as expected. I am sending the result of CasbinJsGetPermissionForUser, but when using the client enforcer, it says false for the same comparison I am doing in the backend.

When I change it to what the example says:

{
  "read": ["data1", "data2"],
  "write": ["data1"]
}

it works as expected.

Model
[request_definition]
r = sub, act, obj

[policy_definition]
p = sub, act, obj

[role_definition]
g = _, _
g2 = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && g(p.act, r.act) && r.obj == p.obj
Policy
p, 123, user, data1
p, 456, admin, data1

g, user, GetRbacResource
g, admin, GetRbacResource
g, admin,CreateRbacResource

Result of CasbinJsGetPermissionForUser
{
  "g": [
    [
      "g",
      "user",
      "GetRbacResource"
    ],
    [
      "g",
      "admin",
      "GetRbacResource"
    ],
    [
      "g",
      "admin",
      "CreateRbacResource"
    ]
  ],
  "m": "[request_definition]\nr = sub, act, obj\n[policy_definition]\np = sub, act, obj\n[role_definition]\ng = _, _\ng2 = _, _\n[policy_effect]\ne = some(where (p.eft == allow))\n[matchers]\nm = r.sub == p.sub && g(p.act, r.act) && r.obj == p.obj\n",
  "p": [
    [
      "p",
      "123",
      "user",
      "data1"
    ],
    [
      "p",
      "456",
      "admin",
      "data1"
    ]
  ]
}

Usage


const authorizer = new casbinjs.Authorizer('manual')

// get permissions from API - responds with above block
authorizer.setPermission(responseFromApi)

// tried this too, but had no effect
// authorizer.setUser('123')

authorizer.can('CreateRbacResource', 'data1').then((can) => {
  // can is false
})

When I swapped out the response from the API with the docs snippet, it worked as expected.

// drastically different response to what I get
authorizer.setPermission({
  read: ['data1', 'data2'],
  write: ['data1'],
})

authorizer.can('write', 'data1').then((can) => {
  // can is true
})
```</issue_description>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

Fixes casbin/casbin.js#284


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copilot avatar Oct 11 '25 18:10 Copilot