@studyportals/cors 2.2.1-0 → 2.3.0-beta.1

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 (39) hide show
  1. package/.codeclimate.yml +25 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.nycrc +22 -0
  4. package/.travis.yml +18 -0
  5. package/README.md +110 -0
  6. package/{index.d.ts → index.ts} +2 -2
  7. package/package.json +38 -39
  8. package/src/domain/allowed-domain-reg-ex-list.ts +26 -0
  9. package/src/domain/allowed-explicit-domains-rules.ts +52 -0
  10. package/src/domain/cors.ts +32 -0
  11. package/src/domain/enums/environment.ts +4 -0
  12. package/src/domain/enums/{index.d.ts → index.ts} +2 -2
  13. package/src/domain/enums/scope.ts +4 -0
  14. package/src/domain/{index.d.ts → index.ts} +3 -3
  15. package/src/implementations/express-middleware.ts +21 -0
  16. package/src/implementations/{index.d.ts → index.ts} +3 -2
  17. package/src/implementations/options.ts +50 -0
  18. package/src/implementations/s3-rules.ts +12 -0
  19. package/test/domain/cors.s3.test.ts +92 -0
  20. package/test/domain/cors.valid.domain.test.ts +132 -0
  21. package/test/mocha.opts +3 -0
  22. package/tsconfig.json +38 -0
  23. package/tslint.json +132 -0
  24. package/index.js +0 -7
  25. package/src/domain/allowed-domain-reg-ex-list.d.ts +0 -10
  26. package/src/domain/allowed-domain-reg-ex-list.js +0 -35
  27. package/src/domain/cors.d.ts +0 -10
  28. package/src/domain/cors.js +0 -28
  29. package/src/domain/enums/environment.d.ts +0 -4
  30. package/src/domain/enums/environment.js +0 -7
  31. package/src/domain/enums/index.js +0 -7
  32. package/src/domain/enums/scope.d.ts +0 -4
  33. package/src/domain/enums/scope.js +0 -7
  34. package/src/domain/index.js +0 -8
  35. package/src/implementations/express-middleware.d.ts +0 -5
  36. package/src/implementations/express-middleware.js +0 -26
  37. package/src/implementations/index.js +0 -7
  38. package/src/implementations/options.d.ts +0 -8
  39. package/src/implementations/options.js +0 -48
