Add access control (#65)
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
interface TokenResponse {
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
token_type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CognitoClient {
|
||||||
|
tokenEndpont: string;
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
scope: string;
|
||||||
|
expiresAt: number;
|
||||||
|
accessToken: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
tokenEndpoint: string,
|
||||||
|
clientId: string,
|
||||||
|
clientSecret: string,
|
||||||
|
scope: string,
|
||||||
|
) {
|
||||||
|
this.tokenEndpont = `${tokenEndpoint}/oauth2/token`;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
this.scope = scope;
|
||||||
|
this.accessToken = '';
|
||||||
|
this.expiresAt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPostData(): URLSearchParams {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('grant_type', 'client_credentials');
|
||||||
|
params.append('client_id', this.clientId);
|
||||||
|
params.append('scope', this.scope);
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getToken(): Promise<string> {
|
||||||
|
const now = Date.now();
|
||||||
|
if (this.expiresAt < now) {
|
||||||
|
await this.getNewToken();
|
||||||
|
}
|
||||||
|
console.log('Getting cached token');
|
||||||
|
return this.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getNewToken(): Promise<void> {
|
||||||
|
console.log('Getting new token');
|
||||||
|
const authString =
|
||||||
|
'Basic ' +
|
||||||
|
Buffer.from(this.clientId + ':' + this.clientSecret).toString('base64');
|
||||||
|
const res: TokenResponse = await axios
|
||||||
|
.post(this.tokenEndpont, this.getPostData(), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Authorization: authString,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((r) => r.data);
|
||||||
|
|
||||||
|
this.accessToken = res.access_token;
|
||||||
|
this.expiresAt = Date.now() + res.expires_in * 1e3;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import CognitoClient from './cognito-client';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
PW_GRAPHQL_URL: 'http://docker.for.mac.localhost:4000',
|
||||||
|
API_TOKEN_ENDPOINT:
|
||||||
|
'https://photowall-test-admin.auth.eu-west-1.amazoncognito.com',
|
||||||
|
API_CLIENT_ID: '',
|
||||||
|
API_CLIENT_SECRET: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doQuery(
|
||||||
|
query: string,
|
||||||
|
variables: any,
|
||||||
|
scopes: string[],
|
||||||
|
): Promise<any> {
|
||||||
|
const cognitoClient = new CognitoClient(
|
||||||
|
config.API_TOKEN_ENDPOINT,
|
||||||
|
config.API_CLIENT_ID,
|
||||||
|
config.API_CLIENT_SECRET,
|
||||||
|
scopes.join(' '),
|
||||||
|
);
|
||||||
|
let token = '';
|
||||||
|
token = await cognitoClient.getToken();
|
||||||
|
|
||||||
|
const url = config.PW_GRAPHQL_URL;
|
||||||
|
const res = await axios({
|
||||||
|
url: url,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
data: JSON.stringify({
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testGetOrder() {
|
||||||
|
const query = `
|
||||||
|
query GetOrder($id: ID!) {
|
||||||
|
order(id:$id){
|
||||||
|
id
|
||||||
|
klarnaOrderId
|
||||||
|
# paypalTransactionId
|
||||||
|
customerType
|
||||||
|
countryCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const variables = {
|
||||||
|
id: '414246',
|
||||||
|
};
|
||||||
|
const res = await doQuery(query, variables, [
|
||||||
|
'https://graphql.photowall.com/orders.read',
|
||||||
|
]);
|
||||||
|
console.log(JSON.stringify(res.data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testGetProduct() {
|
||||||
|
const query = `
|
||||||
|
query GetProduct($id: Int!) {
|
||||||
|
product(id:$id){
|
||||||
|
id
|
||||||
|
fields {
|
||||||
|
artNo
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const variables = {
|
||||||
|
id: 52924,
|
||||||
|
};
|
||||||
|
const res = await doQuery(query, variables, [
|
||||||
|
'https://graphql.photowall.com/products.read',
|
||||||
|
]);
|
||||||
|
console.log(JSON.stringify(res.data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Starting');
|
||||||
|
try {
|
||||||
|
await testGetOrder();
|
||||||
|
await testGetProduct();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e?.response.data.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
+3
-1
@@ -6,7 +6,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"exportSQL": "cd src/__test__/db_generator/ && npm install && node index.js && cd ../../../",
|
"exportSQL": "cd src/__test__/db_generator/ && npm install && node index.js && cd ../../../",
|
||||||
"test": "npm run exportSQL && jest --detectOpenHandles",
|
"test": "npm run exportSQL && jest --detectOpenHandles",
|
||||||
"test:dev": "npm run exportSQL && jest --watch"
|
"test:dev": "npm run exportSQL && jest --watch",
|
||||||
|
"unit:dev": "jest --watch src/__test__/unit.test.ts",
|
||||||
|
"test:manual": "cd manual-tests && ts-node test-scopes.ts"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { checkAccess } from '../cognito/access-control';
|
||||||
|
import {
|
||||||
|
AccessTokenClaim,
|
||||||
|
ClaimVerifyResult,
|
||||||
|
IdTokenClaim,
|
||||||
|
} from '../cognito/cognito-client';
|
||||||
|
|
||||||
|
const idTokenAuth = {
|
||||||
|
userName: 'niklas.fondberg@photowall.se',
|
||||||
|
isValid: true,
|
||||||
|
idToken: {
|
||||||
|
'custom:adgroups':
|
||||||
|
'[Domain Users, Images_Write, VPN_Users, WebAdmin, WebAdminSuperUser]',
|
||||||
|
'cognito:username': 'photowalladfs_photowall\\niklas.fondberg',
|
||||||
|
token_use: 'id',
|
||||||
|
email_verified: false,
|
||||||
|
nonce: 'Qc9kHCXrN----4st19sM',
|
||||||
|
at_hash: '9x_RMAO3_2NW1cQYkhWEtg',
|
||||||
|
email: 'niklas.fondberg@photowall.se',
|
||||||
|
aud: '53og32ofjne7e6i4uf10r3bg2b',
|
||||||
|
sub: '2646dd8b-f914-40eb-895b-c652976a65e1',
|
||||||
|
identities: [null],
|
||||||
|
} as IdTokenClaim,
|
||||||
|
accessToken: null,
|
||||||
|
} as ClaimVerifyResult;
|
||||||
|
|
||||||
|
const accessTokenAuth = {
|
||||||
|
isValid: true,
|
||||||
|
idToken: null,
|
||||||
|
accessToken: {
|
||||||
|
token_use: 'access',
|
||||||
|
username: '',
|
||||||
|
scope:
|
||||||
|
'https://graphql.photowall.com/orders.write https://graphql.photowall.com/orders.read',
|
||||||
|
client_id: '5vftcl43n2jkcdvd9imc270mtu',
|
||||||
|
} as AccessTokenClaim,
|
||||||
|
} as ClaimVerifyResult;
|
||||||
|
|
||||||
|
describe('GraphQL Apollo unit tests', () => {
|
||||||
|
describe('Test auth', () => {
|
||||||
|
it('user test is accepted', () => {
|
||||||
|
const auth: ClaimVerifyResult = {
|
||||||
|
userName: 'testuser',
|
||||||
|
idToken: null,
|
||||||
|
accessToken: null,
|
||||||
|
isValid: true,
|
||||||
|
};
|
||||||
|
expect(() => checkAccess(['orders.read'], auth)).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Test auth not ok for test', () => {
|
||||||
|
it('user bla is accepted', () => {
|
||||||
|
const auth: ClaimVerifyResult = {
|
||||||
|
userName: 'bla',
|
||||||
|
idToken: null,
|
||||||
|
accessToken: null,
|
||||||
|
isValid: true,
|
||||||
|
};
|
||||||
|
expect(() => checkAccess(['orders.read'], auth)).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test access auth', () => {
|
||||||
|
it('user test is accepted', () => {
|
||||||
|
const auth = accessTokenAuth;
|
||||||
|
expect(() =>
|
||||||
|
checkAccess(['orders.read', 'orders.write'], auth),
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test access auth not enough scope', () => {
|
||||||
|
it('user test is accepted', () => {
|
||||||
|
const auth = accessTokenAuth;
|
||||||
|
expect(() =>
|
||||||
|
checkAccess(['products.read', 'orders.read'], auth),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test id auth', () => {
|
||||||
|
it('user test is accepted', () => {
|
||||||
|
const auth = idTokenAuth;
|
||||||
|
expect(() =>
|
||||||
|
checkAccess(['orders.read', 'orders.write'], auth),
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Test id auth no enough', () => {
|
||||||
|
it('user test is accepted', () => {
|
||||||
|
const auth = JSON.parse(JSON.stringify(idTokenAuth)) as ClaimVerifyResult;
|
||||||
|
auth.idToken['custom:adgroups'] = '[]';
|
||||||
|
expect(() =>
|
||||||
|
checkAccess(['orders.read', 'orders.write'], auth),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { AuthenticationError } from 'apollo-server';
|
||||||
|
import { ClaimVerifyResult } from './cognito-client';
|
||||||
|
|
||||||
|
export const RESOURCE = 'https://graphql.photowall.com';
|
||||||
|
|
||||||
|
export enum SCOPES {
|
||||||
|
ORDERS_READ = 'orders.read',
|
||||||
|
ORDERS_WRITE = 'orders.write',
|
||||||
|
DESIGNERS_READ = 'designers.read',
|
||||||
|
DESIGNERS_WRITE = 'designers.write',
|
||||||
|
PRODUCTS_READ = 'products.read',
|
||||||
|
PRODUCTS_WRITE = 'products.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(' ');
|
||||||
|
|
||||||
|
const requiredScoped = scopes.map((s) => `${RESOURCE}/${s}`);
|
||||||
|
const checker = (arr: string[], target: string[]) =>
|
||||||
|
target.every((v) => arr.includes(v));
|
||||||
|
if (checker(authScopes, requiredScoped)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new AuthenticationError('Not authorized for this endpoint');
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ interface Claim {
|
|||||||
iss: string;
|
iss: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AccessTokenClaim extends Claim {
|
export interface AccessTokenClaim extends Claim {
|
||||||
token_use: string;
|
token_use: string;
|
||||||
username: string;
|
username: string;
|
||||||
client_id: string;
|
client_id: string;
|
||||||
@@ -57,7 +57,7 @@ interface AccessTokenClaim extends Claim {
|
|||||||
scope: string;
|
scope: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IdTokenClaim extends Claim {
|
export interface IdTokenClaim extends Claim {
|
||||||
'custom:adgroups': string; // 'custom:adgroups': '[Domain Users, Images_Write, VPN_Users, WebAdmin, WebAdminSuperUser]',
|
'custom:adgroups': string; // 'custom:adgroups': '[Domain Users, Images_Write, VPN_Users, WebAdmin, WebAdminSuperUser]',
|
||||||
'cognito:username': string;
|
'cognito:username': string;
|
||||||
token_use: string;
|
token_use: string;
|
||||||
|
|||||||
+10
-3
@@ -15,7 +15,7 @@ import { MarketLocaleAPI } from './datasources/market-locale-api';
|
|||||||
import { ImageServerApi } from './datasources/imageserver-api';
|
import { ImageServerApi } from './datasources/imageserver-api';
|
||||||
import { InteriorAPI } from './datasources/interior-api';
|
import { InteriorAPI } from './datasources/interior-api';
|
||||||
|
|
||||||
import { verifyToken } from './cognito/cognito-client';
|
import { ClaimVerifyResult, verifyToken } from './cognito/cognito-client';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { InteriorsLambdaAPI } from './datasources/interiors-lambda-api';
|
import { InteriorsLambdaAPI } from './datasources/interiors-lambda-api';
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -51,7 +51,7 @@ const dataSources = () => ({
|
|||||||
|
|
||||||
const context = async ({ req }) => {
|
const context = async ({ req }) => {
|
||||||
if (process.env.NODE_ENV == 'test') {
|
if (process.env.NODE_ENV == 'test') {
|
||||||
return { auth: 'testuser' };
|
return { auth: { userName: 'testuser' } };
|
||||||
}
|
}
|
||||||
|
|
||||||
const authHeader = req.headers.authorization || '';
|
const authHeader = req.headers.authorization || '';
|
||||||
@@ -65,7 +65,14 @@ const context = async ({ req }) => {
|
|||||||
process.env.NODE_ENV === 'development' &&
|
process.env.NODE_ENV === 'development' &&
|
||||||
authHeader == 'Basic ZGV2OnB2N1VmYSFiZVAzeGk='
|
authHeader == 'Basic ZGV2OnB2N1VmYSFiZVAzeGk='
|
||||||
) {
|
) {
|
||||||
return { auth: 'during development' };
|
return {
|
||||||
|
auth: {
|
||||||
|
userName: 'testuser',
|
||||||
|
isValid: true,
|
||||||
|
accessToken: null,
|
||||||
|
idToken: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
throw new AuthenticationError('Not authorized');
|
throw new AuthenticationError('Not authorized');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { checkAccess } from '../cognito/access-control';
|
||||||
import { CategoryAPI } from '../datasources/category-api';
|
import { CategoryAPI } from '../datasources/category-api';
|
||||||
import { ProductAPI } from '../datasources/product-api';
|
import { ProductAPI } from '../datasources/product-api';
|
||||||
|
|
||||||
const Category = {
|
const Category = {
|
||||||
async products({ id }, _args, { dataSources }) {
|
async products({ id }, _args, { dataSources, auth }) {
|
||||||
|
checkAccess(['products.read'], auth);
|
||||||
return (<ProductAPI>dataSources.productApi).getCategoryProducts(id);
|
return (<ProductAPI>dataSources.productApi).getCategoryProducts(id);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
|
import { checkAccess } from '../cognito/access-control';
|
||||||
import { DesignerAPI } from '../datasources/designer-api';
|
import { DesignerAPI } from '../datasources/designer-api';
|
||||||
import { OrderAPI } from '../datasources/order-api';
|
import { OrderAPI } from '../datasources/order-api';
|
||||||
import { ProductAPI } from '../datasources/product-api';
|
import { ProductAPI } from '../datasources/product-api';
|
||||||
import { GeneralInput } from '../types/types';
|
import { GeneralInput } from '../types/types';
|
||||||
|
|
||||||
const Designer = {
|
const Designer = {
|
||||||
async orderRows({ id }, input: GeneralInput, { dataSources }) {
|
async orderRows({ id }, input: GeneralInput, { dataSources, auth }) {
|
||||||
|
checkAccess(['orders.read'], auth);
|
||||||
return (<OrderAPI>dataSources.orderApi).getOrderRowsByDesignerId(id, input);
|
return (<OrderAPI>dataSources.orderApi).getOrderRowsByDesignerId(id, input);
|
||||||
},
|
},
|
||||||
async products({ id }, _args, { dataSources }) {
|
async products({ id }, _args, { dataSources, auth }) {
|
||||||
|
checkAccess(['products.read'], auth);
|
||||||
return (<ProductAPI>dataSources.productApi).getDesignerProducts(id);
|
return (<ProductAPI>dataSources.productApi).getDesignerProducts(id);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getDesigners(_, { limit }, { dataSources }) {
|
async function getDesigners(_, { limit }, { dataSources, auth }) {
|
||||||
|
checkAccess(['designers.read'], auth);
|
||||||
return (<DesignerAPI>dataSources.designerApi).getDesigners(limit);
|
return (<DesignerAPI>dataSources.designerApi).getDesigners(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDesigner(parent, { id }, { dataSources }) {
|
async function getDesigner(_, { id }, { dataSources, auth }) {
|
||||||
|
checkAccess(['designers.read'], auth);
|
||||||
return (<DesignerAPI>dataSources.designerApi).getDesignerById(id);
|
return (<DesignerAPI>dataSources.designerApi).getDesignerById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import { checkAccess } from '../cognito/access-control';
|
||||||
import { KeywordAPI } from '../datasources/keyword-api';
|
import { KeywordAPI } from '../datasources/keyword-api';
|
||||||
import { ProductAPI } from '../datasources/product-api';
|
import { ProductAPI } from '../datasources/product-api';
|
||||||
|
|
||||||
const Keyword = {
|
const Keyword = {
|
||||||
async products({ id }, _args, { dataSources }) {
|
async products({ id }, _args, { dataSources, auth }) {
|
||||||
|
checkAccess(['products.read'], auth);
|
||||||
return (<ProductAPI>dataSources.productApi).getKeywordProducts(id);
|
return (<ProductAPI>dataSources.productApi).getKeywordProducts(id);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { checkAccess } from '../cognito/access-control';
|
||||||
import { MarketLocaleAPI } from '../datasources/market-locale-api';
|
import { MarketLocaleAPI } from '../datasources/market-locale-api';
|
||||||
import { OrderAPI } from '../datasources/order-api';
|
import { OrderAPI } from '../datasources/order-api';
|
||||||
import { OrdersResult } from '../types/order-types';
|
import { OrdersResult } from '../types/order-types';
|
||||||
@@ -27,7 +28,8 @@ const Order = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const OrderRow = {
|
const OrderRow = {
|
||||||
async order(parent, _args, { dataSources }) {
|
async order(parent, _args, { dataSources, auth }) {
|
||||||
|
checkAccess(['orders.read'], auth);
|
||||||
return (<OrderAPI>dataSources.orderApi).getOrderById(parent.orderId);
|
return (<OrderAPI>dataSources.orderApi).getOrderById(parent.orderId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -35,8 +37,9 @@ const OrderRow = {
|
|||||||
async function getOrdersResult(
|
async function getOrdersResult(
|
||||||
_,
|
_,
|
||||||
input: GeneralInput,
|
input: GeneralInput,
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
): Promise<OrdersResult> {
|
): Promise<OrdersResult> {
|
||||||
|
checkAccess(['orders.read'], auth);
|
||||||
const total = (<OrderAPI>dataSources.orderApi).getOrdersTotal(input);
|
const total = (<OrderAPI>dataSources.orderApi).getOrdersTotal(input);
|
||||||
const items = (<OrderAPI>dataSources.orderApi).getOrders(input);
|
const items = (<OrderAPI>dataSources.orderApi).getOrders(input);
|
||||||
return {
|
return {
|
||||||
@@ -49,7 +52,8 @@ async function getOrdersResult(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getOrder(_, { id }, { dataSources }) {
|
async function getOrder(_, { id }, { dataSources, auth }) {
|
||||||
|
checkAccess(['orders.read'], auth);
|
||||||
return (<OrderAPI>dataSources.orderApi).getOrderById(id);
|
return (<OrderAPI>dataSources.orderApi).getOrderById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { InteriorsLambdaAPI } from '../datasources/interiors-lambda-api';
|
|||||||
import { IdNumberResult } from '../types/types';
|
import { IdNumberResult } from '../types/types';
|
||||||
import { moveS3File } from '../s3';
|
import { moveS3File } from '../s3';
|
||||||
import { DesignerAPI } from '../datasources/designer-api';
|
import { DesignerAPI } from '../datasources/designer-api';
|
||||||
|
import { checkAccess } from '../cognito/access-control';
|
||||||
|
|
||||||
const ProductBlacklist = {
|
const ProductBlacklist = {
|
||||||
async market(parent, _args, { dataSources }) {
|
async market(parent, _args, { dataSources }) {
|
||||||
@@ -30,7 +31,8 @@ const Product = {
|
|||||||
async categories({ id }, _args, { dataSources }): Promise<Array<Category>> {
|
async categories({ id }, _args, { dataSources }): Promise<Array<Category>> {
|
||||||
return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id);
|
return (<CategoryAPI>dataSources.categoryApi).getProductCategories(id);
|
||||||
},
|
},
|
||||||
async designer({ designerId }, _args, { dataSources }) {
|
async designer({ designerId }, _args, { dataSources, auth }) {
|
||||||
|
checkAccess(['designers.read'], auth);
|
||||||
if (!designerId) {
|
if (!designerId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -53,8 +55,9 @@ const PrintProduct = {
|
|||||||
async function getProductsResult(
|
async function getProductsResult(
|
||||||
_,
|
_,
|
||||||
input: ProductsFilterInput,
|
input: ProductsFilterInput,
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
): Promise<ProductListResult> {
|
): Promise<ProductListResult> {
|
||||||
|
checkAccess(['products.read'], auth);
|
||||||
const total = (<ProductAPI>dataSources.productApi).getProductsTotal(input);
|
const total = (<ProductAPI>dataSources.productApi).getProductsTotal(input);
|
||||||
const items = (<ProductAPI>dataSources.productApi).getProducts(input);
|
const items = (<ProductAPI>dataSources.productApi).getProducts(input);
|
||||||
|
|
||||||
@@ -71,8 +74,9 @@ async function getProductsResult(
|
|||||||
async function getProductsSearchResult(
|
async function getProductsSearchResult(
|
||||||
_,
|
_,
|
||||||
{ q },
|
{ q },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
): Promise<ProductsSearchResult> {
|
): Promise<ProductsSearchResult> {
|
||||||
|
checkAccess(['products.read', 'designers.read'], auth);
|
||||||
const prodApi = <ProductAPI>dataSources.productApi;
|
const prodApi = <ProductAPI>dataSources.productApi;
|
||||||
const catApi = <CategoryAPI>dataSources.categoryApi;
|
const catApi = <CategoryAPI>dataSources.categoryApi;
|
||||||
const keywApi = <KeywordAPI>dataSources.keywordApi;
|
const keywApi = <KeywordAPI>dataSources.keywordApi;
|
||||||
@@ -92,7 +96,7 @@ async function getProductsSearchResult(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getProduct(_, { id }, { dataSources }): Promise<Product> {
|
async function getProduct(_, { id }, { dataSources, auth }): Promise<Product> {
|
||||||
return (<ProductAPI>dataSources.productApi).getProduct(id);
|
return (<ProductAPI>dataSources.productApi).getProduct(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +115,16 @@ export const productQueryTypeDefs = {
|
|||||||
///////////////////
|
///////////////////
|
||||||
// Mutations below
|
// Mutations below
|
||||||
export const productMutationTypeDefs = {
|
export const productMutationTypeDefs = {
|
||||||
async addProduct(_, { name, batch }, { dataSources }) {
|
async addProduct(_, { name, batch }, { dataSources, auth }) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
const id = await (<ProductAPI>dataSources.productApi).addProduct(
|
const id = await (<ProductAPI>dataSources.productApi).addProduct(
|
||||||
name,
|
name,
|
||||||
batch,
|
batch,
|
||||||
);
|
);
|
||||||
return (<ProductAPI>dataSources.productApi).getProduct(id);
|
return (<ProductAPI>dataSources.productApi).getProduct(id);
|
||||||
},
|
},
|
||||||
async productInfo(_, { productId, info }, { dataSources }) {
|
async productInfo(_, { productId, info }, { dataSources, auth }) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).updateProductInfo(
|
await (<ProductAPI>dataSources.productApi).updateProductInfo(
|
||||||
productId,
|
productId,
|
||||||
info,
|
info,
|
||||||
@@ -128,8 +134,9 @@ export const productMutationTypeDefs = {
|
|||||||
async productFocusPoint(
|
async productFocusPoint(
|
||||||
_,
|
_,
|
||||||
{ productId, focusXpoint2, focusYpoint2 },
|
{ productId, focusXpoint2, focusYpoint2 },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
) {
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).updateFocusPoint(
|
await (<ProductAPI>dataSources.productApi).updateFocusPoint(
|
||||||
productId,
|
productId,
|
||||||
focusXpoint2,
|
focusXpoint2,
|
||||||
@@ -145,14 +152,16 @@ export const productMutationTypeDefs = {
|
|||||||
)).generateNewInteriors(product);
|
)).generateNewInteriors(product);
|
||||||
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
||||||
},
|
},
|
||||||
async productBlacklisting(_, { productId, markets }, { dataSources }) {
|
async productBlacklisting(_, { productId, markets }, { dataSources, auth }) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).updateBlacklisting(
|
await (<ProductAPI>dataSources.productApi).updateBlacklisting(
|
||||||
productId,
|
productId,
|
||||||
markets,
|
markets,
|
||||||
);
|
);
|
||||||
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
||||||
},
|
},
|
||||||
async productGroup(_, { productId, groupIds }, { dataSources }) {
|
async productGroup(_, { productId, groupIds }, { dataSources, auth }) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).updateGroups(
|
await (<ProductAPI>dataSources.productApi).updateGroups(
|
||||||
productId,
|
productId,
|
||||||
groupIds,
|
groupIds,
|
||||||
@@ -168,8 +177,9 @@ export const productMutationTypeDefs = {
|
|||||||
async productGroupInteriors(
|
async productGroupInteriors(
|
||||||
_,
|
_,
|
||||||
{ productId, groupId, uris },
|
{ productId, groupId, uris },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
) {
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).addInteriorsToProductGroup(
|
await (<ProductAPI>dataSources.productApi).addInteriorsToProductGroup(
|
||||||
productId,
|
productId,
|
||||||
groupId,
|
groupId,
|
||||||
@@ -177,7 +187,8 @@ export const productMutationTypeDefs = {
|
|||||||
);
|
);
|
||||||
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
||||||
},
|
},
|
||||||
async productKeywords(_, { productId, keywordIds }, { dataSources }) {
|
async productKeywords(_, { productId, keywordIds }, { dataSources, auth }) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<KeywordAPI>dataSources.keywordApi).setProductKeywords(
|
await (<KeywordAPI>dataSources.keywordApi).setProductKeywords(
|
||||||
productId,
|
productId,
|
||||||
keywordIds,
|
keywordIds,
|
||||||
@@ -187,8 +198,9 @@ export const productMutationTypeDefs = {
|
|||||||
async productProportionsWarning(
|
async productProportionsWarning(
|
||||||
_,
|
_,
|
||||||
{ productId, proportions },
|
{ productId, proportions },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
) {
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).setProportionsWarning(
|
await (<ProductAPI>dataSources.productApi).setProportionsWarning(
|
||||||
productId,
|
productId,
|
||||||
proportions,
|
proportions,
|
||||||
@@ -198,8 +210,9 @@ export const productMutationTypeDefs = {
|
|||||||
async addOwnInteriorToPrintProduct(
|
async addOwnInteriorToPrintProduct(
|
||||||
_,
|
_,
|
||||||
{ printId, uploadedS3Key },
|
{ printId, uploadedS3Key },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
): Promise<IdNumberResult> {
|
): Promise<IdNumberResult> {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
return (<InteriorAPI>dataSources.interiorApi).addOwnUploadToPrintId(
|
return (<InteriorAPI>dataSources.interiorApi).addOwnUploadToPrintId(
|
||||||
printId,
|
printId,
|
||||||
uploadedS3Key,
|
uploadedS3Key,
|
||||||
@@ -208,8 +221,9 @@ export const productMutationTypeDefs = {
|
|||||||
async productWallpaperType(
|
async productWallpaperType(
|
||||||
_,
|
_,
|
||||||
{ productId, wallpaperTypes },
|
{ productId, wallpaperTypes },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
) {
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).setWallpaperTypes(
|
await (<ProductAPI>dataSources.productApi).setWallpaperTypes(
|
||||||
productId,
|
productId,
|
||||||
wallpaperTypes,
|
wallpaperTypes,
|
||||||
@@ -219,8 +233,9 @@ export const productMutationTypeDefs = {
|
|||||||
async updateProductImage(
|
async updateProductImage(
|
||||||
_,
|
_,
|
||||||
{ productId, uploadedS3Key, width, height },
|
{ productId, uploadedS3Key, width, height },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
) {
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await moveS3File(
|
await moveS3File(
|
||||||
CONFIG.uploadBucket,
|
CONFIG.uploadBucket,
|
||||||
uploadedS3Key,
|
uploadedS3Key,
|
||||||
@@ -246,7 +261,12 @@ export const productMutationTypeDefs = {
|
|||||||
)).generateNewInteriors(product);
|
)).generateNewInteriors(product);
|
||||||
return 'success';
|
return 'success';
|
||||||
},
|
},
|
||||||
async addRelatedProducts(_, { productId, articleNumbers }, { dataSources }) {
|
async addRelatedProducts(
|
||||||
|
_,
|
||||||
|
{ productId, articleNumbers },
|
||||||
|
{ dataSources, auth },
|
||||||
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).addRelatedProducts(
|
await (<ProductAPI>dataSources.productApi).addRelatedProducts(
|
||||||
productId,
|
productId,
|
||||||
articleNumbers,
|
articleNumbers,
|
||||||
@@ -256,8 +276,9 @@ export const productMutationTypeDefs = {
|
|||||||
async removeRelatedProducts(
|
async removeRelatedProducts(
|
||||||
_,
|
_,
|
||||||
{ productId, relatedProductIds },
|
{ productId, relatedProductIds },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
) {
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).removeRelatedProducts(
|
await (<ProductAPI>dataSources.productApi).removeRelatedProducts(
|
||||||
productId,
|
productId,
|
||||||
relatedProductIds,
|
relatedProductIds,
|
||||||
@@ -265,7 +286,12 @@ export const productMutationTypeDefs = {
|
|||||||
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
||||||
},
|
},
|
||||||
|
|
||||||
async addCategoriesToProduct(_, { productId, categoryIds }, { dataSources }) {
|
async addCategoriesToProduct(
|
||||||
|
_,
|
||||||
|
{ productId, categoryIds },
|
||||||
|
{ dataSources, auth },
|
||||||
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).addCategoriesToProduct(
|
await (<ProductAPI>dataSources.productApi).addCategoriesToProduct(
|
||||||
productId,
|
productId,
|
||||||
categoryIds,
|
categoryIds,
|
||||||
@@ -276,15 +302,21 @@ export const productMutationTypeDefs = {
|
|||||||
async removeCategoriesFromProduct(
|
async removeCategoriesFromProduct(
|
||||||
_,
|
_,
|
||||||
{ productId, categoryIds },
|
{ productId, categoryIds },
|
||||||
{ dataSources },
|
{ dataSources, auth },
|
||||||
) {
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).removeCategoriesFromProduct(
|
await (<ProductAPI>dataSources.productApi).removeCategoriesFromProduct(
|
||||||
productId,
|
productId,
|
||||||
categoryIds,
|
categoryIds,
|
||||||
);
|
);
|
||||||
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
return (<ProductAPI>dataSources.productApi).getProduct(productId);
|
||||||
},
|
},
|
||||||
async updateProductComments(_, { productId, comments }, { dataSources }) {
|
async updateProductComments(
|
||||||
|
_,
|
||||||
|
{ productId, comments },
|
||||||
|
{ dataSources, auth },
|
||||||
|
) {
|
||||||
|
checkAccess(['products.write'], auth);
|
||||||
await (<ProductAPI>dataSources.productApi).updateComments(
|
await (<ProductAPI>dataSources.productApi).updateComments(
|
||||||
productId,
|
productId,
|
||||||
comments,
|
comments,
|
||||||
|
|||||||
Reference in New Issue
Block a user