From 22fc1438f26808be07209e3633f86cdd4668c8f9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 14 Aug 2020 17:59:13 -0600 Subject: [PATCH] feat: transitioned to vanilla apollo-server-express --- package-lock.json | 336 ++++++++++-------- package.json | 4 +- src/core/server/app/handlers/api/graphql.ts | 80 ----- src/core/server/app/handlers/api/index.ts | 1 - src/core/server/app/middleware/csp/index.ts | 1 + .../app/middleware/graphql/apolloServer.ts | 110 ++++++ .../middleware/graphql/graphqlMiddleware.ts | 76 ---- .../server/app/middleware/graphql/index.ts | 4 +- ...edQueryMiddleware.ts => persistedQuery.ts} | 4 +- src/core/server/app/middleware/index.ts | 17 + src/core/server/app/middleware/installed.ts | 14 +- src/core/server/app/middleware/metrics.ts | 2 - .../server/app/middleware/passport/index.ts | 6 +- src/core/server/app/middleware/tenant.ts | 4 +- src/core/server/app/middleware/userLimiter.ts | 4 +- src/core/server/app/router/api/index.ts | 30 +- .../graph/extensions/MetricsExtension.ts | 43 --- src/core/server/graph/extensions/helpers.ts | 82 ----- src/core/server/graph/extensions/index.ts | 3 - src/core/server/graph/persisted/loader.ts | 2 +- src/core/server/graph/plugins/error.ts | 22 ++ .../helpers.ts} | 107 ++++-- src/core/server/graph/plugins/index.ts | 4 + .../LoggerExtension.ts => plugins/logger.ts} | 65 ++-- src/core/server/graph/plugins/metrics.ts | 40 +++ src/core/server/graph/subscriptions/server.ts | 6 +- 26 files changed, 530 insertions(+), 537 deletions(-) delete mode 100644 src/core/server/app/handlers/api/graphql.ts create mode 100644 src/core/server/app/middleware/csp/index.ts create mode 100644 src/core/server/app/middleware/graphql/apolloServer.ts delete mode 100644 src/core/server/app/middleware/graphql/graphqlMiddleware.ts rename src/core/server/app/middleware/graphql/{persistedQueryMiddleware.ts => persistedQuery.ts} (96%) create mode 100644 src/core/server/app/middleware/index.ts delete mode 100644 src/core/server/graph/extensions/MetricsExtension.ts delete mode 100644 src/core/server/graph/extensions/helpers.ts delete mode 100644 src/core/server/graph/extensions/index.ts create mode 100644 src/core/server/graph/plugins/error.ts rename src/core/server/graph/{extensions/ErrorWrappingExtension.ts => plugins/helpers.ts} (53%) create mode 100644 src/core/server/graph/plugins/index.ts rename src/core/server/graph/{extensions/LoggerExtension.ts => plugins/logger.ts} (72%) create mode 100644 src/core/server/graph/plugins/metrics.ts diff --git a/package-lock.json b/package-lock.json index 5c5d7cba8..f5ffea52a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,9 +40,12 @@ } }, "@apollographql/graphql-playground-html": { - "version": "1.6.24", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz", - "integrity": "sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==" + "version": "1.6.26", + "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz", + "integrity": "sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ==", + "requires": { + "xss": "^1.0.6" + } }, "@ardatan/aggregate-error": { "version": "0.0.1", @@ -7987,7 +7990,7 @@ }, "@types/accepts": { "version": "1.3.5", - "resolved": "http://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", "requires": { "@types/node": "*" @@ -10092,18 +10095,18 @@ } }, "apollo-cache-control": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.0.tgz", - "integrity": "sha512-dmRnQ9AXGw2SHahVGLzB/p4UW/taFBAJxifxubp8hqY5p9qdlSu4MPRq8zvV2ULMYf50rBtZyC4C+dZLqmHuHQ==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.1.tgz", + "integrity": "sha512-6iHa8TkcKt4rx5SKRzDNjUIpCQX+7/FlZwD7vRh9JDnM4VH8SWhpj8fUR3CiEY8Kuc4ChXnOY8bCcMju5KPnIQ==", "requires": { - "apollo-server-env": "^2.4.4", - "apollo-server-plugin-base": "^0.9.0" + "apollo-server-env": "^2.4.5", + "apollo-server-plugin-base": "^0.9.1" }, "dependencies": { "apollo-server-env": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.4.tgz", - "integrity": "sha512-c2oddDS3lwAl6QNCIKCLEzt/dF9M3/tjjYRVdxOVN20TidybI7rAbnT4QOzf4tORnGXtiznEAvr/Kc9ahhKADg==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz", + "integrity": "sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA==", "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" @@ -10112,18 +10115,26 @@ } }, "apollo-datasource": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.1.tgz", - "integrity": "sha512-h++/jQAY7GA+4TBM+7ezvctFmmGNLrAPf51KsagZj+NkT9qvxp585rdsuatynVbSl59toPK2EuVmc6ilmQHf+g==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.2.tgz", + "integrity": "sha512-ibnW+s4BMp4K2AgzLEtvzkjg7dJgCaw9M5b5N0YKNmeRZRnl/I/qBTQae648FsRKgMwTbRQIvBhQ0URUFAqFOw==", "requires": { - "apollo-server-caching": "^0.5.1", - "apollo-server-env": "^2.4.4" + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5" }, "dependencies": { + "apollo-server-caching": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz", + "integrity": "sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ==", + "requires": { + "lru-cache": "^5.0.0" + } + }, "apollo-server-env": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.4.tgz", - "integrity": "sha512-c2oddDS3lwAl6QNCIKCLEzt/dF9M3/tjjYRVdxOVN20TidybI7rAbnT4QOzf4tORnGXtiznEAvr/Kc9ahhKADg==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz", + "integrity": "sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA==", "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" @@ -10132,52 +10143,60 @@ } }, "apollo-engine-reporting": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.0.1.tgz", - "integrity": "sha512-3OYYk7DqNuJ5xKYnyLy5O2n506jYSryim8WqzBTn9MRphRamwPFjHYQm+akPA60AubXrWnYa6A8euMAiQU0ttA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.3.0.tgz", + "integrity": "sha512-SbcPLFuUZcRqDEZ6mSs8uHM9Ftr8yyt2IEu0JA8c3LNBmYXSLM7MHqFe80SVcosYSTBgtMz8mLJO8orhYoSYZw==", "requires": { - "apollo-engine-reporting-protobuf": "^0.5.1", - "apollo-graphql": "^0.4.0", - "apollo-server-caching": "^0.5.1", - "apollo-server-env": "^2.4.4", - "apollo-server-errors": "^2.4.1", - "apollo-server-plugin-base": "^0.9.0", - "apollo-server-types": "^0.5.0", + "apollo-engine-reporting-protobuf": "^0.5.2", + "apollo-graphql": "^0.5.0", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5", + "apollo-server-errors": "^2.4.2", + "apollo-server-plugin-base": "^0.9.1", + "apollo-server-types": "^0.5.1", "async-retry": "^1.2.1", "uuid": "^8.0.0" }, "dependencies": { "apollo-engine-reporting-protobuf": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.1.tgz", - "integrity": "sha512-TSfr9iAaInV8dhXkesdcmqsthRkVcJkzznmiM+1Ob/GScK7r6hBYCjVDt2613EHAg9SUzTOltIKlGD+N+GJRUw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.2.tgz", + "integrity": "sha512-4wm9FR3B7UvJxcK/69rOiS5CAJPEYKufeRWb257ZLfX7NGFTMqvbc1hu4q8Ch7swB26rTpkzfsftLED9DqH9qg==", "requires": { "@apollo/protobufjs": "^1.0.3" } }, + "apollo-server-caching": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz", + "integrity": "sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ==", + "requires": { + "lru-cache": "^5.0.0" + } + }, "apollo-server-env": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.4.tgz", - "integrity": "sha512-c2oddDS3lwAl6QNCIKCLEzt/dF9M3/tjjYRVdxOVN20TidybI7rAbnT4QOzf4tORnGXtiznEAvr/Kc9ahhKADg==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz", + "integrity": "sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA==", "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" } }, "apollo-server-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.0.tgz", - "integrity": "sha512-zhtsqqqfdeoJQAfc41Sy6WnnBVxKNgZ34BKXf/Q+kXmw7rbZ/B5SG3SJMvj1iFsbzZxILmWdUsE9aD20lEr0bg==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.1.tgz", + "integrity": "sha512-my2cPw+DAb2qVnIuBcsRKGyS28uIc2vjFxa1NpRoJZe9gK0BWUBk7wzXnIzWy3HZ5Er11e/40MPTUesNfMYNVA==", "requires": { - "apollo-engine-reporting-protobuf": "^0.5.1", - "apollo-server-caching": "^0.5.1", - "apollo-server-env": "^2.4.4" + "apollo-engine-reporting-protobuf": "^0.5.2", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5" } }, "uuid": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", - "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==" } } }, @@ -10201,9 +10220,9 @@ } }, "apollo-graphql": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.4.5.tgz", - "integrity": "sha512-0qa7UOoq7E71kBYE7idi6mNQhHLVdMEDInWk6TNw3KsSWZE2/I68gARP84Mj+paFTO5NYuw1Dht66PVX76Cc2w==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.5.0.tgz", + "integrity": "sha512-YSdF/BKPbsnQpxWpmCE53pBJX44aaoif31Y22I/qKpB6ZSGzYijV5YBoCL5Q15H2oA/v/02Oazh9lbp4ek3eig==", "requires": { "apollo-env": "^0.6.5", "lodash.sortby": "^4.7.0" @@ -10289,25 +10308,25 @@ } }, "apollo-server-core": { - "version": "2.14.4", - "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.14.4.tgz", - "integrity": "sha512-aAfsvbJ2YrqAXDBgcBQocOmQJ5DkeOnEYQ6ADdkkDNU68V5yBRkAHLTOzPfbUlGHVrnOH8PT1FIVWwu5mBgkVA==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.1.tgz", + "integrity": "sha512-nuwn5ZBbmzPwDetb3FgiFFJlNK7ZBFg8kis/raymrjd3eBGdNcOyMTJDl6J9673X9Xqp+dXQmFYDW/G3G8S1YA==", "requires": { "@apollographql/apollo-tools": "^0.4.3", "@apollographql/graphql-playground-html": "1.6.26", "@types/graphql-upload": "^8.0.0", "@types/ws": "^7.0.0", - "apollo-cache-control": "^0.11.0", - "apollo-datasource": "^0.7.1", - "apollo-engine-reporting": "^2.0.1", - "apollo-server-caching": "^0.5.1", - "apollo-server-env": "^2.4.4", - "apollo-server-errors": "^2.4.1", - "apollo-server-plugin-base": "^0.9.0", - "apollo-server-types": "^0.5.0", - "apollo-tracing": "^0.11.0", + "apollo-cache-control": "^0.11.1", + "apollo-datasource": "^0.7.2", + "apollo-engine-reporting": "^2.3.0", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5", + "apollo-server-errors": "^2.4.2", + "apollo-server-plugin-base": "^0.9.1", + "apollo-server-types": "^0.5.1", + "apollo-tracing": "^0.11.1", "fast-json-stable-stringify": "^2.0.0", - "graphql-extensions": "^0.12.3", + "graphql-extensions": "^0.12.4", "graphql-tag": "^2.9.2", "graphql-tools": "^4.0.0", "graphql-upload": "^8.0.2", @@ -10317,49 +10336,49 @@ "ws": "^6.0.0" }, "dependencies": { - "@apollographql/graphql-playground-html": { - "version": "1.6.26", - "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz", - "integrity": "sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ==", - "requires": { - "xss": "^1.0.6" - } - }, "apollo-engine-reporting-protobuf": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.1.tgz", - "integrity": "sha512-TSfr9iAaInV8dhXkesdcmqsthRkVcJkzznmiM+1Ob/GScK7r6hBYCjVDt2613EHAg9SUzTOltIKlGD+N+GJRUw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.2.tgz", + "integrity": "sha512-4wm9FR3B7UvJxcK/69rOiS5CAJPEYKufeRWb257ZLfX7NGFTMqvbc1hu4q8Ch7swB26rTpkzfsftLED9DqH9qg==", "requires": { "@apollo/protobufjs": "^1.0.3" } }, + "apollo-server-caching": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz", + "integrity": "sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ==", + "requires": { + "lru-cache": "^5.0.0" + } + }, "apollo-server-env": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.4.tgz", - "integrity": "sha512-c2oddDS3lwAl6QNCIKCLEzt/dF9M3/tjjYRVdxOVN20TidybI7rAbnT4QOzf4tORnGXtiznEAvr/Kc9ahhKADg==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz", + "integrity": "sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA==", "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" } }, "apollo-server-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.0.tgz", - "integrity": "sha512-zhtsqqqfdeoJQAfc41Sy6WnnBVxKNgZ34BKXf/Q+kXmw7rbZ/B5SG3SJMvj1iFsbzZxILmWdUsE9aD20lEr0bg==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.1.tgz", + "integrity": "sha512-my2cPw+DAb2qVnIuBcsRKGyS28uIc2vjFxa1NpRoJZe9gK0BWUBk7wzXnIzWy3HZ5Er11e/40MPTUesNfMYNVA==", "requires": { - "apollo-engine-reporting-protobuf": "^0.5.1", - "apollo-server-caching": "^0.5.1", - "apollo-server-env": "^2.4.4" + "apollo-engine-reporting-protobuf": "^0.5.2", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5" } }, "graphql-extensions": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.3.tgz", - "integrity": "sha512-W7iT0kzlwTiZU7fXfw9IgWnsqVj7EFLd0/wVcZZRAbR8L3f4+YsGls0oxKdsrvYBnbG347BXKQmIyo6GTEk4XA==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.4.tgz", + "integrity": "sha512-GnR4LiWk3s2bGOqIh6V1JgnSXw2RCH4NOgbCFEWvB6JqWHXTlXnLZ8bRSkCiD4pltv7RHUPWqN/sGh8R6Ae/ag==", "requires": { "@apollographql/apollo-tools": "^0.4.3", - "apollo-server-env": "^2.4.4", - "apollo-server-types": "^0.5.0" + "apollo-server-env": "^2.4.5", + "apollo-server-types": "^0.5.1" } }, "ws": { @@ -10382,23 +10401,23 @@ } }, "apollo-server-errors": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.1.tgz", - "integrity": "sha512-7oEd6pUxqyWYUbQ9TA8tM0NU/3aGtXSEibo6+txUkuHe7QaxfZ2wHRp+pfT1LC1K3RXYjKj61/C2xEO19s3Kdg==" + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.2.tgz", + "integrity": "sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ==" }, "apollo-server-express": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.14.2.tgz", - "integrity": "sha512-iYyZm0kQqkM561i9l0WC9HbJsGZJbHP9bhnWaa1Itd+yNBS2AJFp6mRR3hQacsWXUw7ewaKAracMIggvfSH5Aw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.16.1.tgz", + "integrity": "sha512-Oq5YNcaMYnRk6jDmA9LWf8oSd2KHDVe7jQ4wtooAvG9FVUD+FaFBgSkytXHMvtifQh2wdF07Ri8uDLMz6IQjTw==", "requires": { - "@apollographql/graphql-playground-html": "1.6.24", + "@apollographql/graphql-playground-html": "1.6.26", "@types/accepts": "^1.3.5", "@types/body-parser": "1.19.0", "@types/cors": "^2.8.4", - "@types/express": "4.17.4", + "@types/express": "4.17.7", "accepts": "^1.3.5", - "apollo-server-core": "^2.14.2", - "apollo-server-types": "^0.5.0", + "apollo-server-core": "^2.16.1", + "apollo-server-types": "^0.5.1", "body-parser": "^1.18.3", "cors": "^2.8.4", "express": "^4.17.1", @@ -10409,68 +10428,95 @@ "type-is": "^1.6.16" }, "dependencies": { + "@types/express": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", + "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, "apollo-engine-reporting-protobuf": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.1.tgz", - "integrity": "sha512-TSfr9iAaInV8dhXkesdcmqsthRkVcJkzznmiM+1Ob/GScK7r6hBYCjVDt2613EHAg9SUzTOltIKlGD+N+GJRUw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.2.tgz", + "integrity": "sha512-4wm9FR3B7UvJxcK/69rOiS5CAJPEYKufeRWb257ZLfX7NGFTMqvbc1hu4q8Ch7swB26rTpkzfsftLED9DqH9qg==", "requires": { "@apollo/protobufjs": "^1.0.3" } }, + "apollo-server-caching": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz", + "integrity": "sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ==", + "requires": { + "lru-cache": "^5.0.0" + } + }, "apollo-server-env": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.4.tgz", - "integrity": "sha512-c2oddDS3lwAl6QNCIKCLEzt/dF9M3/tjjYRVdxOVN20TidybI7rAbnT4QOzf4tORnGXtiznEAvr/Kc9ahhKADg==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz", + "integrity": "sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA==", "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" } }, "apollo-server-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.0.tgz", - "integrity": "sha512-zhtsqqqfdeoJQAfc41Sy6WnnBVxKNgZ34BKXf/Q+kXmw7rbZ/B5SG3SJMvj1iFsbzZxILmWdUsE9aD20lEr0bg==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.1.tgz", + "integrity": "sha512-my2cPw+DAb2qVnIuBcsRKGyS28uIc2vjFxa1NpRoJZe9gK0BWUBk7wzXnIzWy3HZ5Er11e/40MPTUesNfMYNVA==", "requires": { - "apollo-engine-reporting-protobuf": "^0.5.1", - "apollo-server-caching": "^0.5.1", - "apollo-server-env": "^2.4.4" + "apollo-engine-reporting-protobuf": "^0.5.2", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5" } } } }, "apollo-server-plugin-base": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.9.0.tgz", - "integrity": "sha512-LWcPrsy2+xqwlNseh/QaGa/MPNopS8c4qGgh0g0cAn0lZBRrJ9Yab7dq+iQ6vdUBwIhUWYN6s9dwUWCZw2SL8g==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.9.1.tgz", + "integrity": "sha512-kvrX4Z3FdpjrZdHkyl5iY2A1Wvp4b6KQp00DeZqss7GyyKNUBKr80/7RQgBLEw7EWM7WB19j459xM/TjvW0FKQ==", "requires": { - "apollo-server-types": "^0.5.0" + "apollo-server-types": "^0.5.1" }, "dependencies": { "apollo-engine-reporting-protobuf": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.1.tgz", - "integrity": "sha512-TSfr9iAaInV8dhXkesdcmqsthRkVcJkzznmiM+1Ob/GScK7r6hBYCjVDt2613EHAg9SUzTOltIKlGD+N+GJRUw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.2.tgz", + "integrity": "sha512-4wm9FR3B7UvJxcK/69rOiS5CAJPEYKufeRWb257ZLfX7NGFTMqvbc1hu4q8Ch7swB26rTpkzfsftLED9DqH9qg==", "requires": { "@apollo/protobufjs": "^1.0.3" } }, + "apollo-server-caching": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz", + "integrity": "sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ==", + "requires": { + "lru-cache": "^5.0.0" + } + }, "apollo-server-env": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.4.tgz", - "integrity": "sha512-c2oddDS3lwAl6QNCIKCLEzt/dF9M3/tjjYRVdxOVN20TidybI7rAbnT4QOzf4tORnGXtiznEAvr/Kc9ahhKADg==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz", + "integrity": "sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA==", "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" } }, "apollo-server-types": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.0.tgz", - "integrity": "sha512-zhtsqqqfdeoJQAfc41Sy6WnnBVxKNgZ34BKXf/Q+kXmw7rbZ/B5SG3SJMvj1iFsbzZxILmWdUsE9aD20lEr0bg==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.1.tgz", + "integrity": "sha512-my2cPw+DAb2qVnIuBcsRKGyS28uIc2vjFxa1NpRoJZe9gK0BWUBk7wzXnIzWy3HZ5Er11e/40MPTUesNfMYNVA==", "requires": { - "apollo-engine-reporting-protobuf": "^0.5.1", - "apollo-server-caching": "^0.5.1", - "apollo-server-env": "^2.4.4" + "apollo-engine-reporting-protobuf": "^0.5.2", + "apollo-server-caching": "^0.5.2", + "apollo-server-env": "^2.4.5" } } } @@ -10486,18 +10532,18 @@ } }, "apollo-tracing": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.11.0.tgz", - "integrity": "sha512-I9IFb/8lkBW8ZwOAi4LEojfT7dMfUSkpnV8LHQI8Rcj0HtzL9HObQ3woBmzyGHdGHLFuD/6/VHyFD67SesSrJg==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.11.1.tgz", + "integrity": "sha512-l7g+uILw7v32GA46IRXIx5XXbZhFI96BhSqrGK9yyvfq+NMcvVZrj3kIhRImPGhAjMdV+5biA/jztabElAbDjg==", "requires": { - "apollo-server-env": "^2.4.4", - "apollo-server-plugin-base": "^0.9.0" + "apollo-server-env": "^2.4.5", + "apollo-server-plugin-base": "^0.9.1" }, "dependencies": { "apollo-server-env": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.4.tgz", - "integrity": "sha512-c2oddDS3lwAl6QNCIKCLEzt/dF9M3/tjjYRVdxOVN20TidybI7rAbnT4QOzf4tORnGXtiznEAvr/Kc9ahhKADg==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz", + "integrity": "sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA==", "requires": { "node-fetch": "^2.1.2", "util.promisify": "^1.0.0" @@ -29790,9 +29836,9 @@ } }, "graphql-tag": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.3.tgz", - "integrity": "sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA==" + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz", + "integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==" }, "graphql-tools": { "version": "4.0.7", @@ -29831,13 +29877,13 @@ }, "dependencies": { "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", "requires": { "depd": "~1.1.2", "inherits": "2.0.4", - "setprototypeof": "1.1.1", + "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } @@ -29848,9 +29894,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "statuses": { "version": "1.5.0", @@ -37904,9 +37950,9 @@ } }, "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==" + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==" }, "long": { "version": "4.0.0", diff --git a/package.json b/package.json index e2ce9c64f..e1e47045d 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,8 @@ "@sentry/node": "^5.21.1", "abort-controller": "^3.0.0", "akismet-api": "^5.0.0", - "apollo-server-core": "^2.14.4", - "apollo-server-express": "^2.14.2", + "apollo-server-express": "^2.16.1", + "apollo-server-plugin-base": "^0.9.1", "archiver": "^3.1.1", "basic-auth": "^2.0.1", "bcryptjs": "^2.4.3", diff --git a/src/core/server/app/handlers/api/graphql.ts b/src/core/server/app/handlers/api/graphql.ts deleted file mode 100644 index 4294b0eb0..000000000 --- a/src/core/server/app/handlers/api/graphql.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { generateSchemaHash } from "apollo-server-core/dist/utils/schemaHash"; - -import { CLIENT_ID_HEADER } from "coral-common/constants"; -import { AppOptions } from "coral-server/app"; -import { graphqlMiddleware } from "coral-server/app/middleware/graphql"; -import GraphContext, { GraphContextOptions } from "coral-server/graph/context"; -import { - Request, - RequestHandler, - TenantCoralRequest, -} from "coral-server/types/express"; - -export type GraphMiddlewareOptions = Pick< - AppOptions, - | "config" - | "i18n" - | "mailerQueue" - | "scraperQueue" - | "rejectorQueue" - | "notifierQueue" - | "webhookQueue" - | "mongo" - | "redis" - | "schema" - | "signingConfig" - | "pubsub" - | "tenantCache" - | "metrics" - | "broker" - | "reporter" ->; - -export const graphQLHandler = ({ - schema, - config, - metrics, - ...options -}: GraphMiddlewareOptions): RequestHandler => { - // Generate the schema hash. - const schemaHash = generateSchemaHash(schema); - - return graphqlMiddleware( - config, - async (req: Request) => { - // Pull out some useful properties from Coral. - const { id, now, tenant, logger, persisted } = req.coral; - - // Create some new options to store the tenant context details inside. - const opts: GraphContextOptions = { - ...options, - id, - now, - req, - persisted, - config, - tenant, - logger, - }; - - // Add the user if there is one. - if (req.user) { - opts.user = req.user; - } - - // Add the clientID if there is one on the request. - const clientID = req.get(CLIENT_ID_HEADER); - if (clientID) { - // TODO: (wyattjoh) validate length - opts.clientID = clientID; - } - - return { - schema, - schemaHash, - context: new GraphContext(opts), - }; - }, - metrics - ); -}; diff --git a/src/core/server/app/handlers/api/index.ts b/src/core/server/app/handlers/api/index.ts index 13b7ceb03..6b185e600 100644 --- a/src/core/server/app/handlers/api/index.ts +++ b/src/core/server/app/handlers/api/index.ts @@ -1,6 +1,5 @@ export * from "./account"; export * from "./auth"; -export * from "./graphql"; export * from "./health"; export * from "./install"; export * from "./version"; diff --git a/src/core/server/app/middleware/csp/index.ts b/src/core/server/app/middleware/csp/index.ts new file mode 100644 index 000000000..593e67b0c --- /dev/null +++ b/src/core/server/app/middleware/csp/index.ts @@ -0,0 +1 @@ +export * from "./tenant"; diff --git a/src/core/server/app/middleware/graphql/apolloServer.ts b/src/core/server/app/middleware/graphql/apolloServer.ts new file mode 100644 index 000000000..b35b044fd --- /dev/null +++ b/src/core/server/app/middleware/graphql/apolloServer.ts @@ -0,0 +1,110 @@ +import { ApolloServer } from "apollo-server-express"; + +import { CLIENT_ID_HEADER } from "coral-common/constants"; +import { AppOptions } from "coral-server/app"; +import GraphContext, { GraphContextOptions } from "coral-server/graph/context"; +import { + ErrorApolloServerPlugin, + LoggerApolloServerPlugin, + MetricsApolloServerPlugin, +} from "coral-server/graph/plugins"; +import { Request, TenantCoralRequest } from "coral-server/types/express"; + +type ContextProviderOptions = Omit; + +function contextProvider(options: ContextProviderOptions) { + return ({ req }: { req: Request }) => { + // Grab the details from the Coral request. + const { id, now, tenant, logger, persisted } = req.coral; + + // Create some new options to store the tenant context details inside. + const opts: GraphContextOptions = { + ...options, + id, + now, + req, + persisted, + tenant, + logger, + }; + + // Add the user if there is one. + if (req.user) { + opts.user = req.user; + } + + // Add the clientID if there is one on the request. + const clientID = req.get(CLIENT_ID_HEADER); + if (clientID) { + // TODO: (wyattjoh) validate length + opts.clientID = clientID; + } + + // Return the compiled context. + return new GraphContext(opts); + }; +} + +export const apolloGraphQLMiddleware = ({ + schema, + metrics, + ...options +}: AppOptions) => { + // Create the ApolloServer that we'll use to get the middleware from. + const server = new ApolloServer({ + // Provide the executable schema that we assembled earlier. + schema, + + // Create the context provider that'll create a new context for each + // request. + context: contextProvider(options), + + // Introspection is enabled when we aren't in a production environment or if + // the GraphiQL is enabled. + introspection: + options.config.get("env") !== "production" || + options.config.get("enable_graphiql"), + + // Disable uploads, Coral doesn't handle any file uploads. + uploads: false, + + // Disable the embedded playground, Coral provides it's own GraphiQL + // interface. + playground: false, + + // Disable engine, Coral doesn't use it. + engine: false, + + // Disable cache control, Coral doesn't use it yet. + cacheControl: false, + + // Disable subscriptions as we'll be providing it seperatly. + subscriptions: false, + + // Configure plugins to be ran on requests. + plugins: [ + ErrorApolloServerPlugin, + LoggerApolloServerPlugin, + MetricsApolloServerPlugin(metrics), + ], + + // Disable the debug mode, as we already add in our logging function. + debug: false, + }); + + // Get the GraphQL middleware. + return server.getMiddleware({ + // Disable the health check endpoint, Coral does not use this endpoint and + // instead uses the /api/health endpoint. + disableHealthCheck: true, + + // Disable CORS, Coral does not allow cross origin requests. + cors: false, + + // Disable the body parser, we will add our own. + bodyParserConfig: false, + + // Configure the path. + path: "/graphql", + }); +}; diff --git a/src/core/server/app/middleware/graphql/graphqlMiddleware.ts b/src/core/server/app/middleware/graphql/graphqlMiddleware.ts deleted file mode 100644 index 070a71fff..000000000 --- a/src/core/server/app/middleware/graphql/graphqlMiddleware.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { GraphQLExtension, GraphQLOptions } from "apollo-server-express"; -// TODO: when https://github.com/apollographql/apollo-server/pull/1907 is merged, update this import path -import { - ExpressGraphQLOptionsFunction, - graphqlExpress, -} from "apollo-server-express/dist/expressApollo"; -import { Handler } from "express"; -import { FieldDefinitionNode, GraphQLError, ValidationContext } from "graphql"; - -import { Config } from "coral-server/config"; -import { - ErrorWrappingExtension, - LoggerExtension, - MetricsExtension, -} from "coral-server/graph/extensions"; -import { Metrics } from "coral-server/services/metrics"; - -// Sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L46-L57 -const NoIntrospection = (context: ValidationContext) => ({ - Field(node: FieldDefinitionNode) { - if (node.name.value === "__schema" || node.name.value === "__type") { - context.reportError( - new GraphQLError( - "GraphQL introspection is not allowed in production, but the query contained __schema or __type.", - [node] - ) - ); - } - }, -}); - -/** - * graphqlMiddleware wraps the GraphQL middleware server with some custom - * extension management. - * - * @param config application configuration - * @param requestOptions options to pass to the graphql server - */ -const graphqlMiddleware = ( - config: Config, - requestOptions: ExpressGraphQLOptionsFunction, - metrics: Metrics -): Handler => { - const extensions: Array<() => GraphQLExtension> = [ - () => new ErrorWrappingExtension(), - () => new LoggerExtension(), - // Pass the metrics to the extension so it can increment. - () => new MetricsExtension(metrics), - ]; - - // Create a new baseOptions that will be merged into the new options. - const baseOptions: Omit = { - // Disable the debug mode, as we already add in our logging function. - debug: false, - extensions, - }; - - if (config.get("env") === "production" && !config.get("enable_graphiql")) { - // Disable introspection in production. - baseOptions.validationRules = [NoIntrospection]; - } - - // Generate the actual middleware. - return graphqlExpress(async (req, res) => { - // Resolve the options for the GraphQL middleware. - const options = await requestOptions(req, res); - - // Provide the options. - return { - ...options, - ...baseOptions, - }; - }); -}; - -export default graphqlMiddleware; diff --git a/src/core/server/app/middleware/graphql/index.ts b/src/core/server/app/middleware/graphql/index.ts index 5ff781903..6756ca6c0 100644 --- a/src/core/server/app/middleware/graphql/index.ts +++ b/src/core/server/app/middleware/graphql/index.ts @@ -1,2 +1,2 @@ -export { default as graphqlMiddleware } from "./graphqlMiddleware"; -export { default as persistedQueryMiddleware } from "./persistedQueryMiddleware"; +export * from "./apolloServer"; +export * from "./persistedQuery"; diff --git a/src/core/server/app/middleware/graphql/persistedQueryMiddleware.ts b/src/core/server/app/middleware/graphql/persistedQuery.ts similarity index 96% rename from src/core/server/app/middleware/graphql/persistedQueryMiddleware.ts rename to src/core/server/app/middleware/graphql/persistedQuery.ts index 1cef8e03f..f4e5afe50 100644 --- a/src/core/server/app/middleware/graphql/persistedQueryMiddleware.ts +++ b/src/core/server/app/middleware/graphql/persistedQuery.ts @@ -14,7 +14,7 @@ type PersistedQueryMiddlewareOptions = Pick< "persistedQueryCache" | "persistedQueriesRequired" >; -const persistedQueryMiddleware = ({ +export const persistedQueryMiddleware = ({ persistedQueriesRequired, persistedQueryCache, }: PersistedQueryMiddlewareOptions): RequestHandler< @@ -64,5 +64,3 @@ const persistedQueryMiddleware = ({ return next(err); } }; - -export default persistedQueryMiddleware; diff --git a/src/core/server/app/middleware/index.ts b/src/core/server/app/middleware/index.ts new file mode 100644 index 000000000..2edaac111 --- /dev/null +++ b/src/core/server/app/middleware/index.ts @@ -0,0 +1,17 @@ +export * from "./csp"; +export * from "./graphql"; +export * from "./passport"; +export * from "./basicAuth"; +export * from "./cacheHeaders"; +export * from "./error"; +export * from "./installed"; +export * from "./json"; +export * from "./loggedIn"; +export * from "./logging"; +export * from "./metrics"; +export * from "./notFound"; +export * from "./playground"; +export * from "./role"; +export * from "./serveStatic"; +export * from "./tenant"; +export * from "./userLimiter"; diff --git a/src/core/server/app/middleware/installed.ts b/src/core/server/app/middleware/installed.ts index 8cbe86cf6..93cb58937 100644 --- a/src/core/server/app/middleware/installed.ts +++ b/src/core/server/app/middleware/installed.ts @@ -1,24 +1,20 @@ import { isInstalled } from "coral-server/services/tenant"; import { RequestHandler } from "coral-server/types/express"; -export interface InstalledMiddlewareOptions { +interface Options { redirectURL?: string; redirectIfInstalled?: boolean; } -const DefaultInstalledMiddlewareOptions: Required = { +const defaultOptions: Required = { redirectIfInstalled: false, redirectURL: "/install", }; export const installedMiddleware = ({ - redirectIfInstalled = DefaultInstalledMiddlewareOptions.redirectIfInstalled, - redirectURL = DefaultInstalledMiddlewareOptions.redirectURL, -}: InstalledMiddlewareOptions = DefaultInstalledMiddlewareOptions): RequestHandler => async ( - req, - res, - next -) => { + redirectIfInstalled = defaultOptions.redirectIfInstalled, + redirectURL = defaultOptions.redirectURL, +}: Options = defaultOptions): RequestHandler => async (req, res, next) => { const installed = await isInstalled(req.coral.cache.tenant, req.hostname); // If Coral is installed, and redirectIfInstall is true, then it will diff --git a/src/core/server/app/middleware/metrics.ts b/src/core/server/app/middleware/metrics.ts index 621dca056..55dcaf269 100644 --- a/src/core/server/app/middleware/metrics.ts +++ b/src/core/server/app/middleware/metrics.ts @@ -4,8 +4,6 @@ import { createTimer } from "coral-server/helpers"; import { Metrics } from "coral-server/services/metrics"; import { RequestHandler } from "coral-server/types/express"; -export type MetricsRecorderOptions = Metrics; - export const metricsRecorder = ({ httpRequestsTotal, httpRequestDurationMilliseconds, diff --git a/src/core/server/app/middleware/passport/index.ts b/src/core/server/app/middleware/passport/index.ts index e8f5ca744..e0d520e8f 100644 --- a/src/core/server/app/middleware/passport/index.ts +++ b/src/core/server/app/middleware/passport/index.ts @@ -32,14 +32,12 @@ export type VerifyCallback = ( info?: { message: string } ) => void; -export type PassportOptions = Pick< +type Options = Pick< AppOptions, "mongo" | "redis" | "config" | "tenantCache" | "signingConfig" >; -export function createPassport( - options: PassportOptions -): passport.Authenticator { +export function createPassport(options: Options): passport.Authenticator { // Create the authenticator. const auth = new Authenticator(); diff --git a/src/core/server/app/middleware/tenant.ts b/src/core/server/app/middleware/tenant.ts index 5a17d9c89..55d37c685 100644 --- a/src/core/server/app/middleware/tenant.ts +++ b/src/core/server/app/middleware/tenant.ts @@ -5,7 +5,7 @@ import logger from "coral-server/logger"; import { TenantCache } from "coral-server/services/tenant/cache"; import { RequestHandler } from "coral-server/types/express"; -export interface MiddlewareOptions { +interface Options { cache: TenantCache; passNoTenant?: boolean; } @@ -13,7 +13,7 @@ export interface MiddlewareOptions { export const tenantMiddleware = ({ cache, passNoTenant = false, -}: MiddlewareOptions): RequestHandler => async (req, res, next) => { +}: Options): RequestHandler => async (req, res, next) => { try { if (!req.coral) { const id = uuid(); diff --git a/src/core/server/app/middleware/userLimiter.ts b/src/core/server/app/middleware/userLimiter.ts index f5c965a54..4b15f4b27 100644 --- a/src/core/server/app/middleware/userLimiter.ts +++ b/src/core/server/app/middleware/userLimiter.ts @@ -4,7 +4,7 @@ import { RequestLimiter } from "coral-server/app/request/limiter"; import { Config } from "coral-server/config"; import { RequestHandler } from "coral-server/types/express"; -export interface MiddlewareOptions { +interface Options { redis: Redis; config: Config; } @@ -12,7 +12,7 @@ export interface MiddlewareOptions { export const userLimiterMiddleware = ({ redis, config, -}: MiddlewareOptions): RequestHandler => { +}: Options): RequestHandler => { const limiter = new RequestLimiter({ redis, ttl: "1m", diff --git a/src/core/server/app/router/api/index.ts b/src/core/server/app/router/api/index.ts index 6acab91e7..813b46bdc 100644 --- a/src/core/server/app/router/api/index.ts +++ b/src/core/server/app/router/api/index.ts @@ -2,17 +2,19 @@ import express from "express"; import passport from "passport"; import { AppOptions } from "coral-server/app"; -import { graphQLHandler } from "coral-server/app/handlers"; import { oembedHandler } from "coral-server/app/handlers/api/oembed/oembed"; -import { cspSiteMiddleware } from "coral-server/app/middleware/csp/tenant"; -import { JSONErrorHandler } from "coral-server/app/middleware/error"; -import { persistedQueryMiddleware } from "coral-server/app/middleware/graphql"; -import { jsonMiddleware } from "coral-server/app/middleware/json"; -import { loggedInMiddleware } from "coral-server/app/middleware/loggedIn"; -import { notFoundMiddleware } from "coral-server/app/middleware/notFound"; -import { authenticate } from "coral-server/app/middleware/passport"; -import { roleMiddleware } from "coral-server/app/middleware/role"; -import { tenantMiddleware } from "coral-server/app/middleware/tenant"; +import { + apolloGraphQLMiddleware, + authenticate, + cspSiteMiddleware, + JSONErrorHandler, + jsonMiddleware, + loggedInMiddleware, + notFoundMiddleware, + persistedQueryMiddleware, + roleMiddleware, + tenantMiddleware, +} from "coral-server/app/middleware"; import { STAFF_ROLES } from "coral-server/models/user/constants"; import { createNewAccountRouter } from "./account"; @@ -55,15 +57,17 @@ export function createAPIRouter(app: AppOptions, options: RouterOptions) { router.get("/oembed", cspSiteMiddleware(app), oembedHandler(app)); - // Configure the GraphQL route. + // Configure the GraphQL route middleware. router.use( "/graphql", authenticate(options.passport), jsonMiddleware(app.config.get("max_request_size")), - persistedQueryMiddleware(app), - graphQLHandler(app) + persistedQueryMiddleware(app) ); + // Attach the GraphQL router (which will be mounted on the same path). + router.use(apolloGraphQLMiddleware(app)); + router.use( "/dashboard", authenticate(options.passport), diff --git a/src/core/server/graph/extensions/MetricsExtension.ts b/src/core/server/graph/extensions/MetricsExtension.ts deleted file mode 100644 index 96d66f82f..000000000 --- a/src/core/server/graph/extensions/MetricsExtension.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ExecutionArgs } from "graphql"; -import { EndHandler, GraphQLExtension } from "graphql-extensions"; - -import GraphContext from "coral-server/graph/context"; -import { createTimer } from "coral-server/helpers"; -import { Metrics } from "coral-server/services/metrics"; - -import { getOperationMetadata } from "./helpers"; - -export class MetricsExtension implements GraphQLExtension { - constructor(private metrics: Metrics) {} - - public executionDidStart(o: { - executionArgs: ExecutionArgs; - }): EndHandler | void { - // Only try to log things if the context is provided. - if (o.executionArgs.contextValue) { - // Grab the start time so we can calculate the time it takes to execute - // the graph query. - const timer = createTimer(); - return () => { - // Compute the end time. - const responseTime = timer(); - - // Get the request metadata. - const { operation, operationName } = getOperationMetadata( - o.executionArgs.document - ); - - if (operation && operationName) { - // Increment the graph query value, tagging with the name of the query. - this.metrics.executedGraphQueriesTotalCounter - .labels(operation, operationName) - .inc(); - - this.metrics.graphQLExecutionTimingsHistogram - .labels(operation, operationName) - .observe(responseTime); - } - }; - } - } -} diff --git a/src/core/server/graph/extensions/helpers.ts b/src/core/server/graph/extensions/helpers.ts deleted file mode 100644 index a4bbab082..000000000 --- a/src/core/server/graph/extensions/helpers.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - DocumentNode, - GraphQLFormattedError, - OperationDefinitionNode, - OperationTypeNode, -} from "graphql"; - -import { PersistedQuery } from "coral-server/models/queries"; - -export interface OperationMetadata { - operationName: string; - operation: OperationTypeNode; -} - -/** - * getOperationMetadata will extract the operation metadata from the document - * node. - * - * @param doc the document node that can be used to extract operation metadata - * from - */ -export const getOperationMetadata = ( - doc: DocumentNode -): Partial => { - if (doc.kind === "Document") { - const operationDefinition = doc.definitions.find( - ({ kind }) => kind === "OperationDefinition" - ) as OperationDefinitionNode | undefined; - if (operationDefinition) { - let operationName: string | undefined; - if (operationDefinition.name) { - operationName = operationDefinition.name.value; - } - - return { - operationName, - operation: operationDefinition.operation, - }; - } - } - - return {}; -}; - -interface PersistedQueryOperationMetadata extends OperationMetadata { - persistedQueryID: string; - persistedQueryBundle: string; - persistedQueryVersion: string; -} - -/** - * getPersistedQueryMetadata will remap the persisted query to the operation - * metadata. - * - * @param persisted persisted query to remap to operation metadata - */ -export const getPersistedQueryMetadata = ({ - id: persistedQueryID, - operation, - operationName, - bundle: persistedQueryBundle, - version: persistedQueryVersion, -}: PersistedQuery): PersistedQueryOperationMetadata => ({ - persistedQueryID, - persistedQueryBundle, - persistedQueryVersion, - operation, - operationName, -}); - -/** - * getOriginalError tries to return the original error from a - * formatted GraphQL error. - * - * @param err A GraphQL Formatted Error - */ -export const getOriginalError = (err: GraphQLFormattedError) => { - if ((err as any).originalError) { - return (err as any).originalError as Error; - } - return null; -}; diff --git a/src/core/server/graph/extensions/index.ts b/src/core/server/graph/extensions/index.ts deleted file mode 100644 index 465746062..000000000 --- a/src/core/server/graph/extensions/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./ErrorWrappingExtension"; -export * from "./LoggerExtension"; -export * from "./MetricsExtension"; diff --git a/src/core/server/graph/persisted/loader.ts b/src/core/server/graph/persisted/loader.ts index a7e5f8178..3ad7a43a3 100644 --- a/src/core/server/graph/persisted/loader.ts +++ b/src/core/server/graph/persisted/loader.ts @@ -3,7 +3,7 @@ import { parse } from "graphql"; import path from "path"; import { version } from "coral-common/version"; -import { getOperationMetadata } from "coral-server/graph/extensions/helpers"; +import { getOperationMetadata } from "coral-server/graph/plugins"; import logger from "coral-server/logger"; import { PersistedQuery } from "coral-server/models/queries"; diff --git a/src/core/server/graph/plugins/error.ts b/src/core/server/graph/plugins/error.ts new file mode 100644 index 000000000..76c95a426 --- /dev/null +++ b/src/core/server/graph/plugins/error.ts @@ -0,0 +1,22 @@ +import { ApolloServerPlugin } from "apollo-server-plugin-base"; + +import GraphContext from "../context"; +import { enrichError } from "./helpers"; + +export const ErrorApolloServerPlugin: ApolloServerPlugin = { + requestDidStart() { + return { + willSendResponse({ response, context }) { + // If there's any errors on the response, we need to enrich their + // extensions with translated messages. + if (response.errors) { + response.errors = response.errors.map((err) => + enrichError(context, err) + ); + } + }, + }; + }, +}; + +export default ErrorApolloServerPlugin; diff --git a/src/core/server/graph/extensions/ErrorWrappingExtension.ts b/src/core/server/graph/plugins/helpers.ts similarity index 53% rename from src/core/server/graph/extensions/ErrorWrappingExtension.ts rename to src/core/server/graph/plugins/helpers.ts index 04993d105..bc2295796 100644 --- a/src/core/server/graph/extensions/ErrorWrappingExtension.ts +++ b/src/core/server/graph/plugins/helpers.ts @@ -1,6 +1,10 @@ -import { ApolloError } from "apollo-server-core"; -import { GraphQLFormattedError } from "graphql"; -import { GraphQLExtension, GraphQLResponse } from "graphql-extensions"; +import { ApolloError } from "apollo-server-express"; +import { + DocumentNode, + GraphQLFormattedError, + OperationDefinitionNode, + OperationTypeNode, +} from "graphql"; import { merge } from "lodash"; import { @@ -8,9 +12,83 @@ import { InternalDevelopmentError, WrappedInternalError, } from "coral-server/errors"; -import GraphContext from "coral-server/graph/context"; +import { PersistedQuery } from "coral-server/models/queries"; -import { getOriginalError } from "./helpers"; +import GraphContext from "../context"; + +export interface OperationMetadata { + operationName: string; + operation: OperationTypeNode; +} + +/** + * getOperationMetadata will extract the operation metadata from the document + * node. + * + * @param doc the document node that can be used to extract operation metadata + * from + */ +export const getOperationMetadata = ( + doc: DocumentNode +): Partial => { + if (doc.kind === "Document") { + const operationDefinition = doc.definitions.find( + ({ kind }) => kind === "OperationDefinition" + ) as OperationDefinitionNode | undefined; + if (operationDefinition) { + let operationName: string | undefined; + if (operationDefinition.name) { + operationName = operationDefinition.name.value; + } + + return { + operationName, + operation: operationDefinition.operation, + }; + } + } + + return {}; +}; + +interface PersistedQueryOperationMetadata extends OperationMetadata { + persistedQueryID: string; + persistedQueryBundle: string; + persistedQueryVersion: string; +} + +/** + * getPersistedQueryMetadata will remap the persisted query to the operation + * metadata. + * + * @param persisted persisted query to remap to operation metadata + */ +export const getPersistedQueryMetadata = ({ + id: persistedQueryID, + operation, + operationName, + bundle: persistedQueryBundle, + version: persistedQueryVersion, +}: PersistedQuery): PersistedQueryOperationMetadata => ({ + persistedQueryID, + persistedQueryBundle, + persistedQueryVersion, + operation, + operationName, +}); + +/** + * getOriginalError tries to return the original error from a + * formatted GraphQL error. + * + * @param err A GraphQL Formatted Error + */ +export const getOriginalError = (err: GraphQLFormattedError) => { + if ((err as any).originalError) { + return (err as any).originalError as Error; + } + return null; +}; function hoistCoralErrorExtensions( ctx: GraphContext, @@ -100,22 +178,3 @@ export function enrichError( return err; } - -export class ErrorWrappingExtension implements GraphQLExtension { - public willSendResponse(o: { - graphqlResponse: GraphQLResponse; - context: GraphContext; - }): void | { graphqlResponse: GraphQLResponse; context: GraphContext } { - if (o.graphqlResponse.errors) { - return { - ...o, - graphqlResponse: { - ...o.graphqlResponse, - errors: o.graphqlResponse.errors.map((err) => - enrichError(o.context, err) - ), - }, - }; - } - } -} diff --git a/src/core/server/graph/plugins/index.ts b/src/core/server/graph/plugins/index.ts new file mode 100644 index 000000000..ee0e08151 --- /dev/null +++ b/src/core/server/graph/plugins/index.ts @@ -0,0 +1,4 @@ +export * from "./error"; +export * from "./logger"; +export * from "./metrics"; +export * from "./helpers"; diff --git a/src/core/server/graph/extensions/LoggerExtension.ts b/src/core/server/graph/plugins/logger.ts similarity index 72% rename from src/core/server/graph/extensions/LoggerExtension.ts rename to src/core/server/graph/plugins/logger.ts index ae57fd8f2..d9444014f 100644 --- a/src/core/server/graph/extensions/LoggerExtension.ts +++ b/src/core/server/graph/plugins/logger.ts @@ -1,9 +1,5 @@ -import { DocumentNode, ExecutionArgs, GraphQLFormattedError } from "graphql"; -import { - EndHandler, - GraphQLExtension, - GraphQLResponse, -} from "graphql-extensions"; +import { ApolloServerPlugin } from "apollo-server-plugin-base"; +import { DocumentNode, GraphQLFormattedError } from "graphql"; import GraphContext from "coral-server/graph/context"; import { createTimer } from "coral-server/helpers"; @@ -16,10 +12,9 @@ export function logAndReportError( ctx: GraphContext, err: GraphQLFormattedError ) { - ctx.logger.error({ err }, "graphql query error"); - - // If there's no reporter active, then return now. + // If there's no reporter active, then just log what we got and return now. if (!ctx.reporter) { + ctx.logger.error({ err }, "graphql query error"); return; } @@ -106,35 +101,25 @@ export function logQuery( } } -export class LoggerExtension implements GraphQLExtension { - public executionDidStart(o: { - executionArgs: ExecutionArgs; - }): EndHandler | void { - // Only try to log things if the context is provided. - if (o.executionArgs.contextValue) { - // Grab the start time so we can calculate the time it takes to execute - // the graph query. - const timer = createTimer(); - return () => { - // Log out the details of the request. - logQuery( - o.executionArgs.contextValue, - o.executionArgs.document, - undefined, - timer() - ); - }; - } - } +export const LoggerApolloServerPlugin: ApolloServerPlugin = { + requestDidStart() { + return { + willSendResponse({ response, context }) { + if (response.errors) { + // Log out the errors on this request. + response.errors.forEach((err) => logAndReportError(context, err)); + } + }, + executionDidStart({ document, context }) { + // Grab the start time so we can calculate the time it takes to execute + // the graph query. + const timer = createTimer(); - public willSendResponse(response: { - graphqlResponse: GraphQLResponse; - context: GraphContext; - }): void { - if (response.graphqlResponse.errors) { - response.graphqlResponse.errors.forEach((err) => - logAndReportError(response.context, err) - ); - } - } -} + return function executionDidEnd() { + // Log out the details of this request. + logQuery(context, document, context.persisted, timer()); + }; + }, + }; + }, +}; diff --git a/src/core/server/graph/plugins/metrics.ts b/src/core/server/graph/plugins/metrics.ts new file mode 100644 index 000000000..897e04bee --- /dev/null +++ b/src/core/server/graph/plugins/metrics.ts @@ -0,0 +1,40 @@ +import { ApolloServerPlugin } from "apollo-server-plugin-base"; + +import { createTimer } from "coral-server/helpers"; +import { Metrics } from "coral-server/services/metrics"; + +import { getOperationMetadata } from "./helpers"; + +export const MetricsApolloServerPlugin = ( + metrics: Metrics +): ApolloServerPlugin => ({ + requestDidStart() { + return { + executionDidStart({ document }) { + // Grab the start time so we can calculate the time it takes to execute + // the graph query. + const timer = createTimer(); + + return function executionDidEnd() { + // Compute the end time. + const responseTime = timer(); + + // Get the request metadata. + const { operation, operationName } = getOperationMetadata(document); + if (operation && operationName) { + // Increment the graph query value, tagging with the name of the query. + metrics.executedGraphQueriesTotalCounter + .labels(operation, operationName) + .inc(); + + metrics.graphQLExecutionTimingsHistogram + .labels(operation, operationName) + .observe(responseTime); + } + }; + }, + }; + }, +}); + +export default MetricsApolloServerPlugin; diff --git a/src/core/server/graph/subscriptions/server.ts b/src/core/server/graph/subscriptions/server.ts index 0ec58d13b..1a7366afa 100644 --- a/src/core/server/graph/subscriptions/server.ts +++ b/src/core/server/graph/subscriptions/server.ts @@ -29,13 +29,13 @@ import { TenantNotFoundError, WrappedInternalError, } from "coral-server/errors"; +import { getPersistedQuery } from "coral-server/graph/persisted"; import { enrichError, + getOperationMetadata, logAndReportError, logQuery, -} from "coral-server/graph/extensions"; -import { getOperationMetadata } from "coral-server/graph/extensions/helpers"; -import { getPersistedQuery } from "coral-server/graph/persisted"; +} from "coral-server/graph/plugins"; import logger from "coral-server/logger"; import { PersistedQuery } from "coral-server/models/queries"; import { hasStaffRole } from "coral-server/models/user/helpers";