Improve error message and fix security issues in CI workflow Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
Qwik-Authz is an authorization middleware for Qwik, based on Node-Casbin.
npm install qwik-authz casbin
// src/routes/layout.tsx or plugin.ts import { newEnforcer } from 'casbin'; import { authz } from 'qwik-authz'; import type { RequestHandler } from '@builder.io/qwik-city'; // Initialize Casbin enforcer const enforcer = await newEnforcer('path/to/model.conf', 'path/to/policy.csv'); // Apply authz middleware export const onRequest: RequestHandler = authz({ newEnforcer: enforcer });
By default, qwik-authz uses HTTP Basic Authentication in the format:
Authorization: Basic {Base64Encoded(username:password)}
For other authentication methods, set the username in event.sharedMap before applying the authz middleware:
import { newEnforcer } from 'casbin'; import { authz } from 'qwik-authz'; import type { RequestHandler } from '@builder.io/qwik-city'; const enforcer = await newEnforcer('path/to/model.conf', 'path/to/policy.csv'); // Custom authentication middleware export const onRequest: RequestHandler = async (event) => { // Extract username from your auth method (JWT, session, etc.) const token = event.request.headers.get('Authorization')?.replace('Bearer ', ''); if (token) { const username = await verifyToken(token); // Your token verification logic event.sharedMap.set('username', username); } }; // Apply authz middleware export const onGet: RequestHandler = authz({ newEnforcer: enforcer });
Implement the Authorizer interface to add custom authorization logic:
import { Enforcer, newEnforcer } from 'casbin'; import { authz, Authorizer } from 'qwik-authz'; import type { RequestHandler, RequestEventCommon } from '@builder.io/qwik-city'; const enforcer = await newEnforcer('path/to/model.conf', 'path/to/policy.csv'); class CustomAuthorizer implements Authorizer { private event: RequestEventCommon; private enforcer: Enforcer; constructor(event: RequestEventCommon, enforcer: Enforcer) { this.event = event; this.enforcer = enforcer; } async checkPermission(): Promise<boolean> { // Allow public access to certain paths if (this.event.url.pathname.startsWith('/public/')) { return true; } // Use Casbin for other paths const username = this.event.sharedMap.get('username') as string || 'anonymous'; return this.enforcer.enforce( username, this.event.url.pathname, this.event.request.method ); } } export const onRequest: RequestHandler = authz({ newEnforcer: enforcer, authorizer: CustomAuthorizer, });
The authorization is determined based on {subject, object, action}:
/dataset1/resource)model.conf)[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
policy.csv)p, alice, /dataset1/*, GET p, alice, /dataset1/resource1, POST p, bob, /dataset2/*, * p, admin, /*, * g, alice, admin
In this example:
alice can GET any resource under /dataset1/ and POST to /dataset1/resource1bob can perform any action on resources under /dataset2/admin role has access to all resourcesalice has the admin role (via role inheritance)authz(options: AuthzOptions)Creates an authorization middleware for Qwik.
options.newEnforcer - A Casbin Enforcer instance or Promise that resolves to oneoptions.authorizer - (Optional) Custom Authorizer instance or constructorA Qwik RequestHandler middleware function
Authorizer Interfaceinterface Authorizer { checkPermission(): Promise<boolean>; }
Implement this interface to create custom authorization logic.
See the examples directory for more usage examples:
For more information about Casbin and policy configuration:
This project is licensed under the Apache 2.0 License.
Contributions are welcome! Please feel free to submit a Pull Request.