Compare commits

..

45 Commits

Author SHA1 Message Date
standardci 0e5d7c918b chore(release): publish new version
- @standardnotes/api-gateway@1.1.1
 - @standardnotes/auth-server@1.1.1
 - @standardnotes/files-server@1.1.1
 - @standardnotes/scheduler-server@1.1.1
 - @standardnotes/syncing-server@1.1.1
2022-06-23 16:40:39 +00:00
Karol Sójko fd2358a4b8 Merge branch 'main' of github.com:standardnotes/server 2022-06-23 18:39:49 +02:00
Karol Sójko dd36b1859c feat: prepare auth for one branch only development 2022-06-23 18:39:42 +02:00
standardci 8c9a8a484f chore(release): publish new version
- @standardnotes/api-gateway@1.1.0
 - @standardnotes/auth-server@1.1.0
 - @standardnotes/files-server@1.1.0
 - @standardnotes/scheduler-server@1.1.0
 - @standardnotes/syncing-server@1.1.0
2022-06-23 16:36:52 +00:00
Karol Sójko 451ed1ae3a Merge branch 'main' of github.com:standardnotes/server 2022-06-23 18:36:03 +02:00
Karol Sójko 4ec30df2dc fix: versioning on push 2022-06-23 18:35:48 +02:00
Karol Sójko 163b7ff2d8 feat: prepare api-gateway for one branch only development 2022-06-23 18:34:03 +02:00
standardci 6e136e98b3 chore(release): publish new version
- @standardnotes/api-gateway@1.1.0-alpha.4
 - @standardnotes/auth-server@1.1.0-alpha.8
 - @standardnotes/files-server@1.1.0-alpha.6
 - @standardnotes/scheduler-server@1.1.0-alpha.16
 - @standardnotes/syncing-server@1.1.0-alpha.10
2022-06-23 16:28:36 +00:00
Karol Sójko 100eef2cb8 feat: prepare files server and scheduler for one branch only development 2022-06-23 18:27:37 +02:00
Karol Sójko 1d8cf4b675 fix: rename syncing-server release workflow 2022-06-23 14:16:13 +02:00
Karol Sójko 5a01517097 Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-23 14:11:50 +02:00
Karol Sójko ca54d4e0a0 feat: prepare syncing server for one branch only development 2022-06-23 14:11:23 +02:00
standardci 2bcc4a2254 chore(release): publish
- @standardnotes/api-gateway@1.1.0-alpha.3
 - @standardnotes/auth-server@1.1.0-alpha.7
 - @standardnotes/files-server@1.1.0-alpha.5
 - @standardnotes/scheduler-server@1.1.0-alpha.15
 - @standardnotes/syncing-server@1.1.0-alpha.9
2022-06-23 11:49:15 +00:00
Karol Sójko afe5ff3e70 Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-23 13:48:31 +02:00
Karol Sójko 4d8b021284 fix: remove not working discord notifications 2022-06-23 13:48:20 +02:00
standardci 281dd3d378 chore(release): publish
- @standardnotes/api-gateway@1.1.0-alpha.2
 - @standardnotes/auth-server@1.1.0-alpha.6
 - @standardnotes/files-server@1.1.0-alpha.4
 - @standardnotes/scheduler-server@1.1.0-alpha.14
 - @standardnotes/syncing-server@1.1.0-alpha.8
2022-06-23 11:37:21 +00:00
Karol Sójko 7efb48dd2a fix: add missing curl to docker image for healthcheck purposes 2022-06-23 13:36:32 +02:00
standardci d7b68bcafb chore(release): publish
- @standardnotes/syncing-server@1.1.0-alpha.7
2022-06-23 10:23:58 +00:00
Karol Sójko 04d1dffe53 Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-23 12:23:08 +02:00
Karol Sójko db492c3787 fix: add install to the test process of syncing-server to compile microtime package 2022-06-23 12:22:59 +02:00
standardci d04b04507a chore(release): publish
- @standardnotes/syncing-server@1.1.0-alpha.6
2022-06-23 10:14:16 +00:00
Karol Sójko 6c87d3614d fix: upgrade time lib for syncing-server 2022-06-23 12:13:28 +02:00
Karol Sójko dd6d409ebb fix: docker hub building process 2022-06-23 11:47:43 +02:00
standardci 9f75c2b601 chore(release): publish
- @standardnotes/api-gateway@1.1.0-alpha.1
 - @standardnotes/files-server@1.1.0-alpha.3
 - @standardnotes/syncing-server@1.1.0-alpha.5
2022-06-23 09:41:40 +00:00
Karol Sójko 9df87a0e3d fix: local builds before dockage image build 2022-06-23 11:40:52 +02:00
Karol Sójko 628dcf1539 Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-23 11:40:41 +02:00
Karol Sójko 38b42dad62 fix: bump docker github action version 2022-06-23 11:37:25 +02:00
standardci 90359d61d9 chore(release): publish
- @standardnotes/api-gateway@1.1.0-alpha.0
 - @standardnotes/files-server@1.1.0-alpha.2
 - @standardnotes/syncing-server@1.1.0-alpha.4
2022-06-23 09:34:02 +00:00
Karol Sójko 57c3b9c29e feat: add api-gateway package 2022-06-23 11:33:14 +02:00
Karol Sójko b25f2e8c54 fix: remove unnessary cp of dotenv file 2022-06-23 08:50:55 +02:00
standardci 5be40fa99c chore(release): publish
- @standardnotes/auth-server@1.1.0-alpha.5
 - @standardnotes/files-server@1.1.0-alpha.1
 - @standardnotes/scheduler-server@1.1.0-alpha.13
 - @standardnotes/syncing-server@1.1.0-alpha.3
2022-06-22 14:48:20 +00:00
Karol Sójko bc909dd3aa Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-22 16:47:36 +02:00
Karol Sójko 3110c20596 fix: make DISABLE_USER_REGISTRATION env var optional 2022-06-22 16:47:30 +02:00
standardci 69a9c5555b chore(release): publish
- @standardnotes/auth-server@1.1.0-alpha.4
 - @standardnotes/files-server@1.1.0-alpha.0
 - @standardnotes/scheduler-server@1.1.0-alpha.12
 - @standardnotes/syncing-server@1.1.0-alpha.2
2022-06-22 14:46:11 +00:00
Karol Sójko cca6c7f65e Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-22 16:45:02 +02:00
Karol Sójko 7a8a5fcfdf feat: add files server package 2022-06-22 16:44:45 +02:00
standardci 845d310af9 chore(release): publish
- @standardnotes/auth-server@1.1.0-alpha.3
 - @standardnotes/scheduler-server@1.1.0-alpha.11
 - @standardnotes/syncing-server@1.1.0-alpha.1
2022-06-22 14:24:30 +00:00
Karol Sójko d61e6f338e Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-22 16:23:43 +02:00
Karol Sójko f2ddbc82d0 fix: bump @standardnotes/time dependency 2022-06-22 16:23:25 +02:00
standardci 8708b10dfc chore(release): publish
- @standardnotes/auth-server@1.1.0-alpha.2
 - @standardnotes/scheduler-server@1.1.0-alpha.10
 - @standardnotes/syncing-server@1.1.0-alpha.0
2022-06-22 14:17:02 +00:00
Karol Sójko 165ee50dc8 Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-22 16:16:13 +02:00
Karol Sójko 6cc4ef90db feat: add syncing-server package 2022-06-22 16:16:03 +02:00
standardci ad8a07b003 chore(release): publish
- @standardnotes/auth-server@1.1.0-alpha.1
 - @standardnotes/scheduler-server@1.1.0-alpha.9
