mirror of
https://github.com/standardnotes/server
synced 2026-01-26 05:01:12 -05:00
Compare commits
58 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f64d30ec88 | ||
|
|
384dfc8da4 | ||
|
|
841784ae8c | ||
|
|
f5683cfd94 | ||
|
|
0a420ce30e | ||
|
|
a5e7132d3c | ||
|
|
6dfb2be4a2 | ||
|
|
d81cbad550 | ||
|
|
51ad06b303 | ||
|
|
27048ad95c | ||
|
|
fa9bf0b448 | ||
|
|
305190b64e | ||
|
|
98e3d18335 | ||
|
|
72e398956b | ||
|
|
1e69a13a97 | ||
|
|
7f9e6e2f44 | ||
|
|
d3c6c0d48e | ||
|
|
6c83476fd2 | ||
|
|
9cdf7e2c51 | ||
|
|
599119e14e | ||
|
|
a2c484e0f3 | ||
|
|
97ff4d5ac2 | ||
|
|
5255cfbb25 | ||
|
|
780358368b | ||
|
|
cf0b918913 | ||
|
|
4ea690204e | ||
|
|
14eb775749 | ||
|
|
bf4a3be6d9 | ||
|
|
b9e1e47871 | ||
|
|
ff532ecb22 | ||
|
|
eb21872db1 | ||
|
|
8e3df184dc | ||
|
|
b34bbcac8b | ||
|
|
226965a1d7 | ||
|
|
17b2ea126c | ||
|
|
59fc4a089c | ||
|
|
ef26dc8cbb | ||
|
|
8a0fbb28b0 | ||
|
|
618d8d5b1a | ||
|
|
3a936dc9c1 | ||
|
|
031fcd75ee | ||
|
|
c8cd23cb32 | ||
|
|
a3049938a3 | ||
|
|
b23488e862 | ||
|
|
c8203cf04c | ||
|
|
4f2616ef0a | ||
|
|
04ffc69e00 | ||
|
|
5b4bb6e7a7 | ||
|
|
2e953ba998 | ||
|
|
ed5a4eb960 | ||
|
|
31b2c05084 | ||
|
|
6e1662038c | ||
|
|
df78d88f79 | ||
|
|
addedb3091 | ||
|
|
2ea17b2dea | ||
|
|
85d2f42f47 | ||
|
|
cdb655c1bd | ||
|
|
3064d03aa9 |
@@ -190,9 +190,9 @@ jobs:
|
||||
uses: convictional/trigger-workflow-and-wait@master
|
||||
with:
|
||||
owner: standardnotes
|
||||
repo: e2e
|
||||
repo: self-hosted
|
||||
github_token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
workflow_file_name: testing-with-stable-client.yml
|
||||
workflow_file_name: testing-with-updating-client-and-server.yml
|
||||
wait_interval: 30
|
||||
client_payload: '{"${{ inputs.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
|
||||
propagate_failure: true
|
||||
|
||||
336
.pnp.cjs
generated
336
.pnp.cjs
generated
@@ -126,7 +126,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@lerna-lite/cli", "npm:1.6.0"],\
|
||||
["@lerna-lite/list", "npm:1.6.0"],\
|
||||
["@lerna-lite/run", "npm:1.6.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/node", "npm:18.11.9"],\
|
||||
@@ -1968,6 +1968,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@noble/ed25519", [\
|
||||
["npm:1.7.1", {\
|
||||
"packageLocation": "./.yarn/cache/@noble-ed25519-npm-1.7.1-177d9beb01-b1aa4b9264.zip/node_modules/@noble/ed25519/",\
|
||||
"packageDependencies": [\
|
||||
["@noble/ed25519", "npm:1.7.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@nodelib/fs.scandir", [\
|
||||
["npm:2.1.5", {\
|
||||
"packageLocation": "./.yarn/cache/@nodelib-fs.scandir-npm-2.1.5-89c67370dd-5f309a3b37.zip/node_modules/@nodelib/fs.scandir/",\
|
||||
@@ -2324,6 +2333,44 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@peculiar/asn1-android", [\
|
||||
["npm:2.3.3", {\
|
||||
"packageLocation": "./.yarn/cache/@peculiar-asn1-android-npm-2.3.3-28df67d7a3-0c7cad544e.zip/node_modules/@peculiar/asn1-android/",\
|
||||
"packageDependencies": [\
|
||||
["@peculiar/asn1-android", "npm:2.3.3"],\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["asn1js", "npm:3.0.5"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@peculiar/asn1-schema", [\
|
||||
["npm:2.3.3", {\
|
||||
"packageLocation": "./.yarn/cache/@peculiar-asn1-schema-npm-2.3.3-7c2b9469c4-f584f79d5a.zip/node_modules/@peculiar/asn1-schema/",\
|
||||
"packageDependencies": [\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["asn1js", "npm:3.0.5"],\
|
||||
["pvtsutils", "npm:1.3.2"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@peculiar/asn1-x509", [\
|
||||
["npm:2.3.4", {\
|
||||
"packageLocation": "./.yarn/cache/@peculiar-asn1-x509-npm-2.3.4-a579005836-10a8659980.zip/node_modules/@peculiar/asn1-x509/",\
|
||||
"packageDependencies": [\
|
||||
["@peculiar/asn1-x509", "npm:2.3.4"],\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["asn1js", "npm:3.0.5"],\
|
||||
["ipaddr.js", "npm:2.0.1"],\
|
||||
["pvtsutils", "npm:1.3.2"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@pnpm/network.ca-file", [\
|
||||
["npm:1.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/@pnpm-network.ca-file-npm-1.0.1-42bfe40bec-ed952a5574.zip/node_modules/@pnpm/network.ca-file/",\
|
||||
@@ -2447,6 +2494,16 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-core-npm-7.28.1-a468033ea8-f29d747d3e.zip/node_modules/@sentry/core/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/core", "npm:7.28.1"],\
|
||||
["@sentry/types", "npm:7.28.1"],\
|
||||
["@sentry/utils", "npm:7.28.1"],\
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/hub", [\
|
||||
@@ -2476,6 +2533,20 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-node-npm-7.28.1-b0e124fdfc-b4922d1f0a.zip/node_modules/@sentry/node/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/core", "npm:7.28.1"],\
|
||||
["@sentry/types", "npm:7.28.1"],\
|
||||
["@sentry/utils", "npm:7.28.1"],\
|
||||
["cookie", "npm:0.4.2"],\
|
||||
["https-proxy-agent", "npm:5.0.1"],\
|
||||
["lru_map", "npm:0.3.3"],\
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/profiling-node", [\
|
||||
@@ -2506,6 +2577,17 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-tracing-npm-7.28.1-e15d453d8e-be501ca9d7.zip/node_modules/@sentry/tracing/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/tracing", "npm:7.28.1"],\
|
||||
["@sentry/core", "npm:7.28.1"],\
|
||||
["@sentry/types", "npm:7.28.1"],\
|
||||
["@sentry/utils", "npm:7.28.1"],\
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/types", [\
|
||||
@@ -2515,6 +2597,13 @@ const RAW_RUNTIME_STATE =
|
||||
["@sentry/types", "npm:7.27.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-types-npm-7.28.1-42d9a8574c-7dc6639cb7.zip/node_modules/@sentry/types/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/types", "npm:7.28.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/utils", [\
|
||||
@@ -2526,6 +2615,34 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-utils-npm-7.28.1-71eaeb767f-a4b5f73db0.zip/node_modules/@sentry/utils/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/utils", "npm:7.28.1"],\
|
||||
["@sentry/types", "npm:7.28.1"],\
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@simplewebauthn/server", [\
|
||||
["npm:6.2.2", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-6.2.2-ca870b05c2-5ffb9b1c15.zip/node_modules/@simplewebauthn/server/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/server", "npm:6.2.2"],\
|
||||
["@noble/ed25519", "npm:1.7.1"],\
|
||||
["@peculiar/asn1-android", "npm:2.3.3"],\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["@peculiar/asn1-x509", "npm:2.3.4"],\
|
||||
["base64url", "npm:3.0.1"],\
|
||||
["cbor", "npm:5.2.0"],\
|
||||
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"],\
|
||||
["jsrsasign", "npm:10.6.1"],\
|
||||
["jwk-to-pem", "npm:2.0.5"],\
|
||||
["node-fetch", "virtual:25a5f5382d53dbf298bf7a1191760bc2e0a523a619eeb0e667b99a8649e8ad183f9e2e0b45f6fb831b92f4078b61622aa567cf79565f6aa5af9597e3c84864f6#npm:2.6.7"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sinclair/typebox", [\
|
||||
@@ -2581,7 +2698,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -2633,7 +2750,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
@@ -2689,9 +2806,10 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/auth-server", "workspace:packages/auth"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/profiling-node", "npm:0.0.12"],\
|
||||
["@sentry/tracing", "npm:7.27.0"],\
|
||||
["@sentry/tracing", "npm:7.28.1"],\
|
||||
["@simplewebauthn/server", "npm:6.2.2"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
@@ -2915,7 +3033,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/files/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/files-server", "workspace:packages/files"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/config", "npm:2.4.3"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -3050,7 +3168,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/revisions-server", "workspace:packages/revisions"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
@@ -3095,7 +3213,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -3156,7 +3274,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@lerna-lite/cli", "npm:1.6.0"],\
|
||||
["@lerna-lite/list", "npm:1.6.0"],\
|
||||
["@lerna-lite/run", "npm:1.6.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/node", "npm:18.11.9"],\
|
||||
@@ -3222,9 +3340,9 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/profiling-node", "npm:0.0.12"],\
|
||||
["@sentry/tracing", "npm:7.27.0"],\
|
||||
["@sentry/tracing", "npm:7.28.1"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -3324,7 +3442,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -3364,7 +3482,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.27.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
@@ -4818,6 +4936,31 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["asn1.js", [\
|
||||
["npm:5.4.1", {\
|
||||
"packageLocation": "./.yarn/cache/asn1.js-npm-5.4.1-37c7edbcb0-5c36f81388.zip/node_modules/asn1.js/",\
|
||||
"packageDependencies": [\
|
||||
["asn1.js", "npm:5.4.1"],\
|
||||
["bn.js", "npm:4.12.0"],\
|
||||
["inherits", "npm:2.0.4"],\
|
||||
["minimalistic-assert", "npm:1.0.1"],\
|
||||
["safer-buffer", "npm:2.1.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["asn1js", [\
|
||||
["npm:3.0.5", {\
|
||||
"packageLocation": "./.yarn/cache/asn1js-npm-3.0.5-cf5558af33-d0bc57da97.zip/node_modules/asn1js/",\
|
||||
"packageDependencies": [\
|
||||
["asn1js", "npm:3.0.5"],\
|
||||
["pvtsutils", "npm:1.3.2"],\
|
||||
["pvutils", "npm:1.1.3"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["async", [\
|
||||
["npm:3.2.4", {\
|
||||
"packageLocation": "./.yarn/cache/async-npm-3.2.4-aba13508f9-9719e38d24.zip/node_modules/async/",\
|
||||
@@ -5031,6 +5174,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["base64url", [\
|
||||
["npm:3.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/base64url-npm-3.0.1-4c171c4917-72e1401ffe.zip/node_modules/base64url/",\
|
||||
"packageDependencies": [\
|
||||
["base64url", "npm:3.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["bcryptjs", [\
|
||||
["npm:2.4.3", {\
|
||||
"packageLocation": "./.yarn/cache/bcryptjs-npm-2.4.3-32de4957eb-bf6a43e9c4.zip/node_modules/bcryptjs/",\
|
||||
@@ -5049,6 +5201,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["bignumber.js", [\
|
||||
["npm:9.1.1", {\
|
||||
"packageLocation": "./.yarn/cache/bignumber.js-npm-9.1.1-5929e8d8dc-e44d008049.zip/node_modules/bignumber.js/",\
|
||||
"packageDependencies": [\
|
||||
["bignumber.js", "npm:9.1.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["binary-extensions", [\
|
||||
["npm:2.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/binary-extensions-npm-2.2.0-180c33fec7-16cf7c0cfd.zip/node_modules/binary-extensions/",\
|
||||
@@ -5070,6 +5231,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["bn.js", [\
|
||||
["npm:4.12.0", {\
|
||||
"packageLocation": "./.yarn/cache/bn.js-npm-4.12.0-3ec6c884f6-bfb4590775.zip/node_modules/bn.js/",\
|
||||
"packageDependencies": [\
|
||||
["bn.js", "npm:4.12.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["body-parser", [\
|
||||
["npm:1.20.1", {\
|
||||
"packageLocation": "./.yarn/cache/body-parser-npm-1.20.1-759fd14db9-33f202c9d5.zip/node_modules/body-parser/",\
|
||||
@@ -5137,6 +5307,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["brorand", [\
|
||||
["npm:1.1.0", {\
|
||||
"packageLocation": "./.yarn/cache/brorand-npm-1.1.0-ea86634c4b-f736e127fb.zip/node_modules/brorand/",\
|
||||
"packageDependencies": [\
|
||||
["brorand", "npm:1.1.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["browserslist", [\
|
||||
["npm:4.21.1", {\
|
||||
"packageLocation": "./.yarn/cache/browserslist-npm-4.21.1-930e90b93a-617d624493.zip/node_modules/browserslist/",\
|
||||
@@ -5372,6 +5551,17 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["cbor", [\
|
||||
["npm:5.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/cbor-npm-5.2.0-4f6440587f-d60986b9d0.zip/node_modules/cbor/",\
|
||||
"packageDependencies": [\
|
||||
["cbor", "npm:5.2.0"],\
|
||||
["bignumber.js", "npm:9.1.1"],\
|
||||
["nofilter", "npm:1.0.4"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["chalk", [\
|
||||
["npm:2.4.2", {\
|
||||
"packageLocation": "./.yarn/cache/chalk-npm-2.4.2-3ea16dd91e-befd2fe888.zip/node_modules/chalk/",\
|
||||
@@ -6496,6 +6686,22 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["elliptic", [\
|
||||
["npm:6.5.4", {\
|
||||
"packageLocation": "./.yarn/cache/elliptic-npm-6.5.4-0ca8204a86-4453b008cf.zip/node_modules/elliptic/",\
|
||||
"packageDependencies": [\
|
||||
["elliptic", "npm:6.5.4"],\
|
||||
["bn.js", "npm:4.12.0"],\
|
||||
["brorand", "npm:1.1.0"],\
|
||||
["hash.js", "npm:1.1.7"],\
|
||||
["hmac-drbg", "npm:1.0.1"],\
|
||||
["inherits", "npm:2.0.4"],\
|
||||
["minimalistic-assert", "npm:1.0.1"],\
|
||||
["minimalistic-crypto-utils", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["emittery", [\
|
||||
["npm:0.10.2", {\
|
||||
"packageLocation": "./.yarn/cache/emittery-npm-0.10.2-aac10498b5-c55b286714.zip/node_modules/emittery/",\
|
||||
@@ -8002,6 +8208,17 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["hash.js", [\
|
||||
["npm:1.1.7", {\
|
||||
"packageLocation": "./.yarn/cache/hash.js-npm-1.1.7-f1ad187358-e4266370d1.zip/node_modules/hash.js/",\
|
||||
"packageDependencies": [\
|
||||
["hash.js", "npm:1.1.7"],\
|
||||
["inherits", "npm:2.0.4"],\
|
||||
["minimalistic-assert", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["helmet", [\
|
||||
["npm:6.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/helmet-npm-6.0.0-2285459f57-73b6ba802d.zip/node_modules/helmet/",\
|
||||
@@ -8020,6 +8237,18 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["hmac-drbg", [\
|
||||
["npm:1.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/hmac-drbg-npm-1.0.1-3499ad31cd-4e88d58ffc.zip/node_modules/hmac-drbg/",\
|
||||
"packageDependencies": [\
|
||||
["hmac-drbg", "npm:1.0.1"],\
|
||||
["hash.js", "npm:1.1.7"],\
|
||||
["minimalistic-assert", "npm:1.0.1"],\
|
||||
["minimalistic-crypto-utils", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["hosted-git-info", [\
|
||||
["npm:2.8.9", {\
|
||||
"packageLocation": "./.yarn/cache/hosted-git-info-npm-2.8.9-62c44fa93f-c24da52f98.zip/node_modules/hosted-git-info/",\
|
||||
@@ -8400,6 +8629,13 @@ const RAW_RUNTIME_STATE =
|
||||
["ipaddr.js", "npm:1.9.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:2.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/ipaddr.js-npm-2.0.1-04e97280d7-04ce6c896c.zip/node_modules/ipaddr.js/",\
|
||||
"packageDependencies": [\
|
||||
["ipaddr.js", "npm:2.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["is-arguments", [\
|
||||
@@ -9602,6 +9838,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["jsrsasign", [\
|
||||
["npm:10.6.1", {\
|
||||
"packageLocation": "./.yarn/cache/jsrsasign-npm-10.6.1-a8fa295369-e8e9c1b24f.zip/node_modules/jsrsasign/",\
|
||||
"packageDependencies": [\
|
||||
["jsrsasign", "npm:10.6.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["jwa", [\
|
||||
["npm:1.4.1", {\
|
||||
"packageLocation": "./.yarn/cache/jwa-npm-1.4.1-4f19d6572c-0cc3e68b68.zip/node_modules/jwa/",\
|
||||
@@ -9614,6 +9859,18 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["jwk-to-pem", [\
|
||||
["npm:2.0.5", {\
|
||||
"packageLocation": "./.yarn/cache/jwk-to-pem-npm-2.0.5-aff7d9f125-fced3a75b0.zip/node_modules/jwk-to-pem/",\
|
||||
"packageDependencies": [\
|
||||
["jwk-to-pem", "npm:2.0.5"],\
|
||||
["asn1.js", "npm:5.4.1"],\
|
||||
["elliptic", "npm:6.5.4"],\
|
||||
["safe-buffer", "npm:5.2.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["jws", [\
|
||||
["npm:3.2.2", {\
|
||||
"packageLocation": "./.yarn/cache/jws-npm-3.2.2-c1ae59c7af-347ed7c334.zip/node_modules/jws/",\
|
||||
@@ -10227,6 +10484,24 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["minimalistic-assert", [\
|
||||
["npm:1.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/minimalistic-assert-npm-1.0.1-dc8bb23d29-e2310081d8.zip/node_modules/minimalistic-assert/",\
|
||||
"packageDependencies": [\
|
||||
["minimalistic-assert", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["minimalistic-crypto-utils", [\
|
||||
["npm:1.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/minimalistic-crypto-utils-npm-1.0.1-e66b10822e-7d909decd2.zip/node_modules/minimalistic-crypto-utils/",\
|
||||
"packageDependencies": [\
|
||||
["minimalistic-crypto-utils", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["minimatch", [\
|
||||
["npm:3.1.2", {\
|
||||
"packageLocation": "./.yarn/cache/minimatch-npm-3.1.2-9405269906-97f5615ee8.zip/node_modules/minimatch/",\
|
||||
@@ -10646,6 +10921,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["nofilter", [\
|
||||
["npm:1.0.4", {\
|
||||
"packageLocation": "./.yarn/cache/nofilter-npm-1.0.4-1cbdc6c03a-9a26874e7d.zip/node_modules/nofilter/",\
|
||||
"packageDependencies": [\
|
||||
["nofilter", "npm:1.0.4"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["nopt", [\
|
||||
["npm:1.0.10", {\
|
||||
"packageLocation": "./.yarn/cache/nopt-npm-1.0.10-f3db192976-efa5a9c2c1.zip/node_modules/nopt/",\
|
||||
@@ -11702,6 +11986,25 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["pvtsutils", [\
|
||||
["npm:1.3.2", {\
|
||||
"packageLocation": "./.yarn/cache/pvtsutils-npm-1.3.2-e1483da905-eb22d3df60.zip/node_modules/pvtsutils/",\
|
||||
"packageDependencies": [\
|
||||
["pvtsutils", "npm:1.3.2"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["pvutils", [\
|
||||
["npm:1.1.3", {\
|
||||
"packageLocation": "./.yarn/cache/pvutils-npm-1.1.3-da8b07d6cf-0cb4f4878f.zip/node_modules/pvutils/",\
|
||||
"packageDependencies": [\
|
||||
["pvutils", "npm:1.1.3"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["q", [\
|
||||
["npm:1.5.1", {\
|
||||
"packageLocation": "./.yarn/cache/q-npm-1.5.1-a28b3cfeaf-276b7e93fc.zip/node_modules/q/",\
|
||||
@@ -13386,6 +13689,13 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.4.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:2.4.1", {\
|
||||
"packageLocation": "./.yarn/cache/tslib-npm-2.4.1-36f0ed04db-a739a21e3f.zip/node_modules/tslib/",\
|
||||
"packageDependencies": [\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["tsutils", [\
|
||||
|
||||
BIN
.yarn/cache/@noble-ed25519-npm-1.7.1-177d9beb01-b1aa4b9264.zip
vendored
Normal file
BIN
.yarn/cache/@noble-ed25519-npm-1.7.1-177d9beb01-b1aa4b9264.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@peculiar-asn1-android-npm-2.3.3-28df67d7a3-0c7cad544e.zip
vendored
Normal file
BIN
.yarn/cache/@peculiar-asn1-android-npm-2.3.3-28df67d7a3-0c7cad544e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@peculiar-asn1-schema-npm-2.3.3-7c2b9469c4-f584f79d5a.zip
vendored
Normal file
BIN
.yarn/cache/@peculiar-asn1-schema-npm-2.3.3-7c2b9469c4-f584f79d5a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@peculiar-asn1-x509-npm-2.3.4-a579005836-10a8659980.zip
vendored
Normal file
BIN
.yarn/cache/@peculiar-asn1-x509-npm-2.3.4-a579005836-10a8659980.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@sentry-core-npm-7.28.1-a468033ea8-f29d747d3e.zip
vendored
Normal file
BIN
.yarn/cache/@sentry-core-npm-7.28.1-a468033ea8-f29d747d3e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@sentry-node-npm-7.28.1-b0e124fdfc-b4922d1f0a.zip
vendored
Normal file
BIN
.yarn/cache/@sentry-node-npm-7.28.1-b0e124fdfc-b4922d1f0a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@sentry-tracing-npm-7.28.1-e15d453d8e-be501ca9d7.zip
vendored
Normal file
BIN
.yarn/cache/@sentry-tracing-npm-7.28.1-e15d453d8e-be501ca9d7.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@sentry-types-npm-7.28.1-42d9a8574c-7dc6639cb7.zip
vendored
Normal file
BIN
.yarn/cache/@sentry-types-npm-7.28.1-42d9a8574c-7dc6639cb7.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@sentry-utils-npm-7.28.1-71eaeb767f-a4b5f73db0.zip
vendored
Normal file
BIN
.yarn/cache/@sentry-utils-npm-7.28.1-71eaeb767f-a4b5f73db0.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@simplewebauthn-server-npm-6.2.2-ca870b05c2-5ffb9b1c15.zip
vendored
Normal file
BIN
.yarn/cache/@simplewebauthn-server-npm-6.2.2-ca870b05c2-5ffb9b1c15.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/asn1.js-npm-5.4.1-37c7edbcb0-5c36f81388.zip
vendored
Normal file
BIN
.yarn/cache/asn1.js-npm-5.4.1-37c7edbcb0-5c36f81388.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/asn1js-npm-3.0.5-cf5558af33-d0bc57da97.zip
vendored
Normal file
BIN
.yarn/cache/asn1js-npm-3.0.5-cf5558af33-d0bc57da97.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/base64url-npm-3.0.1-4c171c4917-72e1401ffe.zip
vendored
Normal file
BIN
.yarn/cache/base64url-npm-3.0.1-4c171c4917-72e1401ffe.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/bignumber.js-npm-9.1.1-5929e8d8dc-e44d008049.zip
vendored
Normal file
BIN
.yarn/cache/bignumber.js-npm-9.1.1-5929e8d8dc-e44d008049.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/bn.js-npm-4.12.0-3ec6c884f6-bfb4590775.zip
vendored
Normal file
BIN
.yarn/cache/bn.js-npm-4.12.0-3ec6c884f6-bfb4590775.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/brorand-npm-1.1.0-ea86634c4b-f736e127fb.zip
vendored
Normal file
BIN
.yarn/cache/brorand-npm-1.1.0-ea86634c4b-f736e127fb.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/cbor-npm-5.2.0-4f6440587f-d60986b9d0.zip
vendored
Normal file
BIN
.yarn/cache/cbor-npm-5.2.0-4f6440587f-d60986b9d0.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/elliptic-npm-6.5.4-0ca8204a86-4453b008cf.zip
vendored
Normal file
BIN
.yarn/cache/elliptic-npm-6.5.4-0ca8204a86-4453b008cf.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/hash.js-npm-1.1.7-f1ad187358-e4266370d1.zip
vendored
Normal file
BIN
.yarn/cache/hash.js-npm-1.1.7-f1ad187358-e4266370d1.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/hmac-drbg-npm-1.0.1-3499ad31cd-4e88d58ffc.zip
vendored
Normal file
BIN
.yarn/cache/hmac-drbg-npm-1.0.1-3499ad31cd-4e88d58ffc.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/ipaddr.js-npm-2.0.1-04e97280d7-04ce6c896c.zip
vendored
Normal file
BIN
.yarn/cache/ipaddr.js-npm-2.0.1-04e97280d7-04ce6c896c.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/jsrsasign-npm-10.6.1-a8fa295369-e8e9c1b24f.zip
vendored
Normal file
BIN
.yarn/cache/jsrsasign-npm-10.6.1-a8fa295369-e8e9c1b24f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/jwk-to-pem-npm-2.0.5-aff7d9f125-fced3a75b0.zip
vendored
Normal file
BIN
.yarn/cache/jwk-to-pem-npm-2.0.5-aff7d9f125-fced3a75b0.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/minimalistic-assert-npm-1.0.1-dc8bb23d29-e2310081d8.zip
vendored
Normal file
BIN
.yarn/cache/minimalistic-assert-npm-1.0.1-dc8bb23d29-e2310081d8.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/minimalistic-crypto-utils-npm-1.0.1-e66b10822e-7d909decd2.zip
vendored
Normal file
BIN
.yarn/cache/minimalistic-crypto-utils-npm-1.0.1-e66b10822e-7d909decd2.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/nofilter-npm-1.0.4-1cbdc6c03a-9a26874e7d.zip
vendored
Normal file
BIN
.yarn/cache/nofilter-npm-1.0.4-1cbdc6c03a-9a26874e7d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/pvtsutils-npm-1.3.2-e1483da905-eb22d3df60.zip
vendored
Normal file
BIN
.yarn/cache/pvtsutils-npm-1.3.2-e1483da905-eb22d3df60.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/pvutils-npm-1.1.3-da8b07d6cf-0cb4f4878f.zip
vendored
Normal file
BIN
.yarn/cache/pvutils-npm-1.1.3-da8b07d6cf-0cb4f4878f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/tslib-npm-2.4.1-36f0ed04db-a739a21e3f.zip
vendored
Normal file
BIN
.yarn/cache/tslib-npm-2.4.1-36f0ed04db-a739a21e3f.zip
vendored
Normal file
Binary file not shown.
@@ -61,7 +61,7 @@
|
||||
},
|
||||
"packageManager": "yarn@4.0.0-rc.25",
|
||||
"dependencies": {
|
||||
"@sentry/node": "^7.27.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"newrelic": "^9.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,69 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.17.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.7...@standardnotes/analytics@2.17.8) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.17.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.6...@standardnotes/analytics@2.17.7) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** monthly numbers of active users ([b34bbca](https://github.com/standardnotes/server/commit/b34bbcac8b9604283b3a5959ab3218c468ce8a00))
|
||||
|
||||
## [2.17.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.5...@standardnotes/analytics@2.17.6) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** filtered counts for user activity check ([17b2ea1](https://github.com/standardnotes/server/commit/17b2ea126c5ad2d7cf07657def63f9977f239a3c))
|
||||
|
||||
## [2.17.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.4...@standardnotes/analytics@2.17.5) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** accessing analytics in report ([ef26dc8](https://github.com/standardnotes/server/commit/ef26dc8cbb967e088ae7387ff6dbec1e60dc3ee4))
|
||||
|
||||
## [2.17.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.3...@standardnotes/analytics@2.17.4) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.17.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.2...@standardnotes/analytics@2.17.3) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** add debug logs for the report ([031fcd7](https://github.com/standardnotes/server/commit/031fcd75eecdcf4c2f17257754a0ba3f24ba6d6e))
|
||||
|
||||
## [2.17.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.1...@standardnotes/analytics@2.17.2) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** calculating active users ([a304993](https://github.com/standardnotes/server/commit/a3049938a31e21a5867a314ac62bee6aa4990d57))
|
||||
|
||||
## [2.17.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.0...@standardnotes/analytics@2.17.1) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** container binding ([04ffc69](https://github.com/standardnotes/server/commit/04ffc69e000803107d8834c286de97b3d213a842))
|
||||
* **auth:** replace date object with number timestamp ([5b4bb6e](https://github.com/standardnotes/server/commit/5b4bb6e7a78a1b0f4e663bb990619f65f6a5c757))
|
||||
|
||||
# [2.17.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.16.0...@standardnotes/analytics@2.17.0) (2022-12-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add users activit to the report email ([ed5a4eb](https://github.com/standardnotes/server/commit/ed5a4eb960a6c8fe9d0c77331f29dc3c7ffb9100))
|
||||
|
||||
# [2.16.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.15.1...@standardnotes/analytics@2.16.0) (2022-12-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add active users stats to report ([6e16620](https://github.com/standardnotes/server/commit/6e1662038c3340fb60939464616789bab7639160))
|
||||
|
||||
## [2.15.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.15.0...@standardnotes/analytics@2.15.1) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add persisting statistics for all subscription plans ([addedb3](https://github.com/standardnotes/server/commit/addedb3091ddae81618d56663e18f2ae76a43c4e))
|
||||
|
||||
# [2.15.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.14.0...@standardnotes/analytics@2.15.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -121,6 +121,10 @@ const requestReport = async (
|
||||
StatisticMeasureName.NAMES.FiveYearPlansMRR,
|
||||
StatisticMeasureName.NAMES.PlusPlansMRR,
|
||||
StatisticMeasureName.NAMES.ProPlansMRR,
|
||||
StatisticMeasureName.NAMES.ActiveUsers,
|
||||
StatisticMeasureName.NAMES.ActiveFreeUsers,
|
||||
StatisticMeasureName.NAMES.ActivePlusUsers,
|
||||
StatisticMeasureName.NAMES.ActiveProUsers,
|
||||
]
|
||||
for (const statisticName of thirtyDaysStatisticsNames) {
|
||||
statisticsOverTime.push({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.15.0",
|
||||
"version": "2.17.8",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.27.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
|
||||
@@ -218,6 +218,7 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
new StatisticPersistenceRequestedEventHandler(
|
||||
container.get(TYPES.PersistStatistic),
|
||||
container.get(TYPES.Timer),
|
||||
container.get(TYPES.Logger),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -5,6 +5,38 @@ import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { Period } from '../Time/Period'
|
||||
|
||||
const countActiveUsers = (measureName: string, data: any): { yesterday: number; last30Days: number } => {
|
||||
const totalActiveUsersLast30DaysIncludingToday = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === measureName && a.period === 27,
|
||||
)
|
||||
|
||||
const totalActiveUsersYesterday =
|
||||
totalActiveUsersLast30DaysIncludingToday.counts[totalActiveUsersLast30DaysIncludingToday.counts.length - 2]
|
||||
.totalCount
|
||||
|
||||
const filteredCounts = totalActiveUsersLast30DaysIncludingToday.counts.filter(
|
||||
(count: { totalCount: number }) => count.totalCount !== 0,
|
||||
)
|
||||
if (filteredCounts.length === 0) {
|
||||
return {
|
||||
yesterday: 0,
|
||||
last30Days: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const last30DaysNumbers = filteredCounts.map((count: { totalCount: number }) => count.totalCount)
|
||||
const last30DaysCount = last30DaysNumbers.reduce((previousValue: number, currentValue: number) => {
|
||||
return previousValue + currentValue
|
||||
})
|
||||
|
||||
const averageActiveUsersLast30Days = Math.floor(last30DaysCount / last30DaysNumbers.length)
|
||||
|
||||
return {
|
||||
yesterday: totalActiveUsersYesterday,
|
||||
last30Days: averageActiveUsersLast30Days,
|
||||
}
|
||||
}
|
||||
|
||||
const getChartUrls = (
|
||||
data: any,
|
||||
): {
|
||||
@@ -12,7 +44,6 @@ const getChartUrls = (
|
||||
users: string
|
||||
quarterlyPerformance: string
|
||||
churn: string
|
||||
mrr: string
|
||||
mrrMonthly: string
|
||||
} => {
|
||||
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||
@@ -237,82 +268,6 @@ const getChartUrls = (
|
||||
},
|
||||
}
|
||||
|
||||
const mrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||
)
|
||||
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||
)
|
||||
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||
)
|
||||
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||
)
|
||||
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||
)
|
||||
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||
)
|
||||
|
||||
const mrrOverTimeConfig = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: mrrOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||
datasets: [
|
||||
{
|
||||
label: 'MRR',
|
||||
backgroundColor: 'rgb(25, 255, 140)',
|
||||
borderColor: 'rgb(25, 255, 140)',
|
||||
data: mrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - Monthly Plans',
|
||||
backgroundColor: 'rgb(54, 162, 235)',
|
||||
borderColor: 'rgb(54, 162, 235)',
|
||||
data: monthlyPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - Annual Plans',
|
||||
backgroundColor: 'rgb(255, 221, 51)',
|
||||
borderColor: 'rgb(255, 221, 51)',
|
||||
data: annualPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - Five Year Plans',
|
||||
backgroundColor: 'rgb(255, 120, 120)',
|
||||
borderColor: 'rgb(255, 120, 120)',
|
||||
data: fiveYearPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - PRO Plans',
|
||||
backgroundColor: 'rgb(255, 99, 132)',
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
data: proPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - PLUS Plans',
|
||||
backgroundColor: 'rgb(221, 51, 255)',
|
||||
borderColor: 'rgb(221, 51, 255)',
|
||||
data: plusPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const mrrMonthlyOverTime = data.statisticsOverTime
|
||||
.find((a: { name: string; period: Period }) => a.name === 'mrr' && a.period === Period.ThisYear)
|
||||
?.counts.map((count: { totalCount: number }) => +count.totalCount.toFixed(2))
|
||||
@@ -371,7 +326,6 @@ const getChartUrls = (
|
||||
JSON.stringify(quarterlyConfig),
|
||||
)}`,
|
||||
churn: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(churnConfig))}`,
|
||||
mrr: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrOverTimeConfig))}`,
|
||||
mrrMonthly: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrMonthlyConfig))}`,
|
||||
}
|
||||
}
|
||||
@@ -608,12 +562,39 @@ export const html = (data: any, timer: TimerInterface) => {
|
||||
(value: { periodKey: string }) => value.periodKey === thisMonthPeriodKey,
|
||||
)
|
||||
|
||||
const totalActiveUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveUsers, data)
|
||||
const totalActiveFreeUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveFreeUsers, data)
|
||||
const totalActivePlusUsers = countActiveUsers(StatisticMeasureName.NAMES.ActivePlusUsers, data)
|
||||
const totalActiveProUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveProUsers, data)
|
||||
|
||||
return ` <div>
|
||||
<p>Hello,</p>
|
||||
<p>
|
||||
<strong>Here are some statistics from yesterday:</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Active Users</b>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Total:</b> ${totalActiveUsers.yesterday.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>By Subscription Type:</b>
|
||||
<ul>
|
||||
<li>
|
||||
<b>FREE:</b> ${totalActiveFreeUsers.yesterday.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>PLUS:</b> ${totalActivePlusUsers.yesterday.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>PRO:</b> ${totalActiveProUsers.yesterday.toLocaleString('en-US')}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Payments</b>
|
||||
<ul>
|
||||
@@ -812,6 +793,28 @@ export const html = (data: any, timer: TimerInterface) => {
|
||||
<strong>Here are some statistics from last 30 days:</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Active Users (Average)</b>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Total:</b> ${totalActiveUsers.last30Days.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>By Subscription Type:</b>
|
||||
<ul>
|
||||
<li>
|
||||
<b>FREE:</b> ${totalActiveFreeUsers.last30Days.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>PLUS:</b> ${totalActivePlusUsers.last30Days.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>PRO:</b> ${totalActiveProUsers.last30Days.toLocaleString('en-US')}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Payments (This Month)</b>
|
||||
<ul>
|
||||
@@ -944,10 +947,6 @@ export const html = (data: any, timer: TimerInterface) => {
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>Here is the MRR chart over 30 days:</strong>
|
||||
</p>
|
||||
<img src=${chartUrls.mrr}></img>
|
||||
<p>
|
||||
<strong>Here is the MRR Monthly chart this year:</strong>
|
||||
</p>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { DomainEventHandlerInterface, StatisticPersistenceRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Logger } from 'winston'
|
||||
import { PersistStatistic } from '../UseCase/PersistStatistic/PersistStatistic'
|
||||
|
||||
export class StatisticPersistenceRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(private persistStatistic: PersistStatistic, private logger: Logger) {}
|
||||
constructor(private persistStatistic: PersistStatistic, private timer: TimerInterface, private logger: Logger) {}
|
||||
|
||||
async handle(event: StatisticPersistenceRequestedEvent): Promise<void> {
|
||||
const result = await this.persistStatistic.execute({
|
||||
date: event.payload.date,
|
||||
date: this.timer.convertMicrosecondsToDate(event.payload.date),
|
||||
statisticMeasureName: event.payload.statisticMeasureName,
|
||||
value: event.payload.value,
|
||||
})
|
||||
|
||||
@@ -27,6 +27,9 @@ export class StatisticMeasureName extends ValueObject<StatisticMeasureNameProps>
|
||||
ProPlansMRR: 'pro-plans-mrr',
|
||||
PlusPlansMRR: 'plus-plans-mrr',
|
||||
ActiveUsers: 'active-users',
|
||||
ActiveProUsers: 'active-pro-users',
|
||||
ActivePlusUsers: 'active-plus-users',
|
||||
ActiveFreeUsers: 'active-free-users',
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.41.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.41.2...@standardnotes/api-gateway@1.41.3) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.41.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.41.1...@standardnotes/api-gateway@1.41.2) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.41.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.41.0...@standardnotes/api-gateway@1.41.1) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.41.1",
|
||||
"version": "1.41.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.27.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@standardnotes/common": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
|
||||
@@ -13,8 +13,8 @@ ENCRYPTION_SERVER_KEY=change-me-!
|
||||
|
||||
PORT=3000
|
||||
|
||||
DB_HOST=localhost
|
||||
DB_REPLICA_HOST=localhost
|
||||
DB_HOST=127.0.0.1
|
||||
DB_REPLICA_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=auth
|
||||
DB_PASSWORD=changeme123
|
||||
|
||||
@@ -3,6 +3,100 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.74.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.74.0...@standardnotes/auth-server@1.74.1) (2022-12-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** migrations to not include unique index for credentials id ([384dfc8](https://github.com/standardnotes/server/commit/384dfc8da4b1b640964fa6da207a67fcd68dc7ec))
|
||||
|
||||
# [1.74.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.73.1...@standardnotes/auth-server@1.74.0) (2022-12-28)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add verifying authenticator registration response ([f5683cf](https://github.com/standardnotes/server/commit/f5683cfd9494db8e25010e9c4ef5fd4d8fcd6bc7))
|
||||
|
||||
## [1.73.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.73.0...@standardnotes/auth-server@1.73.1) (2022-12-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** temporarily remove credential id index due to mysql 5.6 limitations ([a5e7132](https://github.com/standardnotes/server/commit/a5e7132d3c4b74ed13877d7d437062c509201874))
|
||||
|
||||
# [1.73.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.72.0...@standardnotes/auth-server@1.73.0) (2022-12-28)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add generating authencator registration options ([51ad06b](https://github.com/standardnotes/server/commit/51ad06b303d7dc994920818872fdf8bd37fc445c))
|
||||
|
||||
# [1.72.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.71.1...@standardnotes/auth-server@1.72.0) (2022-12-28)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add authenticator challenges model ([fa9bf0b](https://github.com/standardnotes/server/commit/fa9bf0b448acb3f19ab44c4b431ce367dab37b76))
|
||||
|
||||
## [1.71.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.71.0...@standardnotes/auth-server@1.71.1) (2022-12-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** credential id field type ([98e3d18](https://github.com/standardnotes/server/commit/98e3d1833530dcd9e3e34a4c4a6b14a2a01afea1))
|
||||
|
||||
# [1.71.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.9...@standardnotes/auth-server@1.71.0) (2022-12-28)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add authenticators model ([1e69a13](https://github.com/standardnotes/server/commit/1e69a13a97c4d9022aa96397cce1b349d3cede89))
|
||||
|
||||
## [1.70.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.8...@standardnotes/auth-server@1.70.9) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.70.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.7...@standardnotes/auth-server@1.70.8) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** move tracing sessions to session creation instead of cross service token creation ([5255cfb](https://github.com/standardnotes/server/commit/5255cfbb257cc9e6ac437fe0c5b28d938e3e599b))
|
||||
|
||||
## [1.70.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.6...@standardnotes/auth-server@1.70.7) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** change severity on tracing session errors - most probably hazardous reads ([cf0b918](https://github.com/standardnotes/server/commit/cf0b91891370e1c1799ad80c10ee9f6b98087a94))
|
||||
|
||||
## [1.70.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.5...@standardnotes/auth-server@1.70.6) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** query for session traces ([14eb775](https://github.com/standardnotes/server/commit/14eb775749bfa9972dc3c07049505f3d15f0b556))
|
||||
|
||||
## [1.70.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.4...@standardnotes/auth-server@1.70.5) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add session traces index ([b9e1e47](https://github.com/standardnotes/server/commit/b9e1e4787129f00fab8f98cb721141f2e7d75600))
|
||||
|
||||
## [1.70.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.3...@standardnotes/auth-server@1.70.4) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** replace date object with number timestamp ([5b4bb6e](https://github.com/standardnotes/server/commit/5b4bb6e7a78a1b0f4e663bb990619f65f6a5c757))
|
||||
|
||||
## [1.70.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.2...@standardnotes/auth-server@1.70.3) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add persisting statistics for all subscription plans ([addedb3](https://github.com/standardnotes/server/commit/addedb3091ddae81618d56663e18f2ae76a43c4e))
|
||||
|
||||
## [1.70.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.1...@standardnotes/auth-server@1.70.2) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** docker command ([85d2f42](https://github.com/standardnotes/server/commit/85d2f42f473110e8dfca975bfecc7a56823bdef4))
|
||||
|
||||
## [1.70.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.0...@standardnotes/auth-server@1.70.1) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** saving subscription plan name in session traces ([3064d03](https://github.com/standardnotes/server/commit/3064d03aa9a2ac9ca3acfff30480ea8629faeb14))
|
||||
|
||||
# [1.70.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.69.1...@standardnotes/auth-server@1.70.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -24,7 +24,7 @@ case "$COMMAND" in
|
||||
yarn workspace @standardnotes/auth-server cleanup
|
||||
;;
|
||||
|
||||
'cleanup' )
|
||||
'stats' )
|
||||
echo "[Docker] Starting Persisting Stats..."
|
||||
yarn workspace @standardnotes/auth-server stats
|
||||
;;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class addSessionTracesCompoundIndex1671561748264 implements MigrationInterface {
|
||||
name = 'addSessionTracesCompoundIndex1671561748264'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE UNIQUE INDEX `user_uuid_and_creation_date` ON `session_traces` (`user_uuid`, `creation_date`)',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `user_uuid_and_creation_date` ON `session_traces`')
|
||||
}
|
||||
}
|
||||
15
packages/auth/migrations/1672223738686-add_authenticators.ts
Normal file
15
packages/auth/migrations/1672223738686-add_authenticators.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class addAuthenticators1672223738686 implements MigrationInterface {
|
||||
name = 'addAuthenticators1672223738686'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE `authenticators` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `credential_id` varbinary(1024) NOT NULL, `credential_public_key` blob NOT NULL, `counter` bigint NOT NULL, `credential_device_type` varchar(32) NOT NULL, `credential_backed_up` tinyint NOT NULL, `transports` varchar(255) NULL, `created_at` bigint NOT NULL, `updated_at` bigint NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP TABLE `authentticators`')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class addAuthenticatorChallenges1672227471677 implements MigrationInterface {
|
||||
name = 'addAuthenticatorChallenges1672227471677'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE `authenticator_challenges` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `challenge` varchar(255) NOT NULL, `created_at` bigint NOT NULL, INDEX `user_uuid_and_challenge` (`user_uuid`, `challenge`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `user_uuid_and_challenge` ON `authenticator_challenges`')
|
||||
await queryRunner.query('DROP TABLE `authenticator_challenges`')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class fixAuthenticatorDataTypes1672232035280 implements MigrationInterface {
|
||||
name = 'fixAuthenticatorDataTypes1672232035280'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `authenticators` DROP COLUMN `created_at`')
|
||||
await queryRunner.query('ALTER TABLE `authenticators` ADD `created_at` datetime NOT NULL')
|
||||
await queryRunner.query('ALTER TABLE `authenticators` DROP COLUMN `updated_at`')
|
||||
await queryRunner.query('ALTER TABLE `authenticators` ADD `updated_at` datetime NOT NULL')
|
||||
await queryRunner.query('ALTER TABLE `authenticator_challenges` DROP COLUMN `created_at`')
|
||||
await queryRunner.query('ALTER TABLE `authenticator_challenges` ADD `created_at` datetime NOT NULL')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `authenticator_challenges` DROP COLUMN `created_at`')
|
||||
await queryRunner.query('ALTER TABLE `authenticator_challenges` ADD `created_at` bigint NOT NULL')
|
||||
await queryRunner.query('ALTER TABLE `authenticators` DROP COLUMN `updated_at`')
|
||||
await queryRunner.query('ALTER TABLE `authenticators` ADD `updated_at` bigint NOT NULL')
|
||||
await queryRunner.query('ALTER TABLE `authenticators` DROP COLUMN `created_at`')
|
||||
await queryRunner.query('ALTER TABLE `authenticators` ADD `created_at` bigint NOT NULL')
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.70.0",
|
||||
"version": "1.74.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -33,9 +33,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.27.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@sentry/profiling-node": "^0.0.12",
|
||||
"@sentry/tracing": "^7.27.0",
|
||||
"@sentry/tracing": "^7.28.1",
|
||||
"@simplewebauthn/server": "^6.2.2",
|
||||
"@standardnotes/api": "^1.19.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
|
||||
@@ -203,6 +203,18 @@ import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
|
||||
import { TraceSession } from '../Domain/UseCase/TraceSession/TraceSession'
|
||||
import { CleanupSessionTraces } from '../Domain/UseCase/CleanupSessionTraces/CleanupSessionTraces'
|
||||
import { PersistStatistics } from '../Domain/UseCase/PersistStatistics/PersistStatistics'
|
||||
import { TypeORMAuthenticator } from '../Infra/TypeORM/TypeORMAuthenticator'
|
||||
import { Authenticator } from '../Domain/Authenticator/Authenticator'
|
||||
import { AuthenticatorPersistenceMapper } from '../Mapping/AuthenticatorPersistenceMapper'
|
||||
import { AuthenticatorChallenge } from '../Domain/Authenticator/AuthenticatorChallenge'
|
||||
import { TypeORMAuthenticatorChallenge } from '../Infra/TypeORM/TypeORMAuthenticatorChallenge'
|
||||
import { AuthenticatorChallengePersistenceMapper } from '../Mapping/AuthenticatorChallengePersistenceMapper'
|
||||
import { AuthenticatorRepositoryInterface } from '../Domain/Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { MySQLAuthenticatorRepository } from '../Infra/MySQL/MySQLAuthenticatorRepository'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../Domain/Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { MySQLAuthenticatorChallengeRepository } from '../Infra/MySQL/MySQLAuthenticatorChallengeRepository'
|
||||
import { GenerateAuthenticatorRegistrationOptions } from '../Domain/UseCase/GenerateAuthenticatorRegistrationOptions/GenerateAuthenticatorRegistrationOptions'
|
||||
import { VerifyAuthenticatorRegistrationResponse } from '../Domain/UseCase/VerifyAuthenticatorRegistrationResponse/VerifyAuthenticatorRegistrationResponse'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -280,6 +292,14 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<MapperInterface<SessionTrace, TypeORMSessionTrace>>(TYPES.SessionTracePersistenceMapper)
|
||||
.toConstantValue(new SessionTracePersistenceMapper())
|
||||
container
|
||||
.bind<MapperInterface<Authenticator, TypeORMAuthenticator>>(TYPES.AuthenticatorPersistenceMapper)
|
||||
.toConstantValue(new AuthenticatorPersistenceMapper())
|
||||
container
|
||||
.bind<MapperInterface<AuthenticatorChallenge, TypeORMAuthenticatorChallenge>>(
|
||||
TYPES.AuthenticatorChallengePersistenceMapper,
|
||||
)
|
||||
.toConstantValue(new AuthenticatorChallengePersistenceMapper())
|
||||
|
||||
// Controller
|
||||
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
||||
@@ -316,6 +336,12 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<Repository<TypeORMSessionTrace>>(TYPES.ORMSessionTraceRepository)
|
||||
.toConstantValue(AppDataSource.getRepository(TypeORMSessionTrace))
|
||||
container
|
||||
.bind<Repository<TypeORMAuthenticator>>(TYPES.ORMAuthenticatorRepository)
|
||||
.toConstantValue(AppDataSource.getRepository(TypeORMAuthenticator))
|
||||
container
|
||||
.bind<Repository<TypeORMAuthenticatorChallenge>>(TYPES.ORMAuthenticatorChallengeRepository)
|
||||
.toConstantValue(AppDataSource.getRepository(TypeORMAuthenticatorChallenge))
|
||||
|
||||
// Repositories
|
||||
container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository)
|
||||
@@ -355,6 +381,22 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.SessionTracePersistenceMapper),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AuthenticatorRepositoryInterface>(TYPES.AuthenticatorRepository)
|
||||
.toConstantValue(
|
||||
new MySQLAuthenticatorRepository(
|
||||
container.get(TYPES.ORMAuthenticatorRepository),
|
||||
container.get(TYPES.AuthenticatorPersistenceMapper),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AuthenticatorChallengeRepositoryInterface>(TYPES.AuthenticatorChallengeRepository)
|
||||
.toConstantValue(
|
||||
new MySQLAuthenticatorChallengeRepository(
|
||||
container.get(TYPES.ORMAuthenticatorChallengeRepository),
|
||||
container.get(TYPES.AuthenticatorChallengePersistenceMapper),
|
||||
),
|
||||
)
|
||||
|
||||
// Middleware
|
||||
container.bind<AuthMiddleware>(TYPES.AuthMiddleware).to(AuthMiddleware)
|
||||
@@ -508,6 +550,23 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.SessionTraceRepository),
|
||||
container.get(TYPES.DomainEventPublisher),
|
||||
container.get(TYPES.DomainEventFactory),
|
||||
container.get(TYPES.Timer),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GenerateAuthenticatorRegistrationOptions>(TYPES.GenerateAuthenticatorRegistrationOptions)
|
||||
.toConstantValue(
|
||||
new GenerateAuthenticatorRegistrationOptions(
|
||||
container.get(TYPES.AuthenticatorRepository),
|
||||
container.get(TYPES.AuthenticatorChallengeRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<VerifyAuthenticatorRegistrationResponse>(TYPES.VerifyAuthenticatorRegistrationResponse)
|
||||
.toConstantValue(
|
||||
new VerifyAuthenticatorRegistrationResponse(
|
||||
container.get(TYPES.AuthenticatorRepository),
|
||||
container.get(TYPES.AuthenticatorChallengeRepository),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import { SharedSubscriptionInvitation } from '../Domain/SharedSubscription/Share
|
||||
import { OfflineUserSubscription } from '../Domain/Subscription/OfflineUserSubscription'
|
||||
import { UserSubscription } from '../Domain/Subscription/UserSubscription'
|
||||
import { User } from '../Domain/User/User'
|
||||
import { TypeORMAuthenticator } from '../Infra/TypeORM/TypeORMAuthenticator'
|
||||
import { TypeORMAuthenticatorChallenge } from '../Infra/TypeORM/TypeORMAuthenticatorChallenge'
|
||||
import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
|
||||
import { Env } from './Env'
|
||||
|
||||
@@ -58,6 +60,8 @@ export const AppDataSource = new DataSource({
|
||||
SharedSubscriptionInvitation,
|
||||
SubscriptionSetting,
|
||||
TypeORMSessionTrace,
|
||||
TypeORMAuthenticator,
|
||||
TypeORMAuthenticatorChallenge,
|
||||
],
|
||||
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
|
||||
migrationsRun: true,
|
||||
|
||||
@@ -5,6 +5,8 @@ const TYPES = {
|
||||
SQS: Symbol.for('SQS'),
|
||||
// Mapping
|
||||
SessionTracePersistenceMapper: Symbol.for('SessionTracePersistenceMapper'),
|
||||
AuthenticatorChallengePersistenceMapper: Symbol.for('AuthenticatorChallengePersistenceMapper'),
|
||||
AuthenticatorPersistenceMapper: Symbol.for('AuthenticatorPersistenceMapper'),
|
||||
// Controller
|
||||
AuthController: Symbol.for('AuthController'),
|
||||
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
|
||||
@@ -26,6 +28,8 @@ const TYPES = {
|
||||
SharedSubscriptionInvitationRepository: Symbol.for('SharedSubscriptionInvitationRepository'),
|
||||
PKCERepository: Symbol.for('PKCERepository'),
|
||||
SessionTraceRepository: Symbol.for('SessionTraceRepository'),
|
||||
AuthenticatorRepository: Symbol.for('AuthenticatorRepository'),
|
||||
AuthenticatorChallengeRepository: Symbol.for('AuthenticatorChallengeRepository'),
|
||||
// ORM
|
||||
ORMOfflineSettingRepository: Symbol.for('ORMOfflineSettingRepository'),
|
||||
ORMOfflineUserSubscriptionRepository: Symbol.for('ORMOfflineUserSubscriptionRepository'),
|
||||
@@ -38,6 +42,8 @@ const TYPES = {
|
||||
ORMUserRepository: Symbol.for('ORMUserRepository'),
|
||||
ORMUserSubscriptionRepository: Symbol.for('ORMUserSubscriptionRepository'),
|
||||
ORMSessionTraceRepository: Symbol.for('ORMSessionTraceRepository'),
|
||||
ORMAuthenticatorRepository: Symbol.for('ORMAuthenticatorRepository'),
|
||||
ORMAuthenticatorChallengeRepository: Symbol.for('ORMAuthenticatorChallengeRepository'),
|
||||
// Middleware
|
||||
AuthMiddleware: Symbol.for('AuthMiddleware'),
|
||||
ApiGatewayAuthMiddleware: Symbol.for('ApiGatewayAuthMiddleware'),
|
||||
@@ -127,6 +133,8 @@ const TYPES = {
|
||||
TraceSession: Symbol.for('TraceSession'),
|
||||
CleanupSessionTraces: Symbol.for('CleanupSessionTraces'),
|
||||
PersistStatistics: Symbol.for('PersistStatistics'),
|
||||
GenerateAuthenticatorRegistrationOptions: Symbol.for('GenerateAuthenticatorRegistrationOptions'),
|
||||
VerifyAuthenticatorRegistrationResponse: Symbol.for('VerifyAuthenticatorRegistrationResponse'),
|
||||
// Handlers
|
||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||
|
||||
21
packages/auth/src/Domain/Authenticator/Authenticator.spec.ts
Normal file
21
packages/auth/src/Domain/Authenticator/Authenticator.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Dates, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Authenticator } from './Authenticator'
|
||||
|
||||
describe('Authenticator', () => {
|
||||
it('should create an entity', () => {
|
||||
const entityOrError = Authenticator.create({
|
||||
counter: 1,
|
||||
credentialBackedUp: true,
|
||||
credentialDeviceType: 'singleDevice',
|
||||
credentialId: Buffer.from('credentialId'),
|
||||
credentialPublicKey: Buffer.from('credentialPublicKey'),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
dates: Dates.create(new Date(1), new Date(1)).getValue(),
|
||||
transports: ['usb'],
|
||||
})
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(entityOrError.getValue().id).not.toBeNull()
|
||||
})
|
||||
})
|
||||
17
packages/auth/src/Domain/Authenticator/Authenticator.ts
Normal file
17
packages/auth/src/Domain/Authenticator/Authenticator.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
import { AuthenticatorProps } from './AuthenticatorProps'
|
||||
|
||||
export class Authenticator extends Entity<AuthenticatorProps> {
|
||||
get id(): UniqueEntityId {
|
||||
return this._id
|
||||
}
|
||||
|
||||
private constructor(props: AuthenticatorProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
static create(props: AuthenticatorProps, id?: UniqueEntityId): Result<Authenticator> {
|
||||
return Result.ok<Authenticator>(new Authenticator(props, id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { AuthenticatorChallenge } from './AuthenticatorChallenge'
|
||||
|
||||
describe('AuthenticatorChallenge', () => {
|
||||
it('should create an entity', () => {
|
||||
const entityOrError = AuthenticatorChallenge.create({
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
createdAt: new Date(1),
|
||||
challenge: Buffer.from('challenge'),
|
||||
})
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(entityOrError.getValue().id).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
import { AuthenticatorChallengeProps } from './AuthenticatorChallengeProps'
|
||||
|
||||
export class AuthenticatorChallenge extends Entity<AuthenticatorChallengeProps> {
|
||||
get id(): UniqueEntityId {
|
||||
return this._id
|
||||
}
|
||||
|
||||
private constructor(props: AuthenticatorChallengeProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
static create(props: AuthenticatorChallengeProps, id?: UniqueEntityId): Result<AuthenticatorChallenge> {
|
||||
return Result.ok<AuthenticatorChallenge>(new AuthenticatorChallenge(props, id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export interface AuthenticatorChallengeProps {
|
||||
userUuid: Uuid
|
||||
challenge: Buffer
|
||||
createdAt: Date
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { AuthenticatorChallenge } from './AuthenticatorChallenge'
|
||||
|
||||
export interface AuthenticatorChallengeRepositoryInterface {
|
||||
findByUserUuidAndChallenge(userUuid: Uuid, challenge: Buffer): Promise<AuthenticatorChallenge | null>
|
||||
save(authenticatorChallenge: AuthenticatorChallenge): Promise<void>
|
||||
}
|
||||
12
packages/auth/src/Domain/Authenticator/AuthenticatorProps.ts
Normal file
12
packages/auth/src/Domain/Authenticator/AuthenticatorProps.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Dates, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export interface AuthenticatorProps {
|
||||
userUuid: Uuid
|
||||
credentialId: Buffer
|
||||
credentialPublicKey: Buffer
|
||||
counter: number
|
||||
credentialDeviceType: string
|
||||
credentialBackedUp: boolean
|
||||
transports?: string[]
|
||||
dates: Dates
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { Authenticator } from './Authenticator'
|
||||
|
||||
export interface AuthenticatorRepositoryInterface {
|
||||
findByUserUuid(userUuid: Uuid): Promise<Authenticator[]>
|
||||
save(authenticator: Authenticator): Promise<void>
|
||||
}
|
||||
4
packages/auth/src/Domain/Authenticator/RelyingParty.ts
Normal file
4
packages/auth/src/Domain/Authenticator/RelyingParty.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum RelyingParty {
|
||||
RP_NAME = 'Standard Notes',
|
||||
RP_ID = 'standardnotes.com',
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
createStatisticPersistenceRequestedEvent(dto: {
|
||||
statisticMeasureName: string
|
||||
value: number
|
||||
date: Date
|
||||
date: number
|
||||
}): StatisticPersistenceRequestedEvent {
|
||||
return {
|
||||
type: 'STATISTIC_PERSISTENCE_REQUESTED',
|
||||
|
||||
@@ -92,6 +92,6 @@ export interface DomainEventFactoryInterface {
|
||||
createStatisticPersistenceRequestedEvent(dto: {
|
||||
statisticMeasureName: string
|
||||
value: number
|
||||
date: Date
|
||||
date: number
|
||||
}): StatisticPersistenceRequestedEvent
|
||||
}
|
||||
|
||||
@@ -41,4 +41,23 @@ describe('RoleToSubscriptionMap', () => {
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should filter our subscription roles from an array of roles', () => {
|
||||
const roles = [
|
||||
{
|
||||
name: RoleName.CoreUser,
|
||||
} as jest.Mocked<Role>,
|
||||
{
|
||||
name: RoleName.FilesBetaUser,
|
||||
} as jest.Mocked<Role>,
|
||||
{
|
||||
name: RoleName.PlusUser,
|
||||
} as jest.Mocked<Role>,
|
||||
]
|
||||
expect(createMap().filterSubscriptionRoles(roles)).toEqual([
|
||||
{
|
||||
name: RoleName.PlusUser,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,6 +17,10 @@ export class RoleToSubscriptionMap implements RoleToSubscriptionMapInterface {
|
||||
return roles.filter((role) => this.nonSubscriptionRoles.includes(role.name as RoleName))
|
||||
}
|
||||
|
||||
filterSubscriptionRoles(roles: Role[]): Array<Role> {
|
||||
return roles.filter((role) => !this.nonSubscriptionRoles.includes(role.name as RoleName))
|
||||
}
|
||||
|
||||
getSubscriptionNameForRoleName(roleName: RoleName): SubscriptionName | undefined {
|
||||
return this.roleNameToSubscriptionNameMap.get(roleName)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Role } from './Role'
|
||||
|
||||
export interface RoleToSubscriptionMapInterface {
|
||||
filterNonSubscriptionRoles(roles: Role[]): Array<Role>
|
||||
filterSubscriptionRoles(roles: Role[]): Array<Role>
|
||||
getSubscriptionNameForRoleName(roleName: RoleName): SubscriptionName | undefined
|
||||
getRoleNameForSubscriptionName(subscriptionName: SubscriptionName): RoleName | undefined
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { LogSessionUserAgentOption } from '@standardnotes/settings'
|
||||
import { Setting } from '../Setting/Setting'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { TraceSession } from '../UseCase/TraceSession/TraceSession'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
describe('SessionService', () => {
|
||||
let sessionRepository: SessionRepositoryInterface
|
||||
@@ -28,6 +32,8 @@ describe('SessionService', () => {
|
||||
let timer: TimerInterface
|
||||
let logger: winston.Logger
|
||||
let cryptoNode: CryptoNode
|
||||
let traceSession: TraceSession
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
|
||||
const createService = () =>
|
||||
new SessionService(
|
||||
@@ -41,6 +47,8 @@ describe('SessionService', () => {
|
||||
234,
|
||||
settingService,
|
||||
cryptoNode,
|
||||
traceSession,
|
||||
userSubscriptionRepository,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -106,6 +114,14 @@ describe('SessionService', () => {
|
||||
cryptoNode = {} as jest.Mocked<CryptoNode>
|
||||
cryptoNode.generateRandomKey = jest.fn().mockReturnValue('foo bar')
|
||||
cryptoNode.base64URLEncode = jest.fn().mockReturnValue('foobar')
|
||||
|
||||
traceSession = {} as jest.Mocked<TraceSession>
|
||||
traceSession.execute = jest.fn()
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue({
|
||||
planName: 'PRO_PLAN',
|
||||
} as jest.Mocked<UserSubscription>)
|
||||
})
|
||||
|
||||
it('should mark a revoked session as received', async () => {
|
||||
@@ -204,6 +220,129 @@ describe('SessionService', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should trace a session', async () => {
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
user.email = 'test@test.te'
|
||||
|
||||
await createService().createNewSessionForUser({
|
||||
user,
|
||||
apiVersion: '003',
|
||||
userAgent: 'Google Chrome',
|
||||
readonlyAccess: false,
|
||||
})
|
||||
|
||||
expect(traceSession.execute).toHaveBeenCalledWith({
|
||||
userUuid: '123',
|
||||
username: 'test@test.te',
|
||||
subscriptionPlanName: 'PRO_PLAN',
|
||||
})
|
||||
})
|
||||
|
||||
it('should trace a session without a subscription', async () => {
|
||||
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue(null)
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
user.email = 'test@test.te'
|
||||
|
||||
await createService().createNewSessionForUser({
|
||||
user,
|
||||
apiVersion: '003',
|
||||
userAgent: 'Google Chrome',
|
||||
readonlyAccess: false,
|
||||
})
|
||||
|
||||
expect(traceSession.execute).toHaveBeenCalledWith({
|
||||
userUuid: '123',
|
||||
username: 'test@test.te',
|
||||
subscriptionPlanName: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a session if tracing session throws an error', async () => {
|
||||
traceSession.execute = jest.fn().mockRejectedValue(new Error('foo bar'))
|
||||
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue(null)
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
user.email = 'test@test.te'
|
||||
|
||||
const sessionPayload = await createService().createNewSessionForUser({
|
||||
user,
|
||||
apiVersion: '003',
|
||||
userAgent: 'Google Chrome',
|
||||
readonlyAccess: false,
|
||||
})
|
||||
|
||||
expect(traceSession.execute).toHaveBeenCalledWith({
|
||||
userUuid: '123',
|
||||
username: 'test@test.te',
|
||||
subscriptionPlanName: null,
|
||||
})
|
||||
expect(sessionPayload).toEqual({
|
||||
access_expiration: 123,
|
||||
access_token: expect.any(String),
|
||||
refresh_expiration: 123,
|
||||
refresh_token: expect.any(String),
|
||||
readonly_access: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a session if tracing session throws an error', async () => {
|
||||
traceSession.execute = jest.fn().mockRejectedValue(new Error('foo bar'))
|
||||
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue(null)
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
user.email = 'test@test.te'
|
||||
|
||||
const sessionPayload = await createService().createNewSessionForUser({
|
||||
user,
|
||||
apiVersion: '003',
|
||||
userAgent: 'Google Chrome',
|
||||
readonlyAccess: false,
|
||||
})
|
||||
|
||||
expect(traceSession.execute).toHaveBeenCalledWith({
|
||||
userUuid: '123',
|
||||
username: 'test@test.te',
|
||||
subscriptionPlanName: null,
|
||||
})
|
||||
expect(sessionPayload).toEqual({
|
||||
access_expiration: 123,
|
||||
access_token: expect.any(String),
|
||||
refresh_expiration: 123,
|
||||
refresh_token: expect.any(String),
|
||||
readonly_access: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create a session if tracing session fails', async () => {
|
||||
traceSession.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue(null)
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
user.email = 'test@test.te'
|
||||
|
||||
const sessionPayload = await createService().createNewSessionForUser({
|
||||
user,
|
||||
apiVersion: '003',
|
||||
userAgent: 'Google Chrome',
|
||||
readonlyAccess: false,
|
||||
})
|
||||
|
||||
expect(traceSession.execute).toHaveBeenCalledWith({
|
||||
userUuid: '123',
|
||||
username: 'test@test.te',
|
||||
subscriptionPlanName: null,
|
||||
})
|
||||
expect(sessionPayload).toEqual({
|
||||
access_expiration: 123,
|
||||
access_token: expect.any(String),
|
||||
refresh_expiration: 123,
|
||||
refresh_token: expect.any(String),
|
||||
readonly_access: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create new ephemeral session for a user', async () => {
|
||||
const user = {} as jest.Mocked<User>
|
||||
user.uuid = '123'
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import * as crypto from 'crypto'
|
||||
import * as winston from 'winston'
|
||||
import * as dayjs from 'dayjs'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Logger } from 'winston'
|
||||
import { LogSessionUserAgentOption, SettingName } from '@standardnotes/settings'
|
||||
import { SessionBody } from '@standardnotes/responses'
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { Session } from './Session'
|
||||
@@ -16,10 +20,8 @@ import { EphemeralSession } from './EphemeralSession'
|
||||
import { RevokedSession } from './RevokedSession'
|
||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { LogSessionUserAgentOption, SettingName } from '@standardnotes/settings'
|
||||
import { SessionBody } from '@standardnotes/responses'
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
import { TraceSession } from '../UseCase/TraceSession/TraceSession'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
|
||||
@injectable()
|
||||
export class SessionService implements SessionServiceInterface {
|
||||
@@ -31,11 +33,13 @@ export class SessionService implements SessionServiceInterface {
|
||||
@inject(TYPES.RevokedSessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
||||
@inject(TYPES.DeviceDetector) private deviceDetector: UAParser,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.Logger) private logger: winston.Logger,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@inject(TYPES.ACCESS_TOKEN_AGE) private accessTokenAge: number,
|
||||
@inject(TYPES.REFRESH_TOKEN_AGE) private refreshTokenAge: number,
|
||||
@inject(TYPES.SettingService) private settingService: SettingServiceInterface,
|
||||
@inject(TYPES.CryptoNode) private cryptoNode: CryptoNode,
|
||||
@inject(TYPES.TraceSession) private traceSession: TraceSession,
|
||||
@inject(TYPES.UserSubscriptionRepository) private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async createNewSessionForUser(dto: {
|
||||
@@ -53,6 +57,20 @@ export class SessionService implements SessionServiceInterface {
|
||||
|
||||
await this.sessionRepository.save(session)
|
||||
|
||||
try {
|
||||
const userSubscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.user.uuid)
|
||||
const traceSessionResult = await this.traceSession.execute({
|
||||
userUuid: dto.user.uuid,
|
||||
username: dto.user.email,
|
||||
subscriptionPlanName: userSubscription ? userSubscription.planName : null,
|
||||
})
|
||||
if (traceSessionResult.isFailed()) {
|
||||
this.logger.error(traceSessionResult.getError())
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Could not trace session while creating cross service token.: ${(error as Error).message}`)
|
||||
}
|
||||
|
||||
return sessionPayload
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { SubscriptionPlanName, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SessionTrace } from './SessionTrace'
|
||||
|
||||
@@ -7,4 +7,5 @@ export interface SessionTraceRepositoryInterface {
|
||||
removeExpiredBefore(date: Date): Promise<void>
|
||||
findOneByUserUuidAndDate(userUuid: Uuid, date: Date): Promise<SessionTrace | null>
|
||||
countByDate(date: Date): Promise<number>
|
||||
countByDateAndSubscriptionPlanName(date: Date, subscriptionPlanName: SubscriptionPlanName): Promise<number>
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ import { Role } from '../../Role/Role'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
|
||||
import { CreateCrossServiceToken } from './CreateCrossServiceToken'
|
||||
import { RoleToSubscriptionMapInterface } from '../../Role/RoleToSubscriptionMapInterface'
|
||||
import { TraceSession } from '../TraceSession/TraceSession'
|
||||
import { Logger } from 'winston'
|
||||
import { Result, RoleName, SubscriptionPlanName } from '@standardnotes/domain-core'
|
||||
|
||||
describe('CreateCrossServiceToken', () => {
|
||||
let userProjector: ProjectorInterface<User>
|
||||
@@ -19,9 +15,6 @@ describe('CreateCrossServiceToken', () => {
|
||||
let roleProjector: ProjectorInterface<Role>
|
||||
let tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>
|
||||
let userRepository: UserRepositoryInterface
|
||||
let roleToSubscriptionMap: RoleToSubscriptionMapInterface
|
||||
let traceSession: TraceSession
|
||||
let logger: Logger
|
||||
const jwtTTL = 60
|
||||
|
||||
let session: Session
|
||||
@@ -29,17 +22,7 @@ describe('CreateCrossServiceToken', () => {
|
||||
let role: Role
|
||||
|
||||
const createUseCase = () =>
|
||||
new CreateCrossServiceToken(
|
||||
userProjector,
|
||||
sessionProjector,
|
||||
roleProjector,
|
||||
tokenEncoder,
|
||||
userRepository,
|
||||
jwtTTL,
|
||||
roleToSubscriptionMap,
|
||||
traceSession,
|
||||
logger,
|
||||
)
|
||||
new CreateCrossServiceToken(userProjector, sessionProjector, roleProjector, tokenEncoder, userRepository, jwtTTL)
|
||||
|
||||
beforeEach(() => {
|
||||
session = {} as jest.Mocked<Session>
|
||||
@@ -65,18 +48,6 @@ describe('CreateCrossServiceToken', () => {
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
||||
|
||||
roleToSubscriptionMap = {} as jest.Mocked<RoleToSubscriptionMapInterface>
|
||||
roleToSubscriptionMap.filterNonSubscriptionRoles = jest.fn().mockReturnValue([RoleName.NAMES.PlusUser])
|
||||
roleToSubscriptionMap.getSubscriptionNameForRoleName = jest
|
||||
.fn()
|
||||
.mockReturnValue(SubscriptionPlanName.NAMES.PlusPlan)
|
||||
|
||||
traceSession = {} as jest.Mocked<TraceSession>
|
||||
traceSession.execute = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('should create a cross service token for user', async () => {
|
||||
@@ -85,11 +56,6 @@ describe('CreateCrossServiceToken', () => {
|
||||
session,
|
||||
})
|
||||
|
||||
expect(traceSession.execute).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
username: 'test@test.te',
|
||||
subscriptionPlanName: 'PLUS_PLAN',
|
||||
})
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
@@ -168,126 +134,4 @@ describe('CreateCrossServiceToken', () => {
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should trace session without a subscription role', async () => {
|
||||
roleToSubscriptionMap.filterNonSubscriptionRoles = jest.fn().mockReturnValue([])
|
||||
|
||||
await createUseCase().execute({
|
||||
user,
|
||||
session,
|
||||
})
|
||||
|
||||
expect(traceSession.execute).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
username: 'test@test.te',
|
||||
subscriptionPlanName: null,
|
||||
})
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
{
|
||||
name: 'role1',
|
||||
uuid: '1-3-4',
|
||||
},
|
||||
],
|
||||
session: {
|
||||
test: 'test',
|
||||
},
|
||||
user: {
|
||||
email: 'test@test.te',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
|
||||
it('should trace session without a subscription', async () => {
|
||||
roleToSubscriptionMap.getSubscriptionNameForRoleName = jest.fn().mockReturnValue(undefined)
|
||||
|
||||
await createUseCase().execute({
|
||||
user,
|
||||
session,
|
||||
})
|
||||
|
||||
expect(traceSession.execute).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
username: 'test@test.te',
|
||||
subscriptionPlanName: null,
|
||||
})
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
{
|
||||
name: 'role1',
|
||||
uuid: '1-3-4',
|
||||
},
|
||||
],
|
||||
session: {
|
||||
test: 'test',
|
||||
},
|
||||
user: {
|
||||
email: 'test@test.te',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
|
||||
it('should create token if tracing session throws an error', async () => {
|
||||
traceSession.execute = jest.fn().mockRejectedValue(new Error('test'))
|
||||
|
||||
await createUseCase().execute({
|
||||
user,
|
||||
session,
|
||||
})
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
{
|
||||
name: 'role1',
|
||||
uuid: '1-3-4',
|
||||
},
|
||||
],
|
||||
session: {
|
||||
test: 'test',
|
||||
},
|
||||
user: {
|
||||
email: 'test@test.te',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
|
||||
it('should create token if tracing session fails', async () => {
|
||||
traceSession.execute = jest.fn().mockReturnValue(Result.fail('Ooops'))
|
||||
|
||||
await createUseCase().execute({
|
||||
user,
|
||||
session,
|
||||
})
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
{
|
||||
name: 'role1',
|
||||
uuid: '1-3-4',
|
||||
},
|
||||
],
|
||||
session: {
|
||||
test: 'test',
|
||||
},
|
||||
user: {
|
||||
email: 'test@test.te',
|
||||
uuid: '1-2-3',
|
||||
},
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
import { TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
|
||||
import { Role } from '../../Role/Role'
|
||||
import { RoleToSubscriptionMapInterface } from '../../Role/RoleToSubscriptionMapInterface'
|
||||
import { Session } from '../../Session/Session'
|
||||
import { User } from '../../User/User'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
import { TraceSession } from '../TraceSession/TraceSession'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
|
||||
import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
|
||||
@@ -25,9 +22,6 @@ export class CreateCrossServiceToken implements UseCaseInterface {
|
||||
@inject(TYPES.CrossServiceTokenEncoder) private tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>,
|
||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.AUTH_JWT_TTL) private jwtTTL: number,
|
||||
@inject(TYPES.RoleToSubscriptionMap) private roleToSubscriptionMap: RoleToSubscriptionMapInterface,
|
||||
@inject(TYPES.TraceSession) private traceSession: TraceSession,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateCrossServiceTokenDTO): Promise<CreateCrossServiceTokenResponse> {
|
||||
@@ -51,19 +45,6 @@ export class CreateCrossServiceToken implements UseCaseInterface {
|
||||
authTokenData.session = this.projectSession(dto.session)
|
||||
}
|
||||
|
||||
try {
|
||||
const traceSessionResult = await this.traceSession.execute({
|
||||
userUuid: user.uuid,
|
||||
username: user.email,
|
||||
subscriptionPlanName: this.getSubscriptionNameFromRoles(roles),
|
||||
})
|
||||
if (traceSessionResult.isFailed()) {
|
||||
this.logger.error(traceSessionResult.getError())
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Could not trace session while creating cross service token: ${(error as Error).message}`)
|
||||
}
|
||||
|
||||
return {
|
||||
token: this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL),
|
||||
}
|
||||
@@ -100,17 +81,4 @@ export class CreateCrossServiceToken implements UseCaseInterface {
|
||||
private projectRoles(roles: Array<Role>): Array<{ uuid: string; name: RoleName }> {
|
||||
return roles.map((role) => <{ uuid: string; name: RoleName }>this.roleProjector.projectSimple(role))
|
||||
}
|
||||
|
||||
private getSubscriptionNameFromRoles(roles: Array<Role>): string | null {
|
||||
const nonSubscriptionRoles = this.roleToSubscriptionMap.filterNonSubscriptionRoles(roles)
|
||||
if (nonSubscriptionRoles.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const subscriptionName = this.roleToSubscriptionMap.getSubscriptionNameForRoleName(
|
||||
nonSubscriptionRoles[0].name as RoleName,
|
||||
)
|
||||
|
||||
return subscriptionName === undefined ? null : subscriptionName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Dates, Result, Uuid } from '@standardnotes/domain-core'
|
||||
import { Authenticator } from '../../Authenticator/Authenticator'
|
||||
|
||||
import { AuthenticatorChallenge } from '../../Authenticator/AuthenticatorChallenge'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { GenerateAuthenticatorRegistrationOptions } from './GenerateAuthenticatorRegistrationOptions'
|
||||
|
||||
describe('GenerateAuthenticatorRegistrationOptions', () => {
|
||||
let authenticatorRepository: AuthenticatorRepositoryInterface
|
||||
let authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new GenerateAuthenticatorRegistrationOptions(authenticatorRepository, authenticatorChallengeRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
const authenticator = Authenticator.create({
|
||||
counter: 1,
|
||||
credentialBackedUp: true,
|
||||
credentialDeviceType: 'singleDevice',
|
||||
credentialId: Buffer.from('credentialId'),
|
||||
credentialPublicKey: Buffer.from('credentialPublicKey'),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
dates: Dates.create(new Date(1), new Date(1)).getValue(),
|
||||
transports: ['usb'],
|
||||
}).getValue()
|
||||
|
||||
authenticatorRepository = {} as jest.Mocked<AuthenticatorRepositoryInterface>
|
||||
authenticatorRepository.findByUserUuid = jest.fn().mockReturnValue([authenticator])
|
||||
|
||||
authenticatorChallengeRepository = {} as jest.Mocked<AuthenticatorChallengeRepositoryInterface>
|
||||
authenticatorChallengeRepository.save = jest.fn()
|
||||
})
|
||||
|
||||
it('should return error if userUuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
username: 'username',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe(
|
||||
'Could not generate authenticator registration options: Given value is not a valid uuid: invalid',
|
||||
)
|
||||
})
|
||||
|
||||
it('should return error if username is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
username: '',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Could not generate authenticator registration options: Username cannot be empty')
|
||||
})
|
||||
|
||||
it('should return error if authenticator challenge is invalid', async () => {
|
||||
const mock = jest.spyOn(AuthenticatorChallenge, 'create')
|
||||
mock.mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
username: 'username',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Could not generate authenticator registration options: Oops')
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should return registration options', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
username: 'username',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(authenticatorChallengeRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Result, UseCaseInterface, Username, Uuid } from '@standardnotes/domain-core'
|
||||
import { generateRegistrationOptions } from '@simplewebauthn/server'
|
||||
|
||||
import { GenerateAuthenticatorRegistrationOptionsDTO } from './GenerateAuthenticatorRegistrationOptionsDTO'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { AuthenticatorChallenge } from '../../Authenticator/AuthenticatorChallenge'
|
||||
import { RelyingParty } from '../../Authenticator/RelyingParty'
|
||||
|
||||
export class GenerateAuthenticatorRegistrationOptions implements UseCaseInterface<Record<string, unknown>> {
|
||||
constructor(
|
||||
private authenticatorRepository: AuthenticatorRepositoryInterface,
|
||||
private authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: GenerateAuthenticatorRegistrationOptionsDTO): Promise<Result<Record<string, unknown>>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(`Could not generate authenticator registration options: ${userUuidOrError.getError()}`)
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const usernameOrError = Username.create(dto.username)
|
||||
if (usernameOrError.isFailed()) {
|
||||
return Result.fail(`Could not generate authenticator registration options: ${usernameOrError.getError()}`)
|
||||
}
|
||||
const username = usernameOrError.getValue()
|
||||
|
||||
const authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
|
||||
const options = generateRegistrationOptions({
|
||||
rpID: RelyingParty.RP_ID,
|
||||
rpName: RelyingParty.RP_NAME,
|
||||
userID: userUuid.value,
|
||||
userName: username.value,
|
||||
attestationType: 'none',
|
||||
excludeCredentials: authenticators.map((authenticator) => ({
|
||||
id: authenticator.props.credentialId,
|
||||
type: 'public-key',
|
||||
transports: authenticator.props.transports,
|
||||
})),
|
||||
})
|
||||
|
||||
const authenticatorChallengeOrError = AuthenticatorChallenge.create({
|
||||
challenge: options.challenge,
|
||||
userUuid,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
if (authenticatorChallengeOrError.isFailed()) {
|
||||
return Result.fail(
|
||||
`Could not generate authenticator registration options: ${authenticatorChallengeOrError.getError()}`,
|
||||
)
|
||||
}
|
||||
const authenticatorChallenge = authenticatorChallengeOrError.getValue()
|
||||
|
||||
await this.authenticatorChallengeRepository.save(authenticatorChallenge)
|
||||
|
||||
return Result.ok(options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface GenerateAuthenticatorRegistrationOptionsDTO {
|
||||
userUuid: string
|
||||
username: string
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DomainEventPublisherInterface, StatisticPersistenceRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
import { SessionTraceRepositoryInterface } from '../../Session/SessionTraceRepositoryInterface'
|
||||
@@ -9,11 +10,15 @@ describe('PersistStatistics', () => {
|
||||
let sessionTracesRepository: SessionTraceRepositoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
const createUseCase = () => new PersistStatistics(sessionTracesRepository, domainEventPublisher, domainEventFactory)
|
||||
let timer: TimerInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new PersistStatistics(sessionTracesRepository, domainEventPublisher, domainEventFactory, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
sessionTracesRepository = {} as jest.Mocked<SessionTraceRepositoryInterface>
|
||||
sessionTracesRepository.countByDate = jest.fn().mockReturnValue(1)
|
||||
sessionTracesRepository.countByDateAndSubscriptionPlanName = jest.fn().mockReturnValue(2)
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createStatisticPersistenceRequestedEvent = jest
|
||||
@@ -22,11 +27,14 @@ describe('PersistStatistics', () => {
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.convertDateToMicroseconds = jest.fn().mockReturnValue(3)
|
||||
})
|
||||
|
||||
it('should request statistic persistence', async () => {
|
||||
await createUseCase().execute({ sessionsInADay: new Date() })
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { Result, SubscriptionPlanName, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
import { SessionTraceRepositoryInterface } from '../../Session/SessionTraceRepositoryInterface'
|
||||
import { PersistStatisticsDTO } from './PersistStatisticsDTO'
|
||||
@@ -9,16 +11,51 @@ export class PersistStatistics implements UseCaseInterface<string> {
|
||||
private sessionTracesRepository: SessionTraceRepositoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: PersistStatisticsDTO): Promise<Result<string>> {
|
||||
const countSessionsInADay = await this.sessionTracesRepository.countByDate(dto.sessionsInADay)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createStatisticPersistenceRequestedEvent({
|
||||
statisticMeasureName: 'active-users',
|
||||
value: countSessionsInADay,
|
||||
date: dto.sessionsInADay,
|
||||
date: this.timer.convertDateToMicroseconds(dto.sessionsInADay),
|
||||
}),
|
||||
)
|
||||
|
||||
const proSubscriptionPlanName = SubscriptionPlanName.create(SubscriptionPlanName.NAMES.ProPlan).getValue()
|
||||
const countProSessionsInADay = await this.sessionTracesRepository.countByDateAndSubscriptionPlanName(
|
||||
dto.sessionsInADay,
|
||||
proSubscriptionPlanName,
|
||||
)
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createStatisticPersistenceRequestedEvent({
|
||||
statisticMeasureName: 'active-pro-users',
|
||||
value: countProSessionsInADay,
|
||||
date: this.timer.convertDateToMicroseconds(dto.sessionsInADay),
|
||||
}),
|
||||
)
|
||||
|
||||
const plusSubscriptionPlanName = SubscriptionPlanName.create(SubscriptionPlanName.NAMES.PlusPlan).getValue()
|
||||
const countPlusSessionsInADay = await this.sessionTracesRepository.countByDateAndSubscriptionPlanName(
|
||||
dto.sessionsInADay,
|
||||
plusSubscriptionPlanName,
|
||||
)
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createStatisticPersistenceRequestedEvent({
|
||||
statisticMeasureName: 'active-plus-users',
|
||||
value: countPlusSessionsInADay,
|
||||
date: this.timer.convertDateToMicroseconds(dto.sessionsInADay),
|
||||
}),
|
||||
)
|
||||
|
||||
const countFreeSessionsInADay = countSessionsInADay - countProSessionsInADay - countPlusSessionsInADay
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createStatisticPersistenceRequestedEvent({
|
||||
statisticMeasureName: 'active-free-users',
|
||||
value: countFreeSessionsInADay,
|
||||
date: this.timer.convertDateToMicroseconds(dto.sessionsInADay),
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import * as simeplWebAuthnServer from '@simplewebauthn/server'
|
||||
import { VerifiedRegistrationResponse } from '@simplewebauthn/server'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { Authenticator } from '../../Authenticator/Authenticator'
|
||||
|
||||
import { AuthenticatorChallenge } from '../../Authenticator/AuthenticatorChallenge'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { VerifyAuthenticatorRegistrationResponse } from './VerifyAuthenticatorRegistrationResponse'
|
||||
|
||||
describe('VerifyAuthenticatorRegistrationResponse', () => {
|
||||
let authenticatorRepository: AuthenticatorRepositoryInterface
|
||||
let authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new VerifyAuthenticatorRegistrationResponse(authenticatorRepository, authenticatorChallengeRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
authenticatorRepository = {} as jest.Mocked<AuthenticatorRepositoryInterface>
|
||||
authenticatorRepository.save = jest.fn()
|
||||
|
||||
authenticatorChallengeRepository = {} as jest.Mocked<AuthenticatorChallengeRepositoryInterface>
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
} as jest.Mocked<AuthenticatorChallenge>)
|
||||
})
|
||||
|
||||
it('should return error if user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
challenge: Buffer.from('challenge'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
response: {
|
||||
attestationObject: Buffer.from('attestationObject'),
|
||||
clientDataJSON: Buffer.from('clientDataJSON'),
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual(
|
||||
'Could not verify authenticator registration response: Given value is not a valid uuid: invalid',
|
||||
)
|
||||
})
|
||||
|
||||
it('should return error if challenge is not found', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('challenge'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
response: {
|
||||
attestationObject: Buffer.from('attestationObject'),
|
||||
clientDataJSON: Buffer.from('clientDataJSON'),
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Could not verify authenticator registration response: challenge not found')
|
||||
})
|
||||
|
||||
it('should return error if verification could not verify', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
} as jest.Mocked<AuthenticatorChallenge>)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mock = jest.spyOn(simeplWebAuthnServer, 'verifyRegistrationResponse')
|
||||
mock.mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
verified: false,
|
||||
registrationInfo: {
|
||||
counter: 1,
|
||||
credentialBackedUp: true,
|
||||
credentialDeviceType: 'singleDevice',
|
||||
credentialID: Buffer.from('test'),
|
||||
credentialPublicKey: Buffer.from('test'),
|
||||
},
|
||||
} as jest.Mocked<VerifiedRegistrationResponse>)
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('invalid'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
response: {
|
||||
attestationObject: Buffer.from('attestationObject'),
|
||||
clientDataJSON: Buffer.from('clientDataJSON'),
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Could not verify authenticator registration response: verification failed')
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should return error if authenticator could not be created', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
} as jest.Mocked<AuthenticatorChallenge>)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mock = jest.spyOn(simeplWebAuthnServer, 'verifyRegistrationResponse')
|
||||
mock.mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
verified: true,
|
||||
registrationInfo: {
|
||||
counter: 1,
|
||||
credentialBackedUp: true,
|
||||
credentialDeviceType: 'singleDevice',
|
||||
credentialID: Buffer.from('test'),
|
||||
credentialPublicKey: Buffer.from('test'),
|
||||
},
|
||||
} as jest.Mocked<VerifiedRegistrationResponse>)
|
||||
})
|
||||
|
||||
const mockAuthenticator = jest.spyOn(Authenticator, 'create')
|
||||
mockAuthenticator.mockImplementation(() => {
|
||||
return Result.fail('Oops')
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('invalid'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
response: {
|
||||
attestationObject: Buffer.from('attestationObject'),
|
||||
clientDataJSON: Buffer.from('clientDataJSON'),
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Could not verify authenticator registration response: Oops')
|
||||
|
||||
mock.mockRestore()
|
||||
mockAuthenticator.mockRestore()
|
||||
})
|
||||
|
||||
it('should verify authenticator registration response', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuidAndChallenge = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
} as jest.Mocked<AuthenticatorChallenge>)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mock = jest.spyOn(simeplWebAuthnServer, 'verifyRegistrationResponse')
|
||||
mock.mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
verified: true,
|
||||
registrationInfo: {
|
||||
counter: 1,
|
||||
credentialBackedUp: true,
|
||||
credentialDeviceType: 'singleDevice',
|
||||
credentialID: Buffer.from('test'),
|
||||
credentialPublicKey: Buffer.from('test'),
|
||||
},
|
||||
} as jest.Mocked<VerifiedRegistrationResponse>)
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
challenge: Buffer.from('invalid'),
|
||||
registrationCredential: {
|
||||
id: Buffer.from('id'),
|
||||
rawId: Buffer.from('rawId'),
|
||||
response: {
|
||||
attestationObject: Buffer.from('attestationObject'),
|
||||
clientDataJSON: Buffer.from('clientDataJSON'),
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Dates, Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { verifyRegistrationResponse } from '@simplewebauthn/server'
|
||||
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { RelyingParty } from '../../Authenticator/RelyingParty'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { Authenticator } from '../../Authenticator/Authenticator'
|
||||
import { VerifyAuthenticatorRegistrationResponseDTO } from './VerifyAuthenticatorRegistrationResponseDTO'
|
||||
|
||||
export class VerifyAuthenticatorRegistrationResponse implements UseCaseInterface<boolean> {
|
||||
constructor(
|
||||
private authenticatorRepository: AuthenticatorRepositoryInterface,
|
||||
private authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: VerifyAuthenticatorRegistrationResponseDTO): Promise<Result<boolean>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(`Could not verify authenticator registration response: ${userUuidOrError.getError()}`)
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const authenticatorChallenge = await this.authenticatorChallengeRepository.findByUserUuidAndChallenge(
|
||||
userUuid,
|
||||
dto.challenge,
|
||||
)
|
||||
if (!authenticatorChallenge) {
|
||||
return Result.fail('Could not verify authenticator registration response: challenge not found')
|
||||
}
|
||||
|
||||
const verification = await verifyRegistrationResponse({
|
||||
credential: dto.registrationCredential,
|
||||
expectedChallenge: authenticatorChallenge.props.challenge.toString(),
|
||||
expectedOrigin: `https://${RelyingParty.RP_ID}`,
|
||||
expectedRPID: RelyingParty.RP_ID,
|
||||
})
|
||||
|
||||
if (!verification.verified) {
|
||||
return Result.fail('Could not verify authenticator registration response: verification failed')
|
||||
}
|
||||
|
||||
const authenticatorOrError = Authenticator.create({
|
||||
userUuid,
|
||||
counter: verification.registrationInfo?.counter as number,
|
||||
credentialBackedUp: verification.registrationInfo?.credentialBackedUp as boolean,
|
||||
credentialDeviceType: verification.registrationInfo?.credentialDeviceType,
|
||||
credentialId: verification.registrationInfo?.credentialID as Buffer,
|
||||
credentialPublicKey: verification.registrationInfo?.credentialPublicKey as Buffer,
|
||||
dates: Dates.create(new Date(), new Date()).getValue(),
|
||||
})
|
||||
|
||||
if (authenticatorOrError.isFailed()) {
|
||||
return Result.fail(`Could not verify authenticator registration response: ${authenticatorOrError.getError()}`)
|
||||
}
|
||||
const authenticator = authenticatorOrError.getValue()
|
||||
|
||||
await this.authenticatorRepository.save(authenticator)
|
||||
|
||||
return Result.ok(true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface VerifyAuthenticatorRegistrationResponseDTO {
|
||||
userUuid: string
|
||||
challenge: Buffer
|
||||
registrationCredential: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { Repository } from 'typeorm'
|
||||
|
||||
import { AuthenticatorChallenge } from '../../Domain/Authenticator/AuthenticatorChallenge'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Domain/Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
|
||||
import { TypeORMAuthenticatorChallenge } from '../TypeORM/TypeORMAuthenticatorChallenge'
|
||||
|
||||
export class MySQLAuthenticatorChallengeRepository implements AuthenticatorChallengeRepositoryInterface {
|
||||
constructor(
|
||||
private ormRepository: Repository<TypeORMAuthenticatorChallenge>,
|
||||
private mapper: MapperInterface<AuthenticatorChallenge, TypeORMAuthenticatorChallenge>,
|
||||
) {}
|
||||
|
||||
async findByUserUuidAndChallenge(userUuid: Uuid, challenge: Buffer): Promise<AuthenticatorChallenge | null> {
|
||||
const typeOrm = await this.ormRepository
|
||||
.createQueryBuilder('challenge')
|
||||
.where('challenge.user_uuid = :userUuid and challenge.challenge = :challenge', {
|
||||
userUuid: userUuid.value,
|
||||
challenge,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
if (typeOrm === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(typeOrm)
|
||||
}
|
||||
|
||||
async save(authenticatorChallenge: AuthenticatorChallenge): Promise<void> {
|
||||
const persistence = this.mapper.toProjection(authenticatorChallenge)
|
||||
|
||||
await this.ormRepository.save(persistence)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { Repository } from 'typeorm'
|
||||
|
||||
import { Authenticator } from '../../Domain/Authenticator/Authenticator'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Domain/Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { TypeORMAuthenticator } from '../TypeORM/TypeORMAuthenticator'
|
||||
|
||||
export class MySQLAuthenticatorRepository implements AuthenticatorRepositoryInterface {
|
||||
constructor(
|
||||
private ormRepository: Repository<TypeORMAuthenticator>,
|
||||
private mapper: MapperInterface<Authenticator, TypeORMAuthenticator>,
|
||||
) {}
|
||||
|
||||
async save(authenticator: Authenticator): Promise<void> {
|
||||
const persistence = this.mapper.toProjection(authenticator)
|
||||
|
||||
await this.ormRepository.save(persistence)
|
||||
}
|
||||
|
||||
async findByUserUuid(userUuid: Uuid): Promise<Authenticator[]> {
|
||||
const typeOrm = await this.ormRepository
|
||||
.createQueryBuilder('authenticator')
|
||||
.where('authenticator.user_uuid = :userUuid', {
|
||||
userUuid: userUuid.value,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
return typeOrm.map((authenticator) => this.mapper.toDomain(authenticator))
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { MapperInterface, SubscriptionPlanName, Uuid } from '@standardnotes/domain-core'
|
||||
import { Repository } from 'typeorm'
|
||||
import { SessionTrace } from '../../Domain/Session/SessionTrace'
|
||||
import { SessionTraceRepositoryInterface } from '../../Domain/Session/SessionTraceRepositoryInterface'
|
||||
@@ -10,11 +10,23 @@ export class MySQLSessionTraceRepository implements SessionTraceRepositoryInterf
|
||||
private mapper: MapperInterface<SessionTrace, TypeORMSessionTrace>,
|
||||
) {}
|
||||
|
||||
async countByDateAndSubscriptionPlanName(date: Date, subscriptionPlanName: SubscriptionPlanName): Promise<number> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('trace')
|
||||
.where('trace.creation_date = :creationDate', {
|
||||
creationDate: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
|
||||
})
|
||||
.andWhere('trace.subscription_plan_name = :subscriptionPlanName', {
|
||||
subscriptionPlanName: subscriptionPlanName.value,
|
||||
})
|
||||
.getCount()
|
||||
}
|
||||
|
||||
async countByDate(date: Date): Promise<number> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('trace')
|
||||
.where('trace.creation_date = :creationDate', {
|
||||
creationDate: new Date(date.getFullYear(), date.getMonth(), date.getDate()),
|
||||
creationDate: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
|
||||
})
|
||||
.getCount()
|
||||
}
|
||||
@@ -32,7 +44,7 @@ export class MySQLSessionTraceRepository implements SessionTraceRepositoryInterf
|
||||
.createQueryBuilder('trace')
|
||||
.where('trace.user_uuid = :userUuid AND trace.creation_date = :creationDate', {
|
||||
userUuid: userUuid.value,
|
||||
creationDate: new Date(date.getFullYear(), date.getMonth(), date.getDate()),
|
||||
creationDate: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
|
||||
64
packages/auth/src/Infra/TypeORM/TypeORMAuthenticator.ts
Normal file
64
packages/auth/src/Infra/TypeORM/TypeORMAuthenticator.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'authenticators' })
|
||||
export class TypeORMAuthenticator {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'credential_id',
|
||||
type: 'varbinary',
|
||||
length: 1024,
|
||||
})
|
||||
declare credentialId: Buffer
|
||||
|
||||
@Column({
|
||||
name: 'credential_public_key',
|
||||
type: 'blob',
|
||||
})
|
||||
declare credentialPublicKey: Buffer
|
||||
|
||||
@Column({
|
||||
name: 'counter',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare counter: number
|
||||
|
||||
@Column({
|
||||
name: 'credential_device_type',
|
||||
type: 'varchar',
|
||||
length: 32,
|
||||
})
|
||||
declare credentialDeviceType: string
|
||||
|
||||
@Column({
|
||||
name: 'credential_backed_up',
|
||||
})
|
||||
declare credentialBackedUp: boolean
|
||||
|
||||
@Column({
|
||||
name: 'transports',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
declare transports: string | null
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'datetime',
|
||||
})
|
||||
declare createdAt: Date
|
||||
|
||||
@Column({
|
||||
name: 'updated_at',
|
||||
type: 'datetime',
|
||||
})
|
||||
declare updatedAt: Date
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'authenticator_challenges' })
|
||||
@Index('user_uuid_and_challenge', ['userUuid', 'challenge'])
|
||||
export class TypeORMAuthenticatorChallenge {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'challenge',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
})
|
||||
declare challenge: Buffer
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'datetime',
|
||||
})
|
||||
declare createdAt: Date
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'session_traces' })
|
||||
@Index('user_uuid_and_creation_date', ['userUuid', 'creationDate'], { unique: true })
|
||||
export class TypeORMSessionTrace {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { MapperInterface, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { AuthenticatorChallenge } from '../Domain/Authenticator/AuthenticatorChallenge'
|
||||
import { TypeORMAuthenticatorChallenge } from '../Infra/TypeORM/TypeORMAuthenticatorChallenge'
|
||||
|
||||
export class AuthenticatorChallengePersistenceMapper
|
||||
implements MapperInterface<AuthenticatorChallenge, TypeORMAuthenticatorChallenge>
|
||||
{
|
||||
toDomain(projection: TypeORMAuthenticatorChallenge): AuthenticatorChallenge {
|
||||
const userUuidOrError = Uuid.create(projection.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
throw new Error(`Failed to create authenticator challenge from projection: ${userUuidOrError.getError()}`)
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const authenticatorChallengeOrError = AuthenticatorChallenge.create(
|
||||
{
|
||||
challenge: projection.challenge,
|
||||
userUuid,
|
||||
createdAt: projection.createdAt,
|
||||
},
|
||||
new UniqueEntityId(projection.uuid),
|
||||
)
|
||||
if (authenticatorChallengeOrError.isFailed()) {
|
||||
throw new Error(
|
||||
`Failed to create authenticator challenge from projection: ${authenticatorChallengeOrError.getError()}`,
|
||||
)
|
||||
}
|
||||
const authenticatorChallenge = authenticatorChallengeOrError.getValue()
|
||||
|
||||
return authenticatorChallenge
|
||||
}
|
||||
|
||||
toProjection(domain: AuthenticatorChallenge): TypeORMAuthenticatorChallenge {
|
||||
const typeorm = new TypeORMAuthenticatorChallenge()
|
||||
|
||||
typeorm.uuid = domain.id.toString()
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.challenge = domain.props.challenge
|
||||
typeorm.createdAt = domain.props.createdAt
|
||||
|
||||
return typeorm
|
||||
}
|
||||
}
|
||||
59
packages/auth/src/Mapping/AuthenticatorPersistenceMapper.ts
Normal file
59
packages/auth/src/Mapping/AuthenticatorPersistenceMapper.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Dates, MapperInterface, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Authenticator } from '../Domain/Authenticator/Authenticator'
|
||||
import { TypeORMAuthenticator } from '../Infra/TypeORM/TypeORMAuthenticator'
|
||||
|
||||
export class AuthenticatorPersistenceMapper implements MapperInterface<Authenticator, TypeORMAuthenticator> {
|
||||
toDomain(projection: TypeORMAuthenticator): Authenticator {
|
||||
const userUuidOrError = Uuid.create(projection.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
throw new Error(`Failed to create authenticator from projection: ${userUuidOrError.getError()}`)
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const datesOrError = Dates.create(projection.createdAt, projection.updatedAt)
|
||||
if (datesOrError.isFailed()) {
|
||||
throw new Error(`Failed to create authenticator from projection: ${datesOrError.getError()}`)
|
||||
}
|
||||
const dates = datesOrError.getValue()
|
||||
|
||||
const authenticatorOrError = Authenticator.create(
|
||||
{
|
||||
userUuid,
|
||||
counter: projection.counter,
|
||||
credentialBackedUp: projection.credentialBackedUp,
|
||||
credentialDeviceType: projection.credentialDeviceType,
|
||||
credentialId: projection.credentialId,
|
||||
credentialPublicKey: projection.credentialPublicKey,
|
||||
dates,
|
||||
transports: projection.transports ? JSON.parse(projection.transports) : undefined,
|
||||
},
|
||||
new UniqueEntityId(projection.uuid),
|
||||
)
|
||||
if (authenticatorOrError.isFailed()) {
|
||||
throw new Error(`Failed to create authenticator from projection: ${authenticatorOrError.getError()}`)
|
||||
}
|
||||
const authenticator = authenticatorOrError.getValue()
|
||||
|
||||
return authenticator
|
||||
}
|
||||
|
||||
toProjection(domain: Authenticator): TypeORMAuthenticator {
|
||||
const typeorm = new TypeORMAuthenticator()
|
||||
|
||||
typeorm.uuid = domain.id.toString()
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.credentialId = domain.props.credentialId
|
||||
typeorm.credentialPublicKey = domain.props.credentialPublicKey
|
||||
typeorm.counter = domain.props.counter
|
||||
typeorm.credentialDeviceType = domain.props.credentialDeviceType
|
||||
typeorm.credentialBackedUp = domain.props.credentialBackedUp
|
||||
if (domain.props.transports) {
|
||||
typeorm.transports = JSON.stringify(domain.props.transports)
|
||||
}
|
||||
typeorm.createdAt = domain.props.dates.createdAt
|
||||
typeorm.updatedAt = domain.props.dates.updatedAt
|
||||
|
||||
return typeorm
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.9.59](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.58...@standardnotes/domain-events-infra@1.9.59) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.9.58](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.57...@standardnotes/domain-events-infra@1.9.58) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.9.58",
|
||||
"version": "1.9.59",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.105.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.105.0...@standardnotes/domain-events@2.105.1) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** replace date object with number timestamp ([5b4bb6e](https://github.com/standardnotes/server/commit/5b4bb6e7a78a1b0f4e663bb990619f65f6a5c757))
|
||||
|
||||
# [2.105.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.104.2...@standardnotes/domain-events@2.105.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.105.0",
|
||||
"version": "2.105.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface StatisticPersistenceRequestedEventPayload {
|
||||
statisticMeasureName: string
|
||||
value: number
|
||||
date: Date
|
||||
date: number
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.6.56](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.55...@standardnotes/event-store@1.6.56) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.6.55](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.54...@standardnotes/event-store@1.6.55) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.6.55",
|
||||
"version": "1.6.56",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.9.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.9.2...@standardnotes/files-server@1.9.3) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.9.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.9.1...@standardnotes/files-server@1.9.2) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.9.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.9.0...@standardnotes/files-server@1.9.1) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -25,7 +25,7 @@
|
||||
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "^7.27.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.10.2...@standardnotes/revisions-server@1.10.3) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.10.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.10.1...@standardnotes/revisions-server@1.10.2) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.10.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.10.0...@standardnotes/revisions-server@1.10.1) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.10.1",
|
||||
"version": "1.10.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.27.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@standardnotes/api": "^1.19.0",
|
||||
"@standardnotes/common": "workspace:^",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.16.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.16.3...@standardnotes/scheduler-server@1.16.4) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.16.3](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.16.2...@standardnotes/scheduler-server@1.16.3) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **scheduler:** new pricing for subscription encouragement email ([eb21872](https://github.com/standardnotes/server/commit/eb21872db1726c4f8a55b5d16de712650c5e946b))
|
||||
|
||||
## [1.16.2](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.16.1...@standardnotes/scheduler-server@1.16.2) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.16.1](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.16.0...@standardnotes/scheduler-server@1.16.1) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.16.1",
|
||||
"version": "1.16.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.27.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user