Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@loopback/authorization

Package Overview
Dependencies
Maintainers
7
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@loopback/authorization - npm Package Compare versions

Comparing version 0.1.2 to 0.2.0

11

CHANGELOG.md

@@ -6,2 +6,13 @@ # Change Log

# [0.2.0](https://github.com/strongloop/loopback-next/compare/@loopback/authorization@0.1.2...@loopback/authorization@0.2.0) (2019-08-19)
### Features
* **authorization:** add options to improve decision making ([ce59488](https://github.com/strongloop/loopback-next/commit/ce59488))
## [0.1.2](https://github.com/strongloop/loopback-next/compare/@loopback/authorization@0.1.1...@loopback/authorization@0.1.2) (2019-08-15)

@@ -8,0 +19,0 @@

14

dist/authorization-component.js

@@ -6,11 +6,21 @@ "use strict";

// License text available at https://opensource.org/licenses/MIT
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@loopback/core");
const authorize_interceptor_1 = require("./authorize-interceptor");
class AuthorizationComponent {
const keys_1 = require("./keys");
let AuthorizationComponent = class AuthorizationComponent {
constructor() {
this.bindings = [core_1.createBindingFromClass(authorize_interceptor_1.AuthorizationInterceptor)];
}
}
};
AuthorizationComponent = __decorate([
core_1.bind({ tags: { [core_1.ContextTags.KEY]: keys_1.AuthorizationBindings.COMPONENT.key } })
], AuthorizationComponent);
exports.AuthorizationComponent = AuthorizationComponent;
//# sourceMappingURL=authorization-component.js.map

5

dist/authorize-interceptor.d.ts
import { Interceptor, InvocationContext, Next, Provider } from '@loopback/context';
import { Authorizer } from './types';
import { AuthorizationOptions, Authorizer } from './types';
export declare class AuthorizationInterceptor implements Provider<Interceptor> {
private authorizers;
constructor(authorizers: Authorizer[]);
private options;
constructor(authorizers: Authorizer[], options?: AuthorizationOptions);
value(): Interceptor;
intercept(invocationCtx: InvocationContext, next: Next): Promise<any>;
}

@@ -19,2 +19,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const authentication_1 = require("@loopback/authentication");
const context_1 = require("@loopback/context");

@@ -25,7 +26,8 @@ const debugFactory = require("debug");

const types_1 = require("./types");
const authentication_1 = require("@loopback/authentication");
const debug = debugFactory('loopback:authorization:interceptor');
let AuthorizationInterceptor = class AuthorizationInterceptor {
constructor(authorizers) {
constructor(authorizers, options = {}) {
this.authorizers = authorizers;
this.options = Object.assign({ defaultDecision: types_1.AuthorizationDecision.DENY, precedence: types_1.AuthorizationDecision.DENY }, options);
debug('Authorization options', this.options);
}

@@ -58,7 +60,15 @@ value() {

let authorizers = await loadAuthorizers(invocationCtx, metadata.voters || []);
let finalDecision = this.options.defaultDecision;
authorizers = authorizers.concat(this.authorizers);
for (const fn of authorizers) {
const decision = await fn(authorizationCtx, metadata);
debug('Decision', decision);
// Reset the final decision if an explicit Deny or Allow is voted
if (decision && decision !== types_1.AuthorizationDecision.ABSTAIN) {
finalDecision = decision;
}
// we can add another interceptor to process the error
if (decision === types_1.AuthorizationDecision.DENY) {
if (decision === types_1.AuthorizationDecision.DENY &&
this.options.precedence === types_1.AuthorizationDecision.DENY) {
debug('Access denied');
const error = new types_1.AuthorizationError('Access denied');

@@ -68,3 +78,15 @@ error.statusCode = 401;

}
if (decision === types_1.AuthorizationDecision.ALLOW &&
this.options.precedence === types_1.AuthorizationDecision.ALLOW) {
debug('Access allowed');
break;
}
}
debug('Final decision', finalDecision);
// Handle the final decision
if (finalDecision === types_1.AuthorizationDecision.DENY) {
const error = new types_1.AuthorizationError('Access denied');
error.statusCode = 401;
throw error;
}
return next();

@@ -76,3 +98,4 @@ }

__param(0, context_1.inject(context_1.filterByTag(keys_1.AuthorizationTags.AUTHORIZER))),
__metadata("design:paramtypes", [Array])
__param(1, context_1.config({ fromBinding: keys_1.AuthorizationBindings.COMPONENT })),
__metadata("design:paramtypes", [Array, Object])
], AuthorizationInterceptor);

@@ -79,0 +102,0 @@ exports.AuthorizationInterceptor = AuthorizationInterceptor;

@@ -0,1 +1,4 @@

import { BindingKey } from '@loopback/core';
import { AuthorizationComponent } from './authorization-component';
import { AuthorizationMetadata } from './types';
/**

@@ -5,3 +8,4 @@ * Binding keys used by authorization component.

export declare namespace AuthorizationBindings {
const METADATA = "authorization.operationMetadata";
const METADATA: BindingKey<AuthorizationMetadata>;
const COMPONENT: BindingKey<AuthorizationComponent>;
}

@@ -8,0 +12,0 @@ /**

@@ -7,2 +7,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@loopback/core");
/**

@@ -13,3 +14,4 @@ * Binding keys used by authorization component.

(function (AuthorizationBindings) {
AuthorizationBindings.METADATA = 'authorization.operationMetadata';
AuthorizationBindings.METADATA = core_1.BindingKey.create('authorization.operationMetadata');
AuthorizationBindings.COMPONENT = core_1.BindingKey.create(`${core_1.CoreBindings.COMPONENTS}.AuthorizationComponent`);
})(AuthorizationBindings = exports.AuthorizationBindings || (exports.AuthorizationBindings = {}));

@@ -16,0 +18,0 @@ /**

@@ -220,1 +220,16 @@ import { BindingAddress, InvocationContext } from '@loopback/context';

}
export interface AuthorizationOptions {
/**
* Default decision if all authorizers vote for ABSTAIN
* If not set, default to `AuthorizationDecision.DENY`
*/
defaultDecision?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW;
/**
* Controls if Allow/Deny vote takes precedence and override other votes.
* If not set, default to `AuthorizationDecision.DENY`.
*
* Once a vote matches the `precedence`, it becomes the final decision. The
* rest of votes will be skipped.
*/
precedence?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW;
}
{
"name": "@loopback/authorization",
"version": "0.1.2",
"version": "0.2.0",
"description": "A LoopBack component for authorization support.",

@@ -25,10 +25,10 @@ "engines": {

"dependencies": {
"@loopback/context": "^1.21.3",
"@loopback/core": "^1.9.2",
"@loopback/context": "^1.21.4",
"@loopback/core": "^1.9.3",
"debug": "^4.1.1"
},
"devDependencies": {
"@loopback/authentication": "^2.1.10",
"@loopback/build": "^2.0.7",
"@loopback/testlab": "^1.7.3",
"@loopback/authentication": "^2.1.11",
"@loopback/build": "^2.0.8",
"@loopback/testlab": "^1.7.4",
"@types/debug": "^4.1.4",

@@ -55,3 +55,3 @@ "@types/node": "10.14.15",

},
"gitHead": "535f77559c5d8e8500a6378bc2ea1e569beb8bb6"
"gitHead": "e17358d04cf9986fc692fdea37b582111932551d"
}

@@ -45,2 +45,50 @@ # @loopback/authorization

## Decision matrix
The final decision is controlled by voting results from authorizers and options
for the authorization component.
The following table illustrates the decision matrix with 3 voters and
corresponding options.
| Vote #1 | Vote # 2 | Vote #3 | Options | Final Decision |
| ------- | -------- | ------- | ------------------------ | -------------- |
| Deny | Deny | Deny | **any** | Deny |
| Allow | Allow | Allow | **any** | Allow |
| Abstain | Allow | Abstain | **any** | Allow |
| Abstain | Deny | Abstain | **any** | Deny |
| Deny | Allow | Abstain | {precedence: Deny} | Deny |
| Deny | Allow | Abstain | {precedence: Allow} | Allow |
| Allow | Abstain | Deny | {precedence: Deny} | Deny |
| Allow | Abstain | Deny | {precedence: Allow} | Allow |
| Abstain | Abstain | Abstain | {defaultDecision: Deny} | Deny |
| Abstain | Abstain | Abstain | {defaultDecision: Allow} | Allow |
The `options` is described as follows:
```ts
export interface AuthorizationOptions {
/**
* Default decision if all authorizers vote for ABSTAIN
*/
defaultDecision?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW;
/**
* Controls if Allow/Deny vote takes precedence and override other votes
*/
precedence?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW;
}
```
The authorization component can be configured with options:
```ts
const options: AuthorizationOptions = {
precedence: AuthorizationDecisions.DENY;
defaultDecision: AuthorizationDecisions.DENY;
}
const binding = app.component(AuthorizationComponent);
app.configure(binding.key).to(options);
```
## Installation

@@ -47,0 +95,0 @@

@@ -6,7 +6,14 @@ // Copyright IBM Corp. 2018. All Rights Reserved.

import {Component, createBindingFromClass} from '@loopback/core';
import {
bind,
Component,
ContextTags,
createBindingFromClass,
} from '@loopback/core';
import {AuthorizationInterceptor} from './authorize-interceptor';
import {AuthorizationBindings} from './keys';
@bind({tags: {[ContextTags.KEY]: AuthorizationBindings.COMPONENT.key}})
export class AuthorizationComponent implements Component {
bindings = [createBindingFromClass(AuthorizationInterceptor)];
}

@@ -6,2 +6,3 @@ // Copyright IBM Corp. 2019. All Rights Reserved.

import {AuthenticationBindings, UserProfile} from '@loopback/authentication';
import {

@@ -11,2 +12,4 @@ asGlobalInterceptor,

BindingAddress,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
config,
Context,

@@ -22,11 +25,11 @@ filterByTag,

import {getAuthorizationMetadata} from './decorators/authorize';
import {AuthorizationTags} from './keys';
import {AuthorizationBindings, AuthorizationTags} from './keys';
import {
AuthorizationContext,
AuthorizationDecision,
AuthorizationError,
AuthorizationOptions,
Authorizer,
AuthorizationError,
Principal,
} from './types';
import {AuthenticationBindings, UserProfile} from '@loopback/authentication';

@@ -37,6 +40,17 @@ const debug = debugFactory('loopback:authorization:interceptor');

export class AuthorizationInterceptor implements Provider<Interceptor> {
private options: AuthorizationOptions;
constructor(
@inject(filterByTag(AuthorizationTags.AUTHORIZER))
private authorizers: Authorizer[],
) {}
@config({fromBinding: AuthorizationBindings.COMPONENT})
options: AuthorizationOptions = {},
) {
this.options = {
defaultDecision: AuthorizationDecision.DENY,
precedence: AuthorizationDecision.DENY,
...options,
};
debug('Authorization options', this.options);
}

@@ -84,7 +98,17 @@ value(): Interceptor {

let finalDecision = this.options.defaultDecision;
authorizers = authorizers.concat(this.authorizers);
for (const fn of authorizers) {
const decision = await fn(authorizationCtx, metadata);
debug('Decision', decision);
// Reset the final decision if an explicit Deny or Allow is voted
if (decision && decision !== AuthorizationDecision.ABSTAIN) {
finalDecision = decision;
}
// we can add another interceptor to process the error
if (decision === AuthorizationDecision.DENY) {
if (
decision === AuthorizationDecision.DENY &&
this.options.precedence === AuthorizationDecision.DENY
) {
debug('Access denied');
const error = new AuthorizationError('Access denied');

@@ -94,3 +118,17 @@ error.statusCode = 401;

}
if (
decision === AuthorizationDecision.ALLOW &&
this.options.precedence === AuthorizationDecision.ALLOW
) {
debug('Access allowed');
break;
}
}
debug('Final decision', finalDecision);
// Handle the final decision
if (finalDecision === AuthorizationDecision.DENY) {
const error = new AuthorizationError('Access denied');
error.statusCode = 401;
throw error;
}
return next();

@@ -97,0 +135,0 @@ }

@@ -6,2 +6,6 @@ // Copyright IBM Corp. 2018. All Rights Reserved.

import {BindingKey, CoreBindings} from '@loopback/core';
import {AuthorizationComponent} from './authorization-component';
import {AuthorizationMetadata} from './types';
/**

@@ -11,3 +15,9 @@ * Binding keys used by authorization component.

export namespace AuthorizationBindings {
export const METADATA = 'authorization.operationMetadata';
export const METADATA = BindingKey.create<AuthorizationMetadata>(
'authorization.operationMetadata',
);
export const COMPONENT = BindingKey.create<AuthorizationComponent>(
`${CoreBindings.COMPONENTS}.AuthorizationComponent`,
);
}

@@ -14,0 +24,0 @@

@@ -255,1 +255,17 @@ // Copyright IBM Corp. 2018. All Rights Reserved.

}
export interface AuthorizationOptions {
/**
* Default decision if all authorizers vote for ABSTAIN
* If not set, default to `AuthorizationDecision.DENY`
*/
defaultDecision?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW;
/**
* Controls if Allow/Deny vote takes precedence and override other votes.
* If not set, default to `AuthorizationDecision.DENY`.
*
* Once a vote matches the `precedence`, it becomes the final decision. The
* rest of votes will be skipped.
*/
precedence?: AuthorizationDecision.DENY | AuthorizationDecision.ALLOW;
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc