Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions src/routes/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '~types';

import addressCanExecuteMutation from './mutations';
import addressCanExecuteQuery from './queries';

dotenv.config();

Expand All @@ -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(
Expand Down Expand Up @@ -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(
`${
Expand Down
62 changes: 62 additions & 0 deletions src/routes/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> => {
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<boolean> => {
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;
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down