From 7f341f510e187707e3150113fd49f22e9e581acd Mon Sep 17 00:00:00 2001 From: Anders Gustafsson <34234789+anders-photowall@users.noreply.github.com> Date: Wed, 10 May 2023 09:17:49 +0200 Subject: [PATCH] Fix scopes and promise handling (#152) --- README.md | 33 ++++++++++++++++++------------ src/cognito/access-control.ts | 1 + src/datasources/interior-api.ts | 9 +++++--- src/datasources/product-api.ts | 10 +++++++-- src/resolvers/orders-resolver.ts | 5 +++-- src/resolvers/products-resolver.ts | 33 +++++++++++++++++++++--------- src/rest/cognitoService.ts | 2 -- src/types/order-types.ts | 2 +- src/types/product-types.ts | 18 ++++++++-------- src/types/types.ts | 2 +- 10 files changed, 72 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 7fce978..2693c4d 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,23 @@ When clients want to use the public `data.photowall.com` they use the `/login` R Third party systems like Unbox (Synergy) will use the `graphql.photowall.com` to read their data like they are doing now. This is to make sure the website wont be affected by larger downloads of data like unbox does. Unbox has their own cognito app client with limited scopes. -## Run it locally +## Running queries on staging or production servers -Create an .env file (see .env.example for what to set). -`COGNITO_LOGIN_CLIENT_SECRET`, `COGNITO_LOGIN_CLIENT_ID` is found in cognito `photowall-test-staff` pool. In `photowall-web-test-client` app. +To tests staging or production servers its possible to run the login rest endpoint to get a Bearer access token. Check SSM for the stacks +`${EnvironmentName}/GRAPHQL_WEB_CLIENT_USERNAME` and `${EnvironmentName}/GRAPHQL_WEB_CLIENT_PASSWORD` and use those as Basic auth for the /login route. -## Publish it to staging +## Running it locally -Run `make publish-to-staging`. Please note that further commits on the same branch will not be updated automatically and the same command must be run again if changes are made. +### .env file + +Create an .env file in repo root (see .env.example for what to set). Depending on which resolvers you are going to use different environment variables are needed. Begin by setting DATABASE_URL at least, the rest can be added as needed. + +Notes: +COGNITO_LOGIN_CLIENT_ID and COGNITO_LOGIN_CLIENT_SECRET found in cognito `photowall-test-staff` pool. In `photowall-web-test-client` app but are only needed for testing or using the /login route to get proper Bearer authentication. ### Development -This project does only run in docker, so when developing start the docker environment and let it updated when changing files. Sometimes the watch for files doesnt trigger. Like when you edit the .graphql file. Then just edit a .ts file and it will reload. +This project runs in docker, so when developing start the docker environment and it will restart automatically when files are changed. Sometimes the watch for files doesn't trigger. Like when you edit the .graphql file. Then just edit a .ts file and it will reload. Run @@ -30,12 +35,16 @@ $ docker compose up For testing use a tool like Insomnia or similar. Server is running on http://localhost:4000/ and to connect you need to use `basic auth` with username and password from "GraphQl Basic Auth" in lastpass. -If you wish the Bearer Token used in prodlike environment also works. +If you wish the Bearer Token used in prodlike environment also works. See note under ".env file: above. -If you dont have access to photowall shared lastpass please contact any collegaue. +If you don't have access to photowall shared lastpass please contact any colleague. If you use Insomnia ask a colleague for the queries that can be exported/imported from Insomnia. -To tests the data.photowall.com its possible to run the login rest endpoint to get a Bearer accessToken. Check code for username and password. + +#### Publish feature branch to staging + +Run `make publish-to-staging`. Please note that further commits on the same branch will not be updated automatically and the same command must be run again if changes are made. + ### Run prod-like container locally @@ -46,9 +55,7 @@ $ docker compose -f docker-compose-prodlike.yml build $ docker compose -f docker-compose-prodlike.yml up ``` -Get your AD-token from this path in the browser `/admin/auth/token` -Take the value of the token param and add header in insomnia:Authorization: Bearer . -The token is valid for 2-3 hours. +To access it you must use the login endpoint to get a Bearer token ## Run tests @@ -58,6 +65,6 @@ Tests npm test ``` -### Force update the staging +### Force update staging update 5 diff --git a/src/cognito/access-control.ts b/src/cognito/access-control.ts index c0c7176..0d0c3d8 100644 --- a/src/cognito/access-control.ts +++ b/src/cognito/access-control.ts @@ -21,6 +21,7 @@ export enum Scopes { LOCALES_READ = 'locales.read', TEXTS_READ = 'texts.read', INTERIORS_READ = 'interiors.read', + INTERIORS_PUBLIC_READ = 'interiors.public.read', INTERIORS_WRITE = 'interiors.write', } diff --git a/src/datasources/interior-api.ts b/src/datasources/interior-api.ts index 3ac9262..c4bd28a 100644 --- a/src/datasources/interior-api.ts +++ b/src/datasources/interior-api.ts @@ -103,7 +103,10 @@ export class InteriorAPI extends BaseSQLDataSource { // ---------------------------- async getInteriors(filter: Maybe): Promise> { - ScopeAccess.validate(this.user).all([Scopes.INTERIORS_READ]); + ScopeAccess.validate(this.user).some([ + Scopes.INTERIORS_READ, + Scopes.INTERIORS_PUBLIC_READ, + ]); const sql = /* sql */ ` SELECT rooms.*, room_types.name roomType FROM rooms LEFT JOIN room_types ON rooms.room_type_id = room_types.id @@ -161,9 +164,9 @@ export class InteriorAPI extends BaseSQLDataSource { async getPrintProductInteriorsBatch( printIds: number[], ): Promise { - ScopeAccess.validate(this.user).all([ + ScopeAccess.validate(this.user).some([ Scopes.INTERIORS_READ, - Scopes.PRODUCTS_READ, + Scopes.INTERIORS_PUBLIC_READ, ]); return this.knex .select('*') diff --git a/src/datasources/product-api.ts b/src/datasources/product-api.ts index e6179e1..cb9d480 100644 --- a/src/datasources/product-api.ts +++ b/src/datasources/product-api.ts @@ -198,7 +198,10 @@ export class ProductAPI extends BaseSQLDataSource { // ---------------------------- async getProductsTotal(input: ProductsFilterInput): Promise { - ScopeAccess.validate(this.user).some([Scopes.PRODUCTS_READ]); + ScopeAccess.validate(this.user).some([ + Scopes.PRODUCTS_READ, + Scopes.PRODUCTS_PUBLIC_READ, + ]); const query = sql.productsTotal(input); const res = await this.cachedRaw(query, null) .cache(MINUTE * 5) @@ -254,7 +257,10 @@ export class ProductAPI extends BaseSQLDataSource { } async getProductByPath(path: string): Promise> { - ScopeAccess.validate(this.user).all([Scopes.PRODUCTS_READ]); + ScopeAccess.validate(this.user).some([ + Scopes.PRODUCTS_READ, + Scopes.PRODUCTS_PUBLIC_READ, + ]); const query = sql.productByPath(); const res = await this.knex.raw(query, [path]).then((data) => diff --git a/src/resolvers/orders-resolver.ts b/src/resolvers/orders-resolver.ts index 5062103..b1b6549 100644 --- a/src/resolvers/orders-resolver.ts +++ b/src/resolvers/orders-resolver.ts @@ -36,8 +36,9 @@ async function getOrdersResult( input: GeneralInput, { dataSources }, ): Promise { - const total = (dataSources.orderApi).getOrdersTotal(input); - const items = (dataSources.orderApi).getOrders(input); + const totalPromise = (dataSources.orderApi).getOrdersTotal(input); + const itemsPromise = (dataSources.orderApi).getOrders(input); + const [items, total] = await Promise.all([itemsPromise, totalPromise]); const offset = input.pagination?.offset ?? 0; return { items: items, diff --git a/src/resolvers/products-resolver.ts b/src/resolvers/products-resolver.ts index a35fc8c..e94722f 100644 --- a/src/resolvers/products-resolver.ts +++ b/src/resolvers/products-resolver.ts @@ -114,8 +114,11 @@ async function getProductsResult( extensions: { code: 'BAD_USER_INPUT' }, }); } - const total = (dataSources.productApi).getProductsTotal(input); - const items = (dataSources.productApi).getProducts(input); + const totalPromise = (dataSources.productApi).getProductsTotal( + input, + ); + const itemsPromise = (dataSources.productApi).getProducts(input); + const [total, items] = await Promise.all([totalPromise, itemsPromise]); const offset = input.pagination?.offset ?? 0; return { pagination: { @@ -136,7 +139,7 @@ async function getProductsSearchResult( const catApi = dataSources.categoryApi; const keywApi = dataSources.keywordApi; const designerApi = dataSources.designerApi; - const prods = Promise.all([ + const products = await Promise.all([ prodApi.searchByName(q), prodApi.searchByArtNo(q), prodApi.searchByCompleteProductId(q), @@ -152,15 +155,25 @@ async function getProductsSearchResult( return Object.values(products); }); + const [batches, keywords, categories, designers, copyrights, references] = + await Promise.all([ + prodApi.searchByBatch(q), + keywApi.searchKeywords(q), + catApi.searchCategories(q), + designerApi.searchDesigners(q), + prodApi.searchByCopyright(q), + prodApi.searchByReferences(q), + ]); + return { q: q, - products: prods, - batches: prodApi.searchByBatch(q), - keywords: keywApi.searchKeywords(q), - categories: catApi.searchCategories(q), - designers: designerApi.searchDesigners(q), - copyrights: prodApi.searchByCopyright(q), - references: prodApi.searchByReferences(q), + products: products, + batches: batches, + keywords: keywords, + categories: categories, + designers: designers, + copyrights: copyrights, + references: references, }; } diff --git a/src/rest/cognitoService.ts b/src/rest/cognitoService.ts index 5850039..7d45c90 100644 --- a/src/rest/cognitoService.ts +++ b/src/rest/cognitoService.ts @@ -1,5 +1,4 @@ import axios from 'axios'; -import { Scopes } from '../cognito/access-control'; export const getClientAccessToken = async (req) => { const token = req.headers.authorization || ''; @@ -29,7 +28,6 @@ export const getClientAccessToken = async (req) => { data: { grant_type: 'client_credentials', client_id: clientId, - scope: `https://graphql.photowall.com/${Scopes.PRODUCTS_PUBLIC_READ} https://graphql.photowall.com/${Scopes.DESIGNERS_PUBLIC_READ} https://graphql.photowall.com/${Scopes.CATEGORIES_PUBLIC_READ}`, }, }); diff --git a/src/types/order-types.ts b/src/types/order-types.ts index 721cf5a..e4910d7 100644 --- a/src/types/order-types.ts +++ b/src/types/order-types.ts @@ -2,7 +2,7 @@ import { PaginationResult } from './types'; export interface OrdersResult { pagination: PaginationResult; - items: Array | Promise>; + items: Array; } export interface Address { diff --git a/src/types/product-types.ts b/src/types/product-types.ts index 924c6f7..5f10c00 100644 --- a/src/types/product-types.ts +++ b/src/types/product-types.ts @@ -51,17 +51,17 @@ export interface ProductsFilter { export interface ProductsSearchResult { q: string; - products: Promise>; - batches: Promise>; - keywords: Promise>; - categories: Promise>; - designers: Promise>; - copyrights: Promise>; - references: Promise>; + products: Array; + batches: Array; + keywords: Array; + categories: Array; + designers: Array; + copyrights: Array; + references: Array; } export interface ProductListResult { pagination: PaginationResult; - items: Array | Promise>; + items: Array; } export enum ProductGroup { @@ -133,7 +133,7 @@ export interface Batch { export interface Copyright { name: string; - products: Promise>; + products: Array; } export interface Facet { diff --git a/src/types/types.ts b/src/types/types.ts index afc7f3c..50d301d 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -37,7 +37,7 @@ export interface GeneralInput { } export interface PaginationResult { - total: Promise | number; + total: number; offset: number; limit: number; }