UNPKG

@pothos/plugin-scope-auth

Version:

A Pothos plugin for adding scope based authorization checks to your GraphQL Schema

339 lines (338 loc) 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return RequestCache; } }); const _core = require("@pothos/core"); const _types = require("./types"); const _util = require("./util"); function _define_property(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const contextCache = (0, _core.createContextCache)((ctx, builder)=>new RequestCache(builder, ctx)); class RequestCache { static fromContext(context, builder) { return contextCache(context, builder); } static clearForContext(context) { contextCache.delete(context); } getScopes() { if (!this.scopes) { const scopes = this.builder.options.scopeAuth.authScopes(this.context); this.scopes = (0, _core.isThenable)(scopes) ? scopes.then((resolved)=>{ this.scopes = resolved; return resolved; }) : scopes; } return this.scopes; } withScopes(cb) { const scopes = this.getScopes(); if ((0, _core.isThenable)(scopes)) { return scopes.then((resolvedScopes)=>cb(resolvedScopes)); } return cb(scopes); } saveGrantedScopes(scopes, path) { const key = (0, _util.cacheKey)(path); if (this.grantCache.has(key)) { const set = this.grantCache.get(key); for (const scope of scopes){ set.add(scope); } } else { this.grantCache.set(key, new Set(scopes)); } return null; } testGrantedScopes(scope, path) { var _this_grantCache_get, _path_prev, _this_grantCache_get1; if ((_this_grantCache_get = this.grantCache.get((0, _util.cacheKey)(path.prev))) === null || _this_grantCache_get === void 0 ? void 0 : _this_grantCache_get.has(scope)) { return true; } if (typeof ((_path_prev = path.prev) === null || _path_prev === void 0 ? void 0 : _path_prev.key) === 'number' && ((_this_grantCache_get1 = this.grantCache.get((0, _util.cacheKey)(path.prev.prev))) === null || _this_grantCache_get1 === void 0 ? void 0 : _this_grantCache_get1.has(scope))) { return true; } return false; } grantTypeScopes(type, parent, path, cb) { if (!this.typeGrants.has(type)) { this.typeGrants.set(type, new Map()); } const cache = this.typeGrants.get(type); if (!cache.has(parent)) { const result = cb(); if ((0, _core.isThenable)(result)) { cache.set(parent, result.then((resolved)=>this.saveGrantedScopes(resolved, path))); } else { cache.set(parent, this.saveGrantedScopes(result, path)); } } return cache.get(parent); } evaluateScopeLoader(scopes, name, arg) { if (!this.scopeCache.has(name)) { this.scopeCache.set(name, new Map()); } const cache = this.scopeCache.get(name); const key = this.cacheKey ? this.cacheKey(arg) : arg; if (!cache.has(key)) { let loader = scopes[name]; if (typeof loader !== 'function') { throw new _core.PothosValidationError(`Attempted to evaluate scope ${String(name)} as scope loader, but it is not a function`); } loader = loader.bind(scopes); let result; if (this.treatErrorsAsUnauthorized) { try { result = loader(arg); } catch (error) { cache.set(key, { kind: _types.AuthScopeFailureType.AuthScope, scope: name, parameter: arg, error: error }); return cache.get(key); } } else { result = loader(arg); } if ((0, _core.isThenable)(result)) { let promise = result.then((r)=>r ? null : { kind: _types.AuthScopeFailureType.AuthScope, scope: name, parameter: arg, error: null }); if (this.treatErrorsAsUnauthorized) { promise = promise.catch((error)=>({ kind: _types.AuthScopeFailureType.AuthScope, scope: name, parameter: arg, error: error })); } cache.set(key, promise); } else { cache.set(key, result ? null : { kind: _types.AuthScopeFailureType.AuthScope, scope: name, parameter: arg, error: null }); } } return cache.get(key); } evaluateScopeMapWithScopes({ $all, $any, $granted, ...map }, scopes, info, forAll) { const scopeNames = Object.keys(map); const problems = []; const failure = { kind: forAll ? _types.AuthScopeFailureType.AllAuthScopes : _types.AuthScopeFailureType.AnyAuthScopes, failures: problems }; const loaderList = []; for (const scopeName of scopeNames){ if (scopes[scopeName] == null || scopes[scopeName] === false) { problems.push({ kind: _types.AuthScopeFailureType.AuthScope, scope: scopeName, parameter: map[scopeName], error: null }); if (forAll) { return failure; } continue; } const scope = scopes[scopeName]; if (typeof scope === 'function') { loaderList.push([ scopeName, map[scopeName] ]); } else if (scope && !forAll) { return null; } else if (!scope) { problems.push({ kind: _types.AuthScopeFailureType.AuthScope, scope: scopeName, parameter: map[scopeName], error: null }); if (forAll) { return failure; } } } const promises = []; if ($granted) { const result = !!info && this.testGrantedScopes($granted, info.path); if (result && !forAll) { return null; } if (!result) { problems.push({ kind: _types.AuthScopeFailureType.GrantedScope, scope: $granted }); if (forAll) { return failure; } } } if ($any) { const anyResult = this.evaluateScopeMap($any, info, false); if ((0, _core.isThenable)(anyResult)) { promises.push(anyResult); } else if (anyResult === null && !forAll) { return null; } else if (anyResult) { problems.push(anyResult); if (forAll) { return failure; } } } if ($all) { const allResult = this.evaluateScopeMap($all, info, true); if ((0, _core.isThenable)(allResult)) { promises.push(allResult); } else if (allResult === null && !forAll) { return resolveAndReturn(null); } else if (allResult) { problems.push(allResult); if (forAll) { return resolveAndReturn(failure); } } } for (const [loaderName, arg] of loaderList){ const result = this.evaluateScopeLoader(scopes, loaderName, arg); if ((0, _core.isThenable)(result)) { promises.push(result); } else if (result === null && !forAll) { return resolveAndReturn(null); } else if (result) { problems.push(result); if (forAll) { return resolveAndReturn(failure); } } } if (promises.length === 0) { return forAll && problems.length === 0 ? null : failure; } return Promise.all(promises).then((results)=>{ let hasSuccess = false; for (const result of results){ if (result) { problems.push(result); } else { hasSuccess = true; } } if (forAll) { return problems.length > 0 ? failure : null; } return hasSuccess ? null : failure; }); function resolveAndReturn(val) { if (promises.length > 0) { return Promise.all(promises).then(()=>val); } return val; } } evaluateScopeMap(map, info, forAll = this.defaultStrategy === 'all') { if (typeof map === 'boolean') { return map ? null : { kind: _types.AuthScopeFailureType.AuthScopeFunction, error: null }; } if (!this.mapCache.has(map)) { const result = this.withScopes((scopes)=>this.evaluateScopeMapWithScopes(map, scopes, info, forAll)); if ((0, _util.canCache)(map)) { this.mapCache.set(map, result); } return result; } return this.mapCache.get(map); } evaluateTypeScopeFunction(authScopes, type, parent, info) { const { typeCache } = this; if (!typeCache.has(type)) { typeCache.set(type, new Map()); } const cache = typeCache.get(type); if (!cache.has(parent)) { let result; if (this.treatErrorsAsUnauthorized) { try { result = authScopes(parent, this.context); } catch (error) { cache.set(parent, { kind: _types.AuthScopeFailureType.AuthScopeFunction, error: error }); return cache.get(parent); } } else { result = authScopes(parent, this.context); } if ((0, _core.isThenable)(result)) { let promise = result.then((resolved)=>this.evaluateScopeMap(resolved, info)); if (this.treatErrorsAsUnauthorized) { promise = promise.catch((error)=>({ kind: _types.AuthScopeFailureType.AuthScopeFunction, error: error })); } cache.set(parent, promise); } else { cache.set(parent, this.evaluateScopeMap(result, info)); } } return cache.get(parent); } constructor(builder, context){ var _builder_options_scopeAuth, _builder_options_scopeAuth1, _builder_options_scopeAuth2; _define_property(this, "builder", void 0); _define_property(this, "context", void 0); _define_property(this, "mapCache", new Map()); _define_property(this, "scopeCache", new Map()); _define_property(this, "typeCache", new Map()); _define_property(this, "typeGrants", new Map()); _define_property(this, "grantCache", new Map()); _define_property(this, "scopes", void 0); _define_property(this, "cacheKey", void 0); _define_property(this, "treatErrorsAsUnauthorized", void 0); _define_property(this, "defaultStrategy", void 0); this.builder = builder; this.context = context; this.cacheKey = (_builder_options_scopeAuth = builder.options.scopeAuth) === null || _builder_options_scopeAuth === void 0 ? void 0 : _builder_options_scopeAuth.cacheKey; var _builder_options_scopeAuth_treatErrorsAsUnauthorized; this.treatErrorsAsUnauthorized = (_builder_options_scopeAuth_treatErrorsAsUnauthorized = (_builder_options_scopeAuth1 = builder.options.scopeAuth) === null || _builder_options_scopeAuth1 === void 0 ? void 0 : _builder_options_scopeAuth1.treatErrorsAsUnauthorized) !== null && _builder_options_scopeAuth_treatErrorsAsUnauthorized !== void 0 ? _builder_options_scopeAuth_treatErrorsAsUnauthorized : false; var _builder_options_scopeAuth_defaultStrategy; this.defaultStrategy = (_builder_options_scopeAuth_defaultStrategy = (_builder_options_scopeAuth2 = builder.options.scopeAuth) === null || _builder_options_scopeAuth2 === void 0 ? void 0 : _builder_options_scopeAuth2.defaultStrategy) !== null && _builder_options_scopeAuth_defaultStrategy !== void 0 ? _builder_options_scopeAuth_defaultStrategy : 'any'; } } //# sourceMappingURL=request-cache.js.map