diff --git a/src/routes/graphql/graphql.ts b/src/routes/graphql/graphql.ts index 5a3283c..93ed753 100644 --- a/src/routes/graphql/graphql.ts +++ b/src/routes/graphql/graphql.ts @@ -21,6 +21,7 @@ import { } from '~types'; import addressCanExecuteMutation from './mutations'; +import addressCanExecuteQuery from './queries'; dotenv.config(); @@ -41,7 +42,10 @@ export const operationExecutionHandler: RequestHandler = async ( const requestRemoteAddress = getRemoteIpAddress(request); try { - response.locals.canExecute = await addressCanExecuteMutation(request); + response.locals.canExecuteMutation = await addressCanExecuteMutation( + request, + ); + response.locals.canExecuteQuery = await addressCanExecuteQuery(request); return nextFn(); } catch (error: any) { logger( @@ -81,12 +85,23 @@ export const graphQlProxyRouteHandler: Options = { ); /* - * Queries are all allowed, while mutations need to be handled on a case by case basis + * Mutations need to be handled on a case by case basis * Some are allowed without auth (cache refresh ones) * Others based on if the user has the appropriate address and/or role */ - const canExecute = - response.locals.canExecute || operationType === OperationTypes.Query; + const canExecuteMutation = + operationType === OperationTypes.Mutation && + response.locals.canExecuteMutation; + + /* + * By default, all queries are allowed + * However, some will not execute correctly if a user address is not provided + */ + const canExecuteQuery = + operationType === OperationTypes.Query && + response.locals.canExecuteQuery; + + const canExecute = canExecuteMutation || canExecuteQuery; logger( `${ diff --git a/src/routes/graphql/queries.ts b/src/routes/graphql/queries.ts new file mode 100644 index 0000000..b23869f --- /dev/null +++ b/src/routes/graphql/queries.ts @@ -0,0 +1,62 @@ +import { Request } from 'express-serve-static-core'; + +import { logger, detectOperation } from '~helpers'; +import { QueryOperations } from '~types'; + +const hasQueryPermissions = async ( + operationName: string, + request: Request, +): Promise => { + const userAddress = request.session.auth?.address; + const { variables = '{}' } = detectOperation(request.body); + + try { + switch (operationName) { + /* + * GetUserNotificationsHMAC will fail if no userAddress is provided + */ + case QueryOperations.GetUserNotificationsHMAC: { + if (!userAddress) { + return false; + } + + return true; + } + default: { + // By default all queries are permitted + return true; + } + } + } catch (error) { + logger( + `Error when attempting to check if user ${userAddress} can execute query ${operationName} with variables ${variables}`, + error, + ); + // By default all queries are permitted + return true; + } +}; + +const addressCanExecuteQuery = async (request: Request): Promise => { + try { + const { operations } = detectOperation(request.body); + + if (!operations.length) { + return true; + } + const canExecuteAllOperations = await Promise.all( + operations.map( + async (operationName) => + await hasQueryPermissions(operationName, request), + ), + ); + return canExecuteAllOperations.every((canExecute) => canExecute); + } catch (error) { + /* + * If anything fails still allow the query to execute as by default all queries are permitted + */ + return true; + } +}; + +export default addressCanExecuteQuery; diff --git a/src/types.ts b/src/types.ts index f0962be..0902a9e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,6 +64,14 @@ export enum MutationOperations { BridgeUpdateBankAccount = 'bridgeUpdateBankAccount', } +// All queries are allowed by default, add exceptions with specific rules here +export enum QueryOperations { + /* + * Notifications + */ + GetUserNotificationsHMAC = 'getUserNotificationsHMAC', +} + export enum HttpStatuses { OK = 200, BAD_REQUEST = 400,