mirror of
https://github.com/standardnotes/server
synced 2026-02-03 20:01:11 -05:00
Compare commits
21 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57488bcd16 | ||
|
|
b6fda901ef | ||
|
|
14669df890 | ||
|
|
64525a65f2 | ||
|
|
61fc7efecb | ||
|
|
8c7c1e4745 | ||
|
|
f64d30ec88 | ||
|
|
384dfc8da4 | ||
|
|
841784ae8c | ||
|
|
f5683cfd94 | ||
|
|
0a420ce30e | ||
|
|
a5e7132d3c | ||
|
|
6dfb2be4a2 | ||
|
|
d81cbad550 | ||
|
|
51ad06b303 | ||
|
|
27048ad95c | ||
|
|
fa9bf0b448 | ||
|
|
305190b64e | ||
|
|
98e3d18335 | ||
|
|
72e398956b | ||
|
|
1e69a13a97 |
269
.pnp.cjs
generated
269
.pnp.cjs
generated
@@ -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/",\
|
||||
@@ -2579,6 +2626,34 @@ 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/",\
|
||||
"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"\
|
||||
}]\
|
||||
]],\
|
||||
["@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/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/typescript-types", "npm:6.3.0-alpha.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sinclair/typebox", [\
|
||||
["npm:0.24.44", {\
|
||||
"packageLocation": "./.yarn/cache/@sinclair-typebox-npm-0.24.44-38506ddef6-f37b9d28bf.zip/node_modules/@sinclair/typebox/",\
|
||||
@@ -2743,6 +2818,8 @@ const RAW_RUNTIME_STATE =
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/profiling-node", "npm:0.0.12"],\
|
||||
["@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.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
@@ -4869,6 +4946,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/",\
|
||||
@@ -5082,6 +5184,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/",\
|
||||
@@ -5100,6 +5211,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/",\
|
||||
@@ -5121,6 +5241,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/",\
|
||||
@@ -5188,6 +5317,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/",\
|
||||
@@ -5423,6 +5561,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/",\
|
||||
@@ -6547,6 +6696,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/",\
|
||||
@@ -8053,6 +8218,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/",\
|
||||
@@ -8071,6 +8247,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/",\
|
||||
@@ -8451,6 +8639,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", [\
|
||||
@@ -9653,6 +9848,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/",\
|
||||
@@ -9665,6 +9869,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/",\
|
||||
@@ -10278,6 +10494,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/",\
|
||||
@@ -10697,6 +10931,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/",\
|
||||
@@ -11753,6 +11996,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/",\
|
||||
@@ -13437,6 +13699,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/@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/@simplewebauthn-typescript-types-npm-6.3.0-alpha.1-629da05c10-5667c214e9.zip
vendored
Normal file
BIN
.yarn/cache/@simplewebauthn-typescript-types-npm-6.3.0-alpha.1-629da05c10-5667c214e9.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.
@@ -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.
|
||||
|
||||
# [1.42.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.41.3...@standardnotes/api-gateway@1.42.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add http endpoints for authenticators ([b6fda90](https://github.com/standardnotes/api-gateway/commit/b6fda901ef66a3e66541bd1e3f041b8268a1c3f5))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -21,6 +21,7 @@ import '../src/Controller/v1/FilesController'
|
||||
import '../src/Controller/v1/SubscriptionInvitesController'
|
||||
import '../src/Controller/v1/WorkspacesController'
|
||||
import '../src/Controller/v1/InvitesController'
|
||||
import '../src/Controller/v1/AuthenticatorsController'
|
||||
|
||||
import '../src/Controller/v2/PaymentsControllerV2'
|
||||
import '../src/Controller/v2/ActionsControllerV2'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.41.3",
|
||||
"version": "1.42.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { inject } from 'inversify'
|
||||
import { Request, Response } from 'express'
|
||||
import { controller, BaseHttpController, httpPost, httpGet } from 'inversify-express-utils'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/authenticators', TYPES.AuthMiddleware)
|
||||
export class AuthenticatorsController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/generate-registration-options')
|
||||
async generateRegistrationOptions(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
'authenticators/generate-registration-options',
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpGet('/generate-authentication-options')
|
||||
async generateAuthenticationOptions(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
'authenticators/generate-authentication-options',
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpPost('/verify-registration')
|
||||
async verifyRegistration(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'authenticators/verify-registration', request.body)
|
||||
}
|
||||
|
||||
@httpPost('/verify-authentication')
|
||||
async verifyAuthentication(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'authenticators/verify-authentication', request.body)
|
||||
}
|
||||
}
|
||||
@@ -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.77.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.76.0...@standardnotes/auth-server@1.77.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add http endpoints for authenticators ([b6fda90](https://github.com/standardnotes/server/commit/b6fda901ef66a3e66541bd1e3f041b8268a1c3f5))
|
||||
|
||||
# [1.76.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.75.0...@standardnotes/auth-server@1.76.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add verifying authenticator authentication response ([64525a6](https://github.com/standardnotes/server/commit/64525a65f2e1677f942868903f318d6700c34c74))
|
||||
|
||||
# [1.75.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.74.1...@standardnotes/auth-server@1.75.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add generating authenticator authentication options ([8c7c1e4](https://github.com/standardnotes/server/commit/8c7c1e4745647004f3dc361ec374014390952486))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -21,6 +21,7 @@ import '../src/Controller/ListedController'
|
||||
import '../src/Controller/SubscriptionSettingsController'
|
||||
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthenticatorsController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressUserRequestsController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController'
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class addUniqueIndexOnChallenges1672299743840 implements MigrationInterface {
|
||||
name = 'addUniqueIndexOnChallenges1672299743840'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `user_uuid_and_challenge` ON `authenticator_challenges`')
|
||||
await queryRunner.query('CREATE UNIQUE INDEX `unique_user_uuid` ON `authenticator_challenges` (`user_uuid`)')
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX `user_uuid_and_challenge` ON `authenticator_challenges` (`user_uuid`, `challenge`)',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `user_uuid_and_challenge` ON `authenticator_challenges`')
|
||||
await queryRunner.query('DROP INDEX `unique_user_uuid` ON `authenticator_challenges`')
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX `user_uuid_and_challenge` ON `authenticator_challenges` (`user_uuid`, `challenge`)',
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class removeCompoundIndex1672307975117 implements MigrationInterface {
|
||||
name = 'removeCompoundIndex1672307975117'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `user_uuid_and_challenge` ON `authenticator_challenges`')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX `user_uuid_and_challenge` ON `authenticator_challenges` (`user_uuid`, `challenge`)',
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.70.9",
|
||||
"version": "1.77.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -36,6 +36,8 @@
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@sentry/profiling-node": "^0.0.12",
|
||||
"@sentry/tracing": "^7.28.1",
|
||||
"@simplewebauthn/server": "^6.2.2",
|
||||
"@simplewebauthn/typescript-types": "^6.3.0-alpha.1",
|
||||
"@standardnotes/api": "^1.19.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
|
||||
@@ -203,6 +203,21 @@ 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'
|
||||
import { GenerateAuthenticatorAuthenticationOptions } from '../Domain/UseCase/GenerateAuthenticatorAuthenticationOptions/GenerateAuthenticatorAuthenticationOptions'
|
||||
import { VerifyAuthenticatorAuthenticationResponse } from '../Domain/UseCase/VerifyAuthenticatorAuthenticationResponse/VerifyAuthenticatorAuthenticationResponse'
|
||||
import { AuthenticatorsController } from '../Controller/AuthenticatorsController'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -280,11 +295,14 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<MapperInterface<SessionTrace, TypeORMSessionTrace>>(TYPES.SessionTracePersistenceMapper)
|
||||
.toConstantValue(new SessionTracePersistenceMapper())
|
||||
|
||||
// Controller
|
||||
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
||||
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
|
||||
container.bind<UserRequestsController>(TYPES.UserRequestsController).to(UserRequestsController)
|
||||
container
|
||||
.bind<MapperInterface<Authenticator, TypeORMAuthenticator>>(TYPES.AuthenticatorPersistenceMapper)
|
||||
.toConstantValue(new AuthenticatorPersistenceMapper())
|
||||
container
|
||||
.bind<MapperInterface<AuthenticatorChallenge, TypeORMAuthenticatorChallenge>>(
|
||||
TYPES.AuthenticatorChallengePersistenceMapper,
|
||||
)
|
||||
.toConstantValue(new AuthenticatorChallengePersistenceMapper())
|
||||
|
||||
// ORM
|
||||
container
|
||||
@@ -316,6 +334,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 +379,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)
|
||||
@@ -511,6 +551,38 @@ export class ContainerConfigLoader {
|
||||
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),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GenerateAuthenticatorAuthenticationOptions>(TYPES.GenerateAuthenticatorAuthenticationOptions)
|
||||
.toConstantValue(
|
||||
new GenerateAuthenticatorAuthenticationOptions(
|
||||
container.get(TYPES.AuthenticatorRepository),
|
||||
container.get(TYPES.AuthenticatorChallengeRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<VerifyAuthenticatorAuthenticationResponse>(TYPES.VerifyAuthenticatorAuthenticationResponse)
|
||||
.toConstantValue(
|
||||
new VerifyAuthenticatorAuthenticationResponse(
|
||||
container.get(TYPES.AuthenticatorRepository),
|
||||
container.get(TYPES.AuthenticatorChallengeRepository),
|
||||
),
|
||||
)
|
||||
|
||||
container
|
||||
.bind<CleanupSessionTraces>(TYPES.CleanupSessionTraces)
|
||||
@@ -565,6 +637,21 @@ export class ContainerConfigLoader {
|
||||
container.bind<CreateCrossServiceToken>(TYPES.CreateCrossServiceToken).to(CreateCrossServiceToken)
|
||||
container.bind<ProcessUserRequest>(TYPES.ProcessUserRequest).to(ProcessUserRequest)
|
||||
|
||||
// Controller
|
||||
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
||||
container
|
||||
.bind<AuthenticatorsController>(TYPES.AuthenticatorsController)
|
||||
.toConstantValue(
|
||||
new AuthenticatorsController(
|
||||
container.get(TYPES.GenerateAuthenticatorRegistrationOptions),
|
||||
container.get(TYPES.VerifyAuthenticatorRegistrationResponse),
|
||||
container.get(TYPES.GenerateAuthenticatorAuthenticationOptions),
|
||||
container.get(TYPES.VerifyAuthenticatorAuthenticationResponse),
|
||||
),
|
||||
)
|
||||
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
|
||||
container.bind<UserRequestsController>(TYPES.UserRequestsController).to(UserRequestsController)
|
||||
|
||||
// Handlers
|
||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||
container
|
||||
|
||||
@@ -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,8 +5,11 @@ const TYPES = {
|
||||
SQS: Symbol.for('SQS'),
|
||||
// Mapping
|
||||
SessionTracePersistenceMapper: Symbol.for('SessionTracePersistenceMapper'),
|
||||
AuthenticatorChallengePersistenceMapper: Symbol.for('AuthenticatorChallengePersistenceMapper'),
|
||||
AuthenticatorPersistenceMapper: Symbol.for('AuthenticatorPersistenceMapper'),
|
||||
// Controller
|
||||
AuthController: Symbol.for('AuthController'),
|
||||
AuthenticatorsController: Symbol.for('AuthenticatorsController'),
|
||||
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
|
||||
UserRequestsController: Symbol.for('UserRequestsController'),
|
||||
// Repositories
|
||||
@@ -26,6 +29,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 +43,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 +134,10 @@ const TYPES = {
|
||||
TraceSession: Symbol.for('TraceSession'),
|
||||
CleanupSessionTraces: Symbol.for('CleanupSessionTraces'),
|
||||
PersistStatistics: Symbol.for('PersistStatistics'),
|
||||
GenerateAuthenticatorRegistrationOptions: Symbol.for('GenerateAuthenticatorRegistrationOptions'),
|
||||
VerifyAuthenticatorRegistrationResponse: Symbol.for('VerifyAuthenticatorRegistrationResponse'),
|
||||
GenerateAuthenticatorAuthenticationOptions: Symbol.for('GenerateAuthenticatorAuthenticationOptions'),
|
||||
VerifyAuthenticatorAuthenticationResponse: Symbol.for('VerifyAuthenticatorAuthenticationResponse'),
|
||||
// Handlers
|
||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||
|
||||
122
packages/auth/src/Controller/AuthenticatorsController.ts
Normal file
122
packages/auth/src/Controller/AuthenticatorsController.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { HttpStatusCode } from '@standardnotes/api'
|
||||
|
||||
import { GenerateAuthenticatorAuthenticationOptions } from '../Domain/UseCase/GenerateAuthenticatorAuthenticationOptions/GenerateAuthenticatorAuthenticationOptions'
|
||||
import { GenerateAuthenticatorRegistrationOptions } from '../Domain/UseCase/GenerateAuthenticatorRegistrationOptions/GenerateAuthenticatorRegistrationOptions'
|
||||
import { VerifyAuthenticatorAuthenticationResponse } from '../Domain/UseCase/VerifyAuthenticatorAuthenticationResponse/VerifyAuthenticatorAuthenticationResponse'
|
||||
import { VerifyAuthenticatorRegistrationResponse } from '../Domain/UseCase/VerifyAuthenticatorRegistrationResponse/VerifyAuthenticatorRegistrationResponse'
|
||||
import { GenerateAuthenticatorAuthenticationOptionsRequestParams } from '../Infra/Http/Request/GenerateAuthenticatorAuthenticationOptionsRequestParams'
|
||||
import { GenerateAuthenticatorRegistrationOptionsRequestParams } from '../Infra/Http/Request/GenerateAuthenticatorRegistrationOptionsRequestParams'
|
||||
import { VerifyAuthenticatorAuthenticationResponseRequestParams } from '../Infra/Http/Request/VerifyAuthenticatorAuthenticationResponseRequestParams'
|
||||
import { VerifyAuthenticatorRegistrationResponseRequestParams } from '../Infra/Http/Request/VerifyAuthenticatorRegistrationResponseRequestParams'
|
||||
import { GenerateAuthenticatorAuthenticationOptionsResponse } from '../Infra/Http/Response/GenerateAuthenticatorAuthenticationOptionsResponse'
|
||||
import { GenerateAuthenticatorRegistrationOptionsResponse } from '../Infra/Http/Response/GenerateAuthenticatorRegistrationOptionsResponse'
|
||||
import { VerifyAuthenticatorAuthenticationResponseResponse } from '../Infra/Http/Response/VerifyAuthenticatorAuthenticationResponseResponse'
|
||||
import { VerifyAuthenticatorRegistrationResponseResponse } from '../Infra/Http/Response/VerifyAuthenticatorRegistrationResponseResponse'
|
||||
|
||||
export class AuthenticatorsController {
|
||||
constructor(
|
||||
private generateAuthenticatorRegistrationOptions: GenerateAuthenticatorRegistrationOptions,
|
||||
private verifyAuthenticatorRegistrationResponse: VerifyAuthenticatorRegistrationResponse,
|
||||
private generateAuthenticatorAuthenticationOptions: GenerateAuthenticatorAuthenticationOptions,
|
||||
private verifyAuthenticatorAuthenticationResponse: VerifyAuthenticatorAuthenticationResponse,
|
||||
) {}
|
||||
|
||||
async generateRegistrationOptions(
|
||||
params: GenerateAuthenticatorRegistrationOptionsRequestParams,
|
||||
): Promise<GenerateAuthenticatorRegistrationOptionsResponse> {
|
||||
const result = await this.generateAuthenticatorRegistrationOptions.execute({
|
||||
userUuid: params.userUuid,
|
||||
username: params.username,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: {
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: { options: result.getValue() },
|
||||
}
|
||||
}
|
||||
|
||||
async verifyRegistrationResponse(
|
||||
params: VerifyAuthenticatorRegistrationResponseRequestParams,
|
||||
): Promise<VerifyAuthenticatorRegistrationResponseResponse> {
|
||||
const result = await this.verifyAuthenticatorRegistrationResponse.execute({
|
||||
userUuid: params.userUuid,
|
||||
registrationCredential: params.registrationCredential,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return {
|
||||
status: HttpStatusCode.Unauthorized,
|
||||
data: {
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: { success: result.getValue() },
|
||||
}
|
||||
}
|
||||
|
||||
async generateAuthenticationOptions(
|
||||
params: GenerateAuthenticatorAuthenticationOptionsRequestParams,
|
||||
): Promise<GenerateAuthenticatorAuthenticationOptionsResponse> {
|
||||
const result = await this.generateAuthenticatorAuthenticationOptions.execute({
|
||||
userUuid: params.userUuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: {
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: { options: result.getValue() },
|
||||
}
|
||||
}
|
||||
|
||||
async verifyAuthenticationResponse(
|
||||
params: VerifyAuthenticatorAuthenticationResponseRequestParams,
|
||||
): Promise<VerifyAuthenticatorAuthenticationResponseResponse> {
|
||||
const result = await this.verifyAuthenticatorAuthenticationResponse.execute({
|
||||
userUuid: params.userUuid,
|
||||
authenticationCredential: params.authenticationCredential,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return {
|
||||
status: HttpStatusCode.Unauthorized,
|
||||
data: {
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: { success: result.getValue() },
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
findByUserUuid(userUuid: Uuid): 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,8 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { Authenticator } from './Authenticator'
|
||||
|
||||
export interface AuthenticatorRepositoryInterface {
|
||||
findByUserUuid(userUuid: Uuid): Promise<Authenticator[]>
|
||||
findByUserUuidAndCredentialId(userUuid: Uuid, credentialId: Buffer): Promise<Authenticator | null>
|
||||
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',
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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 { GenerateAuthenticatorAuthenticationOptions } from './GenerateAuthenticatorAuthenticationOptions'
|
||||
|
||||
describe('GenerateAuthenticatorAuthenticationOptions', () => {
|
||||
let authenticatorRepository: AuthenticatorRepositoryInterface
|
||||
let authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new GenerateAuthenticatorAuthenticationOptions(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',
|
||||
})
|
||||
|
||||
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 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',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Could not generate authenticator registration options: Oops')
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should return authentication options', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(authenticatorChallengeRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { generateAuthenticationOptions } from '@simplewebauthn/server'
|
||||
|
||||
import { GenerateAuthenticatorAuthenticationOptionsDTO } from './GenerateAuthenticatorAuthenticationOptionsDTO'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { AuthenticatorChallenge } from '../../Authenticator/AuthenticatorChallenge'
|
||||
|
||||
export class GenerateAuthenticatorAuthenticationOptions implements UseCaseInterface<Record<string, unknown>> {
|
||||
constructor(
|
||||
private authenticatorRepository: AuthenticatorRepositoryInterface,
|
||||
private authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: GenerateAuthenticatorAuthenticationOptionsDTO): 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 authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
|
||||
const options = generateAuthenticationOptions({
|
||||
allowCredentials: authenticators.map((authenticator) => ({
|
||||
id: authenticator.props.credentialId,
|
||||
type: 'public-key',
|
||||
transports: authenticator.props.transports,
|
||||
})),
|
||||
userVerification: 'preferred',
|
||||
})
|
||||
|
||||
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,3 @@
|
||||
export interface GenerateAuthenticatorAuthenticationOptionsDTO {
|
||||
userUuid: string
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import { Uuid, Dates } from '@standardnotes/domain-core'
|
||||
import * as simeplWebAuthnServer from '@simplewebauthn/server'
|
||||
import { VerifiedAuthenticationResponse } from '@simplewebauthn/server'
|
||||
|
||||
import { Authenticator } from '../../Authenticator/Authenticator'
|
||||
import { AuthenticatorChallenge } from '../../Authenticator/AuthenticatorChallenge'
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { VerifyAuthenticatorAuthenticationResponse } from './VerifyAuthenticatorAuthenticationResponse'
|
||||
|
||||
describe('VerifyAuthenticatorAuthenticationResponse', () => {
|
||||
let authenticatorRepository: AuthenticatorRepositoryInterface
|
||||
let authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new VerifyAuthenticatorAuthenticationResponse(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.findByUserUuidAndCredentialId = jest.fn().mockReturnValue(authenticator)
|
||||
authenticatorRepository.save = jest.fn()
|
||||
|
||||
authenticatorChallengeRepository = {} as jest.Mocked<AuthenticatorChallengeRepositoryInterface>
|
||||
authenticatorChallengeRepository.findByUserUuid = 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',
|
||||
registrationCredential: {
|
||||
authenticatorAttachment: 'platform',
|
||||
clientExtensionResults: {},
|
||||
id: 'id',
|
||||
rawId: 'rawId',
|
||||
response: {
|
||||
authenticatorData: 'authenticatorData',
|
||||
clientDataJSON: 'clientDataJSON',
|
||||
signature: 'signature',
|
||||
userHandle: 'userHandle',
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual(
|
||||
'Could not verify authenticator authentication response: Given value is not a valid uuid: invalid',
|
||||
)
|
||||
})
|
||||
|
||||
it('should return error if authenticator is not found', async () => {
|
||||
authenticatorRepository.findByUserUuidAndCredentialId = jest.fn().mockReturnValue(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
registrationCredential: {
|
||||
authenticatorAttachment: 'platform',
|
||||
clientExtensionResults: {},
|
||||
id: 'id',
|
||||
rawId: 'rawId',
|
||||
response: {
|
||||
authenticatorData: 'authenticatorData',
|
||||
clientDataJSON: 'clientDataJSON',
|
||||
signature: 'signature',
|
||||
userHandle: 'userHandle',
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual(
|
||||
'Could not verify authenticator authentication response: authenticator id not found',
|
||||
)
|
||||
})
|
||||
|
||||
it('should return error if authenticator challenge is not found', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
registrationCredential: {
|
||||
authenticatorAttachment: 'platform',
|
||||
clientExtensionResults: {},
|
||||
id: 'id',
|
||||
rawId: 'rawId',
|
||||
response: {
|
||||
authenticatorData: 'authenticatorData',
|
||||
clientDataJSON: 'clientDataJSON',
|
||||
signature: 'signature',
|
||||
userHandle: 'userHandle',
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Could not verify authenticator authentication response: challenge not found')
|
||||
})
|
||||
|
||||
it('should return error if verification throws error', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mock = jest.spyOn(simeplWebAuthnServer, 'verifyAuthenticationResponse')
|
||||
mock.mockImplementation(() => {
|
||||
throw new Error('error')
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
registrationCredential: {
|
||||
authenticatorAttachment: 'platform',
|
||||
clientExtensionResults: {},
|
||||
id: 'id',
|
||||
rawId: 'rawId',
|
||||
response: {
|
||||
authenticatorData: 'authenticatorData',
|
||||
clientDataJSON: 'clientDataJSON',
|
||||
signature: 'signature',
|
||||
userHandle: 'userHandle',
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Could not verify authenticator authentication response: error')
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should return error if verification is not successful', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mock = jest.spyOn(simeplWebAuthnServer, 'verifyAuthenticationResponse')
|
||||
mock.mockReturnValue(
|
||||
Promise.resolve({
|
||||
verified: false,
|
||||
} as jest.Mocked<VerifiedAuthenticationResponse>),
|
||||
)
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
registrationCredential: {
|
||||
authenticatorAttachment: 'platform',
|
||||
clientExtensionResults: {},
|
||||
id: 'id',
|
||||
rawId: 'rawId',
|
||||
response: {
|
||||
authenticatorData: 'authenticatorData',
|
||||
clientDataJSON: 'clientDataJSON',
|
||||
signature: 'signature',
|
||||
userHandle: 'userHandle',
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Could not verify authenticator authentication response: verification failed')
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should persist new authenticator counter', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mock = jest.spyOn(simeplWebAuthnServer, 'verifyAuthenticationResponse')
|
||||
mock.mockReturnValue(
|
||||
Promise.resolve({
|
||||
verified: true,
|
||||
authenticationInfo: {
|
||||
newCounter: 2,
|
||||
},
|
||||
} as jest.Mocked<VerifiedAuthenticationResponse>),
|
||||
)
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
registrationCredential: {
|
||||
authenticatorAttachment: 'platform',
|
||||
clientExtensionResults: {},
|
||||
id: 'id',
|
||||
rawId: 'rawId',
|
||||
response: {
|
||||
authenticatorData: 'authenticatorData',
|
||||
clientDataJSON: 'clientDataJSON',
|
||||
signature: 'signature',
|
||||
userHandle: 'userHandle',
|
||||
},
|
||||
type: 'type',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(authenticatorRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { VerifiedAuthenticationResponse, verifyAuthenticationResponse } from '@simplewebauthn/server'
|
||||
import { AuthenticatorDevice } from '@simplewebauthn/typescript-types'
|
||||
|
||||
import { AuthenticatorChallengeRepositoryInterface } from '../../Authenticator/AuthenticatorChallengeRepositoryInterface'
|
||||
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
|
||||
import { VerifyAuthenticatorAuthenticationResponseDTO } from './VerifyAuthenticatorAuthenticationResponseDTO'
|
||||
import { RelyingParty } from '../../Authenticator/RelyingParty'
|
||||
|
||||
export class VerifyAuthenticatorAuthenticationResponse implements UseCaseInterface<boolean> {
|
||||
constructor(
|
||||
private authenticatorRepository: AuthenticatorRepositoryInterface,
|
||||
private authenticatorChallengeRepository: AuthenticatorChallengeRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: VerifyAuthenticatorAuthenticationResponseDTO): Promise<Result<boolean>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(`Could not verify authenticator authentication response: ${userUuidOrError.getError()}`)
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const authenticatorChallenge = await this.authenticatorChallengeRepository.findByUserUuid(userUuid)
|
||||
if (!authenticatorChallenge) {
|
||||
return Result.fail('Could not verify authenticator authentication response: challenge not found')
|
||||
}
|
||||
|
||||
const authenticator = await this.authenticatorRepository.findByUserUuidAndCredentialId(
|
||||
userUuid,
|
||||
Buffer.from(dto.authenticationCredential.id as string),
|
||||
)
|
||||
if (!authenticator) {
|
||||
return Result.fail(
|
||||
`Could not verify authenticator authentication response: authenticator ${dto.authenticationCredential.id} not found`,
|
||||
)
|
||||
}
|
||||
|
||||
let verification: VerifiedAuthenticationResponse
|
||||
try {
|
||||
verification = await verifyAuthenticationResponse({
|
||||
credential: dto.authenticationCredential,
|
||||
expectedChallenge: authenticatorChallenge.props.challenge.toString(),
|
||||
expectedOrigin: `https://${RelyingParty.RP_ID}`,
|
||||
expectedRPID: RelyingParty.RP_ID,
|
||||
authenticator: {
|
||||
counter: authenticator.props.counter,
|
||||
credentialID: authenticator.props.credentialId,
|
||||
credentialPublicKey: authenticator.props.credentialPublicKey,
|
||||
transports: authenticator.props.transports,
|
||||
} as AuthenticatorDevice,
|
||||
})
|
||||
|
||||
if (!verification.verified) {
|
||||
return Result.fail('Could not verify authenticator authentication response: verification failed')
|
||||
}
|
||||
} catch (error) {
|
||||
return Result.fail(`Could not verify authenticator authentication response: ${(error as Error).message}`)
|
||||
}
|
||||
|
||||
authenticator.props.counter = verification.authenticationInfo.newCounter as number
|
||||
|
||||
await this.authenticatorRepository.save(authenticator)
|
||||
|
||||
return Result.ok(true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface VerifyAuthenticatorAuthenticationResponseDTO {
|
||||
userUuid: string
|
||||
authenticationCredential: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
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.findByUserUuid = 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',
|
||||
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.findByUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
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.findByUserUuid = 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',
|
||||
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 verification throws error', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuid = jest.fn().mockReturnValue({
|
||||
props: {
|
||||
challenge: Buffer.from('challenge'),
|
||||
},
|
||||
} as jest.Mocked<AuthenticatorChallenge>)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mock = jest.spyOn(simeplWebAuthnServer, 'verifyRegistrationResponse')
|
||||
mock.mockImplementation(() => {
|
||||
throw new Error('Oops')
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
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()
|
||||
})
|
||||
|
||||
it('should return error if verification is missing registration info', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuid = 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,
|
||||
} as jest.Mocked<VerifiedRegistrationResponse>)
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
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: registration info not found',
|
||||
)
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should return error if authenticator could not be created', async () => {
|
||||
authenticatorChallengeRepository.findByUserUuid = 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',
|
||||
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.findByUserUuid = 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',
|
||||
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,67 @@
|
||||
import { Dates, Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { VerifiedRegistrationResponse, 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.findByUserUuid(userUuid)
|
||||
if (!authenticatorChallenge) {
|
||||
return Result.fail('Could not verify authenticator registration response: challenge not found')
|
||||
}
|
||||
|
||||
let verification: VerifiedRegistrationResponse
|
||||
try {
|
||||
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')
|
||||
}
|
||||
} catch (error) {
|
||||
return Result.fail(`Could not verify authenticator registration response: ${(error as Error).message}`)
|
||||
}
|
||||
|
||||
if (!verification.registrationInfo) {
|
||||
return Result.fail('Could not verify authenticator registration response: registration info not found')
|
||||
}
|
||||
|
||||
const authenticatorOrError = Authenticator.create({
|
||||
userUuid,
|
||||
counter: verification.registrationInfo.counter,
|
||||
credentialBackedUp: verification.registrationInfo.credentialBackedUp,
|
||||
credentialDeviceType: verification.registrationInfo.credentialDeviceType,
|
||||
credentialId: verification.registrationInfo.credentialID,
|
||||
credentialPublicKey: verification.registrationInfo.credentialPublicKey,
|
||||
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,4 @@
|
||||
export interface VerifyAuthenticatorRegistrationResponseDTO {
|
||||
userUuid: string
|
||||
registrationCredential: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GenerateAuthenticatorAuthenticationOptionsRequestParams {
|
||||
userUuid: string
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface GenerateAuthenticatorRegistrationOptionsRequestParams {
|
||||
userUuid: string
|
||||
username: string
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface VerifyAuthenticatorAuthenticationResponseRequestParams {
|
||||
userUuid: string
|
||||
authenticationCredential: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface VerifyAuthenticatorRegistrationResponseRequestParams {
|
||||
userUuid: string
|
||||
registrationCredential: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { GenerateAuthenticatorAuthenticationOptionsResponseBody } from './GenerateAuthenticatorAuthenticationOptionsResponseBody'
|
||||
|
||||
export interface GenerateAuthenticatorAuthenticationOptionsResponse extends HttpResponse {
|
||||
data: Either<GenerateAuthenticatorAuthenticationOptionsResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GenerateAuthenticatorAuthenticationOptionsResponseBody {
|
||||
options: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { GenerateAuthenticatorRegistrationOptionsResponseBody } from './GenerateAuthenticatorRegistrationOptionsResponseBody'
|
||||
|
||||
export interface GenerateAuthenticatorRegistrationOptionsResponse extends HttpResponse {
|
||||
data: Either<GenerateAuthenticatorRegistrationOptionsResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface GenerateAuthenticatorRegistrationOptionsResponseBody {
|
||||
options: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { VerifyAuthenticatorAuthenticationResponseResponseBody } from './VerifyAuthenticatorAuthenticationResponseResponseBody'
|
||||
|
||||
export interface VerifyAuthenticatorAuthenticationResponseResponse extends HttpResponse {
|
||||
data: Either<VerifyAuthenticatorAuthenticationResponseResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface VerifyAuthenticatorAuthenticationResponseResponseBody {
|
||||
success: boolean
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||
import { Either } from '@standardnotes/common'
|
||||
|
||||
import { VerifyAuthenticatorRegistrationResponseResponseBody } from './VerifyAuthenticatorRegistrationResponseResponseBody'
|
||||
|
||||
export interface VerifyAuthenticatorRegistrationResponseResponse extends HttpResponse {
|
||||
data: Either<VerifyAuthenticatorRegistrationResponseResponseBody, HttpErrorResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface VerifyAuthenticatorRegistrationResponseResponseBody {
|
||||
success: boolean
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpGet,
|
||||
httpPost,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AuthenticatorsController } from '../../Controller/AuthenticatorsController'
|
||||
|
||||
@controller('/authenticators', TYPES.ApiGatewayAuthMiddleware)
|
||||
export class InversifyExpressAuthenticatorsController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.AuthenticatorsController) private authenticatorsController: AuthenticatorsController) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/generate-registration-options')
|
||||
async generateRegistrationOptions(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.authenticatorsController.generateRegistrationOptions({
|
||||
username: response.locals.user.email,
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpPost('/verify-registration')
|
||||
async verifyRegistration(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.authenticatorsController.verifyRegistrationResponse({
|
||||
userUuid: response.locals.user.uuid,
|
||||
registrationCredential: request.body,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpGet('/generate-authentication-options')
|
||||
async generateAuthenticationOptions(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.authenticatorsController.generateAuthenticationOptions({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpPost('/verify-authentication')
|
||||
async verifyAuthentication(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.authenticatorsController.verifyAuthenticationResponse({
|
||||
userUuid: response.locals.user.uuid,
|
||||
authenticationCredential: request.body,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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 save(authenticatorChallenge: AuthenticatorChallenge): Promise<void> {
|
||||
let persistence = this.mapper.toProjection(authenticatorChallenge)
|
||||
|
||||
const existing = await this.findByUserUuid(authenticatorChallenge.props.userUuid)
|
||||
if (existing !== null) {
|
||||
existing.props.challenge = authenticatorChallenge.props.challenge
|
||||
existing.props.createdAt = authenticatorChallenge.props.createdAt
|
||||
|
||||
persistence = this.mapper.toProjection(existing)
|
||||
}
|
||||
|
||||
await this.ormRepository.save(persistence)
|
||||
}
|
||||
|
||||
async findByUserUuid(userUuid: Uuid): Promise<AuthenticatorChallenge | null> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('challenge')
|
||||
.where('challenge.user_uuid = :userUuid', {
|
||||
userUuid: userUuid.value,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
if (persistence === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(persistence)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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 findByUserUuidAndCredentialId(userUuid: Uuid, credentialId: Buffer): Promise<Authenticator | null> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('authenticator')
|
||||
.where('authenticator.user_uuid = :userUuid AND authenticator.credential_id = :credentialId', {
|
||||
userUuid: userUuid.value,
|
||||
credentialId,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
if (persistence === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(persistence)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
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' })
|
||||
export class TypeORMAuthenticatorChallenge {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
@Index('unique_user_uuid', { unique: true })
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'challenge',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
})
|
||||
declare challenge: Buffer
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'datetime',
|
||||
})
|
||||
declare createdAt: Date
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
239
yarn.lock
239
yarn.lock
@@ -1323,6 +1323,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@noble/ed25519@npm:^1.6.1":
|
||||
version: 1.7.1
|
||||
resolution: "@noble/ed25519@npm:1.7.1"
|
||||
checksum: b1aa4b9264c2a26d1905b01493c81c124eb05758c00464fcc9ae92880f9696fc4b525d9ff2a22c859ca753982172ba7b180242a6adb0b8ecc52a944d2173e77c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nodelib/fs.scandir@npm:2.1.5":
|
||||
version: 2.1.5
|
||||
resolution: "@nodelib/fs.scandir@npm:2.1.5"
|
||||
@@ -1614,6 +1621,41 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-android@npm:^2.1.7":
|
||||
version: 2.3.3
|
||||
resolution: "@peculiar/asn1-android@npm:2.3.3"
|
||||
dependencies:
|
||||
"@peculiar/asn1-schema": "npm:^2.3.3"
|
||||
asn1js: "npm:^3.0.5"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 0c7cad544efe3a7235888a56baf70ce913b3604d53d2733f606b9904c8af52bad0cd55b7a28276676a707c655ee6a6d30ee6232454a0c568bc488dcbb88b943b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-schema@npm:^2.1.7, @peculiar/asn1-schema@npm:^2.3.3":
|
||||
version: 2.3.3
|
||||
resolution: "@peculiar/asn1-schema@npm:2.3.3"
|
||||
dependencies:
|
||||
asn1js: "npm:^3.0.5"
|
||||
pvtsutils: "npm:^1.3.2"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: f584f79d5a3652888452e72591cd5f910b1c4c59a359c7495a82d8f6463e3033a715bdd1954b52bcecb864f21f8844335025e84e782cc843c6649bc3304094ec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-x509@npm:^2.1.7":
|
||||
version: 2.3.4
|
||||
resolution: "@peculiar/asn1-x509@npm:2.3.4"
|
||||
dependencies:
|
||||
"@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.0"
|
||||
checksum: 10a865998042fc7dc4fff8de5d7b926ba37962fcf8d5b923c6ba322de388788d8fcf0c235d9298bf32d06f1262adebfe5003f92aaa45108d234303a25175a85b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pnpm/network.ca-file@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@pnpm/network.ca-file@npm:1.0.1"
|
||||
@@ -1844,6 +1886,31 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@simplewebauthn/server@npm:^6.2.2":
|
||||
version: 6.2.2
|
||||
resolution: "@simplewebauthn/server@npm:6.2.2"
|
||||
dependencies:
|
||||
"@noble/ed25519": "npm:^1.6.1"
|
||||
"@peculiar/asn1-android": "npm:^2.1.7"
|
||||
"@peculiar/asn1-schema": "npm:^2.1.7"
|
||||
"@peculiar/asn1-x509": "npm:^2.1.7"
|
||||
base64url: "npm:^3.0.1"
|
||||
cbor: "npm:^5.1.0"
|
||||
debug: "npm:^4.3.2"
|
||||
jsrsasign: "npm:^10.4.0"
|
||||
jwk-to-pem: "npm:^2.0.4"
|
||||
node-fetch: "npm:^2.6.0"
|
||||
checksum: 5ffb9b1c15a69ca74c80adf70d890ba1e0bba21040653d79dc4a443d505aefe9d085763918e491479b0e64dd32c214c2acdc96d44de0384e542411944d01e09b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@simplewebauthn/typescript-types@npm:^6.3.0-alpha.1":
|
||||
version: 6.3.0-alpha.1
|
||||
resolution: "@simplewebauthn/typescript-types@npm:6.3.0-alpha.1"
|
||||
checksum: 5667c214e9271c568dae2d5fe1e7d074f058415112a8f1c1138eefb5ca23735eef4be90185f454a96593624efe7c69135a8041070cb6ad419869bfa95ce2f7f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sinclair/typebox@npm:^0.24.1":
|
||||
version: 0.24.44
|
||||
resolution: "@sinclair/typebox@npm:0.24.44"
|
||||
@@ -1983,6 +2050,8 @@ __metadata:
|
||||
"@sentry/node": "npm:^7.28.1"
|
||||
"@sentry/profiling-node": "npm:^0.0.12"
|
||||
"@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.19.0"
|
||||
"@standardnotes/common": "workspace:*"
|
||||
"@standardnotes/domain-core": "workspace:^"
|
||||
@@ -3644,6 +3713,29 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"asn1.js@npm:^5.3.0":
|
||||
version: 5.4.1
|
||||
resolution: "asn1.js@npm:5.4.1"
|
||||
dependencies:
|
||||
bn.js: "npm:^4.0.0"
|
||||
inherits: "npm:^2.0.1"
|
||||
minimalistic-assert: "npm:^1.0.0"
|
||||
safer-buffer: "npm:^2.1.0"
|
||||
checksum: 5c36f81388e344c9417866bd20acd2d4164d2bc2827d4fd0e35714f8701a249b9c6118c70720758fe710a4723d65699c5be1e827f89e9eff1dbd1bfe910300fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"asn1js@npm:^3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "asn1js@npm:3.0.5"
|
||||
dependencies:
|
||||
pvtsutils: "npm:^1.3.2"
|
||||
pvutils: "npm:^1.1.3"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: d0bc57da97696cbf161ad24cb35f9442b59a9e59a30f30e13ba6e2fb9e69f417666d952cb436a9f309a3c88d4d89404493dcc277a5fdb4d0cdbeb03da0303bb1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"async@npm:^3.2.3, async@npm:^3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "async@npm:3.2.4"
|
||||
@@ -3784,6 +3876,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"base64url@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "base64url@npm:3.0.1"
|
||||
checksum: 72e1401ffe08693524f35bc2912a519bb3bf58f8911f3c4045d8c4bb6b5112187eae2eb45db895f464debe3ff562d46b76305e7cc4de4857365c47f73442c228
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bcryptjs@npm:2.4.3":
|
||||
version: 2.4.3
|
||||
resolution: "bcryptjs@npm:2.4.3"
|
||||
@@ -3798,6 +3897,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bignumber.js@npm:^9.0.1":
|
||||
version: 9.1.1
|
||||
resolution: "bignumber.js@npm:9.1.1"
|
||||
checksum: e44d00804916c299d01e1a83b435111dbced54c6f165df4a0034a8a0a27182d6698f93f408ec804b3ae80896fd6ad8ad43f37939883dc03ecd04a125742f1483
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"binary-extensions@npm:^2.0.0":
|
||||
version: 2.2.0
|
||||
resolution: "binary-extensions@npm:2.2.0"
|
||||
@@ -3816,6 +3922,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bn.js@npm:^4.0.0, bn.js@npm:^4.11.9":
|
||||
version: 4.12.0
|
||||
resolution: "bn.js@npm:4.12.0"
|
||||
checksum: bfb4590775a29dad10c8d42da5ba7fca9d4f796f6d278cb27f53c6a6272df5e58a3ca58d879487c9584db9e7a8f73ac843117183bebea2d627c1f0db95848ec8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"body-parser@npm:1.20.1":
|
||||
version: 1.20.1
|
||||
resolution: "body-parser@npm:1.20.1"
|
||||
@@ -3880,6 +3993,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"brorand@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "brorand@npm:1.1.0"
|
||||
checksum: f736e127fbac2d704b0b55935c297ec261112b93a178e15170da19c17500d448ebacff3b1edb075821363e8daecc739c062b40e920aa19b8cbed7f4fbe1ff6aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"browserslist@npm:^4.20.2":
|
||||
version: 4.21.1
|
||||
resolution: "browserslist@npm:4.21.1"
|
||||
@@ -4093,6 +4213,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cbor@npm:^5.1.0":
|
||||
version: 5.2.0
|
||||
resolution: "cbor@npm:5.2.0"
|
||||
dependencies:
|
||||
bignumber.js: "npm:^9.0.1"
|
||||
nofilter: "npm:^1.0.4"
|
||||
checksum: d60986b9d006dd60e462293629830afe2db89db6a06ee2665ac59618b7a76af5e8cef880e4eb484345c8e978fa8425b1cf1e5f3840b97f39ad9e3d2fa8f28bc6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^2.0.0, chalk@npm:^2.4.2":
|
||||
version: 2.4.2
|
||||
resolution: "chalk@npm:2.4.2"
|
||||
@@ -5038,6 +5168,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"elliptic@npm:^6.5.4":
|
||||
version: 6.5.4
|
||||
resolution: "elliptic@npm:6.5.4"
|
||||
dependencies:
|
||||
bn.js: "npm:^4.11.9"
|
||||
brorand: "npm:^1.1.0"
|
||||
hash.js: "npm:^1.0.0"
|
||||
hmac-drbg: "npm:^1.0.1"
|
||||
inherits: "npm:^2.0.4"
|
||||
minimalistic-assert: "npm:^1.0.1"
|
||||
minimalistic-crypto-utils: "npm:^1.0.1"
|
||||
checksum: 4453b008cf9e741a87f8e1935398c10124291026e7f2b99a512205a645c59586deaeefbb1e7149574481ec2c4cc7f34efc6ae6ae5bc35a94431be71c0375367e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"emittery@npm:^0.10.2":
|
||||
version: 0.10.2
|
||||
resolution: "emittery@npm:0.10.2"
|
||||
@@ -6287,6 +6432,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hash.js@npm:^1.0.0, hash.js@npm:^1.0.3":
|
||||
version: 1.1.7
|
||||
resolution: "hash.js@npm:1.1.7"
|
||||
dependencies:
|
||||
inherits: "npm:^2.0.3"
|
||||
minimalistic-assert: "npm:^1.0.1"
|
||||
checksum: e4266370d194fd31ed7bb51f5a943cf4e3b361321ea19a0dfcaab2e21400c3e581d8dec897364ed4530845c2c1b58d44dd6a9b3682cfd5ec02d0ce7bc802f1db
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"helmet@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "helmet@npm:6.0.0"
|
||||
@@ -6301,6 +6456,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hmac-drbg@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "hmac-drbg@npm:1.0.1"
|
||||
dependencies:
|
||||
hash.js: "npm:^1.0.3"
|
||||
minimalistic-assert: "npm:^1.0.0"
|
||||
minimalistic-crypto-utils: "npm:^1.0.1"
|
||||
checksum: 4e88d58ffc03e027990bbc31c0aa7b90dc4d2b3642ac3a8f3b71e3c43eb03416179ac601f36417312f0375cc382a9e39d80ade1ae239aff188701162bc84226d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hosted-git-info@npm:^2.1.4":
|
||||
version: 2.8.9
|
||||
resolution: "hosted-git-info@npm:2.8.9"
|
||||
@@ -6640,6 +6806,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ipaddr.js@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "ipaddr.js@npm:2.0.1"
|
||||
checksum: 04ce6c896c82b163a87d0be70fa1701dda54f1315f27419207c9ea95bea025cacbe6335d5e0c1270657158f60d17eeaa0bbb19b60e230e34532adfca786c6dc7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-arguments@npm:^1.0.4":
|
||||
version: 1.1.1
|
||||
resolution: "is-arguments@npm:1.1.1"
|
||||
@@ -7664,6 +7837,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsrsasign@npm:^10.4.0":
|
||||
version: 10.6.1
|
||||
resolution: "jsrsasign@npm:10.6.1"
|
||||
checksum: e8e9c1b24f78d506eccd3b0e68e6e9dc8011f2ca4bd5baedc33a3a64b63c5ff7e21d00a73ef95624fd9fd594743cc6dd60b39b17d6938c7a6a712242698bac14
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jwa@npm:^1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "jwa@npm:1.4.1"
|
||||
@@ -7675,6 +7855,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jwk-to-pem@npm:^2.0.4":
|
||||
version: 2.0.5
|
||||
resolution: "jwk-to-pem@npm:2.0.5"
|
||||
dependencies:
|
||||
asn1.js: "npm:^5.3.0"
|
||||
elliptic: "npm:^6.5.4"
|
||||
safe-buffer: "npm:^5.0.1"
|
||||
checksum: fced3a75b0fcc6145e703669e210b670789df5d85dbcbf11d3f97e2bb24954e70e889b69ddb1c6404089ae45c435a9bf09137dfd0290931b49c8ccd09fa5e704
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jws@npm:^3.2.2":
|
||||
version: 3.2.2
|
||||
resolution: "jws@npm:3.2.2"
|
||||
@@ -8209,6 +8400,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "minimalistic-assert@npm:1.0.1"
|
||||
checksum: e2310081d82a7f8a6ee7f338d167c82b3eb5378929b9eda3a9eb633cf0f0f19c029b69e6868264447d4f726644b52fdc4dda3bc985793a1aeba9b02b13ca3f8e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimalistic-crypto-utils@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "minimalistic-crypto-utils@npm:1.0.1"
|
||||
checksum: 7d909decd241bd658f941981ce53db4061c834daba807a5082d08fd2a0c488b8ef67904c90af38b70445e0220951a533ae3a181be5724ad342df4d9454286476
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
|
||||
version: 3.1.2
|
||||
resolution: "minimatch@npm:3.1.2"
|
||||
@@ -8501,7 +8706,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^2.6.7":
|
||||
"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.7":
|
||||
version: 2.6.7
|
||||
resolution: "node-fetch@npm:2.6.7"
|
||||
dependencies:
|
||||
@@ -8600,6 +8805,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nofilter@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "nofilter@npm:1.0.4"
|
||||
checksum: 9a26874e7d5505acbee413d33311ed6d934ff2695e021b1ea1c9f83adc02e6e8038ed2702fcbcfc022bcf5a10cae6e79c6ad7ec23ecb512cdbfa7994fef6532f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nopt@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "nopt@npm:5.0.0"
|
||||
@@ -9554,6 +9766,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pvtsutils@npm:^1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "pvtsutils@npm:1.3.2"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: eb22d3df60a341da289c59b2a34e4e55cadc70bd43caeb5fdd14d28e40f54112142e0e8bd5906f3d756e2c38b651599243def3ced6f1169335feb7933e210fa7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pvutils@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "pvutils@npm:1.1.3"
|
||||
checksum: 0cb4f4878f535b07ad409df26b4e724582a50cc9ddd71375cc8d79dc79b9d712dcc0cbe52f4385c27abe6887aa88a94c98377746d95f35a88f96ebb8a585814c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"q@npm:^1.5.1":
|
||||
version: 1.5.1
|
||||
resolution: "q@npm:1.5.1"
|
||||
@@ -10033,7 +10261,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0":
|
||||
"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.1.0":
|
||||
version: 2.1.2
|
||||
resolution: "safer-buffer@npm:2.1.2"
|
||||
checksum: d4199666e9e792968c0b88c2c35dd400f56d3eecb9affbcf5207922822eadf30cc06995bae3c5d0a653851bbd40fc0af578bf046bbf734199ce22433ba4da659
|
||||
@@ -11003,6 +11231,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tslib@npm:^2.4.0":
|
||||
version: 2.4.1
|
||||
resolution: "tslib@npm:2.4.1"
|
||||
checksum: a739a21e3ff059e62c7c3c0845333fbc9c081bcc91e015f2d246a31eeaf1fb0e29b10c0913c7d22f692b3203bb469fd60ca5288aa4f27d351a969df2d0714899
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tsutils@npm:^3.21.0":
|
||||
version: 3.21.0
|
||||
resolution: "tsutils@npm:3.21.0"
|
||||
|
||||
Reference in New Issue
Block a user