@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
@@ -0,0 +1,397 @@
1
+ ////////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ ////////////////////////////////////////////////////////////////////////////////
4
+ import axios from "axios";
5
+ import * as crypto from "crypto";
6
+ import * as jwt from "jsonwebtoken";
7
+ export const Protocol = {
8
+ OAUTH2: "oauth2",
9
+ OPENID: "openid",
10
+ };
11
+ /**
12
+ * Describes the configuration options that can be used to initialize OIDCStrategy.
13
+ *
14
+ * @author Jean-Philippe Steinmetz
15
+ */
16
+ export class OIDCStrategyOptions {
17
+ /**
18
+ * Retrieves the user data for the given unique identifier after authentication has completed successfully.
19
+ * NOTE: You must override this function when using this strategy.
20
+ * @param token The user's authenticated access token to the third-party provider.
21
+ * @param profile The user's profile data associated with the third-party provider.
22
+ */
23
+ getUser(token, profile) {
24
+ throw new Error("Did you forget to override OIDCStrategyOptions.getUser?");
25
+ }
26
+ constructor(name, provider) {
27
+ /** The unique name of the auth strategy instance. */
28
+ this.name = "oauth";
29
+ this.name = name;
30
+ this.provider = provider;
31
+ }
32
+ }
33
+ export const DEFAULT_PROFILE_MAP = {
34
+ id: "profile.id",
35
+ name: "profile.username",
36
+ email: "profile.email",
37
+ email_verified: "profile.verified && profile.email",
38
+ givenName: "profile.givenName",
39
+ familyName: "profile.familyName",
40
+ birthdate: "profile.birthdate",
41
+ locale: "profile.locale",
42
+ phone: "profile.phone",
43
+ phone_verified: "profile.verified && profile.phone",
44
+ avatar: "profile.avatar",
45
+ };
46
+ /** Matches a valid RFC 7636 code_verifier: 43-128 chars from the unreserved character set. */
47
+ const PKCE_VERIFIER_PATTERN = /^[A-Za-z0-9\-._~]{43,128}$/;
48
+ /**
49
+ * Implements an authentication strategy for performing OIDC 2.0 or OpenID Connect (OIDC) authorization with a
50
+ * third-party provider. This strategy does not require that require an account already be registered. When using
51
+ * this strategy, ensure to allow for account creation to occur after a successful login.
52
+ *
53
+ * The authorization flow has the following steps:
54
+ *
55
+ * 1. Client initiates a request for an Authorization Request URI. Builds the Authorization Request URI and returns it
56
+ * to the client.
57
+ * 2. The client should automatically redirect the user to the Authorization Request URI obtained in step 1.
58
+ * 3. Once the user has approved the authorization request with the third-party provider the client will be
59
+ * redirected to the application's registered callback URL with a single query parameter containing an
60
+ * Authorization Code. The client initiates a request to a route handler, containing the code, and then
61
+ * calls `authenticate()`.
62
+ *
63
+ * NOTE: Requires session support!
64
+ *
65
+ * @author Jean-Philippe Steinmetz <rapidrests@gmail.com>
66
+ */
67
+ export class OIDCStrategy {
68
+ constructor(options) {
69
+ this.name = "oauth";
70
+ /** Cached `jwks-rsa` clients, keyed by JWKS URI, so keys are cached across requests. */
71
+ this.jwksClients = new Map();
72
+ this.name = options.name ?? this.name;
73
+ this.options = options;
74
+ }
75
+ /**
76
+ * Resolves the effective PKCE code challenge method to use, given what the provider requires
77
+ * (if anything specific) and what the client requested. Throws if the client requests an
78
+ * invalid value, or a value that conflicts with a provider-mandated method.
79
+ *
80
+ * @param clientMethod The `code_challenge_method` requested by the client, if any.
81
+ */
82
+ resolvePkceMethod(clientMethod) {
83
+ const pkce = this.options.provider.pkce;
84
+ const required = pkce === "S256" || pkce === "plain" ? pkce : undefined;
85
+ if (clientMethod) {
86
+ if (clientMethod !== "S256" && clientMethod !== "plain") {
87
+ throw new Error(`Invalid code_challenge_method '${clientMethod}'. Must be "S256" or "plain".`);
88
+ }
89
+ if (required && clientMethod !== required) {
90
+ throw new Error(`This provider requires PKCE method '${required}', but '${clientMethod}' was requested.`);
91
+ }
92
+ return clientMethod;
93
+ }
94
+ return required ?? "S256";
95
+ }
96
+ /**
97
+ * Creates and returns the Authorization Request URI for the configured OIDC provider.
98
+ *
99
+ * @param req The source HTTP request.
100
+ * @param redirectURI The source URI to redirect the user to once authentication is complete.
101
+ */
102
+ buildAuthorizationURI(req, redirectURI) {
103
+ if (!req.session) {
104
+ throw new Error("OIDCStrategy requires session support. Configure the `session` config block so the " +
105
+ "session middleware is registered.");
106
+ }
107
+ const provider = this.options.provider;
108
+ // Only a redirect_uri that exactly matches one of the provider's configured/allowed values
109
+ // is honored — this lets a downstream frontend dynamically choose its own callback target
110
+ // (different environments, subdomains, multi-tenant deployments) without opening the door
111
+ // to an attacker supplying an arbitrary redirect_uri.
112
+ const allowedRedirectURIs = Array.isArray(provider.redirectURI)
113
+ ? provider.redirectURI
114
+ : [provider.redirectURI];
115
+ const requestedRedirectURI = redirectURI ?? req.query?.redirect_uri;
116
+ if (requestedRedirectURI && !allowedRedirectURIs.includes(requestedRedirectURI)) {
117
+ throw new Error(`redirect_uri '${requestedRedirectURI}' is not in the list of allowed redirect URIs configured ` +
118
+ "for this provider.");
119
+ }
120
+ const finalRedirectURI = requestedRedirectURI ?? allowedRedirectURIs[0];
121
+ req.session.redirect_uri = finalRedirectURI;
122
+ let url = provider.authorizationURL + `?client_id=${encodeURIComponent(provider.clientID)}`;
123
+ // Combined state: a server-generated CSRF token, plus optional client-supplied
124
+ // app-correlation data. The session cookie is httpOnly (invisible to browser JS), so this
125
+ // is the only channel a frontend has to round-trip its own data through the provider
126
+ // redirect. Only the CSRF-token half is ever validated server-side; the app-data half is
127
+ // handed back to the caller untouched via AuthResult.state.
128
+ const csrfToken = crypto.randomBytes(24).toString("base64url");
129
+ const clientAppData = req.query?.state ?? "";
130
+ req.session.state = csrfToken;
131
+ url += `&state=${encodeURIComponent(`${csrfToken}.${clientAppData}`)}`;
132
+ if (provider.pkce) {
133
+ const method = this.resolvePkceMethod(req.query.code_challenge_method);
134
+ const clientVerifier = req.query.code_verifier;
135
+ if (clientVerifier && !PKCE_VERIFIER_PATTERN.test(clientVerifier)) {
136
+ throw new Error("code_verifier does not meet RFC 7636 requirements (43-128 chars, unreserved charset).");
137
+ }
138
+ const verifier = clientVerifier ?? crypto.randomBytes(32).toString("base64url");
139
+ const codeChallenge = req.query.code_challenge ||
140
+ (method === "plain"
141
+ ? verifier
142
+ : crypto
143
+ .createHash("sha256")
144
+ .update(Buffer.from(verifier, "ascii"))
145
+ .digest("base64")
146
+ .replace(/=/g, "")
147
+ .replace(/\+/g, "-")
148
+ .replace(/\//g, "_"));
149
+ url += `&code_challenge=${encodeURIComponent(codeChallenge)}&code_challenge_method=${encodeURIComponent(method)}`;
150
+ // Store all pkce info in the session for reference later.
151
+ req.session.code_challenge = codeChallenge;
152
+ req.session.code_challenge_method = method;
153
+ req.session.code_verifier = verifier;
154
+ }
155
+ if (provider.protocol === Protocol.OPENID) {
156
+ const nonce = crypto.randomBytes(24).toString("base64url");
157
+ req.session.nonce = nonce;
158
+ url += `&nonce=${encodeURIComponent(nonce)}`;
159
+ }
160
+ url += `&redirect_uri=${encodeURIComponent(finalRedirectURI)}`;
161
+ url += `&response_type=code`;
162
+ url += `&scope=${encodeURIComponent(req.query.scope || (provider.scope ?? []).join(" "))}`;
163
+ return url;
164
+ }
165
+ async authenticate(req, res, required) {
166
+ const error = req.query?.error ?? req.body?.error;
167
+ if (error) {
168
+ const description = req.query?.error_description ?? req.body?.error_description;
169
+ throw new Error(`OIDC provider returned an error: ${error}${description ? ` - ${description}` : ""}`);
170
+ }
171
+ // Is this an authorization request or token exchange?
172
+ if (req.body?.code || req.query?.code) {
173
+ if (!req.session) {
174
+ throw new Error("OIDCStrategy requires session support. Configure the `session` config block so the " +
175
+ "session middleware is registered.");
176
+ }
177
+ const incomingState = (req.body?.state ?? req.query?.state ?? "");
178
+ const separatorIdx = incomingState.indexOf(".");
179
+ const csrfPortion = separatorIdx >= 0 ? incomingState.slice(0, separatorIdx) : incomingState;
180
+ const appData = separatorIdx >= 0 ? incomingState.slice(separatorIdx + 1) : undefined;
181
+ if (!csrfPortion || csrfPortion !== req.session.state) {
182
+ throw new Error("Invalid or missing state parameter. Possible CSRF attempt.");
183
+ }
184
+ delete req.session.state;
185
+ // 1. First exchange the auth code for the access token with the provider
186
+ const accessToken = await this.exchangeOIDCCode(req);
187
+ // 2. Retrieve the user profile.
188
+ const profile = await this.retrieveUserProfile(accessToken, req);
189
+ // Consumed — clear so a stolen/replayed session cookie can't be used against a second flow.
190
+ delete req.session.code_verifier;
191
+ delete req.session.code_challenge;
192
+ delete req.session.code_challenge_method;
193
+ delete req.session.nonce;
194
+ delete req.session.redirect_uri;
195
+ // 3. Notify the verify callback to create a user object for this profile.
196
+ const user = await this.options.getUser(accessToken, profile);
197
+ return {
198
+ data: accessToken,
199
+ method: this.name,
200
+ payload: accessToken,
201
+ state: appData,
202
+ user,
203
+ };
204
+ }
205
+ else {
206
+ const url = this.buildAuthorizationURI(req);
207
+ res.status(302);
208
+ res.setHeader("Location", url);
209
+ res.setHeader("Content-Length", 0);
210
+ res.end();
211
+ return undefined;
212
+ }
213
+ }
214
+ authenticateSync(req, res, required) {
215
+ throw new Error("Not supported. This auth strategy must be used asynchronously.");
216
+ }
217
+ /**
218
+ * Performs a request against the OIDC provider to exchange the given authorization code for an access token.
219
+ *
220
+ * @param req The request data to use in the exchange exchange.
221
+ * @param provider The OIDC provider to perform the exchange with.
222
+ */
223
+ async exchangeOIDCCode(req) {
224
+ const provider = this.options.provider;
225
+ // The code may come from either request query for body
226
+ const code = req.body?.code || req.query?.code;
227
+ if (!code) {
228
+ throw new Error("Authorization code is missing!");
229
+ }
230
+ const allowedRedirectURIs = Array.isArray(provider.redirectURI)
231
+ ? provider.redirectURI
232
+ : [provider.redirectURI];
233
+ // Create a url-encoded payload for the token exchange per RFC-6749 Section 4.1.3
234
+ let data = {
235
+ code,
236
+ grant_type: "authorization_code",
237
+ // Must exactly match what was sent during the authorize step — reuse the value that was
238
+ // validated and stored in the session then, rather than re-trusting client input here.
239
+ redirect_uri: req.session?.redirect_uri ?? allowedRedirectURIs[0],
240
+ };
241
+ // If PKCE is required for this provider make sure to include the challenge. The request body may have already
242
+ // included the challenge details. If not, we'll use what we have from the stored session.
243
+ if (provider.pkce) {
244
+ data.code_verifier = req.body?.code_verifier ?? req.query?.code_verifier ?? req.session?.code_verifier;
245
+ }
246
+ const headers = { "Content-Type": "application/x-www-form-urlencoded" };
247
+ if (provider.clientSecret) {
248
+ headers.Authorization = `Basic ${Buffer.from(provider.clientID + ":" + provider.clientSecret).toString("base64")}`;
249
+ }
250
+ else {
251
+ // Public/PKCE-only clients have no secret to authenticate with via Basic auth — per
252
+ // RFC 6749 §2.3.1 they must instead identify themselves via a body parameter.
253
+ data.client_id = provider.clientID;
254
+ }
255
+ try {
256
+ const result = await axios.post(provider.tokenURL, data, { headers });
257
+ if (result && (result.status !== 200 || !result.data)) {
258
+ throw new Error("Failed to retrieve access token.");
259
+ }
260
+ const json = typeof result.data === "string" ? JSON.parse(result.data) : result.data;
261
+ return json;
262
+ }
263
+ catch (err) {
264
+ const error = err.response?.data || new Error(`Failed to exchange auth token. ${err.message || ""}`);
265
+ error.status = err.status;
266
+ throw error;
267
+ }
268
+ }
269
+ /**
270
+ * Converts the given user profile obtained from the specified OAuth provider into an OIDCProfile object.
271
+ *
272
+ * @param profile The profile to convert.
273
+ * @param provider The OAuth provider that the profile was obtained from.
274
+ */
275
+ convertProfile(profile) {
276
+ const result = {
277
+ provider: this.options.provider.name,
278
+ _json: profile,
279
+ };
280
+ const transform = (profile, map, result) => {
281
+ for (const key in map) {
282
+ try {
283
+ if (typeof map[key] === "object") {
284
+ result[key] = transform(profile, map[key], Array.isArray(map[key]) ? [] : {});
285
+ }
286
+ else {
287
+ // eslint-disable-next-line no-eval
288
+ result[key] = eval(map[key]);
289
+ }
290
+ }
291
+ catch (err) {
292
+ throw new Error(`Failed on transforming ${key} using mapped value ${map[key]}`);
293
+ }
294
+ }
295
+ return result;
296
+ };
297
+ const map = Object.assign({}, DEFAULT_PROFILE_MAP, this.options.provider.profileMap);
298
+ return transform(profile, map, result);
299
+ }
300
+ /**
301
+ * Retrieves the user profile from the specified OIDC provider using the given access token.
302
+ * @param token The access token to use to retrieve the user profile.
303
+ * @param req The source HTTP request, used to validate the id_token's nonce claim.
304
+ */
305
+ async retrieveUserProfile(token, req) {
306
+ // Was an id_token provided? If so, we can verify and decode it to retrieve the profile
307
+ // without having to contact the OIDC provider again — some OpenID providers only expose
308
+ // profile data this way and have no separate profile endpoint at all.
309
+ if (token.id_token && this.options.provider.protocol === Protocol.OPENID) {
310
+ const payload = await this.verifyIdToken(token.id_token, req);
311
+ return this.convertProfile(payload);
312
+ }
313
+ // If no profileURL has been set just immediately return.
314
+ if (!this.options.provider.profileURL) {
315
+ return undefined;
316
+ }
317
+ try {
318
+ const request = await axios.get(this.options.provider.profileURL, {
319
+ headers: {
320
+ Accept: "application/json",
321
+ Authorization: `${token.token_type} ${token.access_token}`,
322
+ },
323
+ });
324
+ if (request && (request.status !== 200 || !request.data)) {
325
+ throw new Error("Failed to retrieve user profile.");
326
+ }
327
+ const json = typeof request.data === "string" ? JSON.parse(request.data) : request.data;
328
+ return this.convertProfile(json);
329
+ }
330
+ catch (err) {
331
+ const error = err.response?.data || new Error(`Failed to retrieve user profile. ${err.message || ""}`);
332
+ error.status = err.status;
333
+ throw error;
334
+ }
335
+ }
336
+ /**
337
+ * Dynamically imports the given optional peer dependency, throwing a helpful error if it is not installed.
338
+ *
339
+ * @param pkg The name of the package to import.
340
+ */
341
+ async importOptionalDependency(pkg) {
342
+ try {
343
+ return await import(pkg);
344
+ }
345
+ catch (err) {
346
+ throw new Error(`OIDC provider '${this.options.provider.name}' uses OpenID with an id_token, which requires ` +
347
+ `the optional peer dependency '${pkg}' for signature verification. Install it with: yarn add ${pkg}`);
348
+ }
349
+ }
350
+ /**
351
+ * Returns a (cached) `jwks-rsa` client for the given JWKS URI.
352
+ * @param jwksURI The JWKS endpoint to fetch signing keys from.
353
+ */
354
+ async getJwksClient(jwksURI) {
355
+ let client = this.jwksClients.get(jwksURI);
356
+ if (!client) {
357
+ const mod = await this.importOptionalDependency("jwks-rsa");
358
+ /* v8 ignore next -- `jwks-rsa`'s CJS export always gets a synthetic `.default` under
359
+ Node's ESM dynamic-import interop; this only guards atypical bundler/interop setups
360
+ where it doesn't. */
361
+ const buildClient = mod.default ?? mod;
362
+ client = buildClient({ jwksUri: jwksURI });
363
+ this.jwksClients.set(jwksURI, client);
364
+ }
365
+ return client;
366
+ }
367
+ /**
368
+ * Verifies the signature, issuer, audience, and nonce of the given ID token.
369
+ *
370
+ * @param idToken The compact JWT id_token to verify.
371
+ * @param req The source HTTP request, used to validate the nonce against the stored session value.
372
+ */
373
+ async verifyIdToken(idToken, req) {
374
+ const provider = this.options.provider;
375
+ if (!provider.jwksURI || !provider.issuer) {
376
+ throw new Error(`OIDC provider '${provider.name}' has protocol 'openid' but is missing required ` +
377
+ "'jwksURI'/'issuer' configuration for ID token verification.");
378
+ }
379
+ const decoded = jwt.decode(idToken, { complete: true });
380
+ if (!decoded || typeof decoded === "string") {
381
+ throw new Error("Invalid id_token: unable to decode JWT header.");
382
+ }
383
+ const client = await this.getJwksClient(provider.jwksURI);
384
+ const signingKey = await client.getSigningKey(decoded.header.kid);
385
+ const publicKey = signingKey.getPublicKey();
386
+ const payload = jwt.verify(idToken, publicKey, {
387
+ algorithms: ["RS256"],
388
+ issuer: provider.issuer,
389
+ audience: provider.clientID,
390
+ });
391
+ if (!req.session?.nonce || payload.nonce !== req.session.nonce) {
392
+ throw new Error("Invalid or missing nonce in id_token. Possible replay attack.");
393
+ }
394
+ return payload;
395
+ }
396
+ }
397
+ //# sourceMappingURL=OIDCStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OIDCStrategy.js","sourceRoot":"","sources":["../../../src/auth/OIDCStrategy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ import { generateOTP, getRequestData, obfuscateContact, verifyOTP } from "./shared.js";
2
+ /**
3
+ * Describes the configuration options that can be used to initialize OTPStrategy.
4
+ *
5
+ * @author Jean-Philippe Steinmetz
6
+ */
7
+ export class OTPStrategyOptions {
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 `otp`. */
12
+ this.headerScheme = "otp";
13
+ }
14
+ /**
15
+ * Retrieves the user's contact information for a given id.
16
+ * NOTE: You must override this function when using this strategy.
17
+ * @param id The unique id of the contact to retrieve.
18
+ */
19
+ getContact(id) {
20
+ throw new Error("Did you forget to override OTPStrategyOptions.getContact?");
21
+ }
22
+ /**
23
+ * Retrieves the list of available contacts for the user with the given id.
24
+ * NOTE: You must override this function when using this strategy.
25
+ * @param id The unique id of the user.
26
+ */
27
+ getContacts(id) {
28
+ throw new Error("Did you forget to override OTPStrategyOptions.getContacts?");
29
+ }
30
+ /**
31
+ * Retrieves the user data for the given unique identifier after authentication has completed successfully.
32
+ * NOTE: You must override this function when using this strategy.
33
+ * @param id The unique identifier of the user that has been successfully authenticated.
34
+ */
35
+ getUser(uid) {
36
+ throw new Error("Did you forget to override OTPStrategyOptions.getUsers?");
37
+ }
38
+ /**
39
+ * Sends a notification to the specified contact with the provided OTP token.
40
+ * NOTE: You must override this function when using this strategy.
41
+ * @param contact The contact to send the OTP token to.
42
+ * @param token The OTP token to send to the user.
43
+ */
44
+ notifyContact(contact, token) {
45
+ throw new Error("Did you forget to override OTPStrategyOptions.notify?");
46
+ }
47
+ }
48
+ /**
49
+ * Implements an strategy for authenticating a user with a One Time Password (OTP) that is sent to one of the user's
50
+ * verified contact methods. This allows for implementation of a password-less login flow. This strategy requires that
51
+ * an existing user account has already been registered with at least one verified contact method.
52
+ *
53
+ * This strategy should not to be confused with Time-Based One Time Password (TOTP) authentication which uses a
54
+ * pre-shared security key, often stored on the user's device (e.g. authenticator app) or digital vault (e.g. 1Password,
55
+ * Bitwarden, etc.). For TOTP authentication, see `TOTPStrategy`.
56
+ *
57
+ * The login flow has three phases:
58
+ *
59
+ * 1. Discovery - The client requests a list of contacts for a given user id. The `getContacts()` callback is used to
60
+ * return the list of contacts.
61
+ * 2. Challenge - The client requests a OTP token to be sent to the contact with a specified id. The
62
+ * OTP token is generated and sent to the contact using the `notify()` callback and stored in the session.
63
+ * 3. Verify - The client submits the OTP token and contact id. The OTP token is verified against the one stored in the
64
+ * session, and the associated user is resolved using the `getUser()` callback.
65
+ *
66
+ * The client sends request data either in the `Authorization` header or the request body in standard form-data format
67
+ * (e.g. `id=<id>&token=<otp>`). For example, the initial challenge request (step 2) can be sent as
68
+ * `Authorization: otp id=<contact_id>`. The final verification request (step 3) is then sent as
69
+ * `Authorization: otp id=<contact_id>&token=<otp>`.
70
+ *
71
+ * WARNING: Allowing discovery (setting `OTPStrategyOptions.allowDiscovery` to `true`) is a potential side-channel
72
+ * attack allowing anyone to discover or gather partial contact information about a given user. For improved security,
73
+ * the returned list of contacts is partially obfuscated. However, enough information is still provided that an
74
+ * attacker may be able to harvest information anyway. For example, a phone number will be obfuscated from
75
+ * `818-867-5309` to `****5309`. An email address will be obfuscated from `john.smith@gmail.com` to
76
+ * `j****th@gmail.com`.
77
+ *
78
+ * @author Jean-Philippe Steinmetz <rapidrests@gmail.com>
79
+ */
80
+ export class OTPStrategy {
81
+ constructor(options) {
82
+ this.name = "otp";
83
+ this.options = options;
84
+ this.regexHeaderScheme = new RegExp("^" + this.options.headerScheme + "$", "i");
85
+ }
86
+ async authenticate(req, res, required) {
87
+ const { data, payload } = getRequestData(req, this.options.headerKey, this.options.headerScheme);
88
+ if (payload.id && payload.token) {
89
+ const user = await this.verify(payload, req, res);
90
+ if (user) {
91
+ return {
92
+ data,
93
+ method: this.name,
94
+ payload,
95
+ user,
96
+ };
97
+ }
98
+ }
99
+ else if (payload.id) {
100
+ return await this.challenge(payload, req, res);
101
+ }
102
+ else if (this.options.allowDiscovery) {
103
+ return await this.discovery(req, res);
104
+ }
105
+ if (required) {
106
+ throw new Error("Invalid authentication request.");
107
+ }
108
+ return undefined;
109
+ }
110
+ authenticateSync(req, res, required) {
111
+ throw new Error("Not supported. This auth strategy must be used asynchronously.");
112
+ }
113
+ async discovery(req, res) {
114
+ const id = req.query?.id ?? undefined;
115
+ let contacts = (await this.options.getContacts(id)) ?? [];
116
+ contacts = contacts.map((c) => ({ contact: obfuscateContact(c.contact, c.type), type: c.type }));
117
+ res.status(200);
118
+ res.json(contacts);
119
+ return undefined;
120
+ }
121
+ async challenge(payload, req, res) {
122
+ if (!req.session) {
123
+ throw new Error("OTPStrategy requires session support. Configure the `session` config " +
124
+ "block so the session middleware is registered.");
125
+ }
126
+ const contact = await this.options.getContact(payload.id);
127
+ if (!contact) {
128
+ return undefined;
129
+ }
130
+ const token = await generateOTP(req, payload);
131
+ await this.options.notifyContact(contact, token);
132
+ res.status(200);
133
+ res.json({});
134
+ return undefined;
135
+ }
136
+ async verify(payload, req, res) {
137
+ if (!(await verifyOTP(req, payload))) {
138
+ return undefined;
139
+ }
140
+ return this.options.getUser(payload.id);
141
+ }
142
+ }
143
+ //# sourceMappingURL=OTPStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OTPStrategy.js","sourceRoot":"","sources":["../../../src/auth/OTPStrategy.ts"],"names":[],"mappings":"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"}