├── .github ├── dependabot.yml └── workflows │ ├── backend-cd-gcp.yml │ ├── backend-ci.yml │ ├── check-linked-issues.yml │ ├── db-manager-cd-gcp.yml │ ├── db-manager-ci.yml │ ├── frontend-cd-gcp.yml │ ├── frontend-ci.yml │ ├── grype.yml │ ├── lib-ci.yml │ └── storybook-cd-gcp.yml ├── .gitignore ├── .gitlab-ci.yml ├── .husky ├── .gitignore ├── pre-commit └── pre-push ├── .nvmrc ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin ├── cleanEnv.js └── createEnv.js ├── docker-compose.yml ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── contributing │ ├── README.md │ └── _sidebar.md ├── developers │ ├── README.md │ ├── _sidebar.md │ └── packages │ │ ├── titus-backend-typescript │ │ ├── README.md │ │ └── _sidebar.md │ │ ├── titus-backend │ │ ├── README.md │ │ └── _sidebar.md │ │ ├── titus-db-manager │ │ ├── README.md │ │ └── _sidebar.md │ │ └── titus-frontend │ │ ├── README.md │ │ └── _sidebar.md ├── devops │ ├── README.md │ ├── _sidebar.md │ ├── aws │ │ ├── README.md │ │ └── infra.png │ ├── azure │ │ ├── README.md │ │ └── _sidebar.md │ ├── gcp │ │ ├── README.md │ │ └── _sidebar.md │ └── k8s │ │ ├── README.md │ │ └── _sidebar.md ├── img │ ├── Accel_Logo_Titus.svg │ ├── NF Logo.svg │ ├── N_Logo.svg │ ├── Path.svg │ ├── circle-add-project.png │ ├── circle-env-variables.png │ ├── circle-tagged.png │ ├── circle-untagged.png │ ├── favicon.ico │ ├── favicon.png │ ├── logo-neg.svg │ ├── logo-pos.svg │ ├── old-titus-home-page.png │ ├── old-titus-login.png │ ├── titus-ci-pipeline.svg │ ├── titus-deployment-workflow.svg │ ├── titus-feature-overview.svg │ ├── titus-home-page.png │ ├── titus-login.png │ ├── titus-overview.svg │ ├── titus-pipeline.svg │ └── titus-quick-start-quote.svg ├── index.html ├── quick-start │ ├── README.md │ └── _sidebar.md └── styles.css ├── infra ├── aws │ ├── terraform-eks │ │ ├── aws.tfplan │ │ ├── backend.tf │ │ ├── cognito.tf │ │ ├── config.auto.tfvars.example │ │ ├── database.tf │ │ ├── eks.tf │ │ ├── iam-policy.json │ │ ├── main.tf │ │ ├── network.tf │ │ ├── outputs.tf │ │ ├── storage.tf │ │ └── variables.tf │ └── terraform │ │ ├── .gitignore │ │ ├── api_gw.tf │ │ ├── aws.tfplan │ │ ├── backend.tf │ │ ├── cdn.tf │ │ ├── cognito.tf │ │ ├── config.auto.tfvars.example │ │ ├── database.tf │ │ ├── main.tf │ │ ├── network.tf │ │ ├── outputs.tf │ │ ├── static-content.tf │ │ ├── static-content │ │ ├── 404.html │ │ ├── 50x.html │ │ ├── css │ │ │ └── styles.css │ │ └── index.html │ │ ├── storage.tf │ │ └── variables.tf ├── azure │ └── terraform │ │ ├── .gitignore │ │ ├── artifact-registry.tf │ │ ├── backend.tf │ │ ├── database.tf │ │ ├── db-manager.tf │ │ ├── frontend.tf │ │ ├── keyvault.tf │ │ ├── main.tf │ │ ├── network.tf │ │ ├── outputs.tf │ │ ├── sample-input.tfvars │ │ └── variables.tf ├── gcp │ └── terraform │ │ ├── .gitignore │ │ ├── .terraform.lock.hcl │ │ ├── artifact-registry.tf │ │ ├── backend.tf │ │ ├── config.auto.tfvars.sample │ │ ├── database.tf │ │ ├── db-manager.tf │ │ ├── frontend.tf │ │ ├── github-actions-service-account.tf │ │ ├── iam.tf │ │ ├── main.tf │ │ ├── storybook.tf │ │ └── variables.tf └── k8s │ └── helm │ ├── .helmignore │ ├── Chart.lock │ ├── Chart.yaml │ ├── charts │ ├── common-1.7.1.tgz │ └── postgresql-10.8.0.tgz │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── db-migrate-job.yaml │ ├── deployment.yaml │ ├── hpa.yaml │ ├── ingress-backend.yaml │ ├── ingress-frontend.yaml │ ├── rbac.yaml │ ├── service-backend.yaml │ ├── service-frontend.yaml │ ├── serviceaccount.yaml │ ├── spc.yaml │ └── tests │ │ └── test-connection.yaml │ └── values.yaml ├── lerna.json ├── lib ├── .jest.json ├── package-lock.json ├── package.json ├── tools.js └── tools.test.js ├── package-lock.json ├── package.json └── packages ├── titus-backend-typescript ├── .dockerignore ├── .env.sample ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .gitlab-ci.yml ├── .nodemonrc ├── .prettierrc ├── Dockerfile ├── README.md ├── azure-pipeline.yml ├── backend-descriptor.yaml ├── jest.config.ts ├── package-lock.json ├── package.json ├── src │ ├── config │ │ ├── auth-routes.ts │ │ ├── authz │ │ │ ├── casbin_model.conf │ │ │ └── casbin_policy.csv │ │ ├── index.ts │ │ └── swagger.ts │ ├── index.ts │ ├── plugins │ │ ├── auth │ │ │ ├── auth0 │ │ │ │ ├── auth0-routes.ts │ │ │ │ ├── auth0.test.ts │ │ │ │ └── index.ts │ │ │ ├── azure-ad │ │ │ │ ├── azure-ad.test.js │ │ │ │ └── index.ts │ │ │ ├── cognito │ │ │ │ ├── cognito-routes.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── authz │ │ │ └── index.ts │ │ ├── healthcheck │ │ │ ├── healthcheck.test.ts │ │ │ └── index.ts │ │ ├── pg │ │ │ ├── index.ts │ │ │ └── pg.test.ts │ │ └── secrets-manager │ │ │ └── index.ts │ ├── routes │ │ ├── authzcheck │ │ │ └── authzcheck.ts │ │ ├── log │ │ │ ├── index.ts │ │ │ └── log.test.ts │ │ └── user │ │ │ ├── index.ts │ │ │ └── user.test.ts │ ├── server.test.ts │ └── server.ts └── tsconfig.json ├── titus-backend ├── .dockerignore ├── .env.sample ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .gitlab-ci.yml ├── .nodemonrc ├── .prettierrc ├── Dockerfile ├── README.md ├── azure-pipeline.yml ├── backend-descriptor.yaml ├── index.js ├── jest.config.js ├── lib │ ├── config │ │ ├── auth-routes.js │ │ ├── authz │ │ │ ├── casbin_model.conf │ │ │ └── casbin_policy.csv │ │ ├── index.js │ │ └── swagger.js │ ├── plugins │ │ ├── auth │ │ │ ├── auth0 │ │ │ │ ├── auth0-routes.js │ │ │ │ ├── auth0.test.js │ │ │ │ └── index.js │ │ │ ├── azure-ad │ │ │ │ ├── azure-ad.test.js │ │ │ │ └── index.js │ │ │ ├── cognito │ │ │ │ ├── cognito-routes.js │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── authz │ │ │ └── index.js │ │ ├── healthcheck │ │ │ ├── healthcheck.test.js │ │ │ └── index.js │ │ ├── pg │ │ │ ├── index.js │ │ │ └── pg.test.js │ │ └── secrets-manager │ │ │ └── index.js │ ├── routes │ │ ├── authzcheck │ │ │ └── authzcheck.js │ │ ├── log │ │ │ ├── index.js │ │ │ └── log.test.js │ │ └── user │ │ │ ├── index.js │ │ │ └── user.test.js │ ├── server.js │ └── server.test.js ├── package-lock.json └── package.json ├── titus-db-manager ├── .dockerignore ├── .env.sample ├── .eslintignore ├── .eslintrc ├── .gitlab-ci.yml ├── .jest.json ├── .prettierrc ├── Dockerfile ├── azure-pipeline.yml ├── index.js ├── lambda.js ├── lib │ ├── build-server.js │ ├── config.js │ ├── config.test.js │ ├── index.js │ ├── plugin.js │ ├── routes.js │ ├── secrets-manager.js │ ├── server.integration.test.js │ └── server.test.js ├── migrate │ ├── index.js │ ├── index.test.js │ └── migrations │ │ ├── 001.do.sql │ │ └── 002.do.sql ├── migration-descriptor.yaml ├── migration-start.js ├── package-lock.json ├── package.json ├── seed │ ├── index.js │ └── index.test.js └── truncate │ ├── index.js │ └── index.test.js ├── titus-frontend ├── .babelrc ├── .dockerignore ├── .env.sample ├── .env.test ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .gitlab-ci.yml ├── .prettierrc ├── .storybook │ ├── decorator-components.js │ ├── docker │ │ ├── Dockerfile │ │ ├── nginx.conf │ │ └── nginx.htpasswd │ ├── main.js │ ├── new-theme.js │ └── preview.js ├── Dockerfile ├── README.md ├── azure-pipeline.yml ├── bin │ └── validateEnv.js ├── docker │ └── nginx.conf ├── jsconfig.json ├── package-lock.json ├── package.json ├── public │ ├── icons │ │ ├── favicon.png │ │ ├── icons-192.png │ │ ├── icons-512.png │ │ ├── maskable-icon.png │ │ └── non-transparent-icon-512.png │ ├── index.html │ ├── manifest.webmanifest │ └── robots.txt └── src │ ├── app.js │ ├── components │ ├── auth-providers │ │ ├── auth0 │ │ │ ├── authentication.test.js │ │ │ └── index.js │ │ ├── aws-amplify │ │ │ ├── authentication.test.js │ │ │ └── index.js │ │ ├── azure-ad │ │ │ ├── adalConfig.js │ │ │ ├── adalConfig.test.js │ │ │ ├── authentication.test.js │ │ │ └── index.js │ │ ├── in-memory │ │ │ ├── authentication.test.js │ │ │ └── index.js │ │ ├── titus-backend │ │ │ ├── authentication.test.js │ │ │ └── index.js │ │ └── utils │ │ │ ├── index.js │ │ │ └── schemas.js │ ├── authentication │ │ ├── authentication-context.js │ │ └── index.js │ ├── authz-check │ │ └── index.js │ ├── dashboard │ │ ├── dashboard.mdx │ │ ├── dashboard.story.js │ │ └── index.js │ ├── error-boundary │ │ └── index.js │ ├── loading │ │ ├── index.js │ │ ├── loading.mdx │ │ └── loading.story.js │ ├── login-form │ │ ├── components │ │ │ └── login-form-inputs │ │ │ │ └── index.js │ │ ├── index.js │ │ ├── login-form.mdx │ │ └── login-form.story.js │ ├── logo │ │ ├── Accel_Logo_Titus.svg │ │ ├── index.js │ │ ├── logo.mdx │ │ └── logo.story.js │ ├── remote-aws-config │ │ └── index.js │ ├── user-info │ │ └── index.js │ └── user-list │ │ └── index.js │ ├── index.js │ ├── lib │ ├── config.js │ ├── constants.js │ ├── i18n.js │ ├── locale │ │ ├── en.json │ │ ├── index.js │ │ ├── pt.json │ │ └── ro.json │ ├── pino.js │ ├── pino.test.js │ ├── router.js │ └── test-utils.js │ ├── pages │ ├── dashboard │ │ ├── index.js │ │ └── index.test.js │ └── login │ │ ├── index.js │ │ └── index.test.js │ ├── serviceWorker.js │ ├── setupTests.js │ ├── styles.css │ ├── validateEnv.js │ └── validateEnv.test.js └── titus-infra-aws-mira ├── .gitignore ├── .mira.snapshot ├── README.md ├── cdk ├── api-gw-alb │ ├── index.test.ts │ └── index.ts ├── api-gw-nlb │ ├── index.test.ts │ └── index.ts ├── core │ ├── authentication.test.ts │ ├── authentication.ts │ ├── database.test.ts │ ├── database.ts │ ├── index.test.ts │ ├── index.ts │ ├── migration.test.ts │ ├── migration.ts │ ├── vpc.test.ts │ └── vpc.ts ├── ecs-alb │ ├── index.test.ts │ └── index.ts ├── ecs-nlb │ ├── index.test.ts │ └── index.ts ├── index.test.ts ├── index.ts └── web-app │ ├── index.test.ts │ └── index.ts ├── config ├── default.sample.json └── test.json ├── infra.png ├── lambda ├── get-config.test.ts └── get-config.ts ├── package-lock.json ├── package.json ├── pipeline ├── buildspec.sample.yaml └── permissions.ts ├── scripts ├── lib │ ├── check-config-exists.js │ ├── validate-certificate.js │ ├── validate-config.js │ └── validate-docker-container.js └── validateDeploy.js └── tsconfig.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | ignore: 8 | - dependency-name: 'aws-cdk' 9 | - dependency-name: '@aws-cdk/*' 10 | - dependency-name: 'chalk' 11 | - package-ecosystem: github-actions 12 | directory: '/' 13 | schedule: 14 | interval: daily 15 | -------------------------------------------------------------------------------- /.github/workflows/backend-ci.yml: -------------------------------------------------------------------------------- 1 | name: Backend CI 2 | on: 3 | push: 4 | paths: 5 | - '.github/workflows/backend-ci.yml' 6 | - 'packages/titus-backend/**' 7 | pull_request: 8 | paths: 9 | - '.github/workflows/backend-ci.yml' 10 | - 'packages/titus-backend/**' 11 | 12 | jobs: 13 | integration-checks: 14 | name: 'Linting, Testing' 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checking out code 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 1 22 | 23 | - uses: actions/setup-node@v3 24 | with: 25 | node-version-file: '.nvmrc' 26 | 27 | - name: Installing dependencies 28 | working-directory: packages/titus-backend 29 | run: | 30 | npm ci 31 | 32 | - name: Running lint check 33 | working-directory: packages/titus-backend 34 | run: | 35 | npm run lint 36 | 37 | - name: Running tests 38 | working-directory: packages/titus-backend 39 | run: | 40 | npm run create:env 41 | npm run test 42 | 43 | automerge: 44 | needs: integration-checks 45 | runs-on: ubuntu-latest 46 | permissions: 47 | pull-requests: write 48 | contents: write 49 | steps: 50 | - uses: fastify/github-action-merge-dependabot@v3 51 | -------------------------------------------------------------------------------- /.github/workflows/check-linked-issues.yml: -------------------------------------------------------------------------------- 1 | name: Check Linked Issues 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, edited, reopened, synchronize] 6 | 7 | jobs: 8 | check_pull_requests: 9 | runs-on: ubuntu-latest 10 | name: Check linked issues 11 | steps: 12 | - uses: nearform/github-action-check-linked-issues@v1 13 | with: 14 | github-token: ${{ secrets.GITHUB_TOKEN }} 15 | exclude-branches: 'release/**, dependabot/**' 16 | -------------------------------------------------------------------------------- /.github/workflows/lib-ci.yml: -------------------------------------------------------------------------------- 1 | name: Lib CI 2 | on: 3 | push: 4 | paths: 5 | - '.github/workflows/lib-ci.yml' 6 | - 'lib/**' 7 | pull_request: 8 | paths: 9 | - '.github/workflows/lib-ci.yml' 10 | - 'lib/**' 11 | 12 | jobs: 13 | integration-checks: 14 | name: 'Testing' 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checking out code 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 1 22 | 23 | - uses: actions/setup-node@v3 24 | with: 25 | node-version-file: '.nvmrc' 26 | 27 | - name: Installing dependencies 28 | working-directory: lib 29 | run: | 30 | npm ci 31 | 32 | - name: Running tests 33 | working-directory: lib 34 | run: | 35 | npm run test 36 | 37 | automerge: 38 | needs: integration-checks 39 | runs-on: ubuntu-latest 40 | permissions: 41 | pull-requests: write 42 | contents: write 43 | steps: 44 | - uses: fastify/github-action-merge-dependabot@v3 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | coverage 6 | 7 | # misc 8 | .DS_Store 9 | .idea 10 | terraform.tfstate 11 | terraform.tfstate.backup 12 | 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | yarn.lock 18 | 19 | lerna-debug.log 20 | 21 | .vscode 22 | .env 23 | 24 | # infra 25 | 26 | infra/**/.terraform* 27 | infra/**/*.tfvars -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | 2 | image: ubuntu:18.04 3 | 4 | include: 5 | - "packages/titus-backend/.gitlab-ci.yml" 6 | - "packages/titus-frontend/.gitlab-ci.yml" 7 | - "packages/titus-db-manager/.gitlab-ci.yml" -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint:staged && npm run test:related 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint:all 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "semi": false, 4 | "singleQuote": true, 5 | "trailingComma": "none" 6 | } 7 | -------------------------------------------------------------------------------- /bin/cleanEnv.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('../lib/tools').cleanEnv() 4 | -------------------------------------------------------------------------------- /bin/createEnv.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('../lib/tools').createEnv() 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | services: 4 | 5 | titus-db: 6 | container_name: titus-db 7 | image: postgres:13.3-alpine 8 | environment: 9 | - POSTGRES_USER=titus 10 | - POSTGRES_PASSWORD=titus 11 | - POSTGRES_DB=titus 12 | ports: 13 | - 5432:5432 14 | volumes: 15 | - titus-pg-data:/var/lib/postgresql/data 16 | 17 | volumes: 18 | titus-pg-data: 19 | external: false 20 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/.nojekyll -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Overview](?id=Overview) 3 | - [Built for Developers](?id=built-for-developers) 4 | - [No compromise DevOps](?id=no-compromise-devops) 5 | - [You decide the details](?id=you-decide-the-details) 6 | - [Explore Titus](?id=explore-titus) 7 | - [Quick Start Guide](/quick-start/) 8 | - [Developers](/developers/) 9 | - [DevOps](/devops/?id=DevOps) 10 | - [Contribute to Titus](/contributing/) 11 | -------------------------------------------------------------------------------- /docs/contributing/README.md: -------------------------------------------------------------------------------- 1 | # Contribute to Titus 2 | 3 | # Code 4 | Code contributions are accepted back to Titus, please make an issue to discuss your change first. 5 | 6 | # Documentation 7 | We welcome documentation contributions. Our documentation can be viewed live at [https://nf-titus.netlify.app/#/][docs]. 8 | 9 | ## Run the Documentation Locally 10 | Our documentation is run by serving the docs folder at a given port. 11 | 12 | From the root folder on a command console, enter the command: 13 | ```sh 14 | npm run start:docs 15 | ``` 16 | 17 | This displays the following: 18 | 19 | ```sh 20 | > titus@x.y.z doc:serve /path/to/your/repo/titus 21 | > docsify serve -p 4000 docs 22 | 23 | Serving /path/to/your/repo/titus/docs now. 24 | Listening at http://localhost:4000 25 | ``` 26 | 27 | The documentation is served on `localhost:4000`. 28 | 29 | ### Serve in Other Ways 30 | Our documentation builds on the fly. All you need to do is serve the docs folder. Any program or CLI tool used for serving a folder works - __as long as it can handle hash routing__. 31 | 32 | Another popular module to serve files is `serve` on npm. To run, with hash router support, enter the command: 33 | 34 | ```sh 35 | npx serve -s -l 4000 docs 36 | ``` 37 | 38 | This displays the following: 39 | 40 | ```sh 41 | 42 | Serving! 43 | 44 | - Local: http://localhost:4000 45 | - On Your Network: http://192.168.1.60:4000 46 | 47 | Copied local address to clipboard! 48 | 49 | ``` 50 | 51 | The documentation is served on `localhost:4000` in this case. 52 | 53 | [docs]:https://nf-titus.netlify.app/#/ 54 | -------------------------------------------------------------------------------- /docs/contributing/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/) 4 | - [DevOps](/devops/) 5 | - [Contribute to Titus](/contributing/?id=contribute-to-titus) 6 | - [Code](/contributing/?id=code) 7 | - [Documentation](/contributing/?id=documentation) -------------------------------------------------------------------------------- /docs/developers/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/?id=developers) 4 | - [Overview](/developers/?id=Overview) 5 | - [Packages](/developers/?id=kits) 6 | - [Services](/developers/?id=services) 7 | - [DevOps](/devops/) 8 | - [Contribute](/contributing/) 9 | -------------------------------------------------------------------------------- /docs/developers/packages/titus-backend-typescript/README.md: -------------------------------------------------------------------------------- 1 | # Titus - supplemental notes for titus-backend-typescript 2 | ## Overview 3 | Titus backend (Typescript) is a starter [fastify] server with [node-postgres] and [Auth0] plugins using Typescript. 4 | 5 | **NB: These are supplemental notes for the Typescript backend, and should be read in conjunction with [titus-backend]** 6 | 7 | ### Organisation 8 | Titus backend (Typescript) differs from the standard backend, in that it has `src` and `dist` folders for source code and transpiled code accordingly. It is structured as follows: 9 | 10 | * `src/` - contains the server sources. 11 | * `src/config` - server configuration: reads environment variable values, with default values from the`.env` file. 12 | * `src/plugins` - fastify plugins for cross-cutting features. Contains a pg instrumenter and Auth0 + [jwt] strategy. 13 | * `src/routes` - fastify plugins to declare HTTP routes. The health check route is also here. 14 | 15 | * `dist/` - mirrors the `src` structure above, but contains the transpiled build. 16 | 17 | ## Build the Application 18 | 19 | To build the application, use the following command within the titus-backend-typescript directory: 20 | ``` 21 | npm run build 22 | ``` 23 | This command produces a new bundle in `dist/` folder. 24 | 25 | You can also run the following command in the root directory of the project: 26 | ``` 27 | npm run build:all 28 | ``` 29 | 30 | [titus-backend]: /developers/packages/titus-backend/ -------------------------------------------------------------------------------- /docs/developers/packages/titus-backend-typescript/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/?id=developers) 4 | - [Overview](/developers/?id=overview) 5 | - [Packages](/developers/?id=kits) 6 | - [Frontend Package](/developers/packages/titus-frontend/) 7 | - [Backend Package](/developers/packages/titus-backend/?id=titus-backend-kit) 8 | - [Overview](/developers/packages/titus-backend/?id=overview) 9 | - [Install the Backend](/developers/packages/titus-backend/?id=install-the-backend) 10 | - [Run the Backend Locally](/developers/packages/titus-backend/?id=run-the-backend-locally) 11 | - [Test and Lint the Backend](/developers/packages/titus-backend/?id=test-and-lint-the-backend) 12 | - [Multi-tenancy with AWS Amplify](/developers/packages/titus-backend/?id=multi-tenancy-with-aws-amplify) 13 | - [Typescript Backend Package](/developers/packages/titus-backend-typescript/) 14 | - [Database Manager Package](/developers/packages/titus-db-manager/) 15 | - [Services](/developers/?id=services) 16 | - [DevOps](/devops/) 17 | - [Contribute to Titus](/contributing/) 18 | -------------------------------------------------------------------------------- /docs/developers/packages/titus-backend/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/?id=developers) 4 | - [Overview](/developers/?id=overview) 5 | - [Packages](/developers/?id=kits) 6 | - [Frontend Package](/developers/packages/titus-frontend/) 7 | - [Backend Package](/developers/packages/titus-backend/?id=titus-backend-kit) 8 | - [Overview](/developers/packages/titus-backend/?id=overview) 9 | - [Install the Backend](/developers/packages/titus-backend/?id=install-the-backend) 10 | - [Run the Backend Locally](/developers/packages/titus-backend/?id=run-the-backend-locally) 11 | - [Test and Lint the Backend](/developers/packages/titus-backend/?id=test-and-lint-the-backend) 12 | - [Multi-tenancy with AWS Amplify](/developers/packages/titus-backend/?id=multi-tenancy-with-aws-amplify) 13 | - [Typescript Backend Package](/developers/packages/titus-backend-typescript/) 14 | - [Database Manager Package](/developers/packages/titus-db-manager/) 15 | - [Services](/developers/?id=services) 16 | - [DevOps](/devops/) 17 | - [Contribute to Titus](/contributing/) 18 | -------------------------------------------------------------------------------- /docs/developers/packages/titus-db-manager/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/?id=developers) 4 | - [Overview](/developers/?id=overview) 5 | - [Packages](/developers/?id=kits) 6 | - [Frontend Package](/developers/packages/titus-frontend/) 7 | - [Backend Package](/developers/packages/titus-backend/?id=titus-backend-kit) 8 | - [Database Manager Package](/developers/packages/titus-db-manager/) 9 | - [Overview](/developers/packages/titus-db-manager/?id=overview) 10 | - [Install Packages](/developers/packages/titus-db-manager/?id=install-packages) 11 | - [Test and Lint the Database Manager](/developers/packages/titus-db-manager/?id=test-and-lint-the-database) 12 | - [Manage the Database](/developers/packages/titus-db-manager/?id=manage-the-database) 13 | - [Services](/developers/?id=services) 14 | - [DevOps](/devops/) 15 | - [Contribute to Titus](/contributing/) 16 | -------------------------------------------------------------------------------- /docs/developers/packages/titus-frontend/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/?id=developers) 4 | - [Overview](/developers/?id=overview) 5 | - [Packages](/developers/?id=kits) 6 | - [Frontend Package](/developers/packages/titus-frontend/?id=titus-frontend-kit) 7 | - [Quick Start](/developers/packages/titus-frontend/?id=quick-start) 8 | - [Overview](/developers/packages/titus-frontend/?id=overview) 9 | - [Configure Authentication](/developers/packages/titus-frontend/?id=configure-authentication) 10 | - [Install the Frontend](/developers/packages/titus-frontend/?id=install-the-frontend) 11 | - [Run the Frontend Locally](/developers/packages/titus-frontend/?id=run-the-frontend-locally) 12 | - [Test and Lint the Frontend](/developers/packages/titus-frontend/?id=test-and-lint-the-frontend) 13 | - [Build the Application](/developers/packages/titus-frontend/?id=build-the-application) 14 | - [Backend Package](/developers/packages/titus-backend/) 15 | - [Typescript Backend Package](/developers/packages/titus-backend-typescript/) 16 | - [Database Manager Package](/developers/packages/titus-db-manager/) 17 | - [Services](/developers/?id=services) 18 | - [DevOps](/devops/) 19 | - [Contribute to Titus](/contributing/) 20 | -------------------------------------------------------------------------------- /docs/devops/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/) 4 | - [DevOps](/devops/?id=devops) 5 | - [Overview](/devops/?id=overview) 6 | - [Deploy Titus](/devops/?id=deploy-titus) 7 | - [Deploy on GCP](/devops/gcp/) 8 | - [Deploy on AWS](/devops/aws/) 9 | - [Deploy on Azure](/devops/azure/?id=Deploy-Titus-on-Azure) 10 | - [Deploy on Kubernetes](/devops/k8s/?id=Deploy-on-Kubernetes) 11 | - [Contribute to Titus](/contributing/) 12 | -------------------------------------------------------------------------------- /docs/devops/aws/infra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/devops/aws/infra.png -------------------------------------------------------------------------------- /docs/devops/azure/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/) 4 | - [DevOps](/devops/?id=devops) 5 | - [Overview](/devops/?id=overview) 6 | - [Deploy Titus](/devops/?id=deploy-titus) 7 | - [Deploy on GCP](/devops/gcp/) 8 | - [Deploy on AWS](/devops/aws/) 9 | - [Deploy on Azure](/devops/azure/?id=Deploy-Titus-on-Azure) 10 | - [Requirements](/devops/azure/?id=requirements) 11 | - [Infrastructure Stack](/devops/azure/?id=infrastructure-stack) 12 | - [Terraform and Azure account](/devops/azure/?id=terraform-and-azure-account) 13 | - [Provision Infrastructure with Terraform](/devops/azure/?id=provision-infrastructure-with-terraform) 14 | - [Create the pipelines in Azure DevOps](/devops/azure/?id=create-the-pipelines-in-azure-devops) 15 | - [Update the Container images](/devops/azure/?id=update-the-container-images) 16 | - [Verifying Azure deployment](/devops/azure/?id=verifying-azure-deployment) 17 | - [Deploy on Kubernetes](/devops/k8s/?id=Deploy-on-Kubernetes) 18 | - [Contribute to Titus](/contributing/) -------------------------------------------------------------------------------- /docs/devops/gcp/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/) 4 | - [DevOps](/devops/?id=devops) 5 | - [Overview](/devops/?id=overview) 6 | - [Deploy Titus](/devops/?id=deploy-titus) 7 | - [Deploy on GCP](/devops/gcp/?id=Deploy-Titus-on-GCP) 8 | - [Set up a Terraform GCP Service Account](/devops/gcp/?id=set-up-a-terraform-gcp-service-account) 9 | - [Configure GCP project variables](/devops/gcp/?id=configure-gcp-project-variables) 10 | - [Enable GCP APIs](/devops/gcp/?id=enable-gcp-apis) 11 | - [Provision infrastruture](/devops/gcp/?id=provision-infrastruture) 12 | - [GitHub configuration](/devops/gcp/?id=github-configuration) 13 | - [Deploying](/devops/gcp/?id=deploying) 14 | - [Verifying GCP deployment](/devops/gcp/?id=verifying-gcp-deployment) 15 | - [Deploy on AWS](/devops/aws/?id=titus-aws-deploy-with-terraform) 16 | - [Deploy on Azure](/devops/azure/?id=Deploy-Titus-on-Azure) 17 | - [Deploy on Kubernetes](/devops/k8s/?id=Deploy-on-Kubernetes) 18 | - [Contribute to Titus](/contributing/) 19 | -------------------------------------------------------------------------------- /docs/devops/k8s/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/) 3 | - [Developers](/developers/) 4 | - [DevOps](/devops/?id=devops) 5 | - [Overview](/devops/?id=overview) 6 | - [Deploy Titus](/devops/?id=deploy-titus) 7 | - [Deploy Titus on GCP](/devops/gcp/?id=Deploy-Titus-on-GCP) 8 | - [Deploy on AWS with Mira](https://github.com/nearform/titus/tree/master/packages/titus-infra-aws-mira) 9 | - [Deploy on Azure](/devops/azure/?id=Deploy-Titus-on-Azure) 10 | - [Deploy on Kubernetes](/devops/k8s/?id=Deploy-on-Kubernetes) 11 | - [Requirements](/devops/k8s/?id=requirements) 12 | - [Infrastructure Stack](/devops/k8s/?id=infrastructure-stack) 13 | - [Helm and KinD](/devops/k8s/?id=helm-and-kinD) 14 | - [Update the Container images](/devops/k8s/?id=update-the-container-images) 15 | - [Config and Deploy](/devops/k8s/?id=config-and-deploy) 16 | - [Verifying Kubernetes deployment](/devops/k8s/?id=verifying-Kubernetes-deployment) 17 | - [Cleanup Kubernetes deployment](/devops/k8s/?id=cleanup-Kubernetes-deployment) 18 | - [Contribute to Titus](/contributing/) 19 | -------------------------------------------------------------------------------- /docs/img/Path.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Path 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/img/circle-add-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/circle-add-project.png -------------------------------------------------------------------------------- /docs/img/circle-env-variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/circle-env-variables.png -------------------------------------------------------------------------------- /docs/img/circle-tagged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/circle-tagged.png -------------------------------------------------------------------------------- /docs/img/circle-untagged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/circle-untagged.png -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/favicon.png -------------------------------------------------------------------------------- /docs/img/logo-neg.svg: -------------------------------------------------------------------------------- 1 | Titus_w -------------------------------------------------------------------------------- /docs/img/logo-pos.svg: -------------------------------------------------------------------------------- 1 | Titus -------------------------------------------------------------------------------- /docs/img/old-titus-home-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/old-titus-home-page.png -------------------------------------------------------------------------------- /docs/img/old-titus-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/old-titus-login.png -------------------------------------------------------------------------------- /docs/img/titus-home-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/titus-home-page.png -------------------------------------------------------------------------------- /docs/img/titus-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/docs/img/titus-login.png -------------------------------------------------------------------------------- /docs/quick-start/_sidebar.md: -------------------------------------------------------------------------------- 1 | - [Home](/) 2 | - [Quick Start Guide](/quick-start/?id=titus-quick-start-guide) 3 | - [Clone the Source Repository](/quick-start/?id=clone-the-source-repository) 4 | - [Install Dependencies](/quick-start/?id=install-dependencies) 5 | - [Configure the Environment](/quick-start/?id=configure-the-environment) 6 | - [Run the Stack](/quick-start/?id=run-the-stack) 7 | - [Log In](/quick-start/?id=log-in) 8 | - [Developers](/developers/) 9 | - [DevOps](/devops/) 10 | - [Contribute to Titus](/contributing/) 11 | -------------------------------------------------------------------------------- /infra/aws/terraform-eks/aws.tfplan: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/infra/aws/terraform-eks/aws.tfplan -------------------------------------------------------------------------------- /infra/aws/terraform-eks/cognito.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cognito_user_pool" "user_pool" { 2 | name = format("%s-user-pool", var.default_name) 3 | username_attributes = ["email"] 4 | } 5 | 6 | resource "aws_cognito_user_pool_client" "user_pool_client" { 7 | name = format("%s-user-pool-client", var.default_name) 8 | user_pool_id = aws_cognito_user_pool.user_pool.id 9 | #allowed_oauth_flows = ["client_credentials"] 10 | callback_urls = ["http://localhost"] 11 | default_redirect_uri = "http://localhost" 12 | allowed_oauth_scopes = aws_cognito_resource_server.resource_server.scope_identifiers 13 | supported_identity_providers = ["COGNITO"] 14 | } 15 | 16 | 17 | resource "aws_cognito_resource_server" "resource_server" { 18 | name = var.default_name 19 | identifier = "https://api.${var.domain_name}" 20 | user_pool_id = aws_cognito_user_pool.user_pool.id 21 | 22 | scope { 23 | scope_name = "all" 24 | scope_description = "Access to all API GW endpoints." 25 | } 26 | } 27 | 28 | resource "aws_cognito_user_pool_domain" "main" { 29 | domain = "${var.default_name}-${random_string.suffix.result}-login" 30 | user_pool_id = aws_cognito_user_pool.user_pool.id 31 | certificate_arn = "" # Certificate ARN 32 | } 33 | -------------------------------------------------------------------------------- /infra/aws/terraform-eks/config.auto.tfvars.example: -------------------------------------------------------------------------------- 1 | region = "eu-west-1" 2 | environment = "dev" 3 | default_name = "titus" 4 | website = {} 5 | cors_rule = [] 6 | lifecycle_enable = false 7 | lifecycle_id = "" 8 | obj_expiration = "" 9 | domain_name = "" 10 | db_user = "" 11 | -------------------------------------------------------------------------------- /infra/aws/terraform-eks/database.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "suffix" { 2 | length = 6 3 | min_numeric = 6 4 | upper = false 5 | special = false 6 | } 7 | 8 | resource "random_password" "db_password" { 9 | length = 16 10 | special = false 11 | } 12 | resource "aws_secretsmanager_secret" "db_password" { 13 | name = "${var.default_name}-${random_string.suffix.result}-db-password" 14 | } 15 | 16 | resource "aws_secretsmanager_secret_version" "db_password" { 17 | secret_id = aws_secretsmanager_secret.db_password.id 18 | secret_string = random_password.db_password.result 19 | } 20 | 21 | resource "aws_db_instance" "this" { 22 | allocated_storage = 5 23 | engine = "postgres" 24 | engine_version = "12.6" 25 | instance_class = "db.t3.micro" 26 | identifier = var.default_name 27 | name = "titus_backend" 28 | username = "titus_db_user" 29 | password = random_password.db_password.result 30 | db_subnet_group_name = aws_db_subnet_group.this.name 31 | skip_final_snapshot = true 32 | vpc_security_group_ids = [ aws_security_group.this.id ] 33 | } -------------------------------------------------------------------------------- /infra/aws/terraform-eks/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.13.0" 3 | 4 | # backend "s3" { 5 | # profile = "titus" 6 | # bucket = "titus-aws-update" 7 | # key = "titus-state-file" 8 | # region = "eu-west-1" 9 | # dynamodb_table = "titus-terraform-state-lock" 10 | # encrypt = true 11 | # } 12 | required_providers { 13 | aws = { 14 | source = "hashicorp/aws" 15 | version = "3.54.0" 16 | } 17 | } 18 | } 19 | 20 | provider "aws" { 21 | profile = "titus" 22 | region = "eu-west-1" 23 | } 24 | -------------------------------------------------------------------------------- /infra/aws/terraform-eks/outputs.tf: -------------------------------------------------------------------------------- 1 | output "db_name" { 2 | value = aws_db_instance.this.name 3 | } 4 | 5 | output "secretmanager_db_password" { 6 | value = aws_secretsmanager_secret.db_password.id 7 | } -------------------------------------------------------------------------------- /infra/aws/terraform-eks/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" {} 2 | variable "environment" {} 3 | 4 | variable "default_name" {} 5 | 6 | variable "website" {} 7 | variable "cors_rule" {} 8 | variable "lifecycle_enable" {} 9 | variable "lifecycle_id" {} 10 | variable "obj_expiration" {} 11 | variable "domain_name" {} 12 | 13 | variable "db_user" {} 14 | 15 | -------------------------------------------------------------------------------- /infra/aws/terraform/.gitignore: -------------------------------------------------------------------------------- 1 | *.terraform.* 2 | config.auto.tfvars 3 | key.json 4 | -------------------------------------------------------------------------------- /infra/aws/terraform/aws.tfplan: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/infra/aws/terraform/aws.tfplan -------------------------------------------------------------------------------- /infra/aws/terraform/cognito.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cognito_user_pool" "user_pool" { 2 | name = format("%s-user-pool", var.default_name) 3 | username_attributes = ["email"] 4 | } 5 | 6 | resource "aws_cognito_user_pool_client" "user_pool_client" { 7 | name = format("%s-user-pool-client", var.default_name) 8 | user_pool_id = aws_cognito_user_pool.user_pool.id 9 | #allowed_oauth_flows = ["client_credentials"] 10 | callback_urls = ["http://localhost"] 11 | default_redirect_uri = "http://localhost" 12 | allowed_oauth_scopes = aws_cognito_resource_server.resource_server.scope_identifiers 13 | supported_identity_providers = ["COGNITO"] 14 | } 15 | 16 | 17 | resource "aws_cognito_resource_server" "resource_server" { 18 | name = var.default_name 19 | identifier = "https://api.${var.domain_name}" 20 | user_pool_id = aws_cognito_user_pool.user_pool.id 21 | 22 | scope { 23 | scope_name = "all" 24 | scope_description = "Access to all API GW endpoints." 25 | } 26 | } 27 | 28 | resource "aws_cognito_user_pool_domain" "main" { 29 | domain = "${var.default_name}-${random_string.suffix.result}-login" 30 | user_pool_id = aws_cognito_user_pool.user_pool.id 31 | certificate_arn = "" # Certificate ARN 32 | } 33 | -------------------------------------------------------------------------------- /infra/aws/terraform/config.auto.tfvars.example: -------------------------------------------------------------------------------- 1 | region = "eu-west-1" 2 | environment = "dev" 3 | default_name = "titus" 4 | website = {} 5 | cors_rule = [] 6 | lifecycle_enable = false 7 | lifecycle_id = "" 8 | obj_expiration = "" 9 | domain_name = "" 10 | db_user = "" 11 | -------------------------------------------------------------------------------- /infra/aws/terraform/database.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "suffix" { 2 | length = 6 3 | min_numeric = 6 4 | upper = false 5 | special = false 6 | } 7 | 8 | resource "random_password" "db_password" { 9 | length = 16 10 | special = false 11 | } 12 | resource "aws_secretsmanager_secret" "db_password" { 13 | name = "${var.default_name}-${random_string.suffix.result}-db-password" 14 | } 15 | 16 | resource "aws_secretsmanager_secret_version" "db_password" { 17 | secret_id = aws_secretsmanager_secret.db_password.id 18 | secret_string = random_password.db_password.result 19 | } 20 | 21 | resource "aws_db_instance" "this" { 22 | allocated_storage = 5 23 | engine = "postgres" 24 | engine_version = "12.6" 25 | instance_class = "db.t3.micro" 26 | identifier = var.default_name 27 | name = "titus_backend" 28 | username = "titus_db_user" 29 | password = random_password.db_password.result 30 | db_subnet_group_name = aws_db_subnet_group.this.name 31 | skip_final_snapshot = true 32 | vpc_security_group_ids = [aws_security_group.this.id] 33 | } -------------------------------------------------------------------------------- /infra/aws/terraform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.4" 3 | 4 | # backend "s3" { 5 | # profile = "titus" 6 | # bucket = "titus-aws-update" 7 | # key = "titus-state-file" 8 | # region = "eu-west-1" 9 | # dynamodb_table = "titus-terraform-state-lock" 10 | # encrypt = true 11 | # } 12 | required_providers { 13 | aws = { 14 | source = "hashicorp/aws" 15 | version = "3.54.0" 16 | } 17 | } 18 | } 19 | 20 | provider "aws" { 21 | profile = "titus" 22 | region = "eu-west-1" 23 | } 24 | -------------------------------------------------------------------------------- /infra/aws/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "db_name" { 2 | value = aws_db_instance.this.name 3 | } 4 | 5 | output "secretmanager_db_password" { 6 | value = aws_secretsmanager_secret.db_password.id 7 | } -------------------------------------------------------------------------------- /infra/aws/terraform/static-content.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | upload_directory = "${path.cwd}/static-content/" 3 | mime_types = { 4 | htm = "text/html" 5 | html = "text/html" 6 | css = "text/css" 7 | ttf = "font/ttf" 8 | js = "application/javascript" 9 | map = "application/javascript" 10 | json = "application/json" 11 | } 12 | } 13 | 14 | resource "aws_s3_bucket_object" "website_files" { 15 | for_each = fileset(local.upload_directory, "**/*.*") 16 | bucket = aws_s3_bucket.this.id 17 | key = replace(each.value, local.upload_directory, "") 18 | source = "${local.upload_directory}${each.value}" 19 | etag = filemd5("${local.upload_directory}${each.value}") 20 | content_type = lookup(local.mime_types, split(".", each.value)[length(split(".", each.value)) - 1]) 21 | } 22 | -------------------------------------------------------------------------------- /infra/aws/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" {} 2 | variable "environment" {} 3 | 4 | variable "default_name" {} 5 | 6 | variable "website" {} 7 | variable "cors_rule" {} 8 | variable "lifecycle_enable" {} 9 | variable "lifecycle_id" {} 10 | variable "obj_expiration" {} 11 | variable "domain_name" {} 12 | 13 | variable "db_user" {} 14 | 15 | -------------------------------------------------------------------------------- /infra/azure/terraform/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | .terraform.lock.hcl 3 | config.auto.tfvars 4 | key.json 5 | input.tfvars 6 | *.tfplan -------------------------------------------------------------------------------- /infra/azure/terraform/artifact-registry.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_container_registry" "titus-container-registry" { 2 | name = var.artifact_registry_repository_name 3 | resource_group_name = var.resource_group_name 4 | location = var.location 5 | sku = "Standard" 6 | admin_enabled = true 7 | 8 | #georeplication_locations = ["East US", "West Europe"] 9 | } 10 | -------------------------------------------------------------------------------- /infra/azure/terraform/database.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "suffix" { 2 | length = 6 3 | min_numeric = 6 4 | upper = false 5 | special = false 6 | } 7 | 8 | resource "azurerm_postgresql_server" "titus-db-server" { 9 | name = "titus-postgresql-server-${random_string.suffix.result}" 10 | location = var.location 11 | resource_group_name = var.resource_group_name 12 | 13 | sku_name = "B_Gen5_2" 14 | 15 | #storage_mb = 5120 16 | #backup_retention_days = 7 17 | #geo_redundant_backup_enabled = false 18 | auto_grow_enabled = true 19 | 20 | administrator_login = var.db_user 21 | administrator_login_password = azurerm_key_vault_secret.titus-db-password.value 22 | version = "11" 23 | ssl_enforcement_enabled = false 24 | #public_network_access_enabled = false 25 | } 26 | 27 | resource "azurerm_postgresql_database" "titus-db" { 28 | name = var.db_instance_name 29 | resource_group_name = var.resource_group_name 30 | server_name = azurerm_postgresql_server.titus-db-server.name 31 | charset = "UTF8" 32 | collation = "English_United States.1252" 33 | } 34 | 35 | resource "azurerm_postgresql_firewall_rule" "titus-fw-rule" { 36 | name = "titus-db-fw-rule" 37 | resource_group_name = var.resource_group_name 38 | server_name = azurerm_postgresql_server.titus-db-server.name 39 | # Allow Access to Azure Services/Container Instances 40 | start_ip_address = "0.0.0.0" 41 | end_ip_address = "0.0.0.0" 42 | } -------------------------------------------------------------------------------- /infra/azure/terraform/keyvault.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_key_vault" "titus-key-vault" { 2 | name = "tituskeyvault" 3 | location = var.location 4 | resource_group_name = var.resource_group_name 5 | tenant_id = data.azurerm_client_config.current.tenant_id 6 | sku_name = "standard" 7 | soft_delete_retention_days = 7 8 | 9 | access_policy { 10 | tenant_id = data.azurerm_client_config.current.tenant_id 11 | object_id = data.azurerm_client_config.current.object_id 12 | 13 | key_permissions = [ 14 | "create", 15 | "get", 16 | "list" 17 | ] 18 | 19 | secret_permissions = [ 20 | "set", 21 | "get", 22 | "delete", 23 | "purge", 24 | "recover", 25 | "list" 26 | ] 27 | } 28 | } 29 | 30 | resource "random_password" "jwt-secret" { 31 | length = 8 32 | special = false 33 | } 34 | 35 | resource "azurerm_key_vault_secret" "titus-jwt-secret" { 36 | name = "titus-jwt-secret-${random_string.suffix.result}" 37 | value = random_password.jwt-secret.result 38 | key_vault_id = azurerm_key_vault.titus-key-vault.id 39 | } 40 | 41 | resource "random_password" "db_password" { 42 | length = 16 43 | special = false 44 | } 45 | 46 | resource "azurerm_key_vault_secret" "titus-db-password" { 47 | name = "titus-db-password-${random_string.suffix.result}" 48 | value = random_password.db_password.result 49 | key_vault_id = azurerm_key_vault.titus-key-vault.id 50 | } -------------------------------------------------------------------------------- /infra/azure/terraform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.4" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | version = "=2.71.0" 7 | } 8 | } 9 | } 10 | 11 | # (Optional) store terraform state in a storage account 12 | # terraform { 13 | # backend "azurerm" { 14 | # resource_group_name = "titus-rg" 15 | # storage_account_name = "titusonazure" 16 | # container_name = "tfstate" 17 | # key = "terraform.tfstate" 18 | # } 19 | # } 20 | 21 | # Configure the Microsoft Azure Provider 22 | provider "azurerm" { 23 | features {} 24 | subscription_id = var.subscription_id 25 | skip_provider_registration = true 26 | } 27 | 28 | data "azurerm_client_config" "current" { 29 | } 30 | 31 | output "account_id" { 32 | value = data.azurerm_client_config.current.client_id 33 | } 34 | 35 | output "tenant_id" { 36 | value = data.azurerm_client_config.current.tenant_id 37 | } 38 | 39 | output "subscription_id" { 40 | value = data.azurerm_client_config.current.subscription_id 41 | } 42 | -------------------------------------------------------------------------------- /infra/azure/terraform/network.tf: -------------------------------------------------------------------------------- 1 | # create virtual network 2 | resource "azurerm_virtual_network" "titus-vnet" { 3 | name = "titus-vnet" 4 | address_space = [var.titus_network_range] 5 | location = var.location 6 | resource_group_name = var.resource_group_name 7 | } 8 | 9 | resource "azurerm_subnet" "titus-subnet" { 10 | name = "titus-subnet" 11 | resource_group_name = var.resource_group_name 12 | virtual_network_name = azurerm_virtual_network.titus-vnet.name 13 | address_prefixes = [var.titus_subnet_range] 14 | 15 | delegation { 16 | name = "delegation" 17 | 18 | service_delegation { 19 | name = "Microsoft.ContainerInstance/containerGroups" 20 | actions = ["Microsoft.Network/virtualNetworks/subnets/action"] 21 | } 22 | } 23 | } 24 | 25 | resource "azurerm_network_profile" "titus-net-profile" { 26 | name = "titus-net-profile" 27 | location = var.location 28 | resource_group_name = var.resource_group_name 29 | 30 | container_network_interface { 31 | name = "titus-net-nic" 32 | 33 | ip_configuration { 34 | name = "titus-ip-config" 35 | subnet_id = azurerm_subnet.titus-subnet.id 36 | } 37 | } 38 | } 39 | 40 | resource "azurerm_dns_zone" "titus-dns-public" { 41 | name = "${var.titus_domain_name}.com" 42 | resource_group_name = var.resource_group_name 43 | } 44 | -------------------------------------------------------------------------------- /infra/azure/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "fontend_fqdn" { 2 | description = "Frontend FQDN" 3 | value = azurerm_container_group.titus-frontend-containergroup.fqdn 4 | } 5 | 6 | output "backend_fqdn" { 7 | description = "Backend FQDN" 8 | value = azurerm_container_group.titus-backend-containergroup.fqdn 9 | } 10 | 11 | output "db_host" { 12 | description = "Database Server URL" 13 | value = azurerm_postgresql_server.titus-db-server.fqdn 14 | } 15 | 16 | output "db_user" { 17 | description = "Database user" 18 | value = "${var.db_user}@${azurerm_postgresql_server.titus-db-server.name}" 19 | } 20 | 21 | output "db_password" { 22 | description = "Database password" 23 | value = "You can extract it from the keyvault or the terraform.tfstate file" 24 | } -------------------------------------------------------------------------------- /infra/azure/terraform/sample-input.tfvars: -------------------------------------------------------------------------------- 1 | subscription_id = "********-****-****-****-************" 2 | resource_group_name = "********************************" 3 | location = "West Europe" 4 | titus_domain_name = "titus-dev1" 5 | titus_network_range = "10.0.0.0/16" 6 | titus_subnet_range = "10.0.1.0/24" 7 | 8 | db_user = "titus" 9 | db_instance_name = "titus" 10 | 11 | artifact_registry_repository_name = "tituscr" 12 | 13 | tags = { 14 | "environment": "development" 15 | "project": "titus" 16 | } -------------------------------------------------------------------------------- /infra/azure/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "subscription_id" { 2 | type = string 3 | } 4 | variable "resource_group_name" { 5 | type = string 6 | } 7 | variable "location" { 8 | type = string 9 | default = "West Europe" 10 | } 11 | variable "titus_domain_name" { 12 | type = string 13 | } 14 | variable "titus_network_range" { 15 | type = string 16 | default = "10.0.0.0/16" 17 | } 18 | variable "titus_subnet_range" { 19 | type = string 20 | default = "10.0.1.0/24" 21 | } 22 | 23 | variable "db_user" { 24 | type = string 25 | default = "titus" 26 | } 27 | variable "db_instance_name" { 28 | type = string 29 | default = "titus" 30 | } 31 | 32 | variable "artifact_registry_repository_name" { 33 | type = string 34 | default = "tituscr" 35 | } 36 | 37 | variable "tags" { 38 | default = { 39 | "environment": "development" 40 | "project": "titus" 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /infra/gcp/terraform/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | config.auto.tfvars 3 | key.json 4 | -------------------------------------------------------------------------------- /infra/gcp/terraform/artifact-registry.tf: -------------------------------------------------------------------------------- 1 | resource "google_artifact_registry_repository" "main" { 2 | provider = google-beta 3 | 4 | location = var.region 5 | repository_id = var.artifact_registry_repository_name 6 | description = "Application Docker repository" 7 | format = "DOCKER" 8 | } -------------------------------------------------------------------------------- /infra/gcp/terraform/config.auto.tfvars.sample: -------------------------------------------------------------------------------- 1 | gcp_project_id = "upheld-dragon-268810" # Nearform GCP org - Titus 2 | region = "europe-west1" 3 | zone = "europe-west1-b" 4 | 5 | cloudsql_db_instance_name = "titus" 6 | cloudsql_tier = "db-custom-1-3840" # where 1 means 1 CPU and 3840 means 3,75GB RAM 7 | cloudsql_db_name = "titus" 8 | cloudsql_db_user = "titus" 9 | 10 | is_production = "true" 11 | 12 | artifact_registry_repository_name = "titus" -------------------------------------------------------------------------------- /infra/gcp/terraform/database.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "suffix" { 2 | length = 6 3 | min_numeric = 6 4 | upper = false 5 | special = false 6 | } 7 | 8 | resource "google_sql_database_instance" "main" { 9 | name = "${var.cloudsql_db_instance_name}-${random_string.suffix.result}" 10 | database_version = "POSTGRES_11" 11 | region = var.region 12 | 13 | settings { 14 | tier = var.cloudsql_tier 15 | ip_configuration { 16 | ipv4_enabled = true 17 | } 18 | } 19 | } 20 | 21 | resource "google_sql_database" "main" { 22 | name = var.cloudsql_db_name 23 | instance = google_sql_database_instance.main.name 24 | } 25 | 26 | resource "random_password" "db_password" { 27 | length = 16 28 | special = false 29 | } 30 | 31 | resource "google_secret_manager_secret" "db_password" { 32 | provider = google-beta 33 | 34 | secret_id = "${var.cloudsql_db_instance_name}-db-password" 35 | 36 | replication { 37 | automatic = true 38 | } 39 | } 40 | 41 | resource "google_secret_manager_secret_version" "db_password" { 42 | provider = google-beta 43 | 44 | secret = google_secret_manager_secret.db_password.id 45 | secret_data = random_password.db_password.result 46 | } 47 | 48 | resource "google_sql_user" "main" { 49 | name = var.cloudsql_db_user 50 | password = random_password.db_password.result 51 | instance = google_sql_database_instance.main.name 52 | } 53 | -------------------------------------------------------------------------------- /infra/gcp/terraform/frontend.tf: -------------------------------------------------------------------------------- 1 | resource "google_cloud_run_service_iam_member" "frontend_noauth" { 2 | location = google_cloud_run_service.frontend.location 3 | service = google_cloud_run_service.frontend.name 4 | role = "roles/run.invoker" 5 | member = "allUsers" 6 | } 7 | 8 | resource "google_cloud_run_service" "frontend" { 9 | name = "titus-frontend" 10 | location = var.region 11 | 12 | template { 13 | spec { 14 | containers { 15 | image = "gcr.io/cloudrun/hello" 16 | } 17 | } 18 | } 19 | 20 | traffic { 21 | percent = 100 22 | latest_revision = true 23 | } 24 | autogenerate_revision_name = true 25 | 26 | lifecycle { 27 | ignore_changes = [ 28 | template[0].spec[0].containers[0].image, 29 | template[0].spec[0].service_account_name, 30 | template[0].metadata[0].annotations 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /infra/gcp/terraform/iam.tf: -------------------------------------------------------------------------------- 1 | 2 | resource "google_project_iam_binding" "db_access_iam" { 3 | depends_on = [ 4 | google_service_account.backend, 5 | google_service_account.db_manager 6 | ] 7 | role = "roles/cloudsql.client" 8 | members = [ 9 | "serviceAccount:${google_service_account.backend.email}", 10 | "serviceAccount:${google_service_account.db_manager.email}" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /infra/gcp/terraform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.4" 3 | backend "gcs" { 4 | credentials = "key.json" 5 | bucket = "titus-terraform-state" 6 | prefix = "core" 7 | } 8 | } 9 | 10 | provider "google" { 11 | credentials = file("key.json") 12 | project = var.gcp_project_id 13 | region = var.region 14 | zone = var.zone 15 | } 16 | 17 | provider "google-beta" { 18 | credentials = file("key.json") 19 | project = var.gcp_project_id 20 | region = var.region 21 | zone = var.zone 22 | } -------------------------------------------------------------------------------- /infra/gcp/terraform/storybook.tf: -------------------------------------------------------------------------------- 1 | resource "google_cloud_run_service_iam_member" "storybook_noauth" { 2 | location = google_cloud_run_service.storybook.location 3 | service = google_cloud_run_service.storybook.name 4 | role = "roles/run.invoker" 5 | member = "allUsers" 6 | } 7 | 8 | resource "google_cloud_run_service" "storybook" { 9 | name = "titus-storybook" 10 | location = var.region 11 | 12 | template { 13 | spec { 14 | containers { 15 | image = "gcr.io/cloudrun/hello" 16 | } 17 | } 18 | } 19 | 20 | traffic { 21 | percent = 100 22 | latest_revision = true 23 | } 24 | autogenerate_revision_name = true 25 | 26 | lifecycle { 27 | ignore_changes = [ 28 | template[0].spec[0].containers[0].image, 29 | template[0].spec[0].service_account_name, 30 | template[0].metadata[0].annotations 31 | ] 32 | } 33 | } -------------------------------------------------------------------------------- /infra/gcp/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "gcp_project_id" {} 2 | variable "region" {} 3 | variable "zone" {} 4 | 5 | variable "cloudsql_db_instance_name" {} 6 | variable "cloudsql_tier" {} 7 | variable "cloudsql_db_name" {} 8 | variable "cloudsql_db_user" {} 9 | 10 | variable "is_production" {} 11 | 12 | variable "artifact_registry_repository_name" {} -------------------------------------------------------------------------------- /infra/k8s/helm/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /infra/k8s/helm/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: common 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 1.7.1 5 | - name: postgresql 6 | repository: https://charts.bitnami.com/bitnami 7 | version: 10.8.0 8 | digest: sha256:bcd71895c8d7a60b10e79ccbb8f3c612f49091520f8fe7c3a4bf59d8493c0009 9 | generated: "2021-08-02T13:13:01.642718526+02:00" 10 | -------------------------------------------------------------------------------- /infra/k8s/helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: titus 3 | description: A Helm chart for Kubernetes installing Titus backend 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | dependencies: 26 | - name: common 27 | repository: https://charts.bitnami.com/bitnami 28 | tags: 29 | - bitnami-common 30 | version: 1.x.x 31 | - condition: postgresql.enabled 32 | name: postgresql 33 | version: 10.x.x 34 | repository: https://charts.bitnami.com/bitnami 35 | -------------------------------------------------------------------------------- /infra/k8s/helm/charts/common-1.7.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/infra/k8s/helm/charts/common-1.7.1.tgz -------------------------------------------------------------------------------- /infra/k8s/helm/charts/postgresql-10.8.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/infra/k8s/helm/charts/postgresql-10.8.0.tgz -------------------------------------------------------------------------------- /infra/k8s/helm/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "..fullname" . }} 6 | labels: 7 | {{- include "..labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "..fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /infra/k8s/helm/templates/ingress-backend.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.backend.ingress.enabled -}} 2 | {{- $fullName := include "..fullname" . -}} 3 | {{- $svcPort := .Values.backend.service.port -}} 4 | {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 5 | apiVersion: networking.k8s.io/v1beta1 6 | {{- else -}} 7 | apiVersion: extensions/v1beta1 8 | {{- end }} 9 | kind: Ingress 10 | metadata: 11 | name: '{{ $fullName }}-backend' 12 | labels: 13 | {{- include "..labels" . | nindent 4 }} 14 | {{- with .Values.backend.ingress.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.backend.ingress.tls }} 20 | tls: 21 | {{- range .Values.backend.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.backend.ingress.hosts }} 31 | - host: {{ .host | quote }} 32 | http: 33 | paths: 34 | {{- range .paths }} 35 | - path: {{ .path }} 36 | backend: 37 | serviceName: '{{ $fullName }}-backend' 38 | servicePort: {{ $svcPort }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /infra/k8s/helm/templates/ingress-frontend.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.frontend.ingress.enabled -}} 2 | {{- $fullName := include "..fullname" . -}} 3 | {{- $svcPort := .Values.frontend.service.port -}} 4 | {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 5 | apiVersion: networking.k8s.io/v1beta1 6 | {{- else -}} 7 | apiVersion: extensions/v1beta1 8 | {{- end }} 9 | kind: Ingress 10 | metadata: 11 | name: '{{ $fullName }}-frontend' 12 | labels: 13 | {{- include "..labels" . | nindent 4 }} 14 | {{- with .Values.frontend.ingress.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.frontend.ingress.tls }} 20 | tls: 21 | {{- range .Values.frontend.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.frontend.ingress.hosts }} 31 | - host: {{ .host | quote }} 32 | http: 33 | paths: 34 | {{- range .paths }} 35 | - path: {{ .path }} 36 | backend: 37 | serviceName: '{{ $fullName }}-frontend' 38 | servicePort: {{ $svcPort }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /infra/k8s/helm/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: {{ include "..fullname" . }} 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | spec: 8 | rules: 9 | - apiGroups: ["batch"] 10 | resources: ["jobs"] 11 | verbs: ["get", "watch", "list"] 12 | 13 | --- 14 | 15 | apiVersion: rbac.authorization.k8s.io/v1 16 | kind: RoleBinding 17 | metadata: 18 | name: {{ include "..fullname" . }} 19 | subjects: 20 | - kind: ServiceAccount 21 | name: {{ include "..serviceAccountName" . }} 22 | roleRef: 23 | kind: Role 24 | name: {{ include "..fullname" . }} 25 | apiGroup: rbac.authorization.k8s.io 26 | -------------------------------------------------------------------------------- /infra/k8s/helm/templates/service-backend.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: '{{ include "..fullname" . }}-backend' 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | tier: backend 8 | spec: 9 | type: {{ .Values.backend.service.type }} 10 | ports: 11 | - port: {{ .Values.backend.service.port }} 12 | targetPort: {{ .Values.backend.port }} 13 | {{ if eq .Values.backend.service.type "NodePort" }} 14 | nodePort: {{ .Values.backend.service.nodePort }} 15 | {{ end }} 16 | protocol: TCP 17 | name: http 18 | selector: 19 | {{- include "..selectorLabels" . | nindent 4 }} 20 | -------------------------------------------------------------------------------- /infra/k8s/helm/templates/service-frontend.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: '{{ include "..fullname" . }}-frontend' 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | tier: frontend 8 | spec: 9 | type: {{ .Values.frontend.service.type }} 10 | ports: 11 | - port: {{ .Values.frontend.service.port }} 12 | targetPort: {{ .Values.frontend.port }} 13 | {{ if eq .Values.frontend.service.type "NodePort" }} 14 | nodePort: {{ .Values.frontend.service.nodePort }} 15 | {{ end }} 16 | protocol: TCP 17 | name: http 18 | selector: 19 | {{- include "..selectorLabels" . | nindent 4 }} 20 | -------------------------------------------------------------------------------- /infra/k8s/helm/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "..serviceAccountName" . }} 6 | labels: 7 | {{- include "..labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /infra/k8s/helm/templates/spc.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.backend.csi.enabled }} 2 | {{- with .Values.backend.csi.spc }} 3 | kind: SecretProviderClass 4 | apiVersion: secrets-store.csi.x-k8s.io/v1alpha1 5 | metadata: 6 | name: {{ .name | quote }} 7 | spec: 8 | provider: {{ .type | quote }} 9 | secretObjects: 10 | {{- range $.Values.backend.secrets }} 11 | - data: 12 | {{- $secretName := .secretName }} 13 | {{- range .secretKeys }} 14 | - key: {{ .key | quote }} 15 | objectName: {{ print $secretName .key | quote }} 16 | {{- end }} 17 | labels: {{- toYaml .labels | nindent 8}} 18 | secretName: {{ .secretName | quote }} 19 | type: {{ .type | quote }} 20 | {{- end }} 21 | parameters: 22 | roleName: {{ .role }} 23 | vaultAddress: {{ .address | quote }} 24 | objects: | 25 | {{- range $.Values.backend.secrets }} 26 | {{- if eq $.Values.backend.csi.spc.type "vault" }} 27 | {{- $secretName := .secretName }} 28 | {{- range .secretKeys }} 29 | - objectName: {{ print $secretName .key | quote }} 30 | secretPath: {{ print $.Values.backend.csi.spc.secretPathPrefix $secretName | quote }} 31 | secretKey: {{ .key | quote }} 32 | {{- end }} 33 | {{- end }} 34 | {{- end }} 35 | {{- end }} 36 | {{- end }} 37 | -------------------------------------------------------------------------------- /infra/k8s/helm/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "..fullname" . }}-test-connection" 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "..fullname" . }}:{{ .Values.backend.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.11.0", 3 | "packages": [ 4 | "packages/titus-backend", 5 | "!packages/titus-backend-typescript", 6 | "packages/titus-db-manager", 7 | "packages/titus-frontend", 8 | "packages/titus-infra-aws-mira", 9 | "lib" 10 | ], 11 | "version": "1.0.0-alpha.1", 12 | "nohoist": "jest" 13 | } 14 | -------------------------------------------------------------------------------- /lib/.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "testEnvironment": "node", 3 | "collectCoverageFrom": [ 4 | "lib/**/*.{js}", 5 | "!**/node_modules/**" 6 | ], 7 | "testPathIgnorePatterns": [ 8 | "/node_modules/", 9 | "/packages/" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "titus-lib", 3 | "scripts": { 4 | "test": "jest --config .jest.json --coverage" 5 | }, 6 | "devDependencies": { 7 | "chalk": "4.1.2", 8 | "jest": "27.4.5" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/tools.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const { execSync } = require('child_process') 6 | const chalk = require('chalk') 7 | 8 | const warning = chalk.keyword('yellow') 9 | const info = chalk.keyword('green') 10 | 11 | const pkgDirs = getPkgDirs() 12 | function getPkgDirs() { 13 | try { 14 | const packages = execSync('npx lerna ls --all --json') 15 | return JSON.parse(packages.toString()).map( 16 | ({ location }) => location 17 | ) 18 | } catch (error) { 19 | console.log(warning('No packages found')) 20 | process.exit(0) 21 | } 22 | } 23 | 24 | function cleanEnv() { 25 | for (const pkgDir of pkgDirs) { 26 | const env = `${pkgDir}/.env` 27 | 28 | if (!fs.existsSync(env)) { 29 | continue 30 | } 31 | 32 | try { 33 | fs.unlinkSync(env) 34 | console.log(info(`Removed .env file at ${pkgDir}`)) 35 | } catch (err) { 36 | console.log(warning(`No .env file at ${pkgDir}`)) 37 | } 38 | } 39 | } 40 | 41 | function createEnv() { 42 | for (const pkgDir of pkgDirs) { 43 | const sample = `${pkgDir}/.env.sample` 44 | 45 | if (!fs.existsSync(sample)) { 46 | continue 47 | } 48 | 49 | const env = `${pkgDir}/.env` 50 | 51 | try { 52 | fs.accessSync(env) 53 | console.log(warning(`.env file at ${pkgDir} already exists`)) 54 | } catch (err) { 55 | if (err && err.code === 'ENOENT') { 56 | fs.copyFileSync(sample, env) 57 | console.log(info(`Created .env file at ${pkgDir}`)) 58 | } 59 | } 60 | } 61 | } 62 | 63 | module.exports = { 64 | cleanEnv, 65 | createEnv 66 | } 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "titus", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build:all": "lerna run --parallel build", 7 | "clean": "lerna clean", 8 | "clean:all": "npm run clean:env && npm run clean", 9 | "clean:env": "node bin/cleanEnv.js", 10 | "create:env": "node bin/createEnv.js", 11 | "db:create-volume": "docker volume create --name titus-pg-data -d local", 12 | "db:delete": "lerna run --stream db:delete", 13 | "db:down": "lerna run --stream db:down", 14 | "db:migrate": "lerna run --stream db:migrate", 15 | "db:seed": "lerna run --stream db:seed", 16 | "db:server:start": "lerna run --stream db:server:start", 17 | "db:truncate": "lerna run --stream db:truncate", 18 | "db:up": "lerna run --stream db:up", 19 | "lint:all": "lerna run --parallel lint", 20 | "lint:fix": "lerna run --parallel lint:fix", 21 | "lint:staged": "lerna run lint:staged --concurrency 1", 22 | "postinstall": "lerna bootstrap", 23 | "start:all": "lerna run --parallel start", 24 | "start:docs": "docsify serve ./docs --port 4000", 25 | "test:all": "lerna run --parallel test", 26 | "test:related": "lerna run test:related", 27 | "deploy:ci": "lerna run deploy:ci", 28 | "deploy": "lerna run deploy", 29 | "prepare": "husky install" 30 | }, 31 | "devDependencies": { 32 | "docsify-cli": "^4.4.3", 33 | "husky": "^7.0.1", 34 | "lerna": "^3.22.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | ** 3 | 4 | # Allow files and directories 5 | !package.json 6 | !package-lock.json 7 | !index.js 8 | !dist/** 9 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/.env.sample: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | IS_PRODUCTION=false 3 | 4 | API_HOST=127.0.0.1 5 | API_PORT=5000 6 | 7 | CORS_ORIGIN=true 8 | 9 | ENABLE_ADMIN=false 10 | 11 | PG_HOST=localhost 12 | PG_PORT=5432 13 | PG_DB=titus 14 | PG_USER=titus 15 | PG_PASS=titus 16 | 17 | # auth0, azureAD, cognito 18 | AUTH_PROVIDER=cognito 19 | 20 | COGNITO_USER_POOL_ID= 21 | COGNITO_REGION= 22 | 23 | AUTH0_DOMAIN= 24 | AUTH0_CLIENT_ID= 25 | AUTH0_CLIENT_SECRET= 26 | AUTH0_AUDIENCE= 27 | AUTH0_GRANT_TYPE= 28 | AUTH0_CONNECTION= 29 | 30 | AD_TENANT= 31 | AD_APP_ID= 32 | AD_SECRET= 33 | 34 | SECRETS_STRATEGY=env 35 | SECRETS_PG_PASS=PG_PASS 36 | 37 | # In production, replace with a proper secrets manager 38 | # See other Secrets Managers supported by fastify-secrets 39 | # 40 | # This is an example for GCP Secrets Manager 41 | # 42 | # SECRETS_STRATEGY=gcp 43 | # SECRETS_PG_PASS=projects/494141678371/secrets/DB_PASSWORD/versions/latest 44 | # 45 | # This is an example for AWS Secrets Manager 46 | # 47 | # SECRETS_STRATEGY=aws 48 | # SECRETS_PG_PASS=DB_PASSWORD 49 | # 50 | # This is an example for Azure Key Vault 51 | # 52 | # SECRETS_STRATEGY=azure 53 | # SECRETS_PG_PASS=your-vault-name|your-secret-name 54 | # 55 | # provide email address for your authorized users (testing/dev only) 56 | # CHECK_AUTHZ_ADMIN_USERS=test@example.com,another@example.com 57 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | node_modules/* 3 | pgdata/* 4 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "standard", 5 | "prettier", 6 | "plugin:@typescript-eslint/eslint-recommended", 7 | "plugin:@typescript-eslint/recommended" 8 | ], 9 | "ignorePatterns": ["dist/**"], 10 | "plugins": ["prettier", "jest"], 11 | "env": { 12 | "es6": true, 13 | "browser": false, 14 | "jest/globals": true 15 | }, 16 | "rules": { 17 | "prettier/prettier": "error", 18 | "import/order": [ 19 | "error", 20 | { 21 | "newlines-between": "always" 22 | } 23 | ], 24 | "@typescript-eslint/camelcase": "off", 25 | "camelcase": "off", 26 | "@typescript-eslint/no-explicit-any": "off", 27 | "@typescript-eslint/no-empty-function": "off", 28 | "@typescript-eslint/no-unused-vars": ["error", { "args": "after-used" }] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # postgres data files 64 | pgdata 65 | 66 | # gcp service account key 67 | key.json 68 | dist -------------------------------------------------------------------------------- /packages/titus-backend-typescript/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Simple example of CI to build a Docker container and push it to Amazon ECR 2 | variables: 3 | DOCKER_REGISTRY: ${ECR_REGISTRY} 4 | AWS_DEFAULT_REGION: eu-west-1 5 | APP_NAME: titus-backend 6 | DOCKER_HOST: tcp://docker:2375 7 | DOCKER_DRIVER: overlay2 8 | DOCKER_TLS_CERTDIR: '' 9 | WORKING_DIRECTORY: packages/titus-backend 10 | 11 | publish: 12 | stage: build 13 | image: 14 | name: docker:latest 15 | services: 16 | - docker:19-dind 17 | before_script: 18 | - apk add --no-cache curl jq python3 py3-pip 19 | - pip install awscli 20 | - docker login -u AWS -p $(aws ecr get-login-password --region $AWS_DEFAULT_REGION) $DOCKER_REGISTRY 21 | - aws --version 22 | - docker info 23 | - docker --version 24 | script: 25 | - cd $WORKING_DIRECTORY 26 | - docker build -t $DOCKER_REGISTRY/$APP_NAME:$CI_PIPELINE_IID . 27 | - docker push $DOCKER_REGISTRY/$APP_NAME:$CI_PIPELINE_IID 28 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/.nodemonrc: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src", 4 | ".env" 5 | ], 6 | "ext": "ts,json", 7 | "exec": "ts-node-transpile-only ./src/index.ts" 8 | } 9 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine as builder 2 | RUN apk add dumb-init 3 | ENV NODE_ENV production 4 | 5 | WORKDIR /service 6 | 7 | COPY --chown=node:node package.json package-lock.json ./ 8 | COPY --chown=node:node dist ./dist 9 | 10 | RUN npm ci --only=production 11 | 12 | USER node 13 | 14 | CMD ["dumb-init", "node", "dist/index.js"] 15 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/README.md: -------------------------------------------------------------------------------- 1 |   2 | 3 | [![Logo][logo-img]][docs] 4 | 5 |   6 | 7 | Your are browsing the repository for **titus-backend**. All documentation for titus is stored in a central location. Including for this repo. 8 | 9 | - Get the whole picture by viewing our **[Documentation][docs]** 10 | 11 | [docs]: https://nf-titus.netlify.app/ 12 | [logo-img]: ../../docs/img/Accel_Logo_Titus.svg 13 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/backend-descriptor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: titus-test 6 | name: titus-test 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: titus-test 12 | template: 13 | metadata: 14 | labels: 15 | app: titus-test 16 | spec: 17 | initContainers: 18 | - name: 'titus-test-init' 19 | image: 'groundnuty/k8s-wait-for:1.3' 20 | imagePullPolicy: IfNotPresent 21 | args: 22 | - 'job' 23 | - 'titus-db-migrate' # since the number is going to change from release to release the name is going to need to be dynamic 24 | containers: 25 | - image: titus:latest 26 | imagePullPolicy: Never 27 | name: titus 28 | env: 29 | - name: PG_HOST 30 | value: localhost 31 | - name: PG_PORT 32 | value: '5432' 33 | - name: PG_DB 34 | value: titus 35 | - name: PG_USER 36 | value: titus 37 | - name: PG_PASS 38 | value: titus 39 | - name: API_PORT 40 | value: '5000' 41 | - name: API_HOST 42 | value: localhost 43 | - name: SECRETS_PG_PASS 44 | value: PG_PASS 45 | - name: SECRETS_STRATEGY 46 | value: env 47 | - name: AUTH_PROVIDER 48 | value: cognito 49 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { jsWithTs as tsjPreset } from 'ts-jest/presets' 2 | 3 | export default { 4 | testEnvironment: 'node', 5 | collectCoverageFrom: ['lib/**/*.{js,jsx,ts,tsx}', '!**/node_modules/**'], 6 | transform: { 7 | ...tsjPreset.transform 8 | }, 9 | testPathIgnorePatterns: ['/node_modules/', '/dist/'], 10 | globals: { 11 | 'ts-jest': { 12 | diagnostics: false 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/config/auth-routes.ts: -------------------------------------------------------------------------------- 1 | export const authRoutes = [{ method: 'GET', regex: /^\/[user|auth]/ }] 2 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/config/authz/casbin_model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [role_definition] 8 | g = _, _ 9 | 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | [matchers] 14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act 15 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/config/authz/casbin_policy.csv: -------------------------------------------------------------------------------- 1 | # RBAC policy - the role_admin has admin access 2 | # users are added to the roles at runtime 3 | p, role_admin, admin, access 4 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/config/swagger.ts: -------------------------------------------------------------------------------- 1 | import { SwaggerOptions } from 'fastify-swagger' 2 | 3 | import { version } from '../../package.json' 4 | 5 | const swaggerConfig = { 6 | staticCSP: true, 7 | routePrefix: '/documentation', 8 | exposeRoute: true, 9 | swagger: { 10 | info: { 11 | title: 'GXP API Swagger', 12 | description: 'The documentation for the GXP API microservice.', 13 | version 14 | }, 15 | consumes: ['application/json'], 16 | produces: ['application/json'], 17 | securityDefinitions: { 18 | apiKey: { 19 | type: 'apiKey', 20 | name: 'authorization', 21 | in: 'header' 22 | } 23 | } 24 | } 25 | } as SwaggerOptions 26 | 27 | export default swaggerConfig 28 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import Fastify from 'fastify' 4 | import closeWithGrace from 'close-with-grace' 5 | 6 | import startServer from './server' 7 | import config from './config' 8 | 9 | // Crash on unhandledRejection 10 | process.on('unhandledRejection', (err) => { 11 | console.error(err) 12 | process.exit(1) 13 | }) 14 | 15 | const main = async () => { 16 | const server = Fastify(config.fastify) 17 | server.register(startServer, config) 18 | 19 | const closeListeners = closeWithGrace( 20 | { delay: 2000 }, 21 | async ({ 22 | signal, 23 | manual, 24 | err 25 | }:{ err?: Error, signal?: any, manual?: boolean }) => { 26 | if (err) { 27 | server.log.error(err) 28 | } 29 | 30 | server.log.info({ signal, manual }, 'closing application') 31 | 32 | await server.close() 33 | } 34 | ) 35 | 36 | server.addHook('onClose', async (instance, done) => { 37 | closeListeners.uninstall() 38 | done() 39 | }) 40 | 41 | try { 42 | await server.listen(config.server) 43 | } catch (err) { 44 | server.log.error(err) 45 | process.exit(1) 46 | } 47 | } 48 | 49 | main() 50 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/plugins/auth/auth0/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync } from 'fastify' 2 | import fp from 'fastify-plugin' 3 | import fastifyAuth0Verify from 'fastify-auth0-verify' 4 | 5 | import { configOptions } from '../../../config' 6 | 7 | import auth0Routes from './auth0-routes' 8 | 9 | const auth0: FastifyPluginAsync = async ( 10 | server, 11 | options 12 | ) => { 13 | server 14 | .register(fastifyAuth0Verify, options.auth.auth0) 15 | .register(auth0Routes, options) 16 | } 17 | 18 | export default fp(auth0) 19 | export const autoload = false 20 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/plugins/auth/cognito/index.ts: -------------------------------------------------------------------------------- 1 | import fp from 'fastify-plugin' 2 | import buildGetJwks from 'get-jwks' 3 | import fastifyJwt from 'fastify-jwt' 4 | import { FastifyPluginAsync } from 'fastify' 5 | 6 | import configOptions from '../../../config' 7 | 8 | import cognitoRoutes from './cognito-routes' 9 | 10 | function authenticate(request) { 11 | return request.jwtVerify() 12 | } 13 | 14 | const cognito: FastifyPluginAsync = async ( 15 | server, 16 | options 17 | ) => { 18 | const getJwks = buildGetJwks() 19 | 20 | server.register(fastifyJwt, { 21 | decode: { complete: true }, 22 | secret: (_, token, callback) => { 23 | const { 24 | // @ts-expect-error TODO 25 | header: { kid, alg }, 26 | // @ts-expect-error TODO 27 | payload: { iss } 28 | } = token 29 | 30 | getJwks 31 | .getPublicKey({ kid, domain: iss, alg }) 32 | .then((publicKey) => callback(null, publicKey), callback) 33 | } 34 | }) 35 | 36 | server.decorate('authenticate', authenticate) 37 | server.register(cognitoRoutes, options) 38 | } 39 | 40 | export default fp(cognito) 41 | export const autoload = false 42 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/plugins/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync } from 'fastify' 2 | import fp from 'fastify-plugin' 3 | 4 | import { configOptions } from '../../config' 5 | 6 | import auth0 from './auth0' 7 | import azureAD from './azure-ad' 8 | import cognito from './cognito' 9 | 10 | const authProviders = { 11 | auth0, 12 | azureAD, 13 | cognito 14 | } 15 | 16 | const auth: FastifyPluginAsync = async ( 17 | server, 18 | options 19 | ) => { 20 | server.register(authProviders[options.auth.provider], options) 21 | } 22 | 23 | export default fp(auth) 24 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/plugins/authz/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync, onRequestHookHandler } from 'fastify' 2 | import fp from 'fastify-plugin' 3 | import { Forbidden } from 'http-errors' 4 | import jwt from 'jsonwebtoken' 5 | 6 | declare module 'fastify' { 7 | export interface FastifyInstance { 8 | authorizeAdminAccess: onRequestHookHandler 9 | } 10 | } 11 | 12 | const authz: FastifyPluginAsync = async (server) => { 13 | // add users to the casbin policy with admin role from the email addresses defined in .env 14 | const adminUsers = process.env.CHECK_AUTHZ_ADMIN_USERS || '' 15 | const policies = adminUsers 16 | .split(',') 17 | .map((u) => server.casbin.addRoleForUser(u, 'role_admin')) 18 | await Promise.all(policies) 19 | 20 | server.decorate('authorizeAdminAccess', async ({ headers }) => { 21 | // the idToken contains user details from the auth provider (Auth0, Cognito or AzureAD) 22 | // different to the accessToken which is used to authenticate the user 23 | // passing it from the client in a custom header 24 | const idToken = headers['x-authz-id'] 25 | const authzUserData = jwt.decode(idToken) 26 | 27 | // Here's where the actual policy check goes ahead - based on the user's email in the idToken 28 | if ( 29 | typeof authzUserData === 'object' && 30 | !(await server.casbin.enforce(authzUserData?.email, 'admin', 'access')) 31 | ) { 32 | throw new Forbidden('Cannot access admin') 33 | } 34 | }) 35 | } 36 | 37 | export default fp(authz, { 38 | name: 'authz', 39 | dependencies: ['fastify-casbin'] 40 | }) 41 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/plugins/healthcheck/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyInstance, FastifyPluginAsync } from 'fastify' 2 | import fp from 'fastify-plugin' 3 | import underPressurePlugin from 'under-pressure' 4 | 5 | import { version } from '../../../package.json' 6 | 7 | async function runCheck(server: FastifyInstance) { 8 | let dbRes: { rowCount: number } | null = null 9 | try { 10 | dbRes = await server.pg.query('SELECT $1::text as message', [ 11 | 'Hello world!' 12 | ]) 13 | } catch (err) { 14 | // swallow error 15 | server.log.debug({ err }, `failed to read DB during health check`) 16 | } 17 | 18 | return { 19 | version, 20 | serverTimestamp: new Date(), 21 | status: 'ok', 22 | memoryUsage: server.memoryUsage && server.memoryUsage(), 23 | db: dbRes?.rowCount === 1 ? 'ok' : 'fail' 24 | } 25 | } 26 | 27 | // @ts-expect-error TODO 28 | const healthCheck: FastifyPluginAsync = async ( 29 | server, 30 | { underPressure }, 31 | next 32 | ) => { 33 | server.register(underPressurePlugin, (parent) => ({ 34 | ...underPressure, 35 | healthCheck: () => runCheck(parent) 36 | })) 37 | 38 | next() 39 | } 40 | 41 | export default fp(healthCheck, { 42 | name: 'healthCheck', 43 | dependencies: ['pg'] 44 | }) 45 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/plugins/pg/index.ts: -------------------------------------------------------------------------------- 1 | import pgRange from 'pg-range' 2 | import fp from 'fastify-plugin' 3 | import pg from 'pg' 4 | import { FastifyPluginAsync } from 'fastify' 5 | import fastifyPostgres from 'fastify-postgres' 6 | 7 | import configOptions from '../../config' 8 | 9 | pgRange.install(pg) 10 | 11 | const pgPlugin: FastifyPluginAsync = async ( 12 | server, 13 | { pgPlugin } 14 | ) => { 15 | server.register(fastifyPostgres, { 16 | ...pgPlugin, 17 | 18 | password: server.secrets.dbPassword 19 | }) 20 | } 21 | 22 | export default fp(pgPlugin, { 23 | name: 'pg', 24 | dependencies: ['secrets-manager'] 25 | }) 26 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/plugins/secrets-manager/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync } from 'fastify' 2 | import fp from 'fastify-plugin' 3 | import gcp from 'fastify-secrets-gcp' 4 | import aws from 'fastify-secrets-aws' 5 | import env from 'fastify-secrets-env' 6 | import azure from 'fastify-secrets-azure' 7 | 8 | import { configOptions } from '../../config' 9 | 10 | const SECRETS_PLUGINS = { 11 | gcp, 12 | aws, 13 | env, 14 | azure 15 | } 16 | 17 | function getPlugin(options: typeof configOptions): FastifyPluginAsync { 18 | if (!options.secretsManager.strategy) { 19 | return SECRETS_PLUGINS.env 20 | } 21 | 22 | return SECRETS_PLUGINS[options.secretsManager.strategy] 23 | } 24 | 25 | declare module 'fastify' { 26 | interface FastifyInstance { 27 | secrets: { 28 | dbPassword: string 29 | } 30 | } 31 | } 32 | 33 | const secretsManager: FastifyPluginAsync = async ( 34 | server, 35 | options 36 | ) => { 37 | const plugin = getPlugin(options) 38 | if (!plugin) { 39 | throw new Error( 40 | `Invalid secrets manager strategy. Choose one of ${Object.keys( 41 | SECRETS_PLUGINS 42 | ).join(', ')}` 43 | ) 44 | } 45 | 46 | if (options.secretsManager.strategy === 'azure') { 47 | // add required options 48 | const [vaultName, secretName] = 49 | options.secretsManager.secrets.dbPassword.split('|') 50 | options.secretsManager.secrets.dbPassword = secretName 51 | options.secretsManager.clientOptions = { vaultName } 52 | } 53 | server.register(plugin, options.secretsManager) 54 | } 55 | 56 | export default fp(secretsManager, { 57 | name: 'secrets-manager' 58 | }) 59 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/routes/authzcheck/authzcheck.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync } from 'fastify' 2 | import fp from 'fastify-plugin' 3 | 4 | const authRoutes: FastifyPluginAsync = async (server) => { 5 | // this is a sample to check the signed-in user's permission to access this resource via casbin policies based on configuration in the OAuth provider 6 | // a real-world example would have more context around the user account and roles/scopes/permissions and policies 7 | server.route({ 8 | method: 'GET', 9 | url: '/authzcheck', 10 | schema: { 11 | tags: ['authz'], 12 | security: [ 13 | { 14 | apiKey: [] 15 | } 16 | ] 17 | }, 18 | onRequest: [server.authenticate, server.authorizeAdminAccess], 19 | handler: async ({ user }) => ({ 20 | isAdmin: true, 21 | user 22 | }) 23 | }) 24 | } 25 | export default fp(authRoutes) 26 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/routes/log/index.ts: -------------------------------------------------------------------------------- 1 | import { Type, Static } from '@sinclair/typebox' 2 | import { FastifyPluginAsync } from 'fastify' 3 | 4 | const BodyType = Type.Strict( 5 | Type.Object({ 6 | msg: Type.String(), 7 | level: Type.KeyOf( 8 | Type.Object({ 9 | trace: Type.String(), 10 | debug: Type.String(), 11 | info: Type.String(), 12 | warn: Type.String(), 13 | error: Type.String(), 14 | fatal: Type.String() 15 | }) 16 | ) 17 | }) 18 | ) 19 | 20 | const log: FastifyPluginAsync = async (server) => { 21 | server.route<{ Body: Static }>({ 22 | method: 'POST', 23 | url: '/', 24 | schema: { 25 | body: BodyType, 26 | tags: ['log'], 27 | response: { 28 | 200: Type.Strict( 29 | Type.Object({ 30 | message: Type.String({ 31 | description: 'Successful response' 32 | }) 33 | }) 34 | ) 35 | } 36 | }, 37 | handler: async (req) => { 38 | const { msg, level = 'info' } = req.body 39 | req.log[level]({ front: msg }) 40 | return { message: 'logged successfully' } 41 | } 42 | }) 43 | } 44 | 45 | export default log 46 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/routes/user/index.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginAsync } from 'fastify' 2 | 3 | const user: FastifyPluginAsync = async (server) => { 4 | server.route({ 5 | method: 'GET', 6 | url: '/', 7 | schema: { 8 | tags: ['user'], 9 | security: [ 10 | { 11 | apiKey: [] 12 | } 13 | ], 14 | response: { 15 | 200: { 16 | description: 'Successful response', 17 | type: 'object', 18 | properties: { 19 | id: { type: 'string' } 20 | } 21 | }, 22 | 400: { 23 | description: 'Bad request: authorization token authentication', 24 | type: 'object', 25 | properties: { 26 | message: { type: 'string' } 27 | } 28 | } 29 | } 30 | }, 31 | handler: async (req) => req.user 32 | }) 33 | } 34 | 35 | export default user 36 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/routes/user/user.test.ts: -------------------------------------------------------------------------------- 1 | import fastify, { FastifyInstance } from 'fastify' 2 | 3 | import userPlugin from '.' 4 | describe('user route', () => { 5 | let server: FastifyInstance 6 | 7 | beforeAll(async () => { 8 | server = fastify() 9 | server.register(userPlugin) 10 | server.addHook('onRequest', async (req) => { 11 | req.user = { id: 'id-01' } 12 | }) 13 | await server.ready() 14 | }) 15 | 16 | beforeEach(() => { 17 | jest.setTimeout(10e4) 18 | jest.resetAllMocks() 19 | }) 20 | 21 | afterAll(async () => server.close()) 22 | 23 | it('should return user', async () => { 24 | const response = await server.inject({ 25 | method: 'GET', 26 | url: '/' 27 | }) 28 | 29 | expect(JSON.parse(response.payload)).toEqual({ id: 'id-01' }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/server.test.ts: -------------------------------------------------------------------------------- 1 | import serverPlugin from './server' 2 | import config from './config' 3 | 4 | describe('server', () => { 5 | it('starts a server and register plugins', async () => { 6 | const server = { register: jest.fn() } 7 | server.register.mockReturnValue(server) 8 | 9 | serverPlugin(server as any, config) 10 | expect(server.register).toHaveBeenCalledTimes(6) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/titus-backend-typescript/src/server.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import swagger from 'fastify-swagger' 4 | import autoLoad from 'fastify-autoload' 5 | import fp from 'fastify-plugin' 6 | import { FastifyPluginAsync } from 'fastify' 7 | import fastifyCors from 'fastify-cors' 8 | import brokeneckFastify from '@nearform/brokeneck-fastify' 9 | import casbin from 'fastify-casbin' 10 | 11 | import configOptions from './config' 12 | 13 | const serverPlugin: FastifyPluginAsync = async ( 14 | server, 15 | config 16 | ) => { 17 | server 18 | // swagger must be registered before helmet 19 | .register(swagger, config.swagger) 20 | .register(fastifyCors, config.cors) 21 | .register(casbin, config.casbin) 22 | .register(autoLoad, { 23 | dir: path.join(__dirname, 'plugins'), 24 | options: config 25 | }) 26 | .register(autoLoad, { 27 | dir: path.join(__dirname, 'routes'), 28 | options: config 29 | }) 30 | 31 | if (config.enableAdmin) { 32 | await server.register(brokeneckFastify, config.auth) 33 | } 34 | } 35 | 36 | export default fp(serverPlugin) 37 | -------------------------------------------------------------------------------- /packages/titus-backend/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | ** 3 | 4 | # Allow files and directories 5 | !package.json 6 | !package-lock.json 7 | !index.js 8 | !lib/** -------------------------------------------------------------------------------- /packages/titus-backend/.env.sample: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | IS_PRODUCTION=false 3 | 4 | API_HOST=127.0.0.1 5 | API_PORT=5000 6 | 7 | CORS_ORIGIN=true 8 | 9 | ENABLE_ADMIN=false 10 | 11 | PG_HOST=localhost 12 | PG_PORT=5432 13 | PG_DB=titus 14 | PG_USER=titus 15 | PG_PASS=titus 16 | 17 | # auth0, azureAD, cognito 18 | AUTH_PROVIDER=cognito 19 | 20 | COGNITO_USER_POOL_ID= 21 | COGNITO_REGION= 22 | 23 | AUTH0_DOMAIN= 24 | AUTH0_CLIENT_ID= 25 | AUTH0_CLIENT_SECRET= 26 | AUTH0_AUDIENCE= 27 | AUTH0_GRANT_TYPE= 28 | AUTH0_CONNECTION= 29 | 30 | AD_TENANT= 31 | AD_APP_ID= 32 | AD_SECRET= 33 | 34 | SECRETS_STRATEGY=env 35 | SECRETS_PG_PASS=PG_PASS 36 | 37 | # In production, replace with a proper secrets manager 38 | # See other Secrets Managers supported by fastify-secrets 39 | # 40 | # This is an example for GCP Secrets Manager 41 | # 42 | # SECRETS_STRATEGY=gcp 43 | # SECRETS_PG_PASS=projects/494141678371/secrets/DB_PASSWORD/versions/latest 44 | # 45 | # This is an example for AWS Secrets Manager 46 | # 47 | # SECRETS_STRATEGY=aws 48 | # SECRETS_PG_PASS=DB_PASSWORD 49 | # 50 | # This is an example for Azure Key Vault 51 | # 52 | # SECRETS_STRATEGY=azure 53 | # SECRETS_PG_PASS=your-vault-name|your-secret-name 54 | # 55 | # provide email address for your authorized users (testing/dev only) 56 | # CHECK_AUTHZ_ADMIN_USERS=test@example.com,another@example.com 57 | -------------------------------------------------------------------------------- /packages/titus-backend/.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | node_modules/* 3 | pgdata/* 4 | -------------------------------------------------------------------------------- /packages/titus-backend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["standard", "prettier"], 4 | "plugins": ["prettier", "jest"], 5 | "env": { 6 | "es6": true, 7 | "browser": false, 8 | "jest/globals": true 9 | }, 10 | "rules": { 11 | "prettier/prettier": "error", 12 | "import/order": [ 13 | "error", 14 | { 15 | "newlines-between": "always" 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/titus-backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # postgres data files 64 | pgdata 65 | 66 | # gcp service account key 67 | key.json 68 | -------------------------------------------------------------------------------- /packages/titus-backend/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Simple example of CI to build a Docker container and push it to Amazon ECR 2 | variables: 3 | DOCKER_REGISTRY: ${ECR_REGISTRY} 4 | AWS_DEFAULT_REGION: eu-west-1 5 | APP_NAME: titus-backend 6 | DOCKER_HOST: tcp://docker:2375 7 | DOCKER_DRIVER: overlay2 8 | DOCKER_TLS_CERTDIR: "" 9 | WORKING_DIRECTORY: packages/titus-backend 10 | 11 | publish: 12 | stage: build 13 | image: 14 | name: docker:latest 15 | services: 16 | - docker:19-dind 17 | before_script: 18 | - apk add --no-cache curl jq python3 py3-pip 19 | - pip install awscli 20 | - docker login -u AWS -p $(aws ecr get-login-password --region $AWS_DEFAULT_REGION) $DOCKER_REGISTRY 21 | - aws --version 22 | - docker info 23 | - docker --version 24 | script: 25 | - cd $WORKING_DIRECTORY 26 | - docker build -t $DOCKER_REGISTRY/$APP_NAME:$CI_PIPELINE_IID . 27 | - docker push $DOCKER_REGISTRY/$APP_NAME:$CI_PIPELINE_IID 28 | 29 | -------------------------------------------------------------------------------- /packages/titus-backend/.nodemonrc: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["lib", ".env"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/titus-backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /packages/titus-backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine as builder 2 | ENV NODE_ENV production 3 | WORKDIR /service 4 | COPY --chown=node:node package.json package-lock.json index.js ./ 5 | COPY --chown=node:node lib lib 6 | RUN apk upgrade --available && \ 7 | apk add dumb-init && \ 8 | rm -rf /var/cache/apk/* 9 | RUN npm ci --only=production 10 | USER node 11 | CMD ["dumb-init", "node", "index.js"] 12 | -------------------------------------------------------------------------------- /packages/titus-backend/README.md: -------------------------------------------------------------------------------- 1 |   2 | 3 | [![Logo][logo-img]][docs] 4 | 5 |   6 | 7 | Your are browsing the repository for **titus-backend**. All documentation for titus is stored in a central location. Including for this repo. 8 | 9 | - Get the whole picture by viewing our **[Documentation][docs]** 10 | 11 | [docs]: https://nf-titus.netlify.app/ 12 | [logo-img]: ../../docs/img/Accel_Logo_Titus.svg 13 | -------------------------------------------------------------------------------- /packages/titus-backend/backend-descriptor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: titus-test 6 | name: titus-test 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: titus-test 12 | template: 13 | metadata: 14 | labels: 15 | app: titus-test 16 | spec: 17 | initContainers: 18 | - name: "titus-test-init" 19 | image: "groundnuty/k8s-wait-for:1.3" 20 | imagePullPolicy: IfNotPresent 21 | args: 22 | - "job" 23 | - "titus-db-migrate" # since the number is going to change from release to release the name is going to need to be dynamic 24 | containers: 25 | - image: titus:latest 26 | imagePullPolicy: Never 27 | name: titus 28 | env: 29 | - name: PG_HOST 30 | value: localhost 31 | - name: PG_PORT 32 | value: "5432" 33 | - name: PG_DB 34 | value: titus 35 | - name: PG_USER 36 | value: titus 37 | - name: PG_PASS 38 | value: titus 39 | - name: API_PORT 40 | value: "5000" 41 | - name: API_HOST 42 | value: localhost 43 | - name: SECRETS_PG_PASS 44 | value: PG_PASS 45 | - name: SECRETS_STRATEGY 46 | value: env 47 | - name: AUTH_PROVIDER 48 | value: cognito 49 | -------------------------------------------------------------------------------- /packages/titus-backend/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | const Fastify = require('fastify') 6 | const closeWithGrace = require('close-with-grace') 7 | 8 | const startServer = require('./lib/server') 9 | const config = require('./lib/config') 10 | 11 | // Crash on unhandledRejection 12 | process.on('unhandledRejection', err => { 13 | console.error(err) 14 | process.exit(1) 15 | }) 16 | 17 | const main = async () => { 18 | const server = Fastify(config.fastify) 19 | server.register(startServer, config) 20 | 21 | const closeListeners = closeWithGrace( 22 | { delay: 2000 }, 23 | async function ({ signal, manual, err }) { 24 | if (err) { 25 | server.log.error(err) 26 | } 27 | 28 | server.log.info({ signal, manual }, 'closing application') 29 | 30 | await server.close() 31 | } 32 | ) 33 | 34 | server.addHook('onClose', async (instance, done) => { 35 | closeListeners.uninstall() 36 | done() 37 | }) 38 | 39 | try { 40 | await server.listen(config.server) 41 | } catch (err) { 42 | server.log.error(err) 43 | process.exit(1) 44 | } 45 | } 46 | 47 | main() 48 | -------------------------------------------------------------------------------- /packages/titus-backend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: ['lib/**/*.{js,jsx}', '!**/node_modules/**'] 3 | } 4 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/config/auth-routes.js: -------------------------------------------------------------------------------- 1 | const authRoutes = [{ method: 'GET', regex: /^\/[user|auth]/ }] 2 | 3 | module.exports = authRoutes 4 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/config/authz/casbin_model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [role_definition] 8 | g = _, _ 9 | 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | [matchers] 14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act 15 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/config/authz/casbin_policy.csv: -------------------------------------------------------------------------------- 1 | # RBAC policy - the role_admin has admin access 2 | # users are added to the roles at runtime 3 | p, role_admin, admin, access 4 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/config/swagger.js: -------------------------------------------------------------------------------- 1 | const swagger = { 2 | routePrefix: '/documentation', 3 | exposeRoute: true, 4 | swagger: { 5 | info: { 6 | title: 'Titus swagger', 7 | description: 8 | 'The documentation of backend for the Titus project using fastify' 9 | }, 10 | consumes: ['application/json'], 11 | produces: ['application/json'], 12 | securityDefinitions: { 13 | apiKey: { 14 | type: 'apiKey', 15 | name: 'authorization', 16 | in: 'header' 17 | } 18 | } 19 | } 20 | } 21 | 22 | module.exports = swagger 23 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/plugins/auth/auth0/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | async function auth0(server, options) { 6 | server 7 | .register(require('fastify-auth0-verify'), options.auth.auth0) 8 | .register(require('./auth0-routes'), options) 9 | } 10 | 11 | module.exports = fp(auth0) 12 | module.exports.autoload = false 13 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/plugins/auth/cognito/cognito-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const AWS = require('aws-sdk') 5 | 6 | async function authRoutes(server, options) { 7 | const cognito = new AWS.CognitoIdentityServiceProvider({ 8 | region: options.auth.cognito.region 9 | }) 10 | 11 | server.route({ 12 | method: 'GET', 13 | url: '/auth', 14 | schema: { 15 | tags: ['cognito'], 16 | security: [ 17 | { 18 | apiKey: [] 19 | } 20 | ] 21 | }, 22 | onRequest: server.authenticate, 23 | handler: async ({ user }) => { 24 | return user || 'NO USER' 25 | } 26 | }) 27 | 28 | // This endpoint was created to test the cognito integration 29 | // The policies to access the UserPool are defined in the CDK deploy script 30 | server.route({ 31 | method: 'GET', 32 | url: '/userlist', 33 | schema: { 34 | tags: ['cognito'], 35 | security: [ 36 | { 37 | apiKey: [] 38 | } 39 | ] 40 | }, 41 | onRequest: server.authenticate, 42 | handler: async () => { 43 | const result = await cognito 44 | .listUsers({ 45 | UserPoolId: options.auth.cognito.userPoolId 46 | }) 47 | .promise() 48 | 49 | return result.Users.map(user => ({ 50 | username: user.Username, 51 | email: ( 52 | user.Attributes.find(attribute => attribute.Name === 'email') || {} 53 | ).Value, 54 | createdAt: user.UserCreateDate, 55 | enabled: user.Enabled, 56 | status: user.UserStatus 57 | })) 58 | } 59 | }) 60 | } 61 | 62 | module.exports = fp(authRoutes) 63 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/plugins/auth/cognito/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const buildGetJwks = require('get-jwks') 5 | 6 | function authenticate(request) { 7 | return request.jwtVerify() 8 | } 9 | 10 | async function cognito(server, options) { 11 | const getJwks = buildGetJwks() 12 | 13 | server.register(require('fastify-jwt'), { 14 | decode: { complete: true }, 15 | secret: (_, token, callback) => { 16 | const { 17 | header: { kid, alg }, 18 | payload: { iss } 19 | } = token 20 | 21 | getJwks 22 | .getPublicKey({ kid, domain: iss, alg }) 23 | .then(publicKey => callback(null, publicKey), callback) 24 | } 25 | }) 26 | 27 | server.decorate('authenticate', authenticate) 28 | server.register(require('./cognito-routes'), options) 29 | } 30 | 31 | module.exports = fp(cognito) 32 | module.exports.autoload = false 33 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/plugins/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | const authProviders = { 6 | auth0: require('./auth0'), 7 | azureAD: require('./azure-ad'), 8 | cognito: require('./cognito') 9 | } 10 | 11 | async function auth(server, options) { 12 | server.register(authProviders[options.auth.provider], options) 13 | } 14 | 15 | module.exports = fp(auth) 16 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/plugins/authz/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const { Forbidden } = require('http-errors') 5 | const jwt = require('jsonwebtoken') 6 | 7 | async function authz(server, options) { 8 | // add users to the casbin policy with admin role from the email addresses defined in .env 9 | const adminUsers = process.env.CHECK_AUTHZ_ADMIN_USERS || '' 10 | const policies = adminUsers 11 | .split(',') 12 | .map(u => server.casbin.addRoleForUser(u, 'role_admin')) 13 | await Promise.all(policies) 14 | 15 | server.decorate('authorizeAdminAccess', async function (request) { 16 | // the idToken contains user details from the auth provider (Auth0, Cognito or AzureAD) 17 | // different to the accessToken which is used to authenticate the user 18 | // passing it from the client in a custom header 19 | const idToken = request.headers['x-authz-id'] 20 | const authzUserData = jwt.decode(idToken) 21 | 22 | // Here's where the actual policy check goes ahead - based on the user's email in the idToken 23 | if ( 24 | !(await server.casbin.enforce(authzUserData.email, 'admin', 'access')) 25 | ) { 26 | throw new Forbidden('Cannot access admin') 27 | } 28 | }) 29 | } 30 | 31 | module.exports = fp(authz, { 32 | name: 'authz', 33 | dependencies: ['fastify-casbin'] 34 | }) 35 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/plugins/healthcheck/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | const { version } = require('../../../package.json') 6 | 7 | async function runCheck(server) { 8 | let dbRes 9 | try { 10 | dbRes = await server.pg.query('SELECT $1::text as message', [ 11 | 'Hello world!' 12 | ]) 13 | } catch (err) { 14 | // swallow error 15 | server.log.debug({ err }, `failed to read DB during health check`) 16 | } 17 | 18 | return { 19 | version, 20 | serverTimestamp: new Date(), 21 | status: 'ok', 22 | memoryUsage: server.memoryUsage && server.memoryUsage(), 23 | db: dbRes && dbRes.rowCount === 1 ? 'ok' : 'fail' 24 | } 25 | } 26 | 27 | async function healthCheck(server, options, next) { 28 | server.register(require('under-pressure'), parent => { 29 | return { 30 | ...options.underPressure, 31 | healthCheck: () => runCheck(parent) 32 | } 33 | }) 34 | 35 | next() 36 | } 37 | 38 | module.exports = fp(healthCheck, { 39 | name: 'healthCheck', 40 | dependencies: ['pg'] 41 | }) 42 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/plugins/pg/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('pg-range').install(require('pg')) 4 | const fp = require('fastify-plugin') 5 | 6 | async function plugin(server, options) { 7 | server.register(require('fastify-postgres'), { 8 | ...options.pgPlugin, 9 | password: server.secrets.dbPassword 10 | }) 11 | } 12 | 13 | module.exports = fp(plugin, { 14 | name: 'pg', 15 | dependencies: ['secrets-manager'] 16 | }) 17 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/plugins/secrets-manager/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const SECRETS_PLUGINS = { 5 | gcp: require('fastify-secrets-gcp'), 6 | aws: require('fastify-secrets-aws'), 7 | env: require('fastify-secrets-env'), 8 | azure: require('fastify-secrets-azure') 9 | } 10 | 11 | function getPlugin(options) { 12 | if (!options.secretsManager.strategy) { 13 | return SECRETS_PLUGINS.env 14 | } 15 | 16 | return SECRETS_PLUGINS[options.secretsManager.strategy] 17 | } 18 | 19 | async function secretsManager(server, options) { 20 | const plugin = getPlugin(options) 21 | if (!plugin) { 22 | throw new Error( 23 | `Invalid secrets manager strategy. Choose one of ${Object.keys( 24 | SECRETS_PLUGINS 25 | ).join(', ')}` 26 | ) 27 | } 28 | 29 | if (options.secretsManager.strategy === 'azure') { 30 | // add required options 31 | const [vaultName, secretName] = 32 | options.secretsManager.secrets.dbPassword.split('|') 33 | options.secretsManager.secrets.dbPassword = secretName 34 | options.secretsManager.clientOptions = { vaultName } 35 | } 36 | server.register(plugin, options.secretsManager) 37 | } 38 | 39 | module.exports = fp(secretsManager, { 40 | name: 'secrets-manager' 41 | }) 42 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/routes/authzcheck/authzcheck.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | async function authRoutes(server, options) { 6 | // this is a sample to check the signed-in user's permission to access this resource via casbin policies based on configuration in the OAuth provider 7 | // a real-world example would have more context around the user account and roles/scopes/permissions and policies 8 | server.route({ 9 | method: 'GET', 10 | url: '/authzcheck', 11 | schema: { 12 | tags: ['authz'], 13 | security: [ 14 | { 15 | apiKey: [] 16 | } 17 | ] 18 | }, 19 | onRequest: [server.authenticate, server.authorizeAdminAccess], 20 | handler: async req => { 21 | return { 22 | isAdmin: true, 23 | user: req.user 24 | } 25 | } 26 | }) 27 | } 28 | module.exports = fp(authRoutes) 29 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/routes/log/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | async function log(server, options) { 4 | server.route({ 5 | method: 'POST', 6 | url: '/', 7 | schema: { 8 | body: { 9 | type: 'object', 10 | properties: { 11 | msg: { type: 'string' }, 12 | level: { 13 | type: 'string', 14 | enum: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] 15 | } 16 | } 17 | }, 18 | tags: ['log'], 19 | response: { 20 | 200: { 21 | description: 'Successful response', 22 | type: 'object', 23 | properties: { 24 | message: { type: 'string' } 25 | } 26 | } 27 | } 28 | }, 29 | handler: async (req, res) => { 30 | const { msg, level = 'info' } = req.body 31 | req.log[level]({ front: msg }) 32 | return { message: 'logged successfully' } 33 | } 34 | }) 35 | } 36 | 37 | module.exports = log 38 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/routes/user/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | async function user(server, options) { 4 | server.route({ 5 | method: 'GET', 6 | url: '/', 7 | schema: { 8 | tags: ['user'], 9 | security: [ 10 | { 11 | apiKey: [] 12 | } 13 | ], 14 | response: { 15 | 200: { 16 | description: 'Successful response', 17 | type: 'object', 18 | properties: { 19 | id: { type: 'string' } 20 | } 21 | }, 22 | 400: { 23 | description: 'Bad request: authorization token authentication', 24 | type: 'object', 25 | properties: { 26 | message: { type: 'string' } 27 | } 28 | } 29 | } 30 | }, 31 | handler: async (req, res) => { 32 | return req.user 33 | } 34 | }) 35 | } 36 | 37 | module.exports = user 38 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/routes/user/user.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | describe('user route', () => { 4 | let server 5 | 6 | beforeAll(async () => { 7 | server = require('fastify')() 8 | server.register(require('.')) 9 | server.addHook('onRequest', async (req, res) => { 10 | req.user = { id: 'id-01' } 11 | }) 12 | await server.ready() 13 | }) 14 | 15 | beforeEach(() => { 16 | jest.setTimeout(10e4) 17 | jest.resetAllMocks() 18 | }) 19 | 20 | afterAll(async () => server.close()) 21 | 22 | it('should return user', async () => { 23 | const response = await server.inject({ 24 | method: 'GET', 25 | url: '/' 26 | }) 27 | 28 | expect(JSON.parse(response.payload)).toEqual({ id: 'id-01' }) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const helmet = require('fastify-helmet') 6 | const swagger = require('fastify-swagger') 7 | const autoLoad = require('fastify-autoload') 8 | const fp = require('fastify-plugin') 9 | 10 | async function plugin(server, config) { 11 | server 12 | // swagger must be registered before helmet 13 | .register(swagger, require('./config/swagger')) 14 | .register(helmet, instance => ({ 15 | contentSecurityPolicy: { 16 | directives: { 17 | ...helmet.contentSecurityPolicy.getDefaultDirectives(), 18 | 'form-action': [`'self'`], 19 | 'img-src': [`'self'`, 'data:', 'validator.swagger.io'], 20 | 'script-src': [`'self'`].concat(instance.swaggerCSP.script), 21 | 'style-src': [`'self'`, 'https:'].concat(instance.swaggerCSP.style) 22 | } 23 | } 24 | })) 25 | .register(require('fastify-cors'), config.cors) 26 | .register(require('fastify-casbin'), config.casbin) 27 | .register(autoLoad, { 28 | dir: path.join(__dirname, 'plugins'), 29 | options: config 30 | }) 31 | .register(autoLoad, { 32 | dir: path.join(__dirname, 'routes'), 33 | options: config 34 | }) 35 | 36 | if (config.enableAdmin) { 37 | await server.register(require('@nearform/brokeneck-fastify'), config.auth) 38 | } 39 | } 40 | 41 | module.exports = fp(plugin) 42 | -------------------------------------------------------------------------------- /packages/titus-backend/lib/server.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | describe('server', () => { 4 | it('starts a server and register plugins', async () => { 5 | const server = { register: jest.fn() } 6 | server.register.mockReturnValue(server) 7 | require('./server')(server, require('./config')) 8 | expect(server.register).toHaveBeenCalledTimes(6) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /packages/titus-db-manager/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | ** 3 | 4 | # Allow files and directories 5 | !package.json 6 | !package-lock.json 7 | !index.js 8 | !migration-start.js 9 | !seed/** 10 | !migrate/** 11 | !truncate/** 12 | !lib/** 13 | -------------------------------------------------------------------------------- /packages/titus-db-manager/.env.sample: -------------------------------------------------------------------------------- 1 | HTTP_HOST=0.0.0.0 2 | HTTP_PORT=3002 3 | NODE_ENV=development 4 | IS_PRODUCTION=false 5 | 6 | PG_HOST=localhost 7 | PG_PORT=5432 8 | PG_USER=titus 9 | PG_PASSWORD=titus 10 | PG_DATABASE=titus 11 | 12 | SECRETS_STRATEGY=env 13 | SECRETS_PG_PASS=PG_PASSWORD 14 | 15 | # In production, replace with a proper secrets manager 16 | # See other Secrets Managers supported by fastify-secrets 17 | # 18 | # This is an example for GCP Secrets Manager 19 | # 20 | # SECRETS_STRATEGY=gcp 21 | # SECRETS_PG_PASS=projects/494141678371/secrets/DB_PASSWORD/versions/latest 22 | # 23 | # This is an example for AWS Secrets Manager 24 | # 25 | # SECRETS_STRATEGY=aws 26 | # SECRETS_PG_PASS=DB_PASSWORD 27 | # 28 | # 29 | # This is an example for Azure Key Vault 30 | # 31 | # SECRETS_STRATEGY=azure 32 | # SECRETS_PG_PASS=your-vault-name|your-secret-name 33 | -------------------------------------------------------------------------------- /packages/titus-db-manager/.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | node_modules/* 3 | pgdata/* 4 | -------------------------------------------------------------------------------- /packages/titus-db-manager/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["standard", "prettier"], 4 | "plugins": ["prettier", "jest"], 5 | "env": { 6 | "es6": true, 7 | "browser": false, 8 | "jest/globals": true 9 | }, 10 | "rules": { 11 | "prettier/prettier": "error", 12 | "import/order": [ 13 | "error", 14 | { 15 | "newlines-between": "always" 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/titus-db-manager/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Simple example of CI to build a Docker container and push it to Amazon ECR 2 | variables: 3 | DOCKER_REGISTRY: ${ECR_REGISTRY} 4 | AWS_DEFAULT_REGION: eu-west-1 5 | APP_NAME: titus-backend 6 | DOCKER_HOST: tcp://docker:2375 7 | DOCKER_DRIVER: overlay2 8 | DOCKER_TLS_CERTDIR: "" 9 | WORKING_DIRECTORY: packages/titus-db-manager 10 | 11 | publish: 12 | stage: build 13 | image: 14 | name: docker:latest 15 | services: 16 | - docker:19-dind 17 | before_script: 18 | - apk add --no-cache curl jq python3 py3-pip 19 | - pip install awscli 20 | - docker login -u AWS -p $(aws ecr get-login-password --region $AWS_DEFAULT_REGION) $DOCKER_REGISTRY 21 | - aws --version 22 | - docker info 23 | - docker --version 24 | script: 25 | - cd $WORKING_DIRECTORY 26 | - docker build -t $DOCKER_REGISTRY/$APP_NAME:$CI_PIPELINE_IID . 27 | - docker push $DOCKER_REGISTRY/$APP_NAME:$CI_PIPELINE_IID 28 | 29 | -------------------------------------------------------------------------------- /packages/titus-db-manager/.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "testEnvironment": "node", 3 | "collectCoverageFrom": [ 4 | "seed/**/*.{js,jsx}", 5 | "truncate/**/*.{js,jsx}", 6 | "migrate/**/*.{js,jsx}", 7 | "!**/node_modules/**" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/titus-db-manager/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /packages/titus-db-manager/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | ENV NODE_ENV production 3 | WORKDIR /titus-db-manager 4 | COPY --chown=node:node package.json package-lock.json index.js migration-start.js ./ 5 | COPY --chown=node:node lib lib 6 | COPY --chown=node:node seed seed 7 | COPY --chown=node:node migrate migrate 8 | COPY --chown=node:node truncate truncate 9 | RUN apk upgrade --available && \ 10 | apk add dumb-init && \ 11 | rm -rf /var/cache/apk/* 12 | RUN npm ci --only=production 13 | USER node 14 | CMD ["dumb-init", "node", "./lib"] 15 | -------------------------------------------------------------------------------- /packages/titus-db-manager/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('dotenv-expand')(require('dotenv').config()) 4 | const logger = require('pino')() 5 | const { Client: ClientGcp } = require('fastify-secrets-gcp') 6 | const { Client: ClientAws } = require('fastify-secrets-aws') 7 | const { Client: ClientEnv } = require('fastify-secrets-env') 8 | 9 | const start = require('./migration-start') 10 | 11 | function getClient() { 12 | if (!process.env.SECRETS_STRATEGY || process.env.SECRETS_STRATEGY === 'env') { 13 | return new ClientEnv() 14 | } 15 | 16 | if (process.env.SECRETS_STRATEGY === 'gcp') { 17 | return new ClientGcp() 18 | } 19 | 20 | if (process.env.SECRETS_STRATEGY === 'aws') { 21 | return new ClientAws() 22 | } 23 | 24 | throw new Error('Unsupported secrets manager strategy') 25 | } 26 | 27 | async function run() { 28 | try { 29 | const client = getClient() 30 | const password = await client.get(process.env.SECRETS_PG_PASS) 31 | 32 | const credentials = { 33 | host: process.env.PG_HOST, 34 | port: process.env.PG_PORT, 35 | database: process.env.PG_DATABASE, 36 | user: process.env.PG_USER, 37 | password 38 | } 39 | 40 | const [ 41 | action = 'migrate', 42 | schema = 'public', 43 | migrationsLoadDir = '/migrations' 44 | ] = process.argv.slice(2) 45 | 46 | await start(action, credentials, { 47 | logger, 48 | schema, 49 | dir: migrationsLoadDir 50 | }) 51 | } catch (err) { 52 | logger.error(err, 'An Error has occurred, stopping') 53 | process.exit(1) 54 | } 55 | } 56 | 57 | run() 58 | -------------------------------------------------------------------------------- /packages/titus-db-manager/lambda.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AWS = require('aws-sdk') 4 | const logger = require('pino')() 5 | 6 | function getSecret(secretName) { 7 | return new Promise((resolve, reject) => { 8 | const client = new AWS.SecretsManager() 9 | client.getSecretValue({ SecretId: secretName }, function (err, data) { 10 | if (err) { 11 | console.log(err) 12 | reject(err) 13 | return 14 | } 15 | const credentials = JSON.parse(data.SecretString) 16 | resolve({ 17 | host: credentials.host, 18 | port: credentials.port, 19 | database: credentials.dbname, 20 | username: credentials.username, 21 | password: credentials.password 22 | }) 23 | }) 24 | }) 25 | } 26 | 27 | const start = require('./migration-start') 28 | 29 | module.exports = { 30 | handler: async () => { 31 | try { 32 | logger.info('Migration Lambda Start') 33 | 34 | logger.info('Retrieve the secrets from ', process.env.SECRET_ARN) 35 | const credentials = await getSecret(process.env.SECRET_ARN) 36 | await start('migrate', credentials) 37 | await start('seed', credentials) 38 | logger.info('Migration done successful') 39 | } catch (e) { 40 | logger.error('Migration failed') 41 | logger.error(e) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/titus-db-manager/lib/build-server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fastify = require('fastify') 4 | 5 | const SecretsManager = require('./secrets-manager') 6 | 7 | const buildServer = config => { 8 | const fastify = Fastify({ 9 | logger: true 10 | }) 11 | fastify.register(SecretsManager) 12 | fastify.register(require('./routes')) 13 | 14 | return fastify 15 | } 16 | 17 | module.exports = buildServer 18 | -------------------------------------------------------------------------------- /packages/titus-db-manager/lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const envSchema = require('env-schema') 6 | const S = require('fluent-schema') 7 | 8 | const config = envSchema({ 9 | dotenv: { path: path.join(__dirname, '..', '.env') }, 10 | schema: S.object() 11 | .prop('HTTP_HOST', S.string().default('localhost')) 12 | .prop('HTTP_PORT', S.string().required()) 13 | .prop('NODE_ENV', S.string().required()) 14 | .prop('IS_PRODUCTION', S.boolean().required()) 15 | .prop('PG_HOST', S.string().required()) 16 | .prop('PG_PORT', S.string().required()) 17 | .prop('PG_USER', S.string().required()) 18 | .prop('PG_DATABASE', S.string().required()) 19 | .prop('SECRETS_STRATEGY', S.string()) 20 | .prop('SECRETS_PG_PASS', S.string().required()) 21 | }) 22 | 23 | /** 24 | * Global configuration, from env variables 25 | */ 26 | module.exports = { 27 | isProduction: config.IS_PRODUCTION, 28 | // Fastify options: https://www.fastify.io/docs/latest/Server/: 29 | fastify: { 30 | host: config.HTTP_HOST, 31 | port: +config.HTTP_PORT 32 | }, 33 | // pg-pool options: https://github.com/brianc/node-pg-pool#create 34 | pgPlugin: { 35 | host: config.PG_HOST, 36 | port: +config.PG_PORT, 37 | database: config.PG_DATABASE, 38 | user: config.PG_USER, 39 | poolSize: 10, 40 | idleTimeoutMillis: 30000 41 | }, 42 | secretsManager: { 43 | strategy: config.SECRETS_STRATEGY, 44 | secrets: { 45 | dbPassword: config.SECRETS_PG_PASS 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/titus-db-manager/lib/config.test.js: -------------------------------------------------------------------------------- 1 | const config = require('./config') 2 | 3 | describe('config', () => { 4 | it('check configuration', () => { 5 | expect(config).toMatchObject({ 6 | isProduction: false, 7 | fastify: { host: '0.0.0.0', port: 3002 }, 8 | pgPlugin: { 9 | host: 'localhost', 10 | port: 5432, 11 | database: 'titus', 12 | user: 'titus', 13 | poolSize: 10, 14 | idleTimeoutMillis: 30000 15 | } 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /packages/titus-db-manager/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = require('./config') 4 | const buildServer = require('./build-server') 5 | 6 | const server = buildServer() 7 | 8 | // Run the server! 9 | server.listen(config.fastify, (err, address) => { 10 | if (err) throw err 11 | server.log.info(`server listening on ${address}`) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/titus-db-manager/lib/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | /** 5 | * Configure and starts Fastify server with all required plugins and routes 6 | * @async 7 | * @param {Object} config - optional configuration options (default to ./config module) 8 | * May contain a key per plugin (key is plugin name), and an extra 9 | * 'fastify' key containing the server configuration object 10 | * @returns {Fastify.Server} started Fastify server instance 11 | */ 12 | 13 | async function plugin(server, config) { 14 | server.register(require('./routes')) 15 | } 16 | 17 | module.exports = fp(plugin) 18 | -------------------------------------------------------------------------------- /packages/titus-db-manager/lib/secrets-manager.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const SECRETS_PLUGINS = { 5 | gcp: require('fastify-secrets-gcp'), 6 | aws: require('fastify-secrets-aws'), 7 | env: require('fastify-secrets-env'), 8 | azure: require('fastify-secrets-azure') 9 | } 10 | 11 | const config = require('./config') 12 | 13 | function getPlugin() { 14 | if (!config.secretsManager.strategy) { 15 | return SECRETS_PLUGINS.env 16 | } 17 | 18 | return SECRETS_PLUGINS[config.secretsManager.strategy] 19 | } 20 | 21 | async function secretsManager(server) { 22 | const plugin = getPlugin() 23 | if (!plugin) { 24 | throw new Error( 25 | `Invalid secrets manager strategy. Choose one of ${Object.keys( 26 | SECRETS_PLUGINS 27 | ).join(', ')}` 28 | ) 29 | } 30 | 31 | if (config.secretsManager.strategy === 'azure') { 32 | // add required options 33 | const [vaultName, secretName] = 34 | config.secretsManager.secrets.dbPassword.split('|') 35 | config.secretsManager.secrets.dbPassword = secretName 36 | config.secretsManager.clientOptions = { vaultName } 37 | } 38 | server.register(plugin, config.secretsManager) 39 | } 40 | 41 | module.exports = fp(secretsManager, { 42 | name: 'secrets-manager' 43 | }) 44 | -------------------------------------------------------------------------------- /packages/titus-db-manager/migrate/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async function migrate(pg, { logger }) { 4 | await pg.migrate() 5 | logger.info('Database migrated') 6 | return true 7 | } 8 | -------------------------------------------------------------------------------- /packages/titus-db-manager/migrate/index.test.js: -------------------------------------------------------------------------------- 1 | const migrate = require('.') 2 | 3 | describe('migrate', () => { 4 | it('runs postgres migration', async () => { 5 | const pg = { 6 | migrate: jest.fn().mockResolvedValueOnce(null) 7 | } 8 | const result = await migrate(pg, { 9 | logger: { 10 | info: jest.fn() 11 | } 12 | }) 13 | expect(result).toBe(true) 14 | expect(pg.migrate).toHaveBeenCalledTimes(1) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/titus-db-manager/migrate/migrations/001.do.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE some_table ( 2 | id SERIAL PRIMARY KEY NOT NULL, 3 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 4 | updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 5 | name VARCHAR(64), 6 | short_desc VARCHAR(128), 7 | detailed_desc VARCHAR(1024) 8 | ); 9 | -------------------------------------------------------------------------------- /packages/titus-db-manager/migrate/migrations/002.do.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE another_table ( 2 | id SERIAL PRIMARY KEY NOT NULL, 3 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 4 | updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 5 | name VARCHAR(64), 6 | short_desc VARCHAR(128), 7 | detailed_desc VARCHAR(1024) 8 | ); 9 | -------------------------------------------------------------------------------- /packages/titus-db-manager/migration-descriptor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: titus-db-migrate 5 | labels: 6 | app: titus-db-migrate 7 | spec: 8 | backoffLimit: 1 9 | template: 10 | metadata: 11 | labels: 12 | app: titus-db-migrate 13 | spec: 14 | containers: 15 | - name: titus-db-migrate 16 | image: titus-db-manager:latest 17 | imagePullPolicy: Never 18 | command: ["npm"] 19 | args: ["run", "db:migrate"] 20 | env: 21 | - name: PG_HOST 22 | value: localhost 23 | - name: PG_PORT 24 | value: "5432" 25 | - name: PG_DB 26 | value: titus 27 | - name: PG_USER 28 | value: titus 29 | - name: PG_PASS 30 | value: titus 31 | - name: API_PORT 32 | value: "5000" 33 | - name: API_HOST 34 | value: localhost 35 | - name: SECRETS_PG_PASS 36 | value: PG_PASS 37 | - name: SECRETS_STRATEGY 38 | value: env 39 | - name: AUTH_PROVIDER 40 | value: cognito 41 | restartPolicy: Never 42 | -------------------------------------------------------------------------------- /packages/titus-db-manager/migration-start.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const Postgrator = require('postgrator') 6 | const { Client } = require('pg') 7 | 8 | const seed = require('./seed') 9 | const migrate = require('./migrate') 10 | const truncate = require('./truncate') 11 | 12 | const start = async (action, credentials, opts = {}) => { 13 | const { logger, schema, dir = schema } = opts 14 | 15 | logger.info(`Starting titus-db-manager tool`) 16 | 17 | if (!(action === 'migrate' || action === 'seed' || action === 'truncate')) { 18 | logger.warn(`${action} is not a valid action, stopping`) 19 | return 20 | } 21 | 22 | logger.info(`Running: ${action} on schema '${schema}'`) 23 | const client = new Client(credentials) 24 | await client.connect() 25 | 26 | switch (action) { 27 | case 'migrate': { 28 | const migrationPattern = path.join(__dirname, 'migrate', dir, '*.sql') 29 | logger.info(`Running migrations matching ${migrationPattern}`) 30 | const pg = await new Postgrator({ 31 | validateChecksums: true, 32 | newline: 'LF', 33 | migrationPattern, 34 | driver: 'pg', 35 | schemaTable: `${schema}.schema_migrations`, 36 | currentSchema: schema, 37 | execQuery: query => client.query(query) 38 | }) 39 | await migrate(pg, opts) 40 | break 41 | } 42 | case 'seed': { 43 | await seed(client, opts) 44 | break 45 | } 46 | case 'truncate': { 47 | await truncate(client, opts) 48 | } 49 | } 50 | 51 | await client.end() 52 | } 53 | 54 | module.exports = start 55 | -------------------------------------------------------------------------------- /packages/titus-db-manager/seed/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async function seed(pg, { logger }) { 4 | logger.info('Database seeded') 5 | return true 6 | } 7 | -------------------------------------------------------------------------------- /packages/titus-db-manager/seed/index.test.js: -------------------------------------------------------------------------------- 1 | const seed = require('.') 2 | 3 | describe('seed', () => { 4 | it('runs seed command', async () => { 5 | const pg = {} 6 | const result = await seed(pg, { 7 | logger: { 8 | info: jest.fn() 9 | } 10 | }) 11 | expect(result).toBe(true) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /packages/titus-db-manager/truncate/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async function truncate(pg, { schema, logger }) { 4 | await pg.query(` 5 | TRUNCATE TABLE 6 | ${schema}.some_table 7 | RESTART IDENTITY CASCADE; 8 | `) 9 | logger.info('Database truncated') 10 | return true 11 | } 12 | -------------------------------------------------------------------------------- /packages/titus-db-manager/truncate/index.test.js: -------------------------------------------------------------------------------- 1 | const truncate = require('.') 2 | 3 | describe('truncate', () => { 4 | it('runs truncate command', async () => { 5 | const pg = { 6 | query: jest.fn().mockResolvedValueOnce(null) 7 | } 8 | const result = await truncate(pg, { 9 | schema: 'foo', 10 | logger: { 11 | info: jest.fn() 12 | } 13 | }) 14 | expect(result).toBe(true) 15 | expect(pg.query).toHaveBeenCalledWith(` 16 | TRUNCATE TABLE 17 | foo.some_table 18 | RESTART IDENTITY CASCADE; 19 | `) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/titus-frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": [] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/titus-frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | ** 3 | 4 | # Allow files and directories 5 | !package.json 6 | !package-lock.json 7 | !jsconfig.json 8 | !.eslintrc 9 | !.eslintignore 10 | !.prettierrc 11 | !src/** 12 | !public/** 13 | !.storybook/** 14 | !docker/** 15 | !.env 16 | -------------------------------------------------------------------------------- /packages/titus-frontend/.env.sample: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | 3 | BROWSER=none 4 | 5 | REACT_APP_ENABLE_ADMIN=false 6 | 7 | # Set auth provider 8 | # AD, AUTH0, AWS, MEM, TITUS 9 | REACT_APP_AUTH_PROVIDER=MEM 10 | 11 | # Set with the data for your Auth0 app 12 | # REACT_APP_AUTH0_DOMAIN= 13 | # REACT_APP_AUTH0_CLIENT_ID= 14 | # REACT_APP_AUTH0_AUDIENCE= 15 | 16 | # If set retrieve the cognito config from the url defined 17 | # REACT_APP_REMOTE_AWS_CONFIG_PATH=/config/v1 18 | 19 | # Set with the data for your AWS Amplify app 20 | # NOTE: do not leave these defined when using auth providers other than cognito - there are conditional checks in the the code to render cognito specific components 21 | # REACT_APP_AWS_REGION= 22 | # REACT_APP_AWS_POOL_ID= 23 | # REACT_APP_AWS_POOL_CLIENT_ID= 24 | # REACT_APP_AWS_IDENTITY_POOL_ID= 25 | 26 | # Set with the data for your Azure AD instance 27 | # REACT_APP_AD_TENANT= 28 | # REACT_APP_AD_APP_ID= 29 | 30 | REACT_APP_API_PATH='http://localhost:5000' 31 | REACT_APP_ADMIN_API_PATH='http://localhost:5000/graphql' 32 | -------------------------------------------------------------------------------- /packages/titus-frontend/.env.test: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | 3 | BROWSER=none 4 | 5 | # Set auth provider 6 | REACT_APP_AUTH_PROVIDER=MEM 7 | REACT_APP_REMOTE_AWS_CONFIG_PATH= 8 | 9 | # Set with the data for your Auth0 app 10 | # REACT_APP_AUTH0_DOMAIN= 11 | # REACT_APP_AUTH0_CLIENT_ID= 12 | # REACT_APP_AUTH0_AUDIENCE= 13 | 14 | # Set with the data for your AWS Amplify app 15 | # REACT_APP_AWS_REGION= 16 | # REACT_APP_AWS_POOL_ID= 17 | # REACT_APP_AWS_POOL_CLIENT_ID= 18 | # REACT_APP_AWS_IDENTITY_POOL_ID= 19 | 20 | # Set with the data for your Azure AD instance 21 | # REACT_APP_AD_TENANT= 22 | # REACT_APP_AD_APP_ID= 23 | 24 | REACT_APP_API_PATH='http://localhost:5000' 25 | -------------------------------------------------------------------------------- /packages/titus-frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | build/* 3 | coverage/* 4 | node_modules/* 5 | src/**/*.css 6 | src/validateEnv.js 7 | src/validateEnv.test.js 8 | .eslintcache -------------------------------------------------------------------------------- /packages/titus-frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "react-app", 4 | "plugin:prettier/recommended", 5 | "plugin:jsx-a11y/recommended" 6 | ], 7 | "plugins": ["prettier", "jsx-a11y"], 8 | "env": { 9 | "es6": true, 10 | "browser": true 11 | }, 12 | "rules": { 13 | "import/no-anonymous-default-export": "off", 14 | "import/order": [ 15 | "error", 16 | { 17 | "newlines-between": "always" 18 | } 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/titus-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /storybook-static 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | yarn.lock 26 | -------------------------------------------------------------------------------- /packages/titus-frontend/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Simple example of CI to build a Docker container and push it to Amazon ECR 2 | variables: 3 | DOCKER_REGISTRY: ${ECR_REGISTRY} 4 | AWS_DEFAULT_REGION: eu-west-1 5 | APP_NAME: titus-frontend 6 | DOCKER_HOST: tcp://docker:2375 7 | DOCKER_DRIVER: overlay2 8 | DOCKER_TLS_CERTDIR: "" 9 | WORKING_DIRECTORY: packages/titus-frontend 10 | 11 | publish: 12 | stage: build 13 | image: 14 | name: docker:latest 15 | services: 16 | - docker:19-dind 17 | before_script: 18 | - apk add --no-cache curl jq python3 py3-pip 19 | - pip install awscli 20 | - docker login -u AWS -p $(aws ecr get-login-password --region $AWS_DEFAULT_REGION) $DOCKER_REGISTRY 21 | - aws --version 22 | - docker info 23 | - docker --version 24 | script: 25 | - cd $WORKING_DIRECTORY 26 | - echo -n $BASE64_ENV_FILE | base64 -d > .env 27 | - docker build -t $DOCKER_REGISTRY/$APP_NAME:$CI_PIPELINE_IID . 28 | - docker push $DOCKER_REGISTRY/$APP_NAME:$CI_PIPELINE_IID 29 | 30 | -------------------------------------------------------------------------------- /packages/titus-frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "semi": false, 4 | "singleQuote": true, 5 | "trailingComma": "none" 6 | } 7 | -------------------------------------------------------------------------------- /packages/titus-frontend/.storybook/decorator-components.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Preview } from '@storybook/addon-docs/blocks' 3 | 4 | export const StyledPreview = ({ children, ...props }) => ( 5 | 6 | {children} 7 | 8 | ) 9 | -------------------------------------------------------------------------------- /packages/titus-frontend/.storybook/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine as builder 2 | WORKDIR /service 3 | COPY --chown=node:node package.json package-lock.json jsconfig.json .eslintrc .eslintignore .prettierrc ./ 4 | COPY --chown=node:node src src 5 | COPY --chown=node:node public public 6 | COPY --chown=node:node .storybook .storybook 7 | RUN apk upgrade --available && \ 8 | rm -rf /var/cache/apk/* 9 | RUN npm ci && \ 10 | npm run storybook:build 11 | 12 | FROM nginx:stable-alpine 13 | COPY .storybook/docker/nginx.conf /etc/nginx/conf.d/default.conf 14 | COPY .storybook/docker/nginx.htpasswd /etc/nginx/conf.d/nginx.htpasswd 15 | COPY --from=builder /service/storybook-static /var/www 16 | # implement changes required to run NGINX as an unprivileged user 17 | RUN sed -i -e '/user/!b' -e '/nginx/!b' -e '/nginx/d' /etc/nginx/nginx.conf \ 18 | && sed -i 's!/var/run/nginx.pid!/tmp/nginx.pid!g' /etc/nginx/nginx.conf \ 19 | && sed -i "/^http {/a \ proxy_temp_path /tmp/proxy_temp;\n client_body_temp_path /tmp/client_temp;\n fastcgi_temp_path /tmp/fastcgi_temp;\n uwsgi_temp_path /tmp/uwsgi_temp;\n scgi_temp_path /tmp/scgi_temp;\n" /etc/nginx/nginx.conf \ 20 | # nginx user must own the cache directory to write cache 21 | && chown -R 101:0 /var/cache/nginx \ 22 | && chmod -R g+w /var/cache/nginx 23 | USER 101 24 | CMD ["nginx", "-g", "daemon off;"] 25 | -------------------------------------------------------------------------------- /packages/titus-frontend/.storybook/docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | server_name _; 4 | root /var/www/; 5 | index index.html; 6 | location / { 7 | try_files $uri /index.html; 8 | 9 | auth_basic "Authentication"; 10 | auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/titus-frontend/.storybook/docker/nginx.htpasswd: -------------------------------------------------------------------------------- 1 | admin:$apr1$zrp7t5vh$BOSkyXtuPSaC8HhhCcL.e0 -------------------------------------------------------------------------------- /packages/titus-frontend/.storybook/main.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | stories: ['../src/**/*.story.js'], 5 | addons: [ 6 | '@storybook/preset-create-react-app', 7 | '@storybook/addon-essentials', 8 | '@storybook/addon-a11y', 9 | '@storybook/addon-links', 10 | 'storybook-readme/register' 11 | ], 12 | webpackFinal: async config => { 13 | config.resolve.alias = { 14 | ...config.resolve.alias, 15 | '.storybook': path.resolve(__dirname) 16 | } 17 | return config 18 | }, 19 | core: { 20 | builder: 'webpack5' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/titus-frontend/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { addDecorator, addParameters } from '@storybook/react' 3 | import { addReadme } from 'storybook-readme' 4 | 5 | import newTheme from './new-theme' 6 | 7 | addParameters({ 8 | options: { 9 | name: 'Titus', 10 | theme: newTheme 11 | } 12 | }) 13 | 14 | addDecorator(addReadme) 15 | 16 | const storyWrapper = story =>
{story()}
17 | addDecorator(storyWrapper) 18 | 19 | import 'styles.css' 20 | -------------------------------------------------------------------------------- /packages/titus-frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine as builder 2 | ARG REACT_APP_API_PATH=http://localhost:5000 3 | ARG REACT_APP_ADMIN_API_PATH=$REACT_APP_API_PATH/graphql 4 | ENV SKIP_PREFLIGHT_CHECK=true 5 | WORKDIR /service 6 | COPY --chown=node:node package.json package-lock.json jsconfig.json .eslintrc .eslintignore .prettierrc .env ./ 7 | COPY --chown=node:node src src 8 | COPY --chown=node:node public public 9 | RUN apk upgrade --available && \ 10 | rm -rf /var/cache/apk/* 11 | RUN npm ci && \ 12 | npm run build 13 | 14 | FROM nginx:stable-alpine 15 | COPY docker/nginx.conf /etc/nginx/conf.d/default.conf 16 | COPY --from=builder /service/build/ /var/www 17 | # implement changes required to run NGINX as an unprivileged user 18 | RUN sed -i -e '/user/!b' -e '/nginx/!b' -e '/nginx/d' /etc/nginx/nginx.conf \ 19 | && sed -i 's!/var/run/nginx.pid!/tmp/nginx.pid!g' /etc/nginx/nginx.conf \ 20 | && sed -i "/^http {/a \ proxy_temp_path /tmp/proxy_temp;\n client_body_temp_path /tmp/client_temp;\n fastcgi_temp_path /tmp/fastcgi_temp;\n uwsgi_temp_path /tmp/uwsgi_temp;\n scgi_temp_path /tmp/scgi_temp;\n" /etc/nginx/nginx.conf \ 21 | # nginx user must own the cache directory to write cache 22 | && chown -R 101:0 /var/cache/nginx \ 23 | && chmod -R g+w /var/cache/nginx 24 | USER 101 25 | CMD ["nginx", "-g", "daemon off;"] 26 | -------------------------------------------------------------------------------- /packages/titus-frontend/README.md: -------------------------------------------------------------------------------- 1 |   2 | 3 | [![Logo][logo-img]][docs] 4 | 5 |   6 | 7 | Your are browsing the repository for __titus-frontend__. All documentation for titus is stored in a central location. Including for this repo. 8 | 9 | - Get the whole picture by viewing our __[Documentation][docs]__ 10 | 11 | [docs]: https://nf-titus.netlify.app/ 12 | [logo-img]: ../../docs/img/Accel_Logo_Titus.svg 13 | -------------------------------------------------------------------------------- /packages/titus-frontend/bin/validateEnv.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('../src/validateEnv').validate() 4 | -------------------------------------------------------------------------------- /packages/titus-frontend/docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | server_name _; 4 | root /var/www/; 5 | index index.html; 6 | location / { 7 | try_files $uri /index.html; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/titus-frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | }, 5 | "include": ["src"] 6 | } 7 | -------------------------------------------------------------------------------- /packages/titus-frontend/public/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/packages/titus-frontend/public/icons/favicon.png -------------------------------------------------------------------------------- /packages/titus-frontend/public/icons/icons-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/packages/titus-frontend/public/icons/icons-192.png -------------------------------------------------------------------------------- /packages/titus-frontend/public/icons/icons-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/packages/titus-frontend/public/icons/icons-512.png -------------------------------------------------------------------------------- /packages/titus-frontend/public/icons/maskable-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/packages/titus-frontend/public/icons/maskable-icon.png -------------------------------------------------------------------------------- /packages/titus-frontend/public/icons/non-transparent-icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/packages/titus-frontend/public/icons/non-transparent-icon-512.png -------------------------------------------------------------------------------- /packages/titus-frontend/public/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Titus FE", 3 | "name": "Titus Frontend", 4 | "icons": [ 5 | { 6 | "src": "icons/favicon.png", 7 | "type": "image/png", 8 | "sizes": "128x128" 9 | }, 10 | { 11 | "src": "icons/icons-192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "icons/icons-512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | }, 20 | { 21 | "src": "icons/maskable-icon.png", 22 | "type": "image/png", 23 | "sizes": "512x512", 24 | "purpose": "maskable" 25 | } 26 | ], 27 | "start_url": "./index.html", 28 | "display": "standalone", 29 | "theme_color": "#000000", 30 | "background_color": "#ffffff" 31 | } 32 | -------------------------------------------------------------------------------- /packages/titus-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # empty robots.txt to ensure the index.html page isn't fallen back to when crawled 2 | # please update as necessary to suit your project requirements 3 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/app.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import { BrowserRouter } from 'react-router-dom' 3 | import AppRouter from 'lib/router' 4 | import { AuthProvider } from 'components/authentication/authentication-context' 5 | import Loading from 'components/loading' 6 | import RemoteAwsConfig from 'components/remote-aws-config' 7 | import ErrorBoundary from 'components/error-boundary' 8 | 9 | import 'styles.css' 10 | 11 | const App = () => ( 12 | 13 | 14 | 15 | 16 | }> 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | export default App 26 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/azure-ad/adalConfig.js: -------------------------------------------------------------------------------- 1 | import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal' 2 | 3 | export const getAdalConfig = config => ({ 4 | tenant: config.adal.tenant, 5 | clientId: config.adal.clientId, 6 | endpoints: { api: config.adal.clientId }, 7 | cacheLocation: 'localStorage' 8 | }) 9 | 10 | export let authContext = null 11 | 12 | export const getAuthContext = config => { 13 | const adalConfigObj = getAdalConfig(config) 14 | const context = new AuthenticationContext(adalConfigObj) 15 | authContext = context 16 | return adalConfigObj.clientId ? context : null 17 | } 18 | 19 | export const adalApiFetch = (auth, config, fetch, url, options) => 20 | adalFetch(auth, config.endpoints.api, fetch, url, options) 21 | 22 | export const withAdalLoginApi = config => { 23 | const authContext = getAuthContext(config) 24 | const adalConfig = getAdalConfig(config) 25 | 26 | return withAdalLogin(authContext, adalConfig.endpoints.api) 27 | } 28 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/azure-ad/adalConfig.test.js: -------------------------------------------------------------------------------- 1 | import * as authContext from './adalConfig' 2 | 3 | describe('Adal config', () => { 4 | beforeEach(() => { 5 | jest.resetModules() 6 | }) 7 | 8 | it('should trigger correctly with process.env', async () => { 9 | const config = { adal: { tenant: 'tenant', clientId: 'id' } } 10 | const adalConfig = authContext.getAdalConfig(config) 11 | const Auth = authContext.getAuthContext(config) 12 | 13 | expect(adalConfig.tenant).toBe('tenant') 14 | expect(adalConfig.clientId).toBe('id') 15 | expect(adalConfig.endpoints.api).toBe('id') 16 | 17 | try { 18 | await authContext.adalApiFetch(Auth, adalConfig) 19 | } catch (err) { 20 | expect(err).toEqual({ 21 | message: 'User login is required', 22 | msg: 'login required' 23 | }) 24 | } 25 | }) 26 | 27 | it('should trigger correctly without process.env', async () => { 28 | const config = { adal: { tenant: undefined, clientId: undefined } } 29 | const adalConfig = authContext.getAdalConfig(config) 30 | const Auth = authContext.getAuthContext(config) 31 | 32 | expect(adalConfig.clientId).toBe(undefined) 33 | expect(adalConfig.clientId).toBe(undefined) 34 | expect(adalConfig.endpoints.api).toBe(undefined) 35 | expect(Auth).toBe(null) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/azure-ad/authentication.test.js: -------------------------------------------------------------------------------- 1 | import Authentication from '.' 2 | 3 | jest.mock('./adalConfig', () => ({ 4 | getAuthContext: jest.fn().mockImplementation(() => ({ 5 | logOut: jest.fn(), 6 | getCachedUser: jest.fn() 7 | })) 8 | })) 9 | 10 | describe('Authorization constructor', () => { 11 | const config = { adal: { tenant: 'tenant', clientId: 'id' } } 12 | const authentication = new Authentication({ config, t: () => {} }) 13 | 14 | it('should trigger logout correctly', () => { 15 | expect(authentication.logout()).toBe(true) 16 | }) 17 | 18 | it('should trigger isAuthenticated correctly', () => { 19 | authentication.authContext.getCachedUser.mockImplementation(() => ({ 20 | username: 'username' 21 | })) 22 | 23 | expect(authentication.isAuthenticated()).toBe(true) 24 | }) 25 | 26 | it('should trigger getUserData correctly', () => { 27 | authentication.authContext.getCachedUser.mockImplementation(() => ({ 28 | username: 'username' 29 | })) 30 | window.localStorage.setItem('adal.idtoken', 'some token') 31 | authentication.isAuthenticated() 32 | expect(authentication.getUserData()).toEqual({ 33 | username: 'username', 34 | idToken: 'some token' 35 | }) 36 | }) 37 | 38 | it('should trigger isAuthenticated correctly', () => { 39 | authentication.authContext.getCachedUser.mockImplementation(() => false) 40 | expect(authentication.isAuthenticated()).toBe(false) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/azure-ad/index.js: -------------------------------------------------------------------------------- 1 | import { getAuthContext } from './adalConfig' 2 | 3 | export default class Authentication { 4 | constructor({ config, t } = {}) { 5 | this.authContext = getAuthContext(config) 6 | this.header = t('header.azure') 7 | this.powerMessage = t('powerMessages.azure') 8 | } 9 | 10 | // Assuming we won't ever see the Login screen with AD 11 | // As we use the directory login and bypass straight to our 12 | // private routes 13 | 14 | logout() { 15 | this.user = false 16 | this.authContext.logOut() 17 | return true 18 | } 19 | 20 | isAuthenticated() { 21 | const user = this.authContext ? this.authContext.getCachedUser() : null 22 | if (user) { 23 | const { username } = user 24 | this.user = { 25 | username, 26 | idToken: localStorage.getItem('adal.idtoken') 27 | } 28 | return true 29 | } 30 | return false 31 | } 32 | 33 | getUserData() { 34 | return this.user 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/in-memory/authentication.test.js: -------------------------------------------------------------------------------- 1 | import Authentication from '.' 2 | 3 | describe('Authorization constructor', () => { 4 | const storage = {} 5 | const authentication = new Authentication({ t: () => {} }) 6 | 7 | Object.defineProperty(window, 'localStorage', { 8 | value: { 9 | setItem: (key, value) => (storage[key] = value), 10 | getItem: key => storage[key], 11 | removeItem: key => delete storage[key] 12 | } 13 | }) 14 | 15 | it('should trigger login correctly', async () => { 16 | expect(storage).toEqual({}) 17 | expect(await authentication.login({ username: 'name' })).toEqual({ 18 | username: 'name' 19 | }) 20 | expect(storage).toEqual({ 'titus-auth-key': 'name' }) 21 | }) 22 | 23 | it('should trigger isAuthenticated correctly', () => { 24 | storage['titus-auth-key'] = 'key' 25 | 26 | expect(authentication.isAuthenticated()).toBe(true) 27 | }) 28 | 29 | it('should trigger logout correctly', async () => { 30 | expect(storage).toEqual({ 'titus-auth-key': 'key' }) 31 | expect(await authentication.logout()).toBe(true) 32 | expect(storage).toEqual({}) 33 | }) 34 | 35 | it('should trigger getUserData correctly', () => { 36 | storage['titus-auth-key'] = 'username' 37 | 38 | expect(authentication.getUserData()).toEqual({ username: 'username' }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/in-memory/index.js: -------------------------------------------------------------------------------- 1 | export default class Authentication { 2 | constructor({ t } = {}) { 3 | this.header = t('header.memory') 4 | } 5 | 6 | authKey = 'titus-auth-key' 7 | 8 | async login({ username }) { 9 | window.localStorage.setItem(this.authKey, username) 10 | return { username } 11 | } 12 | 13 | async logout() { 14 | window.localStorage.removeItem(this.authKey) 15 | return true 16 | } 17 | 18 | isAuthenticated() { 19 | return Boolean(window.localStorage.getItem(this.authKey)) 20 | } 21 | 22 | getUserData() { 23 | return { username: window.localStorage.getItem(this.authKey) } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/titus-backend/index.js: -------------------------------------------------------------------------------- 1 | export default class Authentication { 2 | constructor({ config, t } = {}) { 3 | this.header = t('header.titus') 4 | this.powerMessage = t('powerMessages.titus') 5 | this.config = config 6 | } 7 | 8 | async login({ username, password }) { 9 | const response = await fetch(`${this.config.serverUrl}/login`, { 10 | method: 'POST', 11 | headers: { 'Content-Type': 'application/json' }, 12 | body: JSON.stringify({ username, password }) 13 | }) 14 | if (!response.ok) { 15 | throw Error((await response.json()).message) 16 | } 17 | const authResult = await response.json() 18 | if (authResult && authResult.access_token) { 19 | localStorage.setItem('access_token', authResult.access_token) 20 | localStorage.setItem( 21 | 'expires_at', 22 | authResult.expires_in * 1000 + new Date().getTime() 23 | ) 24 | } 25 | return { username } 26 | } 27 | 28 | async logout() { 29 | localStorage.removeItem('access_token') 30 | localStorage.removeItem('expires_at') 31 | return true 32 | } 33 | 34 | isAuthenticated() { 35 | try { 36 | const expiresAt = JSON.parse(localStorage.getItem('expires_at')) 37 | return new Date().getTime() < expiresAt 38 | } catch (err) { 39 | // unparseable state: log out 40 | this.logout() 41 | return false 42 | } 43 | } 44 | 45 | getUserData() { 46 | return { username: 'Dontknow' } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './schemas' 2 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/auth-providers/utils/schemas.js: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup' 2 | import { AUTH_PROVIDERS } from 'lib/constants' 3 | import i18n from 'lib/i18n' 4 | 5 | let password 6 | if (process.env.REACT_APP_AUTH_PROVIDER === AUTH_PROVIDERS.MEM) { 7 | password = yup 8 | .string() 9 | .matches( 10 | /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{4,}$/i, 11 | i18n.t('validation.regex.password') 12 | ) 13 | .required(i18n.t('validation.required.password')) 14 | } else { 15 | password = yup.string().required(i18n.t('validation.required.password')) 16 | } 17 | 18 | export const loginFormSchema = yup.object().shape({ 19 | username: yup.string().required(i18n.t('validation.required.username')), 20 | password 21 | }) 22 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/authentication/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import { AuthProvider, AuthConsumer } from './authentication-context' 5 | 6 | export const Auth = ({ children, ...rest }) => ( 7 | 8 | 9 | {({ isAuthenticated }) => children(isAuthenticated)} 10 | 11 | 12 | ) 13 | 14 | Auth.propTypes = { 15 | children: PropTypes.func.isRequired 16 | } 17 | 18 | export default Auth 19 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/authz-check/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react' 2 | import { AuthContext } from 'components/authentication/authentication-context' 3 | 4 | import config from '../../lib/config' 5 | 6 | const AuthzCheck = () => { 7 | const { user } = useContext(AuthContext) 8 | const [isAuthorizedAdmin, setIsAuthorizedAdmin] = useState('not checked') 9 | const checkAuthz = async user => { 10 | const headers = { 11 | Authorization: `Bearer ${user.accessToken}`, 12 | 'X-authz-id': user.idToken 13 | } 14 | try { 15 | setIsAuthorizedAdmin('checking...') 16 | const response = await fetch(`${config.serverUrl}/authzcheck`, { 17 | headers 18 | }) 19 | const json = await response.json() 20 | if (json.isAdmin) { 21 | setIsAuthorizedAdmin('confirmed authorized admin') 22 | } else { 23 | setIsAuthorizedAdmin('not authorized admin') 24 | } 25 | } catch (e) { 26 | setIsAuthorizedAdmin('not authorized admin') 27 | console.log(e) 28 | } 29 | } 30 | 31 | return ( 32 |
33 |

