import _ from 'lodash'; import { APIGatewayProxyEvent, Context } from 'aws-lambda'; import { ApolloError } from 'apollo-server-lambda'; import { ClassType, createMethodDecorator, MiddlewareFn } from 'type-graphql'; import Firebase from '@seed/services/auth/FirebaseService'; import { ApolloContext } from '@seed/interfaces/context'; import AccountModel from 'src/accounts/account.model'; import { AccountTypeEnum } from '@src/accounts/account.components'; import { validateOrReject } from 'class-validator'; import { plainToClass } from 'class-transformer'; import { newError } from '@seed/helpers/Error'; export interface EngineMiddlewareInput { authorization?: AccountTypeEnum[]; apiKey?: boolean; checkOrganisation?: boolean; noOrganisationCheck?: boolean; noPermissionCheck?: boolean; validations?: { inputName?: string; schema: ClassType; }[]; } export const checkApiKey = async ({ context }): Promise => { const user: AccountModel = new AccountModel(); let headers: any = {}; if (context.event) headers = context.event.headers; else if (context.req) headers = context.req.headers; /* * Deal with Auth */ const apiKey: string = headers['x-api-key']; if (apiKey) { // Verify that exists in DB try { const account = await user.getOne({ 'apiKeys.key': apiKey }, null); context.ctx.user = account; return true; } catch (error) { throw newError(2200, '403'); } } else throw newError(2202, { required: 'X-API-Key' }); }; export const checkAuth = ({ context }, user, roles: string[]): boolean => { let headers: any = {}; if (context.event) headers = context.event.headers; else if (context.req) headers = context.req.headers; if (roles.includes(AccountTypeEnum.public)) return true; const authorization: string = headers['x-authorization'] || headers.authorization || headers.Authorization; if (!authorization) { throw newError(2002); } if (!user || !user._id) { throw newError(2004, '403'); } if (roles.length > 0) { roles.push(AccountTypeEnum.admin); user.types.push(AccountTypeEnum.public); const rolesToVerif = _.uniq(roles); const userTypes = _.uniq(user.types); const intersection = _.intersection(rolesToVerif, userTypes); if (intersection.length > 0) return true; // no roles matched, restrict access throw newError(2000, { you: user.types, allowed: roles }); } return true; }; export const EngineMiddleware = (init: EngineMiddlewareInput): any => { return createMethodDecorator(async (data, next) => { // console.log('engine', data); const { context, args } = data; let returnError; if (init.authorization) { const user = (context as any).ctx.user; try { checkAuth({ context }, user, init.authorization); } catch (error) { returnError = error; } } if (init.apiKey) { try { await checkApiKey({ context }); returnError = null; } catch (error) { returnError = error; } } if (returnError) throw returnError; if (init.checkOrganisation && !(context as any).ctx.organisationId) throw newError(2102, '400'); if (init.noOrganisationCheck) (context as any).ctx.noOrganisationCheck = true; if (init.noPermissionCheck) (context as any).ctx.noPermissionCheck = true; if (init.validations) for (let index = 0; index < init.validations.length; index++) { const element = init.validations[index]; if (!element.inputName) element.inputName = 'input'; try { const instance = plainToClass(element.schema, args[element.inputName]); await validateOrReject(instance); } catch (error) { throw newError(3001, error); } } return next(); }); }; export const inputValidation: MiddlewareFn = async ({ root, args, context, info }, next) => { console.log({ root, args, context, info }); return next(); };