@stacksjs/auth 0.75.94 → 0.75.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gate.d.ts +38 -0
- package/dist/gate.js +1 -1
- package/package.json +13 -13
package/dist/gate.d.ts
CHANGED
|
@@ -124,6 +124,42 @@ export declare function authorize<const A extends Ability>(ability: A, user: Use
|
|
|
124
124
|
* Get detailed inspection result for an ability check
|
|
125
125
|
*/
|
|
126
126
|
export declare function inspect<const A extends Ability>(ability: A, user: UserModel | null, ...args: AbilityArgs<A>): Promise<AuthorizationResponse>;
|
|
127
|
+
/**
|
|
128
|
+
* Check an ability against the policy registered for a model NAME, answering
|
|
129
|
+
* three states: no policy (null), allowed, denied.
|
|
130
|
+
*
|
|
131
|
+
* The vocabulary in `policy.ts` was unreachable from the code that wanted it.
|
|
132
|
+
* Resolution keys on `args[0].constructor.name`, so it needs a model INSTANCE,
|
|
133
|
+
* and the generated API and the dashboard actions both hold rows the query
|
|
134
|
+
* builder returned - plain objects, whose constructor is `Object`. There was no
|
|
135
|
+
* path from a model name to its policy, which is why 0 of the 750 default
|
|
136
|
+
* actions consult one (stacksjs/stacks#2883).
|
|
137
|
+
*
|
|
138
|
+
* The three-way answer is the point. `allows()` DENIES when nothing is
|
|
139
|
+
* registered, which is right for a named gate and catastrophic for a generated
|
|
140
|
+
* route: it would refuse every one of the 107 models that has no policy. A
|
|
141
|
+
* caller must be able to tell "nobody has an opinion" from "the answer is no",
|
|
142
|
+
* and null makes that impossible to read as fail-open by accident.
|
|
143
|
+
*
|
|
144
|
+
* The global before/after callbacks run when a policy IS registered, so a
|
|
145
|
+
* `Gate.after()` lockdown still applies. They do not run otherwise: with no
|
|
146
|
+
* policy there is no decision to override, and inventing one would turn this
|
|
147
|
+
* back into a deny for every unpoliced model.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* const verdict = await Gate.inspectFor('Order', 'delete', user, row)
|
|
151
|
+
* if (verdict && !verdict.isAllowed)
|
|
152
|
+
* return response.json({ message: verdict.message }, 403)
|
|
153
|
+
*/
|
|
154
|
+
export declare function inspectFor(model: string | { name: string }, ability: Ability, user: UserModel | null, ...args: any[]): Promise<AuthorizationResponse | null>;
|
|
155
|
+
/**
|
|
156
|
+
* {@link inspectFor} as a boolean, reading "no policy registered" as allowed.
|
|
157
|
+
*
|
|
158
|
+
* The convenience form for a caller with nothing of its own to add: no policy
|
|
159
|
+
* means this layer is not the one saying no. Anything that needs to tell that
|
|
160
|
+
* apart from a permission - to log it, or to skip work - uses `inspectFor`.
|
|
161
|
+
*/
|
|
162
|
+
export declare function allowsFor(model: string | { name: string }, ability: Ability, user: UserModel | null, ...args: any[]): Promise<boolean>;
|
|
127
163
|
/**
|
|
128
164
|
* Get a policy instance for a model
|
|
129
165
|
*/
|
|
@@ -153,6 +189,7 @@ export declare const Gate: {
|
|
|
153
189
|
before: typeof before;
|
|
154
190
|
after: typeof after;
|
|
155
191
|
allows: typeof allows;
|
|
192
|
+
allowsFor: typeof allowsFor;
|
|
156
193
|
denies: typeof denies;
|
|
157
194
|
can: typeof can;
|
|
158
195
|
cannot: typeof cannot;
|
|
@@ -161,6 +198,7 @@ export declare const Gate: {
|
|
|
161
198
|
none: typeof none;
|
|
162
199
|
authorize: typeof authorize;
|
|
163
200
|
inspect: typeof inspect;
|
|
201
|
+
inspectFor: typeof inspectFor;
|
|
164
202
|
has: typeof has;
|
|
165
203
|
hasPolicy: typeof hasPolicy;
|
|
166
204
|
abilities: typeof abilities;
|
package/dist/gate.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export function defineGates(definition){return definition}const RESERVED_POLICY_MEMBERS=new Set(["before","allow","deny","denyIf","denyUnless","allowIf","constructor"]);export class AuthorizationResponse{isAllowed;message;code;constructor(allowed,message,code){this.isAllowed=allowed;this.message=message;this.code=code}static allow(message){return new AuthorizationResponse(!0,message)}static deny(message,code){return new AuthorizationResponse(!1,message||"This action is unauthorized.",code)}allowed(){return this.isAllowed}denied(){return!this.isAllowed}authorize(){if(!this.isAllowed)throw new AuthorizationException(this.message||"This action is unauthorized.",this.code)}}export class AuthorizationException extends Error{code;status;constructor(message="This action is unauthorized.",code,status=403){super(message);this.code=code;this.status=status;this.name="AuthorizationException"}}const state={gates:new Map,policies:new Map,beforeCallbacks:[],afterCallbacks:[]};export function define(ability,callback){state.gates.set(ability,callback)}export function policy(model,policyClass){const modelName=typeof model==="string"?model:model.name;state.policies.set(modelName,policyClass)}export function before(callback){state.beforeCallbacks.push(callback)}export function after(callback){state.afterCallbacks.push(callback)}export async function allows(ability,user,...args){return check(ability,user,...args)}export async function denies(ability,user,...args){return!await check(ability,user,...args)}export async function can(ability,user,...args){return check(ability,user,...args)}export async function cannot(ability,user,...args){return!await check(ability,user,...args)}export async function any(abilities,user,...args){for(const ability of abilities)if(await check(ability,user,...args))return!0;return!1}export async function all(abilities,user,...args){for(const ability of abilities)if(!await check(ability,user,...args))return!1;return!0}export async function none(abilities,user,...args){return!await any(abilities,user,...args)}export async function authorize(ability,user,...args){const result=await inspect(ability,user,...args);if(!result.isAllowed)throw new AuthorizationException(result.message,result.code);return result}export async function inspect(ability,user,...args){let response=null;for(const callback of state.beforeCallbacks){const beforeResult=await callback(user,ability,args);if(beforeResult===!0){response=AuthorizationResponse.allow();break}if(beforeResult===!1){response=AuthorizationResponse.deny();break}}if(!response)response=await resolveAbility(ability,user,args);for(const callback of state.afterCallbacks){const afterResult=await callback(user,ability,response.isAllowed,args);if(typeof afterResult==="boolean")return afterResult?AuthorizationResponse.allow():AuthorizationResponse.deny()}return response}async function resolveAbility(ability,user,args){const model=args[0];if(model&&typeof model==="object"){const
|
|
1
|
+
export function defineGates(definition){return definition}const RESERVED_POLICY_MEMBERS=new Set(["before","allow","deny","denyIf","denyUnless","allowIf","constructor"]);export class AuthorizationResponse{isAllowed;message;code;constructor(allowed,message,code){this.isAllowed=allowed;this.message=message;this.code=code}static allow(message){return new AuthorizationResponse(!0,message)}static deny(message,code){return new AuthorizationResponse(!1,message||"This action is unauthorized.",code)}allowed(){return this.isAllowed}denied(){return!this.isAllowed}authorize(){if(!this.isAllowed)throw new AuthorizationException(this.message||"This action is unauthorized.",this.code)}}export class AuthorizationException extends Error{code;status;constructor(message="This action is unauthorized.",code,status=403){super(message);this.code=code;this.status=status;this.name="AuthorizationException"}}const state={gates:new Map,policies:new Map,beforeCallbacks:[],afterCallbacks:[]};export function define(ability,callback){state.gates.set(ability,callback)}export function policy(model,policyClass){const modelName=typeof model==="string"?model:model.name;state.policies.set(modelName,policyClass)}export function before(callback){state.beforeCallbacks.push(callback)}export function after(callback){state.afterCallbacks.push(callback)}export async function allows(ability,user,...args){return check(ability,user,...args)}export async function denies(ability,user,...args){return!await check(ability,user,...args)}export async function can(ability,user,...args){return check(ability,user,...args)}export async function cannot(ability,user,...args){return!await check(ability,user,...args)}export async function any(abilities,user,...args){for(const ability of abilities)if(await check(ability,user,...args))return!0;return!1}export async function all(abilities,user,...args){for(const ability of abilities)if(!await check(ability,user,...args))return!1;return!0}export async function none(abilities,user,...args){return!await any(abilities,user,...args)}export async function authorize(ability,user,...args){const result=await inspect(ability,user,...args);if(!result.isAllowed)throw new AuthorizationException(result.message,result.code);return result}export async function inspect(ability,user,...args){let response=null;for(const callback of state.beforeCallbacks){const beforeResult=await callback(user,ability,args);if(beforeResult===!0){response=AuthorizationResponse.allow();break}if(beforeResult===!1){response=AuthorizationResponse.deny();break}}if(!response)response=await resolveAbility(ability,user,args);for(const callback of state.afterCallbacks){const afterResult=await callback(user,ability,response.isAllowed,args);if(typeof afterResult==="boolean")return afterResult?AuthorizationResponse.allow():AuthorizationResponse.deny()}return response}async function resolveAbility(ability,user,args){const model=args[0];if(model&&typeof model==="object"){const policyClass=state.policies.get(model.constructor?.name);if(policyClass){const response=await runPolicy(policyClass,ability,user,args);if(response)return response}}const gate=state.gates.get(ability);if(gate)return normalizeResponse(await gate(user,...args));return AuthorizationResponse.deny(`No gate or policy defined for ability: ${ability}`)}async function runPolicy(policyClass,ability,user,args){const policyInstance=new policyClass;if(policyInstance.before){const beforeResult=await policyInstance.before(user,ability);if(beforeResult===!0)return AuthorizationResponse.allow();if(beforeResult===!1)return AuthorizationResponse.deny()}const method=RESERVED_POLICY_MEMBERS.has(ability)?void 0:policyInstance[ability];if(typeof method!=="function")return null;const result=await method.call(policyInstance,user,...args);return normalizeResponse(result??!1)}export async function inspectFor(model,ability,user,...args){const modelName=typeof model==="string"?model:model.name,policyClass=state.policies.get(modelName);if(!policyClass)return null;let response=null;for(const callback of state.beforeCallbacks){const beforeResult=await callback(user,ability,args);if(beforeResult===!0){response=AuthorizationResponse.allow();break}if(beforeResult===!1){response=AuthorizationResponse.deny();break}}response??=await runPolicy(policyClass,ability,user,args)??AuthorizationResponse.deny(`${modelName}'s policy defines no ${String(ability)} ability`);for(const callback of state.afterCallbacks){const afterResult=await callback(user,ability,response.isAllowed,args);if(typeof afterResult==="boolean")return afterResult?AuthorizationResponse.allow():AuthorizationResponse.deny()}return response}export async function allowsFor(model,ability,user,...args){const response=await inspectFor(model,ability,user,...args);return response?response.isAllowed:!0}async function check(ability,user,...args){return(await inspect(ability,user,...args)).isAllowed}function normalizeResponse(result){if(result instanceof AuthorizationResponse)return result;if(typeof result!=="boolean")throw TypeError(`[gate] Policy must return boolean or AuthorizationResponse; got ${typeof result}. If you returned a model/value by mistake, return \`true\`/\`false\` instead.`);return result?AuthorizationResponse.allow():AuthorizationResponse.deny()}export function getPolicyFor(model){if(!model||typeof model!=="object")return null;const modelName=model.constructor?.name,policyClass=state.policies.get(modelName);if(policyClass)return new policyClass;return null}export function has(ability){return state.gates.has(ability)}export function hasPolicy(model){const modelName=typeof model==="string"?model:model.name;return state.policies.has(modelName)}export function abilities(){return Array.from(state.gates.keys())}export function flush(){state.gates.clear();state.policies.clear();state.beforeCallbacks=[];state.afterCallbacks=[]}export const Gate={define,policy,before,after,allows,allowsFor,denies,can,cannot,any,all,none,authorize,inspect,inspectFor,has,hasPolicy,abilities,getPolicyFor,flush,AuthorizationResponse,AuthorizationException};export default Gate;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/auth",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.96",
|
|
6
6
|
"description": "A more simplistic way to authenticate.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -58,18 +58,18 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@stacksjs/bun-router": "^0.1.24",
|
|
61
|
-
"@stacksjs/cache": "0.75.
|
|
62
|
-
"@stacksjs/config": "0.75.
|
|
63
|
-
"@stacksjs/database": "0.75.
|
|
64
|
-
"@stacksjs/email": "0.75.
|
|
65
|
-
"@stacksjs/env": "0.75.
|
|
66
|
-
"@stacksjs/error-handling": "0.75.
|
|
67
|
-
"@stacksjs/logging": "0.75.
|
|
68
|
-
"@stacksjs/orm": "0.75.
|
|
69
|
-
"@stacksjs/path": "0.75.
|
|
70
|
-
"@stacksjs/router": "0.75.
|
|
71
|
-
"@stacksjs/security": "0.75.
|
|
72
|
-
"@stacksjs/storage": "0.75.
|
|
61
|
+
"@stacksjs/cache": "0.75.96",
|
|
62
|
+
"@stacksjs/config": "0.75.96",
|
|
63
|
+
"@stacksjs/database": "0.75.96",
|
|
64
|
+
"@stacksjs/email": "0.75.96",
|
|
65
|
+
"@stacksjs/env": "0.75.96",
|
|
66
|
+
"@stacksjs/error-handling": "0.75.96",
|
|
67
|
+
"@stacksjs/logging": "0.75.96",
|
|
68
|
+
"@stacksjs/orm": "0.75.96",
|
|
69
|
+
"@stacksjs/path": "0.75.96",
|
|
70
|
+
"@stacksjs/router": "0.75.96",
|
|
71
|
+
"@stacksjs/security": "0.75.96",
|
|
72
|
+
"@stacksjs/storage": "0.75.96",
|
|
73
73
|
"@stacksjs/ts-auth": "^0.4.5",
|
|
74
74
|
"ts-qr-codes": "^0.1.8"
|
|
75
75
|
},
|