Authorization Check

34 | 35 |
36 | ) 37 | } 38 | 39 | export default AuthzCheck 40 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/dashboard/dashboard.mdx: -------------------------------------------------------------------------------- 1 | import { Story } from '@storybook/addon-docs/blocks' 2 | import { StyledPreview as Preview } from '.storybook/decorator-components' 3 | import Dashboard from './' 4 | 5 | ## Dashboard 6 | 7 | Dashboard component. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/dashboard/dashboard.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import docs from './dashboard.mdx' 4 | 5 | import Dashboard from './' 6 | 7 | export default { 8 | title: 'Dashboard', 9 | parameters: { 10 | docs: { 11 | page: docs 12 | } 13 | } 14 | } 15 | 16 | export const Default = () => 17 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/error-boundary/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withTranslation } from 'react-i18next' 3 | 4 | class ErrorBoundary extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.state = { hasError: false } 8 | } 9 | 10 | static getDerivedStateFromError() { 11 | return { hasError: true } 12 | } 13 | 14 | render() { 15 | if (this.state.hasError) { 16 | return

{this.props.t('errors.loadingFailed')}

17 | } 18 | 19 | return this.props.children 20 | } 21 | } 22 | 23 | export default withTranslation()(ErrorBoundary) 24 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/loading/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Loading = () =>
4 | 5 | export default Loading 6 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/loading/loading.mdx: -------------------------------------------------------------------------------- 1 | import { Story } from '@storybook/addon-docs/blocks' 2 | import { StyledPreview as Preview } from '.storybook/decorator-components' 3 | 4 | ## Loader 5 | 6 | Loader component for project. 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/loading/loading.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import docs from './loading.mdx' 4 | 5 | import Loader from './' 6 | 7 | export default { 8 | title: 'Loader', 9 | parameters: { 10 | docs: { 11 | page: docs 12 | } 13 | } 14 | } 15 | 16 | export const Default = () => 17 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/login-form/login-form.mdx: -------------------------------------------------------------------------------- 1 | import { Story } from '@storybook/addon-docs/blocks' 2 | import { StyledPreview as Preview } from '.storybook/decorator-components' 3 | 4 | ## Login Form 5 | 6 | Login Form component. Uses [`Formik`](https://jaredpalmer.com/formik). This means it can be passed validation schemas. 7 | 8 | 9 | 10 | 11 | 12 | You can pass a `header` message that is displayed above the form. 13 | 14 | 15 | 16 | 17 | 18 | You can also pass a "Powered by" style message to be displayed beneath the form. 19 | 20 | 21 | 22 | 23 | 24 | Allow password change. 25 | 26 | 27 | 28 | 29 | 30 | Display login errors. 31 | 32 | 33 | 34 | 35 | 36 | Allow for custom form rendering. 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/login-form/login-form.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import docs from './login-form.mdx' 4 | 5 | import LoginForm from './' 6 | 7 | export default { 8 | title: 'Login Form', 9 | parameters: { 10 | docs: { 11 | page: docs 12 | } 13 | } 14 | } 15 | 16 | export const Default = () => 17 | 18 | export const CustomHeader = () => ( 19 | 20 | ) 21 | export const CustomPowerMessage = () => ( 22 | 23 | ) 24 | export const AllowPasswordChange = () => ( 25 | 26 | ) 27 | export const LoginError = () => ( 28 | 29 | ) 30 | const CustomForm = ({ isSubmitting }) => ( 31 | 34 | ) 35 | export const RenderPropsForm = () => 36 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/logo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ReactComponent as TitusLogo } from './Accel_Logo_Titus.svg' 4 | 5 | export const Logo = () => 6 | 7 | export default Logo 8 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/logo/logo.mdx: -------------------------------------------------------------------------------- 1 | import { Story } from '@storybook/addon-docs/blocks' 2 | import { StyledPreview as Preview } from '.storybook/decorator-components' 3 | 4 | ## Logo 5 | 6 | Logo component for project. 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/logo/logo.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import docs from './logo.mdx' 4 | 5 | import Logo from './' 6 | 7 | export default { 8 | title: 'Logo', 9 | parameters: { 10 | docs: { 11 | page: docs 12 | } 13 | } 14 | } 15 | 16 | export const Default = () => 17 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/remote-aws-config/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import Loading from 'components/loading' 3 | import config from 'lib/config' 4 | 5 | const RemoteAwsConfigContainer = ({ children }) => { 6 | if (!config.remoteAwsConfigPath) { 7 | return <>{children} 8 | } 9 | 10 | return {children} 11 | } 12 | 13 | const RemoteAwsConfig = ({ children }) => { 14 | const [isInit, setIsInit] = useState(false) 15 | const [error, setError] = useState() 16 | useEffect(() => { 17 | async function fetchConfig() { 18 | try { 19 | const response = await fetch(config.remoteAwsConfigPath, { 20 | method: 'GET', 21 | headers: { 'Content-Type': 'application/json' } 22 | }) 23 | if (!response.ok) { 24 | setError((await response.json()).message) 25 | return 26 | } 27 | const result = await response.json() 28 | config.aws = { 29 | region: result.region, 30 | userPoolId: result.userPoolId, 31 | userPoolWebClientId: result.userPoolWebClientId, 32 | identityPoolId: result.identityPoolId 33 | } 34 | setIsInit(true) 35 | } catch (e) { 36 | setError(e.message) 37 | } 38 | } 39 | 40 | fetchConfig() 41 | }, []) 42 | 43 | if (error) { 44 | return ( 45 |
46 |

