@loopback/authorization
Version:
A LoopBack component for authorization support.
111 lines • 5.13 kB
JavaScript
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
// Node module: @loopback/authorization
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthorizationInterceptor = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@loopback/core");
const security_1 = require("@loopback/security");
const debug_1 = tslib_1.__importDefault(require("debug"));
const authorize_1 = require("./decorators/authorize");
const keys_1 = require("./keys");
const types_1 = require("./types");
const util_1 = require("./util");
const debug = (0, debug_1.default)('loopback:authorization:interceptor');
let AuthorizationInterceptor = class AuthorizationInterceptor {
constructor(options = {}) {
this.options = {
defaultDecision: types_1.AuthorizationDecision.DENY,
precedence: types_1.AuthorizationDecision.DENY,
defaultStatusCodeForDeny: 403,
...options,
};
debug('Authorization options', this.options);
}
value() {
return this.intercept.bind(this);
}
async intercept(invocationCtx, next) {
var _a;
const description = debug.enabled ? invocationCtx.description : '';
let metadata = (0, authorize_1.getAuthorizationMetadata)(invocationCtx.target, invocationCtx.methodName);
if (!metadata) {
debug('No authorization metadata is found for %s', description);
}
metadata = metadata !== null && metadata !== void 0 ? metadata : this.options.defaultMetadata;
if (!metadata || (metadata === null || metadata === void 0 ? void 0 : metadata.skip)) {
debug('Authorization is skipped for %s', description);
const result = await next();
return result;
}
debug('Authorization metadata for %s', description, metadata);
// retrieve it from authentication module
const user = await invocationCtx.get(security_1.SecurityBindings.USER, {
optional: true,
});
debug('Current user', user);
const authorizationCtx = {
principals: user ? [(0, util_1.createPrincipalFromUserProfile)(user)] : [],
roles: [],
scopes: [],
resource: invocationCtx.targetName,
invocationContext: invocationCtx,
};
debug('Security context for %s', description, authorizationCtx);
const authorizers = await loadAuthorizers(invocationCtx, (_a = metadata.voters) !== null && _a !== void 0 ? _a : []);
let finalDecision = this.options.defaultDecision;
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 &&
this.options.precedence === types_1.AuthorizationDecision.DENY) {
debug('Access denied');
const error = new types_1.AuthorizationError('Access denied');
error.statusCode = this.options.defaultStatusCodeForDeny;
throw error;
}
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 = this.options.defaultStatusCodeForDeny;
throw error;
}
return next();
}
};
exports.AuthorizationInterceptor = AuthorizationInterceptor;
exports.AuthorizationInterceptor = AuthorizationInterceptor = tslib_1.__decorate([
(0, core_1.injectable)((0, core_1.asGlobalInterceptor)('authorization')),
tslib_1.__param(0, (0, core_1.config)({ fromBinding: keys_1.AuthorizationBindings.COMPONENT })),
tslib_1.__metadata("design:paramtypes", [Object])
], AuthorizationInterceptor);
async function loadAuthorizers(ctx, authorizers) {
const authorizerFunctions = [];
const bindings = ctx.findByTag(keys_1.AuthorizationTags.AUTHORIZER);
authorizers = authorizers.concat(bindings.map(b => b.key));
for (const keyOrFn of authorizers) {
if (typeof keyOrFn === 'function') {
authorizerFunctions.push(keyOrFn);
}
else {
const fn = await ctx.get(keyOrFn);
authorizerFunctions.push(fn);
}
}
return authorizerFunctions;
}
//# sourceMappingURL=authorize-interceptor.js.map
;