@apolitical/server 1.5.2-beta.6 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/package.json +31 -31
  4. package/src/config.js +17 -14
  5. package/src/container.js +3 -1
  6. package/src/helpers/logger.helper.js +4 -8
  7. package/src/index.js +5 -0
  8. package/src/loaders/documentation.loader.js +9 -4
  9. package/src/loaders/logger.loader.js +20 -13
  10. package/src/loaders/middlewares.loader.js +3 -8
  11. package/src/middlewares/auth.middleware.js +7 -6
  12. package/src/middlewares/error.middleware.js +3 -3
  13. package/src/middlewares/permissions.middleware.js +39 -0
  14. package/src/services/server.service.js +3 -3
  15. package/.gitlab-ci.yml +0 -22
  16. package/.husky/pre-commit +0 -6
  17. package/doc/swagger.json +0 -52
  18. package/index.js +0 -5
  19. package/test/integration/__snapshots__/jwt.apolitical.spec.js.snap +0 -14
  20. package/test/integration/container.js +0 -40
  21. package/test/integration/errors.spec.js +0 -102
  22. package/test/integration/example.spec.js +0 -43
  23. package/test/integration/jwt.apolitical.spec.js +0 -74
  24. package/test/integration/jwt.auth0.spec.js +0 -57
  25. package/test/integration/mocks/configs/jwks.config.js +0 -17
  26. package/test/integration/mocks/endpoints/errors.endpoint.js +0 -40
  27. package/test/integration/mocks/endpoints/example.endpoint.js +0 -8
  28. package/test/integration/mocks/endpoints/index.html +0 -14
  29. package/test/integration/mocks/endpoints/index.js +0 -15
  30. package/test/integration/mocks/endpoints/jwt/apolitical.endpoint.js +0 -9
  31. package/test/integration/mocks/endpoints/jwt/auth0.endpoint.js +0 -13
  32. package/test/integration/mocks/endpoints/static.endpoint.js +0 -15
  33. package/test/integration/probes.spec.js +0 -42
  34. package/test/integration/static.spec.js +0 -94
  35. package/test/test.env +0 -4
package/CHANGELOG.md CHANGED
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.5.2] - 2021-10-18
9
+ ### Changed
10
+ - Allow cors options to be passed in as params on start
11
+
8
12
  ## [1.5.1] - 2021-09-15
9
13
  ### Added
10
14
  - Extend error class to handle too many request error
package/README.md CHANGED
@@ -6,7 +6,7 @@ Node.js module to encapsulate Apolitical's express server setup
6
6
 
7
7
  Requires the following to run:
8
8
 
9
- - [node.js][node] 12.20.1+
9
+ - [node.js][node] 16.13.2+
10
10
  - [yarn][yarn]
11
11
 
12
12
  [node]: https://nodejs.org/en/download/