Initialization Error

47 |
{error}
48 |
49 | ) 50 | } 51 | 52 | return !isInit ? : <>{children} 53 | } 54 | 55 | export default RemoteAwsConfigContainer 56 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/components/user-info/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from 'react' 2 | import { useTranslation } from 'react-i18next' 3 | import { AuthContext } from 'components/authentication/authentication-context' 4 | 5 | import config from '../../lib/config' 6 | 7 | // This component was created only to test the auth endpoint 8 | const UserInfo = () => { 9 | const { t } = useTranslation() 10 | const { user } = useContext(AuthContext) 11 | const [loggedUser, setLoggedUser] = useState(null) 12 | 13 | useEffect(() => { 14 | async function run() { 15 | const response = await fetch(`${config.serverUrl}/auth`, { 16 | method: 'GET', 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | Authorization: `Bearer ${user.idToken}` 20 | } 21 | }) 22 | 23 | setLoggedUser(await response.json()) 24 | } 25 | 26 | run() 27 | }, [user]) 28 | 29 | return ( 30 | loggedUser && ( 31 |
32 |

{t('userInfoTitle')}

33 |
34 | {Object.entries(loggedUser).map(([key, value]) => ( 35 |
36 | {key}: {value} 37 |
38 | ))} 39 |
40 |
41 | ) 42 | ) 43 | } 44 | 45 | export default UserInfo 46 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Required for `runWithAdal` below:*/ 3 | 4 | // import { runWithAdal } from 'react-adal' 5 | // import config from 'lib/config' 6 | // import { getAuthContext } from 'components/auth-providers/azure-ad/adalConfig' 7 | import React from 'react' 8 | import { render } from 'react-dom' 9 | import 'lib/i18n' 10 | import App from 'app' 11 | import { register as registerServiceWorker } from 'serviceWorker' 12 | 13 | render(, document.getElementById('root')) 14 | /* 15 | Replace above `render` with the below, to enabled Azure AD authentication*/ 16 | // runWithAdal( 17 | // getAuthContext(config), 18 | // () => { 19 | // render(, document.getElementById('root')) 20 | // }, 21 | // false 22 | // ) 23 | 24 | registerServiceWorker() 25 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | serverUrl: process.env.REACT_APP_API_PATH, 3 | enableAdmin: /true/i.test(process.env.REACT_APP_ENABLE_ADMIN), 4 | adminServerUrl: process.env.REACT_APP_ADMIN_API_PATH, 5 | env: process.env.NODE_ENV, 6 | publicUrl: process.env.PUBLIC_URL, 7 | remoteAwsConfigPath: process.env.REACT_APP_REMOTE_AWS_CONFIG_PATH, 8 | 9 | adal: { 10 | tenant: process.env.REACT_APP_AD_TENANT, 11 | clientId: process.env.REACT_APP_AD_APP_ID 12 | }, 13 | auth0: { 14 | domain: process.env.REACT_APP_AUTH0_DOMAIN, 15 | clientId: process.env.REACT_APP_AUTH0_CLIENT_ID, 16 | audience: process.env.REACT_APP_AUTH0_AUDIENCE 17 | }, 18 | aws: { 19 | region: process.env.REACT_APP_AWS_REGION, 20 | userPoolId: process.env.REACT_APP_AWS_POOL_ID, 21 | userPoolWebClientId: process.env.REACT_APP_AWS_POOL_CLIENT_ID, 22 | identityPoolId: process.env.REACT_APP_AWS_IDENTITY_POOL_ID 23 | } 24 | } 25 | 26 | export default config 27 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/constants.js: -------------------------------------------------------------------------------- 1 | export const LANGUAGES = [ 2 | { 3 | code: 'en', 4 | name: 'English', 5 | flag: '🇬🇧' 6 | }, 7 | { 8 | code: 'ro', 9 | name: 'Românā', 10 | flag: '🇷🇴' 11 | }, 12 | { 13 | code: 'pt', 14 | name: 'Português', 15 | flag: '🇵🇹' 16 | } 17 | ] 18 | 19 | export const ROUTES = { 20 | LOGIN: '/login', 21 | DASHBOARD: '/', 22 | ADMIN: '/admin' 23 | } 24 | 25 | export const AUTH_PROVIDERS = { 26 | AD: 'AD', 27 | AUTH0: 'AUTH0', 28 | AWS: 'AWS', 29 | MEM: 'MEM', 30 | TITUS: 'TITUS' 31 | } 32 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next' 2 | import detector from 'i18next-browser-languagedetector' 3 | import { initReactI18next } from 'react-i18next' 4 | import locale from 'lib/locale' 5 | import { LANGUAGES } from 'lib/constants' 6 | 7 | const resources = { 8 | en: { 9 | translation: locale.en 10 | } 11 | } 12 | 13 | i18n 14 | .use(detector) 15 | .use(initReactI18next) 16 | .init({ 17 | resources, 18 | fallbackLng: 'en', 19 | detection: { 20 | order: ['navigator'] 21 | }, 22 | interpolation: { 23 | escapeValue: false 24 | }, 25 | keySeparator: '.' // we use content as keys 26 | }) 27 | 28 | for (const lng of LANGUAGES) { 29 | i18n.addResourceBundle(lng.code, 'translation', locale[lng.code]) 30 | } 31 | 32 | export default i18n 33 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/locale/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "logout": "LOGOUT", 3 | "login": "Login", 4 | "withAuth0": "with Auth0", 5 | "description": "Develop and Deploy to features quickly using Titus, an Accelerated Development & Deployment Stack. Titus is production ready and can be deployed to all major cloud providers.", 6 | "docs": "Check out the docs", 7 | "username": "Username", 8 | "password": "Password", 9 | "newPassword": "New Password", 10 | "language": "Language", 11 | "userInfoTitle": "Logged user information", 12 | "userListTitle": "User list", 13 | "header": { 14 | "memory": "Note: Any username followed by a password with at least four characters containing at least one letter and number will work.", 15 | "aws": "Please provide AWS Cognito account details:", 16 | "azure": "Powered by Azure AD", 17 | "titus": "Please provide Auth0 account details:" 18 | }, 19 | "powerMessages": { 20 | "aws": "Powered by AWS Amplify", 21 | "azure": "Powered by Azure AD", 22 | "titus": "Powered by Titus" 23 | }, 24 | "validation": { 25 | "required": { 26 | "username": "Username is required.", 27 | "password": "Password is required." 28 | }, 29 | "regex": { 30 | "password": "Password must be at least 4 characters long and contain at least one letter and one number." 31 | } 32 | }, 33 | "errors": { 34 | "tempPassword": "Please enter your temporary password and a new password", 35 | "loadingFailed": "Loading failed. Please reload" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/locale/index.js: -------------------------------------------------------------------------------- 1 | import en from './en' 2 | import ro from './ro' 3 | import pt from './pt' 4 | 5 | export default { en, ro, pt } 6 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/locale/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "logout": "SAIR", 3 | "login": "ENTRAR", 4 | "withAuth0": "com Auth0", 5 | "description": "Desenvolve funcionalidades rapidamente usando Titus, uma ferramenta de Desenvolvimento e Instalação. Titus esta pronto para uso em produção e preparado para instalar nos provedores Cloud mais comuns.", 6 | "docs": "Ver a documentação", 7 | "username": "Utilizador", 8 | "password": "Senha", 9 | "newPassword": "Nova Senha", 10 | "language": "Língua", 11 | "header": { 12 | "memory": "Nota: Use qualquer nome de Utilizador seguido de senha com pelo menos quatro caracteres contendo no mínimo uma letra ou número.", 13 | "aws": "Por favor indique os valores de conta AWS Cognito:", 14 | "azure": "Suportado por Azure AD", 15 | "titus": "Por favor indique os valores de conta Auth0:" 16 | }, 17 | "powerMessages": { 18 | "aws": "Suportado por AWS Amplify", 19 | "azure": "Suportado por Azure AD", 20 | "titus": "Suportado por Titus" 21 | }, 22 | "validation": { 23 | "required": { 24 | "username": "Utilizador é obrigatório.", 25 | "password": "Senha é obrigratório." 26 | }, 27 | "regex": { 28 | "password": "A senha tem de conter pelo menos quatro caracteres e no mínimo uma letra ou número." 29 | } 30 | }, 31 | "errors": { 32 | "tempPassword": "Por favor indique a sua senha temporaria e uma senha nova.", 33 | "loadingFailed": "Falha no carregamento. Por favor recarregue" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/locale/ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "logout": "IESIRE", 3 | "login": "Autentificare", 4 | "withAuth0": "cu Auth0", 5 | "description": "Dezvoltați și implementați în funcții rapid folosind Titus, o stivă de dezvoltare și implementare accelerată. Titus este gata de producție și poate fi implementat tuturor furnizorilor de cloud majori.", 6 | "docs": "Citeste documentatia", 7 | "username": "Nume utilizator", 8 | "password": "Parola", 9 | "newPassword": "Parola noua", 10 | "language": "Limba", 11 | "header": { 12 | "memory": "Nota: Orice nume de utilizator urmat de o parola de cel putin patru caractere, cu cel putin o litera si un numar, va functiona.", 13 | "aws": "Vă rugăm să furnizați detaliile contului AWS Cognito:", 14 | "azure": "Dezvoltat cu Azure AD", 15 | "titus": "Vă rugăm să furnizați detaliile contului Auth0:" 16 | }, 17 | "powerMessages": { 18 | "aws": "Cu sprijinul AWS Amplify", 19 | "azure": "Cu sprijinul Azure AD", 20 | "titus": "Cu sprijinul Titus" 21 | }, 22 | "validation": { 23 | "required": { 24 | "username": "Nume utilizator este obligatoriu.", 25 | "password": "Parola este obligatorie." 26 | }, 27 | "regex": { 28 | "password": "Parola trebuie sa contina cel putin patru caractere cu cel putin o litera si un numar" 29 | } 30 | }, 31 | "errors": { 32 | "tempPassword": "Te rugam sa introduci parola ta temporara si o noua parola", 33 | "loadingFailed": "Încărcarea a eșuat. Vă rugăm să reîncărcați" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/pino.js: -------------------------------------------------------------------------------- 1 | import pino from 'pino' 2 | import config from 'lib/config' 3 | 4 | const pinoConfig = { 5 | browser: { 6 | asObject: true 7 | } 8 | } 9 | 10 | if (config.serverUrl) { 11 | pinoConfig.browser.transmit = { 12 | level: 'info', 13 | send: async (level, logEvent) => { 14 | const msg = logEvent.messages[0] 15 | 16 | const headers = { 17 | 'Access-Control-Allow-Origin': '*', 18 | type: 'application/json' 19 | } 20 | let blob = new Blob([JSON.stringify({ msg, level })], headers) 21 | 22 | navigator.sendBeacon(`${config.serverUrl}/log`, blob) 23 | } 24 | } 25 | } 26 | 27 | const logger = pino(pinoConfig) 28 | 29 | export const log = msg => logger.info(msg) 30 | export default logger 31 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/lib/pino.test.js: -------------------------------------------------------------------------------- 1 | import pino, { log } from './pino' 2 | 3 | let warnMessage 4 | let infoMessage 5 | 6 | jest.mock('pino', params => { 7 | return params => { 8 | if (params.browser && params.browser.transmit) { 9 | params.browser.transmit.send(0, { messages: ['msg'] }) 10 | } 11 | 12 | return { 13 | info: msg => { 14 | infoMessage = msg 15 | }, 16 | warn: msg => { 17 | warnMessage = msg 18 | } 19 | } 20 | } 21 | }) 22 | 23 | describe('Pino file', () => { 24 | test('should trigger pino log function', () => { 25 | log('log message') 26 | 27 | setTimeout(() => { 28 | expect(global.navigator.sendBeacon.mock.calls.length).toBe(1) 29 | expect(infoMessage).toBe('log message') 30 | }, 300) 31 | }) 32 | 33 | test('should trigger pino default with warn', () => { 34 | pino.warn('warn message') 35 | 36 | expect(warnMessage).toBe('warn message') 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/pages/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { AuthContext } from 'components/authentication/authentication-context' 3 | import Dashboard from 'components/dashboard' 4 | 5 | // TODO:: Need to do something with this if we are using the AD powered auth 6 | // import { authContext } from 'components/auth-providers/azure-ad/adalConfig' 7 | 8 | const DashboardContainer = () => { 9 | const { logout } = useContext(AuthContext) 10 | 11 | // This stuff only happens if the AuthProvider is AD 12 | // const [adIdToken, setAdIdToken] = useState(null) 13 | // useEffect(() => { 14 | // if (!authContext) { 15 | // return 16 | // } 17 | 18 | // const adIdToken = localStorage.getItem( 19 | // authContext.CONSTANTS.STORAGE.IDTOKEN 20 | // ) 21 | 22 | // if (adIdToken) { 23 | // setAdIdToken(adIdToken) 24 | // } 25 | // }, []) 26 | 27 | // const testAzureAuth = async () => { 28 | // const headers = { 29 | // Authorization: `Bearer ${adIdToken}` 30 | // } 31 | // try { 32 | // const response = await fetch('/user', { headers }) 33 | // const json = await response.json() 34 | // alert(`Azure UPN: ${json.userPrincipalName}`) 35 | // } catch (e) { 36 | // console.log(e) 37 | // } 38 | // } 39 | 40 | return 41 | } 42 | 43 | export default DashboardContainer 44 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/pages/dashboard/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | renderWithAuthedRouter, 4 | render, 5 | fireEvent, 6 | waitForElementToBeRemoved, 7 | waitFor 8 | } from 'lib/test-utils' 9 | 10 | import DashboardContainer from '.' 11 | 12 | describe('', () => { 13 | it('renders without crashing', async () => { 14 | const { getByText } = render() 15 | expect(getByText(/develop and deploy/i)).toBeInTheDocument() 16 | expect(getByText(/logout/i)).toBeInTheDocument() 17 | const docsLink = getByText(/check out the docs/i) 18 | expect(docsLink.getAttribute('href')).toBe('https://nf-titus.netlify.com/') 19 | }) 20 | 21 | it('renders in another language (Romanian) when selected', async () => { 22 | const { getByLabelText, getByText } = render() 23 | const languageSelect = getByLabelText('Language:') 24 | fireEvent.change(languageSelect, { target: { value: 'ro' } }) 25 | expect(getByText(/iesire/i)).toBeInTheDocument() 26 | fireEvent.change(languageSelect, { target: { value: 'en' } }) 27 | expect(getByText(/logout/i)).toBeInTheDocument() 28 | }) 29 | 30 | it('logs out when logout is clicked', async () => { 31 | const { getByText } = renderWithAuthedRouter() 32 | await waitForElementToBeRemoved(() => getByText('loading')) 33 | fireEvent.click(getByText(/logout/i)) 34 | await waitFor(() => { 35 | expect(getByText(/login/i)).toBeInTheDocument() 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/pages/login/index.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react' 2 | import { AuthContext } from 'components/authentication/authentication-context' 3 | import LoginForm from 'components/login-form' 4 | 5 | const Login = () => { 6 | const { login, loginMessage, provider, authentication, loginError } = 7 | useContext(AuthContext) 8 | 9 | useEffect(() => { 10 | if (provider === 'AUTH0') { 11 | authentication 12 | .parseHash() 13 | // in case the url contains details, trigger login to resume redirect on dashboard 14 | .then(isAuthenticated => isAuthenticated && login()) 15 | } 16 | }, [authentication, login, provider]) 17 | 18 | return ( 19 | 28 | ) 29 | } 30 | 31 | export default Login 32 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | global.navigator.sendBeacon = jest.fn() 2 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/validateEnv.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const S = require('fluent-schema') 4 | 5 | const env = process.env.NODE_ENV || 'development' 6 | 7 | const schemas = { 8 | development: S.object().prop( 9 | 'REACT_APP_API_PATH', 10 | S.string().minLength(1).required() 11 | ), 12 | 13 | production: S.object().prop( 14 | 'REACT_APP_API_PATH', 15 | S.string().minLength(1).required() 16 | ) 17 | } 18 | 19 | const validate = function () { 20 | require('env-schema')({ 21 | schema: schemas[env], 22 | dotenv: true 23 | }) 24 | } 25 | 26 | module.exports = { 27 | validate 28 | } 29 | -------------------------------------------------------------------------------- /packages/titus-frontend/src/validateEnv.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | describe('validateEnv file', () => { 4 | beforeEach(() => { 5 | jest.resetModules() 6 | }) 7 | 8 | it('should succeed if env variables are present', async () => { 9 | process.env = { 10 | NODE_ENV: 'development', 11 | REACT_APP_API_PATH: 'http://localhost' 12 | } 13 | 14 | try { 15 | const validateResult = require('./validateEnv').validate() 16 | expect(validateResult).toEqual(undefined) 17 | } catch (err) { 18 | console.error(err) 19 | } 20 | }) 21 | 22 | it('should fail if REACT_APP_API_PATH env variables is missing', async () => { 23 | process.env = { 24 | NODE_ENV: 'development', 25 | REACT_APP_API_PATH: undefined 26 | } 27 | 28 | try { 29 | require('./validateEnv').validate() 30 | } catch (err) { 31 | expect(err.message).toEqual( 32 | "env must have required property 'REACT_APP_API_PATH'" 33 | ) 34 | } 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/.gitignore: -------------------------------------------------------------------------------- 1 | # MIRA samples specific 2 | cdk.out/ 3 | /config/default.json 4 | /config/dev.json 5 | .ts-build-cache 6 | /cdk/**/*.js 7 | /config/**/*.js 8 | /pipeline/**/*.js 9 | /lambda/**/*.js 10 | build/ 11 | .build/ 12 | .cdk.* 13 | cdk.* 14 | output.json 15 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/.mira.snapshot: -------------------------------------------------------------------------------- 1 | api/index.test.ts||b47f3d8f3c8936ba6c95f4120c6d693a 2 | api/index.ts||fd72d33f43653783ba3824080372af87 3 | api/migration.ts||2b4b1576c83ec06f7da58b4d264cf4aa 4 | api/routes/index.ts||ec568a03e7550e4c9a177613f2792a78 5 | api/routes||5bca41ed19118b8eb012d485e2d62f9c 6 | api/store/helpers.ts||f3d8e7b4e4e5221955a45480ca8c7506 7 | api/store/index.ts||b63c56e7cb8930f4e64f769e1aee395a 8 | api/store/store.md||424e87e37b728a9ad3896e40d08d1ee6 9 | api/store||fb9fbd00eab0a03869e7ad74fa4f1358 10 | api||63880c164b3c76adbbc9a1e845165a49 11 | config/default.json||6ae01146f07dfa8d576e3191f3ef9d85 12 | config/default.sample.json||5781d3eccf3a27ec43dc398190d35378 13 | config||992003b975885b0efd7502aad662141e 14 | core/authentication.ts||c37009457c213c8ee03103c51e33b26b 15 | core/database.ts||f8e9e573fa1c48bca233f044920fd23a 16 | core/index.test.ts||19fd8daf840ad5d2fc64f9ebc6000aae 17 | core/index.ts||5888ae2e2b66c8098833e1b8e3cf5c3f 18 | core/package.json||37631db7e5c66a11446b6be5e3fe1918 19 | core||63880c164b3c76adbbc9a1e845165a49 20 | index.ts||49ce3e644d702bbc7277c05b8b2aa56b 21 | package-lock.json||79ec9d378adb42b9a88000bb1b23415a 22 | package.json||8bcf30ebe8be98922cf87f591238b68f 23 | web-app/index.ts||64dce0e9bc31dd1dec92a6b6b2189309 24 | web-app||7f514a182a87e74874e815d014ed44d8 -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/api-gw-alb/index.test.ts: -------------------------------------------------------------------------------- 1 | import {countResources, expect as cdkExpect} from '@aws-cdk/assert' 2 | import {Construct} from "@aws-cdk/core"; 3 | import {MiraApp, MiraServiceStack, MiraStack} from 'mira' 4 | import {Core} from '../core' 5 | import {EcsAlb} from '../ecs-alb' 6 | import {ApiGatewayAlb} from '.' 7 | 8 | export class TestStack extends MiraStack { 9 | constructor(parent: Construct) { 10 | super(parent, TestStack.name) 11 | } 12 | } 13 | 14 | describe('Api Gateway Alb', () => { 15 | it('Creates the stack', async () => { 16 | const app = new MiraApp() 17 | const stack = new MiraServiceStack(app, 'default') 18 | const coreStack = new Core(stack) 19 | const ecsStack = new EcsAlb(stack, { 20 | authentication: coreStack.authentication, 21 | database: coreStack.database, 22 | vpc: coreStack.vpc 23 | }) 24 | 25 | const gwStack = new ApiGatewayAlb(stack, { 26 | alb: ecsStack.service.loadBalancer, 27 | userPoolArn: coreStack.authentication.userPoolArn 28 | }) 29 | 30 | cdkExpect(gwStack).to(countResources('AWS::IAM::Role', 1)) 31 | cdkExpect(gwStack).to(countResources('AWS::Lambda::Function', 1)) 32 | cdkExpect(gwStack).to(countResources('AWS::ApiGateway::Authorizer', 1)) 33 | cdkExpect(gwStack).to(countResources('AWS::ApiGateway::Resource', 1)) 34 | cdkExpect(gwStack).to(countResources('AWS::ApiGateway::Method', 2)) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/api-gw-nlb/index.test.ts: -------------------------------------------------------------------------------- 1 | import {countResources, expect as cdkExpect} from '@aws-cdk/assert' 2 | import {Construct} from "@aws-cdk/core"; 3 | import {MiraApp, MiraServiceStack, MiraStack} from 'mira' 4 | import {Core} from '../core' 5 | import {EcsNlb} from '../ecs-nlb' 6 | import {ApiGatewayNlb} from '.' 7 | 8 | export class TestStack extends MiraStack { 9 | constructor(parent: Construct) { 10 | super(parent, TestStack.name) 11 | } 12 | } 13 | 14 | describe('Api Gateway Nlb', () => { 15 | it('Creates the stack', async () => { 16 | const app = new MiraApp() 17 | const stack = new MiraServiceStack(app, 'default') 18 | const coreStack = new Core(stack) 19 | const ecsStack = new EcsNlb(stack, { 20 | authentication: coreStack.authentication, 21 | database: coreStack.database, 22 | vpc: coreStack.vpc 23 | }) 24 | 25 | const gwStack = new ApiGatewayNlb(stack, { 26 | vpc: coreStack.vpc, 27 | nlb: ecsStack.service.loadBalancer, 28 | userPoolArn: coreStack.authentication.userPoolArn 29 | }) 30 | 31 | cdkExpect(gwStack).to(countResources('AWS::IAM::Role', 1)) 32 | cdkExpect(gwStack).to(countResources('AWS::Lambda::Function', 1)) 33 | cdkExpect(gwStack).to(countResources('AWS::ApiGateway::VpcLink', 1)) 34 | cdkExpect(gwStack).to(countResources('AWS::ApiGateway::Authorizer', 1)) 35 | cdkExpect(gwStack).to(countResources('AWS::ApiGateway::Resource', 1)) 36 | cdkExpect(gwStack).to(countResources('AWS::ApiGateway::Method', 2)) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/core/database.test.ts: -------------------------------------------------------------------------------- 1 | import {expect, haveResource} from '@aws-cdk/assert' 2 | import {Construct} from "@aws-cdk/core"; 3 | import {MiraApp, MiraServiceStack, MiraStack} from 'mira' 4 | import {Database} from './database' 5 | import {Vpc} from './vpc' 6 | 7 | export class TestStack extends MiraStack { 8 | constructor(parent: Construct) { 9 | super(parent, TestStack.name) 10 | const coreVpc = new Vpc(this, 'Vpc') 11 | new Database(this, 'Rds', {vpc: coreVpc.vpc, ingressSecurityGroup: coreVpc.ingressSecurityGroup}) 12 | } 13 | } 14 | 15 | describe('core/database', () => { 16 | it('Creates the stack', async () => { 17 | const app = new MiraApp() 18 | const stack = new MiraServiceStack(app, 'default') 19 | const testStack = new TestStack(stack) 20 | 21 | expect(testStack).to(haveResource('AWS::RDS::DBInstance', { 22 | "DBName": "titus", 23 | "Engine": "postgres", 24 | "EngineVersion": "11.5", 25 | })) 26 | expect(testStack).to(haveResource('AWS::SecretsManager::SecretTargetAttachment')) 27 | expect(testStack).to(haveResource('AWS::SecretsManager::Secret')) 28 | expect(testStack).to(haveResource('AWS::RDS::DBSubnetGroup')) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/core/index.test.ts: -------------------------------------------------------------------------------- 1 | import {expect, haveResource, ResourcePart} from '@aws-cdk/assert' 2 | import {MiraApp, MiraServiceStack} from 'mira' 3 | import {Core} from '.' 4 | 5 | describe('Core', () => { 6 | it('Verify the existence for a resource in any stack', async () => { 7 | const app = new MiraApp() 8 | const stack = new MiraServiceStack(app, 'default') 9 | const core = new Core(stack) 10 | 11 | expect(core).to(haveResource('AWS::EC2::VPC')) 12 | expect(core).to(haveResource('AWS::Cognito::UserPool')) 13 | expect(core).to(haveResource('AWS::RDS::DBInstance')) 14 | expect(core).to(haveResource('AWS::Lambda::Function')) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/core/migration.test.ts: -------------------------------------------------------------------------------- 1 | import {countResources, expect, haveResource} from '@aws-cdk/assert' 2 | import {Construct} from "@aws-cdk/core"; 3 | import {MiraApp, MiraServiceStack, MiraStack} from 'mira' 4 | import {Database} from './database' 5 | import {Vpc} from './vpc' 6 | import {Migration} from "./migration"; 7 | 8 | export class TestStack extends MiraStack { 9 | constructor(parent: Construct) { 10 | super(parent, TestStack.name) 11 | const coreVpc = new Vpc(this, 'Vpc') 12 | const database = new Database(this, 'Rds', {vpc: coreVpc.vpc, ingressSecurityGroup: coreVpc.ingressSecurityGroup}) 13 | new Migration(this, 'Migration', { 14 | vpc: coreVpc.vpc, 15 | secret: database.secret, 16 | securityGroup: coreVpc.ingressSecurityGroup 17 | }) 18 | } 19 | } 20 | 21 | describe('core/migration', () => { 22 | it('Creates the stack', async () => { 23 | const app = new MiraApp() 24 | const stack = new MiraServiceStack(app, 'default') 25 | const testStack = new TestStack(stack) 26 | 27 | expect(testStack).to(haveResource('AWS::CloudFormation::CustomResource')) 28 | expect(testStack).to(countResources('AWS::Lambda::Function', 2)) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/core/vpc.test.ts: -------------------------------------------------------------------------------- 1 | import {countResources, expect, SynthUtils} from '@aws-cdk/assert' 2 | import {Construct} from "@aws-cdk/core"; 3 | import {MiraApp, MiraServiceStack, MiraStack} from 'mira' 4 | import {Vpc} from './vpc' 5 | 6 | export class TestStack extends MiraStack { 7 | constructor(parent: Construct) { 8 | super(parent, TestStack.name) 9 | new Vpc(this, 'Vpc') 10 | } 11 | } 12 | 13 | describe('core/vpc', () => { 14 | it('Creates the stack', async () => { 15 | const app = new MiraApp() 16 | const stack = new MiraServiceStack(app, 'default') 17 | const testStack = new TestStack(stack) 18 | 19 | // console.log(Object.values(SynthUtils.toCloudFormation(testStack).Resources as Array).map(val => val.Type)) 20 | expect(testStack).to(countResources('AWS::EC2::EIP', 3)) 21 | expect(testStack).to(countResources('AWS::EC2::InternetGateway', 1)) 22 | expect(testStack).to(countResources('AWS::EC2::NatGateway', 3)) 23 | expect(testStack).to(countResources('AWS::EC2::Route', 6)) 24 | expect(testStack).to(countResources('AWS::EC2::RouteTable', 6)) 25 | expect(testStack).to(countResources('AWS::EC2::Subnet', 6)) 26 | expect(testStack).to(countResources('AWS::EC2::SubnetRouteTableAssociation', 6)) 27 | expect(testStack).to(countResources('AWS::EC2::SecurityGroup', 2)) 28 | expect(testStack).to(countResources('AWS::EC2::VPC', 1)) 29 | expect(testStack).to(countResources('AWS::EC2::VPCGatewayAttachment', 1)) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/core/vpc.ts: -------------------------------------------------------------------------------- 1 | import {Construct} from "@aws-cdk/core"; 2 | import {Vpc as CdkVpc, SecurityGroup, ISecurityGroup} from "@aws-cdk/aws-ec2"; 3 | 4 | export class Vpc extends Construct { 5 | public readonly vpc: CdkVpc 6 | public readonly ingressSecurityGroup: ISecurityGroup 7 | public readonly egressSecurityGroup: ISecurityGroup 8 | 9 | constructor(scope: Construct, id: string) { 10 | super(scope, id) 11 | 12 | this.vpc = new CdkVpc(this, `${id}Vpc`) 13 | 14 | this.ingressSecurityGroup = new SecurityGroup( 15 | this, 16 | 'ingress-security-group', 17 | { 18 | vpc: this.vpc, 19 | allowAllOutbound: false, 20 | securityGroupName: 'IngressSecurityGroup', 21 | } 22 | ) 23 | 24 | this.egressSecurityGroup = new SecurityGroup( 25 | this, 26 | 'egress-security-group', 27 | { 28 | vpc: this.vpc, 29 | allowAllOutbound: false, 30 | securityGroupName: 'EgressSecurityGroup', 31 | } 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/index.test.ts: -------------------------------------------------------------------------------- 1 | import {countResources, expect} from '@aws-cdk/assert' 2 | import {MiraApp} from 'mira' 3 | import {default as MainStack} from './index' 4 | 5 | describe('Core', () => { 6 | it('Verify the existence for a resource in any stack', async () => { 7 | const app = new MiraApp() 8 | const stack = new MainStack(app, 'default') 9 | 10 | expect(stack).to(countResources('AWS::CloudFormation::Stack', 4)) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/cdk/index.ts: -------------------------------------------------------------------------------- 1 | import {MiraServiceStack, MiraApp} from 'mira' 2 | import {Core} from './core' 3 | 4 | import {EcsNlb} from './ecs-nlb' 5 | import {ApiGatewayNlb} from './api-gw-nlb' 6 | 7 | import {WebApp} from './web-app' 8 | 9 | export class MainStack extends MiraServiceStack { 10 | constructor(parent: MiraApp, env: string) { 11 | super(parent, env, MainStack.name) 12 | 13 | const core = new Core(this) 14 | 15 | const ecs = new EcsNlb(this, { 16 | vpc: core.vpc, 17 | authentication: core.authentication, 18 | database: core.database 19 | }) 20 | 21 | const api = new ApiGatewayNlb(this, { 22 | vpc: core.vpc, 23 | nlb: ecs.service.loadBalancer, 24 | userPoolArn: core.authentication.userPoolArn 25 | }) 26 | 27 | new WebApp(this, { 28 | apiUrl: api.url, 29 | }) 30 | } 31 | } 32 | 33 | export default MainStack 34 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/config/default.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "prefix": "nf", 4 | "name": "titus" 5 | }, 6 | "dev": { 7 | "target": "default" 8 | }, 9 | "accounts": { 10 | "default": { 11 | "env": { 12 | "account": "0000000000000", 13 | "region": "eu-west-1", 14 | "domainName": "yourdomain.com", 15 | "webAppUrl": "www.yourdomain.com", 16 | "certificateSslName": "*.yourdomain.com", 17 | "awsEcrRepositoryName": "yourbucket-titus-backend" 18 | }, 19 | "profile": "mira-dev" 20 | }, 21 | "cicd": { 22 | "env": { 23 | "account": "0000000000000", 24 | "region": "eu-west-1" 25 | }, 26 | "profile": "default" 27 | }, 28 | "staging": { 29 | "env": { 30 | "account": "0000000000000", 31 | "region": "eu-west-1" 32 | }, 33 | "profile": "default" 34 | } 35 | }, 36 | "cicd": { 37 | "target": "cicd", 38 | "buildspecFile": "packages/titus-infra-aws/pipeline/buildspec.yaml", 39 | "permissionsFile": "pipeline/permissions.js", 40 | "provider": "codecommit", 41 | "repositoryUrl": "https://git-codecommit.eu-west-2.amazonaws.com/v1/repos/titus-deploy", 42 | "branchName": "master", 43 | "codeCommitUserPublicKey": "ssh-rsa PUBLIC KEY HERE", 44 | "stages": [ 45 | { 46 | "target": "staging", 47 | "withDomain": false, 48 | "requireManualApproval": false 49 | } 50 | ] 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "prefix": "nf", 4 | "name": "titus" 5 | }, 6 | "dev": { 7 | "target": "default" 8 | }, 9 | "accounts": { 10 | "default": { 11 | "env": { 12 | "account": "0000000000000", 13 | "region": "eu-west-1", 14 | "domainName": "yourdomain.com", 15 | "webAppUrl": "www.yourdomain.com", 16 | "certificateSslName": "*.yourdomain.com", 17 | "awsEcrRepositoryName": "yourbucket-titus-backend" 18 | }, 19 | "profile": "mira-dev" 20 | }, 21 | "cicd": { 22 | "env": { 23 | "account": "0000000000000", 24 | "region": "eu-west-1" 25 | }, 26 | "profile": "default" 27 | }, 28 | "staging": { 29 | "env": { 30 | "account": "0000000000000", 31 | "region": "eu-west-1" 32 | }, 33 | "profile": "default" 34 | } 35 | }, 36 | "cicd": { 37 | "target": "cicd", 38 | "buildspecFile": "packages/titus-infra-aws/pipeline/buildspec.yaml", 39 | "permissionsFile": "pipeline/permissions.js", 40 | "provider": "codecommit", 41 | "repositoryUrl": "https://git-codecommit.eu-west-2.amazonaws.com/v1/repos/titus-deploy", 42 | "branchName": "master", 43 | "codeCommitUserPublicKey": "ssh-rsa PUBLIC KEY HERE", 44 | "stages": [ 45 | { 46 | "target": "staging", 47 | "withDomain": false, 48 | "requireManualApproval": false 49 | } 50 | ] 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/infra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/titus/b34735af7053b66647892d22c3ac844802a1fe00/packages/titus-infra-aws-mira/infra.png -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/lambda/get-config.test.ts: -------------------------------------------------------------------------------- 1 | const {handler} = require('./get-config') 2 | 3 | describe('Lambda/get-config', () => { 4 | it('Return the config based on ENV', async () => { 5 | 6 | process.env.APP_CONFIG_foo = 'test' 7 | process.env.APP_CONFIG_bar = 'test2' 8 | process.env.NO_CONFIG_baz = 'test3' 9 | const result = await handler() 10 | 11 | expect(result).toEqual({ 12 | statusCode: 200, 13 | body: '{"foo":"test","bar":"test2"}', 14 | headers: { 15 | 'Access-Control-Allow-Origin': undefined, 16 | 'Access-Control-Allow-Credentials': false 17 | } 18 | }) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/lambda/get-config.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | handler: async () => { 3 | const prefix = 'APP_CONFIG_' 4 | const returnValues = Object.entries(process.env) 5 | .filter(entry => entry[0].startsWith(prefix)) 6 | .reduce((acc: Record, next) => { 7 | acc[next[0].substr(prefix.length)] = next[1] 8 | return acc 9 | }, {}) 10 | 1 11 | return { 12 | statusCode: 200, 13 | body: JSON.stringify(returnValues), 14 | headers: { 15 | 'Access-Control-Allow-Origin': process.env.CORS_ORIGIN, 16 | 'Access-Control-Allow-Credentials': process.env.CORS_CREDENTIALS === 'true' 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/pipeline/buildspec.sample.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | shell: bash 5 | variables: 6 | HUSKY_SKIP_INSTALL: 1 7 | NODE_ENV: development 8 | DEPLOYER_ROLE_ARN: arn:aws:iam::YOURID_ACCOUNT:role/THE_ACCOUNT_CREATED_BY_THE_MIRA_CICD_COMMAND 9 | 10 | phases: 11 | install: 12 | runtime-versions: 13 | nodejs: 14 14 | commands: 15 | - npm install 16 | - npm run postinstall 17 | pre_build: 18 | commands: 19 | # - npm test test 20 | 21 | build: 22 | commands: 23 | - echo Deploy started on `date` 24 | - echo "Assuming role $DEPLOYER_ROLE_ARN ..." 25 | - CREDS=$(aws sts assume-role --role-arn $DEPLOYER_ROLE_ARN --role-session-name my-sls-session --out json) 26 | - echo $CREDS > temp_creds.json 27 | - export AWS_ACCESS_KEY_ID=$(node -p "require('./temp_creds.json').Credentials.AccessKeyId") 28 | - export AWS_SECRET_ACCESS_KEY=$(node -p "require('./temp_creds.json').Credentials.SecretAccessKey") 29 | - export AWS_SESSION_TOKEN=$(node -p "require('./temp_creds.json').Credentials.SessionToken") 30 | - aws sts get-caller-identity 31 | - npm run build:all 32 | - npm run deploy:ci -- --env=$ENVIRONMENT 33 | post_build: 34 | commands: 35 | - echo Build completed on `date` 36 | artifacts: 37 | files: '**/*' 38 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/scripts/lib/check-config-exists.js: -------------------------------------------------------------------------------- 1 | const colors = require('colors') 2 | 3 | function checkConfigExists(config) { 4 | console.log(colors.blue(`Search for config`)) 5 | if(!Object.keys(config).length) { 6 | const errorMessage = `Config fila 'config/default.json' not found 7 | You can start copying the config/default.sample.json 8 | ${colors.green(`cp config/default.sample.json config/default.json`)} 9 | ` 10 | throw new Error(errorMessage) 11 | } 12 | console.log(`Config files found`) 13 | } 14 | 15 | module.exports = checkConfigExists 16 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/scripts/lib/validate-certificate.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AWS = require('aws-sdk') 4 | const colors = require('colors') 5 | 6 | async function validateCertificate(config) { 7 | const certificateSslName = config.accounts.default.env.certificateSslName 8 | const region = config.accounts.default.env.region 9 | const account = config.accounts.default.env.account 10 | console.log(colors.blue(`Validate certificate`)) 11 | console.log(`Search for: ${certificateSslName}`) 12 | var acm = new AWS.ACM({region: 'us-east-1'}) 13 | const result = await acm.listCertificates().promise() 14 | 15 | 16 | if (!result.CertificateSummaryList.find(certificate => certificate.DomainName === certificateSslName)) { 17 | const errorMessage = `Certificate ${certificateSslName} not found 18 | A certificate with the name ${colors.green(certificateSslName)} should exists in ${colors.green('us-east-1')} region. 19 | (It should be in the same region of CloudFormation, if is attached directly to Api GW should be placed in the same region of Api GW) 20 | You can create the repository with the command: 21 | ${colors.green(`AWS_REGION=us-east-1 aws acm request-certificate --domain-name ${certificateSslName}`)} 22 | Once the creation is done, the certificate should be approved, an email is sent to admin@your-domain. 23 | Verify the status of the certificate here: https://console.aws.amazon.com/acm/home?region=us-east-1#/ 24 | ` 25 | throw new Error(errorMessage) 26 | } 27 | console.log('Certificate found on region us-east-1\n') 28 | } 29 | 30 | module.exports = validateCertificate 31 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/scripts/lib/validate-config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const colors = require('colors') 4 | const yup = require('yup') 5 | 6 | async function validateConfig(config, env) { 7 | const configSchema = yup.object().shape({ 8 | app: yup.object().shape({prefix: yup.string().required(), name: yup.string().required()}), 9 | dev: yup.object().shape({target: yup.string().required()}), 10 | accounts: yup.object().shape({ 11 | [env]: yup.object().shape({ 12 | env: yup.object().shape({ 13 | account: yup.string().required(), 14 | region: yup.string().required(), 15 | domainName: yup.string().required(), 16 | webAppUrl: yup.string().required(), 17 | certificateSslName: yup.string().required(), 18 | awsEcrRepositoryName: yup.string().required() 19 | }), 20 | profile: yup.string().required() 21 | }) 22 | }), 23 | }) 24 | await configSchema.validate(config, {abortEarly: false}) 25 | 26 | if (config.accounts.default.env.account === '0000000000000') { 27 | throw new Error(colors.red("\nThe account '0000000000000' is not valid. Set a proper account")) 28 | } 29 | 30 | console.log('Config file valid\n') 31 | } 32 | 33 | module.exports = validateConfig 34 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/scripts/validateDeploy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const colors = require('colors') 4 | const config = require('config') 5 | 6 | const checkConfigExists = require('./lib/check-config-exists') 7 | const validateConfig = require('./lib/validate-config') 8 | const validateDockerContainer = require('./lib/validate-docker-container') 9 | const validateCertificate = require('./lib/validate-certificate') 10 | 11 | async function run() { 12 | try { 13 | const env = process.argv[2] || 'default' 14 | 15 | console.log('Validate configuration for environment: ', colors.green(env), '\n') 16 | 17 | checkConfigExists(config) 18 | await validateConfig(config, env) 19 | await validateDockerContainer(config, env) 20 | await validateCertificate(config, env) 21 | 22 | console.log('\n') 23 | console.log('Configuration check successfull, ready to deploy!! 🚀🚀🚀') 24 | console.log(`\nexec: ${colors.green('npm run deploy')}`) 25 | 26 | } catch (e) { 27 | if(e.errors) { 28 | e.errors.forEach(error => console.log(error)) 29 | } 30 | console.log(e.message) 31 | process.exit(1) 32 | } 33 | } 34 | 35 | run() 36 | -------------------------------------------------------------------------------- /packages/titus-infra-aws-mira/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target":"ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2016", "es2017.object", "es2017.string"], 6 | "strict": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "moduleResolution": "node", 9 | "allowSyntheticDefaultImports": true, 10 | "resolveJsonModule": true, 11 | "rootDir": "." 12 | }, 13 | "exclude": ["cdk.out"] 14 | } 15 | --------------------------------------------------------------------------------