UNPKG

@httpc/kit

Version:

httpc toolbox for building function-based API with minimal code and end-to-end type safety

360 lines (359 loc) 13.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PermissionsChecker = exports.Assertion = exports.AssertionBuilder = exports.Authorization = exports.AuthorizationBuilder = exports.InvalidClaim = exports.PermissionSerializer = exports.PermissionParser = exports.PermissionToken = void 0; const Parser_1 = __importDefault(require("./Parser")); exports.PermissionParser = Parser_1.default; const Token = __importStar(require("./Token")); exports.PermissionToken = Token; const Serializer_1 = __importDefault(require("./Serializer")); exports.PermissionSerializer = Serializer_1.default; const assert_1 = __importDefault(require("assert")); __exportStar(require("./Parser"), exports); __exportStar(require("./Serializer"), exports); __exportStar(require("./model"), exports); class InvalidClaim extends Error { constructor(claim, message) { super(message || "Invalid claim"); this.claim = claim; } } exports.InvalidClaim = InvalidClaim; class AuthorizationBuilder { constructor(extend) { this._claims = []; if (extend) { this.add(extend); } } add(claim) { if (typeof claim === "string") { Parser_1.default.parseAuthorization(claim).forEach(x => this.add(x)); return this; } if (Array.isArray(claim)) { claim.forEach(x => this.add(x)); return this; } if (claim instanceof AuthorizationBuilder) { claim._claims.forEach(x => this.add(x)); return this; } if (claim instanceof Authorization) { claim.claims.forEach(x => this.add(x)); return this; } this._claims.push({ token: Array.isArray(claim.token) ? claim.token.slice() : claim.token, scope: Array.isArray(claim.scope) ? claim.scope.slice() : claim.scope, }); return this; } build() { return new Authorization(this._claims); } } exports.AuthorizationBuilder = AuthorizationBuilder; class Authorization { constructor(claims) { this.claims = claims; } *[Symbol.iterator]() { yield* this.claims; } merge(auth) { return new AuthorizationBuilder(this).add(auth).build(); } toString() { return Serializer_1.default.serialize(this); } static parse(claims) { return new AuthorizationBuilder(claims).build(); } } exports.Authorization = Authorization; class AssertionBuilder { constructor(extend) { this._claims = []; if (extend) { this.add(extend); } } add(claim) { if (typeof claim === "string") { Parser_1.default.parseAssertion(claim).forEach(x => this.add(x)); return this; } if (Array.isArray(claim)) { claim.forEach(x => this.add(x)); return this; } if (claim instanceof AssertionBuilder) { claim._claims.forEach(x => this.add(x)); return this; } if (claim instanceof Assertion) { claim.claims.forEach(x => this.add(x)); return this; } this._claims.push({ token: Array.isArray(claim.token) ? claim.token.slice() : claim.token, scope: Array.isArray(claim.scope) ? claim.scope.slice() : claim.scope, negative: claim.negative, }); return this; } build() { return new Assertion(this._claims); } } exports.AssertionBuilder = AssertionBuilder; class Assertion { constructor(claims) { this.claims = claims; } *[Symbol.iterator]() { yield* this.claims; } toString() { return Serializer_1.default.serialize(this); } test(auth) { if (this.claims.length === 0) return { success: true }; for (const assertion of this.claims) { let isPass = false; for (const claim of auth) { const isTokenPass = Token.match(claim.token, assertion.token); const isScopePass = assertion.scope ? claim.scope ? Token.match(claim.scope, assertion.scope) : false : true; if (isTokenPass && isScopePass) { isPass = true; break; } } isPass = isPass !== assertion.negative; if (!isPass) { return { success: false, failed: assertion }; } } return { success: true }; } static parse(claims) { return new AssertionBuilder(claims).build(); } } exports.Assertion = Assertion; class PermissionsChecker { constructor(options) { this._model = options?.model; this._cache = options?.cache === true ? new Map() : options?.cache || undefined; } can(authorization, assertion) { return this.test(authorization, assertion).success; } test(authorization, assertion) { [authorization] = this._getAuthorization(authorization); authorization = this.validate(authorization); [assertion] = this._getAssertion(assertion); assertion = this.validate(assertion); if (assertion.claims.length === 0) { return { success: true }; } const model = this._model; if (!model) { return assertion.test(authorization); } for (const assertClaim of assertion) { let isPass = false; for (const authClaim of authorization) { const isTokenPass = tokenMatch(authClaim.token, assertClaim.token); const isScopePass = assertClaim.scope ? authClaim.scope ? Token.match(authClaim.scope, assertClaim.scope) : false : true; if (isTokenPass && isScopePass) { isPass = true; break; } } isPass = isPass !== assertClaim.negative; if (!isPass) { return { success: false, failed: assertClaim }; } } return { success: true }; function tokenMatch(source, target) { (0, assert_1.default)(model, "model"); if (Token.match(source, target)) { return true; } const def = model.find(source, "exact"); // must be defined, because it already passed validation, if error -> this is a bug (0, assert_1.default)(def, "token definition not found"); if (def.includes && def.includes.length > 0) { for (const include of def.includes) { if (tokenMatch(Parser_1.default.parseToken(include), target)) { return true; } } } return false; } } supports(authorization) { const model = this._model; if (!model) return true; [authorization] = this._getAuthorization(authorization); for (const { token } of authorization) { if (!model.find(token, "exact")) { return false; } } return true; } parse(what, value) { const model = this._model; let [instance, validated] = what === "assertion" ? this._getAssertion(value) : this._getAuthorization(value); if (!model || validated) { return instance; } instance = this.validate(instance); if (this._cache) { const key = this._getCacheKey(what, value); this._cache.set(key, [instance, true]); } return instance; } validate(what) { const model = this._model; if (!model) return what; for (const claim of what) { if (!model.find(claim.token, "exact")) { throw new InvalidClaim(what instanceof Assertion ? Serializer_1.default.serializeAssertionClaim(claim) : Serializer_1.default.serializeAuthorizationClaim(claim)); } } return this.consolidate(what); } consolidate(what) { const model = this._model; // try to reduce the instance // - alias substitution (model only) // - eliminating duplicate if (what instanceof Authorization) { let isConsolidated = false; let claims = []; for (let claim of what) { // alias substitution const deAliasedToken = replaceAlias(claim); if (deAliasedToken) { isConsolidated = true; claim = { token: deAliasedToken, scope: claim.scope, }; } // look for already present const existing = claims.find(x => x.scope === claim.scope && Token.equals(x.token, claim.token)); if (existing) { isConsolidated = true; continue; } claims.push(claim); } return isConsolidated ? new Authorization(claims) : what; } else if (what instanceof Assertion) { let isConsolidated = false; let claims = []; for (let claim of what) { // alias substitution const deAliasedToken = replaceAlias(claim); if (deAliasedToken) { isConsolidated = true; claim = { token: deAliasedToken, scope: claim.scope, negative: claim.negative, }; } // look for already present const existing = claims.find(x => x.scope === claim.scope && Token.equals(x.token, claim.token) && !!x.negative === !!claim.negative); if (existing) { isConsolidated = true; continue; } claims.push(claim); } return isConsolidated ? new Assertion(claims) : what; } throw new Error("Invalid param: authorization or assertion required"); function replaceAlias(claim) { if (!model) return; // find works with alias const def = model.find(claim.token, "exact"); if (!def) { throw new InvalidClaim(Serializer_1.default.serializeTokenClaim(claim.token)); } // if token are different, it means it matched an alias if (!Token.equals(claim.token, def.fullToken)) { return Token.simplify(def.fullToken); } } } _getAuthorization(authorization) { return this._getCachedOrCreate(authorization, "authorization", authorization => Authorization.parse(authorization)); } _getAssertion(assertion) { return this._getCachedOrCreate(assertion, "assertion", assertion => Assertion.parse(assertion)); } _getCachedOrCreate(value, prefix, factory) { if (typeof value !== "string") { return [value, false]; } if (!this._cache) { return [factory(value), false]; } const key = this._getCacheKey(prefix, value); let item = this._cache.get(key); if (!item) { this._cache.set(key, item = [factory(value), false]); } return item; } _getCacheKey(prefix, value) { return `${prefix}/${value}`; } } exports.PermissionsChecker = PermissionsChecker;