@rapidrest/auth 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/dist/lib/auth/BasicStrategy.js +82 -0
  4. package/dist/lib/auth/BasicStrategy.js.map +1 -0
  5. package/dist/lib/auth/FIDO2Strategy.js +179 -0
  6. package/dist/lib/auth/FIDO2Strategy.js.map +1 -0
  7. package/dist/lib/auth/MFAStrategy.js +219 -0
  8. package/dist/lib/auth/MFAStrategy.js.map +1 -0
  9. package/dist/lib/auth/OIDCStrategy.js +397 -0
  10. package/dist/lib/auth/OIDCStrategy.js.map +1 -0
  11. package/dist/lib/auth/OTPStrategy.js +143 -0
  12. package/dist/lib/auth/OTPStrategy.js.map +1 -0
  13. package/dist/lib/auth/PasskeyStrategy.js +185 -0
  14. package/dist/lib/auth/PasskeyStrategy.js.map +1 -0
  15. package/dist/lib/auth/TOTPStrategy.js +84 -0
  16. package/dist/lib/auth/TOTPStrategy.js.map +1 -0
  17. package/dist/lib/auth/index.js +10 -0
  18. package/dist/lib/auth/index.js.map +1 -0
  19. package/dist/lib/auth/shared.js +431 -0
  20. package/dist/lib/auth/shared.js.map +1 -0
  21. package/dist/lib/auth/types.js +12 -0
  22. package/dist/lib/auth/types.js.map +1 -0
  23. package/dist/lib/index.js +4 -0
  24. package/dist/lib/index.js.map +1 -0
  25. package/dist/lib/models/index.js +2 -0
  26. package/dist/lib/models/index.js.map +1 -0
  27. package/dist/lib/models/mongo/AliasMongo.js +77 -0
  28. package/dist/lib/models/mongo/AliasMongo.js.map +1 -0
  29. package/dist/lib/models/mongo/ProfileMongo.js +99 -0
  30. package/dist/lib/models/mongo/ProfileMongo.js.map +1 -0
  31. package/dist/lib/models/mongo/SecretMongo.js +71 -0
  32. package/dist/lib/models/mongo/SecretMongo.js.map +1 -0
  33. package/dist/lib/models/mongo/UserMongo.js +71 -0
  34. package/dist/lib/models/mongo/UserMongo.js.map +1 -0
  35. package/dist/lib/models/mongo/index.js +5 -0
  36. package/dist/lib/models/mongo/index.js.map +1 -0
  37. package/dist/lib/models/mongo.js +2 -0
  38. package/dist/lib/models/mongo.js.map +1 -0
  39. package/dist/lib/models/sql/AliasSQL.js +77 -0
  40. package/dist/lib/models/sql/AliasSQL.js.map +1 -0
  41. package/dist/lib/models/sql/ProfileSQL.js +99 -0
  42. package/dist/lib/models/sql/ProfileSQL.js.map +1 -0
  43. package/dist/lib/models/sql/SecretSQL.js +71 -0
  44. package/dist/lib/models/sql/SecretSQL.js.map +1 -0
  45. package/dist/lib/models/sql/UserSQL.js +68 -0
  46. package/dist/lib/models/sql/UserSQL.js.map +1 -0
  47. package/dist/lib/models/sql/index.js +5 -0
  48. package/dist/lib/models/sql/index.js.map +1 -0
  49. package/dist/lib/models/sql.js +2 -0
  50. package/dist/lib/models/sql.js.map +1 -0
  51. package/dist/lib/models/types.js +35 -0
  52. package/dist/lib/models/types.js.map +1 -0
  53. package/dist/lib/mongo.js +3 -0
  54. package/dist/lib/mongo.js.map +1 -0
  55. package/dist/lib/routes/BaseAliasRoute.js +10 -0
  56. package/dist/lib/routes/BaseAliasRoute.js.map +1 -0
  57. package/dist/lib/routes/BaseAuthBasicRoute.js +132 -0
  58. package/dist/lib/routes/BaseAuthBasicRoute.js.map +1 -0
  59. package/dist/lib/routes/BaseAuthFIDO2Route.js +191 -0
  60. package/dist/lib/routes/BaseAuthFIDO2Route.js.map +1 -0
  61. package/dist/lib/routes/BaseAuthMFARoute.js +310 -0
  62. package/dist/lib/routes/BaseAuthMFARoute.js.map +1 -0
  63. package/dist/lib/routes/BaseAuthOIDCRoute.js +231 -0
  64. package/dist/lib/routes/BaseAuthOIDCRoute.js.map +1 -0
  65. package/dist/lib/routes/BaseAuthOTPRoute.js +214 -0
  66. package/dist/lib/routes/BaseAuthOTPRoute.js.map +1 -0
  67. package/dist/lib/routes/BaseAuthPasskeyRoute.js +182 -0
  68. package/dist/lib/routes/BaseAuthPasskeyRoute.js.map +1 -0
  69. package/dist/lib/routes/BaseAuthTOTPRoute.js +133 -0
  70. package/dist/lib/routes/BaseAuthTOTPRoute.js.map +1 -0
  71. package/dist/lib/routes/BaseProfileRoute.js +10 -0
  72. package/dist/lib/routes/BaseProfileRoute.js.map +1 -0
  73. package/dist/lib/routes/BaseSecretRoute.js +399 -0
  74. package/dist/lib/routes/BaseSecretRoute.js.map +1 -0
  75. package/dist/lib/routes/BaseUserRoute.js +10 -0
  76. package/dist/lib/routes/BaseUserRoute.js.map +1 -0
  77. package/dist/lib/routes/UserUtils.js +78 -0
  78. package/dist/lib/routes/UserUtils.js.map +1 -0
  79. package/dist/lib/routes/index.js +13 -0
  80. package/dist/lib/routes/index.js.map +1 -0
  81. package/dist/lib/routes/mongo/BaseAliasRouteMongo.js +20 -0
  82. package/dist/lib/routes/mongo/BaseAliasRouteMongo.js.map +1 -0
  83. package/dist/lib/routes/mongo/BaseAuthBasicRouteMongo.js +14 -0
  84. package/dist/lib/routes/mongo/BaseAuthBasicRouteMongo.js.map +1 -0
  85. package/dist/lib/routes/mongo/BaseAuthFIDO2RouteMongo.js +14 -0
  86. package/dist/lib/routes/mongo/BaseAuthFIDO2RouteMongo.js.map +1 -0
  87. package/dist/lib/routes/mongo/BaseAuthMFARouteMongo.js +14 -0
  88. package/dist/lib/routes/mongo/BaseAuthMFARouteMongo.js.map +1 -0
  89. package/dist/lib/routes/mongo/BaseAuthOIDCRouteMongo.js +11 -0
  90. package/dist/lib/routes/mongo/BaseAuthOIDCRouteMongo.js.map +1 -0
  91. package/dist/lib/routes/mongo/BaseAuthOTPRouteMongo.js +14 -0
  92. package/dist/lib/routes/mongo/BaseAuthOTPRouteMongo.js.map +1 -0
  93. package/dist/lib/routes/mongo/BaseAuthPasskeyRouteMongo.js +14 -0
  94. package/dist/lib/routes/mongo/BaseAuthPasskeyRouteMongo.js.map +1 -0
  95. package/dist/lib/routes/mongo/BaseAuthTOTPRouteMongo.js +14 -0
  96. package/dist/lib/routes/mongo/BaseAuthTOTPRouteMongo.js.map +1 -0
  97. package/dist/lib/routes/mongo/BaseProfileRouteMongo.js +20 -0
  98. package/dist/lib/routes/mongo/BaseProfileRouteMongo.js.map +1 -0
  99. package/dist/lib/routes/mongo/BaseSecretRouteMongo.js +20 -0
  100. package/dist/lib/routes/mongo/BaseSecretRouteMongo.js.map +1 -0
  101. package/dist/lib/routes/mongo/BaseUserRouteMongo.js +20 -0
  102. package/dist/lib/routes/mongo/BaseUserRouteMongo.js.map +1 -0
  103. package/dist/lib/routes/mongo/index.js +12 -0
  104. package/dist/lib/routes/mongo/index.js.map +1 -0
  105. package/dist/lib/routes/mongo.js +2 -0
  106. package/dist/lib/routes/mongo.js.map +1 -0
  107. package/dist/lib/routes/sql/BaseAliasRouteSQL.js +20 -0
  108. package/dist/lib/routes/sql/BaseAliasRouteSQL.js.map +1 -0
  109. package/dist/lib/routes/sql/BaseAuthBasicRouteSQL.js +14 -0
  110. package/dist/lib/routes/sql/BaseAuthBasicRouteSQL.js.map +1 -0
  111. package/dist/lib/routes/sql/BaseAuthFIDO2RouteSQL.js +14 -0
  112. package/dist/lib/routes/sql/BaseAuthFIDO2RouteSQL.js.map +1 -0
  113. package/dist/lib/routes/sql/BaseAuthMFARouteSQL.js +14 -0
  114. package/dist/lib/routes/sql/BaseAuthMFARouteSQL.js.map +1 -0
  115. package/dist/lib/routes/sql/BaseAuthOIDCRouteSQL.js +11 -0
  116. package/dist/lib/routes/sql/BaseAuthOIDCRouteSQL.js.map +1 -0
  117. package/dist/lib/routes/sql/BaseAuthOTPRouteSQL.js +14 -0
  118. package/dist/lib/routes/sql/BaseAuthOTPRouteSQL.js.map +1 -0
  119. package/dist/lib/routes/sql/BaseAuthPasskeyRouteSQL.js +14 -0
  120. package/dist/lib/routes/sql/BaseAuthPasskeyRouteSQL.js.map +1 -0
  121. package/dist/lib/routes/sql/BaseAuthTOTPRouteSQL.js +14 -0
  122. package/dist/lib/routes/sql/BaseAuthTOTPRouteSQL.js.map +1 -0
  123. package/dist/lib/routes/sql/BaseProfileRouteSQL.js +20 -0
  124. package/dist/lib/routes/sql/BaseProfileRouteSQL.js.map +1 -0
  125. package/dist/lib/routes/sql/BaseSecretRouteSQL.js +20 -0
  126. package/dist/lib/routes/sql/BaseSecretRouteSQL.js.map +1 -0
  127. package/dist/lib/routes/sql/BaseUserRouteSQL.js +20 -0
  128. package/dist/lib/routes/sql/BaseUserRouteSQL.js.map +1 -0
  129. package/dist/lib/routes/sql/index.js +12 -0
  130. package/dist/lib/routes/sql/index.js.map +1 -0
  131. package/dist/lib/routes/sql.js +2 -0
  132. package/dist/lib/routes/sql.js.map +1 -0
  133. package/dist/lib/sql.js +3 -0
  134. package/dist/lib/sql.js.map +1 -0
  135. package/dist/types/auth/BasicStrategy.d.ts +39 -0
  136. package/dist/types/auth/FIDO2Strategy.d.ts +85 -0
  137. package/dist/types/auth/MFAStrategy.d.ts +110 -0
  138. package/dist/types/auth/OIDCStrategy.d.ts +188 -0
  139. package/dist/types/auth/OTPStrategy.d.ts +88 -0
  140. package/dist/types/auth/PasskeyStrategy.d.ts +81 -0
  141. package/dist/types/auth/TOTPStrategy.d.ts +49 -0
  142. package/dist/types/auth/index.d.ts +9 -0
  143. package/dist/types/auth/shared.d.ts +136 -0
  144. package/dist/types/auth/types.d.ts +127 -0
  145. package/dist/types/index.d.ts +3 -0
  146. package/dist/types/models/index.d.ts +1 -0
  147. package/dist/types/models/mongo/AliasMongo.d.ts +15 -0
  148. package/dist/types/models/mongo/ProfileMongo.d.ts +17 -0
  149. package/dist/types/models/mongo/SecretMongo.d.ts +14 -0
  150. package/dist/types/models/mongo/UserMongo.d.ts +14 -0
  151. package/dist/types/models/mongo/index.d.ts +4 -0
  152. package/dist/types/models/mongo.d.ts +1 -0
  153. package/dist/types/models/sql/AliasSQL.d.ts +15 -0
  154. package/dist/types/models/sql/ProfileSQL.d.ts +17 -0
  155. package/dist/types/models/sql/SecretSQL.d.ts +14 -0
  156. package/dist/types/models/sql/UserSQL.d.ts +14 -0
  157. package/dist/types/models/sql/index.d.ts +4 -0
  158. package/dist/types/models/sql.d.ts +1 -0
  159. package/dist/types/models/types.d.ts +136 -0
  160. package/dist/types/mongo.d.ts +2 -0
  161. package/dist/types/routes/BaseAliasRoute.d.ts +7 -0
  162. package/dist/types/routes/BaseAuthBasicRoute.d.ts +27 -0
  163. package/dist/types/routes/BaseAuthFIDO2Route.d.ts +50 -0
  164. package/dist/types/routes/BaseAuthMFARoute.d.ts +61 -0
  165. package/dist/types/routes/BaseAuthOIDCRoute.d.ts +33 -0
  166. package/dist/types/routes/BaseAuthOTPRoute.d.ts +53 -0
  167. package/dist/types/routes/BaseAuthPasskeyRoute.d.ts +41 -0
  168. package/dist/types/routes/BaseAuthTOTPRoute.d.ts +38 -0
  169. package/dist/types/routes/BaseProfileRoute.d.ts +7 -0
  170. package/dist/types/routes/BaseSecretRoute.d.ts +89 -0
  171. package/dist/types/routes/BaseUserRoute.d.ts +7 -0
  172. package/dist/types/routes/UserUtils.d.ts +21 -0
  173. package/dist/types/routes/index.d.ts +12 -0
  174. package/dist/types/routes/mongo/BaseAliasRouteMongo.d.ts +4 -0
  175. package/dist/types/routes/mongo/BaseAuthBasicRouteMongo.d.ts +7 -0
  176. package/dist/types/routes/mongo/BaseAuthFIDO2RouteMongo.d.ts +7 -0
  177. package/dist/types/routes/mongo/BaseAuthMFARouteMongo.d.ts +7 -0
  178. package/dist/types/routes/mongo/BaseAuthOIDCRouteMongo.d.ts +7 -0
  179. package/dist/types/routes/mongo/BaseAuthOTPRouteMongo.d.ts +7 -0
  180. package/dist/types/routes/mongo/BaseAuthPasskeyRouteMongo.d.ts +7 -0
  181. package/dist/types/routes/mongo/BaseAuthTOTPRouteMongo.d.ts +7 -0
  182. package/dist/types/routes/mongo/BaseProfileRouteMongo.d.ts +4 -0
  183. package/dist/types/routes/mongo/BaseSecretRouteMongo.d.ts +4 -0
  184. package/dist/types/routes/mongo/BaseUserRouteMongo.d.ts +4 -0
  185. package/dist/types/routes/mongo/index.d.ts +11 -0
  186. package/dist/types/routes/mongo.d.ts +1 -0
  187. package/dist/types/routes/sql/BaseAliasRouteSQL.d.ts +4 -0
  188. package/dist/types/routes/sql/BaseAuthBasicRouteSQL.d.ts +7 -0
  189. package/dist/types/routes/sql/BaseAuthFIDO2RouteSQL.d.ts +7 -0
  190. package/dist/types/routes/sql/BaseAuthMFARouteSQL.d.ts +7 -0
  191. package/dist/types/routes/sql/BaseAuthOIDCRouteSQL.d.ts +7 -0
  192. package/dist/types/routes/sql/BaseAuthOTPRouteSQL.d.ts +7 -0
  193. package/dist/types/routes/sql/BaseAuthPasskeyRouteSQL.d.ts +7 -0
  194. package/dist/types/routes/sql/BaseAuthTOTPRouteSQL.d.ts +7 -0
  195. package/dist/types/routes/sql/BaseProfileRouteSQL.d.ts +4 -0
  196. package/dist/types/routes/sql/BaseSecretRouteSQL.d.ts +4 -0
  197. package/dist/types/routes/sql/BaseUserRouteSQL.d.ts +4 -0
  198. package/dist/types/routes/sql/index.d.ts +11 -0
  199. package/dist/types/routes/sql.d.ts +1 -0
  200. package/dist/types/sql.d.ts +2 -0
  201. package/package.json +138 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (C) 2026 Jean-Philippe Steinmetz <caskater47@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # RapidREST: Authentication Library
