205 lines
7.2 KiB
TypeScript
205 lines
7.2 KiB
TypeScript
import _ from 'lodash';
|
|
import { APIGatewayProxyEvent, Context } from 'aws-lambda';
|
|
import { ApolloError } from 'apollo-server-lambda';
|
|
import { AuthChecker, createMethodDecorator } from 'type-graphql';
|
|
|
|
import Firebase from '@seed/services/auth/FirebaseService';
|
|
import { ApolloContext } from '@seed/interfaces/context';
|
|
|
|
import AccountModel from 'src/accounts/account.model';
|
|
import Loaders from '@src/__indexes/__loaders';
|
|
import { AccountTypeEnum } from '@src/accounts/account.components';
|
|
|
|
import { simpleEstimator, getComplexity, fieldExtensionsEstimator } from 'graphql-query-complexity';
|
|
import { clog } from '@seed/helpers/Utils';
|
|
import { newError } from '@seed/helpers/Error';
|
|
import { SecurityLevelEnum } from '@services/module-accounts/account.components';
|
|
|
|
export const ctxMiddleware = async (ctx: { event?: APIGatewayProxyEvent; context?: Context; req?: any }): Promise<ApolloContext> => {
|
|
const user: AccountModel = new AccountModel();
|
|
let headers: any = {};
|
|
let organisationId = null;
|
|
|
|
try {
|
|
if (ctx.event) headers = ctx.event.headers;
|
|
else if (ctx.req) headers = ctx.req.headers;
|
|
|
|
/*
|
|
* Deal with Auth
|
|
*/
|
|
|
|
const authorization: string = headers['x-authorization'] || headers.authorization || headers.Authorization;
|
|
|
|
if (authorization) {
|
|
// Verify the token
|
|
const firebaseRes = await Firebase.getInstance().tokenIdentify(authorization);
|
|
// Verify that exists in DB
|
|
await user.getOne({ _id: firebaseRes.uid }, null);
|
|
|
|
/*
|
|
* Deal with Security
|
|
*/
|
|
|
|
const securityCheck = user.securityCheck();
|
|
if (!securityCheck) {
|
|
throw newError(2400, {
|
|
security: user.security,
|
|
securityLevel: process.env.SECURITY_LEVEL || SecurityLevelEnum.nothing,
|
|
});
|
|
}
|
|
|
|
/*
|
|
* Deal with Organisation
|
|
*/
|
|
organisationId = headers.organisationId || headers.organisationid;
|
|
if (organisationId) {
|
|
// Check if admin
|
|
if (user.types && user.types.includes(AccountTypeEnum.admin)) {
|
|
console.log('[ADMIN BYPASS]');
|
|
} else if (!user._id || !user.organisationIds || !user.organisationIds?.includes(organisationId))
|
|
throw newError(2101, {
|
|
accountOrganisationIds: user.organisationIds,
|
|
organisationId: organisationId,
|
|
});
|
|
|
|
console.log('[ORG]', organisationId);
|
|
}
|
|
|
|
console.log('[AUTH - ]', user.types, user?._id);
|
|
} else {
|
|
console.log('no auth');
|
|
}
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
event: ctx.event || ctx.req || {},
|
|
context: ctx.context || ctx.req || {},
|
|
ctx: {
|
|
organisationId: organisationId,
|
|
user: user,
|
|
loaders: new Loaders(),
|
|
},
|
|
};
|
|
};
|
|
|
|
export const authMiddleware: AuthChecker<ApolloContext> = ({ root, args, context, info }, roles) => {
|
|
const user = context.ctx.user;
|
|
|
|
if (!user || !user._id) {
|
|
throw newError(2004);
|
|
}
|
|
|
|
if (roles.length > 0) {
|
|
roles.push(AccountTypeEnum.admin);
|
|
|
|
const intersection = _.intersection(roles, user.types);
|
|
if (intersection.length > 0) return true;
|
|
|
|
// no roles matched, restrict access
|
|
throw newError(2000, { you: user.types, allowed: roles });
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
export const checkOrganisation = (check = false): any => {
|
|
return createMethodDecorator(async ({ root, args, context, info }, next) => {
|
|
if (check && !(context as any).ctx.organisationId) throw newError(2102, '400');
|
|
return next();
|
|
});
|
|
};
|
|
|
|
export const totalPublic = (check = false): any => {
|
|
return createMethodDecorator(async ({ root, args, context, info }, next) => {
|
|
if (check) (context as any).ctx.noOrganisationCheck = true;
|
|
return next();
|
|
});
|
|
};
|
|
|
|
export const errorMiddleware = (err): any => {
|
|
// Don't give the specific errors to the client.
|
|
|
|
if (err.extensions) {
|
|
const { translations, ...data } = err.extensions;
|
|
const errorToReturn = {
|
|
code: err.extensions.code,
|
|
message: err.extensions.message || err.message,
|
|
translations,
|
|
data: process.env.NODE_ENV == 'production' ? { message: 'see logs' } : data,
|
|
};
|
|
clog(errorToReturn);
|
|
|
|
return errorToReturn;
|
|
}
|
|
|
|
// Otherwise return the original error. The error can also
|
|
// be manipulated in other ways, so long as it's returned.
|
|
return err;
|
|
};
|
|
|
|
export const complexityMiddleware = (schema: any, variables: any, query: any) => {
|
|
const maximumComplexity = process.env.GRAPH_COMPLEXITY ? parseInt(process.env.GRAPH_COMPLEXITY) : 1000;
|
|
|
|
const complexity = getComplexity({
|
|
schema: schema,
|
|
|
|
// The maximum allowed query complexity, queries above this threshold will be rejected
|
|
|
|
// The query variables. This is needed because the variables are not available
|
|
// in the visitor of the graphql-js library
|
|
variables: variables,
|
|
|
|
// specify operation name only when pass multi-operation documents
|
|
query: query,
|
|
|
|
// Add any number of estimators. The estimators are invoked in order, the first
|
|
// numeric value that is being returned by an estimator is used as the field complexity.
|
|
// If no estimator returns a value, an exception is raised.
|
|
estimators: [
|
|
// Add more estimators here...
|
|
fieldExtensionsEstimator(),
|
|
// This will assign each field a complexity of 1 if no other estimator
|
|
// returned a value.
|
|
simpleEstimator({
|
|
defaultComplexity: 1,
|
|
}),
|
|
],
|
|
});
|
|
|
|
// Here we can react to the calculated complexity,
|
|
// like compare it with max and throw error when the threshold is reached.
|
|
if (complexity >= maximumComplexity) {
|
|
throw new Error(`Sorry, too complicated query! ${complexity} is over ${maximumComplexity} that is the max allowed complexity.`);
|
|
}
|
|
// And here we can e.g. subtract the complexity point from hourly API calls limit.
|
|
console.log('Used query complexity points:', complexity);
|
|
};
|
|
|
|
export const oneToManyComplexity = ({ args, childComplexity }): number => {
|
|
if (args.pagination && args.pagination.limit) return childComplexity * args.pagination.limit;
|
|
else return childComplexity * 10;
|
|
};
|
|
|
|
export const oneToManyComplexityNoLoader = ({ args, childComplexity }): number => {
|
|
if (args.pagination && args.pagination.limit) return childComplexity * args.pagination.limit * 100;
|
|
else return childComplexity * 10 * 100;
|
|
};
|
|
|
|
export const createApolloContext = async (accountId?: string, organisationId: string | null = null): Promise<ApolloContext> => {
|
|
const user: AccountModel = new AccountModel();
|
|
if (accountId) {
|
|
await user.getOne({ _id: accountId }, null);
|
|
}
|
|
return {
|
|
event: {} as any,
|
|
context: {} as any,
|
|
ctx: {
|
|
organisationId,
|
|
user: user,
|
|
loaders: new Loaders(),
|
|
},
|
|
};
|
|
};
|