Compare commits

...

24 Commits

Author SHA1 Message Date
standardci
2d2342f9ee chore(release): publish new version
- @standardnotes/analytics@2.19.13
 - @standardnotes/auth-server@1.87.1
 - @standardnotes/common@1.46.5
 - @standardnotes/revisions-server@1.10.24
 - @standardnotes/syncing-server@1.29.10
 - @standardnotes/websockets-server@1.5.16
 - @standardnotes/workspace-server@1.19.19
2023-01-24 12:48:21 +00:00
Karol Sójko
60838a1b7e chore: replace ErrorTag with usage from @standardnotes/api 2023-01-24 13:46:20 +01:00
standardci
63401b7640 chore(release): publish new version
- @standardnotes/auth-server@1.87.0
2023-01-24 11:14:52 +00:00
Karol Sójko
6a5b669ec4 feat(auth): add U2F to MFA verification 2023-01-24 12:12:51 +01:00
standardci
ca201447d2 chore(release): publish new version
- @standardnotes/auth-server@1.86.4
2023-01-24 10:19:45 +00:00
Karol Sójko
f1d3117518 fix(auth): add cleanup of authenticator devices upon sign in with recovery codes 2023-01-24 11:17:51 +01:00
standardci
8559948a5a chore(release): publish new version
- @standardnotes/auth-server@1.86.3
2023-01-24 08:36:36 +00:00
Karol Sójko
a3b4aa3b4a Revert "fix(auth): fido options user verification as discouraged"
This reverts commit 3d475cc779.
2023-01-24 09:34:37 +01:00
standardci
0347fa381f chore(release): publish new version
- @standardnotes/auth-server@1.86.2
2023-01-24 07:26:23 +00:00
Karol Sójko
3d475cc779 fix(auth): fido options user verification as discouraged 2023-01-24 08:24:25 +01:00
standardci
ceec74fb70 chore(release): publish new version
- @standardnotes/auth-server@1.86.1
2023-01-24 05:43:19 +00:00
Karol Sójko
f5296a947e fix(auth): buffer types on authenticator credentials 2023-01-24 06:40:54 +01:00
standardci
5b5fcd9372 chore(release): publish new version
- @standardnotes/auth-server@1.86.0
2023-01-23 14:33:52 +00:00
Karol Sójko
c38817c62e feat(auth): add configurable user verification requirement on u2f via env vars 2023-01-23 15:31:49 +01:00
standardci
ba08c6a707 chore(release): publish new version
- @standardnotes/auth-server@1.85.0
2023-01-23 12:55:06 +00:00
Karol Sójko
1797bc8181 feat(auth): add configuring u2f expect origin 2023-01-23 13:53:10 +01:00
standardci
657aaf75ec chore(release): publish new version
- @standardnotes/auth-server@1.84.11
2023-01-23 10:47:04 +00:00
Karol Sójko
dac3c733b3 chore: fix cbor-extract dependencies 2023-01-23 11:44:35 +01:00
standardci
d02b6b67b5 chore(release): publish new version
- @standardnotes/auth-server@1.84.10
2023-01-23 10:30:43 +00:00
Karol Sójko
52ce5f3a2f fix(auth): upgrade simplewebauthn types 2023-01-23 11:28:36 +01:00
Karol Sójko
1d316e17cb chore: upgrade @simplewebauthn packages 2023-01-23 11:08:07 +01:00
Karol Sójko
6193f4b87a chore: upgrade eslint-config-prettier 2023-01-23 11:08:06 +01:00
standardci
de5a30e46c chore(release): publish new version
- @standardnotes/auth-server@1.84.9
 - @standardnotes/revisions-server@1.10.23
 - @standardnotes/websockets-server@1.5.15
 - @standardnotes/workspace-server@1.19.18
2023-01-23 09:56:32 +00:00
Karol Sójko
973acd22ca chore: fix dependecies 2023-01-23 10:53:40 +01:00
116 changed files with 1468 additions and 624 deletions

423
.pnp.cjs generated
View File

