diff --git a/dist/jwtauth/AuthorizationBadge.d.ts b/dist/jwtauth/AuthorizationBadge.d.ts index 654e2ca..7f96bc3 100644 --- a/dist/jwtauth/AuthorizationBadge.d.ts +++ b/dist/jwtauth/AuthorizationBadge.d.ts @@ -21,9 +21,10 @@ export declare class AuthorizationBadge { scopes: string[]; effectiveScopes: string[]; constructor(jwtPayload?: JwtPayload, rolesConfig?: RolesConfig); - private getEffectiveScopes(rolesConfig); - private getParentScope(scope); + getJwtPayload(): JwtPayload; + sign(secret: string): string; + requireIds(...ids: ("giftbitUserId" | "merchantId" | "cardId" | "programId" | "recipientId" | "templateId" | "teamMemberId" | "serviceId")[]): void; isBadgeAuthorized(scope: string): boolean; requireScopes(...scopes: string[]): void; - requireIds(...ids: ("giftbitUserId" | "merchantId" | "cardId" | "programId" | "recipientId" | "templateId" | "teamMemberId" | "serviceId")[]): void; + private getEffectiveScopes(rolesConfig); } diff --git a/dist/jwtauth/AuthorizationBadge.js b/dist/jwtauth/AuthorizationBadge.js index 8372b3a..93ed9e1 100644 --- a/dist/jwtauth/AuthorizationBadge.js +++ b/dist/jwtauth/AuthorizationBadge.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const cassava = require("cassava"); +const jwt = require("jsonwebtoken"); /** * Expanded representation of the JWT payload. */ @@ -32,11 +33,63 @@ class AuthorizationBadge { this.issuedAtTime = new Date(jwtPayload.iat); } if (typeof jwtPayload.exp === "number") { - this.issuedAtTime = new Date(jwtPayload.exp * 1000); + this.expirationTime = new Date(jwtPayload.exp * 1000); } } this.effectiveScopes = this.getEffectiveScopes(rolesConfig); } + getJwtPayload() { + return { + g: { + gui: this.giftbitUserId, + gci: this.cardId, + gri: this.recipientId, + gti: this.templateId, + gmi: this.merchantId, + pid: this.programId, + tmi: this.teamMemberId, + si: this.serviceId + }, + aud: this.audience, + iss: this.issuer, + roles: this.roles.length ? this.roles : undefined, + scopes: this.scopes.length ? this.scopes : undefined, + jti: this.uniqueIdentifier, + iat: this.issuedAtTime ? this.issuedAtTime.getTime() / 1000 : undefined, + exp: this.expirationTime ? this.expirationTime.getTime() / 1000 : undefined + }; + } + sign(secret) { + return jwt.sign(this.getJwtPayload(), secret, { + algorithm: "HS256", + header: { + ver: 2, + vav: 1 + } + }); + } + requireIds(...ids) { + for (let id of ids) { + if (!this[id]) { + throw new cassava.RestError(cassava.httpStatusCode.clientError.FORBIDDEN); + } + } + } + isBadgeAuthorized(scope) { + for (; scope; scope = getParentScope(scope)) { + if (this.effectiveScopes.indexOf(scope) !== -1) { + return true; + } + } + return false; + } + requireScopes(...scopes) { + for (let scope of scopes) { + if (!this.isBadgeAuthorized(scope)) { + throw new cassava.RestError(cassava.httpStatusCode.clientError.FORBIDDEN); + } + } + } getEffectiveScopes(rolesConfig) { const effectiveScopes = []; if (rolesConfig) { @@ -72,38 +125,16 @@ class AuthorizationBadge { }); return effectiveScopes; } - getParentScope(scope) { - if (!scope || typeof scope !== "string") { - return null; - } - const lastSeparatorIx = scope.lastIndexOf(":"); - if (lastSeparatorIx === -1) { - return null; - } - return scope.substring(0, lastSeparatorIx); - } - isBadgeAuthorized(scope) { - for (; scope; scope = this.getParentScope(scope)) { - if (this.effectiveScopes.indexOf(scope) !== -1) { - return true; - } - } - return false; - } - requireScopes(...scopes) { - for (let scope of scopes) { - if (!this.isBadgeAuthorized(scope)) { - throw new cassava.RestError(cassava.httpStatusCode.clientError.FORBIDDEN); - } - } +} +exports.AuthorizationBadge = AuthorizationBadge; +function getParentScope(scope) { + if (!scope || typeof scope !== "string") { + return null; } - requireIds(...ids) { - for (let id of ids) { - if (!this[id]) { - throw new cassava.RestError(cassava.httpStatusCode.clientError.FORBIDDEN); - } - } + const lastSeparatorIx = scope.lastIndexOf(":"); + if (lastSeparatorIx === -1) { + return null; } + return scope.substring(0, lastSeparatorIx); } -exports.AuthorizationBadge = AuthorizationBadge; //# sourceMappingURL=AuthorizationBadge.js.map \ No newline at end of file diff --git a/dist/jwtauth/AuthorizationBadge.js.map b/dist/jwtauth/AuthorizationBadge.js.map index 369736f..7bae3bb 100644 --- a/dist/jwtauth/AuthorizationBadge.js.map +++ b/dist/jwtauth/AuthorizationBadge.js.map @@ -1 +1 @@ -{"version":3,"file":"AuthorizationBadge.js","sourceRoot":"","sources":["../../src/jwtauth/AuthorizationBadge.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"AuthorizationBadge.js","sourceRoot":"","sources":["../../src/jwtauth/AuthorizationBadge.ts"],"names":[],"mappings":";;AAAA,mCAAmC;AACnC,oCAAoC;AAIpC;;GAEG;AACH;IAqBI,YAAY,UAAuB,EAAE,WAAyB;QAJ9D,UAAK,GAAa,EAAE,CAAC;QACrB,WAAM,GAAa,EAAE,CAAC;QACtB,oBAAe,GAAa,EAAE,CAAC;QAG3B,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;YACb,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;gBACf,IAAI,CAAC,aAAa,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;gBACtC,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;gBAC/B,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;gBACpC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;gBACnC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;gBACnC,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;gBAClC,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;gBACrC,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YACrC,CAAC;YAED,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC;YAC/B,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC;YAC7B,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE,CAAC;YACpC,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,IAAI,EAAE,CAAC;YACtC,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAC,GAAG,CAAC;YAEvC,EAAE,CAAC,CAAC,OAAO,UAAU,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACrC,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;YACxD,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,UAAU,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC;gBAC5C,IAAI,CAAC,YAAY,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACjD,CAAC;YAED,EAAE,CAAC,CAAC,OAAO,UAAU,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACrC,IAAI,CAAC,cAAc,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;YAC1D,CAAC;QACL,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAChE,CAAC;IAED,aAAa;QACT,MAAM,CAAC;YACH,CAAC,EAAE;gBACC,GAAG,EAAE,IAAI,CAAC,aAAa;gBACvB,GAAG,EAAE,IAAI,CAAC,MAAM;gBAChB,GAAG,EAAE,IAAI,CAAC,WAAW;gBACrB,GAAG,EAAE,IAAI,CAAC,UAAU;gBACpB,GAAG,EAAE,IAAI,CAAC,UAAU;gBACpB,GAAG,EAAE,IAAI,CAAC,SAAS;gBACnB,GAAG,EAAE,IAAI,CAAC,YAAY;gBACtB,EAAE,EAAE,IAAI,CAAC,SAAS;aACrB;YACD,GAAG,EAAE,IAAI,CAAC,QAAQ;YAClB,GAAG,EAAE,IAAI,CAAC,MAAM;YAChB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,GAAG,SAAS;YACjD,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,SAAS;YACpD,GAAG,EAAE,IAAI,CAAC,gBAAgB;YAC1B,GAAG,EAAE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,IAAI,GAAG,SAAS;YACvE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,IAAI,GAAG,SAAS;SAC9E,CAAC;IACN,CAAC;IAED,IAAI,CAAC,MAAc;QACf,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE;YAC1C,SAAS,EAAE,OAAO;YAClB,MAAM,EAAE;gBACJ,GAAG,EAAE,CAAC;gBACN,GAAG,EAAE,CAAC;aACT;SACJ,CAAC,CAAC;IACP,CAAC;IAED,UAAU,CAAC,GAAG,GAA8H;QACxI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;YACjB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBACZ,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAC9E,CAAC;QACL,CAAC;IACL,CAAC;IAED,iBAAiB,CAAC,KAAa;QAC3B,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7C,MAAM,CAAC,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QACD,MAAM,CAAC,KAAK,CAAC;IACjB,CAAC;IAED,aAAa,CAAC,GAAG,MAAgB;QAC7B,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC;YACvB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACjC,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAC9E,CAAC;QACL,CAAC;IACL,CAAC;IAEO,kBAAkB,CAAC,WAAwB;QAC/C,MAAM,eAAe,GAAa,EAAE,CAAC;QAErC,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;YACd,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ;gBACvB,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;gBACtF,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;oBACd,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,gBAAgB,6BAA6B,QAAQ,EAAE,CAAC,CAAC;oBACjF,MAAM,CAAC;gBACX,CAAC;gBAED,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK;oBAC3B,EAAE,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;wBACxC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAChC,CAAC;gBACL,CAAC,CAAC,CAAC;YACP,CAAC,CAAC,CAAC;QACP,CAAC;QAED,IAAI,CAAC,MAAM;aACN,MAAM,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;aACpE,OAAO,CAAC,KAAK;YACV,EAAE,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACxC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC;QACL,CAAC,CAAC,CAAC;QAEP,IAAI,CAAC,MAAM;aACN,MAAM,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;aACnE,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;aAChC,OAAO,CAAC,KAAK;YACV,MAAM,UAAU,GAAG,KAAK,GAAG,GAAG,CAAC;YAE/B,IAAI,gBAAgB,GAAG,CAAC,CAAC;YACzB,OAAO,CAAC,gBAAgB,GAAG,eAAe,CAAC,SAAS,CAAC,cAAc,IAAI,cAAc,KAAK,KAAK,IAAI,cAAc,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAClK,eAAe,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;YAChD,CAAC;QACL,CAAC,CAAC,CAAC;QAEP,MAAM,CAAC,eAAe,CAAC;IAC3B,CAAC;CACJ;AAxJD,gDAwJC;AAED,wBAAwB,KAAa;IACjC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,eAAe,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC/C,EAAE,CAAC,CAAC,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;AAC/C,CAAC"} \ No newline at end of file diff --git a/package.json b/package.json index 1fe791d..14df297 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "giftbit-cassava-routes", - "version": "4.0.0", + "version": "4.1.0", "description": "Private Giftbit routes for use with Cassava.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -26,21 +26,21 @@ "cassava": "^0.2.1" }, "devDependencies": { - "@types/aws-lambda": "^0.0.14", - "@types/chai": "^4.0.1", - "@types/cookie": "^0.3.0", - "@types/jsonwebtoken": "^7.2.1", - "@types/mocha": "^2.2.41", + "@types/aws-lambda": "^0.0.16", + "@types/chai": "^4.0.4", + "@types/cookie": "^0.3.1", + "@types/jsonwebtoken": "^7.2.3", + "@types/mocha": "^2.2.43", "@types/node": "^8.0.8", "aws-sdk": "^2.81.0", - "chai": "^4.0.2", - "mocha": "^3.4.2", + "chai": "^4.1.2", + "mocha": "^3.5.0", "rimraf": "^2.6.1", "ts-node": "^3.1.0", "tslint": "^5.5.0", "typescript": "^2.4.1" }, "dependencies": { - "jsonwebtoken": "^7.4.1" + "jsonwebtoken": "^8.0.0" } } diff --git a/src/jwtauth/AuthorizationBadge.test.ts b/src/jwtauth/AuthorizationBadge.test.ts index 745d2a0..f9861c6 100644 --- a/src/jwtauth/AuthorizationBadge.test.ts +++ b/src/jwtauth/AuthorizationBadge.test.ts @@ -1,5 +1,7 @@ import * as chai from "chai"; +import * as jwt from "jsonwebtoken"; import {AuthorizationBadge} from "./AuthorizationBadge"; +import {JwtPayload} from "./JwtPayload"; describe("AuthorizationBadge", () => { describe("effectiveScopes", () => { @@ -221,4 +223,130 @@ describe("AuthorizationBadge", () => { chai.assert.isFalse(badge.isBadgeAuthorized("lightrailV1:foo:create:baz")); }); }); + + describe("getJwtPayload()", () => { + it("returns the same value the badge was constructed with", () => { + const jwt: Partial = { + "g": { + "gui": "user-7052210bcb94448b825ffa68508d29ad-TEST", + "gmi": "user-7052210bcb94448b825ffa68508d29ad-TEST" + }, + "iat": 1488911646.603, + "jti": "badge-dd95b9b582e840ecba1cbf41365d57e1", + "scopes": [ + "C", + "T", + "R", + "CEC", + "CER", + "UA", + "F" + ] + }; + + const auth = new AuthorizationBadge(jwt); + const newJwt = auth.getJwtPayload(); + + // Stringify and parse to remove undefineds. + chai.assert.notEqual(newJwt, jwt); + chai.assert.deepEqual(JSON.parse(JSON.stringify(newJwt)), jwt); + }); + + it("does not mix effective scopes into scopes", () => { + const jwt: Partial = { + "g": { + "gui": "user-7052210bcb94448b825ffa68508d29ad-TEST", + "gmi": "user-7052210bcb94448b825ffa68508d29ad-TEST" + }, + "iat": 1488911646.603, + "jti": "badge-dd95b9b582e840ecba1cbf41365d57e1", + "scopes": [ + "C", + "T", + "R", + "CEC", + "CER", + "UA", + "F", + "wildwest:okcorral:whisky:sipping", + ], + roles: [ + "DocHoliday", + "VirgilEarp" + ] + }; + const rolesConfig = { + roles: [ + { + name: "DocHoliday", + description: "", + scopes: [ + "wildwest:okcorral:gunfighter", + "wildwest:okcorral:dentist", + "wildwest:okcorral:gambler", + "wildwest:okcorral:law:deputy:temp" + ] + }, + { + name: "WyattEarp", + description: "", + scopes: [ + "wildwest:okcorral:gambler", + "wildwest:okcorral:law:deputy" + ] + }, + { + name: "VirgilEarp", + description: "", + scopes: [ + "wildwest:okcorral:law" + ] + } + ] + }; + + const auth = new AuthorizationBadge(jwt, rolesConfig); + const newJwt = auth.getJwtPayload(); + + // Stringify and parse to remove undefineds. + chai.assert.notEqual(newJwt, jwt); + chai.assert.deepEqual(JSON.parse(JSON.stringify(newJwt)), jwt); + }); + }); + + describe("sign()", () => { + it("returns the same jwt that was decoded", () => { + const originalHeader = { + "ver": 2, + "vav": 1, + "alg": "HS256", + "typ": "JWT" + }; + const originalPayload = { + "g": { + "gui": "user-7052210bcb94448b825ffa68508d29ad-TEST", + "gmi": "user-7052210bcb94448b825ffa68508d29ad-TEST" + }, + "iat": 1488911646.603, + "jti": "badge-dd95b9b582e840ecba1cbf41365d57e1", + "scopes": [ + "C", + "T", + "R", + "CEC", + "CER", + "UA", + "F" + ] + }; + + const auth = new AuthorizationBadge(originalPayload); + const newToken = auth.sign("secret"); + const newHeader = (jwt.decode(newToken, {complete: true}) as any).header; + const newPayload = jwt.verify(newToken, "secret", {ignoreExpiration: true, algorithms: ["HS256"]}); + + chai.assert.deepEqual(originalPayload, newPayload); + chai.assert.deepEqual(originalHeader, newHeader); + }); + }); }); diff --git a/src/jwtauth/AuthorizationBadge.ts b/src/jwtauth/AuthorizationBadge.ts index 96e7d2f..151fcdd 100644 --- a/src/jwtauth/AuthorizationBadge.ts +++ b/src/jwtauth/AuthorizationBadge.ts @@ -1,4 +1,5 @@ import * as cassava from "cassava"; +import * as jwt from "jsonwebtoken"; import {JwtPayload} from "./JwtPayload"; import {RolesConfig} from "../secureConfig/RolesConfig"; @@ -52,13 +53,70 @@ export class AuthorizationBadge { } if (typeof jwtPayload.exp === "number") { - this.issuedAtTime = new Date(jwtPayload.exp * 1000); + this.expirationTime = new Date(jwtPayload.exp * 1000); } } this.effectiveScopes = this.getEffectiveScopes(rolesConfig); } + getJwtPayload(): JwtPayload { + return { + g: { + gui: this.giftbitUserId, + gci: this.cardId, + gri: this.recipientId, + gti: this.templateId, + gmi: this.merchantId, + pid: this.programId, + tmi: this.teamMemberId, + si: this.serviceId + }, + aud: this.audience, + iss: this.issuer, + roles: this.roles.length ? this.roles : undefined, + scopes: this.scopes.length ? this.scopes : undefined, + jti: this.uniqueIdentifier, + iat: this.issuedAtTime ? this.issuedAtTime.getTime() / 1000 : undefined, + exp: this.expirationTime ? this.expirationTime.getTime() / 1000 : undefined + }; + } + + sign(secret: string): string { + return jwt.sign(this.getJwtPayload(), secret, { + algorithm: "HS256", + header: { + ver: 2, + vav: 1 + } + }); + } + + requireIds(...ids: ("giftbitUserId" | "merchantId" | "cardId" | "programId" | "recipientId" | "templateId" | "teamMemberId" | "serviceId")[]): void { + for (let id of ids) { + if (!this[id]) { + throw new cassava.RestError(cassava.httpStatusCode.clientError.FORBIDDEN); + } + } + } + + isBadgeAuthorized(scope: string): boolean { + for (; scope; scope = getParentScope(scope)) { + if (this.effectiveScopes.indexOf(scope) !== -1) { + return true; + } + } + return false; + } + + requireScopes(...scopes: string[]): void { + for (let scope of scopes) { + if (!this.isBadgeAuthorized(scope)) { + throw new cassava.RestError(cassava.httpStatusCode.clientError.FORBIDDEN); + } + } + } + private getEffectiveScopes(rolesConfig: RolesConfig): string[] { const effectiveScopes: string[] = []; @@ -100,42 +158,17 @@ export class AuthorizationBadge { return effectiveScopes; } +} - private getParentScope(scope: string): string { - if (!scope || typeof scope !== "string") { - return null; - } - - const lastSeparatorIx = scope.lastIndexOf(":"); - if (lastSeparatorIx === -1) { - return null; - } - - return scope.substring(0, lastSeparatorIx); +function getParentScope(scope: string): string { + if (!scope || typeof scope !== "string") { + return null; } - isBadgeAuthorized(scope: string): boolean { - for (; scope; scope = this.getParentScope(scope)) { - if (this.effectiveScopes.indexOf(scope) !== -1) { - return true; - } - } - return false; + const lastSeparatorIx = scope.lastIndexOf(":"); + if (lastSeparatorIx === -1) { + return null; } - requireScopes(...scopes: string[]): void { - for (let scope of scopes) { - if (!this.isBadgeAuthorized(scope)) { - throw new cassava.RestError(cassava.httpStatusCode.clientError.FORBIDDEN); - } - } - } - - requireIds(...ids: ("giftbitUserId" | "merchantId" | "cardId" | "programId" | "recipientId" | "templateId" | "teamMemberId" | "serviceId")[]): void { - for (let id of ids) { - if (!this[id]) { - throw new cassava.RestError(cassava.httpStatusCode.clientError.FORBIDDEN); - } - } - } + return scope.substring(0, lastSeparatorIx); }