UNPKG

@pothos/plugin-scope-auth

Version:

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

348 lines (347 loc) 13.4 kB
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; } import { createContextCache, isThenable, PothosValidationError } from '@pothos/core'; import { AuthScopeFailureType } from './types.js'; import { cacheKey, canCache } from './util.js'; const contextCache = 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 = isThenable(scopes) ? scopes.then((resolved) => { this.scopes = resolved; return resolved; }) : scopes; } return this.scopes; } withScopes(cb) { const scopes = this.getScopes(); if (isThenable(scopes)) { return scopes.then((resolvedScopes) => cb(resolvedScopes)); } return cb(scopes); } saveGrantedScopes(scopes, path) { const key = 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(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(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 (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 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: AuthScopeFailureType.AuthScope, scope: name, parameter: arg, error: error }); return cache.get(key); } } else { result = loader(arg); } if (isThenable(result)) { let promise = result.then((r) => r ? null : { kind: AuthScopeFailureType.AuthScope, scope: name, parameter: arg, error: null }); if (this.treatErrorsAsUnauthorized) { promise = promise.catch((error) => ({ kind: AuthScopeFailureType.AuthScope, scope: name, parameter: arg, error: error })); } cache.set(key, promise); } else { cache.set(key, result ? null : { kind: 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 ? AuthScopeFailureType.AllAuthScopes : AuthScopeFailureType.AnyAuthScopes, failures: problems }; const loaderList = []; for (const scopeName of scopeNames) { if (scopes[scopeName] == null || scopes[scopeName] === false) { problems.push({ kind: 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: 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: AuthScopeFailureType.GrantedScope, scope: $granted }); if (forAll) { return failure; } } } if ($any) { const anyResult = this.evaluateScopeMap($any, info, false); if (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 (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 (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: AuthScopeFailureType.AuthScopeFunction, error: null }; } if (!this.mapCache.has(map)) { const result = this.withScopes((scopes) => this.evaluateScopeMapWithScopes(map, scopes, info, forAll)); if (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: AuthScopeFailureType.AuthScopeFunction, error: error }); return cache.get(parent); } } else { result = authScopes(parent, this.context); } if (isThenable(result)) { let promise = result.then((resolved) => this.evaluateScopeMap(resolved, info)); if (this.treatErrorsAsUnauthorized) { promise = promise.catch((error) => ({ kind: 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"; } } export { RequestCache as default }; //# sourceMappingURL=request-cache.js.map