2
+
3
+ A library for implementing a complete authentication server with [RapidREST](https://rapidrest.dev). It
4
+ provides the data models, persistence adapters, and HTTP routes needed to register and authenticate users via
5
+ password, TOTP, OTP (email/SMS), WebAuthn passkeys, FIDO2 hardware security keys, multi-factor authentication, and
6
+ OpenID Connect / OAuth 2.0. Using this library you can stand up a fully featured authorization server by writing
7
+ configuration and a handful of one-line route classes.
8
+
9
+ ## Features
10
+
11
+ ### Authentication Strategies:
12
+
13
+ * `BasicStrategy` - Simple id and password authentication
14
+ * `FIDO2Strategy` - FIDO2/WebAuthn hardware based authentication (e.g. YubiKey)
15
+ * `MFAStrategy` - Simple id and password + 2FA authentication [fido2|otp|totp]
16
+ * `OIDCStrategy` - OAuth 2.0 & OpenID Connect authentication
17
+ * `OTPStrategy` - One-Time Password (OTP) authentication (e.g. email, sms)
18
+ * `PasskeyStrategy` - WebAuthn based passkey authentication
19
+ * `TOTPStrategy` - RFC 6238 Time-Based One Time Password authentication (e.g. Google Authenticator, etc.)
20
+
21
+ ### Data Models
22
+
23
+ This library provides variants of each of the following data models that can be used against a MongoDB or SQL database. Classes are post-fixed
24
+ with either `Mongo` or `SQL` at the end of the name (e.g. `Alias` becomes `AliasMongo` for MongoDB, `AliasSQL` for SQL).
25
+
26
+ * `User` - Describes a single user account
27
+ * `Alias` - Describes an alternate identifying name (aka: alias) for a user account (e.g. email, phone, third-party OAuth ID)
28
+ * `Secret` - Stores secrets used to authenticate user accounts (e.g. `fido2`, `passkey`, `password`, `totp` secrets)
29
+ * `Profile` - Stores additional, personally identifying, information about a user (e.g. birthdate, legal name, verified contacts, preferences)
30
+
31
+ ### Route Handlers
32
+
33
+ This library provides variants of each of the following routes that can be used against a MongoDB or SQL database. Classes are post-fixed
34
+ with either `Mongo` or `SQL` at the end of the name (e.g. `BaseAliasRoute` becomes `BaseAliasRouteMongo` for MongoDB, `BaseAliasRouteSQL` for SQL).
35
+
36
+ #### Data Models
37
+
38
+ * `BaseAliasRoute` - Provides full CRUD operations for the `Alias` data model
39
+ * `BaseProfileRoute` - Provides full CRUD operations for the `Profile` data model
40
+ * `BaseSecretRoute` - Provides full CRUD operations for the `Secret` data model. Additionally includes endpoints for registration of
41
+ `fido2`, `passkey` and `totp` secrets.
42
+ * `BaseUserRoute` - Provides full CRUD operations for the `User` data model
43
+
44
+ #### Authentication Strategies
45
+
46
+ * `BaseAuthBasicRoute` - Implements the `BasicStrategy` authentication strategy
47
+ * `BaseAuthFIDO2Route` - Implements the `FIDO2Strategy` authentication strategy
48
+ * `BaseAuthMFARoute` - Implements the `MFAStrategy` authentication strategy
49
+ * `BaseAuthOIDCRoute` - Implements the `OIDCStrategy` authentication strategy
50
+ * `BaseAuthOTPRoute` - Implements the `OTPStrategy` authentication strategy
51
+ * `BaseAuthPasskeyRoute` - Implements the `PasskeyStrategy` authentication strategy
52
+ * `BaseAuthTOTPRoute` - Implements the `TOTPStrategy` authentication strategy
53
+
54
+ ## Requirements
55
+
56
+ This package targets Node.js `>=24.0.0` and is published as an ESM-only package.
57
+
58
+ It declares `@rapidrest/core` and `@rapidrest/service-core` as required peer dependencies. The remaining peer
59
+ dependencies are optional and only need to be installed if you use the corresponding feature:
60
+
61
+ | Peer dependency | Required for |
62
+ | --- | --- |
63
+ | `@rapidrest/core` | Always |
64
+ | `@rapidrest/service-core` | Always |
65
+ | `argon2` | Password (`Basic`, `MFA`) secrets — password hashing |
66
+ | `otplib` | `TOTP`, `OTP`, and `MFA` secrets/strategies |
67
+ | `@simplewebauthn/server` | `Passkey` and `FIDO2` strategies |
68
+ | `jwks-rsa` | Verifying `OIDC` providers that publish a JWKS endpoint |
69
+
70
+ Strategies dynamically `import()` their optional dependency the first time they're used and throw a descriptive
71
+ error (naming the missing package) if it isn't installed, rather than failing at package install time.
72
+
73
+ ## License
74
+
75
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,82 @@
1
+ import { getRequestData } from "./shared.js";
2
+ /**
3
+ * Describes the configuration options that can be used to initialize BasicStrategy.
4
+ *
5
+ * @author Jean-Philippe Steinmetz
6
+ */
7
+ export class BasicStrategyOptions {
8
+ constructor() {
9
+ /** The name of the header to look for when performing header based authentication. Default value is `Authorization`. */
10
+ this.headerKey = "authorization";
11
+ /** The authorization scheme type when using header based authentication. Default value is `jwt`. */
12
+ this.headerScheme = "basic";
13
+ /** The name of the request query parameter to retrieve the token from when using query based authentication. Default value is `auth_basic`. */
14
+ this.queryKey = "auth_basic";
15
+ /**
16
+ * Set to `true` to allow credentials to be supplied via the `queryKey` URL parameter.
17
+ * Disabled by default — query parameters appear in server logs, browser history, and
18
+ * Referer headers, which permanently exposes credentials outside the application.
19
+ */
20
+ this.allowQueryParam = false;
21
+ }
22
+ /** Override this function to handle asynchronous (non-blocking) verification of the login info. */
23
+ verify(uid, secret) {
24
+ throw new Error("Did you forget to override BasicStrategyOptions.verify?");
25
+ }
26
+ /** Override this function to handle synchronous (blocking) verification of the login info. */
27
+ verifySync(uid, secret) {
28
+ throw new Error("Did you forget to override BasicStrategyOptions.verifySync?");
29
+ }
30
+ }
31
+ /**
32
+ * Implements an authentication strategy that performs basic id and password authentication. This strategy requires an
33
+ * existing user account to have already registered a valid password that will be validated. The strategy does not
34
+ * implement how that password is to be validated or stored.
35
+ *
36
+ * @author Jean-Philippe Steinmetz <rapidrests@gmail.com>
37
+ */
38
+ export class BasicStrategy {
39
+ constructor(options = new BasicStrategyOptions()) {
40
+ this.name = "basic";
41
+ this.options = options;
42
+ }
43
+ async authenticate(req, res, required) {
44
+ let { data, payload } = getRequestData(req);
45
+ // If the login info has been found, verify it.
46
+ if (payload.id && payload.password) {
47
+ const user = await this.options.verify(payload.id, payload.password);
48
+ if (user) {
49
+ return {
50
+ data,
51
+ method: this.name,
52
+ payload,
53
+ user,
54
+ };
55
+ }
56
+ }
57
+ if (required) {
58
+ throw new Error("Invalid authorization request.");
59
+ }
60
+ return undefined;
61
+ }
62
+ authenticateSync(req, res, required) {
63
+ let { data, payload } = getRequestData(req);
64
+ // If the login info has been found, verify it.
65
+ if (payload.id && payload.password) {
66
+ const user = this.options.verifySync(payload.id, payload.password);
67
+ if (user) {
68
+ return {
69
+ data,
70
+ method: this.name,
71
+ payload,
72
+ user,
73
+ };
74
+ }
75
+ }
76
+ if (required) {
77
+ throw new Error("Invalid authorization request.");
78
+ }
79
+ return undefined;
80
+ }
81
+ }
82
+ //# sourceMappingURL=BasicStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BasicStrategy.js","sourceRoot":"","sources":["../../../src/auth/BasicStrategy.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAC;AAE3D;;;;GAIG;AACH,MAAM,OAAO,oBAAoB;IAAjC;QACI,wHAAwH;QACjH,cAAS,GAAW,eAAe,CAAC;QAC3C,oGAAoG;QAC7F,iBAAY,GAAW,OAAO,CAAC;QACtC,+IAA+I;QACxI,aAAQ,GAAW,YAAY,CAAC;QACvC;;;;WAIG;QACI,oBAAe,GAAY,KAAK,CAAC;IAS5C,CAAC;IARG,mGAAmG;IAC5F,MAAM,CAAC,GAAW,EAAE,MAAc;QACrC,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC/E,CAAC;IACD,8FAA8F;IACvF,UAAU,CAAC,GAAW,EAAE,MAAc;QACzC,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACnF,CAAC;CACJ;AAED;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IAItB,YAAY,UAAgC,IAAI,oBAAoB,EAAE;QAHtD,SAAI,GAAW,OAAO,CAAC;QAInC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;IAEM,KAAK,CAAC,YAAY,CACrB,GAAgB,EAChB,GAAiB,EACjB,QAAkB;QAElB,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAE5C,+CAA+C;QAC/C,IAAI,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACjC,MAAM,IAAI,GAAwB,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC1F,IAAI,IAAI,EAAE,CAAC;gBACP,OAAO;oBACH,IAAI;oBACJ,MAAM,EAAE,IAAI,CAAC,IAAI;oBACjB,OAAO;oBACP,IAAI;iBACP,CAAC;YACN,CAAC;QACL,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,SAAS,CAAC;IACrB,CAAC;IAEM,gBAAgB,CAAC,GAAgB,EAAE,GAAiB,EAAE,QAAkB;QAC3E,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAE5C,+CAA+C;QAC/C,IAAI,OAAO,CAAC,EAAE,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACjC,MAAM,IAAI,GAAwB,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;YACxF,IAAI,IAAI,EAAE,CAAC;gBACP,OAAO;oBACH,IAAI;oBACJ,MAAM,EAAE,IAAI,CAAC,IAAI;oBACjB,OAAO;oBACP,IAAI;iBACP,CAAC;YACN,CAAC;QACL,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,SAAS,CAAC;IACrB,CAAC;CACJ"}
@@ -0,0 +1,179 @@
1
+ import { generatePasskeyChallenge, isPasskeyResponse, verifyPasskeyChallenge } from "./shared.js";
2
+ /**
3
+ * Describes the configuration options that can be used to initialize FIDO2Strategy.
4
+ *
5
+ * @author Jean-Philippe Steinmetz
6
+ */
7
+ export class FIDO2StrategyOptions {
8
+ constructor(config) {
9
+ this.config = config;
10
+ }
11
+ /**
12
+ * Retrieves a previously-registered credential by its ID. Returns `undefined` if no credential
13
+ * with that ID is known.
14
+ * NOTE: You must override this function when using this strategy.
15
+ */
16
+ async getCredentialById(credentialId) {
17
+ throw new Error("Did you forget to override FIDO2StrategyOptions.getCredentialById?");
18
+ }
19
+ /**
20
+ * Retrieves all credentials registered for the given user. Used to build the `allowCredentials`
21
+ * list when a login ceremony is started with a known user hint.
22
+ * NOTE: You must override this function when using this strategy.
23
+ * @param id The unique identifier of the user or alias.
24
+ */
25
+ async getCredentials(id) {
26
+ throw new Error("Did you forget to override FIDO2StrategyOptions.getCredentials?");
27
+ }
28
+ /**
29
+ * Persists the updated signature counter for the given credential after a successful
30
+ * authentication. Must be called on every successful login to guard against cloned authenticators.
31
+ * NOTE: You must override this function when using this strategy.
32
+ */
33
+ async updateCredentialCounter(credentialId, newCounter) {
34
+ throw new Error("Did you forget to override FIDO2StrategyOptions.updateCredentialCounter?");
35
+ }
36
+ /**
37
+ * Retrieves the user data for the given unique identifier after authentication has completed successfully.
38
+ * NOTE: You must override this function when using this strategy.
39
+ * @param id The unique identifier of the user that has been successfully authenticated.
40
+ */
41
+ getUser(uid) {
42
+ throw new Error("Did you forget to override FIDO2StrategyOptions.getUser?");
43
+ }
44
+ }
45
+ /**
46
+ * Implements an authentication strategy for performing FIDO2 hardware security key (e.g. YubiKey)
47
+ * login via WebAuthn/CTAP2. Functionally this is the same WebAuthn protocol used by `PasskeyStrategy`
48
+ * — the distinction is one of intent and configuration (see `PasskeyConfig.authenticatorAttachment`/
49
+ * `residentKey`): a FIDO2 hardware key is typically registered as a `"cross-platform"`, non-discoverable
50
+ * credential tied to a known account, rather than a discoverable, possibly-synced passkey.
51
+ *
52
+ * The login flow has two phases:
53
+ *
54
+ * 1. Challenge - The client requests a challenge. A random challenge is generated and stored in the
55
+ * session, and the resulting options are returned for use with
56
+ * `navigator.credentials.get()`.
57
+ * 2. Verify - The client submits the signed assertion response. The response is verified against the
58
+ * stored challenge and the credential's public key, the credential's signature counter is updated,
59
+ * and the associated user is resolved via `verify()`.
60
+ *
61
+ * NOTE: Requires session support!
62
+ *
63
+ * @author Jean-Philippe Steinmetz <rapidrests@gmail.com>
64
+ */
65
+ export class FIDO2Strategy {
66
+ constructor(options) {
67
+ this.name = "fido2";
68
+ this.options = options;
69
+ }
70
+ async authenticate(req, res, required) {
71
+ if (req.body?.id || req.body?.response) {
72
+ return await this.verify(req, required);
73
+ }
74
+ else {
75
+ return await this.challenge(req, res);
76
+ }
77
+ }
78
+ authenticateSync(req, res, required) {
79
+ throw new Error("Not supported. This auth strategy must be used asynchronously.");
80
+ }
81
+ /**
82
+ * Begins a login ceremony: generates a challenge, stores it in the session, and writes the
83
+ * resulting options directly to the response for the client to pass to `navigator.credentials.get()`.
84
+ *
85
+ * @param req The source HTTP request. May optionally carry a `uid` query parameter to scope the
86
+ * allowed credentials to a known user; omitted entirely for a discoverable/"usernameless" flow.
87
+ * @param res The response to write the generated options to.
88
+ */
89
+ async challenge(req, res) {
90
+ if (!req.session) {
91
+ throw new Error("FIDO2Strategy requires session support. Configure the `session` config block so the " +
92
+ "session middleware is registered.");
93
+ }
94
+ if (!res) {
95
+ throw new Error("FIDO2Strategy requires a response object to begin a ceremony.");
96
+ }
97
+ // If a uid hint is given, scope allowCredentials to that user's registered credentials. If
98
+ // the hint resolves to zero credentials, allowCredentials is left undefined (same as the
99
+ // no-hint case) rather than sent as an empty list. Otherwise the response shape would leak
100
+ // whether a given uid has any passkeys registered, a user-enumeration side channel.
101
+ const uidHint = req.query?.uid;
102
+ let allowCredentials = undefined;
103
+ if (uidHint) {
104
+ const credentials = await this.options.getCredentials(uidHint);
105
+ if (credentials.length > 0) {
106
+ allowCredentials = credentials.map((c) => ({ id: c.id, transports: c.transports }));
107
+ }
108
+ }
109
+ const result = await generatePasskeyChallenge(this.options.config, req, allowCredentials);
110
+ res.status(200);
111
+ res.json(result);
112
+ return undefined;
113
+ }
114
+ /**
115
+ * Finishes a login ceremony: verifies the client-submitted assertion response against the
116
+ * stored challenge and credential, updates the credential's signature counter, and resolves the
117
+ * associated user.
118
+ *
119
+ * @param req The source HTTP request, carrying the assertion response in its body.
120
+ * @param required Set to `true` if authentication is required to pass, otherwise set to `false`.
121
+ */
122
+ async verify(req, required) {
123
+ if (!req.session) {
124
+ throw new Error("FIDO2Strategy requires session support. Configure the `session` config block so the " +
125
+ "session middleware is registered.");
126
+ }
127
+ if (!req.session.challenge) {
128
+ throw new Error("No FIDO2 ceremony in progress for this session.");
129
+ }
130
+ // The challenge is single-use regardless of outcome — cleared as soon as it's read, before
131
+ // verification is even attempted, rather than only on the success path.
132
+ const expectedChallenge = req.session.challenge;
133
+ delete req.session.challenge;
134
+ // Strict shape validation before touching storage or SimpleWebAuthn — a malformed finish
135
+ // attempt should fail with a clean, actionable error rather than crashing deep inside the
136
+ // verification library with a confusing low-level error. This alone doesn't reveal whether
137
+ // any particular account/credential exists, so it's kept distinct from the generic failure
138
+ // message below.
139
+ if (!isPasskeyResponse(req.body)) {
140
+ throw new Error("Malformed FIDO2 authentication response.");
141
+ }
142
+ // Every failure from here on is reported with the same generic message, regardless of
143
+ // whether the credential ID is unknown, the signature failed to verify, the counter
144
+ // regressed, or the resolved user was rejected by verify() — distinguishing between these
145
+ // would let a caller enumerate which credential IDs/accounts are registered on this server.
146
+ const genericFailure = () => new Error("FIDO2 authentication failed.");
147
+ const credential = await this.options.getCredentialById(req.body.id);
148
+ if (!credential) {
149
+ throw genericFailure();
150
+ }
151
+ const result = await verifyPasskeyChallenge(credential, this.options.config, expectedChallenge, req.body);
152
+ if (!result.verified) {
153
+ throw genericFailure();
154
+ }
155
+ // Counter regression check. SimpleWebAuthn deliberately does not enforce counter monotonicity
156
+ // itself — the signal of a cloned authenticator per the WebAuthn spec's guidance is a
157
+ // non-zero counter that fails to exceed the stored value (many FIDO2 authenticators always
158
+ // report a strictly incrementing counter, unlike some multi-device passkeys which report 0).
159
+ const newCounter = result.authenticationInfo.newCounter;
160
+ if (newCounter !== 0 && newCounter <= credential.counter) {
161
+ throw genericFailure();
162
+ }
163
+ await this.options.updateCredentialCounter(credential.id, newCounter);
164
+ const user = await this.options.getUser(credential.uid);
165
+ if (user) {
166
+ return {
167
+ data: req.body,
168
+ method: this.name,
169
+ payload: result.authenticationInfo,
170
+ user,
171
+ };
172
+ }
173
+ if (required) {
174
+ throw genericFailure();
175
+ }
176
+ return undefined;
177
+ }
178
+ }
179
+ //# sourceMappingURL=FIDO2Strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FIDO2Strategy.js","sourceRoot":"","sources":["../../../src/auth/FIDO2Strategy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,219 @@
1
+ import { generateOTP, generatePasskeyChallenge, getBasicData, getRequestData, isOTPResponse, isPasskeyResponse, verifyOTP, } from "./shared.js";
2
+ /**
3
+ * The different types of secondary authentication methods.
4
+ */
5
+ export var MFAMethodType;
6
+ (function (MFAMethodType) {
7
+ MFAMethodType["FIDO2"] = "fido2";
8
+ MFAMethodType["OTP"] = "otp";
9
+ MFAMethodType["TOTP"] = "totp";
10
+ })(MFAMethodType || (MFAMethodType = {}));
11
+ /**
12
+ * Describes the configuration options that can be used to initialize MFAStrategy.
13
+ *
14
+ * @author Jean-Philippe Steinmetz
15
+ */
16
+ export class MFAStrategyOptions {
17
+ constructor() {
18
+ /** The name of the header to look for when performing header based authentication. Default value is `Authorization`. */
19
+ this.headerKey = "authorization";
20
+ /** The authorization scheme type when using header based authentication. Default value is `basic`. */
21
+ this.headerScheme = "basic";
22
+ /**
23
+ * Set to `true` to require that user's must provide secondary authentication to succeed, otherwise set to
24
+ * `false`. Default is `true`.
25
+ */
26
+ this.require2FA = true;
27
+ }
28
+ /**
29
+ * Retrieves the user's secondary authentication method for a given id.
30
+ * NOTE: You must override this function when using this strategy.
31
+ * @param id The unique id of the secondary auth method to retrieve.
32
+ */
33
+ getMethod(id) {
34
+ throw new Error("Did you forget to override MFAStrategyOptions.getContact?");
35
+ }
36
+ /**
37
+ * Retrieves the list of secondary authentication methods for the user with the given id.
38
+ * NOTE: You must override this function when using this strategy.
39
+ * @param id The unique id of the user.
40
+ */
41
+ getMethods(id) {
42
+ throw new Error("Did you forget to override MFAStrategyOptions.getContacts?");
43
+ }
44
+ /**
45
+ * Retrieves the user data for the given unique identifier after authentication has completed successfully.
46
+ * NOTE: You must override this function when using this strategy.
47
+ * @param id The unique identifier of the user that has been successfully authenticated.
48
+ */
49
+ getUser(uid) {
50
+ throw new Error("Did you forget to override MFAStrategyOptions.getUsers?");
51
+ }
52
+ /**
53
+ * Sends a notification to the specified contact with the provided MFA code.
54
+ * NOTE: You must override this function when using this strategy.
55
+ * @param contact The contact to send the MFA code to.
56
+ * @param totp The MFA code to send to the user.
57
+ */
58
+ notifyContact(contact, totp) {
59
+ throw new Error("Did you forget to override MFAStrategyOptions.notify?");
60
+ }
61
+ /**
62
+ * Called to verify the provided user's id and password.
63
+ * NOTE: You must override this function when using this strategy.
64
+ * @param id The unique id of the user whose password must be verified.
65
+ * @param password The password of the user to verify.
66
+ * @returns The successfully verified user data, otherwise `undefined`.
67
+ */
68
+ verify(id, password) {
69
+ throw new Error("Did you forget to override MFAStrategyOptions.verify?");
70
+ }
71
+ }
72
+ /**
73
+ * Implements a multi-factory authentication (MFA) strategy that performs basic id and password verification, followed
74
+ * by a secondary authentication. This strategy requires an existing user account to have already registered a valid
75
+ * password that will be validated as well as at least one secondary authentication method.
76
+ *
77
+ * Supported secondary authentication (2FA) methods:
78
+ *
79
+ * * `FIDO2` - A hardware-based key challenge exchange that the user has access to (e.g. Yubikey, Passkey, etc).
80
+ * * `OTP` - A One Time Password (OTP) sent to one of the user's verified contacts.
81
+ * * `TOTP` - A Time-Based One Time Password (TOTP) configured on a device the user owns or has access to.
82
+ *
83
+ * The login flow has three phases:
84
+ *
85
+ * 1. Verify Basic - The client sends a request with `Authorization` header containing the user's id and password. The
86
+ * server returns with a list of available secondary authentication methods.
87
+ * 2. Challenge - The client requests a selected 2FA challenge. If a `TOTP` 2FA method is chosen, the request is
88
+ * processed as phase 3. For all others, a challenge is generated and stored in the session. If `OTP` is selected,
89
+ * a notification containing the challenge token is sent to the selected verified contact.
90
+ * 3. Verify - The client submits the completed 2FA challenge. The challenge is verified against the one stored in the
91
+ * session, and the associated user is resolved using the `getUser()` callback.
92
+ *
93
+ * @author Jean-Philippe Steinmetz <rapidrests@gmail.com>
94
+ */
95
+ export class MFAStrategy {
96
+ constructor(options) {
97
+ this.name = "mfa";
98
+ this.options = options;
99
+ }
100
+ async authenticate(req, res, required) {
101
+ const { data, payload } = getRequestData(req);
102
+ if (isOTPResponse(payload)) {
103
+ const user = await this.verifyOTP(payload, req, res);
104
+ if (user) {
105
+ return {
106
+ data,
107
+ method: this.name,
108
+ payload,
109
+ user,
110
+ };
111
+ }
112
+ }
113
+ else if (isPasskeyResponse(payload)) {
114
+ const user = await this.verifyFIDO(payload, req, res);
115
+ if (user) {
116
+ return {
117
+ data,
118
+ method: this.name,
119
+ payload,
120
+ user,
121
+ };
122
+ }
123
+ }
124
+ else if (payload.id && payload.password) {
125
+ const user = await this.verifyBasic(req, res);
126
+ if (user) {
127
+ return {
128
+ data,
129
+ method: this.name,
130
+ payload,
131
+ user,
132
+ };
133
+ }
134
+ }
135
+ else if (payload.id && payload.methodId) {
136
+ await this.challenge(payload, req, res);
137
+ return undefined;
138
+ }
139
+ if (required) {
140
+ throw new Error("Invalid authentication request.");
141
+ }
142
+ return undefined;
143
+ }
144
+ authenticateSync(req, res, required) {
145
+ throw new Error("Not supported. This auth strategy must be used asynchronously.");
146
+ }
147
+ async challenge(payload, req, res) {
148
+ if (!req.session) {
149
+ throw new Error("MFAStrategy requires session support. Configure the `session` config block so the " +
150
+ "session middleware is registered.");
151
+ }
152
+ const method = await this.options.getMethod(payload.methodId);
153
+ if (!method) {
154
+ throw new Error("Invalid secondary authentication method.");
155
+ }
156
+ // Store the method ID used in the session
157
+ req.session.methodId = method.id;
158
+ switch (method.type) {
159
+ case MFAMethodType.FIDO2:
160
+ {
161
+ if (!this.options.fidoConfig) {
162
+ throw new Error("No configuration exists for MFA method: FIDO2");
163
+ }
164
+ const result = await generatePasskeyChallenge(this.options.fidoConfig, req);
165
+ res.status(200);
166
+ res.json(result);
167
+ }
168
+ break;
169
+ case MFAMethodType.OTP:
170
+ {
171
+ const totp = await generateOTP(req, payload);
172
+ await this.options.notifyContact(method.data, totp);
173
+ res.status(200);
174
+ res.json({});
175
+ }
176
+ break;
177
+ default:
178
+ throw new Error("Unsupported MFA method: " + method.type);
179
+ }
180
+ }
181
+ async verifyBasic(req, res) {
182
+ const requestData = getBasicData(req);
183
+ if (!requestData || !requestData.id || !requestData.password) {
184
+ throw new Error("Invalid user id or password.");
185
+ }
186
+ // Verify the id and password
187
+ const user = await this.options.verify(requestData.id, requestData.password);
188
+ if (user) {
189
+ // Now retrieve the user's list of available 2FA methods
190
+ const methods = await this.options.getMethods(user.uid);
191
+ if (methods.length > 0) {
192
+ // Send the list of 2FA methods back to the client to select from
193
+ res.status(200);
194
+ res.json(methods);
195
+ return undefined;
196
+ }
197
+ else if (this.options.require2FA) {
198
+ throw new Error("No secondary authentication methods available.");
199
+ }
200
+ return user;
201
+ }
202
+ else {
203
+ throw new Error("Invalid user id or password.");
204
+ }
205
+ }
206
+ async verifyFIDO(payload, req, res) {
207
+ throw new Error("Not implemented");
208
+ }
209
+ async verifyOTP(payload, req, res) {
210
+ if (!(await verifyOTP(req, payload))) {
211
+ return undefined;
212
+ }
213
+ return await this.options.getUser(payload.id);
214
+ }
215
+ async verifyTOTP(payload, req, res) {
216
+ throw new Error("Not implemented");
217
+ }
218
+ }
219
+ //# sourceMappingURL=MFAStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MFAStrategy.js","sourceRoot":"","sources":["../../../src/auth/MFAStrategy.ts"],"names":[],"mappings":"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"}