@@ -132,7 +132,7 @@ const RAW_RUNTIME_STATE =
["@types/node", "npm:18.11.9"],\
["@typescript-eslint/parser", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:5.40.1"],\
["eslint", "npm:8.32.0"],\
["eslint-config-prettier", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.5.0"],\
["eslint-config-prettier", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.6.0"],\
["ini", "npm:3.0.0"],\
["newrelic", "npm:9.8.0"],\
["npm-check-updates", "npm:16.0.1"],\
@@ -914,6 +914,60 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@cbor-extract/cbor-extract-darwin-arm64", [\
["npm:2.1.1", {\
"packageLocation": "./.yarn/unplugged/@cbor-extract-cbor-extract-darwin-arm64-npm-2.1.1-7f6025512f/node_modules/@cbor-extract/cbor-extract-darwin-arm64/",\
"packageDependencies": [\
["@cbor-extract/cbor-extract-darwin-arm64", "npm:2.1.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["@cbor-extract/cbor-extract-darwin-x64", [\
["npm:2.1.1", {\
"packageLocation": "./.yarn/unplugged/@cbor-extract-cbor-extract-darwin-x64-npm-2.1.1-9c0e0a67cc/node_modules/@cbor-extract/cbor-extract-darwin-x64/",\
"packageDependencies": [\
["@cbor-extract/cbor-extract-darwin-x64", "npm:2.1.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["@cbor-extract/cbor-extract-linux-arm", [\
["npm:2.1.1", {\
"packageLocation": "./.yarn/unplugged/@cbor-extract-cbor-extract-linux-arm-npm-2.1.1-95d0b66b34/node_modules/@cbor-extract/cbor-extract-linux-arm/",\
"packageDependencies": [\
["@cbor-extract/cbor-extract-linux-arm", "npm:2.1.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["@cbor-extract/cbor-extract-linux-arm64", [\
["npm:2.1.1", {\
"packageLocation": "./.yarn/unplugged/@cbor-extract-cbor-extract-linux-arm64-npm-2.1.1-23a641c278/node_modules/@cbor-extract/cbor-extract-linux-arm64/",\
"packageDependencies": [\
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["@cbor-extract/cbor-extract-linux-x64", [\
["npm:2.1.1", {\
"packageLocation": "./.yarn/unplugged/@cbor-extract-cbor-extract-linux-x64-npm-2.1.1-4471164400/node_modules/@cbor-extract/cbor-extract-linux-x64/",\
"packageDependencies": [\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["@cbor-extract/cbor-extract-win32-x64", [\
["npm:2.1.1", {\
"packageLocation": "./.yarn/unplugged/@cbor-extract-cbor-extract-win32-x64-npm-2.1.1-b206bdfc73/node_modules/@cbor-extract/cbor-extract-win32-x64/",\
"packageDependencies": [\
["@cbor-extract/cbor-extract-win32-x64", "npm:2.1.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["@colors/colors", [\
["npm:1.5.0", {\
"packageLocation": "./.yarn/cache/@colors-colors-npm-1.5.0-875af3a8b4-5e08870799.zip/node_modules/@colors/colors/",\
@@ -1216,6 +1270,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@hexagon/base64", [\
["npm:1.1.25", {\
"packageLocation": "./.yarn/cache/@hexagon-base64-npm-1.1.25-44c8260698-0b42e9b676.zip/node_modules/@hexagon/base64/",\
"packageDependencies": [\
["@hexagon/base64", "npm:1.1.25"]\
],\
"linkType": "HARD"\
}]\
]],\
["@humanwhocodes/config-array", [\
["npm:0.11.8", {\
"packageLocation": "./.yarn/cache/@humanwhocodes-config-array-npm-0.11.8-7955bfecc2-010892ba3c.zip/node_modules/@humanwhocodes/config-array/",\
@@ -1954,15 +2017,6 @@ 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/",\
@@ -2331,6 +2385,32 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@peculiar/asn1-ecc", [\
["npm:2.3.4", {\
"packageLocation": "./.yarn/cache/@peculiar-asn1-ecc-npm-2.3.4-d498135879-351f9e0a4f.zip/node_modules/@peculiar/asn1-ecc/",\
"packageDependencies": [\
["@peculiar/asn1-ecc", "npm:2.3.4"],\
["@peculiar/asn1-schema", "npm:2.3.3"],\
["@peculiar/asn1-x509", "npm:2.3.4"],\
["asn1js", "npm:3.0.5"],\
["tslib", "npm:2.4.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["@peculiar/asn1-rsa", [\
["npm:2.3.4", {\
"packageLocation": "./.yarn/cache/@peculiar-asn1-rsa-npm-2.3.4-5015f8b5ba-89bcb894f4.zip/node_modules/@peculiar/asn1-rsa/",\
"packageDependencies": [\
["@peculiar/asn1-rsa", "npm:2.3.4"],\
["@peculiar/asn1-schema", "npm:2.3.3"],\
["@peculiar/asn1-x509", "npm:2.3.4"],\
["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/",\
@@ -2531,30 +2611,39 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/server", [\
["npm:6.2.2", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-6.2.2-ca870b05c2-5ffb9b1c15.zip/node_modules/@simplewebauthn/server/",\
["@simplewebauthn/iso-webcrypto", [\
["npm:7.0.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-iso-webcrypto-npm-7.0.0-352babf4a0-c1644f9b68.zip/node_modules/@simplewebauthn/iso-webcrypto/",\
"packageDependencies": [\
["@simplewebauthn/server", "npm:6.2.2"],\
["@noble/ed25519", "npm:1.7.1"],\
["@simplewebauthn/iso-webcrypto", "npm:7.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/server", [\
["npm:7.0.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-7.0.0-e34589f137-836eb9fb97.zip/node_modules/@simplewebauthn/server/",\
"packageDependencies": [\
["@simplewebauthn/server", "npm:7.0.0"],\
["@hexagon/base64", "npm:1.1.25"],\
["@peculiar/asn1-android", "npm:2.3.3"],\
["@peculiar/asn1-ecc", "npm:2.3.4"],\
["@peculiar/asn1-rsa", "npm:2.3.4"],\
["@peculiar/asn1-schema", "npm:2.3.3"],\
["@peculiar/asn1-x509", "npm:2.3.4"],\
["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"]\
["@simplewebauthn/iso-webcrypto", "npm:7.0.0"],\
["cbor-x", "npm:1.5.0"],\
["cross-fetch", "npm:3.1.5"],\
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"]\
],\
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/typescript-types", [\
["npm:6.3.0-alpha.1", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-6.3.0-alpha.1-629da05c10-5667c214e9.zip/node_modules/@simplewebauthn/typescript-types/",\
["npm:7.0.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-7.0.0-cc6ca20415-124238ea18.zip/node_modules/@simplewebauthn/typescript-types/",\
"packageDependencies": [\
["@simplewebauthn/typescript-types", "npm:6.3.0-alpha.1"]\
["@simplewebauthn/typescript-types", "npm:7.0.0"]\
],\
"linkType": "HARD"\
}]\
@@ -2645,17 +2734,17 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/api", [\
["npm:1.24.5", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.24.5-2a2f029be6-742a9d0936.zip/node_modules/@standardnotes/api/",\
["npm:1.24.9", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.24.9-b92f13a962-73bfd0fe3b.zip/node_modules/@standardnotes/api/",\
"packageDependencies": [\
["@standardnotes/api", "npm:1.24.5"],\
["@standardnotes/api", "npm:1.24.9"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/encryption", "npm:1.21.5"],\
["@standardnotes/models", "npm:1.42.7"],\
["@standardnotes/responses", "npm:1.13.4"],\
["@standardnotes/encryption", "npm:1.21.9"],\
["@standardnotes/models", "npm:1.42.11"],\
["@standardnotes/responses", "npm:1.13.6"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/utils", "npm:1.16.2"],\
["@standardnotes/utils", "npm:1.16.3"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
@@ -2712,12 +2801,14 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/auth/",\
"packageDependencies": [\
["@standardnotes/auth-server", "workspace:packages/auth"],\
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
["@sentry/node", "npm:7.28.1"],\
["@sentry/tracing", "npm:7.28.1"],\
["@simplewebauthn/server", "npm:6.2.2"],\
["@simplewebauthn/typescript-types", "npm:6.3.0-alpha.1"],\
["@standardnotes/api", "npm:1.24.5"],\
["@simplewebauthn/server", "npm:7.0.0"],\
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
["@standardnotes/api", "npm:1.24.9"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
@@ -2863,15 +2954,15 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/encryption", [\
["npm:1.21.5", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.21.5-3a48807445-652b8859ff.zip/node_modules/@standardnotes/encryption/",\
["npm:1.21.9", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.21.9-092bc2cb51-dc1336cc05.zip/node_modules/@standardnotes/encryption/",\
"packageDependencies": [\
["@standardnotes/encryption", "npm:1.21.5"],\
["@standardnotes/encryption", "npm:1.21.9"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/models", "npm:1.42.7"],\
["@standardnotes/responses", "npm:1.13.4"],\
["@standardnotes/models", "npm:1.42.11"],\
["@standardnotes/responses", "npm:1.13.6"],\
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
["@standardnotes/utils", "npm:1.16.2"],\
["@standardnotes/utils", "npm:1.16.3"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
@@ -2919,6 +3010,17 @@ const RAW_RUNTIME_STATE =
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.58.6", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.58.6-7b1e198c39-98550416f1.zip/node_modules/@standardnotes/features/",\
"packageDependencies": [\
["@standardnotes/features", "npm:1.58.6"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/security", "workspace:packages/security"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}]\
]],\
["@standardnotes/files-server", [\
@@ -2976,11 +3078,15 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/models", [\
["npm:1.42.7", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.42.7-6c0d7e2ac9-66271be0a9.zip/node_modules/@standardnotes/models/",\
["npm:1.42.11", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.42.11-7db16001ef-6ff3409f70.zip/node_modules/@standardnotes/models/",\
"packageDependencies": [\
["@standardnotes/models", "npm:1.42.7"],\
["@standardnotes/utils", "npm:1.16.2"]\
["@standardnotes/models", "npm:1.42.11"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.58.6"],\
["@standardnotes/responses", "npm:1.13.6"],\
["@standardnotes/utils", "npm:1.16.3"],\
["lodash", "npm:4.17.21"]\
],\
"linkType": "HARD"\
}]\
@@ -3011,6 +3117,17 @@ const RAW_RUNTIME_STATE =
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.13.6", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.13.6-5df25fe3dd-c57e3e1fa1.zip/node_modules/@standardnotes/responses/",\
"packageDependencies": [\
["@standardnotes/responses", "npm:1.13.6"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.58.6"],\
["@standardnotes/security", "workspace:packages/security"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}]\
]],\
["@standardnotes/revisions-server", [\
@@ -3020,7 +3137,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/revisions-server", "workspace:packages/revisions"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
["@sentry/node", "npm:7.28.1"],\
["@standardnotes/api", "npm:1.24.5"],\
["@standardnotes/api", "npm:1.24.9"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
@@ -3129,7 +3246,7 @@ const RAW_RUNTIME_STATE =
["@types/node", "npm:18.11.9"],\
["@typescript-eslint/parser", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:5.40.1"],\
["eslint", "npm:8.32.0"],\
["eslint-config-prettier", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.5.0"],\
["eslint-config-prettier", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.6.0"],\
["ini", "npm:3.0.0"],\
["newrelic", "npm:9.8.0"],\
["npm-check-updates", "npm:16.0.1"],\
@@ -3199,6 +3316,7 @@ const RAW_RUNTIME_STATE =
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
["@sentry/node", "npm:7.28.1"],\
["@sentry/tracing", "npm:7.28.1"],\
["@standardnotes/api", "npm:1.24.9"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
@@ -3278,6 +3396,17 @@ const RAW_RUNTIME_STATE =
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.16.3", {\
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.16.3-87b47ad954-5c34beaafb.zip/node_modules/@standardnotes/utils/",\
"packageDependencies": [\
["@standardnotes/utils", "npm:1.16.3"],\
["@standardnotes/common", "workspace:packages/common"],\
["dompurify", "npm:2.4.3"],\
["lodash", "npm:4.17.21"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}]\
]],\
["@standardnotes/websockets-server", [\
@@ -3287,7 +3416,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
["@sentry/node", "npm:7.28.1"],\
["@standardnotes/api", "npm:1.24.5"],\
["@standardnotes/api", "npm:1.24.9"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
@@ -3329,12 +3458,12 @@ const RAW_RUNTIME_STATE =
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
["@sentry/node", "npm:7.28.1"],\
["@standardnotes/api", "npm:1.24.5"],\
["@standardnotes/api", "npm:1.24.9"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/models", "npm:1.42.7"],\
["@standardnotes/models", "npm:1.42.11"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/cors", "npm:2.8.12"],\
@@ -4733,19 +4862,6 @@ 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/",\
@@ -4971,15 +5087,6 @@ 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/",\
@@ -4998,15 +5105,6 @@ 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/",\
@@ -5028,15 +5126,6 @@ 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/",\
@@ -5104,15 +5193,6 @@ 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/",\
@@ -5348,13 +5428,29 @@ 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/",\
["cbor-extract", [\
["npm:2.1.1", {\
"packageLocation": "./.yarn/unplugged/cbor-extract-npm-2.1.1-bcad1459e1/node_modules/cbor-extract/",\
"packageDependencies": [\
["cbor", "npm:5.2.0"],\
["bignumber.js", "npm:9.1.1"],\
["nofilter", "npm:1.0.4"]\
["cbor-extract", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-darwin-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-darwin-x64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-arm", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-win32-x64", "npm:2.1.1"],\
["node-gyp", "npm:9.0.0"],\
["node-gyp-build-optional-packages", "npm:5.0.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["cbor-x", [\
["npm:1.5.0", {\
"packageLocation": "./.yarn/cache/cbor-x-npm-1.5.0-9baf767c60-c9de4515b0.zip/node_modules/cbor-x/",\
"packageDependencies": [\
["cbor-x", "npm:1.5.0"],\
["cbor-extract", "npm:2.1.1"]\
],\
"linkType": "HARD"\
}]\
@@ -6042,6 +6138,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["cross-fetch", [\
["npm:3.1.5", {\
"packageLocation": "./.yarn/cache/cross-fetch-npm-3.1.5-e414995db9-83fa7b1318.zip/node_modules/cross-fetch/",\
"packageDependencies": [\
["cross-fetch", "npm:3.1.5"],\
["node-fetch", "virtual:25a5f5382d53dbf298bf7a1191760bc2e0a523a619eeb0e667b99a8649e8ad183f9e2e0b45f6fb831b92f4078b61622aa567cf79565f6aa5af9597e3c84864f6#npm:2.6.7"]\
],\
"linkType": "HARD"\
}]\
]],\
["cross-spawn", [\
["npm:7.0.3", {\
"packageLocation": "./.yarn/cache/cross-spawn-npm-7.0.3-e4ff3e65b3-37ec685f91.zip/node_modules/cross-spawn/",\
@@ -6483,22 +6589,6 @@ 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/",\
@@ -6761,10 +6851,17 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "SOFT"\
}],\
["virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.5.0", {\
"packageLocation": "./.yarn/__virtual__/eslint-config-prettier-virtual-d5f61497f8/0/cache/eslint-config-prettier-npm-8.5.0-a1dd58b6d8-fb61fae9c1.zip/node_modules/eslint-config-prettier/",\
["npm:8.6.0", {\
"packageLocation": "./.yarn/cache/eslint-config-prettier-npm-8.6.0-00192c9409-2aeb302e53.zip/node_modules/eslint-config-prettier/",\
"packageDependencies": [\
["eslint-config-prettier", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.5.0"],\
["eslint-config-prettier", "npm:8.6.0"]\
],\
"linkType": "SOFT"\
}],\
["virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.6.0", {\
"packageLocation": "./.yarn/__virtual__/eslint-config-prettier-virtual-9308b15a23/0/cache/eslint-config-prettier-npm-8.6.0-00192c9409-2aeb302e53.zip/node_modules/eslint-config-prettier/",\
"packageDependencies": [\
["eslint-config-prettier", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.6.0"],\
["@types/eslint", null],\
["eslint", "npm:8.32.0"]\
],\
@@ -7953,17 +8050,6 @@ 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/",\
@@ -7982,18 +8068,6 @@ 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/",\
@@ -9586,15 +9660,6 @@ 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/",\
@@ -9607,18 +9672,6 @@ 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/",\
@@ -10174,24 +10227,6 @@ 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/",\
@@ -10559,6 +10594,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["node-gyp-build-optional-packages", [\
["npm:5.0.3", {\
"packageLocation": "./.yarn/cache/node-gyp-build-optional-packages-npm-5.0.3-50b9c76481-18e2444d34.zip/node_modules/node-gyp-build-optional-packages/",\
"packageDependencies": [\
["node-gyp-build-optional-packages", "npm:5.0.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["node-int64", [\
["npm:0.4.0", {\
"packageLocation": "./.yarn/cache/node-int64-npm-0.4.0-0dc04ec3b2-5333c7f5b1.zip/node_modules/node-int64/",\
@@ -10596,15 +10640,6 @@ 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/",\

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
Platform specific binary for cbor-extract on darwin OS with x64 architecture

View File

@@ -0,0 +1,17 @@
{
"name": "@cbor-extract/cbor-extract-darwin-x64",
"version": "2.1.1",
"os": [
"darwin"
],
"cpu": [
"x64"
],
"license": "MIT",
"author": "Kris Zyp",
"repository": {
"type": "git",
"url": "http://github.com/kriszyp/cbor-extract"
},
"description": "Platform specific binary for cbor-extract on darwin OS with x64 architecture"
}

View File

@@ -0,0 +1 @@
Platform specific binary for cbor-extract on linux OS with arm64 architecture

View File

@@ -0,0 +1,17 @@
{
"name": "@cbor-extract/cbor-extract-linux-arm64",
"version": "2.1.1",
"os": [
"linux"
],
"cpu": [
"arm64"
],
"license": "MIT",
"author": "Kris Zyp",
"repository": {
"type": "git",
"url": "http://github.com/kriszyp/cbor-extract"
},
"description": "Platform specific binary for cbor-extract on linux OS with arm64 architecture"
}

View File

@@ -0,0 +1 @@
Platform specific binary for cbor-extract on linux OS with x64 architecture

View File

@@ -0,0 +1,17 @@
{
"name": "@cbor-extract/cbor-extract-linux-x64",
"version": "2.1.1",
"os": [
"linux"
],
"cpu": [
"x64"
],
"license": "MIT",
"author": "Kris Zyp",
"repository": {
"type": "git",
"url": "http://github.com/kriszyp/cbor-extract"
},
"description": "Platform specific binary for cbor-extract on linux OS with x64 architecture"
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Kris Zyp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,5 @@
## Summary
This module is designed to do fast and efficient native/C-level extraction of strings from CBOR binary data. This works by calling `extractStrings(buffer, start, end)`, and it will extract strings by doing partial CBOR parsing, and scanning to find the string data in the range specified in the buffer. It will return an array of strings that it finds. When it finds strings that can be represented with latin-1/one-byte strings (and important V8 optimization), it will attempt return a continuous string of CBOR data that contains multiple sub-strings, so the decoder can slice off strings by offset. When a string contains non-latin characters, and must be represented as a two-byte string, this will always be returned as the string alone without combination with any other strings. The extractor will return an array of a maximum of 256 strings. The decoder can call the extractStrings again, with a new offset to continue extracting more strings as necessary.
## License
MIT

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env node
const { dirname } = require('path');
const { fileURLToPath } = require('url');
const { exec } = require('child_process');
process.chdir(dirname(__dirname));
exec('prebuildify-ci download', (error, stdout, stderr) => {
console.error(stderr);
console.log(stdout);
});

View File

@@ -0,0 +1,60 @@
{
"variables": {
"os_linux_compiler%": "gcc",
"enable_v8%": "true",
"enable_pointer_compression%": "false",
"build_v8_with_gn": "false"
},
"conditions": [
['OS=="win"', {
"variables": {
"enable_v8%": "<!(echo %ENABLE_V8_FUNCTIONS%)",
}
}],
['OS!="win"', {
"variables": {
"enable_v8%": "<!(echo $ENABLE_V8_FUNCTIONS)",
}
}]
],
"targets": [
{
"target_name": "extract",
"sources": [
"src/extract.cpp",
],
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
"conditions": [
["OS=='linux'", {
"variables": {
"gcc_version" : "<!(<(os_linux_compiler) -dumpversion | cut -d '.' -f 1)",
},
"cflags_cc": [
"-fPIC",
"-fvisibility=hidden",
"-fvisibility-inlines-hidden",
],
"conditions": [
["gcc_version>=7", {
"cflags": [
"-Wimplicit-fallthrough=2",
],
}],
],
"ldflags": [
"-fPIC",
"-fvisibility=hidden"
],
"cflags": [
"-fPIC",
"-fvisibility=hidden",
"-O3"
],
}],
["enable_v8!='false'", {
"defines": ["ENABLE_V8_API=1"]
}],
],
}
]
}

View File

@@ -0,0 +1 @@
module.exports = require('node-gyp-build-optional-packages')(__dirname)

View File

@@ -0,0 +1,48 @@
{
"name": "cbor-extract",
"author": "Kris Zyp",
"version": "2.1.1",
"description": "Node addon for string extraction for cbor-x",
"license": "MIT",
"repository": {
"type": "git",
"url": "http://github.com/kriszyp/cbor-extract"
},
"scripts": {
"install": "node-gyp-build-optional-packages",
"recompile": "node-gyp rebuild",
"before-publish": "prebuildify-ci download && node set-optional-deps.cjs",
"prebuild": "prebuildify-platform-packages --target 18.12.0",
"prebuild-win32": "prebuildify-platform-packages --target 18.12.0 && set ENABLE_V8_FUNCTIONS=false&& prebuildify-platform-packages --platform-packages --napi --target 18.12.0",
"prebuild-libc": "prebuildify-platform-packages --tag-libc --target 18.12.0 && prebuildify-platform-packages --platform-packages --napi --tag-libc --target 16.14.2 && ENABLE_V8_FUNCTIONS=false prebuildify-platform-packages --platform-packages --napi --tag-libc --target 18.12.0",
"prebuild-libc-alpine": "prebuildify-cross --image alpine --tag-libc --target 18.12.0",
"publish-all": "cd prebuilds/win32-x64 && npm publish --access public && cd ../darwin-x64 && npm publish --access public && cd ../darwin-arm64 && npm publish --access public && cd ../linux-x64 && npm publish --access public && cd ../linux-arm64 && npm publish --access public && cd ../linux-arm && npm publish --access public && cd ../.. && npm publish --access public",
"test": "node ./index.js"
},
"main": "./index.js",
"gypfile": true,
"dependencies": {
"node-gyp-build-optional-packages": "5.0.3"
},
"files": [
"index.js",
"/src",
"/*.gyp",
"/bin"
],
"bin": {
"download-cbor-prebuilds": "./bin/download-prebuilds.js"
},
"devDependencies": {
"prebuildify-platform-packages": "5.0.2",
"prebuildify-ci": "^1.0.5"
},
"optionalDependencies": {
"@cbor-extract/cbor-extract-darwin-arm64": "2.1.1",
"@cbor-extract/cbor-extract-darwin-x64": "2.1.1",
"@cbor-extract/cbor-extract-linux-arm": "2.1.1",
"@cbor-extract/cbor-extract-linux-arm64": "2.1.1",
"@cbor-extract/cbor-extract-linux-x64": "2.1.1",
"@cbor-extract/cbor-extract-win32-x64": "2.1.1"
}
}

View File

@@ -0,0 +1,198 @@
/*
This is responsible for extracting the strings, in bulk, from a CBOR buffer. Creating strings from buffers can
be one of the biggest performance bottlenecks of parsing, but creating an array of extracting strings all at once
provides much better performance. This will parse and produce up to 256 strings at once .The JS parser can call this multiple
times as necessary to get more strings. This must be partially capable of parsing CBOR so it can know where to
find the string tokens and determine their position and length. All strings are decoded as UTF-8.
*/
#include <node_api.h>
#if ENABLE_V8_API
#include <v8.h>
#endif
#ifndef thread_local
#ifdef __GNUC__
# define thread_local __thread
#elif __STDC_VERSION__ >= 201112L
# define thread_local _Thread_local
#elif defined(_MSC_VER)
# define thread_local __declspec(thread)
#else
# define thread_local
#endif
#endif
const int MAX_TARGET_SIZE = 255;
napi_value unexpectedEnd(napi_env env) {
napi_value returnValue;
napi_get_undefined(env, &returnValue);
napi_throw_type_error(env, NULL, "Unexpected end of buffer reading string");
return returnValue;
}
class Extractor {
public:
napi_value target[MAX_TARGET_SIZE + 1]; // leave one for the queued string
uint8_t* source;
int position = 0;
int writePosition = 0;
int stringStart = 0;
int lastStringEnd = 0;
void readString(napi_env env, int length, bool allowStringBlocks) {
int start = position;
int end = position + length;
if (allowStringBlocks) { // for larger strings, we don't bother to check every character for being latin, and just go right to creating a new string
while(position < end) {
if (source[position] < 0x80) // ensure we character is latin and can be decoded as one byte
position++;
else {
break;
}
}
}
if (position < end) {
// non-latin character
if (lastStringEnd) {
napi_value value;
napi_create_string_latin1(env, (const char*) source + stringStart, lastStringEnd - stringStart, &value);
target[writePosition++] = value;
lastStringEnd = 0;
}
// use standard utf-8 conversion
napi_value value;
napi_create_string_utf8(env, (const char*) source + start, (int) length, &value);
target[writePosition++] = value;
position = end;
return;
}
if (lastStringEnd) {
if (start - lastStringEnd > 40 || end - stringStart > 6000) {
napi_value value;
napi_create_string_latin1(env, (const char*) source + stringStart, lastStringEnd - stringStart, &value);
target[writePosition++] = value;
stringStart = start;
}
} else {
stringStart = start;
}
lastStringEnd = end;
}
napi_value extractStrings(napi_env env, int startingPosition, int size, int firstStringSize, uint8_t* inputSource) {
writePosition = 0;
lastStringEnd = 0;
position = startingPosition;
source = inputSource;
readString(env, firstStringSize, firstStringSize < 0x100);
while (position < size) {
uint8_t token = source[position++];
uint8_t majorType = token >> 5;
token = token & 0x1f;
if (majorType == 2 || majorType == 3) {
int length;
switch (token) {
case 0x18:
if (position + 1 > size) {
return unexpectedEnd(env);
}
length = source[position++];
break;
case 0x19:
if (position + 2 > size) {
return unexpectedEnd(env);
}
length = source[position++] << 8;
length += source[position++];
break;
case 0x1a:
if (position + 4 > size) {
return unexpectedEnd(env);
}
length = source[position++] << 24;
length += source[position++] << 16;
length += source[position++] << 8;
length += source[position++];
break;
case 0x1b:
return unexpectedEnd(env);
default:
length = token;
}
if (majorType == 3) {
// string
if (length + position > size) {
return unexpectedEnd(env);
}
readString(env, length, length < 0x100);
if (writePosition >= MAX_TARGET_SIZE)
break;
} else { // binary data
position += length;
}
} else { // all other tokens
switch (token) {
case 0x18:
position++;
break;
case 0x19:
position += 2;
break;
case 0x1a:
position += 4;
break;
case 0x1b:
position += 8;
break;
}
}
}
if (lastStringEnd) {
napi_value value;
napi_create_string_latin1(env, (const char*) source + stringStart, lastStringEnd - stringStart, &value);
if (writePosition == 0) {
return value;
}
target[writePosition++] = value;
} else if (writePosition == 1) {
return target[0];
}
napi_value array;
#if ENABLE_V8_API
v8::Local<v8::Array> v8Array = v8::Array::New(v8::Isolate::GetCurrent(), (v8::Local<v8::Value>*) target, writePosition);
memcpy(&array, &v8Array, sizeof(array));
#else
napi_create_array_with_length(env, writePosition, &array);
for (int i = 0; i < writePosition; i++) {
napi_set_element(env, array, i, target[i]);
}
#endif
return array;
}
};
static thread_local Extractor* extractor;
napi_value extractStrings(napi_env env, napi_callback_info info) {
size_t argc = 4;
napi_value args[4];
napi_get_cb_info(env, info, &argc, args, NULL, NULL);
uint32_t position;
uint32_t size;
uint32_t firstStringSize;
napi_get_value_uint32(env, args[0], &position);
napi_get_value_uint32(env, args[1], &size);
napi_get_value_uint32(env, args[2], &firstStringSize);
uint8_t* source;
size_t buffer_size;
napi_get_buffer_info(env, args[3], (void**) &source, &buffer_size);
return extractor->extractStrings(env, position, size, firstStringSize, source);
}
#define EXPORT_NAPI_FUNCTION(name, func) { napi_property_descriptor desc = { name, 0, func, 0, 0, 0, (napi_property_attributes) (napi_writable | napi_configurable), 0 }; napi_define_properties(env, exports, 1, &desc); }
NAPI_MODULE_INIT() {
extractor = new Extractor(); // create our thread-local extractor
EXPORT_NAPI_FUNCTION("extractStrings", extractStrings);
return exports;
}

View File

@@ -52,7 +52,7 @@
"@types/node": "^18.11.9",
"@typescript-eslint/parser": "^5.40.1",
"eslint": "^8.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-prettier": "^8.6.0",
"ini": "^3.0.0",
"npm-check-updates": "^16.0.1",
"prettier": "^2.7.1",

View File

@@ -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.
## [2.19.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.19.12...@standardnotes/analytics@2.19.13) (2023-01-24)
**Note:** Version bump only for package @standardnotes/analytics
## [2.19.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.19.11...@standardnotes/analytics@2.19.12) (2023-01-20)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.19.12",
"version": "2.19.13",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -71,3 +71,4 @@ WEB_SOCKET_CONNECTION_TOKEN_SECRET=
# (Optional) U2F Setup
U2F_RELYING_PARTY_ID=
U2F_RELYING_PARTY_NAME=
U2F_EXPECTED_ORIGIN=

View File

@@ -3,6 +3,66 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.87.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.87.0...@standardnotes/auth-server@1.87.1) (2023-01-24)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.87.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.86.4...@standardnotes/auth-server@1.87.0) (2023-01-24)
### Features
* **auth:** add U2F to MFA verification ([6a5b669](https://github.com/standardnotes/server/commit/6a5b669ec47d3fd71fec3e362d66480d91c544d0))
## [1.86.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.86.3...@standardnotes/auth-server@1.86.4) (2023-01-24)
### Bug Fixes
* **auth:** add cleanup of authenticator devices upon sign in with recovery codes ([f1d3117](https://github.com/standardnotes/server/commit/f1d311751832a2abdbe124cede7f020b28cbcd9d))
## [1.86.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.86.2...@standardnotes/auth-server@1.86.3) (2023-01-24)
### Reverts
* Revert "fix(auth): fido options user verification as discouraged" ([a3b4aa3](https://github.com/standardnotes/server/commit/a3b4aa3b4a8eb39acf0d907e0f3ab6ffadd9d056))
## [1.86.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.86.1...@standardnotes/auth-server@1.86.2) (2023-01-24)
### Bug Fixes
* **auth:** fido options user verification as discouraged ([3d475cc](https://github.com/standardnotes/server/commit/3d475cc779f13c7da961a62f8f6abe69f76609fc))
## [1.86.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.86.0...@standardnotes/auth-server@1.86.1) (2023-01-24)
### Bug Fixes
* **auth:** buffer types on authenticator credentials ([f5296a9](https://github.com/standardnotes/server/commit/f5296a947eabe4d3db22e3afdf1690efa5670532))
# [1.86.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.85.0...@standardnotes/auth-server@1.86.0) (2023-01-23)
### Features
* **auth:** add configurable user verification requirement on u2f via env vars ([c38817c](https://github.com/standardnotes/server/commit/c38817c62e8109f1d5837dcda4a07f1b73976c72))
# [1.85.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.84.11...@standardnotes/auth-server@1.85.0) (2023-01-23)
### Features
* **auth:** add configuring u2f expect origin ([1797bc8](https://github.com/standardnotes/server/commit/1797bc81812be879b92c7d631085e76a36d37d45))
## [1.84.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.84.10...@standardnotes/auth-server@1.84.11) (2023-01-23)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.84.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.84.9...@standardnotes/auth-server@1.84.10) (2023-01-23)
### Bug Fixes
* **auth:** upgrade simplewebauthn types ([52ce5f3](https://github.com/standardnotes/server/commit/52ce5f3a2f3790042793ac73ddad70cd1b578a6b))
## [1.84.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.84.8...@standardnotes/auth-server@1.84.9) (2023-01-23)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.84.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.84.7...@standardnotes/auth-server@1.84.8) (2023-01-20)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.84.8",
"version": "1.87.1",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -32,12 +32,13 @@
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
},
"dependencies": {
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.28.1",
"@sentry/tracing": "^7.28.1",
"@simplewebauthn/server": "^6.2.2",
"@simplewebauthn/typescript-types": "^6.3.0-alpha.1",
"@standardnotes/api": "^1.24.5",
"@simplewebauthn/server": "^7.0.0",
"@standardnotes/api": "^1.24.9",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
@@ -71,6 +72,7 @@
"winston": "^3.8.1"
},
"devDependencies": {
"@simplewebauthn/typescript-types": "^7.0.0",
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.9",
"@types/express": "^4.17.14",

View File

@@ -462,6 +462,12 @@ export class ContainerConfigLoader {
container
.bind(TYPES.U2F_RELYING_PARTY_ID)
.toConstantValue(env.get('U2F_RELYING_PARTY_ID', true) ?? 'standardnotes.com')
container
.bind(TYPES.U2F_EXPECTED_ORIGIN)
.toConstantValue(env.get('U2F_EXPECTED_ORIGIN', true) ?? 'https://app.standardnotes.com')
container
.bind(TYPES.U2F_REQUIRE_USER_VERIFICATION)
.toConstantValue(env.get('U2F_REQUIRE_USER_VERIFICATION', true) === 'true')
// Services
container.bind<UAParser>(TYPES.DeviceDetector).toConstantValue(new UAParser())
container.bind<SessionService>(TYPES.SessionService).to(SessionService)
@@ -575,6 +581,8 @@ export class ContainerConfigLoader {
container.get(TYPES.AuthenticatorRepository),
container.get(TYPES.AuthenticatorChallengeRepository),
container.get(TYPES.U2F_RELYING_PARTY_ID),
container.get(TYPES.U2F_EXPECTED_ORIGIN),
container.get(TYPES.U2F_REQUIRE_USER_VERIFICATION),
),
)
container
@@ -592,6 +600,8 @@ export class ContainerConfigLoader {
container.get(TYPES.AuthenticatorRepository),
container.get(TYPES.AuthenticatorChallengeRepository),
container.get(TYPES.U2F_RELYING_PARTY_ID),
container.get(TYPES.U2F_EXPECTED_ORIGIN),
container.get(TYPES.U2F_REQUIRE_USER_VERIFICATION),
),
)
container
@@ -655,6 +665,7 @@ export class ContainerConfigLoader {
container.get(TYPES.IncreaseLoginAttempts),
container.get(TYPES.ClearLoginAttempts),
container.get(TYPES.DeleteSetting),
container.get(TYPES.AuthenticatorRepository),
),
)
container.bind<DeleteAccount>(TYPES.DeleteAccount).to(DeleteAccount)

View File

@@ -96,6 +96,8 @@ const TYPES = {
SESSION_TRACE_DAYS_TTL: Symbol.for('SESSION_TRACE_DAYS_TTL'),
U2F_RELYING_PARTY_ID: Symbol.for('U2F_RELYING_PARTY_ID'),
U2F_RELYING_PARTY_NAME: Symbol.for('U2F_RELYING_PARTY_NAME'),
U2F_EXPECTED_ORIGIN: Symbol.for('U2F_EXPECTED_ORIGIN'),
U2F_REQUIRE_USER_VERIFICATION: Symbol.for('U2F_REQUIRE_USER_VERIFICATION'),
// use cases
AuthenticateUser: Symbol.for('AuthenticateUser'),
AuthenticateRequest: Symbol.for('AuthenticateRequest'),

View File

@@ -93,7 +93,7 @@ export class AuthenticatorsController {
const result = await this.verifyAuthenticatorRegistrationResponse.execute({
userUuid: params.userUuid,
name: params.name,
registrationCredential: params.registrationCredential,
attestationResponse: params.attestationResponse,
})
if (result.isFailed()) {
@@ -142,7 +142,7 @@ export class AuthenticatorsController {
): Promise<VerifyAuthenticatorAuthenticationResponseResponse> {
const result = await this.verifyAuthenticatorAuthenticationResponse.execute({
userUuid: params.userUuid,
authenticationCredential: params.authenticationCredential,
authenticatorResponse: params.authenticatorResponse,
})
if (result.isFailed()) {

View File

@@ -4,7 +4,7 @@ import { BaseHttpController, controller, httpPost, results } from 'inversify-exp
import { Request, Response } from 'express'
import TYPES from '../Bootstrap/Types'
import { CreateListedAccount } from '../Domain/UseCase/CreateListedAccount/CreateListedAccount'
import { ErrorTag } from '@standardnotes/common'
import { ErrorTag } from '@standardnotes/api'
@controller('/listed')
export class ListedController extends BaseHttpController {

View File

@@ -1,4 +1,4 @@
import { ErrorTag } from '@standardnotes/common'
import { ErrorTag } from '@standardnotes/api'
import { Request, Response } from 'express'
import { inject } from 'inversify'
import {

View File

@@ -1,4 +1,4 @@
import { ErrorTag } from '@standardnotes/common'
import { ErrorTag } from '@standardnotes/api'
import { Request, Response } from 'express'
import { inject } from 'inversify'
import {

View File

@@ -1,5 +1,5 @@
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
import { ErrorTag } from '@standardnotes/common'
import { ErrorTag } from '@standardnotes/api'
import { SettingName } from '@standardnotes/settings'
import { Request, Response } from 'express'
import { inject } from 'inversify'

View File

@@ -1,5 +1,6 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { ErrorTag } from '@standardnotes/api'
import {
BaseHttpController,
controller,
@@ -18,7 +19,6 @@ import { GetUserSubscription } from '../Domain/UseCase/GetUserSubscription/GetUs
import { ClearLoginAttempts } from '../Domain/UseCase/ClearLoginAttempts'
import { IncreaseLoginAttempts } from '../Domain/UseCase/IncreaseLoginAttempts'
import { ChangeCredentials } from '../Domain/UseCase/ChangeCredentials/ChangeCredentials'
import { ErrorTag } from '@standardnotes/common'
@controller('/users')
export class UsersController extends BaseHttpController {

View File

@@ -8,12 +8,12 @@ import {
results,
} from 'inversify-express-utils'
import { CreateValetTokenPayload } from '@standardnotes/responses'
import { ErrorTag } from '@standardnotes/api'
import { ValetTokenOperation } from '@standardnotes/security'
import { Uuid } from '@standardnotes/domain-core'
import TYPES from '../Bootstrap/Types'
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
import { ErrorTag } from '@standardnotes/common'
import { ValetTokenOperation } from '@standardnotes/security'
import { Uuid } from '@standardnotes/domain-core'
@controller('/valet-tokens', TYPES.ApiGatewayAuthMiddleware)
export class ValetTokenController extends BaseHttpController {

View File

@@ -3,8 +3,8 @@ import { Dates, Uuid } from '@standardnotes/domain-core'
export interface AuthenticatorProps {
name: string
userUuid: Uuid
credentialId: Buffer
credentialPublicKey: Buffer
credentialId: Uint8Array
credentialPublicKey: Uint8Array
counter: number
credentialDeviceType: string
credentialBackedUp: boolean

View File

@@ -8,4 +8,5 @@ export interface AuthenticatorRepositoryInterface {
findByUserUuidAndCredentialId(userUuid: Uuid, credentialId: Buffer): Promise<Authenticator | null>
save(authenticator: Authenticator): Promise<void>
remove(authenticator: Authenticator): Promise<void>
removeByUserUuid(userUuid: Uuid): Promise<void>
}

View File

@@ -2,6 +2,7 @@ import { Result } from '@standardnotes/domain-core'
import { AuthResponse20200115 } from '../../Auth/AuthResponse20200115'
import { AuthResponseFactory20200115 } from '../../Auth/AuthResponseFactory20200115'
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
import { CrypterInterface } from '../../Encryption/CrypterInterface'
import { Setting } from '../../Setting/Setting'
import { SettingServiceInterface } from '../../Setting/SettingServiceInterface'
@@ -24,6 +25,7 @@ describe('SignInWithRecoveryCodes', () => {
let increaseLoginAttempts: IncreaseLoginAttempts
let clearLoginAttempts: ClearLoginAttempts
let deleteSetting: DeleteSetting
let authenticatorRepository: AuthenticatorRepositoryInterface
const createUseCase = () =>
new SignInWithRecoveryCodes(
@@ -36,12 +38,13 @@ describe('SignInWithRecoveryCodes', () => {
increaseLoginAttempts,
clearLoginAttempts,
deleteSetting,
authenticatorRepository,
)
beforeEach(() => {
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByEmail = jest.fn().mockReturnValue({
uuid: '1-2-3',
uuid: '00000000-0000-0000-0000-000000000000',
encryptedPassword: '$2a$11$K3g6XoTau8VmLJcai1bB0eD9/YvBSBRtBhMprJOaVZ0U3SgasZH3a',
} as jest.Mocked<User>)
@@ -69,6 +72,9 @@ describe('SignInWithRecoveryCodes', () => {
deleteSetting = {} as jest.Mocked<DeleteSetting>
deleteSetting.execute = jest.fn()
authenticatorRepository = {} as jest.Mocked<AuthenticatorRepositoryInterface>
authenticatorRepository.removeByUserUuid = jest.fn()
})
it('should return error if password is not provided', async () => {
@@ -209,6 +215,24 @@ describe('SignInWithRecoveryCodes', () => {
expect(result.getError()).toBe('Could not sign in with recovery codes: Oops')
})
it('should return error if user has an invalid uuid', async () => {
userRepository.findOneByEmail = jest.fn().mockReturnValue({
uuid: '1-2-3',
encryptedPassword: '$2a$11$K3g6XoTau8VmLJcai1bB0eD9/YvBSBRtBhMprJOaVZ0U3SgasZH3a',
} as jest.Mocked<User>)
const result = await createUseCase().execute({
userAgent: 'user-agent',
username: 'test@test.te',
password: 'qweqwe123123',
codeVerifier: 'code-verifier',
recoveryCodes: 'foo',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Invalid user uuid')
})
it('should return auth response', async () => {
const result = await createUseCase().execute({
userAgent: 'user-agent',
@@ -220,6 +244,7 @@ describe('SignInWithRecoveryCodes', () => {
expect(clearLoginAttempts.execute).toHaveBeenCalled()
expect(deleteSetting.execute).toHaveBeenCalled()
expect(authenticatorRepository.removeByUserUuid).toHaveBeenCalled()
expect(result.isFailed()).toBe(false)
})
})

View File

@@ -1,5 +1,5 @@
import * as bcrypt from 'bcryptjs'
import { Result, UseCaseInterface, Username, Validator } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Username, Uuid, Validator } from '@standardnotes/domain-core'
import { SettingName } from '@standardnotes/settings'
import { ApiVersion } from '@standardnotes/api'
@@ -15,6 +15,7 @@ import { AuthResponseFactory20200115 } from '../../Auth/AuthResponseFactory20200
import { IncreaseLoginAttempts } from '../IncreaseLoginAttempts'
import { ClearLoginAttempts } from '../ClearLoginAttempts'
import { DeleteSetting } from '../DeleteSetting/DeleteSetting'
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
export class SignInWithRecoveryCodes implements UseCaseInterface<AuthResponse20200115> {
constructor(
@@ -27,6 +28,7 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<AuthResponse202
private increaseLoginAttempts: IncreaseLoginAttempts,
private clearLoginAttempts: ClearLoginAttempts,
private deleteSetting: DeleteSetting,
private authenticatorRepository: AuthenticatorRepositoryInterface,
) {}
async execute(dto: SignInWithRecoveryCodesDTO): Promise<Result<AuthResponse20200115>> {
@@ -65,6 +67,14 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<AuthResponse202
return Result.fail('Could not find user')
}
const userUuidOrError = Uuid.create(user.uuid)
if (userUuidOrError.isFailed()) {
await this.increaseLoginAttempts.execute({ email: username.value })
return Result.fail('Invalid user uuid')
}
const userUuid = userUuidOrError.getValue()
const passwordMatches = await bcrypt.compare(dto.password, user.encryptedPassword)
if (!passwordMatches) {
await this.increaseLoginAttempts.execute({ email: username.value })
@@ -110,6 +120,8 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<AuthResponse202
userUuid: user.uuid,
})
await this.authenticatorRepository.removeByUserUuid(userUuid)
await this.clearLoginAttempts.execute({ email: username.value })
return Result.ok(authResponse as AuthResponse20200115)

View File

@@ -17,6 +17,8 @@ describe('VerifyAuthenticatorAuthenticationResponse', () => {
authenticatorRepository,
authenticatorChallengeRepository,
'standardnotes.com',
'https://app.standardnotes.com',
true,
)
beforeEach(() => {
@@ -49,7 +51,7 @@ describe('VerifyAuthenticatorAuthenticationResponse', () => {
const result = await useCase.execute({
userUuid: 'invalid',
authenticationCredential: {
authenticatorResponse: {
authenticatorAttachment: 'platform',
clientExtensionResults: {},
id: 'id',
@@ -77,7 +79,7 @@ describe('VerifyAuthenticatorAuthenticationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
authenticationCredential: {
authenticatorResponse: {
authenticatorAttachment: 'platform',
clientExtensionResults: {},
id: 'id',
@@ -105,7 +107,7 @@ describe('VerifyAuthenticatorAuthenticationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
authenticationCredential: {
authenticatorResponse: {
authenticatorAttachment: 'platform',
clientExtensionResults: {},
id: 'id',
@@ -134,7 +136,7 @@ describe('VerifyAuthenticatorAuthenticationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
authenticationCredential: {
authenticatorResponse: {
authenticatorAttachment: 'platform',
clientExtensionResults: {},
id: 'id',
@@ -167,7 +169,7 @@ describe('VerifyAuthenticatorAuthenticationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
authenticationCredential: {
authenticatorResponse: {
authenticatorAttachment: 'platform',
clientExtensionResults: {},
id: 'id',
@@ -203,7 +205,7 @@ describe('VerifyAuthenticatorAuthenticationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
authenticationCredential: {
authenticatorResponse: {
authenticatorAttachment: 'platform',
clientExtensionResults: {},
id: 'id',

View File

@@ -11,6 +11,8 @@ export class VerifyAuthenticatorAuthenticationResponse implements UseCaseInterfa
private authenticatorRepository: AuthenticatorRepositoryInterface,
private authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface,
private relyingPartyId: string,
private expectedOrigin: string,
private requireUserVerification: boolean,
) {}
async execute(dto: VerifyAuthenticatorAuthenticationResponseDTO): Promise<Result<boolean>> {
@@ -27,21 +29,22 @@ export class VerifyAuthenticatorAuthenticationResponse implements UseCaseInterfa
const authenticator = await this.authenticatorRepository.findByUserUuidAndCredentialId(
userUuid,
Buffer.from(dto.authenticationCredential.id as string),
Buffer.from(dto.authenticatorResponse.id as string),
)
if (!authenticator) {
return Result.fail(
`Could not verify authenticator authentication response: authenticator ${dto.authenticationCredential.id} not found`,
`Could not verify authenticator authentication response: authenticator ${dto.authenticatorResponse.id} not found`,
)
}
let verification: VerifiedAuthenticationResponse
try {
verification = await verifyAuthenticationResponse({
credential: dto.authenticationCredential,
response: dto.authenticatorResponse,
expectedChallenge: authenticatorChallenge.props.challenge.toString(),
expectedOrigin: `https://${this.relyingPartyId}`,
expectedOrigin: this.expectedOrigin,
expectedRPID: this.relyingPartyId,
requireUserVerification: this.requireUserVerification,
authenticator: {
counter: authenticator.props.counter,
credentialID: authenticator.props.credentialId,

View File

@@ -1,4 +1,4 @@
export interface VerifyAuthenticatorAuthenticationResponseDTO {
userUuid: string
authenticationCredential: Record<string, unknown>
authenticatorResponse: Record<string, unknown>
}

View File

@@ -17,6 +17,8 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
authenticatorRepository,
authenticatorChallengeRepository,
'standardnotes.com',
'https://app.standardnotes.com',
true,
)
beforeEach(() => {
@@ -37,7 +39,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
const result = await useCase.execute({
userUuid: 'invalid',
name: 'name',
registrationCredential: {
attestationResponse: {
id: Buffer.from('id'),
rawId: Buffer.from('rawId'),
response: {
@@ -60,7 +62,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
name: '',
registrationCredential: {
attestationResponse: {
id: Buffer.from('id'),
rawId: Buffer.from('rawId'),
response: {
@@ -83,7 +85,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
name: 'name',
registrationCredential: {
attestationResponse: {
id: Buffer.from('id'),
rawId: Buffer.from('rawId'),
response: {
@@ -115,8 +117,8 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
counter: 1,
credentialBackedUp: true,
credentialDeviceType: 'singleDevice',
credentialID: Buffer.from('test'),
credentialPublicKey: Buffer.from('test'),
credentialID: Uint8Array.from([1, 2, 3]),
credentialPublicKey: Uint8Array.from([1, 2, 3]),
},
} as jest.Mocked<VerifiedRegistrationResponse>)
})
@@ -124,7 +126,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
name: 'name',
registrationCredential: {
attestationResponse: {
id: Buffer.from('id'),
rawId: Buffer.from('rawId'),
response: {
@@ -158,7 +160,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
name: 'name',
registrationCredential: {
attestationResponse: {
id: Buffer.from('id'),
rawId: Buffer.from('rawId'),
response: {
@@ -194,7 +196,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
name: 'name',
registrationCredential: {
attestationResponse: {
id: Buffer.from('id'),
rawId: Buffer.from('rawId'),
response: {
@@ -230,8 +232,8 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
counter: 1,
credentialBackedUp: true,
credentialDeviceType: 'singleDevice',
credentialID: Buffer.from('test'),
credentialPublicKey: Buffer.from('test'),
credentialID: Uint8Array.from([1, 2, 3]),
credentialPublicKey: Uint8Array.from([1, 2, 3]),
},
} as jest.Mocked<VerifiedRegistrationResponse>)
})
@@ -244,7 +246,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
name: 'name',
registrationCredential: {
attestationResponse: {
id: Buffer.from('id'),
rawId: Buffer.from('rawId'),
response: {
@@ -279,8 +281,8 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
counter: 1,
credentialBackedUp: true,
credentialDeviceType: 'singleDevice',
credentialID: Buffer.from('test'),
credentialPublicKey: Buffer.from('test'),
credentialID: Uint8Array.from([1, 2, 3]),
credentialPublicKey: Uint8Array.from([1, 2, 3]),
},
} as jest.Mocked<VerifiedRegistrationResponse>)
})
@@ -288,7 +290,7 @@ describe('VerifyAuthenticatorRegistrationResponse', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
name: 'name',
registrationCredential: {
attestationResponse: {
id: Buffer.from('id'),
rawId: Buffer.from('rawId'),
response: {

View File

@@ -11,6 +11,8 @@ export class VerifyAuthenticatorRegistrationResponse implements UseCaseInterface
private authenticatorRepository: AuthenticatorRepositoryInterface,
private authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface,
private relyingPartyId: string,
private expectedOrigin: string,
private requireUserVerification: boolean,
) {}
async execute(dto: VerifyAuthenticatorRegistrationResponseDTO): Promise<Result<boolean>> {
@@ -33,10 +35,11 @@ export class VerifyAuthenticatorRegistrationResponse implements UseCaseInterface
let verification: VerifiedRegistrationResponse
try {
verification = await verifyRegistrationResponse({
credential: dto.registrationCredential,
response: dto.attestationResponse,
expectedChallenge: authenticatorChallenge.props.challenge.toString(),
expectedOrigin: `https://${this.relyingPartyId}`,
expectedOrigin: this.expectedOrigin,
expectedRPID: this.relyingPartyId,
requireUserVerification: this.requireUserVerification,
})
if (!verification.verified) {

View File

@@ -1,5 +1,5 @@
export interface VerifyAuthenticatorRegistrationResponseDTO {
userUuid: string
name: string
registrationCredential: Record<string, unknown>
attestationResponse: Record<string, unknown>
}

View File

@@ -1,14 +1,19 @@
import 'reflect-metadata'
import { authenticator } from 'otplib'
import { SettingName } from '@standardnotes/settings'
import { SelectorInterface } from '@standardnotes/security'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { User } from '../User/User'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { VerifyMFA } from './VerifyMFA'
import { Setting } from '../Setting/Setting'
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
import { SettingName } from '@standardnotes/settings'
import { SelectorInterface } from '@standardnotes/security'
import { LockRepositoryInterface } from '../User/LockRepositoryInterface'
import { AuthenticatorRepositoryInterface } from '../Authenticator/AuthenticatorRepositoryInterface'
import { VerifyMFA } from './VerifyMFA'
import { Logger } from 'winston'
import { Authenticator } from '../Authenticator/Authenticator'
describe('VerifyMFA', () => {
let user: User
@@ -17,13 +22,27 @@ describe('VerifyMFA', () => {
let settingService: SettingServiceInterface
let booleanSelector: SelectorInterface<boolean>
let lockRepository: LockRepositoryInterface
let authenticatorRepository: AuthenticatorRepositoryInterface
let verifyAuthenticatorAuthenticationResponse: UseCaseInterface<boolean>
let logger: Logger
const pseudoKeyParamsKey = 'foobar'
const createVerifyMFA = () =>
new VerifyMFA(userRepository, settingService, booleanSelector, lockRepository, pseudoKeyParamsKey)
new VerifyMFA(
userRepository,
settingService,
booleanSelector,
lockRepository,
pseudoKeyParamsKey,
authenticatorRepository,
verifyAuthenticatorAuthenticationResponse,
logger,
)
beforeEach(() => {
user = {} as jest.Mocked<User>
user = {
uuid: '00000000-0000-0000-0000-000000000000',
} as jest.Mocked<User>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
@@ -42,164 +61,270 @@ describe('VerifyMFA', () => {
settingService = {} as jest.Mocked<SettingServiceInterface>
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
authenticatorRepository = {} as jest.Mocked<AuthenticatorRepositoryInterface>
authenticatorRepository.findByUserUuid = jest.fn().mockReturnValue([])
verifyAuthenticatorAuthenticationResponse = {} as jest.Mocked<UseCaseInterface<boolean>>
verifyAuthenticatorAuthenticationResponse.execute = jest.fn().mockReturnValue(Result.ok())
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
})
it('should pass MFA verification if user has no MFA enabled', async () => {
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
describe('2FA', () => {
it('should pass MFA verification if user has no MFA enabled', async () => {
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
expect(
await createVerifyMFA().execute({ email: 'test@test.te', requestParams: {}, preventOTPFromFurtherUsage: true }),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should pass MFA verification if user has MFA deleted', async () => {
setting = {
name: SettingName.MfaSecret,
value: null,
} as jest.Mocked<Setting>
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
expect(
await createVerifyMFA().execute({ email: 'test@test.te', requestParams: {}, preventOTPFromFurtherUsage: true }),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should pass MFA verification if user is not found and pseudo mfa is not required', async () => {
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
expect(
await createVerifyMFA().execute({ email: 'test@test.te', requestParams: {}, preventOTPFromFurtherUsage: true }),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should not pass MFA verification if user is not found and pseudo mfa is required', async () => {
booleanSelector.select = jest.fn().mockReturnValue(true)
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
expect(
await createVerifyMFA().execute({ email: 'test@test.te', requestParams: {}, preventOTPFromFurtherUsage: true }),
).toEqual({
success: false,
errorTag: 'mfa-required',
errorMessage: 'Please enter your two-factor authentication code.',
errorPayload: { mfa_key: expect.stringMatching(/^mfa_/) },
})
})
it('should pass MFA verification if mfa key is correctly encrypted', async () => {
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': authenticator.generate('shhhh') },
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).toHaveBeenCalledWith('test@test.te', expect.any(String))
})
it('should pass MFA verification without locking otp', async () => {
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': authenticator.generate('shhhh') },
preventOTPFromFurtherUsage: false,
}),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should not pass MFA verification if otp is already used within lock out period', async () => {
lockRepository.isOTPLocked = jest.fn().mockReturnValue(true)
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': authenticator.generate('shhhh') },
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-invalid',
errorMessage:
'The two-factor authentication code you entered has been already utilized. Please try again in a while.',
errorPayload: { mfa_key: 'mfa_1-2-3' },
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should not pass MFA verification if mfa is not correct', async () => {
setting = {
name: SettingName.MfaSecret,
value: 'shhhh2',
} as jest.Mocked<Setting>
settingService = {} as jest.Mocked<SettingServiceInterface>
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': 'test' },
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-invalid',
errorMessage: 'The two-factor authentication code you entered is incorrect. Please try again.',
errorPayload: { mfa_key: 'mfa_1-2-3' },
})
})
it('should not pass MFA verification if no mfa param is found in the request', async () => {
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { foo: 'bar' },
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-required',
errorMessage: 'Please enter your two-factor authentication code.',
errorPayload: { mfa_key: expect.stringMatching(/^mfa_/) },
})
})
it('should throw an error if the error is not handled mfa validation error', async () => {
settingService.findSettingWithDecryptedValue = jest.fn().mockImplementation(() => {
throw new Error('oops!')
})
let error = null
try {
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': 'test' },
preventOTPFromFurtherUsage: true,
expect(
await createVerifyMFA().execute({ email: 'test@test.te', requestParams: {}, preventOTPFromFurtherUsage: true }),
).toEqual({
success: true,
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should pass MFA verification if user has MFA deleted', async () => {
setting = {
name: SettingName.MfaSecret,
value: null,
} as jest.Mocked<Setting>
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
expect(
await createVerifyMFA().execute({ email: 'test@test.te', requestParams: {}, preventOTPFromFurtherUsage: true }),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should pass MFA verification if user is not found and pseudo mfa is not required', async () => {
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
expect(
await createVerifyMFA().execute({ email: 'test@test.te', requestParams: {}, preventOTPFromFurtherUsage: true }),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should not pass MFA verification if user is not found and pseudo mfa is required', async () => {
booleanSelector.select = jest.fn().mockReturnValue(true)
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
expect(
await createVerifyMFA().execute({ email: 'test@test.te', requestParams: {}, preventOTPFromFurtherUsage: true }),
).toEqual({
success: false,
errorTag: 'mfa-required',
errorMessage: 'Please enter your two-factor authentication code.',
errorPayload: { mfa_key: expect.stringMatching(/^mfa_/) },
})
})
it('should pass MFA verification if mfa key is correctly encrypted', async () => {
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': authenticator.generate('shhhh') },
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).toHaveBeenCalledWith('test@test.te', expect.any(String))
})
it('should pass MFA verification without locking otp', async () => {
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': authenticator.generate('shhhh') },
preventOTPFromFurtherUsage: false,
}),
).toEqual({
success: true,
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should not pass MFA verification if otp is already used within lock out period', async () => {
lockRepository.isOTPLocked = jest.fn().mockReturnValue(true)
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': authenticator.generate('shhhh') },
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-invalid',
errorMessage:
'The two-factor authentication code you entered has been already utilized. Please try again in a while.',
errorPayload: { mfa_key: 'mfa_1-2-3' },
})
expect(lockRepository.lockSuccessfullOTP).not.toHaveBeenCalled()
})
it('should not pass MFA verification if mfa is not correct', async () => {
setting = {
name: SettingName.MfaSecret,
value: 'shhhh2',
} as jest.Mocked<Setting>
settingService = {} as jest.Mocked<SettingServiceInterface>
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': 'test' },
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-invalid',
errorMessage: 'The two-factor authentication code you entered is incorrect. Please try again.',
errorPayload: { mfa_key: 'mfa_1-2-3' },
})
})
it('should not pass MFA verification if no mfa param is found in the request', async () => {
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { foo: 'bar' },
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-required',
errorMessage: 'Please enter your two-factor authentication code.',
errorPayload: { mfa_key: expect.stringMatching(/^mfa_/) },
})
})
it('should throw an error if the error is not handled mfa validation error', async () => {
settingService.findSettingWithDecryptedValue = jest.fn().mockImplementation(() => {
throw new Error('oops!')
})
let error = null
try {
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: { 'mfa_1-2-3': 'test' },
preventOTPFromFurtherUsage: true,
})
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
})
describe('U2F', () => {
beforeEach(() => {
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
authenticatorRepository.findByUserUuid = jest.fn().mockReturnValue([{} as jest.Mocked<Authenticator>])
})
it('should not pass if the user has an invalid uuid', async () => {
userRepository.findOneByEmail = jest.fn().mockReturnValue({ uuid: 'invalid' } as jest.Mocked<User>)
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: {},
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorMessage: 'User UUID is invalid.',
})
})
it('should not pass if the request is missing authenticator response', async () => {
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: {},
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-required',
errorMessage: 'Please authenticate with your U2F device.',
})
})
it('should not pass if the authenticator response verification fails', async () => {
verifyAuthenticatorAuthenticationResponse.execute = jest.fn().mockReturnValue(Result.fail('oops!'))
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: {
authenticator_response: {
id: Buffer.from([1]),
},
},
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-invalid',
errorMessage: 'Could not verify U2F device.',
})
})
it('should not pass if the authenticator is not verified', async () => {
verifyAuthenticatorAuthenticationResponse.execute = jest.fn().mockReturnValue(Result.ok(false))
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: {
authenticator_response: {
id: Buffer.from([1]),
},
},
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: false,
errorTag: 'mfa-invalid',
errorMessage: 'Could not verify U2F device.',
})
})
it('should pass if the authenticator is verified', async () => {
verifyAuthenticatorAuthenticationResponse.execute = jest.fn().mockReturnValue(Result.ok(true))
expect(
await createVerifyMFA().execute({
email: 'test@test.te',
requestParams: {
authenticator_response: {
id: Buffer.from([1]),
},
},
preventOTPFromFurtherUsage: true,
}),
).toEqual({
success: true,
})
})
})
})

View File

@@ -1,19 +1,24 @@
import * as crypto from 'crypto'
import { ErrorTag } from '@standardnotes/common'
import { ErrorTag } from '@standardnotes/api'
import { SettingName } from '@standardnotes/settings'
import { v4 as uuidv4 } from 'uuid'
import { inject, injectable } from 'inversify'
import { authenticator } from 'otplib'
import { SelectorInterface } from '@standardnotes/security'
import { UseCaseInterface as DomainUseCaseInterface, Uuid } from '@standardnotes/domain-core'
import TYPES from '../../Bootstrap/Types'
import { MFAValidationError } from '../Error/MFAValidationError'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
import { LockRepositoryInterface } from '../User/LockRepositoryInterface'
import { AuthenticatorRepositoryInterface } from '../Authenticator/AuthenticatorRepositoryInterface'
import { UseCaseInterface } from './UseCaseInterface'
import { VerifyMFADTO } from './VerifyMFADTO'
import { VerifyMFAResponse } from './VerifyMFAResponse'
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
import { SelectorInterface } from '@standardnotes/security'
import { LockRepositoryInterface } from '../User/LockRepositoryInterface'
import { Logger } from 'winston'
import { Setting } from '../Setting/Setting'
@injectable()
export class VerifyMFA implements UseCaseInterface {
@@ -23,6 +28,10 @@ export class VerifyMFA implements UseCaseInterface {
@inject(TYPES.BooleanSelector) private booleanSelector: SelectorInterface<boolean>,
@inject(TYPES.LockRepository) private lockRepository: LockRepositoryInterface,
@inject(TYPES.PSEUDO_KEY_PARAMS_KEY) private pseudoKeyParamsKey: string,
@inject(TYPES.AuthenticatorRepository) private authenticatorRepository: AuthenticatorRepositoryInterface,
@inject(TYPES.VerifyAuthenticatorAuthenticationResponse)
private verifyAuthenticatorAuthenticationResponse: DomainUseCaseInterface<boolean>,
@inject(TYPES.Logger) private logger: Logger,
) {}
async execute(dto: VerifyMFADTO): Promise<VerifyMFAResponse> {
@@ -48,24 +57,78 @@ export class VerifyMFA implements UseCaseInterface {
}
}
const userUuidOrError = Uuid.create(user.uuid)
if (userUuidOrError.isFailed()) {
return {
success: false,
errorMessage: 'User UUID is invalid.',
}
}
const userUuid = userUuidOrError.getValue()
let u2fEnabled = false
const u2fAuthenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
if (u2fAuthenticators.length > 0) {
u2fEnabled = true
}
const mfaSecret = await this.settingService.findSettingWithDecryptedValue({
userUuid: user.uuid,
settingName: SettingName.MfaSecret,
})
if (mfaSecret === null || mfaSecret.value === null) {
const twoFactorEnabled = mfaSecret !== null && mfaSecret.value !== null
if (u2fEnabled === false && twoFactorEnabled === false) {
return {
success: true,
}
}
const verificationResult = await this.verifyMFASecret(
dto.email,
mfaSecret.value,
dto.requestParams,
dto.preventOTPFromFurtherUsage,
)
if (u2fEnabled) {
if (!dto.requestParams.authenticator_response) {
return {
success: false,
errorTag: ErrorTag.MfaRequired,
errorMessage: 'Please authenticate with your U2F device.',
}
}
return verificationResult
const verificationResultOrError = await this.verifyAuthenticatorAuthenticationResponse.execute({
userUuid: userUuid.value,
authenticatorResponse: dto.requestParams.authenticator_response,
})
if (verificationResultOrError.isFailed()) {
this.logger.debug(`Could not verify U2F authentication: ${verificationResultOrError.getError()}`)
return {
success: false,
errorTag: ErrorTag.MfaInvalid,
errorMessage: 'Could not verify U2F device.',
}
}
const verificationResult = verificationResultOrError.getValue()
if (verificationResult === false) {
return {
success: false,
errorTag: ErrorTag.MfaInvalid,
errorMessage: 'Could not verify U2F device.',
}
}
return {
success: true,
}
} else {
const verificationResult = await this.verifyMFASecret(
dto.email,
(mfaSecret as Setting).value as string,
dto.requestParams,
dto.preventOTPFromFurtherUsage,
)
return verificationResult
}
} catch (error) {
if (error instanceof MFAValidationError) {
return {

View File

@@ -1,4 +1,4 @@
export interface VerifyAuthenticatorAuthenticationResponseRequestParams {
userUuid: string
authenticationCredential: Record<string, unknown>
authenticatorResponse: Record<string, unknown>
}

View File

@@ -1,5 +1,5 @@
export interface VerifyAuthenticatorRegistrationResponseRequestParams {
userUuid: string
name: string
registrationCredential: Record<string, unknown>
attestationResponse: Record<string, unknown>
}

View File

@@ -1,4 +1,5 @@
import { Request, Response } from 'express'
import { ErrorTag } from '@standardnotes/api'
import {
BaseHttpController,
controller,
@@ -16,7 +17,6 @@ import { VerifyMFA } from '../../Domain/UseCase/VerifyMFA'
import { IncreaseLoginAttempts } from '../../Domain/UseCase/IncreaseLoginAttempts'
import { Logger } from 'winston'
import { GetUserKeyParams } from '../../Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
import { ErrorTag } from '@standardnotes/common'
import { inject } from 'inversify'
import { AuthController } from '../../Controller/AuthController'

View File

@@ -51,7 +51,7 @@ export class InversifyExpressAuthenticatorsController extends BaseHttpController
async verifyRegistration(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.authenticatorsController.verifyRegistrationResponse({
userUuid: response.locals.user.uuid,
registrationCredential: request.body.registrationCredential,
attestationResponse: request.body.attestationResponse,
name: request.body.name,
})
@@ -71,7 +71,7 @@ export class InversifyExpressAuthenticatorsController extends BaseHttpController
async verifyAuthentication(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.authenticatorsController.verifyAuthenticationResponse({
userUuid: response.locals.user.uuid,
authenticationCredential: request.body,
authenticatorResponse: request.body,
})
return this.json(result.data, result.status)

View File

@@ -1,4 +1,4 @@
import { ErrorTag } from '@standardnotes/common'
import { ErrorTag } from '@standardnotes/api'
import { TokenDecoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
import { Request } from 'express'
import { inject } from 'inversify'

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