UNPKG

@atproto/oauth-scopes

Version:

A library for manipulating and validating ATproto OAuth scopes in TypeScript.

173 lines • 6.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IncludeScope = exports.isNsid = void 0; const did_1 = require("@atproto/did"); const nsid_js_1 = require("../lib/nsid.js"); Object.defineProperty(exports, "isNsid", { enumerable: true, get: function () { return nsid_js_1.isNsid; } }); const parser_js_1 = require("../lib/parser.js"); const syntax_lexicon_js_1 = require("../lib/syntax-lexicon.js"); const syntax_string_js_1 = require("../lib/syntax-string.js"); const syntax_js_1 = require("../lib/syntax.js"); const repo_permission_js_1 = require("./repo-permission.js"); const rpc_permission_js_1 = require("./rpc-permission.js"); /** * This is used to handle "include:" oauth scope values, used to include * permissions from a lexicon defined permission set. Not being a resource * permission, it does not implement `Matchable`. */ class IncludeScope { constructor(nsid, aud = undefined) { Object.defineProperty(this, "nsid", { enumerable: true, configurable: true, writable: true, value: nsid }); Object.defineProperty(this, "aud", { enumerable: true, configurable: true, writable: true, value: aud }); } toString() { return IncludeScope.parser.format(this); } toPermissions(permissionSet) { return Array.from(this.buildPermissions(permissionSet)); } toScopes(permissionSet) { return Array.from(this.buildPermissions(permissionSet), (p) => p.toString()); } /** * Converts an "include:" to the list of permissions it includes, based on the * lexicon defined permission set. */ *buildPermissions(permissionSet) { for (const lexPermission of permissionSet.permissions) { const syntax = this.parseLexPermission(lexPermission); if (!syntax) continue; const resourcePermission = toResourcePermission(syntax); if (!resourcePermission) continue; if (this.isAllowedPermission(resourcePermission)) { yield resourcePermission; } } } parseLexPermission(permission) { // This function converts permissions listed in the permission set into // their respective ScopeSyntax representations, handling special cases as // needed. if (isLexPermissionForResource(permission, 'repo')) { return new syntax_lexicon_js_1.LexPermissionSyntax(permission); } if (isLexPermissionForResource(permission, 'rpc')) { // "rpc" permissions with a defined audience are not allowed in permission // sets if (permission.aud !== undefined && permission.aud !== '*') { return null; } // "rpc" permissions can "inherit" their audience from "aud" param defined // in the "include:<nsid>?aud=<audience>" scope the permission set was // loaded from. if (permission.inheritAud === true && permission.aud === undefined && this.aud !== undefined) { const { inheritAud, ...rest } = permission; return new syntax_lexicon_js_1.LexPermissionSyntax({ aud: this.aud, ...rest }); } return new syntax_lexicon_js_1.LexPermissionSyntax(permission); } return null; } /** * Verifies that a permission included through a lexicon permission set is * allowed in the context of the `include:` scope. This basically checks that * the permission is "under" the namespace authority of the `include:` scope, * and that it only contains "repo:", "rpc:", or "blob:" permissions. */ isAllowedPermission(permission) { if (permission instanceof rpc_permission_js_1.RpcPermission) { return permission.lxm.every(this.isParentAuthorityOf, this); } if (permission instanceof repo_permission_js_1.RepoPermission) { return permission.collection.every(this.isParentAuthorityOf, this); } throw new TypeError(`Unexpected permission ${permission}`); } /** * Verifies that a permission item's nsid is under the same authority as the * nsid of the lexicon itself (which is the same as the nsid of the `include:` * scope). */ isParentAuthorityOf(otherNsid) { if (otherNsid === '*') { return false; } const lexiconNsid = this.nsid; const groupPrefixEnd = lexiconNsid.lastIndexOf('.'); // There should always be a dot, but since this is a security feature, let's // be strict about it. if (groupPrefixEnd === -1) { throw new TypeError('Dot character (".") missing from lexicon NSID'); } // Make sure that otherNsid is at least as long as the "group prefix" if (groupPrefixEnd >= otherNsid.length - 1) { return false; } // Make sure that the "otherNsid" starts with the group of the lexiconNsid, // up to the dot itself. We check in reverse order as nsids tend to have // long common prefixes. for (let i = groupPrefixEnd; i >= 0; i--) { if (lexiconNsid.charCodeAt(i) !== otherNsid.charCodeAt(i)) { return false; } } return true; } static fromString(scope) { if (!(0, syntax_js_1.isScopeStringFor)(scope, 'include')) return null; const syntax = syntax_string_js_1.ScopeStringSyntax.fromString(scope); return IncludeScope.fromSyntax(syntax); } static fromSyntax(syntax) { const result = IncludeScope.parser.parse(syntax); if (!result) return null; return new IncludeScope(result.nsid, result.aud); } } exports.IncludeScope = IncludeScope; Object.defineProperty(IncludeScope, "parser", { enumerable: true, configurable: true, writable: true, value: new parser_js_1.Parser('include', { nsid: { multiple: false, required: true, validate: nsid_js_1.isNsid, }, aud: { multiple: false, required: false, validate: did_1.isAtprotoAudience, }, }, 'nsid') }); function toResourcePermission(syntax) { if ((0, syntax_js_1.isScopeSyntaxFor)(syntax, 'repo')) { return repo_permission_js_1.RepoPermission.fromSyntax(syntax); } if ((0, syntax_js_1.isScopeSyntaxFor)(syntax, 'rpc')) { return rpc_permission_js_1.RpcPermission.fromSyntax(syntax); } return null; } function isLexPermissionForResource(permission, type) { return permission.resource === type; } //# sourceMappingURL=include-scope.js.map