@redmix/auth-dbauth-api 0.0.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.
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/dist/DbAuthHandler.d.ts +373 -0
- package/dist/DbAuthHandler.d.ts.map +1 -0
- package/dist/DbAuthHandler.js +934 -0
- package/dist/decoder.d.ts +5 -0
- package/dist/decoder.d.ts.map +1 -0
- package/dist/decoder.js +46 -0
- package/dist/errors.d.ts +94 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +285 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/shared.d.ts +46 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +268 -0
- package/package.json +82 -0
@@ -0,0 +1,934 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __create = Object.create;
|
3
|
+
var __defProp = Object.defineProperty;
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
8
|
+
var __export = (target, all) => {
|
9
|
+
for (var name in all)
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
11
|
+
};
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
14
|
+
for (let key of __getOwnPropNames(from))
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
17
|
+
}
|
18
|
+
return to;
|
19
|
+
};
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
26
|
+
mod
|
27
|
+
));
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
29
|
+
var DbAuthHandler_exports = {};
|
30
|
+
__export(DbAuthHandler_exports, {
|
31
|
+
DbAuthHandler: () => DbAuthHandler
|
32
|
+
});
|
33
|
+
module.exports = __toCommonJS(DbAuthHandler_exports);
|
34
|
+
var import_base64url = __toESM(require("base64url"));
|
35
|
+
var import_md5 = __toESM(require("md5"));
|
36
|
+
var import_uuid = require("uuid");
|
37
|
+
var import_api = require("@redmix/api");
|
38
|
+
var DbAuthError = __toESM(require("./errors"));
|
39
|
+
var import_shared = require("./shared");
|
40
|
+
const DEFAULT_ALLOWED_USER_FIELDS = ["id", "email"];
|
41
|
+
class DbAuthHandler {
|
42
|
+
event;
|
43
|
+
_normalizedRequest;
|
44
|
+
httpMethod;
|
45
|
+
options;
|
46
|
+
cookie;
|
47
|
+
db;
|
48
|
+
dbAccessor;
|
49
|
+
dbCredentialAccessor;
|
50
|
+
allowedUserFields;
|
51
|
+
hasInvalidSession;
|
52
|
+
session;
|
53
|
+
sessionCsrfToken;
|
54
|
+
corsContext;
|
55
|
+
sessionExpiresDate;
|
56
|
+
webAuthnExpiresDate;
|
57
|
+
encryptedSession = null;
|
58
|
+
createResponse;
|
59
|
+
get normalizedRequest() {
|
60
|
+
if (!this._normalizedRequest) {
|
61
|
+
throw new Error(
|
62
|
+
"dbAuthHandler has not been initialized. Either await dbAuthHandler.invoke() or call await dbAuth.init()"
|
63
|
+
);
|
64
|
+
}
|
65
|
+
return this._normalizedRequest;
|
66
|
+
}
|
67
|
+
// class constant: list of auth methods that are supported
|
68
|
+
static get METHODS() {
|
69
|
+
return [
|
70
|
+
"forgotPassword",
|
71
|
+
"getToken",
|
72
|
+
"login",
|
73
|
+
"logout",
|
74
|
+
"resetPassword",
|
75
|
+
"signup",
|
76
|
+
"validateResetToken",
|
77
|
+
"webAuthnRegOptions",
|
78
|
+
"webAuthnRegister",
|
79
|
+
"webAuthnAuthOptions",
|
80
|
+
"webAuthnAuthenticate"
|
81
|
+
];
|
82
|
+
}
|
83
|
+
// class constant: maps the auth functions to their required HTTP verb for access
|
84
|
+
static get VERBS() {
|
85
|
+
return {
|
86
|
+
forgotPassword: "POST",
|
87
|
+
getToken: "GET",
|
88
|
+
login: "POST",
|
89
|
+
logout: "POST",
|
90
|
+
resetPassword: "POST",
|
91
|
+
signup: "POST",
|
92
|
+
validateResetToken: "POST",
|
93
|
+
webAuthnRegOptions: "GET",
|
94
|
+
webAuthnRegister: "POST",
|
95
|
+
webAuthnAuthOptions: "GET",
|
96
|
+
webAuthnAuthenticate: "POST"
|
97
|
+
};
|
98
|
+
}
|
99
|
+
// default to epoch when we want to expire
|
100
|
+
static get PAST_EXPIRES_DATE() {
|
101
|
+
return (/* @__PURE__ */ new Date("1970-01-01T00:00:00.000+00:00")).toUTCString();
|
102
|
+
}
|
103
|
+
// generate a new token (standard UUID)
|
104
|
+
static get CSRF_TOKEN() {
|
105
|
+
return (0, import_uuid.v4)();
|
106
|
+
}
|
107
|
+
static get AVAILABLE_WEBAUTHN_TRANSPORTS() {
|
108
|
+
return ["usb", "ble", "nfc", "internal"];
|
109
|
+
}
|
110
|
+
/**
|
111
|
+
* Returns the set-cookie header to mark the cookie as expired ("deletes" the session)
|
112
|
+
*
|
113
|
+
* The header keys are case insensitive, but Fastify prefers these to be lowercase.
|
114
|
+
* Therefore, we want to ensure that the headers are always lowercase and unique
|
115
|
+
* for compliance with HTTP/2.
|
116
|
+
*
|
117
|
+
* @see: https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2
|
118
|
+
*/
|
119
|
+
get _deleteSessionHeader() {
|
120
|
+
const deleteHeaders = new Headers();
|
121
|
+
deleteHeaders.append(
|
122
|
+
"set-cookie",
|
123
|
+
[
|
124
|
+
`${(0, import_shared.cookieName)(this.options.cookie?.name)}=`,
|
125
|
+
...this._cookieAttributes({ expires: "now" })
|
126
|
+
].join(";")
|
127
|
+
);
|
128
|
+
deleteHeaders.append(
|
129
|
+
"set-cookie",
|
130
|
+
[`auth-provider=`, ...this._cookieAttributes({ expires: "now" })].join(
|
131
|
+
";"
|
132
|
+
)
|
133
|
+
);
|
134
|
+
return deleteHeaders;
|
135
|
+
}
|
136
|
+
constructor(event, _context, options) {
|
137
|
+
this.options = options;
|
138
|
+
this.event = event;
|
139
|
+
this.httpMethod = (0, import_api.isFetchApiRequest)(event) ? event.method : event.httpMethod;
|
140
|
+
this.cookie = (0, import_shared.extractCookie)(event) || "";
|
141
|
+
this.createResponse = (0, import_shared.getDbAuthResponseBuilder)(event);
|
142
|
+
this._validateOptions();
|
143
|
+
this.db = this.options.db;
|
144
|
+
this.dbAccessor = this.db[this.options.authModelAccessor];
|
145
|
+
this.dbCredentialAccessor = this.options.credentialModelAccessor ? this.db[this.options.credentialModelAccessor] : null;
|
146
|
+
this.hasInvalidSession = false;
|
147
|
+
this.allowedUserFields = this.options.allowedUserFields || DEFAULT_ALLOWED_USER_FIELDS;
|
148
|
+
const sessionExpiresAt = /* @__PURE__ */ new Date();
|
149
|
+
sessionExpiresAt.setSeconds(
|
150
|
+
sessionExpiresAt.getSeconds() + this.options.login.expires
|
151
|
+
);
|
152
|
+
this.sessionExpiresDate = sessionExpiresAt.toUTCString();
|
153
|
+
const webAuthnExpiresAt = /* @__PURE__ */ new Date();
|
154
|
+
webAuthnExpiresAt.setSeconds(
|
155
|
+
webAuthnExpiresAt.getSeconds() + (this.options?.webAuthn?.expires || 0)
|
156
|
+
);
|
157
|
+
this.webAuthnExpiresDate = webAuthnExpiresAt.toUTCString();
|
158
|
+
if (options.cors) {
|
159
|
+
this.corsContext = (0, import_api.createCorsContext)(options.cors);
|
160
|
+
}
|
161
|
+
try {
|
162
|
+
this.encryptedSession = (0, import_shared.getSession)(this.cookie, this.options.cookie?.name);
|
163
|
+
const [session, csrfToken] = (0, import_shared.decryptSession)(this.encryptedSession);
|
164
|
+
this.session = session;
|
165
|
+
this.sessionCsrfToken = csrfToken;
|
166
|
+
} catch (e) {
|
167
|
+
if (e instanceof DbAuthError.SessionDecryptionError) {
|
168
|
+
this.hasInvalidSession = true;
|
169
|
+
} else {
|
170
|
+
throw e;
|
171
|
+
}
|
172
|
+
}
|
173
|
+
}
|
174
|
+
// Initialize the request object. This is async now, because body in Fetch Request
|
175
|
+
// is parsed async
|
176
|
+
async init() {
|
177
|
+
if (!this._normalizedRequest) {
|
178
|
+
this._normalizedRequest = await (0, import_api.normalizeRequest)(
|
179
|
+
this.event
|
180
|
+
);
|
181
|
+
}
|
182
|
+
}
|
183
|
+
// Actual function that triggers everything else to happen: `login`, `signup`,
|
184
|
+
// etc. is called from here, after some checks to make sure the request is good
|
185
|
+
async invoke() {
|
186
|
+
let corsHeaders = {};
|
187
|
+
await this.init();
|
188
|
+
if (this.corsContext) {
|
189
|
+
corsHeaders = this.corsContext.getRequestHeaders(this.normalizedRequest);
|
190
|
+
if (this.corsContext.shouldHandleCors(this.normalizedRequest)) {
|
191
|
+
return this.createResponse({ body: "", statusCode: 200 }, corsHeaders);
|
192
|
+
}
|
193
|
+
}
|
194
|
+
if (this.hasInvalidSession) {
|
195
|
+
return this.createResponse(
|
196
|
+
this._ok(...this._logoutResponse()),
|
197
|
+
corsHeaders
|
198
|
+
);
|
199
|
+
}
|
200
|
+
try {
|
201
|
+
const method = await this._getAuthMethod();
|
202
|
+
if (!DbAuthHandler.METHODS.includes(method)) {
|
203
|
+
return this.createResponse(this._notFound(), corsHeaders);
|
204
|
+
}
|
205
|
+
if (this.httpMethod !== DbAuthHandler.VERBS[method]) {
|
206
|
+
return this.createResponse(this._notFound(), corsHeaders);
|
207
|
+
}
|
208
|
+
const [body, headers, options = { statusCode: 200 }] = await this[method]();
|
209
|
+
return this.createResponse(this._ok(body, headers, options), corsHeaders);
|
210
|
+
} catch (e) {
|
211
|
+
if (e instanceof DbAuthError.WrongVerbError) {
|
212
|
+
return this.createResponse(this._notFound(), corsHeaders);
|
213
|
+
} else {
|
214
|
+
return this.createResponse(
|
215
|
+
this._badRequest(e.message || e),
|
216
|
+
corsHeaders
|
217
|
+
);
|
218
|
+
}
|
219
|
+
}
|
220
|
+
}
|
221
|
+
async forgotPassword() {
|
222
|
+
const { enabled = true } = this.options.forgotPassword;
|
223
|
+
if (!enabled) {
|
224
|
+
throw new DbAuthError.FlowNotEnabledError(
|
225
|
+
this.options.forgotPassword?.errors?.flowNotEnabled || `Forgot password flow is not enabled`
|
226
|
+
);
|
227
|
+
}
|
228
|
+
const { username } = this.normalizedRequest.jsonBody || {};
|
229
|
+
if (!username || username.trim() === "") {
|
230
|
+
throw new DbAuthError.UsernameRequiredError(
|
231
|
+
this.options.forgotPassword?.errors?.usernameRequired || `Username is required`
|
232
|
+
);
|
233
|
+
}
|
234
|
+
let user;
|
235
|
+
try {
|
236
|
+
user = await this.dbAccessor.findUnique({
|
237
|
+
where: { [this.options.authFields.username]: username }
|
238
|
+
});
|
239
|
+
} catch {
|
240
|
+
throw new DbAuthError.GenericError();
|
241
|
+
}
|
242
|
+
if (user) {
|
243
|
+
const tokenExpires = /* @__PURE__ */ new Date();
|
244
|
+
tokenExpires.setSeconds(
|
245
|
+
tokenExpires.getSeconds() + this.options.forgotPassword.expires
|
246
|
+
);
|
247
|
+
let token = (0, import_md5.default)((0, import_uuid.v4)());
|
248
|
+
const buffer = Buffer.from(token);
|
249
|
+
token = buffer.toString("base64").replace("=", "").substring(0, 16);
|
250
|
+
const tokenHash = (0, import_shared.hashToken)(token);
|
251
|
+
try {
|
252
|
+
user = await this.dbAccessor.update({
|
253
|
+
where: {
|
254
|
+
[this.options.authFields.id]: user[this.options.authFields.id]
|
255
|
+
},
|
256
|
+
data: {
|
257
|
+
[this.options.authFields.resetToken]: tokenHash,
|
258
|
+
[this.options.authFields.resetTokenExpiresAt]: tokenExpires
|
259
|
+
}
|
260
|
+
});
|
261
|
+
} catch {
|
262
|
+
throw new DbAuthError.GenericError();
|
263
|
+
}
|
264
|
+
const response = await this.options.forgotPassword.handler(this._sanitizeUser(user), token);
|
265
|
+
return [
|
266
|
+
response ? JSON.stringify(response) : "",
|
267
|
+
this._deleteSessionHeader
|
268
|
+
];
|
269
|
+
} else {
|
270
|
+
throw new DbAuthError.UsernameNotFoundError(
|
271
|
+
this.options.forgotPassword?.errors?.usernameNotFound || `Username '${username} not found`
|
272
|
+
);
|
273
|
+
}
|
274
|
+
}
|
275
|
+
async getToken() {
|
276
|
+
try {
|
277
|
+
const user = await this._getCurrentUser();
|
278
|
+
let headers = new Headers();
|
279
|
+
if ((0, import_shared.isLegacySession)(this.cookie)) {
|
280
|
+
headers = this._loginResponse(user)[1];
|
281
|
+
}
|
282
|
+
return [user[this.options.authFields.id], headers];
|
283
|
+
} catch (e) {
|
284
|
+
if (e instanceof DbAuthError.NotLoggedInError) {
|
285
|
+
return this._logoutResponse();
|
286
|
+
} else {
|
287
|
+
return this._logoutResponse({ error: e.message });
|
288
|
+
}
|
289
|
+
}
|
290
|
+
}
|
291
|
+
async login() {
|
292
|
+
const { enabled = true } = this.options.login;
|
293
|
+
if (!enabled) {
|
294
|
+
throw new DbAuthError.FlowNotEnabledError(
|
295
|
+
this.options.login?.errors?.flowNotEnabled || `Login flow is not enabled`
|
296
|
+
);
|
297
|
+
}
|
298
|
+
const { username, password } = this.normalizedRequest.jsonBody || {};
|
299
|
+
const dbUser = await this._verifyUser(username, password);
|
300
|
+
const handlerUser = await this.options.login.handler(
|
301
|
+
dbUser
|
302
|
+
);
|
303
|
+
if (handlerUser?.[this.options.authFields.id] == null) {
|
304
|
+
throw new DbAuthError.NoUserIdError();
|
305
|
+
}
|
306
|
+
return this._loginResponse(handlerUser);
|
307
|
+
}
|
308
|
+
logout() {
|
309
|
+
return this._logoutResponse();
|
310
|
+
}
|
311
|
+
async resetPassword() {
|
312
|
+
const { enabled = true } = this.options.resetPassword;
|
313
|
+
if (!enabled) {
|
314
|
+
throw new DbAuthError.FlowNotEnabledError(
|
315
|
+
this.options.resetPassword?.errors?.flowNotEnabled || `Reset password flow is not enabled`
|
316
|
+
);
|
317
|
+
}
|
318
|
+
const { password, resetToken } = this.normalizedRequest.jsonBody || {};
|
319
|
+
if (resetToken == null || String(resetToken).trim() === "") {
|
320
|
+
throw new DbAuthError.ResetTokenRequiredError(
|
321
|
+
this.options.resetPassword?.errors?.resetTokenRequired
|
322
|
+
);
|
323
|
+
}
|
324
|
+
if (password == null || String(password).trim() === "") {
|
325
|
+
throw new DbAuthError.PasswordRequiredError();
|
326
|
+
}
|
327
|
+
;
|
328
|
+
this.options.signup.passwordValidation?.(password);
|
329
|
+
let user = await this._findUserByToken(resetToken);
|
330
|
+
const [hashedPassword] = (0, import_shared.hashPassword)(password, {
|
331
|
+
salt: user.salt
|
332
|
+
});
|
333
|
+
const [legacyHashedPassword] = (0, import_shared.legacyHashPassword)(password, user.salt);
|
334
|
+
if (!this.options.resetPassword.allowReusedPassword && user.hashedPassword === hashedPassword || user.hashedPassword === legacyHashedPassword) {
|
335
|
+
throw new DbAuthError.ReusedPasswordError(
|
336
|
+
this.options.resetPassword?.errors?.reusedPassword
|
337
|
+
);
|
338
|
+
}
|
339
|
+
try {
|
340
|
+
user = await this.dbAccessor.update({
|
341
|
+
where: {
|
342
|
+
[this.options.authFields.id]: user[this.options.authFields.id]
|
343
|
+
},
|
344
|
+
data: {
|
345
|
+
[this.options.authFields.hashedPassword]: hashedPassword
|
346
|
+
}
|
347
|
+
});
|
348
|
+
} catch {
|
349
|
+
throw new DbAuthError.GenericError();
|
350
|
+
}
|
351
|
+
await this._clearResetToken(user);
|
352
|
+
const response = await this.options.resetPassword.handler(this._sanitizeUser(user));
|
353
|
+
if (response) {
|
354
|
+
return this._loginResponse(user);
|
355
|
+
} else {
|
356
|
+
return this._logoutResponse({});
|
357
|
+
}
|
358
|
+
}
|
359
|
+
async signup() {
|
360
|
+
const { enabled = true } = this.options.signup;
|
361
|
+
if (!enabled) {
|
362
|
+
throw new DbAuthError.FlowNotEnabledError(
|
363
|
+
this.options.signup?.errors?.flowNotEnabled || `Signup flow is not enabled`
|
364
|
+
);
|
365
|
+
}
|
366
|
+
const { password } = this.normalizedRequest.jsonBody || {};
|
367
|
+
this.options.signup.passwordValidation?.(
|
368
|
+
password
|
369
|
+
);
|
370
|
+
const userOrMessage = await this._createUser();
|
371
|
+
if (typeof userOrMessage === "object") {
|
372
|
+
const user = userOrMessage;
|
373
|
+
return this._loginResponse(user, 201);
|
374
|
+
} else {
|
375
|
+
const message = userOrMessage;
|
376
|
+
return [JSON.stringify({ message }), new Headers(), { statusCode: 201 }];
|
377
|
+
}
|
378
|
+
}
|
379
|
+
async validateResetToken() {
|
380
|
+
const { resetToken } = this.normalizedRequest.jsonBody || {};
|
381
|
+
if (!resetToken || String(resetToken).trim() === "") {
|
382
|
+
throw new DbAuthError.ResetTokenRequiredError(
|
383
|
+
this.options.resetPassword?.errors?.resetTokenRequired
|
384
|
+
);
|
385
|
+
}
|
386
|
+
const user = await this._findUserByToken(resetToken);
|
387
|
+
return [JSON.stringify(this._sanitizeUser(user)), this._deleteSessionHeader];
|
388
|
+
}
|
389
|
+
// browser submits WebAuthn credentials
|
390
|
+
async webAuthnAuthenticate() {
|
391
|
+
const { verifyAuthenticationResponse } = await import("@simplewebauthn/server");
|
392
|
+
const webAuthnOptions = this.options.webAuthn;
|
393
|
+
const { rawId } = this.normalizedRequest.jsonBody || {};
|
394
|
+
if (!rawId) {
|
395
|
+
throw new DbAuthError.WebAuthnError("Missing Id in request");
|
396
|
+
}
|
397
|
+
if (!webAuthnOptions?.enabled) {
|
398
|
+
throw new DbAuthError.WebAuthnError("WebAuthn is not enabled");
|
399
|
+
}
|
400
|
+
const credential = await this.dbCredentialAccessor.findFirst({
|
401
|
+
where: { id: rawId }
|
402
|
+
});
|
403
|
+
if (!credential) {
|
404
|
+
throw new DbAuthError.WebAuthnError("Credentials not found");
|
405
|
+
}
|
406
|
+
const user = await this.dbAccessor.findFirst({
|
407
|
+
where: {
|
408
|
+
[this.options.authFields.id]: credential[webAuthnOptions.credentialFields.userId]
|
409
|
+
}
|
410
|
+
});
|
411
|
+
let verification;
|
412
|
+
try {
|
413
|
+
const opts = {
|
414
|
+
response: this.normalizedRequest?.jsonBody,
|
415
|
+
// by this point jsonBody has been validated
|
416
|
+
expectedChallenge: user[this.options.authFields.challenge],
|
417
|
+
expectedOrigin: webAuthnOptions.origin,
|
418
|
+
expectedRPID: webAuthnOptions.domain,
|
419
|
+
authenticator: {
|
420
|
+
credentialID: import_base64url.default.toBuffer(
|
421
|
+
credential[webAuthnOptions.credentialFields.id]
|
422
|
+
),
|
423
|
+
credentialPublicKey: credential[webAuthnOptions.credentialFields.publicKey],
|
424
|
+
counter: credential[webAuthnOptions.credentialFields.counter],
|
425
|
+
transports: credential[webAuthnOptions.credentialFields.transports] ? JSON.parse(
|
426
|
+
credential[webAuthnOptions.credentialFields.transports]
|
427
|
+
) : DbAuthHandler.AVAILABLE_WEBAUTHN_TRANSPORTS
|
428
|
+
},
|
429
|
+
requireUserVerification: true
|
430
|
+
};
|
431
|
+
verification = await verifyAuthenticationResponse(opts);
|
432
|
+
} catch (e) {
|
433
|
+
throw new DbAuthError.WebAuthnError(e.message);
|
434
|
+
} finally {
|
435
|
+
await this._saveChallenge(user[this.options.authFields.id], null);
|
436
|
+
}
|
437
|
+
const { verified, authenticationInfo } = verification;
|
438
|
+
if (verified) {
|
439
|
+
await this.dbCredentialAccessor.update({
|
440
|
+
where: {
|
441
|
+
[webAuthnOptions.credentialFields.id]: credential[webAuthnOptions.credentialFields.id]
|
442
|
+
},
|
443
|
+
data: {
|
444
|
+
[webAuthnOptions.credentialFields.counter]: authenticationInfo.newCounter
|
445
|
+
}
|
446
|
+
});
|
447
|
+
}
|
448
|
+
const [, headers] = this._loginResponse(user);
|
449
|
+
headers.append(
|
450
|
+
"set-cookie",
|
451
|
+
this._webAuthnCookie(rawId, this.webAuthnExpiresDate)
|
452
|
+
);
|
453
|
+
return [verified, headers];
|
454
|
+
}
|
455
|
+
// get options for a WebAuthn authentication
|
456
|
+
async webAuthnAuthOptions() {
|
457
|
+
const { generateAuthenticationOptions } = await import("@simplewebauthn/server");
|
458
|
+
if (!this.options.webAuthn?.enabled) {
|
459
|
+
throw new DbAuthError.WebAuthnError("WebAuthn is not enabled");
|
460
|
+
}
|
461
|
+
const webAuthnOptions = this.options.webAuthn;
|
462
|
+
const credentialId = (0, import_shared.webAuthnSession)(this.event);
|
463
|
+
let user;
|
464
|
+
if (credentialId) {
|
465
|
+
const credential = await this.dbCredentialAccessor.findUnique({
|
466
|
+
where: { [webAuthnOptions.credentialFields.id]: credentialId },
|
467
|
+
include: { [this.options.authModelAccessor]: true }
|
468
|
+
});
|
469
|
+
user = credential[this.options.authModelAccessor];
|
470
|
+
} else {
|
471
|
+
user = await this._getCurrentUser();
|
472
|
+
}
|
473
|
+
if (!user) {
|
474
|
+
return [
|
475
|
+
{ error: "Log in with username and password to enable WebAuthn" },
|
476
|
+
new Headers([["set-cookie", this._webAuthnCookie("", "now")]]),
|
477
|
+
{ statusCode: 400 }
|
478
|
+
];
|
479
|
+
}
|
480
|
+
const credentials = await this.dbCredentialAccessor.findMany({
|
481
|
+
where: {
|
482
|
+
[webAuthnOptions.credentialFields.userId]: user[this.options.authFields.id]
|
483
|
+
}
|
484
|
+
});
|
485
|
+
const someOptions = {
|
486
|
+
timeout: webAuthnOptions.timeout || 6e4,
|
487
|
+
allowCredentials: credentials.map((cred) => ({
|
488
|
+
id: import_base64url.default.toBuffer(cred[webAuthnOptions.credentialFields.id]),
|
489
|
+
type: "public-key",
|
490
|
+
transports: cred[webAuthnOptions.credentialFields.transports] ? JSON.parse(cred[webAuthnOptions.credentialFields.transports]) : DbAuthHandler.AVAILABLE_WEBAUTHN_TRANSPORTS
|
491
|
+
})),
|
492
|
+
userVerification: "required",
|
493
|
+
rpID: webAuthnOptions.domain
|
494
|
+
};
|
495
|
+
const authOptions = generateAuthenticationOptions(someOptions);
|
496
|
+
await this._saveChallenge(
|
497
|
+
user[this.options.authFields.id],
|
498
|
+
authOptions.challenge
|
499
|
+
);
|
500
|
+
return [authOptions];
|
501
|
+
}
|
502
|
+
// get options for WebAuthn registration
|
503
|
+
async webAuthnRegOptions() {
|
504
|
+
const { generateRegistrationOptions } = await import("@simplewebauthn/server");
|
505
|
+
if (!this.options?.webAuthn?.enabled) {
|
506
|
+
throw new DbAuthError.WebAuthnError("WebAuthn is not enabled");
|
507
|
+
}
|
508
|
+
const webAuthnOptions = this.options.webAuthn;
|
509
|
+
const user = await this._getCurrentUser();
|
510
|
+
const options = {
|
511
|
+
rpName: webAuthnOptions.name,
|
512
|
+
rpID: webAuthnOptions.domain,
|
513
|
+
userID: user[this.options.authFields.id],
|
514
|
+
userName: user[this.options.authFields.username],
|
515
|
+
timeout: webAuthnOptions?.timeout || 6e4,
|
516
|
+
excludeCredentials: [],
|
517
|
+
authenticatorSelection: {
|
518
|
+
userVerification: "required"
|
519
|
+
},
|
520
|
+
// Support the two most common algorithms: ES256, and RS256
|
521
|
+
supportedAlgorithmIDs: [-7, -257]
|
522
|
+
};
|
523
|
+
if (webAuthnOptions.type && webAuthnOptions.type !== "any") {
|
524
|
+
options.authenticatorSelection = Object.assign(
|
525
|
+
options.authenticatorSelection || {},
|
526
|
+
{ authenticatorAttachment: webAuthnOptions.type }
|
527
|
+
);
|
528
|
+
}
|
529
|
+
const regOptions = generateRegistrationOptions(options);
|
530
|
+
await this._saveChallenge(
|
531
|
+
user[this.options.authFields.id],
|
532
|
+
regOptions.challenge
|
533
|
+
);
|
534
|
+
return [regOptions];
|
535
|
+
}
|
536
|
+
// browser submits WebAuthn credentials for the first time on a new device
|
537
|
+
async webAuthnRegister() {
|
538
|
+
const { verifyRegistrationResponse } = await import("@simplewebauthn/server");
|
539
|
+
if (!this.options.webAuthn?.enabled) {
|
540
|
+
throw new DbAuthError.WebAuthnError("WebAuthn is not enabled");
|
541
|
+
}
|
542
|
+
const user = await this._getCurrentUser();
|
543
|
+
let verification;
|
544
|
+
try {
|
545
|
+
const options = {
|
546
|
+
response: this.normalizedRequest.jsonBody,
|
547
|
+
// by this point jsonBody has been validated
|
548
|
+
expectedChallenge: user[this.options.authFields.challenge],
|
549
|
+
expectedOrigin: this.options.webAuthn.origin,
|
550
|
+
expectedRPID: this.options.webAuthn.domain,
|
551
|
+
requireUserVerification: true
|
552
|
+
};
|
553
|
+
verification = await verifyRegistrationResponse(options);
|
554
|
+
} catch (e) {
|
555
|
+
throw new DbAuthError.WebAuthnError(e.message);
|
556
|
+
}
|
557
|
+
const { verified, registrationInfo } = verification;
|
558
|
+
let plainCredentialId;
|
559
|
+
if (verified && registrationInfo) {
|
560
|
+
const { credentialPublicKey, credentialID, counter } = registrationInfo;
|
561
|
+
plainCredentialId = import_base64url.default.encode(Buffer.from(credentialID));
|
562
|
+
const existingDevice = await this.dbCredentialAccessor.findFirst({
|
563
|
+
where: {
|
564
|
+
[this.options.webAuthn.credentialFields.id]: plainCredentialId,
|
565
|
+
[this.options.webAuthn.credentialFields.userId]: user[this.options.authFields.id]
|
566
|
+
}
|
567
|
+
});
|
568
|
+
if (!existingDevice) {
|
569
|
+
const { transports } = this.normalizedRequest.jsonBody || {};
|
570
|
+
await this.dbCredentialAccessor.create({
|
571
|
+
data: {
|
572
|
+
[this.options.webAuthn.credentialFields.id]: plainCredentialId,
|
573
|
+
[this.options.webAuthn.credentialFields.userId]: user[this.options.authFields.id],
|
574
|
+
[this.options.webAuthn.credentialFields.publicKey]: Buffer.from(credentialPublicKey),
|
575
|
+
[this.options.webAuthn.credentialFields.transports]: transports ? JSON.stringify(transports) : null,
|
576
|
+
[this.options.webAuthn.credentialFields.counter]: counter
|
577
|
+
}
|
578
|
+
});
|
579
|
+
}
|
580
|
+
} else {
|
581
|
+
throw new DbAuthError.WebAuthnError("Registration failed");
|
582
|
+
}
|
583
|
+
await this._saveChallenge(user[this.options.authFields.id], null);
|
584
|
+
const headers = new Headers([
|
585
|
+
[
|
586
|
+
"set-cookie",
|
587
|
+
this._webAuthnCookie(plainCredentialId, this.webAuthnExpiresDate)
|
588
|
+
]
|
589
|
+
]);
|
590
|
+
return [verified, headers];
|
591
|
+
}
|
592
|
+
// validates that we have all the ENV and options we need to login/signup
|
593
|
+
_validateOptions() {
|
594
|
+
if (!process.env.SESSION_SECRET) {
|
595
|
+
throw new DbAuthError.NoSessionSecretError();
|
596
|
+
}
|
597
|
+
if (this.options?.login?.enabled !== false && !this.options?.login?.expires) {
|
598
|
+
throw new DbAuthError.NoSessionExpirationError();
|
599
|
+
}
|
600
|
+
if (this.options?.login?.enabled !== false && !this.options?.login?.handler) {
|
601
|
+
throw new DbAuthError.NoLoginHandlerError();
|
602
|
+
}
|
603
|
+
if (this.options?.signup?.enabled !== false && !this.options?.signup?.handler) {
|
604
|
+
throw new DbAuthError.NoSignupHandlerError();
|
605
|
+
}
|
606
|
+
if (this.options?.forgotPassword?.enabled !== false && !this.options?.forgotPassword?.handler) {
|
607
|
+
throw new DbAuthError.NoForgotPasswordHandlerError();
|
608
|
+
}
|
609
|
+
if (this.options?.resetPassword?.enabled !== false && !this.options?.resetPassword?.handler) {
|
610
|
+
throw new DbAuthError.NoResetPasswordHandlerError();
|
611
|
+
}
|
612
|
+
if (this.options?.credentialModelAccessor && !this.options?.webAuthn || this.options?.webAuthn && !this.options?.credentialModelAccessor) {
|
613
|
+
throw new DbAuthError.NoWebAuthnConfigError();
|
614
|
+
}
|
615
|
+
if (this.options?.webAuthn?.enabled && (!this.options?.webAuthn?.name || !this.options?.webAuthn?.domain || !this.options?.webAuthn?.origin || !this.options?.webAuthn?.credentialFields)) {
|
616
|
+
throw new DbAuthError.MissingWebAuthnConfigError();
|
617
|
+
}
|
618
|
+
}
|
619
|
+
// Save challenge string for WebAuthn
|
620
|
+
async _saveChallenge(userId, value) {
|
621
|
+
await this.dbAccessor.update({
|
622
|
+
where: {
|
623
|
+
[this.options.authFields.id]: userId
|
624
|
+
},
|
625
|
+
data: {
|
626
|
+
[this.options.authFields.challenge]: value
|
627
|
+
}
|
628
|
+
});
|
629
|
+
}
|
630
|
+
// returns the string for the webAuthn set-cookie header
|
631
|
+
_webAuthnCookie(id, expires) {
|
632
|
+
return [
|
633
|
+
`webAuthn=${id}`,
|
634
|
+
...this._cookieAttributes({
|
635
|
+
expires,
|
636
|
+
options: { HttpOnly: false }
|
637
|
+
})
|
638
|
+
].join(";");
|
639
|
+
}
|
640
|
+
// removes any fields not explicitly allowed to be sent to the client before
|
641
|
+
// sending a response over the wire
|
642
|
+
_sanitizeUser(user) {
|
643
|
+
const sanitized = JSON.parse(JSON.stringify(user));
|
644
|
+
Object.keys(sanitized).forEach((key) => {
|
645
|
+
if (!this.allowedUserFields.includes(key)) {
|
646
|
+
delete sanitized[key];
|
647
|
+
}
|
648
|
+
});
|
649
|
+
return sanitized;
|
650
|
+
}
|
651
|
+
// Converts LambdaEvent or FetchRequest to
|
652
|
+
_decodeEvent() {
|
653
|
+
}
|
654
|
+
// returns all the cookie attributes in an array with the proper expiration date
|
655
|
+
//
|
656
|
+
// pass the argument `expires` set to "now" to get the attributes needed to expire
|
657
|
+
// the session, or "future" (or left out completely) to set to `futureExpiresDate`
|
658
|
+
_cookieAttributes({
|
659
|
+
expires = "now",
|
660
|
+
options = {}
|
661
|
+
}) {
|
662
|
+
const userCookieAttributes = this.options.cookie?.attributes ? { ...this.options.cookie?.attributes } : { ...this.options.cookie };
|
663
|
+
if (!this.options.cookie?.attributes) {
|
664
|
+
delete userCookieAttributes.name;
|
665
|
+
}
|
666
|
+
const cookieOptions = { ...userCookieAttributes, ...options };
|
667
|
+
const meta = Object.keys(cookieOptions).map((key) => {
|
668
|
+
const optionValue = cookieOptions[key];
|
669
|
+
if (optionValue === true) {
|
670
|
+
return key;
|
671
|
+
} else if (optionValue === false) {
|
672
|
+
return null;
|
673
|
+
} else {
|
674
|
+
return `${key}=${optionValue}`;
|
675
|
+
}
|
676
|
+
}).filter((v) => v);
|
677
|
+
const expiresAt = expires === "now" ? DbAuthHandler.PAST_EXPIRES_DATE : expires;
|
678
|
+
meta.push(`Expires=${expiresAt}`);
|
679
|
+
return meta;
|
680
|
+
}
|
681
|
+
_createAuthProviderCookieString() {
|
682
|
+
return [
|
683
|
+
`auth-provider=dbAuth`,
|
684
|
+
...this._cookieAttributes({ expires: this.sessionExpiresDate })
|
685
|
+
].join(";");
|
686
|
+
}
|
687
|
+
// returns the set-cookie header to be returned in the request (effectively
|
688
|
+
// creates the session)
|
689
|
+
_createSessionCookieString(data, csrfToken) {
|
690
|
+
const session = JSON.stringify(data) + ";" + csrfToken;
|
691
|
+
const encrypted = (0, import_shared.encryptSession)(session);
|
692
|
+
const sessionCookieString = [
|
693
|
+
`${(0, import_shared.cookieName)(this.options.cookie?.name)}=${encrypted}`,
|
694
|
+
...this._cookieAttributes({ expires: this.sessionExpiresDate })
|
695
|
+
].join(";");
|
696
|
+
return sessionCookieString;
|
697
|
+
}
|
698
|
+
// checks the CSRF token in the header against the CSRF token in the session
|
699
|
+
// and throw an error if they are not the same (not used yet)
|
700
|
+
async _validateCsrf() {
|
701
|
+
if (this.sessionCsrfToken !== this.normalizedRequest.headers.get("csrf-token")) {
|
702
|
+
throw new DbAuthError.CsrfTokenMismatchError();
|
703
|
+
}
|
704
|
+
return true;
|
705
|
+
}
|
706
|
+
async _findUserByToken(token) {
|
707
|
+
const tokenExpires = /* @__PURE__ */ new Date();
|
708
|
+
tokenExpires.setSeconds(
|
709
|
+
tokenExpires.getSeconds() - this.options.forgotPassword.expires
|
710
|
+
);
|
711
|
+
const tokenHash = (0, import_shared.hashToken)(token);
|
712
|
+
const user = await this.dbAccessor.findFirst({
|
713
|
+
where: {
|
714
|
+
[this.options.authFields.resetToken]: tokenHash
|
715
|
+
}
|
716
|
+
});
|
717
|
+
if (!user) {
|
718
|
+
throw new DbAuthError.ResetTokenInvalidError(
|
719
|
+
this.options.resetPassword?.errors?.resetTokenInvalid
|
720
|
+
);
|
721
|
+
}
|
722
|
+
if (user[this.options.authFields.resetTokenExpiresAt] < tokenExpires) {
|
723
|
+
await this._clearResetToken(user);
|
724
|
+
throw new DbAuthError.ResetTokenExpiredError(
|
725
|
+
this.options.resetPassword?.errors?.resetTokenExpired
|
726
|
+
);
|
727
|
+
}
|
728
|
+
return user;
|
729
|
+
}
|
730
|
+
// removes the resetToken from the database
|
731
|
+
async _clearResetToken(user) {
|
732
|
+
try {
|
733
|
+
await this.dbAccessor.update({
|
734
|
+
where: {
|
735
|
+
[this.options.authFields.id]: user[this.options.authFields.id]
|
736
|
+
},
|
737
|
+
data: {
|
738
|
+
[this.options.authFields.resetToken]: null,
|
739
|
+
[this.options.authFields.resetTokenExpiresAt]: null
|
740
|
+
}
|
741
|
+
});
|
742
|
+
} catch {
|
743
|
+
throw new DbAuthError.GenericError();
|
744
|
+
}
|
745
|
+
}
|
746
|
+
// verifies that a username and password are correct, and returns the user if so
|
747
|
+
async _verifyUser(username, password) {
|
748
|
+
if (!username || username.toString().trim() === "" || !password || password.toString().trim() === "") {
|
749
|
+
throw new DbAuthError.UsernameAndPasswordRequiredError(
|
750
|
+
this.options.login?.errors?.usernameOrPasswordMissing
|
751
|
+
);
|
752
|
+
}
|
753
|
+
const usernameMatchFlowOption = this.options.login?.usernameMatch;
|
754
|
+
const findUniqueUserMatchCriteriaOptions = this._getUserMatchCriteriaOptions(username, usernameMatchFlowOption);
|
755
|
+
let user;
|
756
|
+
try {
|
757
|
+
user = await this.dbAccessor.findFirst({
|
758
|
+
where: findUniqueUserMatchCriteriaOptions
|
759
|
+
});
|
760
|
+
} catch {
|
761
|
+
throw new DbAuthError.GenericError();
|
762
|
+
}
|
763
|
+
if (!user) {
|
764
|
+
throw new DbAuthError.UserNotFoundError(
|
765
|
+
username,
|
766
|
+
this.options.login?.errors?.usernameNotFound
|
767
|
+
);
|
768
|
+
}
|
769
|
+
await this._verifyPassword(user, password);
|
770
|
+
return user;
|
771
|
+
}
|
772
|
+
// extracts scrypt strength options from hashed password (if present) and
|
773
|
+
// compares the hashed plain text password just submitted using those options
|
774
|
+
// with the one in the database. Falls back to the legacy CryptoJS algorihtm
|
775
|
+
// if no options are present.
|
776
|
+
async _verifyPassword(user, password) {
|
777
|
+
const options = (0, import_shared.extractHashingOptions)(
|
778
|
+
user[this.options.authFields.hashedPassword]
|
779
|
+
);
|
780
|
+
if (Object.keys(options).length) {
|
781
|
+
const [hashedPassword] = (0, import_shared.hashPassword)(password, {
|
782
|
+
salt: user[this.options.authFields.salt],
|
783
|
+
options
|
784
|
+
});
|
785
|
+
if (hashedPassword === user[this.options.authFields.hashedPassword]) {
|
786
|
+
return user;
|
787
|
+
}
|
788
|
+
} else {
|
789
|
+
const [legacyHashedPassword] = (0, import_shared.legacyHashPassword)(
|
790
|
+
password,
|
791
|
+
user[this.options.authFields.salt]
|
792
|
+
);
|
793
|
+
if (legacyHashedPassword === user[this.options.authFields.hashedPassword]) {
|
794
|
+
const [newHashedPassword] = (0, import_shared.hashPassword)(password, {
|
795
|
+
salt: user[this.options.authFields.salt]
|
796
|
+
});
|
797
|
+
await this.dbAccessor.update({
|
798
|
+
where: { id: user.id },
|
799
|
+
data: { [this.options.authFields.hashedPassword]: newHashedPassword }
|
800
|
+
});
|
801
|
+
return user;
|
802
|
+
}
|
803
|
+
}
|
804
|
+
throw new DbAuthError.IncorrectPasswordError(
|
805
|
+
user[this.options.authFields.username],
|
806
|
+
this.options.login?.errors?.incorrectPassword
|
807
|
+
);
|
808
|
+
}
|
809
|
+
// gets the user from the database and returns only its ID
|
810
|
+
async _getCurrentUser() {
|
811
|
+
if (!this.session?.[this.options.authFields.id]) {
|
812
|
+
throw new DbAuthError.NotLoggedInError();
|
813
|
+
}
|
814
|
+
const select = {
|
815
|
+
[this.options.authFields.id]: true,
|
816
|
+
[this.options.authFields.username]: true
|
817
|
+
};
|
818
|
+
if (this.options.webAuthn?.enabled && this.options.authFields.challenge) {
|
819
|
+
select[this.options.authFields.challenge] = true;
|
820
|
+
}
|
821
|
+
let user;
|
822
|
+
try {
|
823
|
+
user = await this.dbAccessor.findUnique({
|
824
|
+
where: {
|
825
|
+
[this.options.authFields.id]: this.session?.[this.options.authFields.id]
|
826
|
+
},
|
827
|
+
select
|
828
|
+
});
|
829
|
+
} catch (e) {
|
830
|
+
throw new DbAuthError.GenericError(e.message);
|
831
|
+
}
|
832
|
+
if (!user) {
|
833
|
+
throw new DbAuthError.UserNotFoundError();
|
834
|
+
}
|
835
|
+
return user;
|
836
|
+
}
|
837
|
+
// creates and returns a user, first checking that the username/password
|
838
|
+
// values pass validation
|
839
|
+
async _createUser() {
|
840
|
+
const { username, password, ...userAttributes } = this.normalizedRequest.jsonBody || {};
|
841
|
+
if (this._validateField("username", username) && this._validateField("password", password)) {
|
842
|
+
const usernameMatchFlowOption = this.options.signup?.usernameMatch;
|
843
|
+
const findUniqueUserMatchCriteriaOptions = this._getUserMatchCriteriaOptions(username, usernameMatchFlowOption);
|
844
|
+
const user = await this.dbAccessor.findFirst({
|
845
|
+
where: findUniqueUserMatchCriteriaOptions
|
846
|
+
});
|
847
|
+
if (user) {
|
848
|
+
throw new DbAuthError.DuplicateUsernameError(
|
849
|
+
username,
|
850
|
+
this.options.signup?.errors?.usernameTaken
|
851
|
+
);
|
852
|
+
}
|
853
|
+
const [hashedPassword, salt] = (0, import_shared.hashPassword)(password);
|
854
|
+
const newUser = await this.options.signup.handler({
|
855
|
+
username,
|
856
|
+
hashedPassword,
|
857
|
+
salt,
|
858
|
+
userAttributes
|
859
|
+
});
|
860
|
+
return newUser;
|
861
|
+
}
|
862
|
+
}
|
863
|
+
// figure out which auth method we're trying to call
|
864
|
+
async _getAuthMethod() {
|
865
|
+
let methodName = this.normalizedRequest.query?.method;
|
866
|
+
if (!DbAuthHandler.METHODS.includes(methodName) && this.normalizedRequest.jsonBody) {
|
867
|
+
try {
|
868
|
+
methodName = this.normalizedRequest.jsonBody.method;
|
869
|
+
} catch {
|
870
|
+
}
|
871
|
+
}
|
872
|
+
return methodName;
|
873
|
+
}
|
874
|
+
// checks that a single field meets validation requirements
|
875
|
+
// currently checks for presence only
|
876
|
+
_validateField(name, value) {
|
877
|
+
if (!value || value.trim() === "") {
|
878
|
+
throw new DbAuthError.FieldRequiredError(
|
879
|
+
name,
|
880
|
+
this.options.signup?.errors?.fieldMissing
|
881
|
+
);
|
882
|
+
} else {
|
883
|
+
return true;
|
884
|
+
}
|
885
|
+
}
|
886
|
+
_loginResponse(user, statusCode = 200) {
|
887
|
+
const sessionData = this._sanitizeUser(user);
|
888
|
+
const csrfToken = DbAuthHandler.CSRF_TOKEN;
|
889
|
+
const headers = new Headers();
|
890
|
+
headers.append("csrf-token", csrfToken);
|
891
|
+
headers.append("set-cookie", this._createAuthProviderCookieString());
|
892
|
+
headers.append(
|
893
|
+
"set-cookie",
|
894
|
+
this._createSessionCookieString(sessionData, csrfToken)
|
895
|
+
);
|
896
|
+
return [sessionData, headers, { statusCode }];
|
897
|
+
}
|
898
|
+
_logoutResponse(response) {
|
899
|
+
return [response ? JSON.stringify(response) : "", this._deleteSessionHeader];
|
900
|
+
}
|
901
|
+
_ok(body, headers = new Headers(), options = { statusCode: 200 }) {
|
902
|
+
headers.append("content-type", "application/json");
|
903
|
+
return {
|
904
|
+
statusCode: options.statusCode,
|
905
|
+
body: typeof body === "string" ? body : JSON.stringify(body),
|
906
|
+
headers
|
907
|
+
};
|
908
|
+
}
|
909
|
+
_notFound() {
|
910
|
+
return {
|
911
|
+
statusCode: 404
|
912
|
+
};
|
913
|
+
}
|
914
|
+
_badRequest(message) {
|
915
|
+
return {
|
916
|
+
statusCode: 400,
|
917
|
+
body: JSON.stringify({ error: message }),
|
918
|
+
headers: new Headers({ "content-type": "application/json" })
|
919
|
+
};
|
920
|
+
}
|
921
|
+
_getUserMatchCriteriaOptions(username, usernameMatchFlowOption) {
|
922
|
+
const findUniqueUserMatchCriteriaOptions = !usernameMatchFlowOption ? { [this.options.authFields.username]: username } : {
|
923
|
+
[this.options.authFields.username]: {
|
924
|
+
equals: username,
|
925
|
+
mode: usernameMatchFlowOption
|
926
|
+
}
|
927
|
+
};
|
928
|
+
return findUniqueUserMatchCriteriaOptions;
|
929
|
+
}
|
930
|
+
}
|
931
|
+
// Annotate the CommonJS export names for ESM import in node:
|
932
|
+
0 && (module.exports = {
|
933
|
+
DbAuthHandler
|
934
|
+
});
|