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:
Arwid Thornström
2022-12-12 12:58:24 +01:00
committed by GitHub
parent 4248465031
commit c68c182b64
78 changed files with 3783 additions and 14606 deletions
+87 -20
View File
@@ -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));
},
};
})();