@eaccess/auth 0.1.2 → 0.1.4
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/dist/index.cjs +605 -522
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -56
- package/dist/index.d.ts +262 -56
- package/dist/index.js +607 -523
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/auth-manager.ts
|
|
8
|
+
import { hash as hash4 } from "@prsm/hash";
|
|
9
|
+
import ms3 from "@prsm/ms";
|
|
10
|
+
|
|
11
|
+
// src/types.ts
|
|
12
|
+
import "express-session";
|
|
13
|
+
var TwoFactorMechanism = /* @__PURE__ */ ((TwoFactorMechanism2) => {
|
|
14
|
+
TwoFactorMechanism2[TwoFactorMechanism2["TOTP"] = 1] = "TOTP";
|
|
15
|
+
TwoFactorMechanism2[TwoFactorMechanism2["EMAIL"] = 2] = "EMAIL";
|
|
16
|
+
TwoFactorMechanism2[TwoFactorMechanism2["SMS"] = 3] = "SMS";
|
|
17
|
+
return TwoFactorMechanism2;
|
|
18
|
+
})(TwoFactorMechanism || {});
|
|
19
|
+
var AuthStatus = {
|
|
20
|
+
Normal: 0,
|
|
21
|
+
Archived: 1,
|
|
22
|
+
Banned: 2,
|
|
23
|
+
Locked: 3,
|
|
24
|
+
PendingReview: 4,
|
|
25
|
+
Suspended: 5
|
|
26
|
+
};
|
|
27
|
+
var AuthRole = {
|
|
28
|
+
Admin: 1,
|
|
29
|
+
Author: 2,
|
|
30
|
+
Collaborator: 4,
|
|
31
|
+
Consultant: 8,
|
|
32
|
+
Consumer: 16,
|
|
33
|
+
Contributor: 32,
|
|
34
|
+
Coordinator: 64,
|
|
35
|
+
Creator: 128,
|
|
36
|
+
Developer: 256,
|
|
37
|
+
Director: 512,
|
|
38
|
+
Editor: 1024,
|
|
39
|
+
Employee: 2048,
|
|
40
|
+
Maintainer: 4096,
|
|
41
|
+
Manager: 8192,
|
|
42
|
+
Moderator: 16384,
|
|
43
|
+
Publisher: 32768,
|
|
44
|
+
Reviewer: 65536,
|
|
45
|
+
Subscriber: 131072,
|
|
46
|
+
SuperAdmin: 262144,
|
|
47
|
+
SuperEditor: 524288,
|
|
48
|
+
SuperModerator: 1048576,
|
|
49
|
+
Translator: 2097152
|
|
50
|
+
};
|
|
51
|
+
var AuthActivityAction = {
|
|
52
|
+
Login: "login",
|
|
53
|
+
Logout: "logout",
|
|
54
|
+
FailedLogin: "failed_login",
|
|
55
|
+
Register: "register",
|
|
56
|
+
EmailConfirmed: "email_confirmed",
|
|
57
|
+
PasswordResetRequested: "password_reset_requested",
|
|
58
|
+
PasswordResetCompleted: "password_reset_completed",
|
|
59
|
+
PasswordChanged: "password_changed",
|
|
60
|
+
EmailChanged: "email_changed",
|
|
61
|
+
RoleChanged: "role_changed",
|
|
62
|
+
StatusChanged: "status_changed",
|
|
63
|
+
ForceLogout: "force_logout",
|
|
64
|
+
OAuthConnected: "oauth_connected",
|
|
65
|
+
RememberTokenCreated: "remember_token_created",
|
|
66
|
+
TwoFactorSetup: "two_factor_setup",
|
|
67
|
+
TwoFactorVerified: "two_factor_verified",
|
|
68
|
+
TwoFactorFailed: "two_factor_failed",
|
|
69
|
+
TwoFactorDisabled: "two_factor_disabled",
|
|
70
|
+
BackupCodeUsed: "backup_code_used"
|
|
71
|
+
};
|
|
4
72
|
|
|
5
73
|
// src/queries.ts
|
|
6
74
|
var AuthQueries = class {
|
|
@@ -264,6 +332,158 @@ var AuthQueries = class {
|
|
|
264
332
|
}
|
|
265
333
|
};
|
|
266
334
|
|
|
335
|
+
// src/activity-logger.ts
|
|
336
|
+
import Bowser from "bowser";
|
|
337
|
+
var ActivityLogger = class {
|
|
338
|
+
constructor(config) {
|
|
339
|
+
this.config = config;
|
|
340
|
+
this.enabled = config.activityLog?.enabled !== false;
|
|
341
|
+
this.maxEntries = config.activityLog?.maxEntries || 1e4;
|
|
342
|
+
this.allowedActions = config.activityLog?.actions || null;
|
|
343
|
+
this.tablePrefix = config.tablePrefix || "user_";
|
|
344
|
+
}
|
|
345
|
+
get activityTable() {
|
|
346
|
+
return `${this.tablePrefix}activity_log`;
|
|
347
|
+
}
|
|
348
|
+
parseUserAgent(userAgent) {
|
|
349
|
+
if (!userAgent) {
|
|
350
|
+
return { browser: null, os: null, device: null };
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const browser = Bowser.getParser(userAgent);
|
|
354
|
+
const result = browser.getResult();
|
|
355
|
+
return {
|
|
356
|
+
browser: result.browser.name || null,
|
|
357
|
+
os: result.os.name || null,
|
|
358
|
+
device: result.platform.type || "desktop"
|
|
359
|
+
};
|
|
360
|
+
} catch (error) {
|
|
361
|
+
return this.parseUserAgentSimple(userAgent);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
parseUserAgentSimple(userAgent) {
|
|
365
|
+
let browser = null;
|
|
366
|
+
if (userAgent.includes("Chrome")) browser = "Chrome";
|
|
367
|
+
else if (userAgent.includes("Firefox")) browser = "Firefox";
|
|
368
|
+
else if (userAgent.includes("Safari")) browser = "Safari";
|
|
369
|
+
else if (userAgent.includes("Edge")) browser = "Edge";
|
|
370
|
+
let os = null;
|
|
371
|
+
if (userAgent.includes("Windows")) os = "Windows";
|
|
372
|
+
else if (userAgent.includes("Mac OS")) os = "macOS";
|
|
373
|
+
else if (userAgent.includes("Linux")) os = "Linux";
|
|
374
|
+
else if (userAgent.includes("Android")) os = "Android";
|
|
375
|
+
else if (userAgent.includes("iOS")) os = "iOS";
|
|
376
|
+
let device = "desktop";
|
|
377
|
+
if (userAgent.includes("Mobile")) device = "mobile";
|
|
378
|
+
else if (userAgent.includes("Tablet")) device = "tablet";
|
|
379
|
+
return { browser, os, device };
|
|
380
|
+
}
|
|
381
|
+
getIpAddress(req) {
|
|
382
|
+
return req.ip || req.connection?.remoteAddress || req.socket?.remoteAddress || req.connection?.socket?.remoteAddress || null;
|
|
383
|
+
}
|
|
384
|
+
async logActivity(accountId, action, req, success = true, metadata = {}) {
|
|
385
|
+
if (!this.enabled) return;
|
|
386
|
+
if (this.allowedActions && !this.allowedActions.includes(action)) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const userAgent = (typeof req.get === "function" ? req.get("User-Agent") : req.headers?.["user-agent"]) || null;
|
|
390
|
+
const ip = this.getIpAddress(req);
|
|
391
|
+
const parsed = this.parseUserAgent(userAgent);
|
|
392
|
+
try {
|
|
393
|
+
await this.config.db.query(
|
|
394
|
+
`
|
|
395
|
+
INSERT INTO ${this.activityTable}
|
|
396
|
+
(account_id, action, ip_address, user_agent, browser, os, device, success, metadata)
|
|
397
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
398
|
+
`,
|
|
399
|
+
[accountId, action, ip, userAgent, parsed.browser, parsed.os, parsed.device, success, Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null]
|
|
400
|
+
);
|
|
401
|
+
if (Math.random() < 0.01) {
|
|
402
|
+
await this.cleanup();
|
|
403
|
+
}
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error("ActivityLogger: Failed to log activity:", error);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async cleanup() {
|
|
409
|
+
if (!this.enabled) return;
|
|
410
|
+
try {
|
|
411
|
+
await this.config.db.query(
|
|
412
|
+
`
|
|
413
|
+
DELETE FROM ${this.activityTable}
|
|
414
|
+
WHERE id NOT IN (
|
|
415
|
+
SELECT id FROM ${this.activityTable}
|
|
416
|
+
ORDER BY created_at DESC
|
|
417
|
+
LIMIT $1
|
|
418
|
+
)
|
|
419
|
+
`,
|
|
420
|
+
[this.maxEntries]
|
|
421
|
+
);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.error("ActivityLogger: Failed to cleanup old entries:", error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async getRecentActivity(limit = 100, accountId) {
|
|
427
|
+
if (!this.enabled) return [];
|
|
428
|
+
try {
|
|
429
|
+
let sql = `
|
|
430
|
+
SELECT
|
|
431
|
+
al.*,
|
|
432
|
+
a.email
|
|
433
|
+
FROM ${this.activityTable} al
|
|
434
|
+
LEFT JOIN ${this.tablePrefix}accounts a ON al.account_id = a.id
|
|
435
|
+
`;
|
|
436
|
+
const params = [];
|
|
437
|
+
if (accountId !== void 0) {
|
|
438
|
+
sql += " WHERE al.account_id = $1";
|
|
439
|
+
params.push(accountId);
|
|
440
|
+
}
|
|
441
|
+
sql += ` ORDER BY al.created_at DESC LIMIT $${params.length + 1}`;
|
|
442
|
+
params.push(Math.min(limit, 1e3));
|
|
443
|
+
const result = await this.config.db.query(sql, params);
|
|
444
|
+
return result.rows.map((row) => ({
|
|
445
|
+
...row,
|
|
446
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
447
|
+
}));
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error("ActivityLogger: Failed to get recent activity:", error);
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async getActivityStats() {
|
|
454
|
+
if (!this.enabled) {
|
|
455
|
+
return {
|
|
456
|
+
totalEntries: 0,
|
|
457
|
+
uniqueUsers: 0,
|
|
458
|
+
recentLogins: 0,
|
|
459
|
+
failedAttempts: 0
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const [total, unique, recent, failed] = await Promise.all([
|
|
464
|
+
this.config.db.query(`SELECT COUNT(*) as count FROM ${this.activityTable}`),
|
|
465
|
+
this.config.db.query(`SELECT COUNT(DISTINCT account_id) as count FROM ${this.activityTable} WHERE account_id IS NOT NULL`),
|
|
466
|
+
this.config.db.query(`SELECT COUNT(*) as count FROM ${this.activityTable} WHERE action = 'login' AND created_at > NOW() - INTERVAL '24 hours'`),
|
|
467
|
+
this.config.db.query(`SELECT COUNT(*) as count FROM ${this.activityTable} WHERE success = false AND created_at > NOW() - INTERVAL '24 hours'`)
|
|
468
|
+
]);
|
|
469
|
+
return {
|
|
470
|
+
totalEntries: parseInt(total.rows[0]?.count || "0"),
|
|
471
|
+
uniqueUsers: parseInt(unique.rows[0]?.count || "0"),
|
|
472
|
+
recentLogins: parseInt(recent.rows[0]?.count || "0"),
|
|
473
|
+
failedAttempts: parseInt(failed.rows[0]?.count || "0")
|
|
474
|
+
};
|
|
475
|
+
} catch (error) {
|
|
476
|
+
console.error("ActivityLogger: Failed to get activity stats:", error);
|
|
477
|
+
return {
|
|
478
|
+
totalEntries: 0,
|
|
479
|
+
uniqueUsers: 0,
|
|
480
|
+
recentLogins: 0,
|
|
481
|
+
failedAttempts: 0
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
267
487
|
// src/errors.ts
|
|
268
488
|
var AuthError = class extends Error {
|
|
269
489
|
constructor(message) {
|
|
@@ -378,515 +598,23 @@ var TwoFactorSetupIncompleteError = class extends AuthError {
|
|
|
378
598
|
}
|
|
379
599
|
};
|
|
380
600
|
|
|
381
|
-
// src/create-user.ts
|
|
382
|
-
import { hash } from "@prsm/hash";
|
|
383
|
-
|
|
384
601
|
// src/util.ts
|
|
385
602
|
var isValidEmail = (email) => {
|
|
386
603
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
387
604
|
return emailRegex.test(email);
|
|
388
605
|
};
|
|
389
|
-
var validateEmail = (email) => {
|
|
390
|
-
if (typeof email !== "string") {
|
|
391
|
-
throw new InvalidEmailError();
|
|
392
|
-
}
|
|
393
|
-
if (!email.trim()) {
|
|
394
|
-
throw new InvalidEmailError();
|
|
395
|
-
}
|
|
396
|
-
if (!isValidEmail(email)) {
|
|
397
|
-
throw new InvalidEmailError();
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
var createMapFromEnum = (enumObj) => Object.fromEntries(Object.entries(enumObj).map(([key, value]) => [value, key]));
|
|
401
|
-
|
|
402
|
-
// src/types.ts
|
|
403
|
-
import "express-session";
|
|
404
|
-
var TwoFactorMechanism = /* @__PURE__ */ ((TwoFactorMechanism2) => {
|
|
405
|
-
TwoFactorMechanism2[TwoFactorMechanism2["TOTP"] = 1] = "TOTP";
|
|
406
|
-
TwoFactorMechanism2[TwoFactorMechanism2["EMAIL"] = 2] = "EMAIL";
|
|
407
|
-
TwoFactorMechanism2[TwoFactorMechanism2["SMS"] = 3] = "SMS";
|
|
408
|
-
return TwoFactorMechanism2;
|
|
409
|
-
})(TwoFactorMechanism || {});
|
|
410
|
-
var AuthStatus = {
|
|
411
|
-
Normal: 0,
|
|
412
|
-
Archived: 1,
|
|
413
|
-
Banned: 2,
|
|
414
|
-
Locked: 3,
|
|
415
|
-
PendingReview: 4,
|
|
416
|
-
Suspended: 5
|
|
417
|
-
};
|
|
418
|
-
var AuthRole = {
|
|
419
|
-
Admin: 1,
|
|
420
|
-
Author: 2,
|
|
421
|
-
Collaborator: 4,
|
|
422
|
-
Consultant: 8,
|
|
423
|
-
Consumer: 16,
|
|
424
|
-
Contributor: 32,
|
|
425
|
-
Coordinator: 64,
|
|
426
|
-
Creator: 128,
|
|
427
|
-
Developer: 256,
|
|
428
|
-
Director: 512,
|
|
429
|
-
Editor: 1024,
|
|
430
|
-
Employee: 2048,
|
|
431
|
-
Maintainer: 4096,
|
|
432
|
-
Manager: 8192,
|
|
433
|
-
Moderator: 16384,
|
|
434
|
-
Publisher: 32768,
|
|
435
|
-
Reviewer: 65536,
|
|
436
|
-
Subscriber: 131072,
|
|
437
|
-
SuperAdmin: 262144,
|
|
438
|
-
SuperEditor: 524288,
|
|
439
|
-
SuperModerator: 1048576,
|
|
440
|
-
Translator: 2097152
|
|
441
|
-
};
|
|
442
|
-
var AuthActivityAction = {
|
|
443
|
-
Login: "login",
|
|
444
|
-
Logout: "logout",
|
|
445
|
-
FailedLogin: "failed_login",
|
|
446
|
-
Register: "register",
|
|
447
|
-
EmailConfirmed: "email_confirmed",
|
|
448
|
-
PasswordResetRequested: "password_reset_requested",
|
|
449
|
-
PasswordResetCompleted: "password_reset_completed",
|
|
450
|
-
PasswordChanged: "password_changed",
|
|
451
|
-
EmailChanged: "email_changed",
|
|
452
|
-
RoleChanged: "role_changed",
|
|
453
|
-
StatusChanged: "status_changed",
|
|
454
|
-
ForceLogout: "force_logout",
|
|
455
|
-
OAuthConnected: "oauth_connected",
|
|
456
|
-
RememberTokenCreated: "remember_token_created",
|
|
457
|
-
TwoFactorSetup: "two_factor_setup",
|
|
458
|
-
TwoFactorVerified: "two_factor_verified",
|
|
459
|
-
TwoFactorFailed: "two_factor_failed",
|
|
460
|
-
TwoFactorDisabled: "two_factor_disabled",
|
|
461
|
-
BackupCodeUsed: "backup_code_used"
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
// src/create-user.ts
|
|
465
|
-
function validatePassword(password, config) {
|
|
466
|
-
const minLength = config.minPasswordLength || 8;
|
|
467
|
-
const maxLength = config.maxPasswordLength || 64;
|
|
468
|
-
if (typeof password !== "string") {
|
|
469
|
-
throw new InvalidPasswordError();
|
|
470
|
-
}
|
|
471
|
-
if (password.length < minLength) {
|
|
472
|
-
throw new InvalidPasswordError();
|
|
473
|
-
}
|
|
474
|
-
if (password.length > maxLength) {
|
|
475
|
-
throw new InvalidPasswordError();
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
function generateAutoUserId() {
|
|
479
|
-
return crypto.randomUUID();
|
|
480
|
-
}
|
|
481
|
-
async function createConfirmationToken(queries, account, email, callback) {
|
|
482
|
-
const token = hash.encode(email);
|
|
483
|
-
const expires = new Date(Date.now() + 1e3 * 60 * 60 * 24 * 7);
|
|
484
|
-
await queries.createConfirmation({
|
|
485
|
-
accountId: account.id,
|
|
486
|
-
token,
|
|
487
|
-
email,
|
|
488
|
-
expires
|
|
489
|
-
});
|
|
490
|
-
if (callback) {
|
|
491
|
-
callback(token);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
async function createUser(config, credentials, userId, callback) {
|
|
495
|
-
validateEmail(credentials.email);
|
|
496
|
-
validatePassword(credentials.password, config);
|
|
497
|
-
const queries = new AuthQueries(config);
|
|
498
|
-
const existing = await queries.findAccountByEmail(credentials.email);
|
|
499
|
-
if (existing) {
|
|
500
|
-
throw new EmailTakenError();
|
|
501
|
-
}
|
|
502
|
-
const finalUserId = userId || generateAutoUserId();
|
|
503
|
-
const hashedPassword = hash.encode(credentials.password);
|
|
504
|
-
const verified = typeof callback !== "function";
|
|
505
|
-
const account = await queries.createAccount({
|
|
506
|
-
userId: finalUserId,
|
|
507
|
-
email: credentials.email,
|
|
508
|
-
password: hashedPassword,
|
|
509
|
-
verified,
|
|
510
|
-
status: AuthStatus.Normal,
|
|
511
|
-
rolemask: 0
|
|
512
|
-
});
|
|
513
|
-
if (!verified && callback) {
|
|
514
|
-
await createConfirmationToken(queries, account, credentials.email, callback);
|
|
515
|
-
}
|
|
516
|
-
return account;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// src/auth-admin-manager.ts
|
|
520
|
-
var AuthAdminManager = class {
|
|
521
|
-
constructor(req, res, config, auth) {
|
|
522
|
-
this.req = req;
|
|
523
|
-
this.res = res;
|
|
524
|
-
this.config = config;
|
|
525
|
-
this.queries = new AuthQueries(config);
|
|
526
|
-
this.auth = auth;
|
|
527
|
-
}
|
|
528
|
-
validatePassword(password) {
|
|
529
|
-
const minLength = this.config.minPasswordLength || 8;
|
|
530
|
-
const maxLength = this.config.maxPasswordLength || 64;
|
|
531
|
-
if (typeof password !== "string") {
|
|
532
|
-
throw new InvalidPasswordError();
|
|
533
|
-
}
|
|
534
|
-
if (password.length < minLength) {
|
|
535
|
-
throw new InvalidPasswordError();
|
|
536
|
-
}
|
|
537
|
-
if (password.length > maxLength) {
|
|
538
|
-
throw new InvalidPasswordError();
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
async findAccountByIdentifier(identifier) {
|
|
542
|
-
if (identifier.accountId !== void 0) {
|
|
543
|
-
return await this.queries.findAccountById(identifier.accountId);
|
|
544
|
-
} else if (identifier.email !== void 0) {
|
|
545
|
-
return await this.queries.findAccountByEmail(identifier.email);
|
|
546
|
-
} else if (identifier.userId !== void 0) {
|
|
547
|
-
return await this.queries.findAccountByUserId(identifier.userId);
|
|
548
|
-
}
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
async createConfirmationToken(account, email, callback) {
|
|
552
|
-
const token = hash2.encode(email);
|
|
553
|
-
const expires = new Date(Date.now() + 1e3 * 60 * 60 * 24 * 7);
|
|
554
|
-
await this.queries.createConfirmation({
|
|
555
|
-
accountId: account.id,
|
|
556
|
-
token,
|
|
557
|
-
email,
|
|
558
|
-
expires
|
|
559
|
-
});
|
|
560
|
-
if (callback) {
|
|
561
|
-
callback(token);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* Create a new user account (admin function).
|
|
566
|
-
*
|
|
567
|
-
* @param credentials - Email and password for new account
|
|
568
|
-
* @param userId - Optional user ID to link this auth account to. If not provided, a UUID will be generated automatically.
|
|
569
|
-
* @param callback - If provided, account is created unverified and callback receives confirmation token. Create a URL like /confirm/{token} and call confirmEmail() in that handler. If omitted, account is immediately verified.
|
|
570
|
-
* @returns The created account record
|
|
571
|
-
* @throws {EmailTakenError} Email is already registered
|
|
572
|
-
* @throws {InvalidPasswordError} Password doesn't meet length requirements
|
|
573
|
-
*/
|
|
574
|
-
async createUser(credentials, userId, callback) {
|
|
575
|
-
return createUser(this.config, credentials, userId, callback);
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Log in as another user (admin function).
|
|
579
|
-
* Creates a new session as the target user without requiring their password.
|
|
580
|
-
*
|
|
581
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
582
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
583
|
-
*/
|
|
584
|
-
async loginAsUserBy(identifier) {
|
|
585
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
586
|
-
if (!account) {
|
|
587
|
-
throw new UserNotFoundError();
|
|
588
|
-
}
|
|
589
|
-
await this.auth.onLoginSuccessful(account, false);
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* Delete a user account and all associated data (admin function).
|
|
593
|
-
* Removes account, confirmations, remember tokens, and reset tokens.
|
|
594
|
-
*
|
|
595
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
596
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
597
|
-
*/
|
|
598
|
-
async deleteUserBy(identifier) {
|
|
599
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
600
|
-
if (!account) {
|
|
601
|
-
throw new UserNotFoundError();
|
|
602
|
-
}
|
|
603
|
-
await this.queries.deleteAccount(account.id);
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Add a role to a user's account (admin function).
|
|
607
|
-
* Uses bitwise OR to add role to existing rolemask.
|
|
608
|
-
*
|
|
609
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
610
|
-
* @param role - Role bitmask to add (e.g., AuthRole.Admin)
|
|
611
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
612
|
-
*/
|
|
613
|
-
async addRoleForUserBy(identifier, role) {
|
|
614
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
615
|
-
if (!account) {
|
|
616
|
-
throw new UserNotFoundError();
|
|
617
|
-
}
|
|
618
|
-
const rolemask = account.rolemask | role;
|
|
619
|
-
await this.queries.updateAccount(account.id, { rolemask });
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Remove a role from a user's account (admin function).
|
|
623
|
-
* Uses bitwise operations to remove role from rolemask.
|
|
624
|
-
*
|
|
625
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
626
|
-
* @param role - Role bitmask to remove (e.g., AuthRole.Admin)
|
|
627
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
628
|
-
*/
|
|
629
|
-
async removeRoleForUserBy(identifier, role) {
|
|
630
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
631
|
-
if (!account) {
|
|
632
|
-
throw new UserNotFoundError();
|
|
633
|
-
}
|
|
634
|
-
const rolemask = account.rolemask & ~role;
|
|
635
|
-
await this.queries.updateAccount(account.id, { rolemask });
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Check if a user has a specific role (admin function).
|
|
639
|
-
*
|
|
640
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
641
|
-
* @param role - Role bitmask to check (e.g., AuthRole.Admin)
|
|
642
|
-
* @returns true if user has the role, false otherwise
|
|
643
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
644
|
-
*/
|
|
645
|
-
async hasRoleForUserBy(identifier, role) {
|
|
646
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
647
|
-
if (!account) {
|
|
648
|
-
throw new UserNotFoundError();
|
|
649
|
-
}
|
|
650
|
-
return (account.rolemask & role) === role;
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Change a user's password (admin function).
|
|
654
|
-
* Does not require knowing the current password.
|
|
655
|
-
*
|
|
656
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
657
|
-
* @param password - New password (will be hashed)
|
|
658
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
659
|
-
* @throws {InvalidPasswordError} New password doesn't meet requirements
|
|
660
|
-
*/
|
|
661
|
-
async changePasswordForUserBy(identifier, password) {
|
|
662
|
-
this.validatePassword(password);
|
|
663
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
664
|
-
if (!account) {
|
|
665
|
-
throw new UserNotFoundError();
|
|
666
|
-
}
|
|
667
|
-
await this.queries.updateAccount(account.id, {
|
|
668
|
-
password: hash2.encode(password)
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Change a user's account status (admin function).
|
|
673
|
-
*
|
|
674
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
675
|
-
* @param status - New status (0=Normal, 1=Archived, 2=Banned, 3=Locked, etc.)
|
|
676
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
677
|
-
*/
|
|
678
|
-
async setStatusForUserBy(identifier, status) {
|
|
679
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
680
|
-
if (!account) {
|
|
681
|
-
throw new UserNotFoundError();
|
|
682
|
-
}
|
|
683
|
-
await this.queries.updateAccount(account.id, { status });
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Initiate password reset for a user (admin function).
|
|
687
|
-
* Creates a reset token without rate limiting.
|
|
688
|
-
*
|
|
689
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
690
|
-
* @param expiresAfter - Token expiration (default: 6h). Accepts ms format like '1h', '30m'
|
|
691
|
-
* @param callback - Called with reset token. Create a URL like /reset/{token} and call confirmResetPassword() in that handler
|
|
692
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
693
|
-
* @throws {EmailNotVerifiedError} Account exists but email is not verified
|
|
694
|
-
*/
|
|
695
|
-
async initiatePasswordResetForUserBy(identifier, expiresAfter = null, callback) {
|
|
696
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
697
|
-
if (!account) {
|
|
698
|
-
throw new UserNotFoundError();
|
|
699
|
-
}
|
|
700
|
-
if (!account.verified) {
|
|
701
|
-
throw new EmailNotVerifiedError();
|
|
702
|
-
}
|
|
703
|
-
const expiry = !expiresAfter ? ms("6h") : ms(expiresAfter);
|
|
704
|
-
const token = hash2.encode(account.email);
|
|
705
|
-
const expires = new Date(Date.now() + expiry);
|
|
706
|
-
await this.queries.createResetToken({
|
|
707
|
-
accountId: account.id,
|
|
708
|
-
token,
|
|
709
|
-
expires
|
|
710
|
-
});
|
|
711
|
-
if (callback) {
|
|
712
|
-
callback(token);
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Force logout all sessions for a specific user (admin function).
|
|
717
|
-
* Increments force_logout counter and deletes all remember tokens.
|
|
718
|
-
* If target user is currently logged in, marks their session for logout.
|
|
719
|
-
*
|
|
720
|
-
* @param identifier - Find user by accountId, email, or userId
|
|
721
|
-
* @throws {UserNotFoundError} No account matches the identifier
|
|
722
|
-
*/
|
|
723
|
-
async forceLogoutForUserBy(identifier) {
|
|
724
|
-
const account = await this.findAccountByIdentifier(identifier);
|
|
725
|
-
if (!account) {
|
|
726
|
-
throw new UserNotFoundError();
|
|
727
|
-
}
|
|
728
|
-
await this.queries.incrementForceLogout(account.id);
|
|
729
|
-
if (this.auth.getId() === account.id) {
|
|
730
|
-
this.req.session.auth.shouldForceLogout = true;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
// src/auth-manager.ts
|
|
736
|
-
import { hash as hash5 } from "@prsm/hash";
|
|
737
|
-
import ms3 from "@prsm/ms";
|
|
738
|
-
|
|
739
|
-
// src/activity-logger.ts
|
|
740
|
-
import Bowser from "bowser";
|
|
741
|
-
var ActivityLogger = class {
|
|
742
|
-
constructor(config) {
|
|
743
|
-
this.config = config;
|
|
744
|
-
this.enabled = config.activityLog?.enabled !== false;
|
|
745
|
-
this.maxEntries = config.activityLog?.maxEntries || 1e4;
|
|
746
|
-
this.allowedActions = config.activityLog?.actions || null;
|
|
747
|
-
this.tablePrefix = config.tablePrefix || "user_";
|
|
748
|
-
}
|
|
749
|
-
get activityTable() {
|
|
750
|
-
return `${this.tablePrefix}activity_log`;
|
|
751
|
-
}
|
|
752
|
-
parseUserAgent(userAgent) {
|
|
753
|
-
if (!userAgent) {
|
|
754
|
-
return { browser: null, os: null, device: null };
|
|
755
|
-
}
|
|
756
|
-
try {
|
|
757
|
-
const browser = Bowser.getParser(userAgent);
|
|
758
|
-
const result = browser.getResult();
|
|
759
|
-
return {
|
|
760
|
-
browser: result.browser.name || null,
|
|
761
|
-
os: result.os.name || null,
|
|
762
|
-
device: result.platform.type || "desktop"
|
|
763
|
-
};
|
|
764
|
-
} catch (error) {
|
|
765
|
-
return this.parseUserAgentSimple(userAgent);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
parseUserAgentSimple(userAgent) {
|
|
769
|
-
let browser = null;
|
|
770
|
-
if (userAgent.includes("Chrome")) browser = "Chrome";
|
|
771
|
-
else if (userAgent.includes("Firefox")) browser = "Firefox";
|
|
772
|
-
else if (userAgent.includes("Safari")) browser = "Safari";
|
|
773
|
-
else if (userAgent.includes("Edge")) browser = "Edge";
|
|
774
|
-
let os = null;
|
|
775
|
-
if (userAgent.includes("Windows")) os = "Windows";
|
|
776
|
-
else if (userAgent.includes("Mac OS")) os = "macOS";
|
|
777
|
-
else if (userAgent.includes("Linux")) os = "Linux";
|
|
778
|
-
else if (userAgent.includes("Android")) os = "Android";
|
|
779
|
-
else if (userAgent.includes("iOS")) os = "iOS";
|
|
780
|
-
let device = "desktop";
|
|
781
|
-
if (userAgent.includes("Mobile")) device = "mobile";
|
|
782
|
-
else if (userAgent.includes("Tablet")) device = "tablet";
|
|
783
|
-
return { browser, os, device };
|
|
784
|
-
}
|
|
785
|
-
getIpAddress(req) {
|
|
786
|
-
return req.ip || req.connection?.remoteAddress || req.socket?.remoteAddress || req.connection?.socket?.remoteAddress || null;
|
|
787
|
-
}
|
|
788
|
-
async logActivity(accountId, action, req, success = true, metadata = {}) {
|
|
789
|
-
if (!this.enabled) return;
|
|
790
|
-
if (this.allowedActions && !this.allowedActions.includes(action)) {
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
const userAgent = (typeof req.get === "function" ? req.get("User-Agent") : req.headers?.["user-agent"]) || null;
|
|
794
|
-
const ip = this.getIpAddress(req);
|
|
795
|
-
const parsed = this.parseUserAgent(userAgent);
|
|
796
|
-
try {
|
|
797
|
-
await this.config.db.query(
|
|
798
|
-
`
|
|
799
|
-
INSERT INTO ${this.activityTable}
|
|
800
|
-
(account_id, action, ip_address, user_agent, browser, os, device, success, metadata)
|
|
801
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
802
|
-
`,
|
|
803
|
-
[accountId, action, ip, userAgent, parsed.browser, parsed.os, parsed.device, success, Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null]
|
|
804
|
-
);
|
|
805
|
-
if (Math.random() < 0.01) {
|
|
806
|
-
await this.cleanup();
|
|
807
|
-
}
|
|
808
|
-
} catch (error) {
|
|
809
|
-
console.error("ActivityLogger: Failed to log activity:", error);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
async cleanup() {
|
|
813
|
-
if (!this.enabled) return;
|
|
814
|
-
try {
|
|
815
|
-
await this.config.db.query(
|
|
816
|
-
`
|
|
817
|
-
DELETE FROM ${this.activityTable}
|
|
818
|
-
WHERE id NOT IN (
|
|
819
|
-
SELECT id FROM ${this.activityTable}
|
|
820
|
-
ORDER BY created_at DESC
|
|
821
|
-
LIMIT $1
|
|
822
|
-
)
|
|
823
|
-
`,
|
|
824
|
-
[this.maxEntries]
|
|
825
|
-
);
|
|
826
|
-
} catch (error) {
|
|
827
|
-
console.error("ActivityLogger: Failed to cleanup old entries:", error);
|
|
828
|
-
}
|
|
606
|
+
var validateEmail = (email) => {
|
|
607
|
+
if (typeof email !== "string") {
|
|
608
|
+
throw new InvalidEmailError();
|
|
829
609
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
try {
|
|
833
|
-
let sql = `
|
|
834
|
-
SELECT
|
|
835
|
-
al.*,
|
|
836
|
-
a.email
|
|
837
|
-
FROM ${this.activityTable} al
|
|
838
|
-
LEFT JOIN ${this.tablePrefix}accounts a ON al.account_id = a.id
|
|
839
|
-
`;
|
|
840
|
-
const params = [];
|
|
841
|
-
if (accountId !== void 0) {
|
|
842
|
-
sql += " WHERE al.account_id = $1";
|
|
843
|
-
params.push(accountId);
|
|
844
|
-
}
|
|
845
|
-
sql += ` ORDER BY al.created_at DESC LIMIT $${params.length + 1}`;
|
|
846
|
-
params.push(Math.min(limit, 1e3));
|
|
847
|
-
const result = await this.config.db.query(sql, params);
|
|
848
|
-
return result.rows.map((row) => ({
|
|
849
|
-
...row,
|
|
850
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
851
|
-
}));
|
|
852
|
-
} catch (error) {
|
|
853
|
-
console.error("ActivityLogger: Failed to get recent activity:", error);
|
|
854
|
-
return [];
|
|
855
|
-
}
|
|
610
|
+
if (!email.trim()) {
|
|
611
|
+
throw new InvalidEmailError();
|
|
856
612
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
return {
|
|
860
|
-
totalEntries: 0,
|
|
861
|
-
uniqueUsers: 0,
|
|
862
|
-
recentLogins: 0,
|
|
863
|
-
failedAttempts: 0
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
try {
|
|
867
|
-
const [total, unique, recent, failed] = await Promise.all([
|
|
868
|
-
this.config.db.query(`SELECT COUNT(*) as count FROM ${this.activityTable}`),
|
|
869
|
-
this.config.db.query(`SELECT COUNT(DISTINCT account_id) as count FROM ${this.activityTable} WHERE account_id IS NOT NULL`),
|
|
870
|
-
this.config.db.query(`SELECT COUNT(*) as count FROM ${this.activityTable} WHERE action = 'login' AND created_at > NOW() - INTERVAL '24 hours'`),
|
|
871
|
-
this.config.db.query(`SELECT COUNT(*) as count FROM ${this.activityTable} WHERE success = false AND created_at > NOW() - INTERVAL '24 hours'`)
|
|
872
|
-
]);
|
|
873
|
-
return {
|
|
874
|
-
totalEntries: parseInt(total.rows[0]?.count || "0"),
|
|
875
|
-
uniqueUsers: parseInt(unique.rows[0]?.count || "0"),
|
|
876
|
-
recentLogins: parseInt(recent.rows[0]?.count || "0"),
|
|
877
|
-
failedAttempts: parseInt(failed.rows[0]?.count || "0")
|
|
878
|
-
};
|
|
879
|
-
} catch (error) {
|
|
880
|
-
console.error("ActivityLogger: Failed to get activity stats:", error);
|
|
881
|
-
return {
|
|
882
|
-
totalEntries: 0,
|
|
883
|
-
uniqueUsers: 0,
|
|
884
|
-
recentLogins: 0,
|
|
885
|
-
failedAttempts: 0
|
|
886
|
-
};
|
|
887
|
-
}
|
|
613
|
+
if (!isValidEmail(email)) {
|
|
614
|
+
throw new InvalidEmailError();
|
|
888
615
|
}
|
|
889
616
|
};
|
|
617
|
+
var createMapFromEnum = (enumObj) => Object.fromEntries(Object.entries(enumObj).map(([key, value]) => [value, key]));
|
|
890
618
|
|
|
891
619
|
// src/providers/base-provider.ts
|
|
892
620
|
var BaseOAuthProvider = class {
|
|
@@ -1131,7 +859,7 @@ var AzureProvider = class extends BaseOAuthProvider {
|
|
|
1131
859
|
|
|
1132
860
|
// src/two-factor/totp-provider.ts
|
|
1133
861
|
import Otp from "@eaccess/totp";
|
|
1134
|
-
import { hash
|
|
862
|
+
import { hash } from "@prsm/hash";
|
|
1135
863
|
var TotpProvider = class {
|
|
1136
864
|
constructor(config) {
|
|
1137
865
|
this.config = config;
|
|
@@ -1160,11 +888,11 @@ var TotpProvider = class {
|
|
|
1160
888
|
return codes;
|
|
1161
889
|
}
|
|
1162
890
|
hashBackupCodes(codes) {
|
|
1163
|
-
return codes.map((code) =>
|
|
891
|
+
return codes.map((code) => hash.encode(code));
|
|
1164
892
|
}
|
|
1165
893
|
verifyBackupCode(hashedCodes, inputCode) {
|
|
1166
894
|
for (let i = 0; i < hashedCodes.length; i++) {
|
|
1167
|
-
if (
|
|
895
|
+
if (hash.verify(hashedCodes[i], inputCode.toUpperCase())) {
|
|
1168
896
|
return { isValid: true, index: i };
|
|
1169
897
|
}
|
|
1170
898
|
}
|
|
@@ -1180,8 +908,8 @@ var TotpProvider = class {
|
|
|
1180
908
|
};
|
|
1181
909
|
|
|
1182
910
|
// src/two-factor/otp-provider.ts
|
|
1183
|
-
import
|
|
1184
|
-
import { hash as
|
|
911
|
+
import ms from "@prsm/ms";
|
|
912
|
+
import { hash as hash2 } from "@prsm/hash";
|
|
1185
913
|
var OtpProvider = class {
|
|
1186
914
|
constructor(config) {
|
|
1187
915
|
this.config = config;
|
|
@@ -1206,9 +934,9 @@ var OtpProvider = class {
|
|
|
1206
934
|
async createAndStoreOTP(accountId, mechanism) {
|
|
1207
935
|
const otp = this.generateOTP();
|
|
1208
936
|
const selector = this.generateSelector();
|
|
1209
|
-
const tokenHash =
|
|
937
|
+
const tokenHash = hash2.encode(otp);
|
|
1210
938
|
const expiryDuration = this.config.twoFactor?.tokenExpiry || "5m";
|
|
1211
|
-
const expiresAt = new Date(Date.now() +
|
|
939
|
+
const expiresAt = new Date(Date.now() + ms(expiryDuration));
|
|
1212
940
|
await this.queries.deleteTwoFactorTokensByAccountAndMechanism(accountId, mechanism);
|
|
1213
941
|
await this.queries.createTwoFactorToken({
|
|
1214
942
|
accountId,
|
|
@@ -1228,7 +956,7 @@ var OtpProvider = class {
|
|
|
1228
956
|
await this.queries.deleteTwoFactorToken(token.id);
|
|
1229
957
|
return { isValid: false };
|
|
1230
958
|
}
|
|
1231
|
-
const isValid =
|
|
959
|
+
const isValid = hash2.verify(token.token_hash, inputCode);
|
|
1232
960
|
if (isValid) {
|
|
1233
961
|
await this.queries.deleteTwoFactorToken(token.id);
|
|
1234
962
|
return { isValid: true, token };
|
|
@@ -1626,6 +1354,248 @@ var TwoFactorManager = class {
|
|
|
1626
1354
|
}
|
|
1627
1355
|
};
|
|
1628
1356
|
|
|
1357
|
+
// src/auth-functions.ts
|
|
1358
|
+
var auth_functions_exports = {};
|
|
1359
|
+
__export(auth_functions_exports, {
|
|
1360
|
+
addRoleForUserBy: () => addRoleForUserBy,
|
|
1361
|
+
changePasswordForUserBy: () => changePasswordForUserBy,
|
|
1362
|
+
confirmResetPassword: () => confirmResetPassword,
|
|
1363
|
+
createUser: () => createUser,
|
|
1364
|
+
deleteUserBy: () => deleteUserBy,
|
|
1365
|
+
forceLogoutForUserBy: () => forceLogoutForUserBy,
|
|
1366
|
+
hasRoleForUserBy: () => hasRoleForUserBy,
|
|
1367
|
+
initiatePasswordResetForUserBy: () => initiatePasswordResetForUserBy,
|
|
1368
|
+
register: () => register,
|
|
1369
|
+
removeRoleForUserBy: () => removeRoleForUserBy,
|
|
1370
|
+
resetPassword: () => resetPassword,
|
|
1371
|
+
setStatusForUserBy: () => setStatusForUserBy
|
|
1372
|
+
});
|
|
1373
|
+
import { hash as hash3 } from "@prsm/hash";
|
|
1374
|
+
import ms2 from "@prsm/ms";
|
|
1375
|
+
function validatePassword(password, config) {
|
|
1376
|
+
const minLength = config.minPasswordLength || 8;
|
|
1377
|
+
const maxLength = config.maxPasswordLength || 64;
|
|
1378
|
+
if (typeof password !== "string") {
|
|
1379
|
+
throw new InvalidPasswordError();
|
|
1380
|
+
}
|
|
1381
|
+
if (password.length < minLength) {
|
|
1382
|
+
throw new InvalidPasswordError();
|
|
1383
|
+
}
|
|
1384
|
+
if (password.length > maxLength) {
|
|
1385
|
+
throw new InvalidPasswordError();
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function generateAutoUserId() {
|
|
1389
|
+
return crypto.randomUUID();
|
|
1390
|
+
}
|
|
1391
|
+
async function findAccountByIdentifier(queries, identifier) {
|
|
1392
|
+
if (identifier.accountId !== void 0) {
|
|
1393
|
+
return await queries.findAccountById(identifier.accountId);
|
|
1394
|
+
} else if (identifier.email !== void 0) {
|
|
1395
|
+
return await queries.findAccountByEmail(identifier.email);
|
|
1396
|
+
} else if (identifier.userId !== void 0) {
|
|
1397
|
+
return await queries.findAccountByUserId(identifier.userId);
|
|
1398
|
+
}
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
async function createConfirmationToken(queries, account, email, callback) {
|
|
1402
|
+
const token = hash3.encode(email);
|
|
1403
|
+
const expires = new Date(Date.now() + 1e3 * 60 * 60 * 24 * 7);
|
|
1404
|
+
await queries.createConfirmation({
|
|
1405
|
+
accountId: account.id,
|
|
1406
|
+
token,
|
|
1407
|
+
email,
|
|
1408
|
+
expires
|
|
1409
|
+
});
|
|
1410
|
+
if (callback) {
|
|
1411
|
+
callback(token);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
async function createUser(config, credentials, userId, callback) {
|
|
1415
|
+
validateEmail(credentials.email);
|
|
1416
|
+
validatePassword(credentials.password, config);
|
|
1417
|
+
const queries = new AuthQueries(config);
|
|
1418
|
+
const existing = await queries.findAccountByEmail(credentials.email);
|
|
1419
|
+
if (existing) {
|
|
1420
|
+
throw new EmailTakenError();
|
|
1421
|
+
}
|
|
1422
|
+
const finalUserId = userId || generateAutoUserId();
|
|
1423
|
+
const hashedPassword = hash3.encode(credentials.password);
|
|
1424
|
+
const verified = typeof callback !== "function";
|
|
1425
|
+
const account = await queries.createAccount({
|
|
1426
|
+
userId: finalUserId,
|
|
1427
|
+
email: credentials.email,
|
|
1428
|
+
password: hashedPassword,
|
|
1429
|
+
verified,
|
|
1430
|
+
status: AuthStatus.Normal,
|
|
1431
|
+
rolemask: 0
|
|
1432
|
+
});
|
|
1433
|
+
if (!verified && callback) {
|
|
1434
|
+
await createConfirmationToken(queries, account, credentials.email, callback);
|
|
1435
|
+
}
|
|
1436
|
+
return account;
|
|
1437
|
+
}
|
|
1438
|
+
async function register(config, email, password, userId, callback) {
|
|
1439
|
+
validateEmail(email);
|
|
1440
|
+
validatePassword(password, config);
|
|
1441
|
+
const queries = new AuthQueries(config);
|
|
1442
|
+
const existing = await queries.findAccountByEmail(email);
|
|
1443
|
+
if (existing) {
|
|
1444
|
+
throw new EmailTakenError();
|
|
1445
|
+
}
|
|
1446
|
+
const finalUserId = userId || generateAutoUserId();
|
|
1447
|
+
const hashedPassword = hash3.encode(password);
|
|
1448
|
+
const verified = typeof callback !== "function";
|
|
1449
|
+
const account = await queries.createAccount({
|
|
1450
|
+
userId: finalUserId,
|
|
1451
|
+
email,
|
|
1452
|
+
password: hashedPassword,
|
|
1453
|
+
verified,
|
|
1454
|
+
status: AuthStatus.Normal,
|
|
1455
|
+
rolemask: 0
|
|
1456
|
+
});
|
|
1457
|
+
if (!verified && callback) {
|
|
1458
|
+
await createConfirmationToken(queries, account, email, callback);
|
|
1459
|
+
}
|
|
1460
|
+
return account;
|
|
1461
|
+
}
|
|
1462
|
+
async function deleteUserBy(config, identifier) {
|
|
1463
|
+
const queries = new AuthQueries(config);
|
|
1464
|
+
const account = await findAccountByIdentifier(queries, identifier);
|
|
1465
|
+
if (!account) {
|
|
1466
|
+
throw new UserNotFoundError();
|
|
1467
|
+
}
|
|
1468
|
+
await queries.deleteAccount(account.id);
|
|
1469
|
+
}
|
|
1470
|
+
async function addRoleForUserBy(config, identifier, role) {
|
|
1471
|
+
const queries = new AuthQueries(config);
|
|
1472
|
+
const account = await findAccountByIdentifier(queries, identifier);
|
|
1473
|
+
if (!account) {
|
|
1474
|
+
throw new UserNotFoundError();
|
|
1475
|
+
}
|
|
1476
|
+
const rolemask = account.rolemask | role;
|
|
1477
|
+
await queries.updateAccount(account.id, { rolemask });
|
|
1478
|
+
}
|
|
1479
|
+
async function removeRoleForUserBy(config, identifier, role) {
|
|
1480
|
+
const queries = new AuthQueries(config);
|
|
1481
|
+
const account = await findAccountByIdentifier(queries, identifier);
|
|
1482
|
+
if (!account) {
|
|
1483
|
+
throw new UserNotFoundError();
|
|
1484
|
+
}
|
|
1485
|
+
const rolemask = account.rolemask & ~role;
|
|
1486
|
+
await queries.updateAccount(account.id, { rolemask });
|
|
1487
|
+
}
|
|
1488
|
+
async function hasRoleForUserBy(config, identifier, role) {
|
|
1489
|
+
const queries = new AuthQueries(config);
|
|
1490
|
+
const account = await findAccountByIdentifier(queries, identifier);
|
|
1491
|
+
if (!account) {
|
|
1492
|
+
throw new UserNotFoundError();
|
|
1493
|
+
}
|
|
1494
|
+
return (account.rolemask & role) === role;
|
|
1495
|
+
}
|
|
1496
|
+
async function changePasswordForUserBy(config, identifier, password) {
|
|
1497
|
+
validatePassword(password, config);
|
|
1498
|
+
const queries = new AuthQueries(config);
|
|
1499
|
+
const account = await findAccountByIdentifier(queries, identifier);
|
|
1500
|
+
if (!account) {
|
|
1501
|
+
throw new UserNotFoundError();
|
|
1502
|
+
}
|
|
1503
|
+
await queries.updateAccount(account.id, {
|
|
1504
|
+
password: hash3.encode(password)
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
async function setStatusForUserBy(config, identifier, status) {
|
|
1508
|
+
const queries = new AuthQueries(config);
|
|
1509
|
+
const account = await findAccountByIdentifier(queries, identifier);
|
|
1510
|
+
if (!account) {
|
|
1511
|
+
throw new UserNotFoundError();
|
|
1512
|
+
}
|
|
1513
|
+
await queries.updateAccount(account.id, { status });
|
|
1514
|
+
}
|
|
1515
|
+
async function initiatePasswordResetForUserBy(config, identifier, expiresAfter = null, callback) {
|
|
1516
|
+
const queries = new AuthQueries(config);
|
|
1517
|
+
const account = await findAccountByIdentifier(queries, identifier);
|
|
1518
|
+
if (!account) {
|
|
1519
|
+
throw new UserNotFoundError();
|
|
1520
|
+
}
|
|
1521
|
+
if (!account.verified) {
|
|
1522
|
+
throw new EmailNotVerifiedError();
|
|
1523
|
+
}
|
|
1524
|
+
const expiry = !expiresAfter ? ms2("6h") : ms2(expiresAfter);
|
|
1525
|
+
const token = hash3.encode(account.email);
|
|
1526
|
+
const expires = new Date(Date.now() + expiry);
|
|
1527
|
+
await queries.createResetToken({
|
|
1528
|
+
accountId: account.id,
|
|
1529
|
+
token,
|
|
1530
|
+
expires
|
|
1531
|
+
});
|
|
1532
|
+
if (callback) {
|
|
1533
|
+
callback(token);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
async function resetPassword(config, email, expiresAfter = null, maxOpenRequests = null, callback) {
|
|
1537
|
+
validateEmail(email);
|
|
1538
|
+
const expiry = !expiresAfter ? ms2("6h") : ms2(expiresAfter);
|
|
1539
|
+
const maxRequests = maxOpenRequests === null ? 2 : Math.max(1, maxOpenRequests);
|
|
1540
|
+
const queries = new AuthQueries(config);
|
|
1541
|
+
const account = await queries.findAccountByEmail(email);
|
|
1542
|
+
if (!account || !account.verified) {
|
|
1543
|
+
throw new EmailNotVerifiedError();
|
|
1544
|
+
}
|
|
1545
|
+
if (!account.resettable) {
|
|
1546
|
+
throw new ResetDisabledError();
|
|
1547
|
+
}
|
|
1548
|
+
const openRequests = await queries.countActiveResetTokensForAccount(account.id);
|
|
1549
|
+
if (openRequests >= maxRequests) {
|
|
1550
|
+
throw new TooManyResetsError();
|
|
1551
|
+
}
|
|
1552
|
+
const token = hash3.encode(email);
|
|
1553
|
+
const expires = new Date(Date.now() + expiry);
|
|
1554
|
+
await queries.createResetToken({
|
|
1555
|
+
accountId: account.id,
|
|
1556
|
+
token,
|
|
1557
|
+
expires
|
|
1558
|
+
});
|
|
1559
|
+
if (callback) {
|
|
1560
|
+
callback(token);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
async function confirmResetPassword(config, token, password) {
|
|
1564
|
+
const queries = new AuthQueries(config);
|
|
1565
|
+
const reset = await queries.findResetToken(token);
|
|
1566
|
+
if (!reset) {
|
|
1567
|
+
throw new ResetNotFoundError();
|
|
1568
|
+
}
|
|
1569
|
+
if (new Date(reset.expires) < /* @__PURE__ */ new Date()) {
|
|
1570
|
+
throw new ResetExpiredError();
|
|
1571
|
+
}
|
|
1572
|
+
const account = await queries.findAccountById(reset.account_id);
|
|
1573
|
+
if (!account) {
|
|
1574
|
+
throw new UserNotFoundError();
|
|
1575
|
+
}
|
|
1576
|
+
if (!account.resettable) {
|
|
1577
|
+
throw new ResetDisabledError();
|
|
1578
|
+
}
|
|
1579
|
+
validatePassword(password, config);
|
|
1580
|
+
if (!hash3.verify(token, account.email)) {
|
|
1581
|
+
throw new InvalidTokenError();
|
|
1582
|
+
}
|
|
1583
|
+
await queries.updateAccount(account.id, {
|
|
1584
|
+
password: hash3.encode(password)
|
|
1585
|
+
});
|
|
1586
|
+
await queries.deleteResetToken(token);
|
|
1587
|
+
return { accountId: account.id, email: account.email };
|
|
1588
|
+
}
|
|
1589
|
+
async function forceLogoutForUserBy(config, identifier) {
|
|
1590
|
+
const queries = new AuthQueries(config);
|
|
1591
|
+
const account = await findAccountByIdentifier(queries, identifier);
|
|
1592
|
+
if (!account) {
|
|
1593
|
+
throw new UserNotFoundError();
|
|
1594
|
+
}
|
|
1595
|
+
await queries.incrementForceLogout(account.id);
|
|
1596
|
+
return { accountId: account.id };
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1629
1599
|
// src/auth-manager.ts
|
|
1630
1600
|
var AuthManager = class {
|
|
1631
1601
|
constructor(req, res, config) {
|
|
@@ -1817,7 +1787,7 @@ var AuthManager = class {
|
|
|
1817
1787
|
});
|
|
1818
1788
|
}
|
|
1819
1789
|
async createRememberDirective(account) {
|
|
1820
|
-
const token =
|
|
1790
|
+
const token = hash4.encode(account.email);
|
|
1821
1791
|
const duration = this.config.rememberDuration || "30d";
|
|
1822
1792
|
const expires = new Date(Date.now() + ms3(duration));
|
|
1823
1793
|
await this.queries.createRememberToken({
|
|
@@ -1855,7 +1825,7 @@ var AuthManager = class {
|
|
|
1855
1825
|
await this.activityLogger.logActivity(null, AuthActivityAction.FailedLogin, this.req, false, { email, reason: "account_not_found" });
|
|
1856
1826
|
throw new UserNotFoundError();
|
|
1857
1827
|
}
|
|
1858
|
-
if (!account.password || !
|
|
1828
|
+
if (!account.password || !hash4.verify(account.password, password)) {
|
|
1859
1829
|
await this.activityLogger.logActivity(account.id, AuthActivityAction.FailedLogin, this.req, false, { email, reason: "invalid_password" });
|
|
1860
1830
|
throw new InvalidPasswordError();
|
|
1861
1831
|
}
|
|
@@ -1967,7 +1937,7 @@ var AuthManager = class {
|
|
|
1967
1937
|
throw new EmailTakenError();
|
|
1968
1938
|
}
|
|
1969
1939
|
const finalUserId = userId || this.generateAutoUserId();
|
|
1970
|
-
const hashedPassword =
|
|
1940
|
+
const hashedPassword = hash4.encode(password);
|
|
1971
1941
|
const verified = typeof callback !== "function";
|
|
1972
1942
|
const account = await this.queries.createAccount({
|
|
1973
1943
|
userId: finalUserId,
|
|
@@ -1984,7 +1954,7 @@ var AuthManager = class {
|
|
|
1984
1954
|
return account;
|
|
1985
1955
|
}
|
|
1986
1956
|
async createConfirmationToken(account, email, callback) {
|
|
1987
|
-
const token =
|
|
1957
|
+
const token = hash4.encode(email);
|
|
1988
1958
|
const expires = new Date(Date.now() + 1e3 * 60 * 60 * 24 * 7);
|
|
1989
1959
|
await this.queries.createConfirmation({
|
|
1990
1960
|
accountId: account.id,
|
|
@@ -2118,7 +2088,7 @@ var AuthManager = class {
|
|
|
2118
2088
|
if (new Date(confirmation.expires) < /* @__PURE__ */ new Date()) {
|
|
2119
2089
|
throw new ConfirmationExpiredError();
|
|
2120
2090
|
}
|
|
2121
|
-
if (!
|
|
2091
|
+
if (!hash4.verify(token, confirmation.email)) {
|
|
2122
2092
|
throw new InvalidTokenError();
|
|
2123
2093
|
}
|
|
2124
2094
|
await this.queries.updateAccount(confirmation.account_id, {
|
|
@@ -2215,7 +2185,7 @@ var AuthManager = class {
|
|
|
2215
2185
|
if (openRequests >= maxRequests) {
|
|
2216
2186
|
throw new TooManyResetsError();
|
|
2217
2187
|
}
|
|
2218
|
-
const token =
|
|
2188
|
+
const token = hash4.encode(email);
|
|
2219
2189
|
const expires = new Date(Date.now() + expiry);
|
|
2220
2190
|
await this.queries.createResetToken({
|
|
2221
2191
|
accountId: account.id,
|
|
@@ -2257,11 +2227,11 @@ var AuthManager = class {
|
|
|
2257
2227
|
throw new ResetDisabledError();
|
|
2258
2228
|
}
|
|
2259
2229
|
this.validatePassword(password);
|
|
2260
|
-
if (!
|
|
2230
|
+
if (!hash4.verify(token, account.email)) {
|
|
2261
2231
|
throw new InvalidTokenError();
|
|
2262
2232
|
}
|
|
2263
2233
|
await this.queries.updateAccount(account.id, {
|
|
2264
|
-
password:
|
|
2234
|
+
password: hash4.encode(password)
|
|
2265
2235
|
});
|
|
2266
2236
|
if (logout) {
|
|
2267
2237
|
await this.forceLogoutForAccountById(account.id);
|
|
@@ -2289,7 +2259,7 @@ var AuthManager = class {
|
|
|
2289
2259
|
if (!account.password) {
|
|
2290
2260
|
return false;
|
|
2291
2261
|
}
|
|
2292
|
-
return
|
|
2262
|
+
return hash4.verify(account.password, password);
|
|
2293
2263
|
}
|
|
2294
2264
|
async forceLogoutForAccountById(accountId) {
|
|
2295
2265
|
await this.queries.deleteRememberTokensForAccount(accountId);
|
|
@@ -2327,6 +2297,61 @@ var AuthManager = class {
|
|
|
2327
2297
|
await this.logoutEverywhereElse();
|
|
2328
2298
|
await this.logout();
|
|
2329
2299
|
}
|
|
2300
|
+
async findAccountByIdentifier(identifier) {
|
|
2301
|
+
if (identifier.accountId !== void 0) {
|
|
2302
|
+
return await this.queries.findAccountById(identifier.accountId);
|
|
2303
|
+
} else if (identifier.email !== void 0) {
|
|
2304
|
+
return await this.queries.findAccountByEmail(identifier.email);
|
|
2305
|
+
} else if (identifier.userId !== void 0) {
|
|
2306
|
+
return await this.queries.findAccountByUserId(identifier.userId);
|
|
2307
|
+
}
|
|
2308
|
+
return null;
|
|
2309
|
+
}
|
|
2310
|
+
// Admin/standalone functions (delegated to auth-functions.js)
|
|
2311
|
+
async createUser(credentials, userId, callback) {
|
|
2312
|
+
return createUser(this.config, credentials, userId, callback);
|
|
2313
|
+
}
|
|
2314
|
+
async deleteUserBy(identifier) {
|
|
2315
|
+
return deleteUserBy(this.config, identifier);
|
|
2316
|
+
}
|
|
2317
|
+
async addRoleForUserBy(identifier, role) {
|
|
2318
|
+
return addRoleForUserBy(this.config, identifier, role);
|
|
2319
|
+
}
|
|
2320
|
+
async removeRoleForUserBy(identifier, role) {
|
|
2321
|
+
return removeRoleForUserBy(this.config, identifier, role);
|
|
2322
|
+
}
|
|
2323
|
+
async hasRoleForUserBy(identifier, role) {
|
|
2324
|
+
return hasRoleForUserBy(this.config, identifier, role);
|
|
2325
|
+
}
|
|
2326
|
+
async changePasswordForUserBy(identifier, password) {
|
|
2327
|
+
return changePasswordForUserBy(this.config, identifier, password);
|
|
2328
|
+
}
|
|
2329
|
+
async setStatusForUserBy(identifier, status) {
|
|
2330
|
+
return setStatusForUserBy(this.config, identifier, status);
|
|
2331
|
+
}
|
|
2332
|
+
async initiatePasswordResetForUserBy(identifier, expiresAfter, callback) {
|
|
2333
|
+
return initiatePasswordResetForUserBy(this.config, identifier, expiresAfter, callback);
|
|
2334
|
+
}
|
|
2335
|
+
async forceLogoutForUserBy(identifier) {
|
|
2336
|
+
const result = await forceLogoutForUserBy(this.config, identifier);
|
|
2337
|
+
if (this.getId() === result.accountId) {
|
|
2338
|
+
this.req.session.auth.shouldForceLogout = true;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Log in as another user (admin function).
|
|
2343
|
+
* Creates a new session as the target user without requiring their password.
|
|
2344
|
+
*
|
|
2345
|
+
* @param identifier - Find user by accountId, email, or userId
|
|
2346
|
+
* @throws {UserNotFoundError} No account matches the identifier
|
|
2347
|
+
*/
|
|
2348
|
+
async loginAsUserBy(identifier) {
|
|
2349
|
+
const account = await this.findAccountByIdentifier(identifier);
|
|
2350
|
+
if (!account) {
|
|
2351
|
+
throw new UserNotFoundError();
|
|
2352
|
+
}
|
|
2353
|
+
await this.onLoginSuccessful(account, false);
|
|
2354
|
+
}
|
|
2330
2355
|
};
|
|
2331
2356
|
|
|
2332
2357
|
// src/middleware.ts
|
|
@@ -2334,9 +2359,7 @@ function createAuthMiddleware(config) {
|
|
|
2334
2359
|
return async (req, res, next) => {
|
|
2335
2360
|
try {
|
|
2336
2361
|
const authManager = new AuthManager(req, res, config);
|
|
2337
|
-
const authAdminManager = new AuthAdminManager(req, res, config, authManager);
|
|
2338
2362
|
req.auth = authManager;
|
|
2339
|
-
req.authAdmin = authAdminManager;
|
|
2340
2363
|
await authManager.resyncSession();
|
|
2341
2364
|
await authManager.processRememberDirective();
|
|
2342
2365
|
next();
|
|
@@ -2544,6 +2567,62 @@ async function getAuthTableStats(config) {
|
|
|
2544
2567
|
expiredTwoFactorTokens: parseInt(expiredTwoFactorTokensResult.rows[0]?.count || "0")
|
|
2545
2568
|
};
|
|
2546
2569
|
}
|
|
2570
|
+
|
|
2571
|
+
// src/auth-context.ts
|
|
2572
|
+
function createAuthContext(config) {
|
|
2573
|
+
return {
|
|
2574
|
+
createUser: (credentials, userId, callback) => createUser(config, credentials, userId, callback),
|
|
2575
|
+
register: (email, password, userId, callback) => register(config, email, password, userId, callback),
|
|
2576
|
+
deleteUserBy: (identifier) => deleteUserBy(config, identifier),
|
|
2577
|
+
addRoleForUserBy: (identifier, role) => addRoleForUserBy(config, identifier, role),
|
|
2578
|
+
removeRoleForUserBy: (identifier, role) => removeRoleForUserBy(config, identifier, role),
|
|
2579
|
+
hasRoleForUserBy: (identifier, role) => hasRoleForUserBy(config, identifier, role),
|
|
2580
|
+
changePasswordForUserBy: (identifier, password) => changePasswordForUserBy(config, identifier, password),
|
|
2581
|
+
setStatusForUserBy: (identifier, status) => setStatusForUserBy(config, identifier, status),
|
|
2582
|
+
initiatePasswordResetForUserBy: (identifier, expiresAfter, callback) => initiatePasswordResetForUserBy(config, identifier, expiresAfter, callback),
|
|
2583
|
+
resetPassword: (email, expiresAfter, maxOpenRequests, callback) => resetPassword(config, email, expiresAfter, maxOpenRequests, callback),
|
|
2584
|
+
confirmResetPassword: (token, password) => confirmResetPassword(config, token, password),
|
|
2585
|
+
forceLogoutForUserBy: (identifier) => forceLogoutForUserBy(config, identifier)
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
// src/user-roles.ts
|
|
2590
|
+
async function findAccountByIdentifier2(queries, identifier) {
|
|
2591
|
+
let account = null;
|
|
2592
|
+
if (identifier.accountId !== void 0) {
|
|
2593
|
+
account = await queries.findAccountById(identifier.accountId);
|
|
2594
|
+
} else if (identifier.email !== void 0) {
|
|
2595
|
+
account = await queries.findAccountByEmail(identifier.email);
|
|
2596
|
+
} else if (identifier.userId !== void 0) {
|
|
2597
|
+
account = await queries.findAccountByUserId(identifier.userId);
|
|
2598
|
+
}
|
|
2599
|
+
if (!account) {
|
|
2600
|
+
throw new UserNotFoundError();
|
|
2601
|
+
}
|
|
2602
|
+
return account;
|
|
2603
|
+
}
|
|
2604
|
+
async function addRoleToUser(config, identifier, role) {
|
|
2605
|
+
const queries = new AuthQueries(config);
|
|
2606
|
+
const account = await findAccountByIdentifier2(queries, identifier);
|
|
2607
|
+
const rolemask = account.rolemask | role;
|
|
2608
|
+
await queries.updateAccount(account.id, { rolemask });
|
|
2609
|
+
}
|
|
2610
|
+
async function removeRoleFromUser(config, identifier, role) {
|
|
2611
|
+
const queries = new AuthQueries(config);
|
|
2612
|
+
const account = await findAccountByIdentifier2(queries, identifier);
|
|
2613
|
+
const rolemask = account.rolemask & ~role;
|
|
2614
|
+
await queries.updateAccount(account.id, { rolemask });
|
|
2615
|
+
}
|
|
2616
|
+
async function setUserRoles(config, identifier, rolemask) {
|
|
2617
|
+
const queries = new AuthQueries(config);
|
|
2618
|
+
const account = await findAccountByIdentifier2(queries, identifier);
|
|
2619
|
+
await queries.updateAccount(account.id, { rolemask });
|
|
2620
|
+
}
|
|
2621
|
+
async function getUserRoles(config, identifier) {
|
|
2622
|
+
const queries = new AuthQueries(config);
|
|
2623
|
+
const account = await findAccountByIdentifier2(queries, identifier);
|
|
2624
|
+
return account.rolemask;
|
|
2625
|
+
}
|
|
2547
2626
|
export {
|
|
2548
2627
|
ActivityLogger,
|
|
2549
2628
|
AuthActivityAction,
|
|
@@ -2579,13 +2658,18 @@ export {
|
|
|
2579
2658
|
UserInactiveError,
|
|
2580
2659
|
UserNotFoundError,
|
|
2581
2660
|
UserNotLoggedInError,
|
|
2661
|
+
addRoleToUser,
|
|
2662
|
+
auth_functions_exports as authFunctions,
|
|
2582
2663
|
cleanupExpiredTokens,
|
|
2664
|
+
createAuthContext,
|
|
2583
2665
|
createAuthMiddleware,
|
|
2584
2666
|
createAuthTables,
|
|
2585
|
-
createUser,
|
|
2586
2667
|
dropAuthTables,
|
|
2587
2668
|
getAuthTableStats,
|
|
2669
|
+
getUserRoles,
|
|
2588
2670
|
isValidEmail,
|
|
2671
|
+
removeRoleFromUser,
|
|
2672
|
+
setUserRoles,
|
|
2589
2673
|
validateEmail
|
|
2590
2674
|
};
|
|
2591
2675
|
//# sourceMappingURL=index.js.map
|