package/package.json CHANGED
@@ -1,19 +1,13 @@
1
1
  {
2
2
  "name": "@apolitical/server",
3
- "version": "1.5.2-beta.6",
3
+ "version": "2.0.0-beta.0",
4
4
  "description": "Node.js module to encapsulate Apolitical's express server setup",
5
5
  "author": "Apolitical Group Limited <engineering@apolitical.co>",
6
- "contributors": [
7
- "Anouska Hopkins <anouska.hopkins@apolitical.co>",
8
- "Antonio Cordasco <antonio.cordasco@apolitical.co>",
9
- "Dorothy Yau <dorothy.yau@apolitical.co>",
10
- "Laura Hanna-White <laura.hanna-white@apolitical.co>",
11
- "Fatimat Gbajabiamila <fatimat.gbajabiamila@apolitical.co>",
12
- "Renzo Rozza <renzo.rozza@apolitical.co>",
13
- "Rihards Jukna <rihards.jukna@apolitical.co>"
14
- ],
15
6
  "license": "MIT",
16
- "main": "index.js",
7
+ "main": "src/index.js",
8
+ "files": [
9
+ "src"
10
+ ],
17
11
  "scripts": {
18
12
  "test": "jest --bail --runInBand",
19
13
  "unit-test": "jest test/unit/**/* --bail --runInBand",
@@ -30,38 +24,39 @@
30
24
  "Node Modules"
31
25
  ],
32
26
  "dependencies": {
33
- "@apolitical/logger": "1.0.2",
27
+ "@apolitical/logger": "2.0.0-beta.4",
34
28
  "@cloudnative/health-connect": "2.1.0",
35
- "awilix": "5.0.1",
36
- "body-parser": "1.19.0",
29
+ "awilix": "6.0.0",
30
+ "body-parser": "1.19.1",
37
31
  "compression": "1.7.4",
38
- "cookie-parser": "1.4.5",
32
+ "cookie-parser": "1.4.6",
39
33
  "cors": "2.8.5",
40
- "dotenv": "10.0.0",
41
- "express": "4.17.1",
34
+ "dotenv": "14.2.0",
35
+ "express": "4.17.2",
42
36
  "express-jwt": "6.1.0",
43
- "http-status-codes": "2.1.4",
44
- "http-terminator": "3.0.0",
45
- "jsrsasign": "10.4.0",
46
- "jwks-rsa": "2.0.4",
37
+ "http-status-codes": "2.2.0",
38
+ "http-terminator": "3.0.4",
39
+ "jsrsasign": "10.5.1",
40
+ "jwks-rsa": "2.0.5",
47
41
  "jwt-decode": "3.1.2",
48
42
  "morgan": "1.10.0",
49
- "passport": "0.4.1",
43
+ "passport": "0.5.2",
50
44
  "passport-jwt": "4.0.0",
51
- "swagger-ui-express": "4.1.6"
45
+ "swagger-ui-express": "4.3.0"
52
46
  },
53
47
  "devDependencies": {
54
- "@apolitical/eslint-config": "0.0.1",
48
+ "@apolitical/eslint-config": "1.0.0-beta.0",
55
49
  "@apolitical/testing": "0.0.4",
56
- "audit-ci": "4.1.0",
57
- "husky": "7.0.2",
58
- "jest": "27.1.0",
59
- "lint-staged": "11.1.2",
50
+ "audit-ci": "5.1.2",
51
+ "husky": "7.0.4",
52
+ "jest": "27.4.7",
53
+ "jest-junit": "13.0.0",
54
+ "lint-staged": "12.1.7",
60
55
  "mock-jwks": "1.0.1",
61
- "nock": "13.1.3"
56
+ "nock": "13.2.2"
62
57
  },
63
58
  "engines": {
64
- "node": ">=12.20.1"
59
+ "node": ">=16.13.2"
65
60
  },
66
61
  "eslintConfig": {
67
62
  "extends": "@apolitical/eslint-config/base.config"
@@ -86,7 +81,12 @@
86
81
  "testEnvironment": "node",
87
82
  "testTimeout": 60000,
88
83
  "maxConcurrency": 1,
89
- "maxWorkers": 1
84
+ "maxWorkers": 1,
85
+ "reporters": [
86
+ "default",
87
+ "jest-junit"
88
+ ],
89
+ "testResultsProcessor": "jest-junit"
90
90
  },
91
91
  "lint-staged": {
92
92
  "*.js": "eslint --cache --fix --ignore-path .gitignore",
package/src/config.js CHANGED
@@ -2,15 +2,18 @@
2
2
 
3
3
  const { NODE_ENV, LOG_LEVEL } = process.env;
4
4
 
5
- const serviceName = 'apolitical-server';
5
+ const NAME = 'apolitical-server';
6
+ const VERSION = 'v2.0.0';
7
+ const ADMIN_ROLE = 'administrator';
6
8
 
7
9
  module.exports = {
8
10
  NODE_ENV,
9
11
  LOG_LEVEL,
10
12
  LOGGER_OPTIONS: {
11
13
  logLevel: LOG_LEVEL,
12
- metadata: {
13
- serviceName,
14
+ labels: {
15
+ name: NAME,
16
+ version: VERSION,
14
17
  },
15
18
  },
16
19
  ENDPOINTS: {
@@ -20,16 +23,16 @@ module.exports = {
20
23
  },
21
24
  DOCUMENTATION: '/docs/',
22
25
  },
26
+ MIDDLEWARES: {
27
+ PERMISSIONS: {
28
+ ADMIN_ROLE,
29
+ MYSELF_SLUG: 'me',
30
+ },
31
+ },
23
32
  SERVER: {
24
33
  CORS_OPTIONS: {
25
- ORIGIN_TRUE: {
26
- origin: true,
27
- credentials: true,
28
- },
29
- ORIGIN_FALSE: {
30
- origin: false,
31
- credentials: false,
32
- },
34
+ origin: true,
35
+ credentials: true,
33
36
  },
34
37
  BODY_PARSER_OPTIONS: {
35
38
  extended: false,
@@ -61,10 +64,10 @@ module.exports = {
61
64
  ALGORITHM: 'HS256',
62
65
  HEADER: { alg: 'HS256', typ: 'JWT' },
63
66
  DEFAULT_PAYLOAD: {
64
- role: 'administrator',
67
+ role: ADMIN_ROLE,
65
68
  admin: true,
66
- slug: serviceName,
67
- iss: serviceName,
69
+ slug: NAME,
70
+ iss: NAME,
68
71
  sub: 'login',
69
72
  },
70
73
  },
package/src/container.js CHANGED
@@ -23,7 +23,7 @@ const apoliticalLogger = require('@apolitical/logger');
23
23
  // Configuration
24
24
  const config = require('./config');
25
25
  // Logger
26
- const logger = apoliticalLogger(config.LOGGER_OPTIONS);
26
+ const logger = apoliticalLogger.createLogger(config.LOGGER_OPTIONS);
27
27
  // Errors
28
28
  const serverError = require('./errors/server.error');
29
29
  // Helpers
@@ -42,6 +42,7 @@ const authMiddleware = require('./middlewares/auth.middleware');
42
42
  const jwtApoliticalMiddleware = require('./middlewares/jwt/apolitical.middleware');
43
43
  const jwtAuth0Middleware = require('./middlewares/jwt/auth0.middleware');
44
44
  const errorMiddleware = require('./middlewares/error.middleware');
45
+ const permissionsMiddleware = require('./middlewares/permissions.middleware');
45
46
  // Services
46
47
  const jwtService = require('./services/jwt.service');
47
48
  const serverService = require('./services/server.service');
@@ -90,6 +91,7 @@ container.register({
90
91
  jwtApoliticalMiddleware: asFunction(jwtApoliticalMiddleware).singleton(),
91
92
  jwtAuth0Middleware: asFunction(jwtAuth0Middleware).singleton(),
92
93
  errorMiddleware: asFunction(errorMiddleware).singleton(),
94
+ permissionsMiddleware: asFunction(permissionsMiddleware).singleton(),
93
95
  // Services
94
96
  jwtService: asFunction(jwtService).singleton(),
95
97
  serverService: asFunction(serverService).singleton(),
@@ -1,16 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = ({ apoliticalLogger, config: { LOG_LEVEL } }) => ({
4
- build({ filename, method, serviceName }) {
4
+ build({ filename, method, labels }) {
5
5
  const options = { logLevel: LOG_LEVEL };
6
- if (serviceName) {
7
- Object.assign(options, {
8
- metadata: {
9
- serviceName,
10
- },
11
- });
6
+ if (labels) {
7
+ Object.assign(options, { labels });
12
8
  }
13
- const logger = apoliticalLogger(options);
9
+ const logger = apoliticalLogger.createLogger(options);
14
10
  return logger.where(filename, method);
15
11
  },
16
12
  });
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const { resolve } = require('./container');
4
+
5
+ module.exports = resolve('serverService');
@@ -1,18 +1,23 @@
1
1
  'use strict';
2
2
 
3
- module.exports = ({ httpStatusCodes: { MOVED_PERMANENTLY }, config, logger, swaggerUi: { serve, setup } }) => {
4
- const { NODE_ENV } = config;
3
+ module.exports = ({
4
+ httpStatusCodes: { MOVED_PERMANENTLY },
5
+ config,
6
+ logger,
7
+ swaggerUi: { serve, setup },
8
+ permissionsMiddleware,
9
+ }) => {
5
10
  const { DOCUMENTATION } = config.ENDPOINTS;
6
11
 
7
12
  return function load(app, { swaggerDocument }) {
8
13
  const childLogger = logger.where(__filename, 'load');
9
14
  childLogger.debug('Started');
10
15
  // Load document endpoint
11
- if (NODE_ENV !== 'production' && swaggerDocument) {
16
+ if (swaggerDocument) {
12
17
  // Redirection to documentation
13
18
  app.use(`${DOCUMENTATION}index.html`, (req, res) => res.redirect(MOVED_PERMANENTLY, DOCUMENTATION));
14
19
  // Documentation express setup
15
- app.use(DOCUMENTATION, serve, setup(swaggerDocument));
20
+ app.use(DOCUMENTATION, permissionsMiddleware(), serve, setup(swaggerDocument));
16
21
  }
17
22
  childLogger.debug('Finished');
18
23
  };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- module.exports = ({ morgan, config, logger, loggerHelper }) => {
3
+ module.exports = ({ apoliticalLogger, morgan, config, logger, loggerHelper }) => {
4
4
  const {
5
5
  LOGGED_OUT_SLUG,
6
6
  TOKENS: { USER_SLUG },
@@ -21,22 +21,29 @@ module.exports = ({ morgan, config, logger, loggerHelper }) => {
21
21
  ].join(' ');
22
22
  }
23
23
 
24
- return function load(app, { serviceName }) {
24
+ return async function load(app, { labels }) {
25
25
  const childLogger = logger.where(__filename, 'load');
26
26
  childLogger.debug('Started');
27
- // Add user slug token
27
+ // Setup Morgan with custom format and Apolitical Logger stream
28
28
  morgan.token(USER_SLUG, getUserSlug);
29
- // Setup Morgan with custom format and Apolitical Logger (with service metadata) stream
30
- app.use(
31
- morgan(
32
- buildCustomFormat,
33
- loggerHelper.build({
34
- filename: __filename,
35
- method: 'morganHandler',
36
- serviceName,
37
- }),
38
- ),
29
+ const morganMiddleware = morgan(
30
+ buildCustomFormat,
31
+ loggerHelper.build({
32
+ filename: __filename,
33
+ method: 'morganMiddleware',
34
+ labels,
35
+ }),
39
36
  );
37
+ app.use(morganMiddleware);
38
+ // Setup Google Cloud with Apolitical Logger
39
+ const googleMiddleware = await apoliticalLogger.loggerMiddleware(
40
+ loggerHelper.build({
41
+ filename: __filename,
42
+ method: 'googleMiddleware',
43
+ labels,
44
+ }),
45
+ );
46
+ app.use(googleMiddleware);
40
47
  childLogger.debug('Finished');
41
48
  };
42
49
  };
@@ -1,21 +1,16 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = ({ bodyParser: { json, urlencoded }, compression, cors, cookieParser, config, logger }) => {
4
- const {
5
- BODY_PARSER_OPTIONS,
6
- CORS_OPTIONS: { ORIGIN_TRUE, ORIGIN_FALSE },
7
- } = config.SERVER;
4
+ const { BODY_PARSER_OPTIONS, CORS_OPTIONS } = config.SERVER;
8
5
 
9
- return function load(app, { disableCors }) {
6
+ return function load(app, { corsOptions }) {
10
7
  const childLogger = logger.where(__filename, 'load');
11
8
  childLogger.debug('Started');
12
- // Compute cors config
13
- const corsOptions = disableCors ? ORIGIN_FALSE : ORIGIN_TRUE;
14
9
  // Load useful middlewares
15
10
  app.use(cookieParser());
16
11
  app.use(json());
17
12
  app.use(urlencoded(BODY_PARSER_OPTIONS));
18
- app.use(cors(corsOptions));
13
+ app.use(cors(Object.assign({}, CORS_OPTIONS, corsOptions)));
19
14
  app.use(compression());
20
15
  childLogger.debug('Finished');
21
16
  };
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- module.exports =
4
- ({ jwtApoliticalMiddleware, logger, serverError: { Forbidden }, config }) =>
5
- ({ allowLoggedOut = false } = {}) => {
6
- const { COOKIE_KEY } = config.JWT.APOLITICAL;
7
-
3
+ module.exports = ({ jwtApoliticalMiddleware, logger, serverError: { Forbidden }, config }) => {
4
+ const { COOKIE_KEY } = config.JWT.APOLITICAL;
5
+ // Define external options
6
+ return ({ allowLoggedOut = false } = {}) => {
7
+ // Return middleware handler
8
8
  return async function handler(req, res, next) {
9
9
  const childLogger = logger.where(__filename, 'handler');
10
10
  childLogger.debug('Started');
@@ -12,7 +12,7 @@ module.exports =
12
12
  const user = await jwtApoliticalMiddleware(req, res);
13
13
  // If logged out users are not allowed and there is no user object throws forbidden error
14
14
  if (!allowLoggedOut && !user) {
15
- return next(new Forbidden('No Apolitical authorization token', ['cookie-jwt', 'logged-out']));
15
+ return next(new Forbidden('Cannot authenticate action', ['cookie-jwt', 'logged-out']));
16
16
  }
17
17
  // Assigns user to the request object
18
18
  Object.assign(req, { user });
@@ -22,3 +22,4 @@ module.exports =
22
22
  return next();
23
23
  };
24
24
  };
25
+ };
@@ -10,12 +10,12 @@ module.exports =
10
10
  loggerHelper,
11
11
  serverError: { GeneralError },
12
12
  }) =>
13
- ({ serviceName, redirectURL }) => {
13
+ ({ labels, redirectURL }) => {
14
14
  // Apolitical Logger (with service metadata)
15
15
  const errorLogger = loggerHelper.build({
16
16
  filename: __filename,
17
- method: 'errorHandler',
18
- serviceName,
17
+ method: 'errorMiddleware',
18
+ labels,
19
19
  });
20
20
 
21
21
  // eslint-disable-next-line no-unused-vars
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ module.exports = ({ logger, serverError: { Forbidden }, config }) => {
4
+ const { ADMIN_ROLE, MYSELF_SLUG } = config.MIDDLEWARES.PERMISSIONS;
5
+ // Define external options
6
+ return ({ myselfSource = null, myselfTarget = null, allowNonAdmin = false } = {}) => {
7
+ // Return middleware handler
8
+ return async function handler(req, res, next) {
9
+ const childLogger = logger.where(__filename, 'handler');
10
+ childLogger.debug('Started');
11
+ let isNotMyselfSlug = false;
12
+ let isNotAdmin = false;
13
+ // Check myself param
14
+ if (myselfSource) {
15
+ isNotMyselfSlug = req.params[myselfSource] !== MYSELF_SLUG;
16
+ }
17
+ // Check user role (from JWT)
18
+ if (!allowNonAdmin) {
19
+ isNotAdmin = req.user && req.user.role !== ADMIN_ROLE;
20
+ }
21
+ // Check permissions and prevent unauthorised requests
22
+ let unauthorised = false;
23
+ if (isNotMyselfSlug && myselfSource && isNotAdmin && !allowNonAdmin) {
24
+ unauthorised = true;
25
+ } else if (isNotAdmin && !allowNonAdmin) {
26
+ unauthorised = true;
27
+ }
28
+ if (unauthorised) {
29
+ return next(new Forbidden('Cannot authorise action', ['cookie-jwt', 'non-admin']));
30
+ }
31
+ // Update myself slug with real slug (from JWT)
32
+ if (myselfSource && myselfTarget && req.params[myselfSource] === MYSELF_SLUG) {
33
+ Object.assign(req.params, { [myselfSource]: req.user[myselfTarget] });
34
+ }
35
+ childLogger.debug('Finished');
36
+ return next();
37
+ };
38
+ };
39
+ };
@@ -44,13 +44,13 @@ module.exports = ({
44
44
  // Load useful middlewares
45
45
  middlewaresLoader(app, opts);
46
46
  // Load logger
47
- loggerLoader(app, opts);
48
- // Load documentation
49
- documentationLoader(app, opts);
47
+ await loggerLoader(app, opts);
50
48
  // Load custom resources
51
49
  if (opts.appLoader) {
52
50
  await opts.appLoader(app);
53
51
  }
52
+ // Load documentation
53
+ documentationLoader(app, opts);
54
54
  // Load static resources
55
55
  if (opts.staticFiles) {
56
56
  staticLoader(app, opts);
package/.gitlab-ci.yml DELETED
@@ -1,22 +0,0 @@
1
- # External jobs
2
- include:
3
- - project: 'apolitical/templates/gitlab-pipelines'
4
- ref: master
5
- file: 'Node-Testing.gitlab-ci.yml'
6
-
7
- stages:
8
- - cache
9
- - test
10
- - publish
11
-
12
- publish-module:
13
- stage: publish
14
- image: node:12.20.1-alpine3.11
15
- extends:
16
- - .yarn-install
17
- only:
18
- - tags
19
- - /^v\d+\.\d+\.\d+$/
20
- script:
21
- - echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > ~/.npmrc
22
- - yarn publish --access=public
package/.husky/pre-commit DELETED
@@ -1,6 +0,0 @@
1
- #!/bin/sh
2
- . "$(dirname "$0")/_/husky.sh"
3
-
4
- yarn lint-format
5
-
6
- yarn test
package/doc/swagger.json DELETED
@@ -1,52 +0,0 @@
1
- {
2
- "openapi": "3.0.1",
3
- "info": {
4
- "title": "Apolitical Server",
5
- "description": "Node.js module to encapsulate Apolitical's express server setup",
6
- "version": "1.0.0"
7
- },
8
- "servers": [
9
- {
10
- "url": "http://localhost:3000"
11
- }
12
- ],
13
- "paths": {
14
- "/example": {
15
- "get": {
16
- "description": "Example Endpoint",
17
- "parameters": [],
18
- "responses": {
19
- "200": {
20
- "description": "Example Response",
21
- "content": {
22
- "application/json; charset=utf-8": {
23
- "schema": {
24
- "$ref": "#/components/schemas/Response"
25
- },
26
- "examples": {
27
- "message": {
28
- "summary": "Default Response",
29
- "value": { "message": "hello" }
30
- }
31
- }
32
- }
33
- }
34
- }
35
- }
36
- }
37
- }
38
- },
39
- "components": {
40
- "schemas": {
41
- "Response": {
42
- "type": "object",
43
- "required": ["message"],
44
- "properties": {
45
- "message": {
46
- "type": "string"
47
- }
48
- }
49
- }
50
- }
51
- }
52
- }
package/index.js DELETED
@@ -1,5 +0,0 @@
1
- 'use strict';
2
-
3
- const container = require('./src/container');
4
-
5
- module.exports = container.resolve('serverService');
@@ -1,14 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`JWT Apolitical Endpoints GET /jwt should return 200 with token 1`] = `
4
- Object {
5
- "admin": true,
6
- "email": "apolitical.testing@email.com",
7
- "exp": Any<Number>,
8
- "iss": "apolitical-testing",
9
- "name": "Apolitical Testing",
10
- "role": "public-servant",
11
- "slug": "apolitical-testing",
12
- "sub": "login",
13
- }
14
- `;
@@ -1,40 +0,0 @@
1
- 'use strict';
2
-
3
- // External Modules
4
- require('dotenv').config({ path: `${__dirname}/../test.env` });
5
- const { createContainer, asValue } = require('awilix');
6
- const { default: jwksClient } = require('mock-jwks');
7
- // Internal Modules
8
- const apoliticalTesting = require('@apolitical/testing');
9
- // API Container
10
- const apiContainer = require('../../src/container');
11
- // API Documentation
12
- const swaggerDocument = require('../../doc/swagger.json');
13
- // Mocks
14
- const jwksConfig = require('./mocks/configs/jwks.config');
15
- const mockEndpoints = require('./mocks/endpoints');
16
- // Testing environment variables
17
- const config = apiContainer.resolve('config');
18
- Object.assign(config.SERVER, {
19
- PORT: process.env.PORT,
20
- SESSION_SECRET: process.env.SESSION_SECRET,
21
- });
22
- // Test Container
23
- const testContainer = createContainer();
24
- testContainer.register(apiContainer.registrations);
25
- testContainer.register({
26
- // Internal Modules
27
- apoliticalTesting: asValue(apoliticalTesting),
28
- // Configuration
29
- config: asValue(config),
30
- // Documentation
31
- swaggerDocument: asValue(swaggerDocument),
32
- // Mocks
33
- mockEndpoints: asValue(mockEndpoints),
34
- mockJwks: asValue({
35
- jwksClient,
36
- jwksConfig,
37
- }),
38
- });
39
-
40
- module.exports = testContainer;
@@ -1,102 +0,0 @@
1
- 'use strict';
2
-
3
- const { resolve } = require('./container');
4
- // Internal Modules
5
- const { request } = resolve('apoliticalTesting');
6
- // Configuration
7
- const {
8
- SERVER: { PORT, SESSION_SECRET },
9
- } = resolve('config');
10
- // Services
11
- const { start, stop, errors } = resolve('serverService');
12
- // Mocks
13
- const { errorsEndpoint } = resolve('mockEndpoints');
14
- const { metadata, url, controller } = errorsEndpoint(errors);
15
-
16
- describe('Error Handling', () => {
17
- let agents = null;
18
-
19
- beforeAll(async () => {
20
- const app = await start({
21
- port: PORT,
22
- appLoader: (app) => {
23
- app.get(url, controller);
24
- },
25
- handleErrors: true,
26
- serviceName: 'test-service',
27
- });
28
-
29
- agents = request({ sessionSecret: SESSION_SECRET }).generateAgents(app);
30
- });
31
-
32
- afterAll(async () => {
33
- await stop();
34
- });
35
-
36
- describe('GET /error', () => {
37
- test('should return 400', async () => {
38
- const res = await agents.loggedOut.get(url.replace(':code', 400));
39
- expect(res.statusCode).toEqual(400);
40
- expect(res.body).toEqual(metadata);
41
- });
42
-
43
- test('should return 403', async () => {
44
- const res = await agents.loggedOut.get(url.replace(':code', 403));
45
- expect(res.statusCode).toEqual(403);
46
- expect(res.body).toEqual(metadata);
47
- });
48
-
49
- test('should return 404', async () => {
50
- const res = await agents.loggedOut.get(url.replace(':code', 404));
51
- expect(res.statusCode).toEqual(404);
52
- expect(res.body).toEqual(metadata);
53
- });
54
-
55
- test('should return 429', async () => {
56
- const res = await agents.loggedOut.get(url.replace(':code', 429));
57
- expect(res.statusCode).toEqual(429);
58
- expect(res.body).toEqual(metadata);
59
- });
60
-
61
- test('should return 500', async () => {
62
- const res = await agents.loggedOut.get(url.replace(':code', 500));
63
- expect(res.statusCode).toEqual(500);
64
- expect(res.body).toEqual({ message: metadata.message, errors: [] });
65
- });
66
-
67
- test('should return 500 (unexpected error)', async () => {
68
- const res = await agents.loggedOut.get(url.replace(':code', 501));
69
- expect(res.statusCode).toEqual(500);
70
- expect(res.body).toEqual({ message: 'Unexpected error: Some Unexpected Error', errors: ['unknown-error'] });
71
- });
72
- });
73
- });
74
-
75
- describe('Error Redirect', () => {
76
- let agents = null;
77
- const redirectURL = `http://localhost:${PORT}/not-found`;
78
-
79
- beforeAll(async () => {
80
- const app = await start({
81
- port: PORT,
82
- appLoader: (app) => {
83
- app.get(url, controller);
84
- },
85
- handleErrors: true,
86
- redirectURL,
87
- serviceName: 'test-service',
88
- });
89
-
90
- agents = request({ sessionSecret: SESSION_SECRET }).generateAgents(app);
91
- });
92
-
93
- afterAll(async () => {
94
- await stop();
95
- });
96
-
97
- test('should return 307', async () => {
98
- const res = await agents.loggedOut.get(url.replace(':code', 500));
99
- expect(res.statusCode).toEqual(307);
100
- expect(res.headers.location).toEqual(`${redirectURL}?errorMessage=${encodeURI(metadata.message)}`);
101
- });
102
- });
@@ -1,43 +0,0 @@
1
- 'use strict';
2
-
3
- const { resolve } = require('./container');
4
- // Internal Modules
5
- const { request } = resolve('apoliticalTesting');
6
- // Configuration
7
- const {
8
- SERVER: { PORT, SESSION_SECRET },
9
- } = resolve('config');
10
- // Services
11
- const { start, stop } = resolve('serverService');
12
- // Mocks
13
- const { exampleEndpoint } = resolve('mockEndpoints');
14
- const { url, controller } = exampleEndpoint();
15
-
16
- describe('Example Endpoint', () => {
17
- let agents = null;
18
-
19
- beforeAll(async () => {
20
- const app = await start({
21
- port: PORT,
22
- appLoader: (app) => {
23
- app.get(url, controller);
24
- },
25
- handleErrors: false,
26
- serviceName: 'test-service',
27
- });
28
-
29
- agents = request({ sessionSecret: SESSION_SECRET }).generateAgents(app);
30
- });
31
-
32
- afterAll(async () => {
33
- await stop();
34
- });
35
-
36
- describe('GET /dummy', () => {
37
- test('should return 200', async () => {
38
- const res = await agents.loggedOut.get(url);
39
- expect(res.statusCode).toEqual(200);
40
- expect(res.body).toEqual({ message: 'some-message' });
41
- });
42
- });
43
- });
@@ -1,74 +0,0 @@
1
- 'use strict';
2
-
3
- const { resolve } = require('./container');
4
- // Internal Modules
5
- const { request } = resolve('apoliticalTesting');
6
- // Configuration
7
- const {
8
- ENDPOINTS: { DOCUMENTATION },
9
- SERVER: { PORT, SESSION_SECRET },
10
- } = resolve('config');
11
- // Documentation
12
- const swaggerDocument = resolve('swaggerDocument');
13
- // Services
14
- const {
15
- start,
16
- stop,
17
- jwt,
18
- middlewares: { auth },
19
- } = resolve('serverService');
20
- // Mocks
21
- const { jwtApoliticalEndpoint } = resolve('mockEndpoints');
22
- const { url, controller } = jwtApoliticalEndpoint();
23
-
24
- describe('JWT Apolitical Endpoints', () => {
25
- let agents = null;
26
-
27
- beforeAll(async () => {
28
- const app = await start({
29
- port: PORT,
30
- appLoader: (app) => {
31
- jwt.apolitical.setup(SESSION_SECRET);
32
- app.use(auth());
33
- app.get(url, controller);
34
- },
35
- swaggerDocument,
36
- handleErrors: true,
37
- serviceName: 'test-service',
38
- });
39
-
40
- agents = request({ sessionSecret: SESSION_SECRET }).generateAgents(app);
41
- });
42
-
43
- afterAll(async () => {
44
- await stop();
45
- });
46
-
47
- describe('GET /jwt', () => {
48
- test('should return 200 with token', async () => {
49
- const res = await agents.loggedIn.get(url);
50
- expect(res.statusCode).toEqual(200);
51
- expect(res.body.user).toMatchSnapshot({ exp: expect.any(Number) });
52
- });
53
-
54
- test('should return 403 without token', async () => {
55
- const res = await agents.loggedOut.get(url);
56
- expect(res.statusCode).toEqual(403);
57
- expect(res.body.message).toEqual('No Apolitical authorization token');
58
- });
59
- });
60
-
61
- describe('GET /docs', () => {
62
- test('should return 200 with token', async () => {
63
- const res = await agents.loggedIn.get(DOCUMENTATION);
64
- expect(res.statusCode).toEqual(200);
65
- expect(res.headers['content-type']).toEqual('text/html; charset=utf-8');
66
- });
67
-
68
- test('should return 301 with token (index file)', async () => {
69
- const res = await agents.loggedIn.get(`${DOCUMENTATION}index.html`);
70
- expect(res.statusCode).toEqual(301);
71
- expect(res.headers.location).toEqual(DOCUMENTATION);
72
- });
73
- });
74
- });
@@ -1,57 +0,0 @@
1
- 'use strict';
2
-
3
- const { resolve } = require('./container');
4
- // Internal Modules
5
- const { request } = resolve('apoliticalTesting');
6
- // Configuration
7
- const {
8
- SERVER: { PORT, SESSION_SECRET },
9
- } = resolve('config');
10
- // Services
11
- const { start, stop, jwt } = resolve('serverService');
12
- // Mocks
13
- const { jwksClient, jwksConfig } = resolve('mockJwks');
14
- const { jwtAuth0Endpoint } = resolve('mockEndpoints');
15
- const { url, controller, auth } = jwtAuth0Endpoint(jwt, jwksConfig);
16
-
17
- describe('JWT Auth0 Endpoints', () => {
18
- let agents = null;
19
-
20
- const jwksMock = jwksClient(jwksConfig.ISSUER);
21
-
22
- beforeAll(async () => {
23
- jwksMock.start();
24
-
25
- const app = await start({
26
- port: PORT,
27
- appLoader: (app) => {
28
- app.get(url, auth, controller);
29
- },
30
- handleErrors: true,
31
- serviceName: 'test-service',
32
- });
33
-
34
- agents = request({ sessionSecret: SESSION_SECRET }).generateAgents(app);
35
- });
36
-
37
- afterAll(async () => {
38
- jwksMock.stop();
39
- await stop();
40
- });
41
-
42
- describe('GET /jwt', () => {
43
- test('should return 200 with token', async () => {
44
- const token = jwksMock.token(jwksConfig.PAYLOAD);
45
- agents.loggedInMyself.set('Authorization', `Bearer ${token}`);
46
- const res = await agents.loggedInMyself.get(url);
47
- expect(res.statusCode).toEqual(200);
48
- expect(res.body).toEqual({ ok: true });
49
- });
50
-
51
- test('should return 403 without token', async () => {
52
- const res = await agents.loggedOut.get(url);
53
- expect(res.statusCode).toEqual(403);
54
- expect(res.body.message).toEqual('Unauthorized error: No authorization token was found');
55
- });
56
- });
57
- });
@@ -1,17 +0,0 @@
1
- 'use strict';
2
-
3
- const DOMAIN = 'some-domain.eu.auth0.com';
4
- const AUDIENCE = `https://some.url.co/api/auth-api/`;
5
- const ISSUER = `https://${DOMAIN}/`;
6
-
7
- module.exports = {
8
- DOMAIN,
9
- AUDIENCE,
10
- ISSUER,
11
- PAYLOAD: {
12
- iss: ISSUER,
13
- sub: 'some-sub@clients',
14
- aud: AUDIENCE,
15
- gty: 'client-credentials',
16
- },
17
- };
@@ -1,40 +0,0 @@
1
- 'use strict';
2
-
3
- const errorArray = ['some-error'];
4
- const errorMessage = 'Some Error Message';
5
-
6
- module.exports = (errors) => ({
7
- metadata: {
8
- errors: errorArray,
9
- message: errorMessage,
10
- },
11
- url: '/error/:code',
12
- controller: (req, res, next) => {
13
- switch (parseInt(req.params.code)) {
14
- case 400: {
15
- next(new errors.BadRequest(errorMessage, errorArray));
16
- break;
17
- }
18
- case 403: {
19
- next(new errors.Forbidden(errorMessage, errorArray));
20
- break;
21
- }
22
- case 404: {
23
- next(new errors.NotFound(errorMessage, errorArray));
24
- break;
25
- }
26
- case 429: {
27
- next(new errors.TooManyRequests(errorMessage, errorArray));
28
- break;
29
- }
30
- case 500: {
31
- next(new errors.InternalError(errorMessage));
32
- break;
33
- }
34
- default: {
35
- next(new Error('Some Unexpected Error'));
36
- break;
37
- }
38
- }
39
- },
40
- });
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = () => ({
4
- url: '/example',
5
- controller: (req, res) => {
6
- res.status(200).json({ message: 'some-message' });
7
- },
8
- });
@@ -1,14 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <meta name="theme-color" content="#000000" />
7
- <meta property="og:title" content="__OG_TITLE__" />
8
- <title>Static Server | Apolitical</title>
9
- </head>
10
- <body>
11
- <noscript>You need to enable JavaScript to run this app.</noscript>
12
- <div id="root"></div>
13
- </body>
14
- </html>
@@ -1,15 +0,0 @@
1
- 'use strict';
2
-
3
- const exampleEndpoint = require('./example.endpoint');
4
- const errorsEndpoint = require('./errors.endpoint');
5
- const jwtApoliticalEndpoint = require('./jwt/apolitical.endpoint');
6
- const jwtAuth0Endpoint = require('./jwt/auth0.endpoint');
7
- const staticEndpoint = require('./static.endpoint');
8
-
9
- module.exports = {
10
- exampleEndpoint,
11
- errorsEndpoint,
12
- jwtApoliticalEndpoint,
13
- jwtAuth0Endpoint,
14
- staticEndpoint,
15
- };
@@ -1,9 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = () => ({
4
- url: '/jwt',
5
- controller: (req, res) => {
6
- const { user } = req;
7
- res.status(200).json({ user });
8
- },
9
- });
@@ -1,13 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = (jwt, { DOMAIN, AUDIENCE, ISSUER }) => ({
4
- url: '/jwt',
5
- controller: (req, res) => {
6
- res.status(200).json({ ok: true });
7
- },
8
- auth: jwt.auth0.parse({
9
- domain: DOMAIN,
10
- audience: AUDIENCE,
11
- issuer: ISSUER,
12
- }),
13
- });
@@ -1,15 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- module.exports = () => ({
7
- baseUrl: '/',
8
- folderPath: path.join(__dirname),
9
- indexFilePath: path.join(__dirname, 'index.html'),
10
- controller: (req, res) => {
11
- const indexFile = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
12
- const indexModified = indexFile.replace('__OG_TITLE__', 'some-title');
13
- res.send(indexModified);
14
- },
15
- });
@@ -1,42 +0,0 @@
1
- 'use strict';
2
-
3
- const { resolve } = require('./container');
4
- // Internal Modules
5
- const { request } = resolve('apoliticalTesting');
6
- const {
7
- ENDPOINTS: {
8
- PROBES: { LIVENESS, READINESS },
9
- },
10
- SERVER: { PORT, SESSION_SECRET },
11
- } = resolve('config');
12
- // Services
13
- const { start, stop } = resolve('serverService');
14
-
15
- describe('Probes Endpoints', () => {
16
- let agents = null;
17
-
18
- beforeAll(async () => {
19
- const app = await start({ port: PORT, serviceName: 'test-service' });
20
- agents = request({ sessionSecret: SESSION_SECRET }).generateAgents(app);
21
- });
22
-
23
- afterAll(async () => {
24
- await stop();
25
- });
26
-
27
- describe('GET /liveness', () => {
28
- test('should return 200', async () => {
29
- const res = await agents.loggedOut.get(LIVENESS);
30
- expect(res.statusCode).toEqual(200);
31
- expect(res.body).toEqual({});
32
- });
33
- });
34
-
35
- describe('GET /readiness', () => {
36
- test('should return 200', async () => {
37
- const res = await agents.loggedOut.get(READINESS);
38
- expect(res.statusCode).toEqual(200);
39
- expect(res.body).toEqual({});
40
- });
41
- });
42
- });
@@ -1,94 +0,0 @@
1
- 'use strict';
2
-
3
- const { resolve } = require('./container');
4
- // Internal Modules
5
- const { request } = resolve('apoliticalTesting');
6
- // Configuration
7
- const {
8
- SERVER: { PORT, SESSION_SECRET },
9
- } = resolve('config');
10
- // Services
11
- const { start, stop } = resolve('serverService');
12
- // Mocks
13
- const { staticEndpoint } = resolve('mockEndpoints');
14
- const { baseUrl, folderPath, indexFilePath, controller } = staticEndpoint();
15
-
16
- describe('Static Server', () => {
17
- let agents = null;
18
-
19
- describe('Default static loader', () => {
20
- beforeAll(async () => {
21
- const app = await start({
22
- port: PORT,
23
- staticFiles: {
24
- baseUrl,
25
- folderPath,
26
- indexFilePath,
27
- },
28
- handleErrors: true,
29
- serviceName: 'test-service',
30
- });
31
-
32
- agents = request({ sessionSecret: SESSION_SECRET }).generateAgents(app);
33
- });
34
-
35
- afterAll(async () => {
36
- await stop();
37
- });
38
-
39
- describe('GET /index.html', () => {
40
- test('should return 200 with token', async () => {
41
- const res = await agents.loggedIn.get('/');
42
- expect(res.statusCode).toEqual(200);
43
- expect(res.headers['content-type']).toEqual('text/html; charset=UTF-8');
44
- expect(res.text.includes('__OG_TITLE__')).toBeTruthy();
45
- });
46
-
47
- test('should return 200 without token', async () => {
48
- const res = await agents.loggedOut.get('/');
49
- expect(res.statusCode).toEqual(200);
50
- expect(res.headers['content-type']).toEqual('text/html; charset=UTF-8');
51
- expect(res.text.includes('__OG_TITLE__')).toBeTruthy();
52
- });
53
- });
54
- });
55
-
56
- describe('Custom static loader', () => {
57
- beforeAll(async () => {
58
- const app = await start({
59
- port: PORT,
60
- staticFiles: {
61
- baseUrl,
62
- folderPath,
63
- },
64
- appLoader: (app) => {
65
- app.get(`${baseUrl}*`, controller);
66
- },
67
- handleErrors: true,
68
- serviceName: 'test-service',
69
- });
70
-
71
- agents = request({ sessionSecret: SESSION_SECRET }).generateAgents(app);
72
- });
73
-
74
- afterAll(async () => {
75
- await stop();
76
- });
77
-
78
- describe('GET /index.html', () => {
79
- test('should return 200 with token', async () => {
80
- const res = await agents.loggedIn.get('/');
81
- expect(res.statusCode).toEqual(200);
82
- expect(res.headers['content-type']).toEqual('text/html; charset=utf-8');
83
- expect(res.text.includes('some-title')).toBeTruthy();
84
- });
85
-
86
- test('should return 200 without token', async () => {
87
- const res = await agents.loggedOut.get('/');
88
- expect(res.statusCode).toEqual(200);
89
- expect(res.headers['content-type']).toEqual('text/html; charset=utf-8');
90
- expect(res.text.includes('some-title')).toBeTruthy();
91
- });
92
- });
93
- });
94
- });
package/test/test.env DELETED
@@ -1,4 +0,0 @@
1
- LOG_LEVEL=error
2
- NODE_ENV=testing
3
- PORT=3000
4
- SESSION_SECRET=hello