mirror of
https://github.com/standardnotes/server
synced 2026-01-18 08:04:28 -05:00
Compare commits
41 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d54b812881 | ||
|
|
28dc5ba2a4 | ||
|
|
979a320ca6 | ||
|
|
c46186b237 | ||
|
|
27cf093f85 | ||
|
|
ec0fb7e0b9 | ||
|
|
90029456fe | ||
|
|
b167b00075 | ||
|
|
b13fab76f3 | ||
|
|
782a9d310d | ||
|
|
537b1f2a29 | ||
|
|
2fad6b62cb | ||
|
|
bf173b4ede | ||
|
|
c52f038c76 | ||
|
|
b12ba98a5c | ||
|
|
dbccdf342b | ||
|
|
49b6d029c4 | ||
|
|
d6469954ce | ||
|
|
5f40550ad4 | ||
|
|
79ccbdf100 | ||
|
|
1983cfcab2 | ||
|
|
753f86707f | ||
|
|
16d0ed505b | ||
|
|
9de09c55f8 | ||
|
|
c3d7a33aa2 | ||
|
|
a9cc00a478 | ||
|
|
ec035ba648 | ||
|
|
5446f3cae4 | ||
|
|
6a550092c2 | ||
|
|
1b691f6bcd | ||
|
|
98f45cc4c2 | ||
|
|
edc4a20859 | ||
|
|
74e1380df8 | ||
|
|
dfa5187ff7 | ||
|
|
c99c4425cd | ||
|
|
2d8919a079 | ||
|
|
f638287213 | ||
|
|
991d885b63 | ||
|
|
bb17efa817 | ||
|
|
deec29c1b4 | ||
|
|
9d872008a7 |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -95,11 +95,6 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/workspace"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
||||
78
.github/workflows/pr.yml
vendored
78
.github/workflows/pr.yml
vendored
@@ -94,81 +94,3 @@ jobs:
|
||||
name: E2E
|
||||
uses: standardnotes/server/.github/workflows/common-e2e.yml@main
|
||||
secrets: inherit
|
||||
|
||||
legacy_e2e:
|
||||
needs: build
|
||||
name: Legacy E2E
|
||||
strategy:
|
||||
matrix:
|
||||
application:
|
||||
- { "service_name": "api-gateway", "workspace_name": "@standardnotes/api-gateway", "e2e_tag_parameter_name": "api_gateway_image_tag", "package_path": "packages/api-gateway" }
|
||||
- { "service_name": "auth", "workspace_name": "@standardnotes/auth-server", "e2e_tag_parameter_name": "auth_image_tag", "package_path": "packages/auth" }
|
||||
- { "service_name": "files", "workspace_name": "@standardnotes/files-server", "e2e_tag_parameter_name": "files_image_tag", "package_path": "packages/files" }
|
||||
- { "service_name": "revisions", "workspace_name": "@standardnotes/revisions-server", "e2e_tag_parameter_name": "revisions_image_tag", "package_path": "packages/revisions"}
|
||||
- { "service_name": "syncing-server-js", "workspace_name": "@standardnotes/syncing-server", "e2e_tag_parameter_name": "syncing_server_js_image_tag", "package_path": "packages/syncing-server" }
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Create Bundle Dir
|
||||
id: bundle-dir
|
||||
run: echo "temp_dir=$(mktemp -d -t ${{ matrix.application.service_name }}-${{ github.sha }}-XXXXXXX)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache build
|
||||
id: cache-build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
packages/**/dist
|
||||
${{ needs.legacy_e2e.outputs.temp_dir }}
|
||||
key: ${{ runner.os }}-build-${{ github.sha }}
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Build
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
run: yarn build
|
||||
|
||||
- name: Bundle
|
||||
run: yarn workspace ${{ matrix.application.workspace_name }} bundle --no-compress --output-directory ${{ steps.bundle-dir.outputs.temp_dir }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
with:
|
||||
platforms: all
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
|
||||
- name: Publish Docker image for E2E testing
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: ${{ steps.bundle-dir.outputs.temp_dir }}
|
||||
file: ${{ steps.bundle-dir.outputs.temp_dir }}/${{ matrix.application.package_path }}/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: standardnotes/${{ matrix.application.service_name }}:${{ github.sha }}
|
||||
|
||||
- name: Run E2E test suite
|
||||
uses: convictional/trigger-workflow-and-wait@master
|
||||
with:
|
||||
owner: standardnotes
|
||||
repo: self-hosted
|
||||
github_token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
workflow_file_name: testing-with-updating-client-and-server.yml
|
||||
wait_interval: 30
|
||||
client_payload: '{"${{ matrix.application.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
|
||||
propagate_failure: true
|
||||
trigger_workflow: true
|
||||
wait_workflow: true
|
||||
|
||||
22
.github/workflows/proxy.yml
vendored
22
.github/workflows/proxy.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Proxy Server
|
||||
|
||||
concurrency:
|
||||
group: proxy_server
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/proxy-server*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call_server_application_workflow:
|
||||
name: Server Application
|
||||
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
|
||||
with:
|
||||
service_name: proxy
|
||||
workspace_name: "@standardnotes/proxy-server"
|
||||
deploy_worker: false
|
||||
package_path: packages/proxy
|
||||
secrets: inherit
|
||||
47
.github/workflows/workspace.yml
vendored
47
.github/workflows/workspace.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: Workspace Server
|
||||
|
||||
concurrency:
|
||||
group: workspace
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*standardnotes/workspace-server*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call_server_application_workflow:
|
||||
name: Server Application
|
||||
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
|
||||
with:
|
||||
service_name: workspace
|
||||
workspace_name: "@standardnotes/workspace-server"
|
||||
package_path: packages/workspace
|
||||
secrets: inherit
|
||||
|
||||
newrelic:
|
||||
needs: call_server_application_workflow
|
||||
|
||||
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_WORKSPACE_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_WORKSPACE_WORKER_PROD }}
|
||||
revision: "${{ github.sha }}"
|
||||
description: "Automated Deployment via Github Actions"
|
||||
user: "${{ github.actor }}"
|
||||
186
.pnp.cjs
generated
186
.pnp.cjs
generated
@@ -53,10 +53,6 @@ const RAW_RUNTIME_STATE =
|
||||
"name": "@standardnotes/predicates",\
|
||||
"reference": "workspace:packages/predicates"\
|
||||
},\
|
||||
{\
|
||||
"name": "@standardnotes/proxy-server",\
|
||||
"reference": "workspace:packages/proxy"\
|
||||
},\
|
||||
{\
|
||||
"name": "@standardnotes/revisions-server",\
|
||||
"reference": "workspace:packages/revisions"\
|
||||
@@ -88,10 +84,6 @@ const RAW_RUNTIME_STATE =
|
||||
{\
|
||||
"name": "@standardnotes/websockets-server",\
|
||||
"reference": "workspace:packages/websockets"\
|
||||
},\
|
||||
{\
|
||||
"name": "@standardnotes/workspace-server",\
|
||||
"reference": "workspace:packages/workspace"\
|
||||
}\
|
||||
],\
|
||||
"enableTopLevelFallback": true,\
|
||||
@@ -107,7 +99,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/event-store", ["workspace:packages/event-store"]],\
|
||||
["@standardnotes/files-server", ["workspace:packages/files"]],\
|
||||
["@standardnotes/predicates", ["workspace:packages/predicates"]],\
|
||||
["@standardnotes/proxy-server", ["workspace:packages/proxy"]],\
|
||||
["@standardnotes/revisions-server", ["workspace:packages/revisions"]],\
|
||||
["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\
|
||||
["@standardnotes/security", ["workspace:packages/security"]],\
|
||||
@@ -116,8 +107,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/sncrypto-node", ["workspace:packages/sncrypto-node"]],\
|
||||
["@standardnotes/syncing-server", ["workspace:packages/syncing-server"]],\
|
||||
["@standardnotes/time", ["workspace:packages/time"]],\
|
||||
["@standardnotes/websockets-server", ["workspace:packages/websockets"]],\
|
||||
["@standardnotes/workspace-server", ["workspace:packages/workspace"]]\
|
||||
["@standardnotes/websockets-server", ["workspace:packages/websockets"]]\
|
||||
],\
|
||||
"fallbackPool": [\
|
||||
],\
|
||||
@@ -3970,26 +3960,26 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["@simplewebauthn/iso-webcrypto", [\
|
||||
["npm:7.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-iso-webcrypto-npm-7.0.0-352babf4a0-c1644f9b68.zip/node_modules/@simplewebauthn/iso-webcrypto/",\
|
||||
["npm:7.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-iso-webcrypto-npm-7.0.1-bae5f6738c-ed506490e0.zip/node_modules/@simplewebauthn/iso-webcrypto/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/iso-webcrypto", "npm:7.0.0"]\
|
||||
["@simplewebauthn/iso-webcrypto", "npm:7.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@simplewebauthn/server", [\
|
||||
["npm:7.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-7.0.0-e34589f137-836eb9fb97.zip/node_modules/@simplewebauthn/server/",\
|
||||
["npm:7.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-7.0.1-ac81233d49-d11c708008.zip/node_modules/@simplewebauthn/server/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/server", "npm:7.0.0"],\
|
||||
["@simplewebauthn/server", "npm:7.0.1"],\
|
||||
["@hexagon/base64", "npm:1.1.25"],\
|
||||
["@peculiar/asn1-android", "npm:2.3.3"],\
|
||||
["@peculiar/asn1-ecc", "npm:2.3.4"],\
|
||||
["@peculiar/asn1-rsa", "npm:2.3.4"],\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["@peculiar/asn1-x509", "npm:2.3.4"],\
|
||||
["@simplewebauthn/iso-webcrypto", "npm:7.0.0"],\
|
||||
["@simplewebauthn/iso-webcrypto", "npm:7.0.1"],\
|
||||
["cbor-x", "npm:1.5.0"],\
|
||||
["cross-fetch", "npm:3.1.5"],\
|
||||
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"]\
|
||||
@@ -4093,17 +4083,17 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/api", [\
|
||||
["npm:1.24.10", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.24.10-63391538ba-1b9a97fdd8.zip/node_modules/@standardnotes/api/",\
|
||||
["npm:1.25.3", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.25.3-29ba336725-bc7953c440.zip/node_modules/@standardnotes/api/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/api", "npm:1.24.10"],\
|
||||
["@standardnotes/api", "npm:1.25.3"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/encryption", "npm:1.21.9"],\
|
||||
["@standardnotes/models", "npm:1.42.11"],\
|
||||
["@standardnotes/responses", "npm:1.13.6"],\
|
||||
["@standardnotes/encryption", "npm:1.21.17"],\
|
||||
["@standardnotes/models", "npm:1.43.5"],\
|
||||
["@standardnotes/responses", "npm:1.13.9"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/utils", "npm:1.16.3"],\
|
||||
["@standardnotes/utils", "npm:1.16.4"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
@@ -4166,16 +4156,16 @@ const RAW_RUNTIME_STATE =
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/tracing", "npm:7.28.1"],\
|
||||
["@simplewebauthn/server", "npm:7.0.0"],\
|
||||
["@simplewebauthn/server", "npm:7.0.1"],\
|
||||
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
|
||||
["@standardnotes/api", "npm:1.24.10"],\
|
||||
["@standardnotes/api", "npm:1.25.3"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/features", "npm:1.58.4"],\
|
||||
["@standardnotes/features", "npm:1.58.9"],\
|
||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||
["@standardnotes/responses", "npm:1.13.4"],\
|
||||
["@standardnotes/responses", "npm:1.13.9"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
||||
@@ -4312,15 +4302,15 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/encryption", [\
|
||||
["npm:1.21.9", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.21.9-092bc2cb51-dc1336cc05.zip/node_modules/@standardnotes/encryption/",\
|
||||
["npm:1.21.17", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.21.17-0801937c9c-ece7ac644e.zip/node_modules/@standardnotes/encryption/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/encryption", "npm:1.21.9"],\
|
||||
["@standardnotes/encryption", "npm:1.21.17"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/models", "npm:1.42.11"],\
|
||||
["@standardnotes/responses", "npm:1.13.6"],\
|
||||
["@standardnotes/models", "npm:1.43.5"],\
|
||||
["@standardnotes/responses", "npm:1.13.9"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
|
||||
["@standardnotes/utils", "npm:1.16.3"],\
|
||||
["@standardnotes/utils", "npm:1.16.4"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
@@ -4358,10 +4348,10 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/features", [\
|
||||
["npm:1.58.4", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.58.4-a84962d125-a39afc145a.zip/node_modules/@standardnotes/features/",\
|
||||
["npm:1.58.8", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.58.8-d97ff2aae1-77bac7d0a0.zip/node_modules/@standardnotes/features/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/features", "npm:1.58.4"],\
|
||||
["@standardnotes/features", "npm:1.58.8"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
@@ -4369,10 +4359,10 @@ const RAW_RUNTIME_STATE =
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.58.6", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.58.6-7b1e198c39-98550416f1.zip/node_modules/@standardnotes/features/",\
|
||||
["npm:1.58.9", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.58.9-c278f712cd-218350ee55.zip/node_modules/@standardnotes/features/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/features", "npm:1.58.6"],\
|
||||
["@standardnotes/features", "npm:1.58.9"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
@@ -4438,14 +4428,14 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/models", [\
|
||||
["npm:1.42.11", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.42.11-7db16001ef-6ff3409f70.zip/node_modules/@standardnotes/models/",\
|
||||
["npm:1.43.5", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.43.5-5180388ed4-fd8e3b60bd.zip/node_modules/@standardnotes/models/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/models", "npm:1.42.11"],\
|
||||
["@standardnotes/models", "npm:1.43.5"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/features", "npm:1.58.6"],\
|
||||
["@standardnotes/responses", "npm:1.13.6"],\
|
||||
["@standardnotes/utils", "npm:1.16.3"],\
|
||||
["@standardnotes/features", "npm:1.58.8"],\
|
||||
["@standardnotes/responses", "npm:1.13.9"],\
|
||||
["@standardnotes/utils", "npm:1.16.4"],\
|
||||
["lodash", "npm:4.17.21"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
@@ -4466,40 +4456,13 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/proxy-server", [\
|
||||
["workspace:packages/proxy", {\
|
||||
"packageLocation": "./packages/proxy/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/proxy-server", "workspace:packages/proxy"],\
|
||||
["@types/newrelic", "npm:9.4.0"],\
|
||||
["@types/node", "npm:18.14.0"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:5.48.2"],\
|
||||
["eslint", "npm:8.32.0"],\
|
||||
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
||||
["newrelic", "npm:9.8.0"],\
|
||||
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/responses", [\
|
||||
["npm:1.13.4", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.13.4-70cbd72561-4803ee14bd.zip/node_modules/@standardnotes/responses/",\
|
||||
["npm:1.13.9", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.13.9-5b1858da5d-5cb5daf9f3.zip/node_modules/@standardnotes/responses/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/responses", "npm:1.13.4"],\
|
||||
["@standardnotes/responses", "npm:1.13.9"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/features", "npm:1.58.4"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.13.6", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.13.6-5df25fe3dd-c57e3e1fa1.zip/node_modules/@standardnotes/responses/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/responses", "npm:1.13.6"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/features", "npm:1.58.6"],\
|
||||
["@standardnotes/features", "npm:1.58.8"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
@@ -4515,11 +4478,12 @@ const RAW_RUNTIME_STATE =
|
||||
["@aws-sdk/client-sqs", "npm:3.259.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/api", "npm:1.24.10"],\
|
||||
["@standardnotes/api", "npm:1.25.3"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/responses", "npm:1.13.9"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/cors", "npm:2.8.12"],\
|
||||
@@ -4637,6 +4601,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/settings/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.48.2"],\
|
||||
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:4.2.1"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
@@ -4694,12 +4659,12 @@ const RAW_RUNTIME_STATE =
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/tracing", "npm:7.28.1"],\
|
||||
["@standardnotes/api", "npm:1.24.10"],\
|
||||
["@standardnotes/api", "npm:1.25.3"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/responses", "npm:1.13.4"],\
|
||||
["@standardnotes/responses", "npm:1.13.9"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
@@ -4772,10 +4737,10 @@ const RAW_RUNTIME_STATE =
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.16.3", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.16.3-87b47ad954-5c34beaafb.zip/node_modules/@standardnotes/utils/",\
|
||||
["npm:1.16.4", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.16.4-d7c627b154-ed29da54cb.zip/node_modules/@standardnotes/utils/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/utils", "npm:1.16.3"],\
|
||||
["@standardnotes/utils", "npm:1.16.4"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["dompurify", "npm:2.4.3"],\
|
||||
["lodash", "npm:4.17.21"],\
|
||||
@@ -4792,11 +4757,12 @@ const RAW_RUNTIME_STATE =
|
||||
["@aws-sdk/client-sqs", "npm:3.259.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/api", "npm:1.24.10"],\
|
||||
["@standardnotes/api", "npm:1.25.3"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/responses", "npm:1.13.9"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/utils", "npm:1.16.2"],\
|
||||
["@types/cors", "npm:2.8.12"],\
|
||||
@@ -4826,49 +4792,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/workspace-server", [\
|
||||
["workspace:packages/workspace", {\
|
||||
"packageLocation": "./packages/workspace/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
|
||||
["@aws-sdk/client-sns", "npm:3.259.0"],\
|
||||
["@aws-sdk/client-sqs", "npm:3.259.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/api", "npm:1.24.10"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/models", "npm:1.42.11"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/cors", "npm:2.8.12"],\
|
||||
["@types/express", "npm:4.17.14"],\
|
||||
["@types/ioredis", "npm:5.0.0"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:9.4.0"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:5.48.2"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
["eslint", "npm:8.32.0"],\
|
||||
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
||||
["express", "npm:4.18.2"],\
|
||||
["inversify", "npm:6.0.1"],\
|
||||
["inversify-express-utils", "npm:6.4.3"],\
|
||||
["ioredis", "npm:5.2.4"],\
|
||||
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
|
||||
["mysql2", "npm:3.0.1"],\
|
||||
["newrelic", "npm:9.8.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.0.3"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.10"],\
|
||||
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"],\
|
||||
["winston", "npm:3.8.2"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
]],\
|
||||
["@szmarczak/http-timer", [\
|
||||
["npm:5.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/@szmarczak-http-timer-npm-5.0.1-52261e5986-67236cba79.zip/node_modules/@szmarczak/http-timer/",\
|
||||
@@ -5272,13 +5195,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/node", "npm:18.11.9"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:18.14.0", {\
|
||||
"packageLocation": "./.yarn/cache/@types-node-npm-18.14.0-ddc1a221d2-d17dff07c7.zip/node_modules/@types/node/",\
|
||||
"packageDependencies": [\
|
||||
["@types/node", "npm:18.14.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/nodemailer", [\
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@standardnotes-api-npm-1.25.3-29ba336725-bc7953c440.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-api-npm-1.25.3-29ba336725-bc7953c440.zip
vendored
Normal file
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.
BIN
.yarn/cache/@standardnotes-responses-npm-1.13.9-5b1858da5d-5cb5daf9f3.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-responses-npm-1.13.9-5b1858da5d-5cb5daf9f3.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -327,7 +327,7 @@ endif
|
||||
|
||||
quiet_cmd_regen_makefile = ACTION Regenerating $@
|
||||
cmd_regen_makefile = cd $(srcdir); /Users/mo/Desktop/sn/dev/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/mo/Library/Caches/node-gyp/18.13.0" "-Dnode_gyp_dir=/Users/mo/Desktop/sn/dev/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp" "-Dnode_lib_file=/Users/mo/Library/Caches/node-gyp/18.13.0/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/mo/Desktop/sn/dev/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/mo/Desktop/sn/dev/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics/build/config.gypi -I/Users/mo/Desktop/sn/dev/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi -I/Users/mo/Library/Caches/node-gyp/18.13.0/include/node/common.gypi "--toplevel-dir=." binding.gyp
|
||||
Makefile: $(srcdir)/binding.gyp $(srcdir)/build/config.gypi $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../../../../../Library/Caches/node-gyp/18.13.0/include/node/common.gypi
|
||||
Makefile: $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi $(srcdir)/binding.gyp $(srcdir)/../../../../../../../../../../Library/Caches/node-gyp/18.13.0/include/node/common.gypi $(srcdir)/build/config.gypi
|
||||
$(call do_cmd,regen_makefile)
|
||||
|
||||
# "all" is a concatenation of the "all" targets from all the included
|
||||
|
||||
Binary file not shown.
@@ -58,7 +58,9 @@ if [ -z "$REDIS_HOST" ]; then
|
||||
export REDIS_HOST="cache"
|
||||
fi
|
||||
|
||||
export REDIS_URL="redis://$REDIS_HOST"
|
||||
if [ -z "$REDIS_URL" ]; then
|
||||
export REDIS_URL="redis://$REDIS_HOST"
|
||||
fi
|
||||
|
||||
##########
|
||||
# SHARED #
|
||||
@@ -349,8 +351,7 @@ export API_GATEWAY_NEW_RELIC_NO_CONFIG_FILE=true
|
||||
|
||||
export API_GATEWAY_SYNCING_SERVER_JS_URL=http://localhost:$SYNCING_SERVER_PORT
|
||||
export API_GATEWAY_AUTH_SERVER_URL=http://localhost:$AUTH_SERVER_PORT
|
||||
export API_GATEWAY_WORKSPACE_SERVER_URL=http://localhost:3004
|
||||
export API_GATEWAY_REVISIONS_SERVER_URL=http://localhost:3005
|
||||
export API_GATEWAY_REVISIONS_SERVER_URL=http://localhost:$REVISIONS_SERVER_PORT
|
||||
if [ -z "$PUBLIC_FILES_SERVER_URL" ]; then
|
||||
export PUBLIC_FILES_SERVER_URL=http://localhost:3125
|
||||
fi
|
||||
|
||||
@@ -91,13 +91,6 @@ TOPIC_CREATED_RESULT=$(create_topic ${SCHEDULER_TOPIC_NAME})
|
||||
echo "created topic: $TOPIC_CREATED_RESULT"
|
||||
SCHEDULER_TOPIC_ARN=$(get_topic_arn_from_name $SCHEDULER_TOPIC_NAME)
|
||||
|
||||
WORKSPACE_TOPIC_NAME="workspace-local-topic"
|
||||
|
||||
echo "creating topic $WORKSPACE_TOPIC_NAME"
|
||||
TOPIC_CREATED_RESULT=$(create_topic ${WORKSPACE_TOPIC_NAME})
|
||||
echo "created topic: $TOPIC_CREATED_RESULT"
|
||||
WORKSPACE_TOPIC_ARN=$(get_topic_arn_from_name $WORKSPACE_TOPIC_NAME)
|
||||
|
||||
QUEUE_NAME="analytics-local-queue"
|
||||
|
||||
echo "creating queue $QUEUE_NAME"
|
||||
@@ -182,13 +175,6 @@ QUEUE_URL=$(create_queue ${QUEUE_NAME})
|
||||
echo "created queue: $QUEUE_URL"
|
||||
SCHEDULER_QUEUE_ARN=$(get_queue_arn_from_name $QUEUE_NAME)
|
||||
|
||||
QUEUE_NAME="workspace-local-queue"
|
||||
|
||||
echo "creating queue $QUEUE_NAME"
|
||||
QUEUE_URL=$(create_queue ${QUEUE_NAME})
|
||||
echo "created queue: $QUEUE_URL"
|
||||
WORKSPACE_QUEUE_ARN=$(get_queue_arn_from_name $QUEUE_NAME)
|
||||
|
||||
echo "all topics are:"
|
||||
echo "$(get_all_topics)"
|
||||
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.21.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.21.1...@standardnotes/analytics@2.21.2) (2023-03-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.21.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.21.0...@standardnotes/analytics@2.21.1) (2023-02-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** add general activity metric to mixpanel ([9d87200](https://github.com/standardnotes/server/commit/9d872008a7df7ccdd9afe7e7d99ccb0f12680319))
|
||||
|
||||
# [2.21.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.20.3...@standardnotes/analytics@2.21.0) (2023-02-23)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.21.0",
|
||||
"version": "2.21.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -19,6 +19,10 @@ export class SessionCreatedEventHandler implements DomainEventHandlerInterface {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
})
|
||||
|
||||
this.mixpanelClient.track('GENERAL_ACTIVITY', {
|
||||
distinct_id: analyticsId.toString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ export class SessionRefreshedEventHandler implements DomainEventHandlerInterface
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
})
|
||||
|
||||
this.mixpanelClient.track('GENERAL_ACTIVITY', {
|
||||
distinct_id: analyticsId.toString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ PORT=3000
|
||||
|
||||
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
|
||||
AUTH_SERVER_URL=http://auth:3000
|
||||
WORKSPACE_SERVER_URL=http://workspace:3000
|
||||
WEB_SOCKET_SERVER_URL=http://websockets:3000
|
||||
PAYMENTS_SERVER_URL=http://payments:3000
|
||||
FILES_SERVER_URL=http://files:3000
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.49.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.49.4...@standardnotes/api-gateway@1.49.5) (2023-03-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.49.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.49.3...@standardnotes/api-gateway@1.49.4) (2023-02-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.49.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.49.2...@standardnotes/api-gateway@1.49.3) (2023-02-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateywa:** remove stale proxy references ([dfa5187](https://github.com/standardnotes/api-gateway/commit/dfa5187ff73833bf981d273da79f78ae0309a493))
|
||||
|
||||
## [1.49.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.49.1...@standardnotes/api-gateway@1.49.2) (2023-02-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.49.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.49.0...@standardnotes/api-gateway@1.49.1) (2023-02-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -19,10 +19,7 @@ import '../src/Controller/v1/TokensController'
|
||||
import '../src/Controller/v1/OfflineController'
|
||||
import '../src/Controller/v1/FilesController'
|
||||
import '../src/Controller/v1/SubscriptionInvitesController'
|
||||
import '../src/Controller/v1/WorkspacesController'
|
||||
import '../src/Controller/v1/InvitesController'
|
||||
import '../src/Controller/v1/AuthenticatorsController'
|
||||
import '../src/Controller/v1/ProxyController'
|
||||
|
||||
import '../src/Controller/v2/PaymentsControllerV2'
|
||||
import '../src/Controller/v2/ActionsControllerV2'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.49.1",
|
||||
"version": "1.49.5",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -59,9 +59,7 @@ export class ContainerConfigLoader {
|
||||
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.WORKSPACE_SERVER_URL).toConstantValue(env.get('WORKSPACE_SERVER_URL', true))
|
||||
container.bind(TYPES.WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL', true))
|
||||
container.bind(TYPES.PROXY_SERVER_URL).toConstantValue(env.get('PROXY_SERVER_URL', true))
|
||||
container
|
||||
.bind(TYPES.HTTP_CALL_TIMEOUT)
|
||||
.toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000)
|
||||
|
||||
@@ -9,9 +9,7 @@ const TYPES = {
|
||||
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
|
||||
REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'),
|
||||
EMAIL_SERVER_URL: Symbol.for('EMAIL_SERVER_URL'),
|
||||
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
|
||||
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
|
||||
PROXY_SERVER_URL: Symbol.for('PROXY_SERVER_URL'),
|
||||
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
|
||||
HTTP_CALL_TIMEOUT: Symbol.for('HTTP_CALL_TIMEOUT'),
|
||||
VERSION: Symbol.for('VERSION'),
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { inject } from 'inversify'
|
||||
import { Request, Response } from 'express'
|
||||
import { controller, BaseHttpController, httpPost } from 'inversify-express-utils'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/invites', TYPES.AuthMiddleware)
|
||||
export class InvitesController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpPost('/:inviteUuid/accept')
|
||||
async accept(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callWorkspaceServer(
|
||||
request,
|
||||
response,
|
||||
`invites/${request.params.inviteUuid}/accept`,
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -145,11 +145,6 @@ export class PaymentsController extends BaseHttpController {
|
||||
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)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import { all, BaseHttpController, controller } from 'inversify-express-utils'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/proxy')
|
||||
export class ProxyController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
super()
|
||||
}
|
||||
|
||||
@all('*', TYPES.AuthMiddleware)
|
||||
async createToken(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callProxyServer(request, response, request.path.replace('/v1/proxy', ''), request.body)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { inject } from 'inversify'
|
||||
import { Request, Response } from 'express'
|
||||
import { controller, BaseHttpController, httpPost, httpGet } from 'inversify-express-utils'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/workspaces', TYPES.AuthMiddleware)
|
||||
export class WorkspacesController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpPost('/')
|
||||
async create(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callWorkspaceServer(request, response, 'workspaces', request.body)
|
||||
}
|
||||
|
||||
@httpGet('/:workspaceUuid/users')
|
||||
async listWorkspaceUsers(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callWorkspaceServer(
|
||||
request,
|
||||
response,
|
||||
`workspaces/${request.params.workspaceUuid}/users`,
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpPost('/:workspaceUuid/users/:userUuid/keyshare')
|
||||
async initiateKeyshare(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callWorkspaceServer(
|
||||
request,
|
||||
response,
|
||||
`workspaces/${request.params.workspaceUuid}/users/${request.params.userUuid}/keyshare`,
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpGet('/')
|
||||
async listWorkspaces(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callWorkspaceServer(request, response, 'workspaces', request.body)
|
||||
}
|
||||
|
||||
@httpPost('/:workspaceUuid/invites')
|
||||
async invite(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callWorkspaceServer(
|
||||
request,
|
||||
response,
|
||||
`workspaces/${request.params.workspaceUuid}/invites`,
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,9 @@ export class HttpService implements HttpServiceInterface {
|
||||
@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.WORKSPACE_SERVER_URL) private workspaceServerUrl: string,
|
||||
@inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
|
||||
@inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string,
|
||||
@inject(TYPES.EMAIL_SERVER_URL) private emailServerUrl: string,
|
||||
@inject(TYPES.PROXY_SERVER_URL) private proxyServerUrl: string,
|
||||
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
|
||||
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@@ -82,21 +80,6 @@ export class HttpService implements HttpServiceInterface {
|
||||
await this.callServer(this.emailServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callWorkspaceServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (!this.workspaceServerUrl) {
|
||||
response.status(400).send({ message: 'Workspace Server not configured' })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callWebSocketServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
@@ -112,21 +95,6 @@ export class HttpService implements HttpServiceInterface {
|
||||
await this.callServer(this.webSocketServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callProxyServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (!this.proxyServerUrl) {
|
||||
this.logger.debug('Proxy Server URL not defined. Skipped request to Proxy.')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await this.callServer(this.proxyServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callPaymentsServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
|
||||
@@ -43,22 +43,10 @@ export interface HttpServiceInterface {
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
callWorkspaceServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
callWebSocketServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
callProxyServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,89 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.93.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.92.0...@standardnotes/auth-server@1.93.0) (2023-03-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** setting name value objects in typeorm queries ([28dc5ba](https://github.com/standardnotes/server/commit/28dc5ba2a4e946b7aed86432da160c0be76f839d))
|
||||
|
||||
### Features
|
||||
|
||||
* **domain-core:** add internal team user role ([#473](https://github.com/standardnotes/server/issues/473)) ([979a320](https://github.com/standardnotes/server/commit/979a320ca666991ad2b023436f58c59ae168c768))
|
||||
|
||||
# [1.92.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.91.2...@standardnotes/auth-server@1.92.0) (2023-03-08)
|
||||
|
||||
### Features
|
||||
|
||||
* sign in setting refactor ([#472](https://github.com/standardnotes/server/issues/472)) ([27cf093](https://github.com/standardnotes/server/commit/27cf093f85d0f2e208f48e7c7ddcce36b341ffb7))
|
||||
|
||||
## [1.91.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.91.1...@standardnotes/auth-server@1.91.2) (2023-03-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** associate setting with sign in alerts permission ([9002945](https://github.com/standardnotes/server/commit/90029456fe6d654747d6b8b7ae106d3d58b3a3fe))
|
||||
* **auth:** remove sign in emails permission from free accounts ([b167b00](https://github.com/standardnotes/server/commit/b167b0007555b3850ae274354b6c271fe0a1e47f))
|
||||
|
||||
## [1.91.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.91.0...@standardnotes/auth-server@1.91.1) (2023-03-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** disable sign in emails on newly created accounts ([782a9d3](https://github.com/standardnotes/server/commit/782a9d310dc2d2819a49540138ed10b36ebd0d94))
|
||||
|
||||
# [1.91.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.90.1...@standardnotes/auth-server@1.91.0) (2023-03-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add cleanup of expired sessions ([2fad6b6](https://github.com/standardnotes/server/commit/2fad6b62cbb5bec38a3171a996d3f9c4eedf7836))
|
||||
|
||||
## [1.90.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.90.0...@standardnotes/auth-server@1.90.1) (2023-03-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** prevent listing sessions on readonly access ([dbccdf3](https://github.com/standardnotes/server/commit/dbccdf342b52f81fb14f246784d5dc6def2ff3fc))
|
||||
|
||||
# [1.90.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.89.7...@standardnotes/auth-server@1.90.0) (2023-03-02)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add configurable list of readonly users ([#462](https://github.com/standardnotes/server/issues/462)) ([d646995](https://github.com/standardnotes/server/commit/d6469954ceb24580c465535e61588b04924734ab))
|
||||
|
||||
## [1.89.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.89.6...@standardnotes/auth-server@1.89.7) (2023-03-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** function naming for more clarity ([79ccbdf](https://github.com/standardnotes/server/commit/79ccbdf1000c699074b5271f3c04a30fcb1b3311))
|
||||
|
||||
## [1.89.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.89.5...@standardnotes/auth-server@1.89.6) (2023-03-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** changing the updated_at property on sessions ([753f867](https://github.com/standardnotes/server/commit/753f86707ffdbab0d04f49b42275dbb28589780b))
|
||||
|
||||
## [1.89.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.89.4...@standardnotes/auth-server@1.89.5) (2023-03-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.89.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.89.3...@standardnotes/auth-server@1.89.4) (2023-03-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** updating counter post authenticator verification ([a9cc00a](https://github.com/standardnotes/server/commit/a9cc00a4783c12e71eb181a3ccf3218b418750d9))
|
||||
|
||||
## [1.89.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.89.2...@standardnotes/auth-server@1.89.3) (2023-02-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.89.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.89.1...@standardnotes/auth-server@1.89.2) (2023-02-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add cross-platform authenticator selection option ([edc4a20](https://github.com/standardnotes/server/commit/edc4a2085952efe0b83c8e837a52555087714ef7))
|
||||
|
||||
## [1.89.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.89.0...@standardnotes/auth-server@1.89.1) (2023-02-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.89.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.88.3...@standardnotes/auth-server@1.89.0) (2023-02-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -32,36 +32,36 @@ const requestBackups = async (
|
||||
): Promise<void> => {
|
||||
let settingName: SettingName,
|
||||
permissionName: PermissionName,
|
||||
muteEmailsSettingName: SettingName,
|
||||
muteEmailsSettingName: string,
|
||||
muteEmailsSettingValue: string,
|
||||
providerTokenSettingName: SettingName
|
||||
switch (backupProvider) {
|
||||
case 'email':
|
||||
settingName = SettingName.EmailBackupFrequency
|
||||
settingName = SettingName.create(SettingName.NAMES.EmailBackupFrequency).getValue()
|
||||
permissionName = PermissionName.DailyEmailBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
||||
break
|
||||
case 'dropbox':
|
||||
settingName = SettingName.DropboxBackupFrequency
|
||||
settingName = SettingName.create(SettingName.NAMES.DropboxBackupFrequency).getValue()
|
||||
permissionName = PermissionName.DailyDropboxBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.DropboxBackupToken
|
||||
providerTokenSettingName = SettingName.create(SettingName.NAMES.DropboxBackupToken).getValue()
|
||||
break
|
||||
case 'one_drive':
|
||||
settingName = SettingName.OneDriveBackupFrequency
|
||||
settingName = SettingName.create(SettingName.NAMES.OneDriveBackupFrequency).getValue()
|
||||
permissionName = PermissionName.DailyOneDriveBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.OneDriveBackupToken
|
||||
providerTokenSettingName = SettingName.create(SettingName.NAMES.OneDriveBackupToken).getValue()
|
||||
break
|
||||
case 'google_drive':
|
||||
settingName = SettingName.GoogleDriveBackupFrequency
|
||||
settingName = SettingName.create(SettingName.NAMES.GoogleDriveBackupFrequency).getValue()
|
||||
permissionName = PermissionName.DailyGDriveBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.GoogleDriveBackupToken
|
||||
providerTokenSettingName = SettingName.create(SettingName.NAMES.GoogleDriveBackupToken).getValue()
|
||||
break
|
||||
default:
|
||||
throw new Error(`Not handled backup provider: ${backupProvider}`)
|
||||
|
||||
@@ -8,6 +8,17 @@ import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { CleanupSessionTraces } from '../src/Domain/UseCase/CleanupSessionTraces/CleanupSessionTraces'
|
||||
import { CleanupExpiredSessions } from '../src/Domain/UseCase/CleanupExpiredSessions/CleanupExpiredSessions'
|
||||
|
||||
const cleanup = async (
|
||||
cleanupSessionTraces: CleanupSessionTraces,
|
||||
cleanupExpiredSessions: CleanupExpiredSessions,
|
||||
): Promise<void> => {
|
||||
const date = new Date()
|
||||
|
||||
await cleanupSessionTraces.execute({ date })
|
||||
await cleanupExpiredSessions.execute({ date })
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
void container.load().then((container) => {
|
||||
@@ -16,22 +27,19 @@ void container.load().then((container) => {
|
||||
|
||||
const logger: Logger = container.get(TYPES.Logger)
|
||||
|
||||
logger.info('Starting session traces cleanup')
|
||||
logger.info('Starting sessions and session traces cleanup')
|
||||
|
||||
const cleanupSessionTraces: CleanupSessionTraces = container.get(TYPES.CleanupSessionTraces)
|
||||
const cleanupExpiredSessions: CleanupExpiredSessions = container.get(TYPES.CleanupExpiredSessions)
|
||||
|
||||
Promise.resolve(
|
||||
cleanupSessionTraces.execute({
|
||||
date: new Date(),
|
||||
}),
|
||||
)
|
||||
Promise.resolve(cleanup(cleanupSessionTraces, cleanupExpiredSessions))
|
||||
.then(() => {
|
||||
logger.info('Expired session traces cleaned.')
|
||||
logger.info('Expired sessions and session traces cleaned.')
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not clean session traces: ${error.message}`)
|
||||
logger.error(`Could not clean sessions and session traces: ${error.message}`)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
137
packages/auth/bin/migrate_email_settings.ts
Normal file
137
packages/auth/bin/migrate_email_settings.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import 'newrelic'
|
||||
|
||||
import { Stream } from 'stream'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import * as dayjs from 'dayjs'
|
||||
import * as utc from 'dayjs/plugin/utc'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { EmailLevel } from '@standardnotes/domain-core'
|
||||
import { UserSubscriptionServiceInterface } from '../src/Domain/Subscription/UserSubscriptionServiceInterface'
|
||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
||||
import { SubscriptionSettingServiceInterface } from '../src/Domain/Setting/SubscriptionSettingServiceInterface'
|
||||
import { EncryptionVersion } from '../src/Domain/Encryption/EncryptionVersion'
|
||||
|
||||
const requestSettingMigration = async (
|
||||
settingRepository: SettingRepositoryInterface,
|
||||
subscriptionSettingService: SubscriptionSettingServiceInterface,
|
||||
userRepository: UserRepositoryInterface,
|
||||
userSubscriptionService: UserSubscriptionServiceInterface,
|
||||
domainEventFactory: DomainEventFactoryInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
): Promise<void> => {
|
||||
const stream = await settingRepository.streamAllByNameAndValue(
|
||||
SettingName.create(SettingName.NAMES.MuteSignInEmails).getValue(),
|
||||
'not_muted',
|
||||
)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream
|
||||
.pipe(
|
||||
new Stream.Transform({
|
||||
objectMode: true,
|
||||
transform: async (setting, _encoding, callback) => {
|
||||
const user = await userRepository.findOneByUuid(setting.setting_user_uuid)
|
||||
if (!user) {
|
||||
callback()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const { regularSubscription, sharedSubscription } =
|
||||
await userSubscriptionService.findRegularSubscriptionForUserUuid(user.uuid)
|
||||
|
||||
const subscription = sharedSubscription ?? regularSubscription
|
||||
if (!subscription) {
|
||||
await domainEventPublisher.publish(
|
||||
domainEventFactory.createMuteEmailsSettingChangedEvent({
|
||||
username: user.email,
|
||||
mute: true,
|
||||
emailSubscriptionRejectionLevel: EmailLevel.LEVELS.SignIn,
|
||||
}),
|
||||
)
|
||||
|
||||
await settingRepository.deleteByUserUuid({
|
||||
userUuid: user.uuid,
|
||||
settingName: SettingName.NAMES.MuteSignInEmails,
|
||||
})
|
||||
|
||||
callback()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await subscriptionSettingService.createOrReplace({
|
||||
userSubscription: subscription,
|
||||
props: {
|
||||
name: SettingName.NAMES.MuteSignInEmails,
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
unencryptedValue: 'not_muted',
|
||||
},
|
||||
})
|
||||
|
||||
await settingRepository.deleteByUserUuid({
|
||||
userUuid: user.uuid,
|
||||
settingName: SettingName.NAMES.MuteSignInEmails,
|
||||
})
|
||||
|
||||
callback()
|
||||
},
|
||||
}),
|
||||
)
|
||||
.on('finish', resolve)
|
||||
.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
void container.load().then((container) => {
|
||||
dayjs.extend(utc)
|
||||
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const logger: Logger = container.get(TYPES.Logger)
|
||||
|
||||
logger.info('Starting migration of mute sign in emails settings to subscription settings...')
|
||||
|
||||
const settingRepository: SettingRepositoryInterface = container.get(TYPES.SettingRepository)
|
||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||
const subscriptionSettingService: SubscriptionSettingServiceInterface = container.get(
|
||||
TYPES.SubscriptionSettingService,
|
||||
)
|
||||
const userRepository: UserRepositoryInterface = container.get(TYPES.UserRepository)
|
||||
const userSubscriptionService: UserSubscriptionServiceInterface = container.get(TYPES.UserSubscriptionService)
|
||||
|
||||
Promise.resolve(
|
||||
requestSettingMigration(
|
||||
settingRepository,
|
||||
subscriptionSettingService,
|
||||
userRepository,
|
||||
userSubscriptionService,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
logger.info('Migration of mute sign in emails settings to subscription settings finished successfully.')
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Migration of mute sign in emails settings to subscription settings failed: ${error.message}`)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
@@ -28,7 +28,7 @@ const requestBackups = async (
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
): Promise<void> => {
|
||||
const permissionName = PermissionName.DailyEmailBackup
|
||||
const muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
||||
const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
||||
|
||||
if (!backupEmail) {
|
||||
|
||||
@@ -4,7 +4,7 @@ const path = require('path')
|
||||
|
||||
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
|
||||
|
||||
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/worker.js')))
|
||||
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/migrate_email_settings.js')))
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
|
||||
@@ -40,6 +40,11 @@ case "$COMMAND" in
|
||||
node docker/entrypoint-user-email-backup.js $EMAIL
|
||||
;;
|
||||
|
||||
'migrate-email-settings' )
|
||||
echo "[Docker] Starting Email Settings Migration..."
|
||||
node docker/entrypoint-migrate-email-settings.js
|
||||
;;
|
||||
|
||||
'dropbox-daily-backup' )
|
||||
echo "[Docker] Starting Dropbox Daily Backup..."
|
||||
node docker/entrypoint-backup.js dropbox daily
|
||||
|
||||
@@ -34,7 +34,7 @@ export class moveMfaItemsToUserSettings1627638504691 implements MigrationInterfa
|
||||
|
||||
const setting = new Setting()
|
||||
setting.uuid = item['uuid']
|
||||
setting.name = SettingName.MfaSecret
|
||||
setting.name = SettingName.NAMES.MfaSecret
|
||||
setting.value = item['content']
|
||||
if (item['deleted']) {
|
||||
setting.value = null
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class removeSignInEmailsOnFreeAcounts1678110075698 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'DELETE FROM `role_permissions` WHERE role_uuid="23bf88ca-bee1-4a4c-adf0-b7a48749eea7" AND permission_uuid="2074d312-78bc-4533-b008-38e1232226c0"',
|
||||
)
|
||||
await queryRunner.query(
|
||||
'DELETE FROM `role_permissions` WHERE role_uuid="bde42e26-628c-44e6-9d76-21b08954b0bf" AND permission_uuid="2074d312-78bc-4533-b008-38e1232226c0"',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class addInternalTeamUserRole1678266947362 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// remove beta files user role and permission
|
||||
await queryRunner.query('DELETE FROM `role_permissions` WHERE role_uuid="1cd9ee6e-bc95-4f32-957c-d8c41f94d4ef"')
|
||||
await queryRunner.query('DELETE FROM `user_roles` WHERE role_uuid="1cd9ee6e-bc95-4f32-957c-d8c41f94d4ef"')
|
||||
await queryRunner.query('DELETE FROM `roles` WHERE name="FILES_BETA_USER"')
|
||||
await queryRunner.query('DELETE FROM `permissions` WHERE name="app:files-beta"')
|
||||
|
||||
// add internal team user role and permission
|
||||
await queryRunner.query(
|
||||
'INSERT INTO `roles` (uuid, name, version) VALUES ("9f8d2313-e8d0-48ad-b19c-026601d0ddf4", "INTERNAL_TEAM_USER", 1)',
|
||||
)
|
||||
await queryRunner.query(
|
||||
'INSERT INTO `permissions` (uuid, name) VALUES ("fb13e7d3-936f-4ded-a543-e1650cc99dfd", "server:universal-second-factor")',
|
||||
)
|
||||
await queryRunner.query(
|
||||
'INSERT INTO `role_permissions` (role_uuid, permission_uuid) VALUES ("9f8d2313-e8d0-48ad-b19c-026601d0ddf4", "fb13e7d3-936f-4ded-a543-e1650cc99dfd")',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.89.0",
|
||||
"version": "1.93.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -39,15 +39,15 @@
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@sentry/tracing": "^7.28.1",
|
||||
"@simplewebauthn/server": "^7.0.0",
|
||||
"@standardnotes/api": "^1.24.10",
|
||||
"@simplewebauthn/server": "^7.0.1",
|
||||
"@standardnotes/api": "^1.25.3",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
"@standardnotes/features": "^1.58.4",
|
||||
"@standardnotes/features": "^1.58.9",
|
||||
"@standardnotes/predicates": "workspace:*",
|
||||
"@standardnotes/responses": "^1.13.4",
|
||||
"@standardnotes/responses": "^1.13.9",
|
||||
"@standardnotes/security": "workspace:*",
|
||||
"@standardnotes/settings": "workspace:*",
|
||||
"@standardnotes/sncrypto-common": "^1.9.0",
|
||||
|
||||
@@ -168,7 +168,6 @@ import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedS
|
||||
import { UserSubscriptionServiceInterface } from '../Domain/Subscription/UserSubscriptionServiceInterface'
|
||||
import { UserSubscriptionService } from '../Domain/Subscription/UserSubscriptionService'
|
||||
import { SubscriptionSettingProjector } from '../Projection/SubscriptionSettingProjector'
|
||||
import { GetSubscriptionSetting } from '../Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSetting'
|
||||
import { SubscriptionSettingsAssociationService } from '../Domain/Setting/SubscriptionSettingsAssociationService'
|
||||
import { SubscriptionSettingsAssociationServiceInterface } from '../Domain/Setting/SubscriptionSettingsAssociationServiceInterface'
|
||||
import { PKCERepositoryInterface } from '../Domain/User/PKCERepositoryInterface'
|
||||
@@ -216,6 +215,7 @@ import { DeleteAuthenticator } from '../Domain/UseCase/DeleteAuthenticator/Delet
|
||||
import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/GenerateRecoveryCodes'
|
||||
import { SignInWithRecoveryCodes } from '../Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
|
||||
import { GetUserKeyParamsRecovery } from '../Domain/UseCase/GetUserKeyParamsRecovery/GetUserKeyParamsRecovery'
|
||||
import { CleanupExpiredSessions } from '../Domain/UseCase/CleanupExpiredSessions/CleanupExpiredSessions'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -463,6 +463,10 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind(TYPES.U2F_REQUIRE_USER_VERIFICATION)
|
||||
.toConstantValue(env.get('U2F_REQUIRE_USER_VERIFICATION', true) === 'true')
|
||||
container
|
||||
.bind(TYPES.READONLY_USERS)
|
||||
.toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : [])
|
||||
|
||||
// Services
|
||||
container.bind<UAParser>(TYPES.DeviceDetector).toConstantValue(new UAParser())
|
||||
container.bind<SessionService>(TYPES.SessionService).to(SessionService)
|
||||
@@ -612,6 +616,9 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<CleanupSessionTraces>(TYPES.CleanupSessionTraces)
|
||||
.toConstantValue(new CleanupSessionTraces(container.get(TYPES.SessionTraceRepository)))
|
||||
container
|
||||
.bind<CleanupExpiredSessions>(TYPES.CleanupExpiredSessions)
|
||||
.toConstantValue(new CleanupExpiredSessions(container.get(TYPES.SessionRepository)))
|
||||
container.bind<AuthenticateUser>(TYPES.AuthenticateUser).to(AuthenticateUser)
|
||||
container.bind<AuthenticateRequest>(TYPES.AuthenticateRequest).to(AuthenticateRequest)
|
||||
container.bind<RefreshSessionToken>(TYPES.RefreshSessionToken).to(RefreshSessionToken)
|
||||
@@ -683,7 +690,6 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<ListSharedSubscriptionInvitations>(TYPES.ListSharedSubscriptionInvitations)
|
||||
.to(ListSharedSubscriptionInvitations)
|
||||
container.bind<GetSubscriptionSetting>(TYPES.GetSubscriptionSetting).to(GetSubscriptionSetting)
|
||||
container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate)
|
||||
container.bind<CreateCrossServiceToken>(TYPES.CreateCrossServiceToken).to(CreateCrossServiceToken)
|
||||
container.bind<ProcessUserRequest>(TYPES.ProcessUserRequest).to(ProcessUserRequest)
|
||||
|
||||
@@ -97,6 +97,7 @@ const TYPES = {
|
||||
U2F_RELYING_PARTY_NAME: Symbol.for('U2F_RELYING_PARTY_NAME'),
|
||||
U2F_EXPECTED_ORIGIN: Symbol.for('U2F_EXPECTED_ORIGIN'),
|
||||
U2F_REQUIRE_USER_VERIFICATION: Symbol.for('U2F_REQUIRE_USER_VERIFICATION'),
|
||||
READONLY_USERS: Symbol.for('READONLY_USERS'),
|
||||
// use cases
|
||||
AuthenticateUser: Symbol.for('AuthenticateUser'),
|
||||
AuthenticateRequest: Symbol.for('AuthenticateRequest'),
|
||||
@@ -131,12 +132,12 @@ const TYPES = {
|
||||
DeclineSharedSubscriptionInvitation: Symbol.for('DeclineSharedSubscriptionInvitation'),
|
||||
CancelSharedSubscriptionInvitation: Symbol.for('CancelSharedSubscriptionInvitation'),
|
||||
ListSharedSubscriptionInvitations: Symbol.for('ListSharedSubscriptionInvitations'),
|
||||
GetSubscriptionSetting: Symbol.for('GetSubscriptionSetting'),
|
||||
VerifyPredicate: Symbol.for('VerifyPredicate'),
|
||||
CreateCrossServiceToken: Symbol.for('CreateCrossServiceToken'),
|
||||
ProcessUserRequest: Symbol.for('ProcessUserRequest'),
|
||||
TraceSession: Symbol.for('TraceSession'),
|
||||
CleanupSessionTraces: Symbol.for('CleanupSessionTraces'),
|
||||
CleanupExpiredSessions: Symbol.for('CleanupExpiredSessions'),
|
||||
PersistStatistics: Symbol.for('PersistStatistics'),
|
||||
GenerateAuthenticatorRegistrationOptions: Symbol.for('GenerateAuthenticatorRegistrationOptions'),
|
||||
VerifyAuthenticatorRegistrationResponse: Symbol.for('VerifyAuthenticatorRegistrationResponse'),
|
||||
|
||||
@@ -69,7 +69,7 @@ export class AdminController extends BaseHttpController {
|
||||
const result = await this.doDeleteSetting.execute({
|
||||
uuid,
|
||||
userUuid,
|
||||
settingName: SettingName.MfaSecret,
|
||||
settingName: SettingName.NAMES.MfaSecret,
|
||||
timestamp: updatedAt,
|
||||
softDelete: true,
|
||||
})
|
||||
@@ -115,7 +115,7 @@ export class AdminController extends BaseHttpController {
|
||||
|
||||
const result = await this.doDeleteSetting.execute({
|
||||
userUuid,
|
||||
settingName: SettingName.EmailBackupFrequency,
|
||||
settingName: SettingName.NAMES.EmailBackupFrequency,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -2,12 +2,12 @@ import { inject, injectable } from 'inversify'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import {
|
||||
ApiVersion,
|
||||
HttpStatusCode,
|
||||
UserDeletionResponse,
|
||||
UserRegistrationRequestParams,
|
||||
UserRegistrationResponse,
|
||||
UserServerInterface,
|
||||
UserDeletionResponseBody,
|
||||
UserRegistrationResponseBody,
|
||||
} from '@standardnotes/api'
|
||||
import { HttpResponse, HttpStatusCode } from '@standardnotes/responses'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
@@ -16,13 +16,13 @@ import { Register } from '../Domain/UseCase/Register'
|
||||
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
|
||||
import { SignInWithRecoveryCodes } from '../Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
|
||||
import { SignInWithRecoveryCodesRequestParams } from '../Infra/Http/Request/SignInWithRecoveryCodesRequestParams'
|
||||
import { SignInWithRecoveryCodesResponse } from '../Infra/Http/Response/SignInWithRecoveryCodesResponse'
|
||||
import { GetUserKeyParamsRecovery } from '../Domain/UseCase/GetUserKeyParamsRecovery/GetUserKeyParamsRecovery'
|
||||
import { RecoveryKeyParamsRequestParams } from '../Infra/Http/Request/RecoveryKeyParamsRequestParams'
|
||||
import { RecoveryKeyParamsResponse } from '../Infra/Http/Response/RecoveryKeyParamsResponse'
|
||||
import { SignInWithRecoveryCodesResponseBody } from '../Infra/Http/Response/SignInWithRecoveryCodesResponseBody'
|
||||
import { RecoveryKeyParamsResponseBody } from '../Infra/Http/Response/RecoveryKeyParamsResponseBody'
|
||||
import { GenerateRecoveryCodesResponseBody } from '../Infra/Http/Response/GenerateRecoveryCodesResponseBody'
|
||||
import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/GenerateRecoveryCodes'
|
||||
import { GenerateRecoveryCodesRequestParams } from '../Infra/Http/Request/GenerateRecoveryCodesRequestParams'
|
||||
import { GenerateRecoveryCodesResponse } from '../Infra/Http/Response/GenerateRecoveryCodesResponse'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@injectable()
|
||||
@@ -38,11 +38,11 @@ export class AuthController implements UserServerInterface {
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async deleteAccount(_params: never): Promise<UserDeletionResponse> {
|
||||
async deleteAccount(_params: never): Promise<HttpResponse<UserDeletionResponseBody>> {
|
||||
throw new Error('This method is implemented on the payments server.')
|
||||
}
|
||||
|
||||
async register(params: UserRegistrationRequestParams): Promise<UserRegistrationResponse> {
|
||||
async register(params: UserRegistrationRequestParams): Promise<HttpResponse<UserRegistrationResponseBody>> {
|
||||
if (!params.email || !params.password) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
@@ -93,7 +93,9 @@ export class AuthController implements UserServerInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async generateRecoveryCodes(params: GenerateRecoveryCodesRequestParams): Promise<GenerateRecoveryCodesResponse> {
|
||||
async generateRecoveryCodes(
|
||||
params: GenerateRecoveryCodesRequestParams,
|
||||
): Promise<HttpResponse<GenerateRecoveryCodesResponseBody>> {
|
||||
const result = await this.doGenerateRecoveryCodes.execute({
|
||||
userUuid: params.userUuid,
|
||||
})
|
||||
@@ -119,7 +121,7 @@ export class AuthController implements UserServerInterface {
|
||||
|
||||
async signInWithRecoveryCodes(
|
||||
params: SignInWithRecoveryCodesRequestParams,
|
||||
): Promise<SignInWithRecoveryCodesResponse> {
|
||||
): Promise<HttpResponse<SignInWithRecoveryCodesResponseBody>> {
|
||||
if (params.apiVersion !== ApiVersion.v0) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
@@ -158,7 +160,9 @@ export class AuthController implements UserServerInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async recoveryKeyParams(params: RecoveryKeyParamsRequestParams): Promise<RecoveryKeyParamsResponse> {
|
||||
async recoveryKeyParams(
|
||||
params: RecoveryKeyParamsRequestParams,
|
||||
): Promise<HttpResponse<RecoveryKeyParamsResponseBody>> {
|
||||
if (params.apiVersion !== ApiVersion.v0) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HttpStatusCode } from '@standardnotes/api'
|
||||
import { HttpResponse, HttpStatusCode } from '@standardnotes/responses'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
import { Authenticator } from '../Domain/Authenticator/Authenticator'
|
||||
import { DeleteAuthenticator } from '../Domain/UseCase/DeleteAuthenticator/DeleteAuthenticator'
|
||||
@@ -13,11 +13,11 @@ import { GenerateAuthenticatorAuthenticationOptionsRequestParams } from '../Infr
|
||||
import { GenerateAuthenticatorRegistrationOptionsRequestParams } from '../Infra/Http/Request/GenerateAuthenticatorRegistrationOptionsRequestParams'
|
||||
import { ListAuthenticatorsRequestParams } from '../Infra/Http/Request/ListAuthenticatorsRequestParams'
|
||||
import { VerifyAuthenticatorRegistrationResponseRequestParams } from '../Infra/Http/Request/VerifyAuthenticatorRegistrationResponseRequestParams'
|
||||
import { DeleteAuthenticatorResponse } from '../Infra/Http/Response/DeleteAuthenticatorResponse'
|
||||
import { GenerateAuthenticatorAuthenticationOptionsResponse } from '../Infra/Http/Response/GenerateAuthenticatorAuthenticationOptionsResponse'
|
||||
import { GenerateAuthenticatorRegistrationOptionsResponse } from '../Infra/Http/Response/GenerateAuthenticatorRegistrationOptionsResponse'
|
||||
import { ListAuthenticatorsResponse } from '../Infra/Http/Response/ListAuthenticatorsResponse'
|
||||
import { VerifyAuthenticatorRegistrationResponseResponse } from '../Infra/Http/Response/VerifyAuthenticatorRegistrationResponseResponse'
|
||||
import { DeleteAuthenticatorResponseBody } from '../Infra/Http/Response/DeleteAuthenticatorResponseBody'
|
||||
import { GenerateAuthenticatorAuthenticationOptionsResponseBody } from '../Infra/Http/Response/GenerateAuthenticatorAuthenticationOptionsResponseBody'
|
||||
import { GenerateAuthenticatorRegistrationOptionsResponseBody } from '../Infra/Http/Response/GenerateAuthenticatorRegistrationOptionsResponseBody'
|
||||
import { ListAuthenticatorsResponseBody } from '../Infra/Http/Response/ListAuthenticatorsResponseBody'
|
||||
import { VerifyAuthenticatorRegistrationResponseResponseBody } from '../Infra/Http/Response/VerifyAuthenticatorRegistrationResponseResponseBody'
|
||||
|
||||
export class AuthenticatorsController {
|
||||
constructor(
|
||||
@@ -29,7 +29,7 @@ export class AuthenticatorsController {
|
||||
private authenticatorHttpMapper: MapperInterface<Authenticator, AuthenticatorHttpProjection>,
|
||||
) {}
|
||||
|
||||
async list(params: ListAuthenticatorsRequestParams): Promise<ListAuthenticatorsResponse> {
|
||||
async list(params: ListAuthenticatorsRequestParams): Promise<HttpResponse<ListAuthenticatorsResponseBody>> {
|
||||
const result = await this.listAuthenticators.execute({
|
||||
userUuid: params.userUuid,
|
||||
})
|
||||
@@ -44,7 +44,7 @@ export class AuthenticatorsController {
|
||||
}
|
||||
}
|
||||
|
||||
async delete(params: DeleteAuthenticatorRequestParams): Promise<DeleteAuthenticatorResponse> {
|
||||
async delete(params: DeleteAuthenticatorRequestParams): Promise<HttpResponse<DeleteAuthenticatorResponseBody>> {
|
||||
const result = await this.deleteAuthenticator.execute({
|
||||
userUuid: params.userUuid,
|
||||
authenticatorId: params.authenticatorId,
|
||||
@@ -60,7 +60,7 @@ export class AuthenticatorsController {
|
||||
|
||||
async generateRegistrationOptions(
|
||||
params: GenerateAuthenticatorRegistrationOptionsRequestParams,
|
||||
): Promise<GenerateAuthenticatorRegistrationOptionsResponse> {
|
||||
): Promise<HttpResponse<GenerateAuthenticatorRegistrationOptionsResponseBody>> {
|
||||
const result = await this.generateAuthenticatorRegistrationOptions.execute({
|
||||
userUuid: params.userUuid,
|
||||
username: params.username,
|
||||
@@ -85,7 +85,7 @@ export class AuthenticatorsController {
|
||||
|
||||
async verifyRegistrationResponse(
|
||||
params: VerifyAuthenticatorRegistrationResponseRequestParams,
|
||||
): Promise<VerifyAuthenticatorRegistrationResponseResponse> {
|
||||
): Promise<HttpResponse<VerifyAuthenticatorRegistrationResponseResponseBody>> {
|
||||
const result = await this.verifyAuthenticatorRegistrationResponse.execute({
|
||||
userUuid: params.userUuid,
|
||||
name: params.name,
|
||||
@@ -111,7 +111,7 @@ export class AuthenticatorsController {
|
||||
|
||||
async generateAuthenticationOptions(
|
||||
params: GenerateAuthenticatorAuthenticationOptionsRequestParams,
|
||||
): Promise<GenerateAuthenticatorAuthenticationOptionsResponse> {
|
||||
): Promise<HttpResponse<GenerateAuthenticatorAuthenticationOptionsResponseBody>> {
|
||||
const result = await this.generateAuthenticatorAuthenticationOptions.execute({
|
||||
username: params.username,
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import { BaseHttpController, controller, httpPost, results } from 'inversify-exp
|
||||
import { Request, Response } from 'express'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { CreateListedAccount } from '../Domain/UseCase/CreateListedAccount/CreateListedAccount'
|
||||
import { ErrorTag } from '@standardnotes/api'
|
||||
import { ErrorTag } from '@standardnotes/responses'
|
||||
|
||||
@controller('/listed')
|
||||
export class ListedController extends BaseHttpController {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ErrorTag } from '@standardnotes/api'
|
||||
import { ErrorTag } from '@standardnotes/responses'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
|
||||
@@ -58,6 +58,10 @@ export class SessionsController extends BaseHttpController {
|
||||
|
||||
@httpGet('/', TYPES.AuthMiddleware, TYPES.SessionMiddleware)
|
||||
async getSessions(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (response.locals.readOnlyAccess) {
|
||||
return this.json([])
|
||||
}
|
||||
|
||||
const useCaseResponse = await this.getActiveSessionsForUser.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
@@ -90,7 +90,7 @@ describe('SettingsController', () => {
|
||||
const httpResponse = <results.JsonResult>await createController().getSetting(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(getSetting.execute).toHaveBeenCalledWith({ userUuid: '1-2-3', settingName: 'test' })
|
||||
expect(getSetting.execute).toHaveBeenCalledWith({ userUuid: '1-2-3', settingName: 'TEST' })
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
})
|
||||
@@ -124,7 +124,7 @@ describe('SettingsController', () => {
|
||||
const httpResponse = <results.JsonResult>await createController().getSetting(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(getSetting.execute).toHaveBeenCalledWith({ userUuid: '1-2-3', settingName: 'test' })
|
||||
expect(getSetting.execute).toHaveBeenCalledWith({ userUuid: '1-2-3', settingName: 'TEST' })
|
||||
|
||||
expect(result.statusCode).toEqual(400)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ErrorTag } from '@standardnotes/api'
|
||||
import { ErrorTag } from '@standardnotes/responses'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
@@ -61,7 +61,7 @@ export class SettingsController extends BaseHttpController {
|
||||
}
|
||||
|
||||
const { userUuid, settingName } = request.params
|
||||
const result = await this.doGetSetting.execute({ userUuid, settingName })
|
||||
const result = await this.doGetSetting.execute({ userUuid, settingName: settingName.toUpperCase() })
|
||||
|
||||
if (result.success) {
|
||||
return this.json(result)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import {
|
||||
AppleIAPConfirmRequestParams,
|
||||
AppleIAPConfirmResponse,
|
||||
HttpStatusCode,
|
||||
AppleIAPConfirmResponseBody,
|
||||
SubscriptionInviteAcceptRequestParams,
|
||||
SubscriptionInviteAcceptResponse,
|
||||
SubscriptionInviteAcceptResponseBody,
|
||||
SubscriptionInviteCancelRequestParams,
|
||||
SubscriptionInviteCancelResponse,
|
||||
SubscriptionInviteCancelResponseBody,
|
||||
SubscriptionInviteDeclineRequestParams,
|
||||
SubscriptionInviteDeclineResponse,
|
||||
SubscriptionInviteDeclineResponseBody,
|
||||
SubscriptionInviteListRequestParams,
|
||||
SubscriptionInviteListResponse,
|
||||
SubscriptionInviteListResponseBody,
|
||||
SubscriptionInviteRequestParams,
|
||||
SubscriptionInviteResponse,
|
||||
SubscriptionInviteResponseBody,
|
||||
SubscriptionServerInterface,
|
||||
} from '@standardnotes/api'
|
||||
import { HttpResponse, HttpStatusCode } from '@standardnotes/responses'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
@@ -37,11 +37,13 @@ export class SubscriptionInvitesController implements SubscriptionServerInterfac
|
||||
private listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations,
|
||||
) {}
|
||||
|
||||
async confirmAppleIAP(_params: AppleIAPConfirmRequestParams): Promise<AppleIAPConfirmResponse> {
|
||||
async confirmAppleIAP(_params: AppleIAPConfirmRequestParams): Promise<HttpResponse<AppleIAPConfirmResponseBody>> {
|
||||
throw new Error('Method implemented on the payments service.')
|
||||
}
|
||||
|
||||
async acceptInvite(params: SubscriptionInviteAcceptRequestParams): Promise<SubscriptionInviteAcceptResponse> {
|
||||
async acceptInvite(
|
||||
params: SubscriptionInviteAcceptRequestParams,
|
||||
): Promise<HttpResponse<SubscriptionInviteAcceptResponseBody>> {
|
||||
const result = await this.acceptSharedSubscriptionInvitation.execute({
|
||||
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||
})
|
||||
@@ -59,7 +61,9 @@ export class SubscriptionInvitesController implements SubscriptionServerInterfac
|
||||
}
|
||||
}
|
||||
|
||||
async declineInvite(params: SubscriptionInviteDeclineRequestParams): Promise<SubscriptionInviteDeclineResponse> {
|
||||
async declineInvite(
|
||||
params: SubscriptionInviteDeclineRequestParams,
|
||||
): Promise<HttpResponse<SubscriptionInviteDeclineResponseBody>> {
|
||||
const result = await this.declineSharedSubscriptionInvitation.execute({
|
||||
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||
})
|
||||
@@ -77,7 +81,7 @@ export class SubscriptionInvitesController implements SubscriptionServerInterfac
|
||||
}
|
||||
}
|
||||
|
||||
async invite(params: SubscriptionInviteRequestParams): Promise<SubscriptionInviteResponse> {
|
||||
async invite(params: SubscriptionInviteRequestParams): Promise<HttpResponse<SubscriptionInviteResponseBody>> {
|
||||
if (!params.identifier) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
@@ -109,7 +113,9 @@ export class SubscriptionInvitesController implements SubscriptionServerInterfac
|
||||
}
|
||||
}
|
||||
|
||||
async cancelInvite(params: SubscriptionInviteCancelRequestParams): Promise<SubscriptionInviteCancelResponse> {
|
||||
async cancelInvite(
|
||||
params: SubscriptionInviteCancelRequestParams,
|
||||
): Promise<HttpResponse<SubscriptionInviteCancelResponseBody>> {
|
||||
const result = await this.cancelSharedSubscriptionInvitation.execute({
|
||||
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||
inviterEmail: params.inviterEmail as string,
|
||||
@@ -128,7 +134,9 @@ export class SubscriptionInvitesController implements SubscriptionServerInterfac
|
||||
}
|
||||
}
|
||||
|
||||
async listInvites(params: SubscriptionInviteListRequestParams): Promise<SubscriptionInviteListResponse> {
|
||||
async listInvites(
|
||||
params: SubscriptionInviteListRequestParams,
|
||||
): Promise<HttpResponse<SubscriptionInviteListResponseBody>> {
|
||||
const result = await this.listSharedSubscriptionInvitations.execute({
|
||||
inviterEmail: params.inviterEmail as string,
|
||||
})
|
||||
|
||||
@@ -4,24 +4,24 @@ import * as express from 'express'
|
||||
|
||||
import { results } from 'inversify-express-utils'
|
||||
import { User } from '../Domain/User/User'
|
||||
import { GetSubscriptionSetting } from '../Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSetting'
|
||||
import { SubscriptionSettingsController } from './SubscriptionSettingsController'
|
||||
import { GetSetting } from '../Domain/UseCase/GetSetting/GetSetting'
|
||||
|
||||
describe('SubscriptionSettingsController', () => {
|
||||
let getSubscriptionSetting: GetSubscriptionSetting
|
||||
let getSetting: GetSetting
|
||||
|
||||
let request: express.Request
|
||||
let response: express.Response
|
||||
let user: User
|
||||
|
||||
const createController = () => new SubscriptionSettingsController(getSubscriptionSetting)
|
||||
const createController = () => new SubscriptionSettingsController(getSetting)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
|
||||
getSubscriptionSetting = {} as jest.Mocked<GetSubscriptionSetting>
|
||||
getSubscriptionSetting.execute = jest.fn()
|
||||
getSetting = {} as jest.Mocked<GetSetting>
|
||||
getSetting.execute = jest.fn()
|
||||
|
||||
request = {
|
||||
headers: {},
|
||||
@@ -41,12 +41,12 @@ describe('SubscriptionSettingsController', () => {
|
||||
uuid: '1-2-3',
|
||||
}
|
||||
|
||||
getSubscriptionSetting.execute = jest.fn().mockReturnValue({ success: true })
|
||||
getSetting.execute = jest.fn().mockReturnValue({ success: true })
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().getSubscriptionSetting(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(getSubscriptionSetting.execute).toHaveBeenCalledWith({ userUuid: '1-2-3', subscriptionSettingName: 'test' })
|
||||
expect(getSetting.execute).toHaveBeenCalledWith({ userUuid: '1-2-3', settingName: 'TEST' })
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
})
|
||||
@@ -58,12 +58,12 @@ describe('SubscriptionSettingsController', () => {
|
||||
uuid: '1-2-3',
|
||||
}
|
||||
|
||||
getSubscriptionSetting.execute = jest.fn().mockReturnValue({ success: false })
|
||||
getSetting.execute = jest.fn().mockReturnValue({ success: false })
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().getSubscriptionSetting(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(getSubscriptionSetting.execute).toHaveBeenCalledWith({ userUuid: '1-2-3', subscriptionSettingName: 'test' })
|
||||
expect(getSetting.execute).toHaveBeenCalledWith({ userUuid: '1-2-3', settingName: 'TEST' })
|
||||
|
||||
expect(result.statusCode).toEqual(400)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
@@ -9,19 +8,19 @@ import {
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { GetSubscriptionSetting } from '../Domain/UseCase/GetSubscriptionSetting/GetSubscriptionSetting'
|
||||
import { GetSetting } from '../Domain/UseCase/GetSetting/GetSetting'
|
||||
|
||||
@controller('/users/:userUuid')
|
||||
export class SubscriptionSettingsController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.GetSubscriptionSetting) private doGetSubscriptionSetting: GetSubscriptionSetting) {
|
||||
constructor(@inject(TYPES.GetSetting) private doGetSetting: GetSetting) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/subscription-settings/:subscriptionSettingName', TYPES.ApiGatewayAuthMiddleware)
|
||||
async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.doGetSubscriptionSetting.execute({
|
||||
const result = await this.doGetSetting.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
subscriptionSettingName: request.params.subscriptionSettingName as SubscriptionSettingName,
|
||||
settingName: request.params.subscriptionSettingName.toUpperCase(),
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
|
||||
import { ErrorTag } from '@standardnotes/api'
|
||||
import { ErrorTag } from '@standardnotes/responses'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
@@ -77,7 +77,7 @@ export class SubscriptionTokensController extends BaseHttpController {
|
||||
const user = authenticateTokenResponse.user as User
|
||||
let extensionKey = undefined
|
||||
const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.ExtensionKey,
|
||||
settingName: SettingName.create(SettingName.NAMES.ExtensionKey).getValue(),
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
if (extensionKeySetting !== null) {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
HttpStatusCode,
|
||||
UserRequestRequestParams,
|
||||
UserRequestResponse,
|
||||
UserRequestServerInterface,
|
||||
} from '@standardnotes/api'
|
||||
import { UserRequestRequestParams, UserRequestResponseBody, UserRequestServerInterface } from '@standardnotes/api'
|
||||
import { HttpResponse, HttpStatusCode } from '@standardnotes/responses'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
|
||||
@@ -12,7 +8,7 @@ import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/Process
|
||||
export class UserRequestsController implements UserRequestServerInterface {
|
||||
constructor(@inject(TYPES.ProcessUserRequest) private processUserRequest: ProcessUserRequest) {}
|
||||
|
||||
async submitUserRequest(params: UserRequestRequestParams): Promise<UserRequestResponse> {
|
||||
async submitUserRequest(params: UserRequestRequestParams): Promise<HttpResponse<UserRequestResponseBody>> {
|
||||
const result = await this.processUserRequest.execute({
|
||||
requestType: params.requestType,
|
||||
userEmail: params.userEmail as string,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import { ErrorTag } from '@standardnotes/api'
|
||||
import { ErrorTag } from '@standardnotes/responses'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import { CreateValetTokenPayload } from '@standardnotes/responses'
|
||||
import { ErrorTag } from '@standardnotes/api'
|
||||
import { CreateValetTokenPayload, ErrorTag } from '@standardnotes/responses'
|
||||
import { ValetTokenOperation } from '@standardnotes/security'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface AuthenticatorRepositoryInterface {
|
||||
findById(id: UniqueEntityId): Promise<Authenticator | null>
|
||||
findByUserUuidAndCredentialId(userUuid: Uuid, credentialId: string): Promise<Authenticator | null>
|
||||
save(authenticator: Authenticator): Promise<void>
|
||||
updateCounter(id: UniqueEntityId, counter: number): Promise<void>
|
||||
remove(authenticator: Authenticator): Promise<void>
|
||||
removeByUserUuid(userUuid: Uuid): Promise<void>
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ describe('FeatureService', () => {
|
||||
])
|
||||
|
||||
const nonSubscriptionRole = {
|
||||
name: RoleName.NAMES.FilesBetaUser,
|
||||
name: RoleName.NAMES.InternalTeamUser,
|
||||
uuid: 'role-files-beta',
|
||||
permissions: Promise.resolve([nonSubscriptionPermission]),
|
||||
} as jest.Mocked<Role>
|
||||
|
||||
@@ -27,13 +27,13 @@ export class EmailSubscriptionUnsubscribedEventHandler implements DomainEventHan
|
||||
private getSettingNameFromLevel(level: string): string {
|
||||
switch (level) {
|
||||
case EmailLevel.LEVELS.FailedCloudBackup:
|
||||
return SettingName.MuteFailedCloudBackupsEmails
|
||||
return SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
case EmailLevel.LEVELS.FailedEmailBackup:
|
||||
return SettingName.MuteFailedBackupsEmails
|
||||
return SettingName.NAMES.MuteFailedBackupsEmails
|
||||
case EmailLevel.LEVELS.Marketing:
|
||||
return SettingName.MuteMarketingEmails
|
||||
return SettingName.NAMES.MuteMarketingEmails
|
||||
case EmailLevel.LEVELS.SignIn:
|
||||
return SettingName.MuteSignInEmails
|
||||
return SettingName.NAMES.MuteSignInEmails
|
||||
default:
|
||||
throw new Error(`Unknown level: ${level}`)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export class ExtensionKeyGrantedEventHandler implements DomainEventHandlerInterf
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ExtensionKey,
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DomainEventHandlerInterface, FileRemovedEvent } from '@standardnotes/domain-events'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@@ -38,7 +38,7 @@ export class FileRemovedEventHandler implements DomainEventHandlerInterface {
|
||||
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||
userUuid: user.uuid,
|
||||
userSubscriptionUuid: subscription.uuid,
|
||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
subscriptionSettingName: SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
|
||||
})
|
||||
if (bytesUsedSetting === null) {
|
||||
this.logger.warn(`Could not find bytes used setting for user with uuid: ${user.uuid}`)
|
||||
@@ -51,7 +51,7 @@ export class FileRemovedEventHandler implements DomainEventHandlerInterface {
|
||||
await this.subscriptionSettingService.createOrReplace({
|
||||
userSubscription: subscription,
|
||||
props: {
|
||||
name: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||
unencryptedValue: (+bytesUsed - byteSize).toString(),
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DomainEventHandlerInterface, FileUploadedEvent } from '@standardnotes/domain-events'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@@ -47,7 +47,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
|
||||
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||
userUuid: (await subscription.user).uuid,
|
||||
userSubscriptionUuid: subscription.uuid,
|
||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
subscriptionSettingName: SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
|
||||
})
|
||||
if (bytesUsedSetting !== null) {
|
||||
bytesUsed = bytesUsedSetting.value as string
|
||||
@@ -56,7 +56,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
|
||||
await this.subscriptionSettingService.createOrReplace({
|
||||
userSubscription: subscription,
|
||||
props: {
|
||||
name: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||
unencryptedValue: (+bytesUsed + byteSize).toString(),
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter
|
||||
let authSecrets: ListedAuthorSecretsData = [newSecret]
|
||||
|
||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.ListedAuthorSecrets,
|
||||
settingName: SettingName.create(SettingName.NAMES.ListedAuthorSecrets).getValue(),
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
if (listedAuthorSecretsSetting !== null) {
|
||||
@@ -40,7 +40,7 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ListedAuthorSecrets,
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
unencryptedValue: JSON.stringify(authSecrets),
|
||||
sensitive: false,
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
||||
}
|
||||
|
||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.ListedAuthorSecrets,
|
||||
settingName: SettingName.create(SettingName.NAMES.ListedAuthorSecrets).getValue(),
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
if (listedAuthorSecretsSetting === null) {
|
||||
@@ -43,7 +43,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ListedAuthorSecrets,
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
unencryptedValue: JSON.stringify(filteredSecrets),
|
||||
sensitive: false,
|
||||
},
|
||||
|
||||
@@ -47,7 +47,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ExtensionKey,
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
|
||||
@@ -95,7 +95,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ExtensionKey,
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('RoleToSubscriptionMap', () => {
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
} as jest.Mocked<Role>,
|
||||
{
|
||||
name: RoleName.NAMES.FilesBetaUser,
|
||||
name: RoleName.NAMES.InternalTeamUser,
|
||||
} as jest.Mocked<Role>,
|
||||
{
|
||||
name: RoleName.NAMES.PlusUser,
|
||||
@@ -38,7 +38,7 @@ describe('RoleToSubscriptionMap', () => {
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
{
|
||||
name: RoleName.NAMES.FilesBetaUser,
|
||||
name: RoleName.NAMES.InternalTeamUser,
|
||||
},
|
||||
])
|
||||
})
|
||||
@@ -49,7 +49,7 @@ describe('RoleToSubscriptionMap', () => {
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
} as jest.Mocked<Role>,
|
||||
{
|
||||
name: RoleName.NAMES.FilesBetaUser,
|
||||
name: RoleName.NAMES.InternalTeamUser,
|
||||
} as jest.Mocked<Role>,
|
||||
{
|
||||
name: RoleName.NAMES.PlusUser,
|
||||
|
||||
@@ -12,7 +12,7 @@ export class RoleToSubscriptionMap implements RoleToSubscriptionMapInterface {
|
||||
[RoleName.NAMES.ProUser, SubscriptionName.ProPlan],
|
||||
])
|
||||
|
||||
private readonly nonSubscriptionRoles = [RoleName.NAMES.CoreUser, RoleName.NAMES.FilesBetaUser]
|
||||
private readonly nonSubscriptionRoles = [RoleName.NAMES.CoreUser, RoleName.NAMES.InternalTeamUser]
|
||||
|
||||
filterNonSubscriptionRoles(roles: Role[]): Array<Role> {
|
||||
return roles.filter((role) => this.nonSubscriptionRoles.includes(role.name))
|
||||
|
||||
@@ -12,4 +12,5 @@ export interface SessionRepositoryInterface {
|
||||
save(session: Session): Promise<Session>
|
||||
remove(session: Session): Promise<Session>
|
||||
clearUserAgentByUserUuid(userUuid: string): Promise<void>
|
||||
removeExpiredBefore(date: Date): Promise<void>
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ describe('SessionService', () => {
|
||||
let cryptoNode: CryptoNode
|
||||
let traceSession: TraceSession
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
const readonlyUsers = ['demo@standardnotes.com']
|
||||
|
||||
const createService = () =>
|
||||
new SessionService(
|
||||
@@ -49,6 +50,7 @@ describe('SessionService', () => {
|
||||
cryptoNode,
|
||||
traceSession,
|
||||
userSubscriptionRepository,
|
||||
readonlyUsers,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -59,6 +61,7 @@ describe('SessionService', () => {
|
||||
session.apiVersion = ApiVersion.v20200115
|
||||
session.hashedAccessToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
||||
session.hashedRefreshToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
||||
session.readonlyAccess = false
|
||||
|
||||
revokedSession = {} as jest.Mocked<RevokedSession>
|
||||
revokedSession.uuid = '2e1e43'
|
||||
@@ -182,6 +185,42 @@ describe('SessionService', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should create new readonly session for a user that is readonly restricted', async () => {
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.email = 'demo@standardnotes.com'
|
||||
user.uuid = '123'
|
||||
|
||||
const sessionPayload = await createService().createNewSessionForUser({
|
||||
user,
|
||||
apiVersion: '003',
|
||||
userAgent: 'Google Chrome',
|
||||
readonlyAccess: false,
|
||||
})
|
||||
|
||||
expect(sessionRepository.save).toHaveBeenCalledWith(expect.any(Session))
|
||||
expect(sessionRepository.save).toHaveBeenCalledWith({
|
||||
accessExpiration: expect.any(Date),
|
||||
apiVersion: '003',
|
||||
createdAt: expect.any(Date),
|
||||
hashedAccessToken: expect.any(String),
|
||||
hashedRefreshToken: expect.any(String),
|
||||
refreshExpiration: expect.any(Date),
|
||||
updatedAt: expect.any(Date),
|
||||
userAgent: 'Google Chrome',
|
||||
userUuid: '123',
|
||||
uuid: expect.any(String),
|
||||
readonlyAccess: true,
|
||||
})
|
||||
|
||||
expect(sessionPayload).toEqual({
|
||||
access_expiration: 123,
|
||||
access_token: expect.any(String),
|
||||
refresh_expiration: 123,
|
||||
refresh_token: expect.any(String),
|
||||
readonly_access: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create new session for a user with disabled user agent logging', async () => {
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
@@ -409,9 +448,9 @@ describe('SessionService', () => {
|
||||
})
|
||||
|
||||
it('should determine if a refresh token is valid', async () => {
|
||||
expect(createService().isRefreshTokenValid(session, '1:2:3')).toBeTruthy()
|
||||
expect(createService().isRefreshTokenValid(session, '1:2:4')).toBeFalsy()
|
||||
expect(createService().isRefreshTokenValid(session, '1:2')).toBeFalsy()
|
||||
expect(createService().isRefreshTokenMatchingHashedSessionToken(session, '1:2:3')).toBeTruthy()
|
||||
expect(createService().isRefreshTokenMatchingHashedSessionToken(session, '1:2:4')).toBeFalsy()
|
||||
expect(createService().isRefreshTokenMatchingHashedSessionToken(session, '1:2')).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should return device info based on user agent', () => {
|
||||
|
||||
@@ -39,6 +39,7 @@ export class SessionService implements SessionServiceInterface {
|
||||
@inject(TYPES.CryptoNode) private cryptoNode: CryptoNode,
|
||||
@inject(TYPES.TraceSession) private traceSession: TraceSession,
|
||||
@inject(TYPES.UserSubscriptionRepository) private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.READONLY_USERS) private readonlyUsers: string[],
|
||||
) {}
|
||||
|
||||
async createNewSessionForUser(dto: {
|
||||
@@ -113,7 +114,7 @@ export class SessionService implements SessionServiceInterface {
|
||||
return sessionPayload
|
||||
}
|
||||
|
||||
isRefreshTokenValid(session: Session, token: string): boolean {
|
||||
isRefreshTokenMatchingHashedSessionToken(session: Session, token: string): boolean {
|
||||
const tokenParts = token.split(':')
|
||||
const refreshToken = tokenParts[2]
|
||||
if (!refreshToken) {
|
||||
@@ -268,7 +269,9 @@ export class SessionService implements SessionServiceInterface {
|
||||
session.apiVersion = dto.apiVersion
|
||||
session.createdAt = this.timer.getUTCDate()
|
||||
session.updatedAt = this.timer.getUTCDate()
|
||||
session.readonlyAccess = dto.readonlyAccess
|
||||
|
||||
const userIsReadonly = this.readonlyUsers.includes(dto.user.email)
|
||||
session.readonlyAccess = userIsReadonly || dto.readonlyAccess
|
||||
|
||||
return session
|
||||
}
|
||||
@@ -302,13 +305,13 @@ export class SessionService implements SessionServiceInterface {
|
||||
refresh_token: `${SessionService.SESSION_TOKEN_VERSION}:${session.uuid}:${refreshToken}`,
|
||||
access_expiration: this.timer.convertStringDateToMilliseconds(accessTokenExpiration.toString()),
|
||||
refresh_expiration: this.timer.convertStringDateToMilliseconds(refreshTokenExpiration.toString()),
|
||||
readonly_access: false,
|
||||
readonly_access: session.readonlyAccess,
|
||||
}
|
||||
}
|
||||
|
||||
private async isLoggingUserAgentEnabledOnSessions(user: User): Promise<boolean> {
|
||||
const loggingSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.LogSessionUserAgent,
|
||||
settingName: SettingName.create(SettingName.NAMES.LogSessionUserAgent).getValue(),
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface SessionServiceInterface {
|
||||
getRevokedSessionFromToken(token: string): Promise<RevokedSession | null>
|
||||
markRevokedSessionAsReceived(revokedSession: RevokedSession): Promise<RevokedSession>
|
||||
deleteSessionByToken(token: string): Promise<string | null>
|
||||
isRefreshTokenValid(session: Session, token: string): boolean
|
||||
isRefreshTokenMatchingHashedSessionToken(session: Session, token: string): boolean
|
||||
getDeviceInfo(session: Session): string
|
||||
getOperatingSystemInfoFromUserAgent(userAgent: string): string
|
||||
getBrowserInfoFromUserAgent(userAgent: string): string
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
|
||||
export type FindSubscriptionSettingDTO = {
|
||||
userUuid: string
|
||||
userSubscriptionUuid: string
|
||||
subscriptionSettingName: SubscriptionSettingName
|
||||
subscriptionSettingName: SettingName
|
||||
settingUuid?: string
|
||||
}
|
||||
|
||||
@@ -70,12 +70,11 @@ describe('SettingInterpreter', () => {
|
||||
})
|
||||
|
||||
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
|
||||
const setting = {
|
||||
name: SettingName.LogSessionUserAgent,
|
||||
value: LogSessionUserAgentOption.Disabled,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, LogSessionUserAgentOption.Disabled)
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
user,
|
||||
LogSessionUserAgentOption.Disabled,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
|
||||
@@ -85,55 +84,50 @@ describe('SettingInterpreter', () => {
|
||||
})
|
||||
|
||||
it('should trigger backup if email backup setting is created - emails not muted', async () => {
|
||||
const setting = {
|
||||
name: SettingName.EmailBackupFrequency,
|
||||
value: EmailBackupFrequency.Daily,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily)
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
user,
|
||||
EmailBackupFrequency.Daily,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '', false)
|
||||
})
|
||||
|
||||
it('should trigger backup if email backup setting is created - emails muted', async () => {
|
||||
const setting = {
|
||||
name: SettingName.EmailBackupFrequency,
|
||||
value: EmailBackupFrequency.Daily,
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||
name: SettingName.MuteFailedBackupsEmails,
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
uuid: '6-7-8',
|
||||
value: 'muted',
|
||||
} as jest.Mocked<Setting>)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily)
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
user,
|
||||
EmailBackupFrequency.Daily,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '6-7-8', true)
|
||||
})
|
||||
|
||||
it('should not trigger backup if email backup setting is disabled', async () => {
|
||||
const setting = {
|
||||
name: SettingName.EmailBackupFrequency,
|
||||
value: EmailBackupFrequency.Disabled,
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Disabled)
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
user,
|
||||
EmailBackupFrequency.Disabled,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should trigger cloud backup if dropbox backup setting is created', async () => {
|
||||
const setting = {
|
||||
name: SettingName.DropboxBackupToken,
|
||||
value: 'test-token',
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'test-token')
|
||||
await createInterpreter().interpretSettingUpdated(SettingName.NAMES.DropboxBackupToken, user, 'test-token')
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createCloudBackupRequestedEvent).toHaveBeenCalledWith(
|
||||
@@ -146,17 +140,13 @@ describe('SettingInterpreter', () => {
|
||||
})
|
||||
|
||||
it('should trigger cloud backup if dropbox backup setting is created - muted emails', async () => {
|
||||
const setting = {
|
||||
name: SettingName.DropboxBackupToken,
|
||||
value: 'test-token',
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||
name: SettingName.MuteFailedCloudBackupsEmails,
|
||||
name: SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
uuid: '6-7-8',
|
||||
value: 'muted',
|
||||
} as jest.Mocked<Setting>)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'test-token')
|
||||
await createInterpreter().interpretSettingUpdated(SettingName.NAMES.DropboxBackupToken, user, 'test-token')
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createCloudBackupRequestedEvent).toHaveBeenCalledWith(
|
||||
@@ -169,13 +159,9 @@ describe('SettingInterpreter', () => {
|
||||
})
|
||||
|
||||
it('should trigger cloud backup if google drive backup setting is created', async () => {
|
||||
const setting = {
|
||||
name: SettingName.GoogleDriveBackupToken,
|
||||
value: 'test-token',
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'test-token')
|
||||
await createInterpreter().interpretSettingUpdated(SettingName.NAMES.GoogleDriveBackupToken, user, 'test-token')
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createCloudBackupRequestedEvent).toHaveBeenCalledWith(
|
||||
@@ -188,13 +174,9 @@ describe('SettingInterpreter', () => {
|
||||
})
|
||||
|
||||
it('should trigger cloud backup if one drive backup setting is created', async () => {
|
||||
const setting = {
|
||||
name: SettingName.OneDriveBackupToken,
|
||||
value: 'test-token',
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'test-token')
|
||||
await createInterpreter().interpretSettingUpdated(SettingName.NAMES.OneDriveBackupToken, user, 'test-token')
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createCloudBackupRequestedEvent).toHaveBeenCalledWith(
|
||||
@@ -207,13 +189,13 @@ describe('SettingInterpreter', () => {
|
||||
})
|
||||
|
||||
it('should trigger mute subscription emails rejection if mute setting changed', async () => {
|
||||
const setting = {
|
||||
name: SettingName.MuteMarketingEmails,
|
||||
value: MuteMarketingEmailsOption.Muted,
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, MuteMarketingEmailsOption.Muted)
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
user,
|
||||
MuteMarketingEmailsOption.Muted,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createMuteEmailsSettingChangedEvent).toHaveBeenCalledWith({
|
||||
@@ -225,19 +207,13 @@ describe('SettingInterpreter', () => {
|
||||
|
||||
it('should trigger cloud backup if backup frequency setting is updated and a backup token setting is present', async () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
||||
name: SettingName.OneDriveBackupToken,
|
||||
name: SettingName.NAMES.OneDriveBackupToken,
|
||||
serverEncryptionVersion: 1,
|
||||
value: 'encrypted-backup-token',
|
||||
sensitive: true,
|
||||
} as jest.Mocked<Setting>)
|
||||
const setting = {
|
||||
name: SettingName.OneDriveBackupFrequency,
|
||||
serverEncryptionVersion: 0,
|
||||
value: 'daily',
|
||||
sensitive: false,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'daily')
|
||||
await createInterpreter().interpretSettingUpdated(SettingName.NAMES.OneDriveBackupFrequency, user, 'daily')
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createCloudBackupRequestedEvent).toHaveBeenCalledWith(
|
||||
@@ -251,19 +227,17 @@ describe('SettingInterpreter', () => {
|
||||
|
||||
it('should not trigger cloud backup if backup frequency setting is updated as disabled', async () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
||||
name: SettingName.OneDriveBackupToken,
|
||||
name: SettingName.NAMES.OneDriveBackupToken,
|
||||
serverEncryptionVersion: 1,
|
||||
value: 'encrypted-backup-token',
|
||||
sensitive: true,
|
||||
} as jest.Mocked<Setting>)
|
||||
const setting = {
|
||||
name: SettingName.OneDriveBackupFrequency,
|
||||
serverEncryptionVersion: 0,
|
||||
value: OneDriveBackupFrequency.Disabled,
|
||||
sensitive: false,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, OneDriveBackupFrequency.Disabled)
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.OneDriveBackupFrequency,
|
||||
user,
|
||||
OneDriveBackupFrequency.Disabled,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createCloudBackupRequestedEvent).not.toHaveBeenCalled()
|
||||
@@ -271,14 +245,8 @@ describe('SettingInterpreter', () => {
|
||||
|
||||
it('should not trigger cloud backup if backup frequency setting is updated and a backup token setting is not present', async () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce(null)
|
||||
const setting = {
|
||||
name: SettingName.OneDriveBackupFrequency,
|
||||
serverEncryptionVersion: 0,
|
||||
value: 'daily',
|
||||
sensitive: false,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'daily')
|
||||
await createInterpreter().interpretSettingUpdated(SettingName.NAMES.OneDriveBackupFrequency, user, 'daily')
|
||||
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createCloudBackupRequestedEvent).not.toHaveBeenCalled()
|
||||
|
||||
@@ -15,7 +15,6 @@ import { Logger } from 'winston'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingDecrypterInterface } from './SettingDecrypterInterface'
|
||||
import { SettingInterpreterInterface } from './SettingInterpreterInterface'
|
||||
import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
||||
@@ -23,15 +22,15 @@ import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
||||
@injectable()
|
||||
export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
private readonly cloudBackupTokenSettings = [
|
||||
SettingName.DropboxBackupToken,
|
||||
SettingName.GoogleDriveBackupToken,
|
||||
SettingName.OneDriveBackupToken,
|
||||
SettingName.NAMES.DropboxBackupToken,
|
||||
SettingName.NAMES.GoogleDriveBackupToken,
|
||||
SettingName.NAMES.OneDriveBackupToken,
|
||||
]
|
||||
|
||||
private readonly cloudBackupFrequencySettings = [
|
||||
SettingName.DropboxBackupFrequency,
|
||||
SettingName.GoogleDriveBackupFrequency,
|
||||
SettingName.OneDriveBackupFrequency,
|
||||
SettingName.NAMES.DropboxBackupFrequency,
|
||||
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||
SettingName.NAMES.OneDriveBackupFrequency,
|
||||
]
|
||||
|
||||
private readonly cloudBackupFrequencyDisabledValues = [
|
||||
@@ -40,11 +39,11 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
OneDriveBackupFrequency.Disabled,
|
||||
]
|
||||
|
||||
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<SettingName, string> = new Map([
|
||||
[SettingName.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
|
||||
[SettingName.MuteFailedCloudBackupsEmails, EmailLevel.LEVELS.FailedCloudBackup],
|
||||
[SettingName.MuteMarketingEmails, EmailLevel.LEVELS.Marketing],
|
||||
[SettingName.MuteSignInEmails, EmailLevel.LEVELS.SignIn],
|
||||
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<string, string> = new Map([
|
||||
[SettingName.NAMES.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
|
||||
[SettingName.NAMES.MuteFailedCloudBackupsEmails, EmailLevel.LEVELS.FailedCloudBackup],
|
||||
[SettingName.NAMES.MuteMarketingEmails, EmailLevel.LEVELS.Marketing],
|
||||
[SettingName.NAMES.MuteSignInEmails, EmailLevel.LEVELS.SignIn],
|
||||
])
|
||||
|
||||
constructor(
|
||||
@@ -55,20 +54,24 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async interpretSettingUpdated(updatedSetting: Setting, user: User, unencryptedValue: string | null): Promise<void> {
|
||||
if (this.isChangingMuteEmailsSetting(updatedSetting)) {
|
||||
await this.triggerEmailSubscriptionChange(user, updatedSetting.name as SettingName, unencryptedValue)
|
||||
async interpretSettingUpdated(
|
||||
updatedSettingName: string,
|
||||
user: User,
|
||||
unencryptedValue: string | null,
|
||||
): Promise<void> {
|
||||
if (this.isChangingMuteEmailsSetting(updatedSettingName)) {
|
||||
await this.triggerEmailSubscriptionChange(user, updatedSettingName, unencryptedValue)
|
||||
}
|
||||
|
||||
if (this.isEnablingEmailBackupSetting(updatedSetting)) {
|
||||
if (this.isEnablingEmailBackupSetting(updatedSettingName, unencryptedValue)) {
|
||||
await this.triggerEmailBackup(user.uuid)
|
||||
}
|
||||
|
||||
if (this.isEnablingCloudBackupSetting(updatedSetting)) {
|
||||
await this.triggerCloudBackup(updatedSetting, user.uuid, unencryptedValue)
|
||||
if (this.isEnablingCloudBackupSetting(updatedSettingName, unencryptedValue)) {
|
||||
await this.triggerCloudBackup(updatedSettingName, user.uuid, unencryptedValue)
|
||||
}
|
||||
|
||||
if (this.isDisablingSessionUserAgentLogging(updatedSetting)) {
|
||||
if (this.isDisablingSessionUserAgentLogging(updatedSettingName, unencryptedValue)) {
|
||||
await this.triggerSessionUserAgentCleanup(user)
|
||||
}
|
||||
}
|
||||
@@ -77,7 +80,7 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
let userHasEmailsMuted = false
|
||||
let muteEmailsSettingUuid = ''
|
||||
const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||
SettingName.MuteFailedBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
userUuid,
|
||||
)
|
||||
if (muteFailedEmailsBackupSetting !== null) {
|
||||
@@ -90,36 +93,39 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
)
|
||||
}
|
||||
|
||||
private isChangingMuteEmailsSetting(setting: Setting): boolean {
|
||||
private isChangingMuteEmailsSetting(settingName: string): boolean {
|
||||
return [
|
||||
SettingName.MuteFailedBackupsEmails,
|
||||
SettingName.MuteFailedCloudBackupsEmails,
|
||||
SettingName.MuteMarketingEmails,
|
||||
SettingName.MuteSignInEmails,
|
||||
].includes(setting.name as SettingName)
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
].includes(settingName)
|
||||
}
|
||||
|
||||
private isEnablingEmailBackupSetting(setting: Setting): boolean {
|
||||
return setting.name === SettingName.EmailBackupFrequency && setting.value !== EmailBackupFrequency.Disabled
|
||||
}
|
||||
|
||||
private isEnablingCloudBackupSetting(setting: Setting): boolean {
|
||||
private isEnablingEmailBackupSetting(settingName: string, newValue: string | null): boolean {
|
||||
return (
|
||||
(this.cloudBackupFrequencySettings.includes(setting.name as SettingName) ||
|
||||
this.cloudBackupTokenSettings.includes(setting.name as SettingName)) &&
|
||||
settingName === SettingName.NAMES.EmailBackupFrequency &&
|
||||
[EmailBackupFrequency.Daily, EmailBackupFrequency.Weekly].includes(newValue as EmailBackupFrequency)
|
||||
)
|
||||
}
|
||||
|
||||
private isEnablingCloudBackupSetting(settingName: string, newValue: string | null): boolean {
|
||||
return (
|
||||
(this.cloudBackupFrequencySettings.includes(settingName) ||
|
||||
this.cloudBackupTokenSettings.includes(settingName)) &&
|
||||
!this.cloudBackupFrequencyDisabledValues.includes(
|
||||
setting.value as DropboxBackupFrequency | OneDriveBackupFrequency | GoogleDriveBackupFrequency,
|
||||
newValue as DropboxBackupFrequency | OneDriveBackupFrequency | GoogleDriveBackupFrequency,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private isDisablingSessionUserAgentLogging(setting: Setting): boolean {
|
||||
return SettingName.LogSessionUserAgent === setting.name && LogSessionUserAgentOption.Disabled === setting.value
|
||||
private isDisablingSessionUserAgentLogging(settingName: string, newValue: string | null): boolean {
|
||||
return SettingName.NAMES.LogSessionUserAgent === settingName && LogSessionUserAgentOption.Disabled === newValue
|
||||
}
|
||||
|
||||
private async triggerEmailSubscriptionChange(
|
||||
user: User,
|
||||
settingName: SettingName,
|
||||
settingName: string,
|
||||
unencryptedValue: string | null,
|
||||
): Promise<void> {
|
||||
await this.domainEventPublisher.publish(
|
||||
@@ -140,33 +146,34 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
)
|
||||
}
|
||||
|
||||
private async triggerCloudBackup(setting: Setting, userUuid: string, unencryptedValue: string | null): Promise<void> {
|
||||
private async triggerCloudBackup(
|
||||
settingName: string,
|
||||
userUuid: string,
|
||||
unencryptedValue: string | null,
|
||||
): Promise<void> {
|
||||
let cloudProvider
|
||||
let tokenSettingName
|
||||
switch (setting.name) {
|
||||
case SettingName.DropboxBackupToken:
|
||||
case SettingName.DropboxBackupFrequency:
|
||||
switch (settingName) {
|
||||
case SettingName.NAMES.DropboxBackupToken:
|
||||
case SettingName.NAMES.DropboxBackupFrequency:
|
||||
cloudProvider = 'DROPBOX'
|
||||
tokenSettingName = SettingName.DropboxBackupToken
|
||||
tokenSettingName = SettingName.NAMES.DropboxBackupToken
|
||||
break
|
||||
case SettingName.GoogleDriveBackupToken:
|
||||
case SettingName.GoogleDriveBackupFrequency:
|
||||
case SettingName.NAMES.GoogleDriveBackupToken:
|
||||
case SettingName.NAMES.GoogleDriveBackupFrequency:
|
||||
cloudProvider = 'GOOGLE_DRIVE'
|
||||
tokenSettingName = SettingName.GoogleDriveBackupToken
|
||||
tokenSettingName = SettingName.NAMES.GoogleDriveBackupToken
|
||||
break
|
||||
case SettingName.OneDriveBackupToken:
|
||||
case SettingName.OneDriveBackupFrequency:
|
||||
case SettingName.NAMES.OneDriveBackupToken:
|
||||
case SettingName.NAMES.OneDriveBackupFrequency:
|
||||
cloudProvider = 'ONE_DRIVE'
|
||||
tokenSettingName = SettingName.OneDriveBackupToken
|
||||
tokenSettingName = SettingName.NAMES.OneDriveBackupToken
|
||||
break
|
||||
}
|
||||
|
||||
let backupToken = null
|
||||
if (this.cloudBackupFrequencySettings.includes(setting.name as SettingName)) {
|
||||
const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(
|
||||
tokenSettingName as SettingName,
|
||||
userUuid,
|
||||
)
|
||||
if (this.cloudBackupFrequencySettings.includes(settingName)) {
|
||||
const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(tokenSettingName as string, userUuid)
|
||||
if (tokenSetting !== null) {
|
||||
backupToken = await this.settingDecrypter.decryptSettingValue(tokenSetting, userUuid)
|
||||
}
|
||||
@@ -183,7 +190,7 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
let userHasEmailsMuted = false
|
||||
let muteEmailsSettingUuid = ''
|
||||
const muteFailedCloudBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||
SettingName.MuteFailedCloudBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
userUuid,
|
||||
)
|
||||
if (muteFailedCloudBackupSetting !== null) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from './Setting'
|
||||
|
||||
export interface SettingInterpreterInterface {
|
||||
interpretSettingUpdated(updatedSetting: Setting, user: User, newUnencryptedValue: string | null): Promise<void>
|
||||
interpretSettingUpdated(updatedSettingName: string, user: User, newUnencryptedValue: string | null): Promise<void>
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ describe('SettingService', () => {
|
||||
} as jest.Mocked<User>
|
||||
user.isPotentiallyAVaultAccount = jest.fn().mockReturnValue(false)
|
||||
|
||||
setting = {} as jest.Mocked<Setting>
|
||||
setting = {
|
||||
name: SettingName.NAMES.DropboxBackupToken,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
factory = {} as jest.Mocked<SettingFactoryInterface>
|
||||
factory.create = jest.fn().mockReturnValue(setting)
|
||||
@@ -54,7 +56,7 @@ describe('SettingService', () => {
|
||||
settingsAssociationService.getDefaultSettingsAndValuesForNewUser = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SettingName.MuteSignInEmails,
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
{
|
||||
value: MuteSignInEmailsOption.NotMuted,
|
||||
sensitive: 0,
|
||||
@@ -67,7 +69,7 @@ describe('SettingService', () => {
|
||||
settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
@@ -107,7 +109,7 @@ describe('SettingService', () => {
|
||||
const result = await createService().createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: 'name',
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
@@ -117,6 +119,20 @@ describe('SettingService', () => {
|
||||
expect(result.status).toEqual('created')
|
||||
})
|
||||
|
||||
it('should throw error if setting name is not valid', async () => {
|
||||
await expect(
|
||||
createService().createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: 'invalid',
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrowError('Invalid setting name: invalid')
|
||||
})
|
||||
|
||||
it('should create setting with a given uuid if it does not exist', async () => {
|
||||
settingRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
@@ -124,7 +140,7 @@ describe('SettingService', () => {
|
||||
user,
|
||||
props: {
|
||||
uuid: '1-2-3',
|
||||
name: 'name',
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
@@ -174,7 +190,10 @@ describe('SettingService', () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||
|
||||
expect(
|
||||
await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' as SettingName }),
|
||||
await createService().findSettingWithDecryptedValue({
|
||||
userUuid: '1-2-3',
|
||||
settingName: SettingName.create(SettingName.NAMES.LogSessionUserAgent).getValue(),
|
||||
}),
|
||||
).toEqual({
|
||||
serverEncryptionVersion: 1,
|
||||
value: 'decrypted',
|
||||
|
||||
@@ -57,7 +57,7 @@ export class SettingService implements SettingServiceInterface {
|
||||
if (dto.settingUuid !== undefined) {
|
||||
setting = await this.settingRepository.findOneByUuid(dto.settingUuid)
|
||||
} else {
|
||||
setting = await this.settingRepository.findLastByNameAndUserUuid(dto.settingName, dto.userUuid)
|
||||
setting = await this.settingRepository.findLastByNameAndUserUuid(dto.settingName.value, dto.userUuid)
|
||||
}
|
||||
|
||||
if (setting === null) {
|
||||
@@ -72,9 +72,15 @@ export class SettingService implements SettingServiceInterface {
|
||||
async createOrReplace(dto: CreateOrReplaceSettingDto): Promise<CreateOrReplaceSettingResponse> {
|
||||
const { user, props } = dto
|
||||
|
||||
const settingNameOrError = SettingName.create(props.name)
|
||||
if (settingNameOrError.isFailed()) {
|
||||
throw new Error(settingNameOrError.getError())
|
||||
}
|
||||
const settingName = settingNameOrError.getValue()
|
||||
|
||||
const existing = await this.findSettingWithDecryptedValue({
|
||||
userUuid: user.uuid,
|
||||
settingName: props.name as SettingName,
|
||||
settingName,
|
||||
settingUuid: props.uuid,
|
||||
})
|
||||
|
||||
@@ -83,7 +89,7 @@ export class SettingService implements SettingServiceInterface {
|
||||
|
||||
this.logger.debug('[%s] Created setting %s: %O', user.uuid, props.name, setting)
|
||||
|
||||
await this.settingInterpreter.interpretSettingUpdated(setting, user, props.unencryptedValue)
|
||||
await this.settingInterpreter.interpretSettingUpdated(setting.name, user, props.unencryptedValue)
|
||||
|
||||
return {
|
||||
status: 'created',
|
||||
@@ -95,7 +101,7 @@ export class SettingService implements SettingServiceInterface {
|
||||
|
||||
this.logger.debug('[%s] Replaced existing setting %s with: %O', user.uuid, props.name, setting)
|
||||
|
||||
await this.settingInterpreter.interpretSettingUpdated(setting, user, props.unencryptedValue)
|
||||
await this.settingInterpreter.interpretSettingUpdated(setting.name, user, props.unencryptedValue)
|
||||
|
||||
return {
|
||||
status: 'replaced',
|
||||
|
||||
@@ -11,52 +11,68 @@ describe('SettingsAssociationService', () => {
|
||||
const createService = () => new SettingsAssociationService()
|
||||
|
||||
it('should tell if a setting is mutable by the client', () => {
|
||||
expect(createService().isSettingMutableByClient(SettingName.DropboxBackupFrequency)).toBeTruthy()
|
||||
expect(
|
||||
createService().isSettingMutableByClient(SettingName.create(SettingName.NAMES.DropboxBackupFrequency).getValue()),
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should tell if a setting is immutable by the client', () => {
|
||||
expect(createService().isSettingMutableByClient(SettingName.ListedAuthorSecrets)).toBeFalsy()
|
||||
expect(
|
||||
createService().isSettingMutableByClient(SettingName.create(SettingName.NAMES.ListedAuthorSecrets).getValue()),
|
||||
).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should return default encryption version for a setting which enecryption version is not strictly defined', () => {
|
||||
expect(createService().getEncryptionVersionForSetting(SettingName.MfaSecret)).toEqual(EncryptionVersion.Default)
|
||||
expect(
|
||||
createService().getEncryptionVersionForSetting(SettingName.create(SettingName.NAMES.MfaSecret).getValue()),
|
||||
).toEqual(EncryptionVersion.Default)
|
||||
})
|
||||
|
||||
it('should return a defined encryption version for a setting which enecryption version is strictly defined', () => {
|
||||
expect(createService().getEncryptionVersionForSetting(SettingName.EmailBackupFrequency)).toEqual(
|
||||
EncryptionVersion.Unencrypted,
|
||||
)
|
||||
expect(
|
||||
createService().getEncryptionVersionForSetting(
|
||||
SettingName.create(SettingName.NAMES.EmailBackupFrequency).getValue(),
|
||||
),
|
||||
).toEqual(EncryptionVersion.Unencrypted)
|
||||
})
|
||||
|
||||
it('should return default sensitivity for a setting which sensitivity is not strictly defined', () => {
|
||||
expect(createService().getSensitivityForSetting(SettingName.DropboxBackupToken)).toBeTruthy()
|
||||
expect(
|
||||
createService().getSensitivityForSetting(SettingName.create(SettingName.NAMES.DropboxBackupToken).getValue()),
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return a defined sensitivity for a setting which sensitivity is strictly defined', () => {
|
||||
expect(createService().getSensitivityForSetting(SettingName.DropboxBackupFrequency)).toBeFalsy()
|
||||
expect(
|
||||
createService().getSensitivityForSetting(SettingName.create(SettingName.NAMES.DropboxBackupFrequency).getValue()),
|
||||
).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should return the default set of settings for a newly registered user', () => {
|
||||
const settings = createService().getDefaultSettingsAndValuesForNewUser()
|
||||
const flatSettings = [...(settings as Map<SettingName, SettingDescription>).keys()]
|
||||
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||
const flatSettings = [...(settings as Map<string, SettingDescription>).keys()]
|
||||
expect(flatSettings).toEqual(['MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||
})
|
||||
|
||||
it('should return the default set of settings for a newly registered vault account', () => {
|
||||
const settings = createService().getDefaultSettingsAndValuesForNewVaultAccount()
|
||||
const flatSettings = [...(settings as Map<SettingName, SettingDescription>).keys()]
|
||||
expect(flatSettings).toEqual(['MUTE_SIGN_IN_EMAILS', 'MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||
const flatSettings = [...(settings as Map<string, SettingDescription>).keys()]
|
||||
expect(flatSettings).toEqual(['MUTE_MARKETING_EMAILS', 'LOG_SESSION_USER_AGENT'])
|
||||
|
||||
expect(settings.get(SettingName.LogSessionUserAgent)?.value).toEqual('disabled')
|
||||
expect(settings.get(SettingName.NAMES.LogSessionUserAgent)?.value).toEqual('disabled')
|
||||
})
|
||||
|
||||
it('should return a permission name associated to a given setting', () => {
|
||||
expect(createService().getPermissionAssociatedWithSetting(SettingName.EmailBackupFrequency)).toEqual(
|
||||
PermissionName.DailyEmailBackup,
|
||||
)
|
||||
expect(
|
||||
createService().getPermissionAssociatedWithSetting(
|
||||
SettingName.create(SettingName.NAMES.EmailBackupFrequency).getValue(),
|
||||
),
|
||||
).toEqual(PermissionName.DailyEmailBackup)
|
||||
})
|
||||
|
||||
it('should not return a permission name if not associated to a given setting', () => {
|
||||
expect(createService().getPermissionAssociatedWithSetting(SettingName.ExtensionKey)).toBeUndefined()
|
||||
expect(
|
||||
createService().getPermissionAssociatedWithSetting(SettingName.create(SettingName.NAMES.ExtensionKey).getValue()),
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import {
|
||||
LogSessionUserAgentOption,
|
||||
MuteMarketingEmailsOption,
|
||||
MuteSignInEmailsOption,
|
||||
SettingName,
|
||||
} from '@standardnotes/settings'
|
||||
import { LogSessionUserAgentOption, MuteMarketingEmailsOption, SettingName } from '@standardnotes/settings'
|
||||
import { injectable } from 'inversify'
|
||||
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
@@ -15,48 +10,44 @@ import { SettingsAssociationServiceInterface } from './SettingsAssociationServic
|
||||
@injectable()
|
||||
export class SettingsAssociationService implements SettingsAssociationServiceInterface {
|
||||
private readonly UNENCRYPTED_SETTINGS = [
|
||||
SettingName.EmailBackupFrequency,
|
||||
SettingName.MuteFailedBackupsEmails,
|
||||
SettingName.MuteFailedCloudBackupsEmails,
|
||||
SettingName.MuteSignInEmails,
|
||||
SettingName.MuteMarketingEmails,
|
||||
SettingName.DropboxBackupFrequency,
|
||||
SettingName.GoogleDriveBackupFrequency,
|
||||
SettingName.OneDriveBackupFrequency,
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
SettingName.NAMES.DropboxBackupFrequency,
|
||||
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||
SettingName.NAMES.OneDriveBackupFrequency,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
]
|
||||
|
||||
private readonly UNSENSITIVE_SETTINGS = [
|
||||
SettingName.DropboxBackupFrequency,
|
||||
SettingName.GoogleDriveBackupFrequency,
|
||||
SettingName.OneDriveBackupFrequency,
|
||||
SettingName.EmailBackupFrequency,
|
||||
SettingName.MuteFailedBackupsEmails,
|
||||
SettingName.MuteFailedCloudBackupsEmails,
|
||||
SettingName.MuteSignInEmails,
|
||||
SettingName.MuteMarketingEmails,
|
||||
SettingName.ListedAuthorSecrets,
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.DropboxBackupFrequency,
|
||||
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||
SettingName.NAMES.OneDriveBackupFrequency,
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
SettingName.NAMES.ListedAuthorSecrets,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
]
|
||||
|
||||
private readonly CLIENT_IMMUTABLE_SETTINGS = [SettingName.ListedAuthorSecrets]
|
||||
private readonly CLIENT_IMMUTABLE_SETTINGS = [
|
||||
SettingName.NAMES.ListedAuthorSecrets,
|
||||
SettingName.NAMES.FileUploadBytesLimit,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
]
|
||||
|
||||
private readonly permissionsAssociatedWithSettings = new Map<SettingName, PermissionName>([
|
||||
[SettingName.EmailBackupFrequency, PermissionName.DailyEmailBackup],
|
||||
private readonly permissionsAssociatedWithSettings = new Map<string, PermissionName>([
|
||||
[SettingName.NAMES.EmailBackupFrequency, PermissionName.DailyEmailBackup],
|
||||
[SettingName.NAMES.MuteSignInEmails, PermissionName.SignInAlerts],
|
||||
])
|
||||
|
||||
private readonly defaultSettings = new Map<SettingName, SettingDescription>([
|
||||
private readonly defaultSettings = new Map<string, SettingDescription>([
|
||||
[
|
||||
SettingName.MuteSignInEmails,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: MuteSignInEmailsOption.NotMuted,
|
||||
replaceable: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
SettingName.MuteMarketingEmails,
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
@@ -65,7 +56,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
},
|
||||
],
|
||||
[
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
@@ -75,9 +66,9 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
],
|
||||
])
|
||||
|
||||
private readonly vaultAccountDefaultSettingsOverwrites = new Map<SettingName, SettingDescription>([
|
||||
private readonly vaultAccountDefaultSettingsOverwrites = new Map<string, SettingDescription>([
|
||||
[
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
@@ -88,7 +79,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
])
|
||||
|
||||
isSettingMutableByClient(settingName: SettingName): boolean {
|
||||
if (this.CLIENT_IMMUTABLE_SETTINGS.includes(settingName)) {
|
||||
if (this.CLIENT_IMMUTABLE_SETTINGS.includes(settingName.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -96,7 +87,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
}
|
||||
|
||||
getSensitivityForSetting(settingName: SettingName): boolean {
|
||||
if (this.UNSENSITIVE_SETTINGS.includes(settingName)) {
|
||||
if (this.UNSENSITIVE_SETTINGS.includes(settingName.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -104,7 +95,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
}
|
||||
|
||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion {
|
||||
if (this.UNENCRYPTED_SETTINGS.includes(settingName)) {
|
||||
if (this.UNENCRYPTED_SETTINGS.includes(settingName.value)) {
|
||||
return EncryptionVersion.Unencrypted
|
||||
}
|
||||
|
||||
@@ -112,18 +103,18 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
}
|
||||
|
||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined {
|
||||
if (!this.permissionsAssociatedWithSettings.has(settingName)) {
|
||||
if (!this.permissionsAssociatedWithSettings.has(settingName.value)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return this.permissionsAssociatedWithSettings.get(settingName)
|
||||
return this.permissionsAssociatedWithSettings.get(settingName.value)
|
||||
}
|
||||
|
||||
getDefaultSettingsAndValuesForNewUser(): Map<SettingName, SettingDescription> {
|
||||
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription> {
|
||||
return this.defaultSettings
|
||||
}
|
||||
|
||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<SettingName, SettingDescription> {
|
||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription> {
|
||||
const defaultVaultSettings = new Map(this.defaultSettings)
|
||||
|
||||
for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { SettingName, SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { SettingDescription } from './SettingDescription'
|
||||
|
||||
export interface SettingsAssociationServiceInterface {
|
||||
getDefaultSettingsAndValuesForNewUser(): Map<SettingName, SettingDescription>
|
||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<SettingName, SettingDescription>
|
||||
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription>
|
||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription>
|
||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined
|
||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion
|
||||
getSensitivityForSetting(settingName: SettingName): boolean
|
||||
isSettingMutableByClient(settingName: SettingName | SubscriptionSettingName): boolean
|
||||
isSettingMutableByClient(settingName: SettingName): boolean
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@ import { SubscriptionSetting } from './SubscriptionSetting'
|
||||
export interface SubscriptionSettingRepositoryInterface {
|
||||
findOneByUuid(uuid: string): Promise<SubscriptionSetting | null>
|
||||
findLastByNameAndUserSubscriptionUuid(name: string, userSubscriptionUuid: string): Promise<SubscriptionSetting | null>
|
||||
findAllBySubscriptionUuid(userSubscriptionUuid: string): Promise<SubscriptionSetting[]>
|
||||
save(subscriptionSetting: SubscriptionSetting): Promise<SubscriptionSetting>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { Logger } from 'winston'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
|
||||
@@ -14,6 +13,8 @@ import { User } from '../User/User'
|
||||
import { SettingFactoryInterface } from './SettingFactoryInterface'
|
||||
import { SubscriptionSettingsAssociationServiceInterface } from './SubscriptionSettingsAssociationServiceInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingInterpreterInterface } from './SettingInterpreterInterface'
|
||||
|
||||
describe('SubscriptionSettingService', () => {
|
||||
let setting: SubscriptionSetting
|
||||
@@ -22,6 +23,7 @@ describe('SubscriptionSettingService', () => {
|
||||
let factory: SettingFactoryInterface
|
||||
let subscriptionSettingRepository: SubscriptionSettingRepositoryInterface
|
||||
let subscriptionSettingsAssociationService: SubscriptionSettingsAssociationServiceInterface
|
||||
let settingInterpreter: SettingInterpreterInterface
|
||||
let settingDecrypter: SettingDecrypterInterface
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let logger: Logger
|
||||
@@ -31,6 +33,7 @@ describe('SubscriptionSettingService', () => {
|
||||
factory,
|
||||
subscriptionSettingRepository,
|
||||
subscriptionSettingsAssociationService,
|
||||
settingInterpreter,
|
||||
settingDecrypter,
|
||||
userSubscriptionRepository,
|
||||
logger,
|
||||
@@ -44,7 +47,9 @@ describe('SubscriptionSettingService', () => {
|
||||
user: Promise.resolve(user),
|
||||
} as jest.Mocked<UserSubscription>
|
||||
|
||||
setting = {} as jest.Mocked<SubscriptionSetting>
|
||||
setting = {
|
||||
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||
} as jest.Mocked<SubscriptionSetting>
|
||||
|
||||
factory = {} as jest.Mocked<SettingFactoryInterface>
|
||||
factory.createSubscriptionSetting = jest.fn().mockReturnValue(setting)
|
||||
@@ -68,7 +73,7 @@ describe('SubscriptionSettingService', () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
@@ -79,6 +84,9 @@ describe('SubscriptionSettingService', () => {
|
||||
]),
|
||||
)
|
||||
|
||||
settingInterpreter = {} as jest.Mocked<SettingInterpreterInterface>
|
||||
settingInterpreter.interpretSettingUpdated = jest.fn()
|
||||
|
||||
settingDecrypter = {} as jest.Mocked<SettingDecrypterInterface>
|
||||
settingDecrypter.decryptSettingValue = jest.fn().mockReturnValue('decrypted')
|
||||
|
||||
@@ -98,11 +106,59 @@ describe('SubscriptionSettingService', () => {
|
||||
expect(subscriptionSettingRepository.save).toHaveBeenCalledWith(setting)
|
||||
})
|
||||
|
||||
it('should throw error if subscription setting is invalid', async () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
'invalid',
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
replaceable: true,
|
||||
},
|
||||
],
|
||||
]),
|
||||
)
|
||||
|
||||
await expect(
|
||||
createService().applyDefaultSubscriptionSettingsForSubscription(
|
||||
userSubscription,
|
||||
SubscriptionName.PlusPlan,
|
||||
'1-2-3',
|
||||
),
|
||||
).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('should throw error if setting name is not a subscription setting when applying defaults', async () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SettingName.NAMES.DropboxBackupFrequency,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
replaceable: false,
|
||||
},
|
||||
],
|
||||
]),
|
||||
)
|
||||
|
||||
await expect(
|
||||
createService().applyDefaultSubscriptionSettingsForSubscription(
|
||||
userSubscription,
|
||||
SubscriptionName.PlusPlan,
|
||||
'1-2-3',
|
||||
),
|
||||
).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('should reassign existing default settings for a subscription if it is not replaceable', async () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
@@ -127,7 +183,7 @@ describe('SubscriptionSettingService', () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
@@ -152,7 +208,7 @@ describe('SubscriptionSettingService', () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
@@ -196,7 +252,7 @@ describe('SubscriptionSettingService', () => {
|
||||
const result = await createService().createOrReplace({
|
||||
userSubscription,
|
||||
props: {
|
||||
name: 'name',
|
||||
name: SettingName.NAMES.FileUploadBytesLimit,
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
@@ -206,6 +262,34 @@ describe('SubscriptionSettingService', () => {
|
||||
expect(result.status).toEqual('created')
|
||||
})
|
||||
|
||||
it('should throw error if the setting name is not valid', async () => {
|
||||
await expect(
|
||||
createService().createOrReplace({
|
||||
userSubscription,
|
||||
props: {
|
||||
name: 'invalid',
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('should throw error if the setting name is not a subscription setting', async () => {
|
||||
await expect(
|
||||
createService().createOrReplace({
|
||||
userSubscription,
|
||||
props: {
|
||||
name: SettingName.NAMES.DropboxBackupFrequency,
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('should create setting with a given uuid if it does not exist', async () => {
|
||||
subscriptionSettingRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
@@ -213,7 +297,7 @@ describe('SubscriptionSettingService', () => {
|
||||
userSubscription,
|
||||
props: {
|
||||
uuid: '1-2-3',
|
||||
name: 'name',
|
||||
name: SettingName.NAMES.FileUploadBytesLimit,
|
||||
unencryptedValue: 'value',
|
||||
serverEncryptionVersion: 1,
|
||||
sensitive: false,
|
||||
@@ -266,11 +350,21 @@ describe('SubscriptionSettingService', () => {
|
||||
await createService().findSubscriptionSettingWithDecryptedValue({
|
||||
userSubscriptionUuid: '2-3-4',
|
||||
userUuid: '1-2-3',
|
||||
subscriptionSettingName: 'test' as SubscriptionSettingName,
|
||||
subscriptionSettingName: SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
|
||||
}),
|
||||
).toEqual({
|
||||
serverEncryptionVersion: 1,
|
||||
value: 'decrypted',
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw error when trying to find and decrypt a setting with invalid subscription setting name', async () => {
|
||||
await expect(
|
||||
createService().findSubscriptionSettingWithDecryptedValue({
|
||||
userSubscriptionUuid: '2-3-4',
|
||||
userUuid: '1-2-3',
|
||||
subscriptionSettingName: SettingName.create(SettingName.NAMES.DropboxBackupFrequency).getValue(),
|
||||
}),
|
||||
).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@@ -17,6 +16,8 @@ import { SubscriptionSettingRepositoryInterface } from './SubscriptionSettingRep
|
||||
import { SettingFactoryInterface } from './SettingFactoryInterface'
|
||||
import { SubscriptionSettingsAssociationServiceInterface } from './SubscriptionSettingsAssociationServiceInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingInterpreterInterface } from './SettingInterpreterInterface'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionSettingService implements SubscriptionSettingServiceInterface {
|
||||
@@ -26,6 +27,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
private subscriptionSettingRepository: SubscriptionSettingRepositoryInterface,
|
||||
@inject(TYPES.SubscriptionSettingsAssociationService)
|
||||
private subscriptionSettingAssociationService: SubscriptionSettingsAssociationServiceInterface,
|
||||
@inject(TYPES.SettingInterpreter) private settingInterpreter: SettingInterpreterInterface,
|
||||
@inject(TYPES.SettingDecrypter) private settingDecrypter: SettingDecrypterInterface,
|
||||
@inject(TYPES.UserSubscriptionRepository) private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@@ -44,8 +46,17 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
return
|
||||
}
|
||||
|
||||
for (const settingName of defaultSettingsWithValues.keys()) {
|
||||
const setting = defaultSettingsWithValues.get(settingName) as SettingDescription
|
||||
for (const settingNameString of defaultSettingsWithValues.keys()) {
|
||||
const settingNameOrError = SettingName.create(settingNameString)
|
||||
if (settingNameOrError.isFailed()) {
|
||||
throw new Error(settingNameOrError.getError())
|
||||
}
|
||||
const settingName = settingNameOrError.getValue()
|
||||
if (!settingName.isASubscriptionSetting()) {
|
||||
throw new Error(`Setting ${settingName.value} is not a subscription setting`)
|
||||
}
|
||||
|
||||
const setting = defaultSettingsWithValues.get(settingName.value) as SettingDescription
|
||||
if (!setting.replaceable) {
|
||||
const existingSetting = await this.findPreviousSubscriptionSetting(settingName, userSubscription.uuid, userUuid)
|
||||
if (existingSetting !== null) {
|
||||
@@ -59,7 +70,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
await this.createOrReplace({
|
||||
userSubscription,
|
||||
props: {
|
||||
name: settingName,
|
||||
name: settingName.value,
|
||||
unencryptedValue: setting.value,
|
||||
serverEncryptionVersion: setting.serverEncryptionVersion,
|
||||
sensitive: setting.sensitive,
|
||||
@@ -71,12 +82,16 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
async findSubscriptionSettingWithDecryptedValue(
|
||||
dto: FindSubscriptionSettingDTO,
|
||||
): Promise<SubscriptionSetting | null> {
|
||||
if (!dto.subscriptionSettingName.isASubscriptionSetting()) {
|
||||
throw new Error(`Setting ${dto.subscriptionSettingName.value} is not a subscription setting`)
|
||||
}
|
||||
|
||||
let setting: SubscriptionSetting | null
|
||||
if (dto.settingUuid !== undefined) {
|
||||
setting = await this.subscriptionSettingRepository.findOneByUuid(dto.settingUuid)
|
||||
} else {
|
||||
setting = await this.subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid(
|
||||
dto.subscriptionSettingName,
|
||||
dto.subscriptionSettingName.value,
|
||||
dto.userSubscriptionUuid,
|
||||
)
|
||||
}
|
||||
@@ -95,10 +110,21 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
): Promise<CreateOrReplaceSubscriptionSettingResponse> {
|
||||
const { userSubscription, props } = dto
|
||||
|
||||
const settingNameOrError = SettingName.create(props.name)
|
||||
if (settingNameOrError.isFailed()) {
|
||||
throw new Error(settingNameOrError.getError())
|
||||
}
|
||||
const settingName = settingNameOrError.getValue()
|
||||
|
||||
if (!settingName.isASubscriptionSetting()) {
|
||||
throw new Error(`Setting ${settingName.value} is not a subscription setting`)
|
||||
}
|
||||
|
||||
const user = await userSubscription.user
|
||||
const existing = await this.findSubscriptionSettingWithDecryptedValue({
|
||||
userUuid: (await userSubscription.user).uuid,
|
||||
userUuid: user.uuid,
|
||||
userSubscriptionUuid: userSubscription.uuid,
|
||||
subscriptionSettingName: props.name as SubscriptionSettingName,
|
||||
subscriptionSettingName: settingName,
|
||||
settingUuid: props.uuid,
|
||||
})
|
||||
|
||||
@@ -109,6 +135,8 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
|
||||
this.logger.debug('Created subscription setting %s: %O', props.name, subscriptionSetting)
|
||||
|
||||
await this.settingInterpreter.interpretSettingUpdated(settingName.value, user, props.unencryptedValue)
|
||||
|
||||
return {
|
||||
status: 'created',
|
||||
subscriptionSetting,
|
||||
@@ -121,6 +149,8 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
|
||||
this.logger.debug('Replaced existing subscription setting %s with: %O', props.name, subscriptionSetting)
|
||||
|
||||
await this.settingInterpreter.interpretSettingUpdated(settingName.value, user, props.unencryptedValue)
|
||||
|
||||
return {
|
||||
status: 'replaced',
|
||||
subscriptionSetting,
|
||||
@@ -128,7 +158,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
}
|
||||
|
||||
private async findPreviousSubscriptionSetting(
|
||||
settingName: SubscriptionSettingName,
|
||||
settingName: SettingName,
|
||||
currentUserSubscriptionUuid: string,
|
||||
userUuid: string,
|
||||
): Promise<SubscriptionSetting | null> {
|
||||
@@ -142,6 +172,9 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
return null
|
||||
}
|
||||
|
||||
return this.subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid(settingName, lastSubscription.uuid)
|
||||
return this.subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid(
|
||||
settingName.value,
|
||||
lastSubscription.uuid,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { RoleRepositoryInterface } from '../Role/RoleRepositoryInterface'
|
||||
import { RoleToSubscriptionMapInterface } from '../Role/RoleToSubscriptionMapInterface'
|
||||
@@ -51,14 +51,11 @@ describe('SubscriptionSettingsAssociationService', () => {
|
||||
|
||||
const flatSettings = [
|
||||
...(
|
||||
settings as Map<
|
||||
SubscriptionSettingName,
|
||||
{ value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }
|
||||
>
|
||||
settings as Map<string, { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }>
|
||||
).keys(),
|
||||
]
|
||||
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
||||
expect(settings?.get(SubscriptionSettingName.FileUploadBytesLimit)).toEqual({
|
||||
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'MUTE_SIGN_IN_EMAILS', 'FILE_UPLOAD_BYTES_LIMIT'])
|
||||
expect(settings?.get(SettingName.NAMES.FileUploadBytesLimit)).toEqual({
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: 0,
|
||||
value: '107374182400',
|
||||
@@ -79,14 +76,11 @@ describe('SubscriptionSettingsAssociationService', () => {
|
||||
|
||||
const flatSettings = [
|
||||
...(
|
||||
settings as Map<
|
||||
SubscriptionSettingName,
|
||||
{ value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }
|
||||
>
|
||||
settings as Map<string, { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }>
|
||||
).keys(),
|
||||
]
|
||||
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'FILE_UPLOAD_BYTES_LIMIT'])
|
||||
expect(settings?.get(SubscriptionSettingName.FileUploadBytesLimit)).toEqual({
|
||||
expect(flatSettings).toEqual(['FILE_UPLOAD_BYTES_USED', 'MUTE_SIGN_IN_EMAILS', 'FILE_UPLOAD_BYTES_LIMIT'])
|
||||
expect(settings?.get(SettingName.NAMES.FileUploadBytesLimit)).toEqual({
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: 0,
|
||||
value: '104857600',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -19,40 +19,55 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
||||
@inject(TYPES.RoleRepository) private roleRepository: RoleRepositoryInterface,
|
||||
) {}
|
||||
|
||||
private readonly settingsToSubscriptionNameMap = new Map<
|
||||
SubscriptionName,
|
||||
Map<SubscriptionSettingName, SettingDescription>
|
||||
>([
|
||||
private readonly settingsToSubscriptionNameMap = new Map<SubscriptionName, Map<string, SettingDescription>>([
|
||||
[
|
||||
SubscriptionName.PlusPlan,
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
||||
],
|
||||
[
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: 'not_muted',
|
||||
replaceable: false,
|
||||
},
|
||||
],
|
||||
]),
|
||||
],
|
||||
[
|
||||
SubscriptionName.ProPlan,
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
||||
],
|
||||
[
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: 'not_muted',
|
||||
replaceable: false,
|
||||
},
|
||||
],
|
||||
]),
|
||||
],
|
||||
])
|
||||
|
||||
async getDefaultSettingsAndValuesForSubscriptionName(
|
||||
subscriptionName: SubscriptionName,
|
||||
): Promise<Map<SubscriptionSettingName, SettingDescription> | undefined> {
|
||||
): Promise<Map<string, SettingDescription> | undefined> {
|
||||
const defaultSettings = this.settingsToSubscriptionNameMap.get(subscriptionName)
|
||||
|
||||
if (defaultSettings === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
defaultSettings.set(SubscriptionSettingName.FileUploadBytesLimit, {
|
||||
defaultSettings.set(SettingName.NAMES.FileUploadBytesLimit, {
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: (await this.getFileUploadLimit(subscriptionName)).toString(),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
|
||||
import { SettingDescription } from './SettingDescription'
|
||||
|
||||
export interface SubscriptionSettingsAssociationServiceInterface {
|
||||
getDefaultSettingsAndValuesForSubscriptionName(
|
||||
subscriptionName: SubscriptionName,
|
||||
): Promise<Map<SubscriptionSettingName, SettingDescription> | undefined>
|
||||
): Promise<Map<string, SettingDescription> | undefined>
|
||||
getFileUploadLimit(subscriptionName: SubscriptionName): Promise<number>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { SessionRepositoryInterface } from '../../Session/SessionRepositoryInterface'
|
||||
|
||||
import { CleanupExpiredSessions } from './CleanupExpiredSessions'
|
||||
|
||||
describe('CleanupExpiredSessions', () => {
|
||||
let sessionsRepository: SessionRepositoryInterface
|
||||
|
||||
const createUseCase = () => new CleanupExpiredSessions(sessionsRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
sessionsRepository = {} as jest.Mocked<SessionRepositoryInterface>
|
||||
sessionsRepository.removeExpiredBefore = jest.fn()
|
||||
})
|
||||
|
||||
it('should remove stale sessions', async () => {
|
||||
await createUseCase().execute({ date: new Date() })
|
||||
|
||||
expect(sessionsRepository.removeExpiredBefore).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
import { SessionRepositoryInterface } from '../../Session/SessionRepositoryInterface'
|
||||
|
||||
import { CleanupExpiredSessionsDTO } from './CleanupExpiredSessionsDTO'
|
||||
|
||||
export class CleanupExpiredSessions implements UseCaseInterface<string> {
|
||||
constructor(private sessionTracesRepository: SessionRepositoryInterface) {}
|
||||
|
||||
async execute(dto: CleanupExpiredSessionsDTO): Promise<Result<string>> {
|
||||
await this.sessionTracesRepository.removeExpiredBefore(dto.date)
|
||||
|
||||
return Result.ok('Expired sessions removed')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface CleanupExpiredSessionsDTO {
|
||||
date: Date
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user