3010: Apollo 4 major update and complete security overhaul to prepare for public api support
* 2919: rewrite for update to apollo 4 (#131) * rewrite for update to apollo 4 * removed container tests, edited make to publish any branch to staging * revert makefile * cleanup * update 1 * more fixes to get it working on aws * enabled csrf prevention protection, testing if build works * switched to express version * changed the healthcheck url * added cache to restdatasources * 3011: create a login mutation on graphql server that responds with a access token (#133) * 3011 added login rest endpoint and general scope fixes * changed login auth to basic auth username and password * minor changes from CR feedback * force update * added tighter timeout for idle knex connection * Refactor authentication and scopes (#134) * big refactor of scopes in graphql * cr fixes * some security fixes (#135) * some security fixes * cleanup * update staging * minor readme change
This commit is contained in:
@@ -1,35 +1,102 @@
|
||||
import { AuthenticationError } from 'apollo-server';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { ClaimVerifyResult } from './cognito-client';
|
||||
|
||||
export const RESOURCE = 'https://graphql.photowall.com';
|
||||
|
||||
export enum SCOPES {
|
||||
export enum Scopes {
|
||||
ORDERS_READ = 'orders.read',
|
||||
ORDERS_WRITE = 'orders.write',
|
||||
DESIGNERS_READ = 'designers.read',
|
||||
DESIGNERS_PUBLIC_READ = 'designers.public.read',
|
||||
DESIGNERS_WRITE = 'designers.write',
|
||||
PRODUCTS_READ = 'products.read',
|
||||
PRODUCTS_PUBLIC_READ = 'products.public.read',
|
||||
PRODUCTS_WRITE = 'products.write',
|
||||
CATEGORIES_READ = 'categories.read',
|
||||
CATEGORIES_WRITE = 'categories.write',
|
||||
MARKETS_READ = 'markets.read',
|
||||
KEYWORDS_READ = 'keywords.read',
|
||||
KEYWORDS_WRITE = 'keywords.write',
|
||||
LOCALES_READ = 'locales.read',
|
||||
TEXTS_READ = 'texts.read',
|
||||
INTERIORS_READ = 'interiors.read',
|
||||
INTERIORS_WRITE = 'interiors.write',
|
||||
}
|
||||
|
||||
export function checkAccess(scopes: string[], auth: ClaimVerifyResult): void {
|
||||
if (auth.userName === 'testuser') {
|
||||
return;
|
||||
}
|
||||
if (auth.idToken) {
|
||||
if (auth.idToken['custom:adgroups'].includes('WebAdmin')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (auth.accessToken) {
|
||||
const authScopes = auth.accessToken.scope.split(' ');
|
||||
function getScopes(user: ClaimVerifyResult): Scopes[] | [] {
|
||||
if (user.accessToken) {
|
||||
const authScopes = user.accessToken.scope.split(' ');
|
||||
|
||||
const requiredScoped = scopes.map((s) => `${RESOURCE}/${s}`);
|
||||
const checker = (arr: string[], target: string[]) =>
|
||||
target.every((v) => arr.includes(v));
|
||||
if (checker(authScopes, requiredScoped)) {
|
||||
return;
|
||||
}
|
||||
return authScopes
|
||||
.map((scopeStr) => {
|
||||
if (scopeStr.indexOf(RESOURCE) === 0) {
|
||||
return Scopes[
|
||||
scopeStr
|
||||
.replace(`${RESOURCE}/`, '')
|
||||
.replaceAll('.', '_')
|
||||
.toLocaleUpperCase()
|
||||
];
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
throw new AuthenticationError('Not authorized for this endpoint');
|
||||
return [];
|
||||
}
|
||||
|
||||
function throwForbidden() {
|
||||
throw new GraphQLError('You are not authorized to perform this action', {
|
||||
extensions: { code: 'FORBIDDEN' },
|
||||
});
|
||||
}
|
||||
|
||||
export const ScopeAccess = (() => {
|
||||
const validationObject = (scopes) => {
|
||||
return {
|
||||
scopes,
|
||||
all(input: Scopes[]) {
|
||||
if (input.length === 0) throwForbidden(); // GUARD
|
||||
// If the input list contains something thats not in this.scopes array
|
||||
// then when we concat the lists and remove duplicates the length will be larger
|
||||
// in the new list than this.scopes.
|
||||
const checkList = [...new Set(this.scopes.concat(input))];
|
||||
if (checkList.length === this.scopes.length) return this;
|
||||
throwForbidden();
|
||||
},
|
||||
some(input: Scopes[]) {
|
||||
if (input.length === 0) throwForbidden(); // GUARD
|
||||
for (const s of input) {
|
||||
if (this.scopes.includes(s)) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
throwForbidden();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
validate(user) {
|
||||
// If ifToken user then give full access.
|
||||
// TODO: Should this be removed?
|
||||
if (user.idToken) {
|
||||
if (user.idToken['custom:adgroups'].includes('WebAdmin')) {
|
||||
return validationObject(Object.values(Scopes));
|
||||
}
|
||||
}
|
||||
|
||||
// If development environment and Basic auth user
|
||||
// push all scopes to give full access
|
||||
if (
|
||||
(process.env.NODE_ENV === 'development' ||
|
||||
process.env.NODE_ENV === 'test') &&
|
||||
user.userName === 'development_basic_user'
|
||||
) {
|
||||
return validationObject(Object.values(Scopes));
|
||||
}
|
||||
|
||||
// AccessToken with scopes
|
||||
return validationObject(getScopes(user));
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user