2022-06-22 12:43:19 +00:00
Karol Sójko 923fe2a19b Merge branch 'develop' of github.com:standardnotes/server into develop 2022-06-22 14:42:31 +02:00
Karol Sójko f3c6fff20f fix: docker entrypoint for auth 2022-06-22 14:41:56 +02:00
336 changed files with 17730 additions and 298 deletions
+141
View File
@@ -0,0 +1,141 @@
name: Api Gateway
concurrency:
group: api_gateway
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/api-gateway*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v1
with:
node-version: '16.x'
- run: yarn lint:api-gateway
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build locally
run: yarn build:api-gateway
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: api-gateway
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/api-gateway -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build locally
run: yarn build:api-gateway
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build, tag, and push image to Docker Hub
run: |
yarn docker build @standardnotes/api-gateway -t standardnotes/api-gateway:${{ github.sha }}
docker push standardnotes/api-gateway:${{ github.sha }}
docker tag standardnotes/api-gateway:${{ github.sha }} standardnotes/api-gateway:latest
docker push standardnotes/api-gateway:latest
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: DEV - Download task definition
run: |
aws ecs describe-task-definition --task-definition api-gateway-dev --query taskDefinition > task-definition.json
- name: DEV - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="api-gateway-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: DEV - Fill in the new image ID in the Amazon ECS task definition
id: task-def-dev
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: api-gateway-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/api-gateway:${{ github.sha }}
- name: DEV - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
service: api-gateway-dev
cluster: dev
wait-for-service-stability: true
- name: PROD - Download task definition
run: |
aws ecs describe-task-definition --task-definition api-gateway-prod --query taskDefinition > task-definition.json
- name: PROD - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="api-gateway-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: PROD - Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: api-gateway-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/api-gateway:${{ github.sha }}
- name: PROD - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: api-gateway-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: deploy-web
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_API_GATEWAY_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
@@ -1,14 +1,13 @@
name: Auth Server Dev
name: Auth Server
concurrency:
group: auth_dev_environment
group: auth
cancel-in-progress: true
on:
push:
tags:
- '@standardnotes/auth-server@[0-9]*.[0-9]*.[0-9]*-alpha.[0-9]*'
- '@standardnotes/auth-server@[0-9]*.[0-9]*.[0-9]*-beta.[0-9]*'
- '*standardnotes/auth-server*'
workflow_dispatch:
jobs:
@@ -50,8 +49,8 @@ jobs:
run: |
yarn docker build @standardnotes/auth-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:dev
docker push $ECR_REGISTRY/$ECR_REPOSITORY:dev
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
@@ -71,8 +70,8 @@ jobs:
run: |
yarn docker build @standardnotes/auth-server -t standardnotes/auth:${{ github.sha }}
docker push standardnotes/auth:${{ github.sha }}
docker tag standardnotes/auth:${{ github.sha }} standardnotes/auth:dev
docker push standardnotes/auth:dev
docker tag standardnotes/auth:${{ github.sha }} standardnotes/auth:latest
docker push standardnotes/auth:latest
deploy-web:
needs: publish-aws-ecr
@@ -86,26 +85,46 @@ jobs:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
- name: DEV - Download task definition
run: |
aws ecs describe-task-definition --task-definition auth-dev --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
- name: DEV - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="auth-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
- name: DEV - Fill in the new image ID in the Amazon ECS task definition
id: task-def-dev
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: auth-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/auth:${{ github.sha }}
- name: Deploy Amazon ECS task definition
- name: DEV - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
service: auth-dev
cluster: dev
wait-for-service-stability: true
- name: PROD - Download task definition
run: |
aws ecs describe-task-definition --task-definition auth-prod --query taskDefinition > task-definition.json
- name: PROD - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="auth-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: PROD - Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: auth-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/auth:${{ github.sha }}
- name: PROD - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: auth-prod
cluster: prod
wait-for-service-stability: true
deploy-worker:
needs: publish-aws-ecr
@@ -119,26 +138,46 @@ jobs:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
- name: DEV - Download task definition
run: |
aws ecs describe-task-definition --task-definition auth-worker-dev --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
- name: DEV - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="auth-worker-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
- name: DEV - Fill in the new image ID in the Amazon ECS task definition
id: task-def-dev
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: auth-worker-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/auth:${{ github.sha }}
- name: Deploy Amazon ECS task definition
- name: DEV - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
service: auth-worker-dev
cluster: dev
wait-for-service-stability: true
- name: PROD - Download task definition
run: |
aws ecs describe-task-definition --task-definition auth-worker-prod --query taskDefinition > task-definition.json
- name: PROD - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="auth-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: PROD - Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: auth-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/auth:${{ github.sha }}
- name: PROD - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: auth-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-web, deploy-worker ]
@@ -150,7 +189,7 @@ jobs:
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_AUTH_WEB_DEV }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_AUTH_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
@@ -159,19 +198,7 @@ jobs:
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_AUTH_WORKER_DEV }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_AUTH_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
notify_discord:
needs: [ deploy-web, deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Run Discord Webhook
uses: johnnyhuy/actions-discord-git-webhook@main
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
+204
View File
@@ -0,0 +1,204 @@
name: Files Server
concurrency:
group: files
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/files-server*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v1
with:
node-version: '16.x'
- run: yarn lint:files
- run: yarn test:files
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build locally
run: yarn build:files
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: files
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/files-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build locally
run: yarn build:files
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build, tag, and push image to Docker Hub
run: |
yarn docker build @standardnotes/files-server -t standardnotes/files:${{ github.sha }}
docker push standardnotes/files:${{ github.sha }}
docker tag standardnotes/files:${{ github.sha }} standardnotes/files:latest
docker push standardnotes/files:latest
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: DEV - Download task definition
run: |
aws ecs describe-task-definition --task-definition files-dev --query taskDefinition > task-definition.json
- name: DEV - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="files-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: DEV - Fill in the new image ID in the Amazon ECS task definition
id: task-def-dev
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: files-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/files:${{ github.sha }}
- name: DEV - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
service: files-dev
cluster: dev
wait-for-service-stability: true
- name: PROD - Download task definition
run: |
aws ecs describe-task-definition --task-definition files-prod --query taskDefinition > task-definition.json
- name: PROD - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="files-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: PROD - Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: files-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/files:${{ github.sha }}
- name: PROD - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: files-prod
cluster: prod
wait-for-service-stability: true
deploy-worker:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: DEV - Download task definition
run: |
aws ecs describe-task-definition --task-definition files-worker-dev --query taskDefinition > task-definition.json
- name: DEV - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="files-worker-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: DEV - Fill in the new image ID in the Amazon ECS task definition
id: task-def-dev
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: files-worker-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/files:${{ github.sha }}
- name: DEV - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
service: files-worker-dev
cluster: dev
wait-for-service-stability: true
- name: PROD - Download task definition
run: |
aws ecs describe-task-definition --task-definition files-worker-prod --query taskDefinition > task-definition.json
- name: PROD - Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="files-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: PROD - Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: files-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/files:${{ github.sha }}
- name: PROD - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: files-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-web, deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_FILES_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_FILES_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
@@ -1,14 +1,13 @@
name: Scheduler Server Dev
name: Scheduler Server
concurrency:
group: scheduler_dev_environment
group: scheduler
cancel-in-progress: true
on:
push:
tags:
- '@standardnotes/scheduler-server@[0-9]*.[0-9]*.[0-9]*-alpha.[0-9]*'
- '@standardnotes/scheduler-server@[0-9]*.[0-9]*.[0-9]*-beta.[0-9]*'
- '*standardnotes/scheduler-server*'
workflow_dispatch:
jobs:
@@ -50,8 +49,8 @@ jobs:
run: |
yarn docker build @standardnotes/scheduler-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:dev
docker push $ECR_REGISTRY/$ECR_REPOSITORY:dev
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
@@ -71,8 +70,8 @@ jobs:
run: |
yarn docker build @standardnotes/scheduler-server -t standardnotes/scheduler:${{ github.sha }}
docker push standardnotes/scheduler:${{ github.sha }}
docker tag standardnotes/scheduler:${{ github.sha }} standardnotes/scheduler:dev
docker push standardnotes/scheduler:dev
docker tag standardnotes/scheduler:${{ github.sha }} standardnotes/scheduler:latest
docker push standardnotes/scheduler:latest
deploy-worker:
needs: publish-aws-ecr
@@ -86,26 +85,46 @@ jobs:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
- name: DEV - Download task definition
run: |
aws ecs describe-task-definition --task-definition scheduler-worker-dev --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="scheduler-worker-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
- name: DEV - Fill in the new image ID in the Amazon ECS task definition
id: task-def-dev
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: scheduler-worker-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/scheduler-worker:${{ github.sha }}
- name: Deploy Amazon ECS task definition
- name: DEV - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
service: scheduler-worker-dev
cluster: dev
wait-for-service-stability: true
- name: PROD - Download task definition
run: |
aws ecs describe-task-definition --task-definition scheduler-worker-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="scheduler-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: PROD - Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: scheduler-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/scheduler-worker:${{ github.sha }}
- name: PROD - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: scheduler-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-worker ]
@@ -118,19 +137,7 @@ jobs:
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SCHEDULER_WORKER_DEV }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SCHEDULER_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
notify_discord:
needs: [ deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: Run Discord Webhook
uses: johnnyhuy/actions-discord-git-webhook@main
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
@@ -0,0 +1,223 @@
name: Syncing Server
concurrency:
group: syncing_server
cancel-in-progress: true
on:
push:
tags:
- '*standardnotes/syncing-server*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v1
with:
node-version: '16.x'
- run: yarn install --immutable
- run: yarn lint:syncing-server
- run: yarn test:syncing-server
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build locally
run: yarn build:syncing-server
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: syncing-server-js
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/syncing-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build locally
run: yarn build:syncing-server
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build, tag, and push image to Docker Hub
run: |
yarn docker build @standardnotes/syncing-server -t standardnotes/syncing-server-js:${{ github.sha }}
docker push standardnotes/syncing-server-js:${{ github.sha }}
docker tag standardnotes/syncing-server-js:${{ github.sha }} standardnotes/syncing-server-js:latest
docker push standardnotes/syncing-server-js:latest
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: DEV - Download task definition
run: |
aws ecs describe-task-definition --task-definition syncing-server-js-dev --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="syncing-server-js-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: DEV - Fill in the new image ID in the Amazon ECS task definition
id: task-def-dev
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: syncing-server-js-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/syncing-server-js:${{ github.sha }}
- name: DEV - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
service: syncing-server-js-dev
cluster: dev
wait-for-service-stability: true
- name: PROD - Download task definition
run: |
aws ecs describe-task-definition --task-definition syncing-server-js-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="syncing-server-js-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: PROD - Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: syncing-server-js-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/syncing-server-js:${{ github.sha }}
- name: PROD - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: syncing-server-js-prod
cluster: prod
wait-for-service-stability: true
deploy-worker:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: DEV - Download task definition
run: |
aws ecs describe-task-definition --task-definition syncing-server-js-worker-dev --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="syncing-server-js-worker-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: DEV - Fill in the new image ID in the Amazon ECS task definition
id: task-def-dev
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: syncing-server-js-worker-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/syncing-server-js:${{ github.sha }}
- name: DEV - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
service: syncing-server-js-worker-dev
cluster: dev
wait-for-service-stability: true
- name: PROD - Download task definition
run: |
aws ecs describe-task-definition --task-definition syncing-server-js-worker-prod --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="syncing-server-js-worker-prod") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: PROD - Fill in the new image ID in the Amazon ECS task definition
id: task-def-prod
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: syncing-server-js-worker-prod
image: ${{ secrets.AWS_ECR_REGISTRY }}/syncing-server-js:${{ github.sha }}
- name: PROD - Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
service: syncing-server-js-worker-prod
cluster: prod
wait-for-service-stability: true
newrelic:
needs: [ deploy-web, deploy-worker ]
runs-on: ubuntu-latest
steps:
- name: DEV - Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SYNCING_SERVER_WEB_DEV }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: DEV - Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SYNCING_SERVER_WORKER_DEV }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: PROD - Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SYNCING_SERVER_WEB_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
- name: PROD - Create New Relic deployment marker for Worker
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_SYNCING_SERVER_WORKER_PROD }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
+2 -7
View File
@@ -2,7 +2,7 @@ name: Version Bump
on:
push:
branches: [ develop, main ]
branches: [ main ]
jobs:
bump:
@@ -35,10 +35,5 @@ jobs:
- name: Install locally
run: yarn install --immutable
- name: Bump Prod Version
if: ${{ github.ref == 'refs/heads/main' }}
- name: Bump Version
run: yarn release:prod
- name: Bump Beta Version
if: ${{ github.ref == 'refs/heads/develop' }}
run: yarn release:beta
+3
View File
@@ -15,3 +15,6 @@ newrelic_agent.log
!.yarn/unplugged
!.yarn/sdks
!.yarn/versions
packages/files/uploads/*
!packages/files/uploads/.gitkeep
Generated
+925 -178
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15 -1
View File
@@ -14,18 +14,31 @@
"lint": "yarn workspaces foreach -p -j 10 --verbose run lint",
"lint:auth": "yarn workspace @standardnotes/auth-server lint",
"lint:scheduler": "yarn workspace @standardnotes/scheduler-server lint",
"lint:syncing-server": "yarn workspace @standardnotes/syncing-server lint",
"lint:files": "yarn workspace @standardnotes/files-server lint",
"lint:api-gateway": "yarn workspace @standardnotes/api-gateway lint",
"test": "yarn workspaces foreach -p -j 10 --verbose run test",
"test:auth": "yarn workspace @standardnotes/auth-server test",
"test:scheduler": "yarn workspace @standardnotes/scheduler-server test",
"test:syncing-server": "yarn workspace @standardnotes/syncing-server test",
"test:files": "yarn workspace @standardnotes/files-server test",
"clean": "yarn workspaces foreach -p --verbose run clean",
"setup:env": "yarn workspaces foreach -p --verbose run setup:env",
"build": "yarn workspaces foreach -pt -j 10 --verbose run build",
"build:auth": "yarn workspace @standardnotes/auth-server build",
"build:scheduler": "yarn workspace @standardnotes/scheduler-server build",
"build:syncing-server": "yarn workspace @standardnotes/syncing-server build",
"build:files": "yarn workspace @standardnotes/files-server build",
"build:api-gateway": "yarn workspace @standardnotes/api-gateway build",
"start:auth": "yarn workspace @standardnotes/auth-server start",
"start:auth-worker": "yarn workspace @standardnotes/auth-server worker",
"start:scheduler": "yarn workspace @standardnotes/scheduler-server worker",
"release:beta": "lerna version --conventional-prerelease --conventional-commits --yes -m \"chore(release): publish\""
"start:syncing-server": "yarn workspace @standardnotes/syncing-server start",
"start:syncing-server-worker": "yarn workspace @standardnotes/syncing-server worker",
"start:files": "yarn workspace @standardnotes/files-server start",
"start:files-worker": "yarn workspace @standardnotes/files-server worker",
"start:api-gateway": "yarn workspace @standardnotes/api-gateway start",
"release:prod": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\""
},
"devDependencies": {
"@commitlint/cli": "^17.0.2",
@@ -34,6 +47,7 @@
"@lerna-lite/list": "^1.5.1",
"@lerna-lite/run": "^1.5.1",
"@types/jest": "^28.1.3",
"@types/node": "^18.0.0",
"@typescript-eslint/parser": "^5.29.0",
"eslint": "^8.17.0",
"eslint-config-prettier": "^8.5.0",
+33
View File
@@ -0,0 +1,33 @@
LOG_LEVEL=debug
NODE_ENV=development
VERSION=development
PORT=3000
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
AUTH_SERVER_URL=http://auth:3000
PAYMENTS_SERVER_URL=http://payments:3000
FILES_SERVER_URL=http://files:3000
HTTP_CALL_TIMEOUT=60000
AUTH_JWT_SECRET=auth_jwt_secret
# (Optional) New Relic Setup
NEW_RELIC_ENABLED=false
NEW_RELIC_APP_NAME=API Gateway
NEW_RELIC_LICENSE_KEY=
NEW_RELIC_NO_CONFIG_FILE=true
NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=false
NEW_RELIC_LOG_ENABLED=false
NEW_RELIC_LOG_LEVEL=info
REDIS_URL=redis://cache
REDIS_EVENTS_CHANNEL=events
# (Optional) SNS Setup
SNS_TOPIC_ARN=
SNS_AWS_REGION=
# (Optional) Caching Cross Service Tokens
CROSS_SERVICE_TOKEN_CACHE_TTL=
+2
View File
@@ -0,0 +1,2 @@
dist
test-setup.ts
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "../../.eslintrc",
"parserOptions": {
"project": "./linter.tsconfig.json"
}
}
+36
View File
@@ -0,0 +1,36 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.1.0...@standardnotes/api-gateway@1.1.1) (2022-06-23)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.1.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.1.0-alpha.4...@standardnotes/api-gateway@1.1.0) (2022-06-23)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.1.0-alpha.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.1.0-alpha.3...@standardnotes/api-gateway@1.1.0-alpha.4) (2022-06-23)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.1.0-alpha.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.1.0-alpha.2...@standardnotes/api-gateway@1.1.0-alpha.3) (2022-06-23)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.1.0-alpha.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.1.0-alpha.1...@standardnotes/api-gateway@1.1.0-alpha.2) (2022-06-23)
### Bug Fixes
* add missing curl to docker image for healthcheck purposes ([7efb48d](https://github.com/standardnotes/api-gateway/commit/7efb48dd2a6066c29601d34bfcbfe6231f644c50))
# [1.1.0-alpha.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.1.0-alpha.0...@standardnotes/api-gateway@1.1.0-alpha.1) (2022-06-23)
**Note:** Version bump only for package @standardnotes/api-gateway
# 1.1.0-alpha.0 (2022-06-23)
### Features
* add api-gateway package ([57c3b9c](https://github.com/standardnotes/api-gateway/commit/57c3b9c29e5b16449c864e59dbc1fd11689125f9))
+25
View File
@@ -0,0 +1,25 @@
FROM node:16.15.1-alpine AS builder
# Install dependencies for building native libraries
RUN apk add --update git openssh-client curl python3 alpine-sdk
WORKDIR /workspace
# docker-build plugin copies everything needed for `yarn install` to `manifests` folder.
COPY manifests ./
RUN yarn install --immutable
FROM node:16.15.1-alpine
WORKDIR /workspace
# Copy the installed dependencies from the previous stage.
COPY --from=builder /workspace ./
# docker-build plugin runs `yarn pack` in all workspace dependencies and copies them to `packs` folder.
COPY packs ./
ENTRYPOINT [ "/workspace/packages/api-gateway/docker/entrypoint.sh" ]
CMD [ "start-web" ]
+75
View File
@@ -0,0 +1,75 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventPublisherInterface, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
import { AnalyticsActivity, AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
const requestReport = async (
analyticsStore: AnalyticsStoreInterface,
statisticsStore: StatisticsStoreInterface,
domainEventPublisher: DomainEventPublisherInterface,
): Promise<void> => {
const event: DailyAnalyticsReportGeneratedEvent = {
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
createdAt: new Date(),
meta: {
correlation: {
userIdentifier: '',
userIdentifierType: 'uuid',
},
},
payload: {
applicationStatistics: await statisticsStore.getYesterdayApplicationUsage(),
snjsStatistics: await statisticsStore.getYesterdaySNJSUsage(),
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
activityStatistics: [
{
name: AnalyticsActivity.EditingItems,
retention: await analyticsStore.calculateActivityRetention(
AnalyticsActivity.EditingItems,
Period.DayBeforeYesterday,
Period.Yesterday,
),
totalCount: await analyticsStore.calculateActivityTotalCount(
AnalyticsActivity.EditingItems,
Period.Yesterday,
),
},
],
},
}
await domainEventPublisher.publish(event)
}
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting usage report generation...')
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
const statisticsStore: StatisticsStoreInterface = container.get(TYPES.StatisticsStore)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher))
.then(() => {
logger.info('Usage report generation complete')
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish usage report generation: ${error.message}`)
process.exit(1)
})
})
+113
View File
@@ -0,0 +1,113 @@
import 'reflect-metadata'
import 'newrelic'
import * as Sentry from '@sentry/node'
import '../src/Controller/LegacyController'
import '../src/Controller/HealthCheckController'
import '../src/Controller/v1/SessionsController'
import '../src/Controller/v1/UsersController'
import '../src/Controller/v1/ActionsController'
import '../src/Controller/v1/InvoicesController'
import '../src/Controller/v1/RevisionsController'
import '../src/Controller/v1/ItemsController'
import '../src/Controller/v1/PaymentsController'
import '../src/Controller/v1/WebSocketsController'
import '../src/Controller/v1/TokensController'
import '../src/Controller/v1/OfflineController'
import '../src/Controller/v1/FilesController'
import '../src/Controller/v1/SubscriptionInvitesController'
import '../src/Controller/v2/PaymentsControllerV2'
import '../src/Controller/v2/ActionsControllerV2'
import * as helmet from 'helmet'
import * as cors from 'cors'
import { text, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'
import * as winston from 'winston'
import { InversifyExpressServer } from 'inversify-express-utils'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const server = new InversifyExpressServer(container)
server.setConfig((app) => {
app.use((_request: Request, response: Response, next: NextFunction) => {
response.setHeader('X-API-Gateway-Version', container.get(TYPES.VERSION))
next()
})
/* eslint-disable */
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["https: 'self'"],
baseUri: ["'self'"],
childSrc: ["*", "blob:"],
connectSrc: ["*"],
fontSrc: ["*", "'self'"],
formAction: ["'self'"],
frameAncestors: ["*", "*.standardnotes.org", "*.standardnotes.com"],
frameSrc: ["*", "blob:"],
imgSrc: ["'self'", "*", "data:"],
manifestSrc: ["'self'"],
mediaSrc: ["'self'"],
objectSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'"]
}
}
}))
/* eslint-enable */
app.use(json({ limit: '50mb' }))
app.use(
text({
type: ['text/plain', 'application/x-www-form-urlencoded', 'application/x-www-form-urlencoded; charset=utf-8'],
}),
)
app.use(cors())
if (env.get('SENTRY_DSN', true)) {
Sentry.init({
dsn: env.get('SENTRY_DSN'),
integrations: [new Sentry.Integrations.Http({ tracing: false, breadcrumbs: true })],
tracesSampleRate: 0,
})
app.use(Sentry.Handlers.requestHandler() as RequestHandler)
}
})
const logger: winston.Logger = container.get(TYPES.Logger)
server.setErrorConfig((app) => {
if (env.get('SENTRY_DSN', true)) {
app.use(Sentry.Handlers.errorHandler() as ErrorRequestHandler)
}
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {
logger.error(error.stack)
response.status(500).send({
error: {
message:
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
},
})
})
})
const serverInstance = server.build()
serverInstance.listen(env.get('PORT'))
logger.info(`Server started on port ${process.env.PORT}`)
})
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
set -e
COMMAND=$1 && shift 1
case "$COMMAND" in
'start-local' )
echo "Building the project..."
yarn workspace @standardnotes/api-gateway build
echo "Starting Web..."
yarn workspace @standardnotes/api-gateway start
;;
'start-web' )
echo "Starting Web..."
yarn workspace @standardnotes/api-gateway start
;;
'report' )
echo "Starting Usage Report Generation..."
yarn workspace @standardnotes/api-gateway report
;;
* )
echo "Unknown command"
;;
esac
exec "$@"
+18
View File
@@ -0,0 +1,18 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const base = require('../../jest.config');
module.exports = {
...base,
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
},
},
coveragePathIgnorePatterns: [
'/Bootstrap/',
'HealthCheckController'
],
setupFilesAfterEnv: [
'./test-setup.ts'
]
};
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["dist", "test-setup.ts"]
}
+60
View File
@@ -0,0 +1,60 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.1.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
"description": "API Gateway For Standard Notes Services",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"repository": "git@github.com:standardnotes/api-gateway.git",
"author": "Karol Sójko <karolsojko@standardnotes.com>",
"license": "AGPL-3.0-or-later",
"scripts": {
"clean": "rm -fr dist",
"prebuild": "yarn clean",
"build": "tsc --rootDir ./",
"lint": "eslint . --ext .ts",
"start": "yarn node dist/bin/server.js",
"report": "yarn node dist/bin/report.js"
},
"dependencies": {
"@newrelic/native-metrics": "7.0.2",
"@newrelic/winston-enricher": "^2.1.0",
"@sentry/node": "^6.16.1",
"@standardnotes/analytics": "^1.4.0",
"@standardnotes/auth": "3.19.2",
"@standardnotes/domain-events": "2.29.0",
"@standardnotes/domain-events-infra": "1.4.127",
"@standardnotes/time": "^1.7.0",
"aws-sdk": "^2.1160.0",
"axios": "0.24.0",
"cors": "2.8.5",
"dotenv": "8.2.0",
"express": "4.17.1",
"helmet": "4.4.1",
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.0.6",
"jsonwebtoken": "8.5.1",
"newrelic": "8.6.0",
"prettyjson": "1.2.1",
"reflect-metadata": "0.1.13",
"winston": "3.3.3"
},
"devDependencies": {
"@types/cors": "^2.8.9",
"@types/express": "^4.17.11",
"@types/ioredis": "^4.28.10",
"@types/jest": "^28.1.3",
"@types/jsonwebtoken": "^8.5.0",
"@types/newrelic": "^7.0.1",
"@types/prettyjson": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"eslint": "^8.14.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^28.1.1",
"nodemon": "^2.0.16",
"ts-jest": "^28.0.1"
}
}
@@ -0,0 +1,117 @@
import * as winston from 'winston'
import axios, { AxiosInstance } from 'axios'
import Redis from 'ioredis'
import { Container } from 'inversify'
import * as AWS from 'aws-sdk'
import {
AnalyticsStoreInterface,
PeriodKeyGenerator,
RedisAnalyticsStore,
RedisStatisticsStore,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
import { RedisDomainEventPublisher, SNSDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { Timer, TimerInterface } from '@standardnotes/time'
import { Env } from './Env'
import TYPES from './Types'
import { AuthMiddleware } from '../Controller/AuthMiddleware'
import { HttpServiceInterface } from '../Service/Http/HttpServiceInterface'
import { HttpService } from '../Service/Http/HttpService'
import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionTokenAuthMiddleware'
import { StatisticsMiddleware } from '../Controller/StatisticsMiddleware'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicWinstonEnricher = require('@newrelic/winston-enricher')
export class ContainerConfigLoader {
async load(): Promise<Container> {
const env: Env = new Env()
env.load()
const container = new Container()
const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
winstonFormatters.push(newrelicWinstonEnricher())
}
const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info',
format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })],
})
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Redis).toConstantValue(redis)
if (env.get('SNS_AWS_REGION', true)) {
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
new AWS.SNS({
apiVersion: 'latest',
region: env.get('SNS_AWS_REGION', true),
}),
)
}
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
// env vars
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL'))
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container
.bind(TYPES.HTTP_CALL_TIMEOUT)
.toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000)
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
container.bind(TYPES.SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN', true))
container.bind(TYPES.SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
container.bind(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL).toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
// Middleware
container.bind<AuthMiddleware>(TYPES.AuthMiddleware).to(AuthMiddleware)
container
.bind<SubscriptionTokenAuthMiddleware>(TYPES.SubscriptionTokenAuthMiddleware)
.to(SubscriptionTokenAuthMiddleware)
container.bind<StatisticsMiddleware>(TYPES.StatisticsMiddleware).to(StatisticsMiddleware)
// Services
container.bind<HttpServiceInterface>(TYPES.HTTPService).to(HttpService)
const periodKeyGenerator = new PeriodKeyGenerator()
container
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
.toConstantValue(new RedisAnalyticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
container
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
.toConstantValue(new RedisStatisticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
if (env.get('SNS_TOPIC_ARN', true)) {
container
.bind<SNSDomainEventPublisher>(TYPES.DomainEventPublisher)
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
} else {
container
.bind<RedisDomainEventPublisher>(TYPES.DomainEventPublisher)
.toConstantValue(
new RedisDomainEventPublisher(container.get(TYPES.Redis), container.get(TYPES.REDIS_EVENTS_CHANNEL)),
)
}
return container
}
}
+24
View File
@@ -0,0 +1,24 @@
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}
@@ -0,0 +1,31 @@
const TYPES = {
Logger: Symbol.for('Logger'),
Redis: Symbol.for('Redis'),
HTTPClient: Symbol.for('HTTPClient'),
SNS: Symbol.for('SNS'),
// env vars
SYNCING_SERVER_JS_URL: Symbol.for('SYNCING_SERVER_JS_URL'),
AUTH_SERVER_URL: Symbol.for('AUTH_SERVER_URL'),
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
HTTP_CALL_TIMEOUT: Symbol.for('HTTP_CALL_TIMEOUT'),
VERSION: Symbol.for('VERSION'),
SNS_TOPIC_ARN: Symbol.for('SNS_TOPIC_ARN'),
SNS_AWS_REGION: Symbol.for('SNS_AWS_REGION'),
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
CROSS_SERVICE_TOKEN_CACHE_TTL: Symbol.for('CROSS_SERVICE_TOKEN_CACHE_TTL'),
// Middleware
StatisticsMiddleware: Symbol.for('StatisticsMiddleware'),
AuthMiddleware: Symbol.for('AuthMiddleware'),
SubscriptionTokenAuthMiddleware: Symbol.for('SubscriptionTokenAuthMiddleware'),
// Services
HTTPService: Symbol.for('HTTPService'),
CrossServiceTokenCache: Symbol.for('CrossServiceTokenCache'),
AnalyticsStore: Symbol.for('AnalyticsStore'),
StatisticsStore: Symbol.for('StatisticsStore'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
Timer: Symbol.for('Timer'),
}
export default TYPES
@@ -0,0 +1,124 @@
import { CrossServiceTokenData } from '@standardnotes/auth'
import { TimerInterface } from '@standardnotes/time'
import { NextFunction, Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { BaseMiddleware } from 'inversify-express-utils'
import { verify } from 'jsonwebtoken'
import { AxiosError, AxiosInstance } from 'axios'
import { Logger } from 'winston'
import TYPES from '../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
@injectable()
export class AuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.AUTH_JWT_SECRET) private jwtSecret: string,
@inject(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL) private crossServiceTokenCacheTTL: number,
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
const authHeaderValue = request.headers.authorization as string
if (!authHeaderValue) {
response.status(401).send({
error: {
tag: 'invalid-auth',
message: 'Invalid login credentials.',
},
})
return
}
try {
let crossServiceTokenFetchedFromCache = true
let crossServiceToken = null
if (this.crossServiceTokenCacheTTL) {
crossServiceToken = await this.crossServiceTokenCache.get(authHeaderValue)
}
if (crossServiceToken === null) {
const authResponse = await this.httpClient.request({
method: 'POST',
headers: {
Authorization: authHeaderValue,
Accept: 'application/json',
},
validateStatus: (status: number) => {
return status >= 200 && status < 500
},
url: `${this.authServerUrl}/sessions/validate`,
})
if (authResponse.status > 200) {
response.setHeader('content-type', authResponse.headers['content-type'])
response.status(authResponse.status).send(authResponse.data)
return
}
crossServiceToken = authResponse.data.authToken
crossServiceTokenFetchedFromCache = false
}
response.locals.authToken = crossServiceToken
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
await this.crossServiceTokenCache.set({
authorizationHeaderValue: authHeaderValue,
encodedCrossServiceToken: crossServiceToken,
expiresAtInSeconds: this.getCrossServiceTokenCacheExpireTimestamp(decodedToken),
userUuid: decodedToken.user.uuid,
})
}
response.locals.userUuid = decodedToken.user.uuid
response.locals.roles = decodedToken.roles
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError
? JSON.stringify((error as AxiosError).response?.data)
: (error as Error).message
this.logger.error(
`Could not pass the request to ${this.authServerUrl}/sessions/validate on underlying service: ${errorMessage}`,
)
this.logger.debug('Response error: %O', (error as AxiosError).response ?? error)
if ((error as AxiosError).response?.headers['content-type']) {
response.setHeader('content-type', (error as AxiosError).response?.headers['content-type'] as string)
}
const errorCode = (error as AxiosError).isAxiosError ? +((error as AxiosError).code as string) : 500
response.status(errorCode).send(errorMessage)
return
}
return next()
}
private getCrossServiceTokenCacheExpireTimestamp(token: CrossServiceTokenData): number {
const crossServiceTokenDefaultCacheExpiration = this.timer.getTimestampInSeconds() + this.crossServiceTokenCacheTTL
if (token.session === undefined) {
return crossServiceTokenDefaultCacheExpiration
}
const sessionAccessExpiration = this.timer.convertStringDateToSeconds(token.session.access_expiration)
const sessionRefreshExpiration = this.timer.convertStringDateToSeconds(token.session.refresh_expiration)
return Math.min(crossServiceTokenDefaultCacheExpiration, sessionAccessExpiration, sessionRefreshExpiration)
}
}
@@ -0,0 +1,9 @@
import { controller, httpGet } from 'inversify-express-utils'
@controller('/healthcheck')
export class HealthCheckController {
@httpGet('/')
public async get(): Promise<string> {
return 'OK'
}
}
@@ -0,0 +1,124 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { controller, all, BaseHttpController, httpPost, httpGet, results, httpDelete } from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { HttpServiceInterface } from '../Service/Http/HttpServiceInterface'
@controller('', TYPES.StatisticsMiddleware)
export class LegacyController extends BaseHttpController {
private AUTH_ROUTES: Map<string, string>
private PARAMETRIZED_AUTH_ROUTES: Map<string, string>
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
this.AUTH_ROUTES = new Map([
['POST:/auth', 'POST:auth'],
['POST:/auth/sign_out', 'POST:auth/sign_out'],
['POST:/auth/change_pw', 'PUT:/users/legacy-endpoint-user/attributes/credentials'],
['GET:/sessions', 'GET:sessions'],
['DELETE:/session', 'DELETE:session'],
['DELETE:/session/all', 'DELETE:session/all'],
['POST:/session/refresh', 'POST:session/refresh'],
['POST:/auth/sign_in', 'POST:auth/sign_in'],
['GET:/auth/params', 'GET:auth/params'],
])
this.PARAMETRIZED_AUTH_ROUTES = new Map([
['PATCH:/users/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', 'users/{uuid}'],
])
}
@httpPost('/items/sync', TYPES.AuthMiddleware)
async legacyItemsSync(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
@httpGet('/items/:item_id/revisions', TYPES.AuthMiddleware)
async legacyGetRevisions(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
@httpGet('/items/:item_id/revisions/:id', TYPES.AuthMiddleware)
async legacyGetRevision(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
@httpGet('/items/mfa/:userUuid')
async blockedMFARequest(): Promise<results.StatusCodeResult> {
return this.statusCode(401)
}
@httpDelete('/items/mfa/:userUuid')
async blockedMFARemoveRequest(): Promise<results.StatusCodeResult> {
return this.statusCode(401)
}
@all('*')
async legacyProxyToSyncingServer(request: Request, response: Response): Promise<void> {
if (request.path === '/') {
response.send('Welcome to the Standard Notes server infrastructure. Learn more at https://docs.standardnotes.com')
return
}
if (this.shouldBeRedirectedToAuthService(request)) {
const methodAndPath = this.getMethodAndPath(request)
request.method = methodAndPath.method
await this.httpService.callAuthServerWithLegacyFormat(request, response, methodAndPath.path, request.body)
return
}
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
private getMethodAndPath(request: Request): { method: string; path: string } {
const requestKey = `${request.method}:${request.path}`
if (this.AUTH_ROUTES.has(requestKey)) {
const legacyRoute = this.AUTH_ROUTES.get(requestKey) as string
const legacyRouteMethodAndPath = legacyRoute.split(':')
return {
method: legacyRouteMethodAndPath[0],
path: legacyRouteMethodAndPath[1],
}
}
for (const key of this.AUTH_ROUTES.keys()) {
const regExp = new RegExp(key)
const matches = regExp.exec(requestKey)
if (matches !== null) {
const legacyRoute = (this.AUTH_ROUTES.get(key) as string).replace('{uuid}', matches[1])
const legacyRouteMethodAndPath = legacyRoute.split(':')
return {
method: legacyRouteMethodAndPath[0],
path: legacyRouteMethodAndPath[1],
}
}
}
throw Error('could not find path for key')
}
private shouldBeRedirectedToAuthService(request: Request): boolean {
const requestKey = `${request.method}:${request.path}`
if (this.AUTH_ROUTES.has(requestKey)) {
return true
}
for (const key of this.PARAMETRIZED_AUTH_ROUTES.keys()) {
const regExp = new RegExp(key)
const matches = regExp.test(requestKey)
if (matches) {
return true
}
}
return false
}
}
@@ -0,0 +1,31 @@
import { NextFunction, Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { BaseMiddleware } from 'inversify-express-utils'
import { Logger } from 'winston'
import { StatisticsStoreInterface } from '@standardnotes/analytics'
import TYPES from '../Bootstrap/Types'
@injectable()
export class StatisticsMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
async handler(request: Request, _response: Response, next: NextFunction): Promise<void> {
try {
const snjsVersion = request.headers['x-snjs-version'] ?? 'unknown'
await this.statisticsStore.incrementSNJSVersionUsage(snjsVersion as string)
const applicationVersion = request.headers['x-application-version'] ?? 'unknown'
await this.statisticsStore.incrementApplicationVersionUsage(applicationVersion as string)
} catch (error) {
this.logger.error(`Could not store analytics data: ${(error as Error).message}`)
}
return next()
}
}
@@ -0,0 +1,120 @@
import { OfflineUserTokenData, CrossServiceTokenData } from '@standardnotes/auth'
import { NextFunction, Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { BaseMiddleware } from 'inversify-express-utils'
import { verify } from 'jsonwebtoken'
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios'
import { Logger } from 'winston'
import TYPES from '../Bootstrap/Types'
import { TokenAuthenticationMethod } from './TokenAuthenticationMethod'
@injectable()
export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.AUTH_JWT_SECRET) private jwtSecret: string,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
const subscriptionToken = request.query.subscription_token
const email = request.headers['x-offline-email']
if (!subscriptionToken) {
response.status(401).send({
error: {
tag: 'invalid-auth',
message: 'Invalid login credentials.',
},
})
return
}
response.locals.tokenAuthenticationMethod = email
? TokenAuthenticationMethod.OfflineSubscriptionToken
: TokenAuthenticationMethod.SubscriptionToken
try {
const url =
response.locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken
? `${this.authServerUrl}/offline/subscription-tokens/${subscriptionToken}/validate`
: `${this.authServerUrl}/subscription-tokens/${subscriptionToken}/validate`
const authResponse = await this.httpClient.request({
method: 'POST',
headers: {
Accept: 'application/json',
},
data: {
email,
},
validateStatus: (status: number) => {
return status >= 200 && status < 500
},
url,
})
if (authResponse.status > 200) {
response.setHeader('content-type', authResponse.headers['content-type'])
response.status(authResponse.status).send(authResponse.data)
return
}
if (response.locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken) {
this.handleOfflineAuthTokenValidationResponse(response, authResponse)
return next()
}
this.handleAuthTokenValidationResponse(response, authResponse)
return next()
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError
? JSON.stringify((error as AxiosError).response?.data)
: (error as Error).message
this.logger.error(
`Could not pass the request to ${this.authServerUrl}/subscription-tokens/${subscriptionToken}/validate on underlying service: ${errorMessage}`,
)
this.logger.debug('Response error: %O', (error as AxiosError).response ?? error)
if ((error as AxiosError).response?.headers['content-type']) {
response.setHeader('content-type', (error as AxiosError).response?.headers['content-type'] as string)
}
const errorCode = (error as AxiosError).isAxiosError ? +((error as AxiosError).code as string) : 500
response.status(errorCode).send(errorMessage)
return
}
}
private handleOfflineAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) {
response.locals.offlineAuthToken = authResponse.data.authToken
const decodedToken = <OfflineUserTokenData>(
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
)
response.locals.offlineUserEmail = decodedToken.userEmail
response.locals.offlineFeaturesToken = decodedToken.featuresToken
}
private handleAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) {
response.locals.authToken = authResponse.data.authToken
const decodedToken = <CrossServiceTokenData>(
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
)
response.locals.userUuid = decodedToken.user.uuid
response.locals.roles = decodedToken.roles
}
}
@@ -0,0 +1,4 @@
export enum TokenAuthenticationMethod {
OfflineSubscriptionToken = 'OfflineSubscriptionToken',
SubscriptionToken = 'SubscriptionToken',
}
@@ -0,0 +1,52 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1', TYPES.StatisticsMiddleware)
export class ActionsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/login')
async login(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/sign_in', request.body)
}
@httpGet('/login-params')
async loginParams(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/params', request.body)
}
@httpPost('/logout')
async logout(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/sign_out', request.body)
}
@httpGet('/auth/methods')
async methods(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/methods', request.body)
}
@httpGet('/failed-backups-emails/mute/:settingUuid')
async muteFailedBackupsEmails(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`internal/settings/email_backup/${request.params.settingUuid}/mute`,
request.body,
)
}
@httpGet('/sign-in-emails/mute/:settingUuid')
async muteSignInEmails(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`internal/settings/sign_in/${request.params.settingUuid}/mute`,
request.body,
)
}
}
@@ -0,0 +1,18 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/files', TYPES.StatisticsMiddleware)
export class FilesController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/valet-tokens', TYPES.AuthMiddleware)
async createToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'valet-tokens', request.body)
}
}
@@ -0,0 +1,17 @@
import { Request, Response } from 'express'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { inject } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1', TYPES.StatisticsMiddleware)
export class InvoicesController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/invoices/send-latest', TYPES.SubscriptionTokenAuthMiddleware)
async sendLatestInvoice(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-invoice', request.body)
}
}
@@ -0,0 +1,27 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/items', TYPES.StatisticsMiddleware, TYPES.AuthMiddleware)
export class ItemsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/')
async sync(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(request, response, 'items/sync', request.body)
}
@httpPost('/check-integrity')
async checkIntegrity(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(request, response, 'items/check-integrity', request.body)
}
@httpGet('/:uuid')
async getItem(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(request, response, `items/${request.params.uuid}`, request.body)
}
}
@@ -0,0 +1,33 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/offline', TYPES.StatisticsMiddleware)
export class OfflineController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/features')
async getOfflineFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'offline/features', request.body)
}
@httpPost('/subscription-tokens')
async createOfflineSubscriptionToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'offline/subscription-tokens', request.body)
}
@httpPost('/payments/stripe-setup-intent')
async createStripeSetupIntent(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
'api/pro_users/stripe-setup-intent/offline',
request.body,
)
}
}
@@ -0,0 +1,162 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { all, BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1', TYPES.StatisticsMiddleware)
export class PaymentsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/downloads')
async downloads(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/downloads', request.body)
}
@httpGet('/downloads/download-info')
async downloadInfo(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/downloads/download-info', request.body)
}
@httpGet('/downloads/platforms')
async platformDownloads(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/downloads/platforms', request.body)
}
@httpGet('/help/categories')
async categoriesHelp(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/help/categories', request.body)
}
@httpGet('/knowledge/categories')
async categoriesKnowledge(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/knowledge/categories', request.body)
}
@httpGet('/extensions')
async extensions(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/extensions', request.body)
}
@httpPost('/subscriptions/tiered', TYPES.SubscriptionTokenAuthMiddleware)
async createTieredSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/tiered', request.body)
}
@all('/subscriptions(/*)?')
async subscriptions(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, request.path.replace('v1', 'api'), request.body)
}
@httpGet('/reset/validate')
async validateReset(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/reset/validate', request.body)
}
@httpDelete('/reset')
async reset(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/reset', request.body)
}
@httpPost('/reset')
async resetRequest(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/reset', request.body)
}
@httpPost('/user-registration')
async userRegistration(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'admin/events/registration', request.body)
}
@httpPost('/admin/graphql')
async adminGraphql(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'admin/graphql', request.body)
}
@httpPost('/admin/auth/login')
async adminLogin(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'admin/auth/login', request.body)
}
@httpPost('/admin/auth/logout')
async adminLogout(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'admin/auth/logout', request.body)
}
@httpPost('/students')
async students(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/students', request.body)
}
@httpPost('/students/:token/approve')
async studentsApprove(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/students/${request.params.token}/approve`,
request.body,
)
}
@httpPost('/email_subscriptions/:token/less')
async subscriptionsLess(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/email_subscriptions/${request.params.token}/less`,
request.body,
)
}
@httpPost('/email_subscriptions/:token/more')
async subscriptionsMore(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/email_subscriptions/${request.params.token}/more`,
request.body,
)
}
@httpPost('/email_subscriptions/:token/mute/:campaignId')
async subscriptionsMute(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/email_subscriptions/${request.params.token}/mute/${request.params.campaignId}`,
request.body,
)
}
@httpPost('/email_subscriptions/:token/unsubscribe')
async subscriptionsUnsubscribe(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/email_subscriptions/${request.params.token}/unsubscribe`,
request.body,
)
}
@httpPost('/payments/stripe-setup-intent', TYPES.SubscriptionTokenAuthMiddleware)
async createStripeSetupIntent(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/stripe-setup-intent', request.body)
}
@httpGet('/pro_users/cp-prepayment-info', TYPES.SubscriptionTokenAuthMiddleware)
async coinpaymentsPrepaymentInfo(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/cp-prepayment-info', request.body)
}
@all('/pro_users(/*)?')
async proUsers(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, request.path.replace('v1', 'api'), request.body)
}
@all('/refunds')
async refunds(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/refunds', request.body)
}
}
@@ -0,0 +1,35 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/items/:item_id/revisions', TYPES.StatisticsMiddleware, TYPES.AuthMiddleware)
export class RevisionsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/')
async getRevisions(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(request, response, `items/${request.params.item_id}/revisions`)
}
@httpGet('/:id')
async getRevision(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
`items/${request.params.item_id}/revisions/${request.params.id}`,
)
}
@httpDelete('/:id')
async deleteRevision(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
`items/${request.params.item_id}/revisions/${request.params.id}`,
)
}
}
@@ -0,0 +1,34 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/sessions', TYPES.StatisticsMiddleware)
export class SessionsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
async getSessions(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'sessions')
}
@httpDelete('/:uuid', TYPES.AuthMiddleware)
async deleteSession(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'session', {
uuid: request.params.uuid,
})
}
@httpDelete('/', TYPES.AuthMiddleware)
async deleteSessions(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'session/all')
}
@httpPost('/refresh')
async refreshSession(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'session/refresh', request.body)
}
}
@@ -0,0 +1,42 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/subscription-invites', TYPES.StatisticsMiddleware)
export class SubscriptionInvitesController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/', TYPES.AuthMiddleware)
async inviteToSubscriptionSharing(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'subscription-invites', request.body)
}
@httpGet('/', TYPES.AuthMiddleware)
async listInvites(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'subscription-invites', request.body)
}
@httpDelete('/:inviteUuid', TYPES.AuthMiddleware)
async cancelSubscriptionSharing(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `subscription-invites/${request.params.inviteUuid}`)
}
@httpGet('/:inviteUuid/accept')
async acceptInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `subscription-invites/${request.params.inviteUuid}/accept`)
}
@httpGet('/:inviteUuid/decline')
async declineInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`subscription-invites/${request.params.inviteUuid}/decline`,
)
}
}
@@ -0,0 +1,18 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/subscription-tokens', TYPES.StatisticsMiddleware)
export class TokensController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/', TYPES.AuthMiddleware)
async createToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'subscription-tokens', request.body)
}
}
@@ -0,0 +1,150 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import {
all,
BaseHttpController,
controller,
httpDelete,
httpGet,
httpPatch,
httpPost,
httpPut,
results,
} from 'inversify-express-utils'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
import { TokenAuthenticationMethod } from '../TokenAuthenticationMethod'
@controller('/v1/users', TYPES.StatisticsMiddleware)
export class UsersController extends BaseHttpController {
constructor(
@inject(TYPES.HTTPService) private httpService: HttpServiceInterface,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
@httpPost('/claim-account')
async claimAccount(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/claim-account', request.body)
}
@httpPost('/send-activation-code', TYPES.SubscriptionTokenAuthMiddleware)
async sendActivationCode(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-activation-code', request.body)
}
@httpPatch('/:userId', TYPES.AuthMiddleware)
async updateUser(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userId}`, request.body)
}
@httpPut('/:userUuid/password', TYPES.AuthMiddleware)
async changePassword(request: Request, response: Response): Promise<void> {
this.logger.debug(
'[DEPRECATED] use endpoint /v1/users/:userUuid/attributes/credentials instead of /v1/users/:userUuid/password',
)
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/attributes/credentials`,
request.body,
)
}
@httpPut('/:userUuid/attributes/credentials', TYPES.AuthMiddleware)
async changeCredentials(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/attributes/credentials`,
request.body,
)
}
@httpGet('/:userId/params', TYPES.AuthMiddleware)
async getKeyParams(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/params')
}
@all('/:userId/mfa', TYPES.AuthMiddleware)
async blockMFA(): Promise<results.StatusCodeResult> {
return this.statusCode(401)
}
@httpPost('/:userUuid/integrations/listed', TYPES.AuthMiddleware)
async createListedAccount(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'listed', request.body)
}
@httpPost('/')
async register(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth', request.body)
}
@httpGet('/:userUuid/settings', TYPES.AuthMiddleware)
async listSettings(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/settings`)
}
@httpPut('/:userUuid/settings', TYPES.AuthMiddleware)
async putSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/settings`, request.body)
}
@httpGet('/:userUuid/settings/:settingName', TYPES.AuthMiddleware)
async getSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/settings/${request.params.settingName}`,
)
}
@httpDelete('/:userUuid/settings/:settingName', TYPES.AuthMiddleware)
async deleteSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/settings/${request.params.settingName}`,
request.body,
)
}
@httpGet('/:userUuid/subscription-settings/:subscriptionSettingName', TYPES.AuthMiddleware)
async getSubscriptionSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/subscription-settings/${request.params.subscriptionSettingName}`,
)
}
@httpGet('/:userUuid/features', TYPES.AuthMiddleware)
async getFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/features`)
}
@httpGet('/:userUuid/subscription', TYPES.AuthMiddleware)
async getSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/subscription`)
}
@httpGet('/subscription', TYPES.SubscriptionTokenAuthMiddleware)
async getSubscriptionBySubscriptionToken(request: Request, response: Response): Promise<void> {
if (response.locals.tokenAuthenticationMethod === TokenAuthenticationMethod.OfflineSubscriptionToken) {
await this.httpService.callAuthServer(request, response, 'offline/users/subscription')
return
}
await this.httpService.callAuthServer(request, response, `users/${response.locals.userUuid}/subscription`)
}
@httpDelete('/:userUuid', TYPES.AuthMiddleware)
async deleteUser(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/account', request.body)
}
}
@@ -0,0 +1,34 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/sockets')
export class WebSocketsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/', TYPES.AuthMiddleware)
async createWebSocketConnection(request: Request, response: Response): Promise<void> {
if (!request.headers.connectionid) {
response.status(400).send('Missing connection id in the request')
return
}
await this.httpService.callAuthServer(request, response, `sockets/${request.headers.connectionid}`, request.body)
}
@httpDelete('/')
async deleteWebSocketConnection(request: Request, response: Response): Promise<void> {
if (!request.headers.connectionid) {
response.status(400).send('Missing connection id in the request')
return
}
await this.httpService.callAuthServer(request, response, `sockets/${request.headers.connectionid}`, request.body)
}
}
@@ -0,0 +1,23 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v2', TYPES.StatisticsMiddleware)
export class ActionsControllerV2 extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/login')
async login(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/pkce_sign_in', request.body)
}
@httpPost('/login-params')
async loginParams(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/pkce_params', request.body)
}
}
@@ -0,0 +1,62 @@
import { Request, Response } from 'express'
import { BaseHttpController, controller, httpDelete, httpGet, httpPatch, httpPost } from 'inversify-express-utils'
import { inject } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v2', TYPES.StatisticsMiddleware)
export class PaymentsControllerV2 extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/subscriptions')
async getSubscriptionsWithFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/features', request.body)
}
@httpGet('/subscriptions/tailored', TYPES.SubscriptionTokenAuthMiddleware)
async getTailoredSubscriptionsWithFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/features', request.body)
}
@httpGet('/subscriptions/deltas', TYPES.SubscriptionTokenAuthMiddleware)
async getSubscriptionDeltasForChangingPlan(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/deltas', request.body)
}
@httpPost('/subscriptions/deltas/apply', TYPES.SubscriptionTokenAuthMiddleware)
async applySubscriptionDelta(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/deltas/apply', request.body)
}
@httpGet('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
async getSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/subscriptions/${request.params.subscriptionId}`,
request.body,
)
}
@httpDelete('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
async cancelSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/subscriptions/${request.params.subscriptionId}`,
request.body,
)
}
@httpPatch('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
async updateSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/subscriptions/${request.params.subscriptionId}`,
request.body,
)
}
}
@@ -0,0 +1,46 @@
import { inject, injectable } from 'inversify'
import * as IORedis from 'ioredis'
import TYPES from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../../Service/Cache/CrossServiceTokenCacheInterface'
@injectable()
export class RedisCrossServiceTokenCache implements CrossServiceTokenCacheInterface {
private readonly PREFIX = 'cst'
private readonly USER_CST_PREFIX = 'user-cst'
constructor(@inject(TYPES.Redis) private redisClient: IORedis.Redis) {}
async set(dto: {
authorizationHeaderValue: string
encodedCrossServiceToken: string
expiresAtInSeconds: number
userUuid: string
}): Promise<void> {
const pipeline = this.redisClient.pipeline()
pipeline.sadd(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.authorizationHeaderValue)
pipeline.expireat(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.expiresAtInSeconds)
pipeline.set(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.encodedCrossServiceToken)
pipeline.expireat(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.expiresAtInSeconds)
await pipeline.exec()
}
async get(authorizationHeaderValue: string): Promise<string | null> {
return this.redisClient.get(`${this.PREFIX}:${authorizationHeaderValue}`)
}
async invalidate(userUuid: string): Promise<void> {
const userAuthorizationHeaderValues = await this.redisClient.smembers(`${this.USER_CST_PREFIX}:${userUuid}`)
const pipeline = this.redisClient.pipeline()
for (const authorizationHeaderValue of userAuthorizationHeaderValues) {
pipeline.del(`${this.PREFIX}:${authorizationHeaderValue}`)
}
pipeline.del(`${this.USER_CST_PREFIX}:${userUuid}`)
await pipeline.exec()
}
}
@@ -0,0 +1,10 @@
export interface CrossServiceTokenCacheInterface {
set(dto: {
authorizationHeaderValue: string
encodedCrossServiceToken: string
expiresAtInSeconds: number
userUuid: string
}): Promise<void>
get(authorizationHeaderValue: string): Promise<string | null>
invalidate(userUuid: string): Promise<void>
}
@@ -0,0 +1,231 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { AxiosInstance, AxiosError, AxiosResponse, Method } from 'axios'
import { Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { HttpServiceInterface } from './HttpServiceInterface'
@injectable()
export class HttpService implements HttpServiceInterface {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.SYNCING_SERVER_JS_URL) private syncingServerJsUrl: string,
@inject(TYPES.PAYMENTS_SERVER_URL) private paymentsServerUrl: string,
@inject(TYPES.FILES_SERVER_URL) private filesServerUrl: string,
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async callSyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload)
}
async callLegacySyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServerWithLegacyFormat(this.syncingServerJsUrl, request, response, endpoint, payload)
}
async callAuthServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
}
async callPaymentsServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.paymentsServerUrl) {
this.logger.debug('Payments Server URL not defined. Skipped request to Payments API.')
return
}
await this.callServerWithLegacyFormat(this.paymentsServerUrl, request, response, endpoint, payload)
}
async callAuthServerWithLegacyFormat(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServerWithLegacyFormat(this.authServerUrl, request, response, endpoint, payload)
}
private async getServerResponse(
serverUrl: string,
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<AxiosResponse | undefined> {
try {
const headers: Record<string, string> = {}
for (const headerName of Object.keys(request.headers)) {
headers[headerName] = request.headers[headerName] as string
}
delete headers.host
delete headers['content-length']
if (response.locals.authToken) {
headers['X-Auth-Token'] = response.locals.authToken
}
if (response.locals.offlineAuthToken) {
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken
}
this.logger.debug(`Calling [${request.method}] ${serverUrl}/${endpoint},
headers: ${JSON.stringify(headers)},
query: ${JSON.stringify(request.query)},
payload: ${JSON.stringify(payload)}`)
const serviceResponse = await this.httpClient.request({
method: request.method as Method,
headers,
url: `${serverUrl}/${endpoint}`,
data: this.getRequestData(payload),
maxContentLength: Infinity,
maxBodyLength: Infinity,
params: request.query,
timeout: this.httpCallTimeout,
validateStatus: (status: number) => {
return status >= 200 && status < 500
},
})
if (serviceResponse.headers['x-invalidate-cache']) {
const userUuid = serviceResponse.headers['x-invalidate-cache']
await this.crossServiceTokenCache.invalidate(userUuid)
}
return serviceResponse
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError
? JSON.stringify((error as AxiosError).response?.data)
: (error as Error).message
this.logger.error(`Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${errorMessage}`)
this.logger.debug('Response error: %O', (error as AxiosError).response ?? error)
if ((error as AxiosError).response?.headers['content-type']) {
response.setHeader('content-type', (error as AxiosError).response?.headers['content-type'] as string)
}
const errorCode = (error as AxiosError).isAxiosError ? +((error as AxiosError).code as string) : 500
response.status(errorCode).send(errorMessage)
}
return
}
private async callServer(
serverUrl: string,
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
this.logger.debug(`Response from underlying server: ${JSON.stringify(serviceResponse?.data)},
headers: ${JSON.stringify(serviceResponse?.headers)}`)
if (!serviceResponse) {
return
}
this.applyResponseHeaders(serviceResponse, response)
response.status(serviceResponse.status).send({
meta: {
auth: {
userUuid: response.locals.userUuid,
roles: response.locals.roles,
},
server: {
filesServerUrl: this.filesServerUrl,
},
},
data: serviceResponse.data,
})
}
private async callServerWithLegacyFormat(
serverUrl: string,
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
if (!serviceResponse) {
return
}
this.applyResponseHeaders(serviceResponse, response)
if (serviceResponse.request._redirectable._redirectCount > 0) {
response.status(302).redirect(serviceResponse.request.res.responseUrl)
} else {
response.status(serviceResponse.status).send(serviceResponse.data)
}
}
private getRequestData(
payload: Record<string, unknown> | string | undefined,
): Record<string, unknown> | string | undefined {
if (
payload === '' ||
payload === null ||
payload === undefined ||
(typeof payload === 'object' && Object.keys(payload).length === 0)
) {
return undefined
}
return payload
}
private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void {
const returnedHeadersFromUnderlyingService = [
'access-control-allow-methods',
'access-control-allow-origin',
'access-control-expose-headers',
'authorization',
'content-type',
'x-ssjs-version',
'x-auth-version',
]
returnedHeadersFromUnderlyingService.map((headerName) => {
const headerValue = serviceResponse.headers[headerName]
if (headerValue) {
response.setHeader(headerName, headerValue)
}
})
}
}
@@ -0,0 +1,34 @@
import { Request, Response } from 'express'
export interface HttpServiceInterface {
callAuthServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callAuthServerWithLegacyFormat(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callSyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callLegacySyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callPaymentsServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
}
View File
+12
View File
@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist",
},
"include": [
"src/**/*",
"bin/**/*",
],
"references": []
}
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
set -e
host="$1"
shift
port="$1"
shift
cmd="$@"
while ! nc -vz $host $port; do
>&2 echo "$host:$port is unavailable yet - waiting for it to start"
sleep 10
done
>&2 echo "$host:$port is up - executing command"
exec $cmd
+48
View File
@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.1](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0...@standardnotes/auth-server@1.1.1) (2022-06-23)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.1.0](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.8...@standardnotes/auth-server@1.1.0) (2022-06-23)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.1.0-alpha.8](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.7...@standardnotes/auth-server@1.1.0-alpha.8) (2022-06-23)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.1.0-alpha.7](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.6...@standardnotes/auth-server@1.1.0-alpha.7) (2022-06-23)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.1.0-alpha.6](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.5...@standardnotes/auth-server@1.1.0-alpha.6) (2022-06-23)
### Bug Fixes
* add missing curl to docker image for healthcheck purposes ([7efb48d](https://github.com/standardnotes/auth/commit/7efb48dd2a6066c29601d34bfcbfe6231f644c50))
# [1.1.0-alpha.5](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.4...@standardnotes/auth-server@1.1.0-alpha.5) (2022-06-22)
### Bug Fixes
* make DISABLE_USER_REGISTRATION env var optional ([3110c20](https://github.com/standardnotes/auth/commit/3110c20596b52da8a43551432cfef94e68046385))
# [1.1.0-alpha.4](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.3...@standardnotes/auth-server@1.1.0-alpha.4) (2022-06-22)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.1.0-alpha.3](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.2...@standardnotes/auth-server@1.1.0-alpha.3) (2022-06-22)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.1.0-alpha.2](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.1...@standardnotes/auth-server@1.1.0-alpha.2) (2022-06-22)
### Features
* add syncing-server package ([6cc4ef9](https://github.com/standardnotes/auth/commit/6cc4ef90db9e79d53f9646311d234432ef4cf1e3))
# [1.1.0-alpha.1](https://github.com/standardnotes/auth/compare/@standardnotes/auth-server@1.1.0-alpha.0...@standardnotes/auth-server@1.1.0-alpha.1) (2022-06-22)
### Bug Fixes
* docker entrypoint for auth ([f3c6fff](https://github.com/standardnotes/auth/commit/f3c6fff20f98a9b23b7221e59e6853255f54a369))
# 1.1.0-alpha.0 (2022-06-22)
### Features
+1 -1
View File
@@ -1,7 +1,7 @@
FROM node:16.15.1-alpine AS builder
# Install dependencies for building native libraries
RUN apk add --update git openssh-client python3 alpine-sdk
RUN apk add --update git openssh-client curl python3 alpine-sdk
WORKDIR /workspace
+8 -8
View File
@@ -6,42 +6,42 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-local' )
echo "Starting Web..."
yarn start:local
yarn workspace @standardnotes/auth-server start:local
;;
'start-web' )
echo "Starting Web..."
yarn start
yarn workspace @standardnotes/auth-server start
;;
'start-worker' )
echo "Starting Worker..."
yarn worker
yarn workspace @standardnotes/auth-server worker
;;
'email-daily-backup' )
echo "Starting Email Daily Backup..."
yarn daily-backup:email
yarn workspace @standardnotes/auth-server daily-backup:email
;;
'email-weekly-backup' )
echo "Starting Email Weekly Backup..."
yarn weekly-backup:email
yarn workspace @standardnotes/auth-server weekly-backup:email
;;
'dropbox-daily-backup' )
echo "Starting Dropbox Daily Backup..."
yarn daily-backup:dropbox
yarn workspace @standardnotes/auth-server daily-backup:dropbox
;;
'google-drive-daily-backup' )
echo "Starting Google Drive Daily Backup..."
yarn daily-backup:google_drive
yarn workspace @standardnotes/auth-server daily-backup:google_drive
;;
'one-drive-daily-backup' )
echo "Starting One Drive Daily Backup..."
yarn daily-backup:one_drive
yarn workspace @standardnotes/auth-server daily-backup:one_drive
;;
* )
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.1.0-alpha.0",
"version": "1.1.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
+3 -1
View File
@@ -357,7 +357,9 @@ export class ContainerConfigLoader {
container.bind(TYPES.PSEUDO_KEY_PARAMS_KEY).toConstantValue(env.get('PSEUDO_KEY_PARAMS_KEY'))
container.bind(TYPES.EPHEMERAL_SESSION_AGE).toConstantValue(env.get('EPHEMERAL_SESSION_AGE'))
container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL'))
container.bind(TYPES.DISABLE_USER_REGISTRATION).toConstantValue(env.get('DISABLE_USER_REGISTRATION') === 'true')
container
.bind(TYPES.DISABLE_USER_REGISTRATION)
.toConstantValue(env.get('DISABLE_USER_REGISTRATION', true) === 'true')
container.bind(TYPES.ANALYTICS_ENABLED).toConstantValue(env.get('ANALYTICS_ENABLED', true) === 'true')
container.bind(TYPES.SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN', true))
container.bind(TYPES.SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
+1 -1
View File
@@ -58,7 +58,7 @@ export const AppDataSource = new DataSource({
SubscriptionSetting,
AnalyticsEntity,
],
migrations: [env.get('DB_MIGRATIONS_PATH')],
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
})

Some files were not shown because too many files have changed in this diff Show More