@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
JavaScript
"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