@@ -0,0 +1,25 @@
1
+ engines:
2
+ duplication:
3
+ enabled: true
4
+ config:
5
+ languages:
6
+ - ruby:
7
+ - javascript:
8
+ - php:
9
+ - python:
10
+ eslint:
11
+ enabled: true
12
+ # 5.7.0
13
+ channel: "eslint-5"
14
+ tslint:
15
+ enabled: true
16
+ # 5.9.1
17
+ config: tslint.json
18
+ fixme:
19
+ enabled: true
20
+ ratings:
21
+ paths:
22
+ - "**.js"
23
+ - "**.ts"
24
+ exclude_paths:
25
+ - "**/node_modules/"
@@ -0,0 +1 @@
1
+ * @studyportals/plop @studyportals/samurai-pizza-cats
package/.nycrc ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "include": [
3
+ "src/**/*.ts"
4
+ ],
5
+ "exclude": [
6
+ "src/**/*.interface.ts"
7
+ ],
8
+ "require": [
9
+ "ts-node/register"
10
+ ],
11
+ "extension": [
12
+ ".ts"
13
+ ],
14
+ "reporter": [
15
+ "lcov",
16
+ "text-summary",
17
+ "html"
18
+ ],
19
+ "sourceMap": true,
20
+ "instrument": true,
21
+ "all": false
22
+ }
package/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ env:
2
+ global:
3
+ - CC_TEST_REPORTER_ID=81a49eda82f1c6ca2bcaf8475befb5a95136032496a53d6754f5e657d8c56a48
4
+ language: node_js
5
+ node_js:
6
+ - "8"
7
+ cache:
8
+ npm: true # this cashes ~/.npm folder on Travis
9
+ install:
10
+ - npm ci
11
+ script:
12
+ - npm run test:coverage
13
+ before_script:
14
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
15
+ - chmod +x ./cc-test-reporter
16
+ - ./cc-test-reporter before-build
17
+ after_script:
18
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ ![volkswagen status](https://auchenberg.github.io/volkswagen/volkswargen_ci.svg?v=1)
2
+ # CORS
3
+
4
+ # Getting Started
5
+
6
+ This library can be used to add CORS validation to your API. It can also replace API gateway's implementation of CORS by adding an OPTIONS lambda. This is necessary when you API needs to function for wildcard domains like release candidates.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @studyportals/cors
12
+ ```
13
+
14
+ ## Setting up the CORS check in your repo
15
+ To use this package in a Lambda the CORS class needs to be constructed outside of the lambda handler. Example:
16
+
17
+ ```typescript
18
+ import {CORS, Scope, Environment} from '@studyportals/cors'
19
+
20
+ const cors = new CORS(Scope.Student, Environment.Production)
21
+
22
+ export async function handler(event: any) {
23
+ const response = {body: 'someBody', headers: {}};
24
+ const origin = event.headers['Origin']; // BEAWARE HEADERS ARE CASE SENSITIVE!!
25
+
26
+ if(await cors.isValidDomain(origin)){
27
+ response.headers['Access-Control-Allow-Origin'] = origin;
28
+ }
29
+
30
+ return response;
31
+ }
32
+
33
+ ```
34
+
35
+ Make sure to use the correct `Scope` And `Environment`
36
+
37
+ The lambda needs to have access to these buckets. If this bucket is not in your account you need to ask `Samurai Pizza Cats` for access.
38
+
39
+ ## Setting up the Options lambda.
40
+ To add the OPTIONS lambda you need to make sure all lambda's have CORS disabled. After this you can create a lambda using the following snippet:
41
+
42
+ ```typescript
43
+ import {OptionsLambda, Scope, Environment} from '@studyportals/cors'
44
+
45
+ const optionsLambda = new OptionsLambda(Scope.Student, Environment.Production);
46
+ const handler = optionsLambda.getHandler();
47
+
48
+ // Lambda hook
49
+ export function handler;
50
+ ```
51
+
52
+ This lambda handler needs to be added to the `serverless.yaml` file. The incoming event needs to be a http event with the path `/{any+}` and method `OPTIONS`
53
+
54
+
55
+ ## Express CORS
56
+ Express applications can use the `ExpressMiddleware` provided by this package. Example:
57
+ ```typescript
58
+ import { express } from 'express';
59
+ import {ExpressMiddleware, Scope, Environment} from '@studyportals/cors'
60
+ const app = express();
61
+
62
+ const corsMiddleWare = ExpressMiddleware.getMiddleWare(Scope.Student, Environment.Production);
63
+ app.use(corsMiddleWare);
64
+ ```
65
+
66
+ ## S3 CORS rules
67
+ [GoldenEye](https://github.com/studyportals/goldeneye) and other tools that need to generate S3 IAM policies can use the `S3Rules` provided in this package. Example:
68
+ ```typescript
69
+ import { CorsRule } from "aws-cdk-lib/aws-s3";
70
+ import * as cors from "@studyportals/cors";
71
+
72
+ generateRules = (scope: cors.Scope, environment: cors.Environment): CorsRule[] => {
73
+ return cors.S3Rules.generate(scope, environment) as CorsRule[];
74
+ };
75
+ ```
76
+
77
+
78
+ # Developing the package
79
+
80
+ ## Modifying allowed domain list
81
+ When modifying the allowed domain list follow the following rules
82
+
83
+ 1. When a domain is added or removed for security reasons (Unauthorized access by thirdparty) the major version should be increased. All microservices need to update their version of this package ASAP.
84
+
85
+ 2. When a domain is added for feature reasons (Added another micro website) the minor version should be increased. Only affected microservices should be required to update their CORS package ASAP
86
+
87
+ ## Compiling
88
+ In a terminal, run
89
+ ```
90
+ tsc
91
+ ```
92
+
93
+ The output JavaScript can be found in `/bin`.
94
+
95
+ ## Testing
96
+
97
+ Run `npm test` to execute the unit tests.
98
+
99
+ run `npm test:coverage` to run the unit tests as well as generate a coverage report.
100
+
101
+ If `test` fails, publishing will not continue.
102
+ Consult the terminal to find out why this has happened.
103
+
104
+ We have a reference document for writing tests [here](https://github.com/studyportals/SamuraiPizzaCats/blob/master/Testing.md "SamuraiPizzaCats/Testing.md").
105
+
106
+ ## Deploying
107
+ We publish an npm package, this is done by first committing and pushing the changes. updating the version number, followed by a `npm run deploy` command. This command prepares for deployment and then automatically publishes and pushes to GitHub.
108
+
109
+ # House Rules
110
+ House Rules are located in the [SamuraiPizzaCats](https://github.com/studyportals/SamuraiPizzaCats) repository.
@@ -1,2 +1,2 @@
1
- export * from './src/domain';
2
- export * from './src/implementations';
1
+ export * from './src/domain';
2
+ export * from './src/implementations';
package/package.json CHANGED
@@ -1,39 +1,38 @@
1
- {
2
- "name": "@studyportals/cors",
3
- "version": "2.2.1-0",
4
- "description": "",
5
- "main": "bin/index.js",
6
- "scripts": {
7
- "test": "mocha test/**/*.test.ts",
8
- "test:watch": "mocha -w --reporter min test/**/*.test.ts",
9
- "deploy-patch": "npm version patch && npm run deploy && npm publish bin",
10
- "deploy-minor": "npm version minor && npm run deploy && npm publish bin",
11
- "deploy-major": "npm version major && npm run deploy && npm publish bin",
12
- "deploy-beta": "npm version prerelease && npm run deploy && npm publish dist --tag beta",
13
- "prepare-deployment": "cp package.json bin/package.json",
14
- "deploy": "npm run build && npm run prepare-deployment",
15
- "build": "rm -rf bin && tsc && rm -rf bin/test",
16
- "lint": "tslint 'src/**/*.ts'",
17
- "lint-fix": "tslint --fix 'src/**/*.ts'",
18
- "test:coverage": "nyc mocha test/**/*.test.ts"
19
- },
20
- "private": false,
21
- "author": "SP - Samurai Pizza Cats",
22
- "license": "BSD3",
23
- "dependencies": {
24
- "@types/express": "^4.17.3"
25
- },
26
- "devDependencies": {
27
- "@types/chai": "^4.1.7",
28
- "@types/node": "^10.12.15",
29
- "chai": "^4.2.0",
30
- "mocha": "^5.2.0",
31
- "mocha-typescript": "1.1.12",
32
- "nyc": "^13.3.0",
33
- "ts-loader": "^2.3.7",
34
- "ts-node": "^7.0.1",
35
- "tslint": "^5.11.0",
36
- "typemoq": "^2.1.0",
37
- "typescript": "^3.8.3"
38
- }
39
- }
1
+ {
2
+ "name": "@studyportals/cors",
3
+ "version": "2.3.0-beta.1",
4
+ "description": "",
5
+ "main": "bin/index.js",
6
+ "scripts": {
7
+ "test": "mocha test/**/*.test.ts",
8
+ "test:watch": "mocha -w --reporter min test/**/*.test.ts",
9
+ "deploy-patch": "npm version patch && npm run deploy",
10
+ "deploy-minor": "npm version minor && npm run deploy",
11
+ "deploy-major": "npm version major && npm run deploy",
12
+ "prepare-deployment": "cp package.json bin/package.json",
13
+ "deploy": "npm run build && npm run prepare-deployment && npm publish bin",
14
+ "build": "rm -rf bin && tsc && rm -rf bin/test",
15
+ "lint": "tslint 'src/**/*.ts'",
16
+ "lint-fix": "tslint --fix 'src/**/*.ts'",
17
+ "test:coverage": "nyc mocha test/**/*.test.ts"
18
+ },
19
+ "private": false,
20
+ "author": "SP - Samurai Pizza Cats",
21
+ "license": "BSD3",
22
+ "dependencies": {
23
+ "@types/express": "^4.17.3"
24
+ },
25
+ "devDependencies": {
26
+ "@types/chai": "^4.1.7",
27
+ "@types/node": "^10.12.15",
28
+ "chai": "^4.2.0",
29
+ "mocha": "^5.2.0",
30
+ "mocha-typescript": "1.1.12",
31
+ "nyc": "^13.3.0",
32
+ "ts-loader": "^2.3.7",
33
+ "ts-node": "^7.0.1",
34
+ "tslint": "^5.11.0",
35
+ "typemoq": "^2.1.0",
36
+ "typescript": "^4.8.4"
37
+ }
38
+ }
@@ -0,0 +1,26 @@
1
+ import { Environment } from "./enums/environment";
2
+ import { Scope } from "./enums/scope";
3
+
4
+
5
+ export const AllowedDomainRegExList = {
6
+ [Scope.Student]: {
7
+ [Environment.Production]: [
8
+ /^https?:\/\/(www(?:\.dev)?|cypress\.rc|rc|beta|[0-9]{4,16}(?:-bob)?\.rc)\.mastersportal\.(com|fyi)(?::3030)?$/,
9
+ /^https?:\/\/(www(?:\.dev)?|cypress\.rc|rc|beta|[0-9]{4,16}(?:-bob)?\.rc)\.bachelorsportal\.(com|fyi)(?::3030)?$/,
10
+ /^https?:\/\/(www(?:\.dev)?|cypress\.rc|rc|beta|[0-9]{4,16}(?:-bob)?\.rc)\.phdportal\.(com|fyi)(?::3030)?$/,
11
+ /^https?:\/\/(www(?:\.dev)?|cypress\.rc|rc|beta|[0-9]{4,16}(?:-bob)?\.rc)\.preparationcoursesportal\.(com|fyi)(?::3030)?$/,
12
+ /^https?:\/\/(www(?:\.dev)?|cypress\.rc|rc|beta|[0-9]{4,16}(?:-bob)?\.rc)\.distancelearningportal\.(com|fyi)(?::3030)?$/,
13
+ /^https?:\/\/(www(?:\.dev)?|cypress\.rc|rc|beta|[0-9]{4,16}(?:-bob)?\.rc)\.shortcoursesportal\.(com|fyi)(?::3030)?$/,
14
+ /^https?:\/\/studentemailing\.prtl\.co$/,
15
+ /^https?:\/\/scholarship\.mastersportal\.com$/,
16
+ /^https?:\/\/search-page\.prtl\.co$/,
17
+ ],
18
+ [Environment.Testing]: [
19
+ /(.*?)/,
20
+ ],
21
+ },
22
+ [Scope.UniAdmin]: {
23
+ [Environment.Production]: [/(.*?)/],
24
+ [Environment.Testing]: [/(.*?)/],
25
+ }
26
+ };
@@ -0,0 +1,52 @@
1
+ import { Environment } from "./enums/environment";
2
+ import { Scope } from "./enums/scope";
3
+
4
+ export type AllowedExplicitDomainsRulesType = typeof AllowedExplicitDomainsRules
5
+
6
+ export type CorsRule = {
7
+ allowedOrigins: string[],
8
+ allowedMethods: string[],
9
+ allowedHeaders: string[],
10
+ exposedHeaders: string[],
11
+ maxAge: number,
12
+ }
13
+
14
+ export const AllowedExplicitDomainsRules = {
15
+ [Scope.Student]: {
16
+ [Environment.Production]: {
17
+ allowedOrigins: ["*.mastersportal.com","*.bachelorsportal.com","*.shortcoursesportal.com",
18
+ "*.distancelearningportal.com","*.preparationcoursesportal.com","*.phdportal.com",
19
+ "*.prtl.co","*.rc.mastersportal.fyi","*.rc.bachelorsportal.fyi","*.rc.shortcoursesportal.fyi",
20
+ "*.rc.distancelearningportal.fyi","*.rc.preparationcoursesportal.fyi","*.rc.phdportal.fyi"],
21
+ allowedHeaders: ["Authorization"],
22
+ allowedMethods: ["GET"],
23
+ exposedHeaders: [],
24
+ maxAge: 3000
25
+
26
+ },
27
+ [Environment.Testing]: {
28
+ allowedOrigins: ["*"],
29
+ allowedMethods: ["GET"],
30
+ allowedHeaders: ["Authorization"],
31
+ exposedHeaders: [],
32
+ maxAge: 3000
33
+ }
34
+ },
35
+ [Scope.UniAdmin]: {
36
+ [Environment.Production]: {
37
+ allowedOrigins: ["*.studyportals.eu"],
38
+ allowedHeaders: ["Authorization"],
39
+ allowedMethods: ["GET","POST"],
40
+ exposedHeaders: [],
41
+ maxAge: 3000
42
+
43
+ },
44
+ [Environment.Testing]: {
45
+ allowedOrigins: ["*-uniadmin.prtl.fyi"],
46
+ allowedMethods: ["GET"],
47
+ allowedHeaders: ["*"],
48
+ exposedHeaders: [],
49
+ maxAge: 3000
50
+ }
51
+ }
52
+ };
@@ -0,0 +1,32 @@
1
+ import { AllowedDomainRegExList } from './allowed-domain-reg-ex-list';
2
+ import { AllowedExplicitDomainsRules, CorsRule } from './allowed-explicit-domains-rules';
3
+ import { Environment } from './enums/environment';
4
+ import { Scope } from './enums/scope';
5
+
6
+ export class CORS {
7
+
8
+ private regExes: RegExp[];
9
+ private s3Rules: CorsRule[]
10
+
11
+ constructor(scope: Scope, environment: Environment = Environment.Production) {
12
+ this.regExes = AllowedDomainRegExList[scope][environment];
13
+ this.s3Rules = [ AllowedExplicitDomainsRules[scope][environment] ] ;
14
+ }
15
+
16
+ /**
17
+ * isValidDomain
18
+ */
19
+ public async isValidDomain(domain: string): Promise<boolean> {
20
+ const hasMatch = this.regExes.filter((regex) => domain.match(regex) !== null);
21
+ return (hasMatch.length !== 0);
22
+
23
+ }
24
+
25
+ /**
26
+ * getIamRules
27
+ */
28
+ public getS3Rules(): CorsRule[] {
29
+ return this.s3Rules;
30
+ }
31
+
32
+ }
@@ -0,0 +1,4 @@
1
+ export enum Environment {
2
+ Production,
3
+ Testing,
4
+ }
@@ -1,2 +1,2 @@
1
- export * from './environment';
2
- export * from './scope';
1
+ export * from './environment';
2
+ export * from './scope';
@@ -0,0 +1,4 @@
1
+ export enum Scope {
2
+ Student,
3
+ UniAdmin
4
+ }
@@ -1,3 +1,3 @@
1
- export * from './cors';
2
- export * from './allowed-domain-reg-ex-list';
3
- export * from './enums';
1
+ export * from './cors';
2
+ export * from './allowed-domain-reg-ex-list';
3
+ export * from './enums';
@@ -0,0 +1,21 @@
1
+ import { NextFunction, Request, RequestHandler, Response } from 'express';
2
+ import { CORS } from '../domain';
3
+ import { Environment, Scope } from '../domain/enums';
4
+
5
+ export class ExpressMiddleware {
6
+
7
+ public static getMiddleWare(scope: Scope, environment: Environment = Environment.Production): RequestHandler {
8
+ const cors = new CORS(scope, environment);
9
+
10
+ return async (req: Request, res: Response, next: NextFunction) => {
11
+ const origin = req.get('origin') || '';
12
+
13
+ if (await cors.isValidDomain(origin)) {
14
+ res.header('Access-Control-Allow-Origin', origin);
15
+ }
16
+
17
+ next();
18
+ };
19
+
20
+ }
21
+ }
@@ -1,2 +1,3 @@
1
- export * from './options';
2
- export * from './express-middleware';
1
+ export * from './options';
2
+ export * from './express-middleware';
3
+ export * from './s3-rules';
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ import {CORS} from '../domain';
4
+ import { Environment, Scope } from '../domain/enums';
5
+
6
+ export class OptionsLambda {
7
+ private cors: CORS;
8
+
9
+ constructor(scope: Scope, environment: Environment = Environment.Production) {
10
+ this.cors = new CORS(scope, environment);
11
+ }
12
+
13
+ getLowercasedHeaders(event: any): any {
14
+ const headers = {};
15
+
16
+ Object.keys(event.headers).forEach((key) => {
17
+ headers[key.toLowerCase()] = event.headers[key];
18
+ });
19
+
20
+ return headers;
21
+ }
22
+
23
+ getOrigin(event: any): string {
24
+ const headers = this.getLowercasedHeaders(event);
25
+ return headers['origin'];
26
+ }
27
+
28
+ getHandler(): (event: any) => Promise<any> {
29
+ return async (event) => {
30
+
31
+ const origin = this.getOrigin(event);
32
+ const headers = {
33
+ 'Cache-Control': 'max-age=600, s-maxage=600, proxy-revalidate',
34
+ };
35
+
36
+ if (await this.cors.isValidDomain(origin)) {
37
+ headers['Access-Control-Allow-Origin'] = origin;
38
+ headers['Access-Control-Allow-Headers'] = 'authorization, x-api-key, content-type';
39
+ headers['Access-Control-Allow-Methods'] = 'GET, POST, DELETE, PATCH, HEAD, PUT, OPTIONS';
40
+ headers['Access-Control-Max-Age'] = '600';
41
+ }
42
+
43
+ return {
44
+ statusCode: 200,
45
+ headers,
46
+ };
47
+ };
48
+ }
49
+
50
+ }
@@ -0,0 +1,12 @@
1
+ import { NextFunction, Request, RequestHandler, Response } from 'express';
2
+ import { CORS } from '../domain';
3
+ import { AllowedExplicitDomainsRulesType, CorsRule } from '../domain/allowed-explicit-domains-rules';
4
+ import { Environment, Scope } from '../domain/enums';
5
+
6
+ export class S3Rules {
7
+
8
+ public static generate(scope: Scope, environment: Environment = Environment.Production): CorsRule[] {
9
+ const cors = new CORS(scope, environment);
10
+ return cors.getS3Rules();
11
+ }
12
+ }
@@ -0,0 +1,92 @@
1
+ import { assert, expect } from "chai";
2
+ import { suite, test } from "mocha-typescript";
3
+ import * as Moq from "typemoq";
4
+
5
+ import { CORS } from "../../src/domain";
6
+ import { Environment } from "../../src/domain/enums/environment";
7
+ import { Scope } from "../../src/domain/enums/scope";
8
+
9
+ @suite()
10
+ class CORSS3RulesTest {
11
+ private testInstanceMock: Moq.IMock<CORS>;
12
+
13
+ private get testInstance(): CORS {
14
+ return this.testInstanceMock.object;
15
+ }
16
+
17
+ public before(): void {
18
+
19
+ const cors = new CORS(Scope.Student, Environment.Production);
20
+ this.testInstanceMock = Moq.Mock.ofInstance(cors);
21
+ this.testInstanceMock.callBase = true;
22
+ }
23
+
24
+ @test
25
+ public async getS3Rules_Should_Return_All_Required_Properties(): Promise<void> {
26
+ // given
27
+ const expectedProperties = ['allowedOrigins','allowedHeaders','allowedMethods','exposedHeaders','maxAge']
28
+
29
+ // when
30
+ const allDefaultRules = await this.testInstance.getS3Rules();
31
+
32
+ //then
33
+ expectedProperties.forEach((key) => {
34
+ allDefaultRules.every( rule => expect(rule).to.have.property(key));
35
+ });
36
+
37
+ }
38
+
39
+ @test
40
+ public async getS3Rules_Should_Return_Correct_Default_Domains(): Promise<void> {
41
+ // given
42
+ const expectedDomains = ['*.mastersportal.com', '*.bachelorsportal.com', '*.phdportal.com'];
43
+
44
+ // when
45
+ const allDefaultRules = await this.testInstance.getS3Rules();
46
+
47
+ // then
48
+ allDefaultRules.some( rule => expectedDomains.every( domain => rule.allowedOrigins.includes(domain) ) )
49
+ }
50
+
51
+ @test
52
+ public async getS3Rules_Should_Return_Correct_Allowed_Headers(): Promise<void> {
53
+ // given
54
+ const expectedHeaders = ['Authorization'];
55
+
56
+ // when
57
+ const allDefaultRules = await this.testInstance.getS3Rules();
58
+
59
+ // then
60
+ allDefaultRules.some( rule => expectedHeaders.every( header => !rule.allowedHeaders.includes(header) ) )
61
+ }
62
+
63
+ @test
64
+ public async getS3Rules_Non_Production_Should_Not_Return_Production_Domains(): Promise<void> {
65
+ // given
66
+ const expectedDomains = ['*.mastersportal.fyi', '*.bachelorsportal.fyi', '*.phdportal.fyi'];
67
+
68
+ // when
69
+ const allDefaultRules = await this.testInstance.getS3Rules();
70
+
71
+ // then
72
+ allDefaultRules.some( rule => expectedDomains.every( domain => !rule.allowedOrigins.includes(domain) ) )
73
+
74
+ }
75
+
76
+ @test
77
+ public async getS3Rules_Should_Return_Correct_Testing_Domains(): Promise<void> {
78
+
79
+ const cors = new CORS(Scope.Student, Environment.Testing);
80
+ this.testInstanceMock = Moq.Mock.ofInstance(cors);
81
+ this.testInstanceMock.callBase = true;
82
+
83
+ // given
84
+ const expectedDomains = ['*'];
85
+
86
+ // when
87
+ const allDefaultRules = await this.testInstance.getS3Rules();
88
+
89
+ // then
90
+ allDefaultRules.some( rule => expectedDomains.every( domain => rule.allowedOrigins.includes(domain) ) )
91
+ }
92
+ }
@@ -0,0 +1,132 @@
1
+ import { assert } from "chai";
2
+ import { suite, test } from "mocha-typescript";
3
+ import * as Moq from "typemoq";
4
+
5
+ import { CORS } from "../../src/domain";
6
+ import { Environment } from "../../src/domain/enums/environment";
7
+ import { Scope } from "../../src/domain/enums/scope";
8
+
9
+
10
+ @suite()
11
+ class CORSValidDomainTest {
12
+ private testInstanceMock: Moq.IMock<CORS>;
13
+
14
+ private get testInstance(): CORS {
15
+ return this.testInstanceMock.object;
16
+ }
17
+
18
+ public before(): void {
19
+
20
+ const cors = new CORS(Scope.Student, Environment.Production);
21
+ this.testInstanceMock = Moq.Mock.ofInstance(cors);
22
+ this.testInstanceMock.callBase = true;
23
+ }
24
+
25
+ @test
26
+ public async isValidDomain_Should_Return_True_When_DomainIs_Mastersportal(): Promise<void> {
27
+ // given
28
+ const providedDomain = 'https://www.mastersportal.com';
29
+
30
+ // when
31
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
32
+
33
+ // then
34
+ assert.isTrue(validDomain);
35
+ }
36
+
37
+ @test
38
+ public async isValidDomain_Should_Return_True_When_DomainIs_Mastersportal_With_HTTP(): Promise<void> {
39
+ // given
40
+ const providedDomain = 'http://www.mastersportal.com';
41
+
42
+ // when
43
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
44
+
45
+ // then
46
+ assert.isTrue(validDomain);
47
+ }
48
+
49
+ @test
50
+ public async isValidDomain_Should_Return_True_When_DomainIs_Bachelorsportal(): Promise<void> {
51
+ // given
52
+ const providedDomain = 'https://www.bachelorsportal.com';
53
+
54
+ // when
55
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
56
+
57
+ // then
58
+ assert.isTrue(validDomain);
59
+ }
60
+
61
+ @test
62
+ public async isValidDomain_Should_Return_True_When_DomainIs_PhDportal(): Promise<void> {
63
+ // given
64
+ const providedDomain = 'https://www.phdportal.com';
65
+
66
+ // when
67
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
68
+
69
+ // then
70
+ assert.isTrue(validDomain);
71
+ }
72
+
73
+ @test
74
+ public async isValidDomain_Should_Return_True_When_DomainIs_PhDportalRC(): Promise<void> {
75
+ // given
76
+ const providedDomain = 'https://1554112850.rc.phdportal.fyi';
77
+
78
+ // when
79
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
80
+
81
+ // then
82
+ assert.isTrue(validDomain);
83
+ }
84
+
85
+ @test
86
+ public async isValidDomain_Should_Return_False_When_DomainIs_BabyShower(): Promise<void> {
87
+ // given
88
+ const providedDomain = 'https://www.babyshower.com';
89
+
90
+ // when
91
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
92
+
93
+ // then
94
+ assert.isFalse(validDomain);
95
+ }
96
+
97
+ @test
98
+ public async isValidDomain_Should_Return_False_When_DomainIs_Masterportal(): Promise<void> {
99
+ // given
100
+ const providedDomain = 'https://www.masterportal.com';
101
+
102
+ // when
103
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
104
+
105
+ // then
106
+ assert.isFalse(validDomain);
107
+ }
108
+
109
+ @test
110
+ public async isValidDomain_Should_Return_False_When_DomainIs_Mastersportal_With_SPDY(): Promise<void> {
111
+ // given
112
+ const providedDomain = 'spdy://www.mastersportal.com';
113
+
114
+ // when
115
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
116
+
117
+ // then
118
+ assert.isFalse(validDomain);
119
+ }
120
+
121
+ @test
122
+ public async isValidDomain_Should_Return_True_When_DomainIs_searchPageMicroFrontend(): Promise<void> {
123
+ // given
124
+ const providedDomain = 'https://search-page.prtl.co';
125
+
126
+ // when
127
+ const validDomain = await this.testInstance.isValidDomain(providedDomain);
128
+
129
+ // then
130
+ assert.isTrue(validDomain);
131
+ }
132
+ }
@@ -0,0 +1,3 @@
1
+ --require ts-node/register
2
+ --watch-extensions ts
3
+ -c
package/tsconfig.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "bin",
4
+ "declaration": true,
5
+ "moduleResolution": "node",
6
+ "alwaysStrict": true,
7
+ "strictNullChecks": true,
8
+ "allowJs": false,
9
+ "allowUnreachableCode": false,
10
+ "allowUnusedLabels": false,
11
+ "noUnusedLocals": false,
12
+ "noUnusedParameters": false,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "noImplicitAny": false,
15
+ "noImplicitReturns": true,
16
+ "noImplicitThis": true,
17
+ "target": "ES6",
18
+ "module": "commonjs",
19
+ "experimentalDecorators": true,
20
+ "esModuleInterop": true,
21
+ "lib": [
22
+ "es2017",
23
+ "dom",
24
+ "es5",
25
+ "es6"
26
+ ],
27
+ "types": [
28
+ "node"
29
+ ]
30
+ },
31
+ "typeRoots": [
32
+ "node_modules/@types/"
33
+ ],
34
+ "exclude": [
35
+ "node_modules",
36
+ "bin"
37
+ ]
38
+ }
package/tslint.json ADDED
@@ -0,0 +1,132 @@
1
+ {
2
+ "extends": [
3
+ "tslint:latest"
4
+ ],
5
+ "rules": {
6
+ "ordered-imports": true,
7
+ "arrow-return-shorthand": true,
8
+ "callable-types": true,
9
+ "class-name": true,
10
+ "comment-format": [
11
+ true,
12
+ "check-space"
13
+ ],
14
+ "curly": true,
15
+ "eofline": true,
16
+ "forin": true,
17
+ "import-spacing": false,
18
+ "indent": [
19
+ true,
20
+ "tabs"
21
+ ],
22
+ "interface-over-type-literal": true,
23
+ "label-position": true,
24
+ "max-line-length": [
25
+ true,
26
+ 200
27
+ ],
28
+ "member-access": false,
29
+ "member-ordering": [
30
+ true,
31
+ {
32
+ "order": [
33
+ "static-field",
34
+ "instance-field",
35
+ "instance-method",
36
+ "static-method"
37
+ ]
38
+ }
39
+ ],
40
+ "no-arg": true,
41
+ "no-console": [
42
+ true,
43
+ "debug",
44
+ "info",
45
+ "time",
46
+ "timeEnd",
47
+ "trace"
48
+ ],
49
+ "no-construct": true,
50
+ "no-debugger": true,
51
+ "no-duplicate-super": true,
52
+ "no-empty": false,
53
+ "no-empty-interface": true,
54
+ "no-eval": true,
55
+ "no-inferrable-types": [
56
+ true,
57
+ "ignore-params"
58
+ ],
59
+ "no-misused-new": true,
60
+ "no-non-null-assertion": true,
61
+ "no-shadowed-variable": true,
62
+ "no-string-literal": false,
63
+ "no-string-throw": true,
64
+ "no-switch-case-fall-through": true,
65
+ "no-trailing-whitespace": true,
66
+ "no-unnecessary-initializer": true,
67
+ "no-unused-expression": true,
68
+ "no-use-before-declare": true,
69
+ "no-var-keyword": true,
70
+ "object-literal-sort-keys": false,
71
+ "one-line": [
72
+ true,
73
+ "check-open-brace",
74
+ "check-whitespace"
75
+ ],
76
+ "prefer-const": false,
77
+ "radix": true,
78
+ "quotemark": [
79
+ true,
80
+ "single"
81
+ ],
82
+ "semicolon": [
83
+ true,
84
+ "always"
85
+ ],
86
+ "triple-equals": [
87
+ true,
88
+ "allow-null-check"
89
+ ],
90
+ "typedef": [
91
+ true,
92
+ "call-signature",
93
+ "parameter",
94
+ "member-variable-declaration",
95
+ "property-declaration"
96
+ ],
97
+ "typedef-whitespace": [
98
+ true,
99
+ {
100
+ "call-signature": "nospace",
101
+ "index-signature": "nospace",
102
+ "parameter": "nospace",
103
+ "property-declaration": "nospace",
104
+ "variable-declaration": "nospace"
105
+ }
106
+ ],
107
+ "no-implicit-dependencies": [
108
+ false,
109
+ [
110
+ "mocha-typescript",
111
+ "chai",
112
+ "typemoq"
113
+ ]
114
+ ],
115
+ "unified-signatures": true,
116
+ "variable-name": false,
117
+ "whitespace": [
118
+ true,
119
+ "check-branch",
120
+ "check-decl",
121
+ "check-operator",
122
+ "check-separator",
123
+ "check-type",
124
+ "check-typecast"
125
+ ]
126
+ },
127
+ "linterOptions": {
128
+ "exclude": [
129
+ "node_modules/**"
130
+ ]
131
+ }
132
+ }
package/index.js DELETED
@@ -1,7 +0,0 @@
1
- "use strict";
2
- function __export(m) {
3
- for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4
- }
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- __export(require("./src/domain"));
7
- __export(require("./src/implementations"));
@@ -1,10 +0,0 @@
1
- export declare const AllowedDomainRegExList: {
2
- 0: {
3
- 0: RegExp[];
4
- 1: RegExp[];
5
- };
6
- 1: {
7
- 0: RegExp[];
8
- 1: RegExp[];
9
- };
10
- };
@@ -1,35 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const environment_1 = require("./enums/environment");
4
- const scope_1 = require("./enums/scope");
5
- exports.AllowedDomainRegExList = {
6
- [scope_1.Scope.Student]: {
7
- [environment_1.Environment.Production]: [
8
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.mastersportal\.(com|fyi)(?::3030)?$/,
9
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.bachelorsportal\.(com|fyi)(?::3030)?$/,
10
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.phdportal\.(com|fyi)(?::3030)?$/,
11
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.preparationcoursesportal\.(com|fyi)(?::3030)?$/,
12
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.distancelearningportal\.(com|fyi)(?::3030)?$/,
13
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.shortcoursesportal\.(com|fyi)(?::3030)?$/,
14
- /^https?:\/\/studentemailing\.prtl\.co$/,
15
- /^https?:\/\/scholarship\.mastersportal\.com$/,
16
- /^https?:\/\/search-page\.prtl\.co$/,
17
- ],
18
- [environment_1.Environment.Testing]: [
19
- /(.*?)/,
20
- ],
21
- },
22
- [scope_1.Scope.Currency]: {
23
- [environment_1.Environment.Production]: [
24
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.mastersportal\.(com|fyi)(?::3030)?$/,
25
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.bachelorsportal\.(com|fyi)(?::3030)?$/,
26
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.phdportal\.(com|fyi)(?::3030)?$/,
27
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.preparationcoursesportal\.(com|fyi)(?::3030)?$/,
28
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.distancelearningportal\.(com|fyi)(?::3030)?$/,
29
- /^https?:\/\/(www(?:\.dev)?|cypress\.rc|[0-9]{4,16}(?:-bob)?\.rc)\.shortcoursesportal\.(com|fyi)(?::3030)?$/,
30
- ],
31
- [environment_1.Environment.Testing]: [
32
- /(.*?)/,
33
- ],
34
- }
35
- };
@@ -1,10 +0,0 @@
1
- import { Environment } from './enums/environment';
2
- import { Scope } from './enums/scope';
3
- export declare class CORS {
4
- private regExes;
5
- constructor(scope: Scope, environment?: Environment);
6
- /**
7
- * isValidDomain
8
- */
9
- isValidDomain(domain: string): Promise<boolean>;
10
- }
@@ -1,28 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- const allowed_domain_reg_ex_list_1 = require("./allowed-domain-reg-ex-list");
13
- const environment_1 = require("./enums/environment");
14
- class CORS {
15
- constructor(scope, environment = environment_1.Environment.Production) {
16
- this.regExes = allowed_domain_reg_ex_list_1.AllowedDomainRegExList[scope][environment];
17
- }
18
- /**
19
- * isValidDomain
20
- */
21
- isValidDomain(domain) {
22
- return __awaiter(this, void 0, void 0, function* () {
23
- const hasMatch = this.regExes.filter((regex) => domain.match(regex) !== null);
24
- return (hasMatch.length !== 0);
25
- });
26
- }
27
- }
28
- exports.CORS = CORS;
@@ -1,4 +0,0 @@
1
- export declare enum Environment {
2
- Production = 0,
3
- Testing = 1
4
- }
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- var Environment;
4
- (function (Environment) {
5
- Environment[Environment["Production"] = 0] = "Production";
6
- Environment[Environment["Testing"] = 1] = "Testing";
7
- })(Environment = exports.Environment || (exports.Environment = {}));
@@ -1,7 +0,0 @@
1
- "use strict";
2
- function __export(m) {
3
- for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4
- }
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- __export(require("./environment"));
7
- __export(require("./scope"));
@@ -1,4 +0,0 @@
1
- export declare enum Scope {
2
- Student = 0,
3
- Currency = 1
4
- }
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- var Scope;
4
- (function (Scope) {
5
- Scope[Scope["Student"] = 0] = "Student";
6
- Scope[Scope["Currency"] = 1] = "Currency";
7
- })(Scope = exports.Scope || (exports.Scope = {}));
@@ -1,8 +0,0 @@
1
- "use strict";
2
- function __export(m) {
3
- for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4
- }
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- __export(require("./cors"));
7
- __export(require("./allowed-domain-reg-ex-list"));
8
- __export(require("./enums"));
@@ -1,5 +0,0 @@
1
- import { RequestHandler } from 'express';
2
- import { Environment, Scope } from '../domain/enums';
3
- export declare class ExpressMiddleware {
4
- static getMiddleWare(scope: Scope, environment?: Environment): RequestHandler;
5
- }
@@ -1,26 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- const domain_1 = require("../domain");
13
- const enums_1 = require("../domain/enums");
14
- class ExpressMiddleware {
15
- static getMiddleWare(scope, environment = enums_1.Environment.Production) {
16
- const cors = new domain_1.CORS(scope, environment);
17
- return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
18
- const origin = req.get('origin') || '';
19
- if (yield cors.isValidDomain(origin)) {
20
- res.header('Access-Control-Allow-Origin', origin);
21
- }
22
- next();
23
- });
24
- }
25
- }
26
- exports.ExpressMiddleware = ExpressMiddleware;
@@ -1,7 +0,0 @@
1
- "use strict";
2
- function __export(m) {
3
- for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
4
- }
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- __export(require("./options"));
7
- __export(require("./express-middleware"));
@@ -1,8 +0,0 @@
1
- import { Environment, Scope } from '../domain/enums';
2
- export declare class OptionsLambda {
3
- private cors;
4
- constructor(scope: Scope, environment?: Environment);
5
- getLowercasedHeaders(event: any): any;
6
- getOrigin(event: any): string;
7
- getHandler(): (event: any) => Promise<any>;
8
- }
@@ -1,48 +0,0 @@
1
- 'use strict';
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- const domain_1 = require("../domain");
13
- const enums_1 = require("../domain/enums");
14
- class OptionsLambda {
15
- constructor(scope, environment = enums_1.Environment.Production) {
16
- this.cors = new domain_1.CORS(scope, environment);
17
- }
18
- getLowercasedHeaders(event) {
19
- const headers = {};
20
- Object.keys(event.headers).forEach((key) => {
21
- headers[key.toLowerCase()] = event.headers[key];
22
- });
23
- return headers;
24
- }
25
- getOrigin(event) {
26
- const headers = this.getLowercasedHeaders(event);
27
- return headers['origin'];
28
- }
29
- getHandler() {
30
- return (event) => __awaiter(this, void 0, void 0, function* () {
31
- const origin = this.getOrigin(event);
32
- const headers = {
33
- 'Cache-Control': 'max-age=600, s-maxage=600, proxy-revalidate',
34
- };
35
- if (yield this.cors.isValidDomain(origin)) {
36
- headers['Access-Control-Allow-Origin'] = origin;
37
- headers['Access-Control-Allow-Headers'] = 'authorization, x-api-key, content-type';
38
- headers['Access-Control-Allow-Methods'] = 'GET, POST, DELETE, PATCH, HEAD, PUT, OPTIONS';
39
- headers['Access-Control-Max-Age'] = '600';
40
- }
41
- return {
42
- statusCode: 200,
43
- headers,
44
- };
45
- });
46
- }
47
- }
48
- exports.OptionsLambda = OptionsLambda;