add webauthn routes
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
"@fastify/multipart": "^9.0.1",
|
||||
"@fastify/oauth2": "^8.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@simplewebauthn/server": "^13.1.1",
|
||||
"axios": "^1.7.9",
|
||||
"bcryptjs": "^3.0.0",
|
||||
"fastify": "^5.2.0",
|
||||
"fastify-type-provider-zod": "^4.0.2",
|
||||
|
||||
287
pnpm-lock.yaml
generated
287
pnpm-lock.yaml
generated
@@ -29,6 +29,12 @@ importers:
|
||||
'@paralleldrive/cuid2':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@simplewebauthn/server':
|
||||
specifier: ^13.1.1
|
||||
version: 13.1.1
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.7.9
|
||||
bcryptjs:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@@ -301,6 +307,12 @@ packages:
|
||||
'@hapi/wreck@18.1.0':
|
||||
resolution: {integrity: sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==}
|
||||
|
||||
'@hexagon/base64@1.1.28':
|
||||
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
|
||||
|
||||
'@levischuck/tiny-cbor@0.2.11':
|
||||
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
|
||||
|
||||
'@lukeed/ms@2.0.2':
|
||||
resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -315,6 +327,21 @@ packages:
|
||||
'@paralleldrive/cuid2@2.2.2':
|
||||
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
|
||||
|
||||
'@peculiar/asn1-android@2.3.15':
|
||||
resolution: {integrity: sha512-8U2TIj59cRlSXTX2d0mzUKP7whfWGFMzTeC3qPgAbccXFrPNZLaDhpNEdG5U2QZ/tBv/IHlCJ8s+KYXpJeop6w==}
|
||||
|
||||
'@peculiar/asn1-ecc@2.3.15':
|
||||
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
|
||||
|
||||
'@peculiar/asn1-rsa@2.3.15':
|
||||
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
|
||||
|
||||
'@peculiar/asn1-schema@2.3.15':
|
||||
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
|
||||
|
||||
'@peculiar/asn1-x509@2.3.15':
|
||||
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
|
||||
|
||||
'@sideway/address@4.1.5':
|
||||
resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
|
||||
|
||||
@@ -324,6 +351,10 @@ packages:
|
||||
'@sideway/pinpoint@2.0.0':
|
||||
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
|
||||
|
||||
'@simplewebauthn/server@13.1.1':
|
||||
resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@smithy/abort-controller@3.1.9':
|
||||
resolution: {integrity: sha512-yiW0WI30zj8ZKoSYNx90no7ugVn3khlyH/z5W8qtKBtVE6awRALbhSG+2SAHA1r6bO/6M9utxYKVZ3PCJ1rWxw==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
@@ -559,6 +590,13 @@ packages:
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
asn1js@3.0.5:
|
||||
resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
atomic-sleep@1.0.0:
|
||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@@ -566,6 +604,9 @@ packages:
|
||||
avvio@9.1.0:
|
||||
resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==}
|
||||
|
||||
axios@1.7.9:
|
||||
resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
@@ -583,6 +624,10 @@ packages:
|
||||
resolution: {integrity: sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
camel-case@4.1.2:
|
||||
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
|
||||
|
||||
@@ -592,6 +637,10 @@ packages:
|
||||
change-case@4.1.2:
|
||||
resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
constant-case@3.0.4:
|
||||
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
|
||||
|
||||
@@ -612,6 +661,10 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -619,6 +672,26 @@ packages:
|
||||
dot-case@3.0.4:
|
||||
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
@@ -675,6 +748,19 @@ packages:
|
||||
resolution: {integrity: sha512-Y5jIsuYR4BwWDYYQ2A/RWWE6gD8a0FMgtU+HOq1WKku+Cwdz8M1v8wcAmRXXM1/iqtoqg06v+LjAxMYbCjViMw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
follow-redirects@1.15.9:
|
||||
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
form-data@4.0.2:
|
||||
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -682,11 +768,38 @@ packages:
|
||||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
get-intrinsic@1.2.7:
|
||||
resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
glob@8.1.0:
|
||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
header-case@2.0.4:
|
||||
resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
|
||||
|
||||
@@ -739,9 +852,21 @@ packages:
|
||||
resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
memory-pager@1.5.0:
|
||||
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime@3.0.0:
|
||||
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -845,10 +970,20 @@ packages:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
pvtsutils@1.3.6:
|
||||
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
|
||||
|
||||
pvutils@1.1.3:
|
||||
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
quick-format-unescaped@4.0.4:
|
||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||
|
||||
@@ -1621,6 +1756,10 @@ snapshots:
|
||||
'@hapi/bourne': 3.0.0
|
||||
'@hapi/hoek': 11.0.7
|
||||
|
||||
'@hexagon/base64@1.1.28': {}
|
||||
|
||||
'@levischuck/tiny-cbor@0.2.11': {}
|
||||
|
||||
'@lukeed/ms@2.0.2': {}
|
||||
|
||||
'@mongodb-js/saslprep@1.1.9':
|
||||
@@ -1633,6 +1772,39 @@ snapshots:
|
||||
dependencies:
|
||||
'@noble/hashes': 1.6.1
|
||||
|
||||
'@peculiar/asn1-android@2.3.15':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
asn1js: 3.0.5
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-ecc@2.3.15':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
'@peculiar/asn1-x509': 2.3.15
|
||||
asn1js: 3.0.5
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-rsa@2.3.15':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
'@peculiar/asn1-x509': 2.3.15
|
||||
asn1js: 3.0.5
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-schema@2.3.15':
|
||||
dependencies:
|
||||
asn1js: 3.0.5
|
||||
pvtsutils: 1.3.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@peculiar/asn1-x509@2.3.15':
|
||||
dependencies:
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
asn1js: 3.0.5
|
||||
pvtsutils: 1.3.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@sideway/address@4.1.5':
|
||||
dependencies:
|
||||
'@hapi/hoek': 9.3.0
|
||||
@@ -1641,6 +1813,16 @@ snapshots:
|
||||
|
||||
'@sideway/pinpoint@2.0.0': {}
|
||||
|
||||
'@simplewebauthn/server@13.1.1':
|
||||
dependencies:
|
||||
'@hexagon/base64': 1.1.28
|
||||
'@levischuck/tiny-cbor': 0.2.11
|
||||
'@peculiar/asn1-android': 2.3.15
|
||||
'@peculiar/asn1-ecc': 2.3.15
|
||||
'@peculiar/asn1-rsa': 2.3.15
|
||||
'@peculiar/asn1-schema': 2.3.15
|
||||
'@peculiar/asn1-x509': 2.3.15
|
||||
|
||||
'@smithy/abort-controller@3.1.9':
|
||||
dependencies:
|
||||
'@smithy/types': 3.7.2
|
||||
@@ -2003,6 +2185,14 @@ snapshots:
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
asn1js@3.0.5:
|
||||
dependencies:
|
||||
pvtsutils: 1.3.6
|
||||
pvutils: 1.1.3
|
||||
tslib: 2.8.1
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
atomic-sleep@1.0.0: {}
|
||||
|
||||
avvio@9.1.0:
|
||||
@@ -2010,6 +2200,14 @@ snapshots:
|
||||
'@fastify/error': 4.0.0
|
||||
fastq: 1.17.1
|
||||
|
||||
axios@1.7.9:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
form-data: 4.0.2
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
bcryptjs@3.0.0: {}
|
||||
@@ -2022,6 +2220,11 @@ snapshots:
|
||||
|
||||
bson@6.10.1: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
camel-case@4.1.2:
|
||||
dependencies:
|
||||
pascal-case: 3.1.2
|
||||
@@ -2048,6 +2251,10 @@ snapshots:
|
||||
snake-case: 3.0.4
|
||||
tslib: 2.8.1
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
constant-case@3.0.4:
|
||||
dependencies:
|
||||
no-case: 3.0.4
|
||||
@@ -2064,6 +2271,8 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
dot-case@3.0.4:
|
||||
@@ -2071,6 +2280,27 @@ snapshots:
|
||||
no-case: 3.0.4
|
||||
tslib: 2.8.1
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.2.7
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.2
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
fast-decode-uri-component@1.0.1: {}
|
||||
@@ -2155,10 +2385,39 @@ snapshots:
|
||||
fast-querystring: 1.1.2
|
||||
safe-regex2: 4.0.0
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
|
||||
form-data@4.0.2:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
get-intrinsic@1.2.7:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.2
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
glob@8.1.0:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
@@ -2167,6 +2426,18 @@ snapshots:
|
||||
minimatch: 5.1.6
|
||||
once: 1.4.0
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
dependencies:
|
||||
has-symbols: 1.1.0
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
header-case@2.0.4:
|
||||
dependencies:
|
||||
capital-case: 1.0.4
|
||||
@@ -2231,8 +2502,16 @@ snapshots:
|
||||
|
||||
lru-cache@11.0.2: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
memory-pager@1.5.0: {}
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
mime@3.0.0: {}
|
||||
|
||||
minimatch@5.1.6:
|
||||
@@ -2344,8 +2623,16 @@ snapshots:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
pvtsutils@1.3.6:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
pvutils@1.1.3: {}
|
||||
|
||||
quick-format-unescaped@4.0.4: {}
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
@@ -16,8 +16,9 @@ import { rtsSchemas } from "./rts/rts.schema";
|
||||
import { taskSchemas } from "./task/task.schema";
|
||||
import { notificationSchemas } from "./notification/notification.schema";
|
||||
import { noteSchemas } from "./note/note.schema";
|
||||
import { webAuthnRoutes } from "./webauthn/webauthn.route";
|
||||
|
||||
const app = fastify({ logger: true });
|
||||
const app = fastify({ logger: true, trustProxy: true });
|
||||
|
||||
app.get("/health", (req, res) => {
|
||||
return { status: "OK" };
|
||||
@@ -33,6 +34,7 @@ app.register(cors, {
|
||||
});
|
||||
app.register(multipart, { limits: { fileSize: 50000000 } });
|
||||
app.register(authRoutes, { prefix: "/auth" });
|
||||
app.register(webAuthnRoutes, { prefix: "/webauthn" });
|
||||
app.register(routes, { prefix: "/api/v1" });
|
||||
|
||||
for (const schema of [
|
||||
|
||||
@@ -28,6 +28,21 @@ const userSchema = new mongoose.Schema({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
passKeys: [],
|
||||
challenge: new mongoose.Schema(
|
||||
{
|
||||
value: String,
|
||||
expiry: Date,
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
otp: new mongoose.Schema(
|
||||
{
|
||||
value: String,
|
||||
expiry: Date,
|
||||
},
|
||||
{ _id: false }
|
||||
),
|
||||
createdAt: Date,
|
||||
createdBy: mongoose.Types.ObjectId,
|
||||
lastLogin: Date,
|
||||
|
||||
@@ -17,3 +17,13 @@ export async function generateToken(): Promise<string> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function generateOTP() {
|
||||
let otp = "";
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
otp += crypto.randomInt(10);
|
||||
}
|
||||
|
||||
return parseInt(otp);
|
||||
}
|
||||
|
||||
59
src/utils/mail.ts
Normal file
59
src/utils/mail.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import axios from "axios";
|
||||
|
||||
let token = {
|
||||
value: "",
|
||||
expiry: new Date(),
|
||||
};
|
||||
|
||||
export async function getToken() {
|
||||
if (token.value != "" && new Date() < token.expiry) {
|
||||
return token.value;
|
||||
}
|
||||
|
||||
const res = await axios({
|
||||
url: `https://accounts.zoho.com/oauth/v2/token?refresh_token=${process.env.ZOHO_REFRESH_TOKEN}&grant_type=refresh_token&client_id=${process.env.ZOHO_CLIENT_ID}&client_secret=${process.env.ZOHO_CLIENT_SECRET}`,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (res.data.status && res.data.status.code != 200) {
|
||||
console.dir(res.data, { depth: null });
|
||||
throw "error fetching token";
|
||||
}
|
||||
|
||||
token.value = res.data.access_token;
|
||||
token.expiry = new Date(Date.now() + res.data.expires_in * 1000);
|
||||
|
||||
return token.value;
|
||||
}
|
||||
|
||||
export async function sendMail(
|
||||
to: string,
|
||||
subject: string,
|
||||
body: string
|
||||
): Promise<Boolean> {
|
||||
try {
|
||||
const token = await getToken();
|
||||
|
||||
const res = await axios({
|
||||
url: `https://mail.zoho.com/api/accounts/${process.env.ZOHO_ACCOUNT_ID}/messages`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Zoho-oauthtoken ${token}`,
|
||||
},
|
||||
data: {
|
||||
fromAddress: "akhil.reddy@qualyval.com",
|
||||
toAddress: to,
|
||||
subject,
|
||||
content: body,
|
||||
mailFormat: "plaintext",
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.response) console.log(err.response.data);
|
||||
else console.log(err);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
213
src/webauthn/webauthn.route.ts
Normal file
213
src/webauthn/webauthn.route.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import {
|
||||
generateAuthenticationOptions,
|
||||
generateRegistrationOptions,
|
||||
RegistrationResponseJSON,
|
||||
VerifiedAuthenticationResponse,
|
||||
verifyAuthenticationResponse,
|
||||
verifyRegistrationResponse,
|
||||
} from "@simplewebauthn/server";
|
||||
import { isoUint8Array } from "@simplewebauthn/server/helpers";
|
||||
import { getUserByEmail } from "../user/user.service";
|
||||
import { createSession } from "../auth/auth.service";
|
||||
import { generateOTP } from "../utils/id";
|
||||
import { sendMail } from "../utils/mail";
|
||||
|
||||
const rpID = process.env.SERVER_DOMAIN;
|
||||
const rpName = "Quicker Permits";
|
||||
const origin = `http://${rpID}:3000`;
|
||||
|
||||
export async function webAuthnRoutes(fastify: FastifyInstance) {
|
||||
fastify.post<{ Body: { email: string } }>(
|
||||
"/register/request",
|
||||
{
|
||||
schema: {
|
||||
body: {
|
||||
type: "object",
|
||||
properties: {
|
||||
email: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (req, res: FastifyReply) => {
|
||||
const { email } = req.body;
|
||||
|
||||
if (!email) {
|
||||
return res.code(400).send({ error: "Email is required" });
|
||||
}
|
||||
|
||||
const userInDB = await getUserByEmail(email);
|
||||
if (!userInDB) return res.code(400).send({ error: "not allowed" });
|
||||
|
||||
const otp = generateOTP();
|
||||
|
||||
const userId = userInDB.id;
|
||||
const options = await generateRegistrationOptions({
|
||||
rpName,
|
||||
rpID,
|
||||
userID: isoUint8Array.fromUTF8String(userId),
|
||||
userName: email,
|
||||
attestationType: "none",
|
||||
excludeCredentials: userInDB.passKeys.map((cred) => ({
|
||||
id: cred.credentialID,
|
||||
type: "public-key",
|
||||
transports: cred.transports,
|
||||
})),
|
||||
});
|
||||
|
||||
userInDB.challenge = {
|
||||
value: options.challenge,
|
||||
expiry: new Date(Date.now() + 60 * 5 * 1000),
|
||||
};
|
||||
|
||||
userInDB.otp = {
|
||||
value: otp,
|
||||
expiry: new Date(Date.now() + 60 * 5 * 1000),
|
||||
};
|
||||
|
||||
await userInDB.save();
|
||||
|
||||
const sent = await sendMail(
|
||||
email,
|
||||
"Code for Quicker Permits Authentication",
|
||||
`Your code is ${otp}`
|
||||
);
|
||||
if (!sent) return res.code(500).send({ error: "server error" });
|
||||
|
||||
return res.send(options);
|
||||
}
|
||||
);
|
||||
|
||||
fastify.post<{
|
||||
Body: {
|
||||
email: string;
|
||||
code: string;
|
||||
attestationResponse: RegistrationResponseJSON;
|
||||
};
|
||||
}>("/register/verify", async (req, res: FastifyReply) => {
|
||||
const { email, code, attestationResponse } = req.body;
|
||||
|
||||
const userInDB = await getUserByEmail(email);
|
||||
if (!userInDB) return res.code(400).send({ error: "not allowed" });
|
||||
|
||||
const challenge = userInDB.challenge;
|
||||
const requiredOTP = userInDB.otp;
|
||||
|
||||
if (!challenge || !requiredOTP)
|
||||
return res.code(400).send({ error: "not allowed" });
|
||||
|
||||
if (new Date() > challenge.expiry || new Date() > requiredOTP.expiry)
|
||||
return res.code(400).send({ error: "challenge expired" });
|
||||
|
||||
if (code != requiredOTP.value)
|
||||
return res.code(400).send({ error: "invalid code" });
|
||||
|
||||
try {
|
||||
const verification = await verifyRegistrationResponse({
|
||||
response: attestationResponse,
|
||||
expectedChallenge: challenge.value as string,
|
||||
expectedRPID: rpID,
|
||||
expectedOrigin: origin,
|
||||
});
|
||||
|
||||
if (!verification.verified) {
|
||||
return res.code(400).send({ error: "registration failed" });
|
||||
}
|
||||
|
||||
userInDB.passKeys.push({
|
||||
credentialID: verification.registrationInfo.credential.id,
|
||||
credentialPublicKey: verification.registrationInfo.credential.publicKey,
|
||||
counter: verification.registrationInfo.credential.counter,
|
||||
transports: attestationResponse.response.transports,
|
||||
});
|
||||
|
||||
await userInDB.save();
|
||||
|
||||
return res.code(200).send({ success: "registration complete" });
|
||||
} catch (error) {
|
||||
return res.code(400).send({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
fastify.post<{ Body: { email: string } }>(
|
||||
"/login/request",
|
||||
async (req, res) => {
|
||||
const { email } = req.body;
|
||||
|
||||
const userInDB = await getUserByEmail(email);
|
||||
|
||||
if (!userInDB || userInDB.passKeys.length === 0) {
|
||||
return res.code(400).send({ error: "user not registered" });
|
||||
}
|
||||
|
||||
const options: PublicKeyCredentialRequestOptionsJSON =
|
||||
await generateAuthenticationOptions({
|
||||
rpID,
|
||||
allowCredentials: userInDB.passKeys.map((cred) => ({
|
||||
id: cred.credentialID,
|
||||
type: "public-key",
|
||||
transports: cred.transports,
|
||||
})),
|
||||
userVerification: "preferred",
|
||||
});
|
||||
|
||||
userInDB.challenge.value = options.challenge;
|
||||
userInDB.challenge.expiry = new Date(Date.now() + 60 * 5 * 1000);
|
||||
|
||||
await userInDB.save();
|
||||
|
||||
return res.send(options);
|
||||
}
|
||||
);
|
||||
|
||||
// Authentication Verification (Step 4)
|
||||
fastify.post<{ Body: { email: string; assertionResponse: any } }>(
|
||||
"/login/verify",
|
||||
async (req, res) => {
|
||||
const { email, assertionResponse } = req.body;
|
||||
|
||||
const userInDB = await getUserByEmail(email);
|
||||
if (!userInDB) return res.code(400).send({ error: "not allowed" });
|
||||
|
||||
const challenge = userInDB.challenge;
|
||||
if (!challenge) return res.code(400).send({ error: "not allowed" });
|
||||
|
||||
if (new Date() > challenge.expiry)
|
||||
return res.code(400).send({ error: "challenge expired" });
|
||||
|
||||
try {
|
||||
const credential = userInDB.passKeys.find(
|
||||
(cred) => cred.credentialID === assertionResponse.id
|
||||
);
|
||||
|
||||
if (!credential)
|
||||
return res.code(400).send({ error: "credential not found" });
|
||||
|
||||
const verification: VerifiedAuthenticationResponse =
|
||||
await verifyAuthenticationResponse({
|
||||
response: assertionResponse,
|
||||
expectedChallenge: userInDB.challenge.value as string,
|
||||
expectedRPID: rpID,
|
||||
expectedOrigin: origin,
|
||||
credential: credential,
|
||||
});
|
||||
|
||||
if (!verification.verified)
|
||||
return res.code(400).send({ error: "Authentication failed" });
|
||||
|
||||
credential.counter = verification.authenticationInfo.newCounter;
|
||||
|
||||
const newSession = await createSession(
|
||||
userInDB.id,
|
||||
req.ip,
|
||||
req.headers["user-agent"]
|
||||
);
|
||||
|
||||
res.send({ session_token: newSession.sid });
|
||||
} catch (error) {
|
||||
res.code(400).send({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user