3010: Apollo 4 major update and complete security overhaul to prepare for public api support

* 2919: rewrite for update to apollo 4 (#131)

* rewrite for update to apollo 4

* removed container tests, edited make to publish any branch to staging

* revert makefile

* cleanup

* update 1

* more fixes to get it working on aws

* enabled csrf prevention protection, testing if build works

* switched to express version

* changed the healthcheck url

* added cache to restdatasources

* 3011: create a login mutation on graphql server that responds with a access token (#133)

* 3011 added login rest endpoint and general scope fixes

* changed login auth to basic auth username and password

* minor changes from CR feedback

* force update

* added tighter timeout for idle knex connection

* Refactor authentication and scopes (#134)

* big refactor of scopes in graphql

* cr fixes

* some security fixes (#135)

* some security fixes

* cleanup

* update staging

* minor readme change
This commit is contained in:
Arwid Thornström
2022-12-12 12:58:24 +01:00
committed by GitHub
parent 4248465031
commit c68c182b64
78 changed files with 3783 additions and 14606 deletions
+55 -92
View File
@@ -1,116 +1,79 @@
const newrelic = require('newrelic');
const newRelicPlugin = require('@newrelic/apollo-server-plugin');
import dotenv from 'dotenv';
dotenv.config();
import { ApolloServer, AuthenticationError } from 'apollo-server';
import knexStringcase from 'knex-stringcase';
import { dbConfig } from './config';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import resolvers from './resolvers';
import { OrderAPI } from './datasources/order-api';
import { ProductAPI } from './datasources/product-api';
import { DesignerAPI } from './datasources/designer-api';
import { CategoryAPI } from './datasources/category-api';
import { KeywordAPI } from './datasources/keyword-api';
import { MarketLocaleAPI } from './datasources/market-locale-api';
import { ImageServerApi } from './datasources/imageserver-api';
import { InteriorAPI } from './datasources/interior-api';
import { verifyToken } from './cognito/cognito-client';
import { readFileSync } from 'fs';
import { InteriorsLambdaAPI } from './datasources/interiors-lambda-api';
import { TextsAPI } from './datasources/texts-api';
import { PrintProductDefaultsAPI } from './datasources/printproduct-defaults-api';
const newRelicPlugin = require('@newrelic/apollo-server-plugin');
import { ContextValue } from './context';
import { GraphQLFormattedError } from 'graphql';
import express from 'express';
import http from 'http';
import cors from 'cors';
import { json } from 'body-parser';
import { getClientAccessToken } from './rest/cognitoService';
const path = require('path');
const typeDefs = readFileSync(
path.join(__dirname, './schema.graphql'),
).toString('utf-8');
// Converts column names to CamelCase
const knexConfig = knexStringcase(dbConfig);
// set up any dataSources our resolvers need
const marketLocaleApi = new MarketLocaleAPI(knexConfig);
const textsApi = new TextsAPI(knexConfig, marketLocaleApi);
const categoryApi = new CategoryAPI(knexConfig);
const designerApi = new DesignerAPI(knexConfig);
const imageServerApi = new ImageServerApi();
const interiorsLambdaApi = new InteriorsLambdaAPI();
const interiorApi = new InteriorAPI(knexConfig, imageServerApi);
const printProductDefaultsApi = new PrintProductDefaultsAPI(knexConfig);
const keywordApi = new KeywordAPI(knexConfig, textsApi);
const orderApi = new OrderAPI(knexConfig);
const productApi = new ProductAPI(
knexConfig,
textsApi,
printProductDefaultsApi,
);
const dataSources = () => ({
categoryApi,
designerApi,
interiorApi,
printProductDefaultsApi,
interiorsLambdaApi,
imageServerApi,
keywordApi,
marketLocaleApi,
orderApi,
productApi,
});
const context = async ({ req }) => {
if (process.env.NODE_ENV == 'test') {
return { auth: { userName: 'testuser' } };
}
const authHeader = req.headers.authorization || '';
if (authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7, authHeader.length);
const res = await verifyToken({ token });
if (res.isValid) {
return { auth: res };
}
} else if (
process.env.NODE_ENV === 'development' &&
authHeader == 'Basic ZGV2OnB2N1VmYSFiZVAzeGk='
) {
return {
auth: {
userName: 'testuser',
isValid: true,
accessToken: null,
idToken: null,
},
};
}
throw new AuthenticationError('Not authorized');
};
async function main() {
try {
const server = new ApolloServer({
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer<ContextValue>({
typeDefs,
resolvers,
dataSources,
context,
introspection: true,
plugins: [newRelicPlugin],
formatError: (err) => {
console.log('Error (fmt):', err);
newrelic.noticeError(err);
return err;
plugins: [
newRelicPlugin,
ApolloServerPluginDrainHttpServer({ httpServer }),
],
formatError: (formattedError: GraphQLFormattedError, error: unknown) => {
console.error(error);
newrelic.noticeError(error);
return formattedError;
},
});
await server.listen();
await server.start();
console.log(
'Server is running on http://localhost:4000, ',
process.env.ENVIRONMENT_NAME,
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
app.post('/login', async (req, res) => {
try {
const response = await getClientAccessToken(req);
res.status(200).send(response);
} catch (e) {
newrelic.noticeError(e.message);
res.status(401).send(e.message);
}
});
app.use(
'/',
cors<cors.CorsRequest>(),
json(),
expressMiddleware(server, {
context: async ({ req }) => {
const instance = new ContextValue({ req, server });
const context = await instance.process();
return context;
},
}),
);
await new Promise<void>((resolve) =>
httpServer.listen({ port: 4000 }, resolve),
);
console.log('SERVER IS RUNNING');
console.log(`ENV: ${process.env.ENVIRONMENT_NAME}`);
} catch (ex) {
newrelic.noticeError(ex);
console.error('Got terminal exception:', ex);