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