najm-auth 1.1.39 → 1.1.42

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.js CHANGED
@@ -67,9 +67,9 @@ var permissionsTable = pgTable("permissions", {
67
67
  });
68
68
  var tokensTable = pgTable("tokens", {
69
69
  ...baseFields(10),
70
- userId: text("user_id").references(() => usersTable.id, { onDelete: "cascade" }).unique().notNull(),
70
+ userId: text("user_id").references(() => usersTable.id, { onDelete: "cascade" }).notNull(),
71
71
  token: text("token").notNull(),
72
- tokenFamily: text("token_family"),
72
+ tokenFamily: text("token_family").notNull().unique(),
73
73
  previousHash: text("previous_hash"),
74
74
  previousValidUntil: timestamp("previous_valid_until", { mode: "string" }),
75
75
  previousUsedAt: timestamp("previous_used_at", { mode: "string" }),
@@ -77,6 +77,7 @@ var tokensTable = pgTable("tokens", {
77
77
  status: tokenStatusEnum("status").default("active"),
78
78
  expiresAt: timestamp("expires_at", { mode: "string" }).notNull()
79
79
  }, (table) => ({
80
+ userIdIdx: index("tokens_user_id_idx").on(table.userId),
80
81
  expiresAtIdx: index("tokens_expires_at_idx").on(table.expiresAt)
81
82
  }));
82
83
  var rolePermissionsTable = pgTable("role_permissions", {
@@ -134,9 +135,9 @@ var permissionsTable2 = sqliteTable("permissions", {
134
135
  });
135
136
  var tokensTable2 = sqliteTable("tokens", {
136
137
  ...baseFields2(10),
137
- userId: text2("user_id").references(() => usersTable2.id, { onDelete: "cascade" }).unique().notNull(),
138
+ userId: text2("user_id").references(() => usersTable2.id, { onDelete: "cascade" }).notNull(),
138
139
  token: text2("token").notNull(),
139
- tokenFamily: text2("token_family"),
140
+ tokenFamily: text2("token_family").notNull().unique(),
140
141
  previousHash: text2("previous_hash"),
141
142
  previousValidUntil: text2("previous_valid_until"),
142
143
  previousUsedAt: text2("previous_used_at"),
@@ -144,6 +145,7 @@ var tokensTable2 = sqliteTable("tokens", {
144
145
  status: text2("status").$type().default("active"),
145
146
  expiresAt: text2("expires_at").notNull()
146
147
  }, (table) => ({
148
+ userIdIdx: index2("tokens_user_id_idx").on(table.userId),
147
149
  expiresAtIdx: index2("tokens_expires_at_idx").on(table.expiresAt)
148
150
  }));
149
151
  var rolePermissionsTable2 = sqliteTable("role_permissions", {
@@ -161,87 +163,6 @@ var authSchema2 = {
161
163
  rolePermissions: rolePermissionsTable2
162
164
  };
163
165
 
164
- // src/schema/mysql.ts
165
- import { mysqlTable, varchar, boolean as boolean2, timestamp as timestamp2, mysqlEnum, primaryKey as primaryKey2, int, index as index3 } from "drizzle-orm/mysql-core";
166
- import { sql as sql3 } from "drizzle-orm";
167
- import { nanoid as nanoid3 } from "nanoid";
168
- var baseFields3 = /* @__PURE__ */ __name((idLength = 5) => ({
169
- id: varchar("id", { length: 21 }).primaryKey().$defaultFn(() => nanoid3(idLength)),
170
- createdAt: timestamp2("created_at", { mode: "string" }).defaultNow(),
171
- updatedAt: timestamp2("updated_at", { mode: "string" }).defaultNow().$onUpdate(() => sql3`CURRENT_TIMESTAMP`)
172
- }), "baseFields");
173
- var rolesTable3 = mysqlTable("roles", {
174
- ...baseFields3(5),
175
- name: varchar("name", { length: 255 }).notNull(),
176
- description: varchar("description", { length: 1e3 })
177
- });
178
- var usersTable3 = mysqlTable("users", {
179
- ...baseFields3(8),
180
- name: varchar("name", { length: 255 }),
181
- email: varchar("email", { length: 255 }).notNull().unique(),
182
- emailVerified: boolean2("email_verified").default(false),
183
- phone: varchar("phone", { length: 255 }).unique(),
184
- phoneVerified: boolean2("phone_verified").default(false),
185
- password: varchar("password", { length: 255 }).notNull(),
186
- image: varchar("image", { length: 255 }).default("noavatar.png"),
187
- status: mysqlEnum("status", [...USER_STATUS]).default("pending"),
188
- roleId: varchar("role_id", { length: 21 }).references(() => rolesTable3.id),
189
- lastLogin: timestamp2("last_login", { mode: "string" }),
190
- failedLoginAttempts: int("failed_login_attempts").default(0),
191
- lockoutUntil: timestamp2("lockout_until", { mode: "string" })
192
- }, (table) => ({
193
- roleIdx: index3("users_role_id_idx").on(table.roleId)
194
- }));
195
- var permissionsTable3 = mysqlTable("permissions", {
196
- ...baseFields3(5),
197
- name: varchar("name", { length: 255 }).notNull().unique(),
198
- description: varchar("description", { length: 1e3 }),
199
- resource: varchar("resource", { length: 255 }).notNull(),
200
- action: varchar("action", { length: 255 }).notNull()
201
- });
202
- var tokensTable3 = mysqlTable("tokens", {
203
- ...baseFields3(10),
204
- userId: varchar("user_id", { length: 21 }).references(() => usersTable3.id, { onDelete: "cascade" }).unique().notNull(),
205
- token: varchar("token", { length: 500 }).notNull(),
206
- tokenFamily: varchar("token_family", { length: 16 }),
207
- previousHash: varchar("previous_hash", { length: 500 }),
208
- previousValidUntil: timestamp2("previous_valid_until", { mode: "string" }),
209
- previousUsedAt: timestamp2("previous_used_at", { mode: "string" }),
210
- type: mysqlEnum("type", [...TOKEN_TYPE]).default("refresh"),
211
- status: mysqlEnum("status", [...TOKEN_STATUS]).default("active"),
212
- expiresAt: timestamp2("expires_at", { mode: "string" }).notNull()
213
- }, (table) => ({
214
- expiresAtIdx: index3("tokens_expires_at_idx").on(table.expiresAt)
215
- }));
216
- var rolePermissionsTable3 = mysqlTable("role_permissions", {
217
- roleId: varchar("role_id", { length: 21 }).notNull().references(() => rolesTable3.id, { onDelete: "cascade" }),
218
- permissionId: varchar("permission_id", { length: 21 }).notNull().references(() => permissionsTable3.id, { onDelete: "cascade" }),
219
- createdAt: timestamp2("created_at", { mode: "string" }).defaultNow()
220
- }, (table) => ({
221
- pk: primaryKey2({ columns: [table.roleId, table.permissionId] })
222
- }));
223
- var authSchema3 = {
224
- users: usersTable3,
225
- tokens: tokensTable3,
226
- roles: rolesTable3,
227
- permissions: permissionsTable3,
228
- rolePermissions: rolePermissionsTable3
229
- };
230
-
231
- // src/auth/index.ts
232
- var auth_exports = {};
233
- __export(auth_exports, {
234
- AUTH_MODULE: () => AUTH_MODULE,
235
- AuthController: () => AuthController,
236
- AuthGuard: () => AuthGuard,
237
- AuthResolver: () => AuthResolver,
238
- AuthService: () => AuthService,
239
- CookieManager: () => CookieManager,
240
- EncryptionService: () => EncryptionService,
241
- isAuth: () => isAuth,
242
- runAsUser: () => runAsUser
243
- });
244
-
245
166
  // src/auth/EncryptionService.ts
246
167
  import { Inject, Injectable } from "najm-core";
247
168
  import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
@@ -452,15 +373,16 @@ CookieManager = __decorate2([
452
373
  ], CookieManager);
453
374
 
454
375
  // src/auth/AuthController.ts
455
- import { Controller } from "najm-core";
456
- import { Get, Post, ResMsg } from "najm-core";
457
- import { Body, User as User2, Headers } from "najm-core";
376
+ import { Controller as Controller2 } from "najm-core";
377
+ import { Get as Get2, Post as Post2, ResMsg as ResMsg2 } from "najm-core";
378
+ import { Body as Body2, User as User3, Headers } from "najm-core";
458
379
 
459
380
  // src/auth/AuthService.ts
460
381
  import { Injectable as Injectable7, Inject as Inject8 } from "najm-core";
461
382
  import { Err as Err6, Log } from "najm-core";
462
383
  import { I18n as I18n5, I18nService as I18nService2 } from "najm-i18n";
463
- import { EmailService, passwordResetTemplate } from "najm-email";
384
+ import { EmailService, passwordResetTemplate, accountInviteTemplate } from "najm-email";
385
+ import { nanoid as nanoid5 } from "nanoid";
464
386
 
465
387
  // src/users/UserService.ts
466
388
  import { Injectable as Injectable5, Inject as Inject5 } from "najm-core";
@@ -469,7 +391,7 @@ import { I18nService } from "najm-i18n";
469
391
  import { I18n as I18n3 } from "najm-i18n";
470
392
 
471
393
  // src/users/UserRepository.ts
472
- import { eq as eq2, ne, sql as sql4 } from "drizzle-orm";
394
+ import { eq as eq2, ne, sql as sql3 } from "drizzle-orm";
473
395
  import { Repository, Inject as Inject3 } from "najm-core";
474
396
  import { DB } from "najm-database";
475
397
 
@@ -639,7 +561,7 @@ var UserRepository = class UserRepository2 {
639
561
  return updatedUser;
640
562
  }
641
563
  async incrementFailedAttempts(id) {
642
- const [updatedUser] = await this.db.update(this.users).set({ failedLoginAttempts: sql4`coalesce(${this.users.failedLoginAttempts}, 0) + 1` }).where(eq2(this.users.id, id)).returning();
564
+ const [updatedUser] = await this.db.update(this.users).set({ failedLoginAttempts: sql3`coalesce(${this.users.failedLoginAttempts}, 0) + 1` }).where(eq2(this.users.id, id)).returning();
643
565
  return updatedUser;
644
566
  }
645
567
  async resetFailedAttempts(id) {
@@ -1044,7 +966,7 @@ RoleService = __decorate7([
1044
966
  ], RoleService);
1045
967
 
1046
968
  // src/users/UserService.ts
1047
- import { nanoid as nanoid4 } from "nanoid";
969
+ import { nanoid as nanoid3 } from "nanoid";
1048
970
 
1049
971
  // src/shared/index.ts
1050
972
  import * as fs from "fs/promises";
@@ -1254,7 +1176,7 @@ var UserService = class UserService2 {
1254
1176
  Err4("Password is required");
1255
1177
  }
1256
1178
  this.userValidator.validatePasswordStrength(password);
1257
- let userId = id || nanoid4(10);
1179
+ let userId = id || nanoid3(10);
1258
1180
  await this.userValidator.checkEmailUnique(data.email);
1259
1181
  await this.userValidator.checkUserIdIsUnique(userId);
1260
1182
  const hashedPassword = await this.encryptionService.hashPassword(password);
@@ -1336,7 +1258,7 @@ var UserService = class UserService2 {
1336
1258
  await this.delete(existingUser.id);
1337
1259
  }
1338
1260
  const newAdminUser = await this.create({
1339
- id: nanoid4(10),
1261
+ id: nanoid3(10),
1340
1262
  // Random ID instead of predictable 'USR00'
1341
1263
  name: config.name || "System Administrator",
1342
1264
  email: config.email,
@@ -1378,10 +1300,10 @@ import { I18n as I18n4 } from "najm-i18n";
1378
1300
  import { CacheService } from "najm-cache";
1379
1301
  import { createHash } from "crypto";
1380
1302
  import jwt from "jsonwebtoken";
1381
- import { nanoid as nanoid5 } from "nanoid";
1303
+ import { nanoid as nanoid4 } from "nanoid";
1382
1304
 
1383
1305
  // src/tokens/TokenRepository.ts
1384
- import { and, eq as eq4, isNull } from "drizzle-orm";
1306
+ import { and, eq as eq4, isNull, lt } from "drizzle-orm";
1385
1307
  import { Repository as Repository3, Inject as Inject6 } from "najm-core";
1386
1308
  import { DB as DB3 } from "najm-database";
1387
1309
  var __decorate9 = function(decorators, target, key, desc) {
@@ -1410,12 +1332,17 @@ var TokenRepository = class TokenRepository2 {
1410
1332
  get q() {
1411
1333
  return this.queryHelper ??= new AuthQueries(this.db, this.schema);
1412
1334
  }
1335
+ /**
1336
+ * Upsert the refresh-token row for a session, keyed on `tokenFamily` (the
1337
+ * per-login session identifier, unique). A brand-new login inserts a fresh
1338
+ * family row; a refresh rotation updates only that family's row, leaving the
1339
+ * user's other sessions untouched.
1340
+ */
1413
1341
  async storeRefreshToken(tokenData) {
1414
1342
  return await this.db.insert(this.tokens).values(tokenData).onConflictDoUpdate({
1415
- target: this.tokens.userId,
1343
+ target: this.tokens.tokenFamily,
1416
1344
  set: {
1417
1345
  token: tokenData.token,
1418
- tokenFamily: tokenData.tokenFamily,
1419
1346
  expiresAt: tokenData.expiresAt,
1420
1347
  previousHash: tokenData.previousHash ?? null,
1421
1348
  previousValidUntil: tokenData.previousValidUntil ?? null,
@@ -1424,27 +1351,37 @@ var TokenRepository = class TokenRepository2 {
1424
1351
  }).returning();
1425
1352
  }
1426
1353
  /**
1427
- * Claim the previous-token grace slot. Conditional on BOTH the stored
1428
- * previousHash still matching the presented token AND previousUsedAt being
1429
- * NULL. Gating on the hash (not just the flag) closes the rotation race: the
1430
- * winner's rotation rewrites previousHash via storeRefreshToken, so a loser
1431
- * whose UPDATE lands after that rotation no longer matches and gets zero
1432
- * rows — exactly one caller ever claims the slot.
1354
+ * Claim the previous-token grace slot for a single family. Conditional on
1355
+ * BOTH the stored previousHash still matching the presented token AND
1356
+ * previousUsedAt being NULL. Gating on the hash (not just the flag) closes
1357
+ * the rotation race: the winner's rotation rewrites previousHash via
1358
+ * storeRefreshToken (and resets previousUsedAt to NULL), so a loser whose
1359
+ * UPDATE lands after that rotation no longer matches and gets zero rows —
1360
+ * exactly one caller ever claims the slot.
1433
1361
  */
1434
- async markPreviousUsed(userId, previousHash) {
1435
- return await this.db.update(this.tokens).set({ previousUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and(eq4(this.tokens.userId, userId), eq4(this.tokens.previousHash, previousHash), isNull(this.tokens.previousUsedAt))).returning();
1362
+ async markPreviousUsed(tokenFamily, previousHash) {
1363
+ return await this.db.update(this.tokens).set({ previousUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and(eq4(this.tokens.tokenFamily, tokenFamily), eq4(this.tokens.previousHash, previousHash), isNull(this.tokens.previousUsedAt))).returning();
1436
1364
  }
1437
- async getRefreshTokenWithFamily(userId) {
1438
- const [token] = await this.db.select().from(this.tokens).where(eq4(this.tokens.userId, userId));
1365
+ /** Look up a single session's token row by its family identifier. */
1366
+ async getByFamily(tokenFamily) {
1367
+ const [token] = await this.db.select().from(this.tokens).where(eq4(this.tokens.tokenFamily, tokenFamily));
1439
1368
  return token ?? null;
1440
1369
  }
1441
- async revokeToken(userId) {
1442
- const [deletedToken] = await this.db.delete(this.tokens).where(eq4(this.tokens.userId, userId)).returning();
1443
- return deletedToken;
1444
- }
1445
- async revokeByFamily(tokenFamily) {
1370
+ /** Revoke a single session (one family). */
1371
+ async revokeFamily(tokenFamily) {
1446
1372
  return this.db.delete(this.tokens).where(eq4(this.tokens.tokenFamily, tokenFamily)).returning();
1447
1373
  }
1374
+ /** Revoke every session for a user (password change/reset, logout-all). */
1375
+ async revokeAllForUser(userId) {
1376
+ return this.db.delete(this.tokens).where(eq4(this.tokens.userId, userId)).returning();
1377
+ }
1378
+ /**
1379
+ * Opportunistic cleanup: with one row per family (no unique userId), expired
1380
+ * and abandoned sessions accumulate. Delete every expired row.
1381
+ */
1382
+ async deleteExpired() {
1383
+ return this.db.delete(this.tokens).where(lt(this.tokens.expiresAt, (/* @__PURE__ */ new Date()).toISOString())).returning();
1384
+ }
1448
1385
  async isUserExists(userId) {
1449
1386
  const [user] = await this.db.select({ id: this.users.id }).from(this.users).where(eq4(this.users.id, userId)).limit(1);
1450
1387
  return !!user;
@@ -1559,19 +1496,22 @@ var TokenService = class TokenService2 {
1559
1496
  Err5(this.t("errors.tokenVerificationFailed"));
1560
1497
  }
1561
1498
  const sessionKey = this.sessionVersionKey(payload.userId);
1562
- let activeSessionVersion;
1563
- if (payload.jti) {
1564
- const [blacklisted, sessionVersion] = await this.getCacheValues([
1565
- `${this.blacklistPrefix}${payload.jti}`,
1566
- sessionKey
1567
- ]);
1568
- if (blacklisted !== null) {
1569
- Err5(this.t("errors.tokenRevoked"));
1570
- }
1571
- activeSessionVersion = this.parseSessionVersion(sessionVersion);
1572
- } else {
1573
- activeSessionVersion = this.parseSessionVersion(await this.cache.get(sessionKey));
1499
+ const blacklistKey = payload.jti ? `${this.blacklistPrefix}${payload.jti}` : null;
1500
+ const familyKey = payload.tokenFamily ? this.revokedFamilyKey(payload.tokenFamily) : null;
1501
+ const keys = [
1502
+ ...blacklistKey ? [blacklistKey] : [],
1503
+ sessionKey,
1504
+ ...familyKey ? [familyKey] : []
1505
+ ];
1506
+ const values = await this.getCacheValues(keys);
1507
+ const valueByKey = new Map(keys.map((key, i) => [key, values[i]]));
1508
+ if (blacklistKey && valueByKey.get(blacklistKey) != null) {
1509
+ Err5(this.t("errors.tokenRevoked"));
1574
1510
  }
1511
+ if (familyKey && valueByKey.get(familyKey) != null) {
1512
+ Err5(this.t("errors.tokenRevoked"));
1513
+ }
1514
+ const activeSessionVersion = this.parseSessionVersion(valueByKey.get(sessionKey) ?? null);
1575
1515
  const tokenSessionVersion = payload.sessionVersion ?? 0;
1576
1516
  if (tokenSessionVersion !== activeSessionVersion) {
1577
1517
  Err5(this.t("errors.tokenRevoked"));
@@ -1579,15 +1519,19 @@ var TokenService = class TokenService2 {
1579
1519
  return payload;
1580
1520
  }
1581
1521
  verifyRefreshToken(token) {
1522
+ let decoded;
1582
1523
  try {
1583
- const decoded = jwt.verify(token, this.config.jwt.refreshSecret);
1584
- if (decoded.type && decoded.type !== "refresh") {
1585
- Err5(this.t("errors.tokenVerificationFailed"));
1586
- }
1587
- return decoded.userId;
1524
+ decoded = jwt.verify(token, this.config.jwt.refreshSecret);
1588
1525
  } catch {
1589
1526
  Err5(this.t("errors.tokenVerificationFailed"));
1590
1527
  }
1528
+ if (decoded.type && decoded.type !== "refresh") {
1529
+ Err5(this.t("errors.tokenVerificationFailed"));
1530
+ }
1531
+ if (!decoded.tokenFamily) {
1532
+ Err5(this.t("errors.tokenVerificationFailed"));
1533
+ }
1534
+ return { userId: decoded.userId, tokenFamily: decoded.tokenFamily };
1591
1535
  }
1592
1536
  static PREVIOUS_GRACE_SECONDS = 120;
1593
1537
  /**
@@ -1606,9 +1550,9 @@ var TokenService = class TokenService2 {
1606
1550
  if (!refreshToken) {
1607
1551
  Err5(this.t("errors.refreshTokenMissing"));
1608
1552
  }
1609
- const userId = this.verifyRefreshToken(refreshToken);
1610
- const stored = await this.tokenRepository.getRefreshTokenWithFamily(userId);
1611
- if (!stored) {
1553
+ const { userId, tokenFamily } = this.verifyRefreshToken(refreshToken);
1554
+ const stored = await this.tokenRepository.getByFamily(tokenFamily);
1555
+ if (!stored || stored.userId !== userId) {
1612
1556
  Err5(this.t("errors.refreshTokenInvalid"));
1613
1557
  }
1614
1558
  const presentedHash = this.hashToken(refreshToken);
@@ -1653,7 +1597,7 @@ var TokenService = class TokenService2 {
1653
1597
  return jwt.decode(token);
1654
1598
  }
1655
1599
  async signAccessToken(data) {
1656
- const jti = nanoid5(16);
1600
+ const jti = nanoid4(16);
1657
1601
  const sessionVersion = await this.getUserSessionVersion(data.userId);
1658
1602
  const expiresAt = this.expiresAt(this.config.jwt.accessExpiresIn);
1659
1603
  if (sessionVersion > 0) {
@@ -1670,30 +1614,33 @@ var TokenService = class TokenService2 {
1670
1614
  return (await this.signAccessToken(data)).token;
1671
1615
  }
1672
1616
  /**
1673
- * Generate refresh token with unique jti
1617
+ * Generate refresh token with unique jti. The token carries its session's
1618
+ * family so rotation/revocation can target a single session.
1674
1619
  */
1675
1620
  signRefreshToken(data) {
1676
- const jti = nanoid5(16);
1621
+ const jti = nanoid4(16);
1677
1622
  const expiresAt = this.expiresAt(this.config.jwt.refreshExpiresIn);
1678
1623
  const token = jwt.sign({ ...data, jti, type: "refresh", exp: expiresAt }, this.config.jwt.refreshSecret);
1679
1624
  return { token, expiresAt };
1680
1625
  }
1681
1626
  generateRefreshToken(data) {
1682
- return this.signRefreshToken(data).token;
1627
+ return this.signRefreshToken({ userId: data.userId, tokenFamily: data.tokenFamily ?? nanoid4(16) }).token;
1683
1628
  }
1684
1629
  async generateTokens(userId, tokenFamily) {
1685
- const family = tokenFamily ?? nanoid5(16);
1630
+ const family = tokenFamily ?? nanoid4(16);
1686
1631
  const { roleName, permissions } = await this.tokenRepository.getRoleAndPermissions(userId);
1687
1632
  const accessTokenData = {
1688
1633
  userId,
1689
1634
  roles: roleName ? [roleName] : [],
1690
- permissions: permissions ?? []
1635
+ permissions: permissions ?? [],
1636
+ tokenFamily: family
1691
1637
  };
1692
1638
  const access = await this.signAccessToken(accessTokenData);
1693
- const refresh = this.signRefreshToken({ userId });
1639
+ const refresh = this.signRefreshToken({ userId, tokenFamily: family });
1694
1640
  await this.storeRefreshToken(userId, refresh.token, family);
1695
1641
  return {
1696
1642
  userId,
1643
+ tokenFamily: family,
1697
1644
  roles: accessTokenData.roles,
1698
1645
  permissions: accessTokenData.permissions,
1699
1646
  accessToken: access.token,
@@ -1743,7 +1690,7 @@ var TokenService = class TokenService2 {
1743
1690
  async storeRefreshToken(userId, refreshToken, tokenFamily) {
1744
1691
  const expireInSecond = timestring2(this.config.jwt.refreshExpiresIn, "s");
1745
1692
  const hashedToken = this.hashToken(refreshToken);
1746
- const existing = await this.tokenRepository.getRefreshTokenWithFamily(userId);
1693
+ const existing = await this.tokenRepository.getByFamily(tokenFamily);
1747
1694
  const previousHash = existing?.token ?? null;
1748
1695
  const previousValidUntil = previousHash ? new Date(Date.now() + TokenService_1.PREVIOUS_GRACE_SECONDS * 1e3).toISOString() : null;
1749
1696
  await this.tokenRepository.storeRefreshToken({
@@ -1765,28 +1712,45 @@ var TokenService = class TokenService2 {
1765
1712
  if (!refreshToken) {
1766
1713
  Err5(this.t("errors.refreshTokenMissing"));
1767
1714
  }
1768
- const userId = this.verifyRefreshToken(refreshToken);
1769
- const stored = await this.tokenRepository.getRefreshTokenWithFamily(userId);
1770
- if (!stored) {
1715
+ const { userId, tokenFamily } = this.verifyRefreshToken(refreshToken);
1716
+ const stored = await this.tokenRepository.getByFamily(tokenFamily);
1717
+ if (!stored || stored.userId !== userId) {
1771
1718
  Err5(this.t("errors.refreshTokenInvalid"));
1772
1719
  }
1773
1720
  const presentedHash = this.hashToken(refreshToken);
1774
1721
  if (presentedHash === stored.token) {
1775
- return this.generateTokens(userId, stored.tokenFamily ?? void 0);
1722
+ return this.generateTokens(userId, tokenFamily);
1776
1723
  }
1777
1724
  const canRecover = stored.previousHash && presentedHash === stored.previousHash && stored.previousValidUntil && new Date(stored.previousValidUntil).getTime() > Date.now() && !stored.previousUsedAt;
1778
1725
  if (canRecover) {
1779
- const claimed = await this.tokenRepository.markPreviousUsed(userId, presentedHash);
1726
+ const claimed = await this.tokenRepository.markPreviousUsed(tokenFamily, presentedHash);
1780
1727
  if (!claimed?.length) {
1781
1728
  Err5(this.t("errors.refreshTokenInvalid"));
1782
1729
  }
1783
- return this.generateTokens(userId, stored.tokenFamily ?? void 0);
1730
+ return this.generateTokens(userId, tokenFamily);
1784
1731
  }
1785
- await this.revokeSuspectRefreshFamily(userId, stored.tokenFamily ?? null);
1732
+ await this.revokeSuspectRefreshFamily(userId, tokenFamily);
1786
1733
  Err5(this.t("errors.refreshTokenInvalid"));
1787
1734
  }
1788
- async revokeToken(userId) {
1789
- return this.tokenRepository.revokeToken(userId);
1735
+ /** Revoke every refresh session for a user (password change/reset, logout-all). */
1736
+ async revokeAllForUser(userId) {
1737
+ return this.tokenRepository.revokeAllForUser(userId);
1738
+ }
1739
+ /** Revoke a single refresh session (one family). */
1740
+ async revokeFamily(tokenFamily) {
1741
+ await this.markFamilyRevoked(tokenFamily);
1742
+ return this.tokenRepository.revokeFamily(tokenFamily);
1743
+ }
1744
+ /**
1745
+ * Opportunistic cleanup of expired/abandoned sessions. With one row per
1746
+ * family (no unique userId), abandoned logins would otherwise accumulate.
1747
+ * Best-effort — never let cleanup failure break the calling flow.
1748
+ */
1749
+ async deleteExpiredSessions() {
1750
+ try {
1751
+ await this.tokenRepository.deleteExpired();
1752
+ } catch {
1753
+ }
1790
1754
  }
1791
1755
  async invalidateUserAccessTokens(userId) {
1792
1756
  const nextVersion = await this.getUserSessionVersion(userId) + 1;
@@ -1802,47 +1766,135 @@ var TokenService = class TokenService2 {
1802
1766
  }
1803
1767
  return user;
1804
1768
  }
1769
+ get revokedFamilyPrefix() {
1770
+ return "auth:revoked-family:";
1771
+ }
1772
+ revokedFamilyKey(tokenFamily) {
1773
+ return `${this.revokedFamilyPrefix}${tokenFamily}`;
1774
+ }
1775
+ /**
1776
+ * Mark a family as revoked in cache for the access-token TTL, so every
1777
+ * access token minted for that family (not just the presented one) is
1778
+ * rejected by verifyAccessToken until it would have expired anyway.
1779
+ */
1780
+ async markFamilyRevoked(tokenFamily) {
1781
+ await this.cache.set(this.revokedFamilyKey(tokenFamily), "1", this.accessTokenTtlMs());
1782
+ }
1783
+ /**
1784
+ * Revoke only the suspect family — NOT the whole user. Bumping the global
1785
+ * per-user session version here would kill every device's access tokens on a
1786
+ * single family's reuse detection. Instead drop the family's refresh row and
1787
+ * mark the family revoked so its access tokens stop verifying.
1788
+ */
1805
1789
  async revokeSuspectRefreshFamily(userId, tokenFamily) {
1806
- await this.invalidateUserAccessTokens(userId);
1807
1790
  if (tokenFamily) {
1808
- await this.tokenRepository.revokeByFamily(tokenFamily);
1791
+ await this.revokeFamily(tokenFamily);
1809
1792
  return;
1810
1793
  }
1811
- await this.revokeToken(userId);
1794
+ await this.invalidateUserAccessTokens(userId);
1795
+ await this.revokeAllForUser(userId);
1812
1796
  }
1813
1797
  /**
1814
- * Logout user - blacklist access token and revoke refresh token
1798
+ * Logout the CURRENT session only — blacklist the presented access token,
1799
+ * mark its family revoked, and delete that family's refresh row. Other
1800
+ * devices/sessions for the same user keep working. Use a password change or
1801
+ * reset (revoke-all) to terminate every session.
1802
+ *
1803
+ * The family is resolved from, in order: a verified Bearer access token's
1804
+ * `tokenFamily` claim, then a verified refresh cookie whose hash still
1805
+ * matches the current family row. If neither is available, fall back to
1806
+ * revoke-all.
1815
1807
  */
1816
1808
  async logout(userId, authorization) {
1809
+ let tokenFamily = null;
1817
1810
  if (authorization) {
1811
+ let accessToken = null;
1818
1812
  try {
1819
- const accessToken = this.extractAccessToken(authorization);
1820
- await this.blacklistCurrentToken(accessToken);
1813
+ accessToken = this.extractAccessToken(authorization);
1821
1814
  } catch {
1822
1815
  }
1816
+ if (accessToken) {
1817
+ try {
1818
+ const decoded = await this.verifyAccessToken(accessToken);
1819
+ if (decoded.userId === userId && decoded.tokenFamily) {
1820
+ tokenFamily = decoded.tokenFamily;
1821
+ }
1822
+ } catch {
1823
+ }
1824
+ await this.blacklistCurrentToken(accessToken);
1825
+ }
1826
+ }
1827
+ if (!tokenFamily) {
1828
+ tokenFamily = await this.resolveRefreshCookieFamily(userId);
1829
+ }
1830
+ if (tokenFamily) {
1831
+ await this.revokeFamily(tokenFamily);
1832
+ return;
1823
1833
  }
1824
1834
  await this.invalidateUserAccessTokens(userId);
1825
- await this.revokeToken(userId);
1835
+ await this.revokeAllForUser(userId);
1836
+ }
1837
+ /**
1838
+ * Resolve a logout target from the refresh cookie only if the cookie maps to
1839
+ * the user's current/valid family row. This mirrors resolveUserFromCookie()
1840
+ * without throwing, because logout can still fall back to revoke-all.
1841
+ */
1842
+ async resolveRefreshCookieFamily(userId) {
1843
+ const refreshToken = this.cookieManager.getRefreshToken();
1844
+ if (!refreshToken)
1845
+ return null;
1846
+ try {
1847
+ const decoded = this.verifyRefreshToken(refreshToken);
1848
+ if (decoded.userId !== userId)
1849
+ return null;
1850
+ const stored = await this.tokenRepository.getByFamily(decoded.tokenFamily);
1851
+ if (!stored || stored.userId !== userId)
1852
+ return null;
1853
+ const presentedHash = this.hashToken(refreshToken);
1854
+ if (presentedHash === stored.token) {
1855
+ return decoded.tokenFamily;
1856
+ }
1857
+ const canRecover = stored.previousHash && presentedHash === stored.previousHash && stored.previousValidUntil && new Date(stored.previousValidUntil).getTime() > Date.now() && !stored.previousUsedAt;
1858
+ return canRecover ? decoded.tokenFamily : null;
1859
+ } catch {
1860
+ return null;
1861
+ }
1826
1862
  }
1827
1863
  // ============ PASSWORD RESET TOKENS ============
1828
1864
  /**
1829
- * Generate secure password reset token
1830
- * Returns both the plain token (to send via email) and userId for identification
1865
+ * Generate a one-time set-password token (used by both password reset and
1866
+ * account invites). Stores the jti in cache so the token can only be used
1867
+ * once, with a TTL matching the JWT expiry.
1831
1868
  */
1832
- async generateResetToken(userId) {
1833
- const jti = nanoid5(16);
1834
- const resetData = {
1869
+ async generateSetPasswordToken(userId, type, expiresIn) {
1870
+ const jti = nanoid4(16);
1871
+ const data = {
1835
1872
  userId,
1836
- type: "reset",
1873
+ type,
1837
1874
  jti,
1838
1875
  timestamp: Date.now()
1839
1876
  };
1840
- const token = jwt.sign(resetData, this.config.jwt.refreshSecret, {
1841
- expiresIn: "1h"
1877
+ const token = jwt.sign(data, this.config.jwt.refreshSecret, {
1878
+ expiresIn
1842
1879
  });
1843
- await this.cache.set(`${this.resetTokenPrefix}${userId}`, jti, 36e5);
1880
+ await this.cache.set(`${this.resetTokenPrefix}${userId}`, jti, timestring2(expiresIn, "ms"));
1844
1881
  return { token, userId };
1845
1882
  }
1883
+ /**
1884
+ * Generate secure password reset token
1885
+ * Returns both the plain token (to send via email) and userId for identification
1886
+ */
1887
+ async generateResetToken(userId) {
1888
+ return this.generateSetPasswordToken(userId, "reset", "1h");
1889
+ }
1890
+ /**
1891
+ * Generate secure account-invite token.
1892
+ * Longer expiry (3d) than reset because an invited user may not check
1893
+ * their email immediately. Consumed via the same reset-password endpoint.
1894
+ */
1895
+ async generateInviteToken(userId) {
1896
+ return this.generateSetPasswordToken(userId, "invite", "3d");
1897
+ }
1846
1898
  /**
1847
1899
  * Verify password reset token
1848
1900
  * Returns userId if valid, throws error if expired/invalid
@@ -1854,7 +1906,7 @@ var TokenService = class TokenService2 {
1854
1906
  } catch {
1855
1907
  Err5(this.t("errors.resetTokenExpired"));
1856
1908
  }
1857
- if (decoded.type !== "reset" || !decoded.jti) {
1909
+ if (decoded.type !== "reset" && decoded.type !== "invite" || !decoded.jti) {
1858
1910
  Err5(this.t("errors.invalidResetToken"));
1859
1911
  }
1860
1912
  const key = `${this.resetTokenPrefix}${decoded.userId}`;
@@ -1943,6 +1995,38 @@ var AuthService = class AuthService2 {
1943
1995
  async registerUser(body) {
1944
1996
  return await this.userService.create(body);
1945
1997
  }
1998
+ /**
1999
+ * Admin-initiated account creation. The user is created with a random,
2000
+ * unusable password (the schema requires one) and then emailed a one-time
2001
+ * link to set their own. They can't log in until they do, since they never
2002
+ * learn the random password.
2003
+ *
2004
+ * Email is best-effort: a send failure logs a warning but never rolls back
2005
+ * account creation (and with the console provider, nothing is actually sent).
2006
+ */
2007
+ async inviteUser(body) {
2008
+ const randomPassword = `${nanoid5(24)}Aa1!`;
2009
+ const user = await this.userService.create({
2010
+ name: body.name,
2011
+ email: body.email,
2012
+ roleId: body.roleId,
2013
+ image: body.image,
2014
+ password: randomPassword,
2015
+ status: "active",
2016
+ emailVerified: false
2017
+ });
2018
+ const { token } = await this.tokenService.generateInviteToken(user.id);
2019
+ const inviteLink = `${this.config.frontendUrl}/reset-password?token=${token}`;
2020
+ try {
2021
+ await this.emailService.sendHtml(body.email, this.t("emails.accountInvite.subject"), accountInviteTemplate({
2022
+ inviteLink,
2023
+ userName: user.name || body.email
2024
+ }));
2025
+ } catch (error) {
2026
+ this.logger.warn("Account invite email failed", { email: body.email, error });
2027
+ }
2028
+ return user;
2029
+ }
1946
2030
  async loginUser(body) {
1947
2031
  const { email: email2, password } = body;
1948
2032
  const user = await this.userService.findByEmail(email2);
@@ -1972,6 +2056,7 @@ var AuthService = class AuthService2 {
1972
2056
  if ((user.failedLoginAttempts ?? 0) > 0 || user.lockoutUntil) {
1973
2057
  await this.userService.resetFailedAttempts(user.id);
1974
2058
  }
2059
+ await this.tokenService.deleteExpiredSessions();
1975
2060
  const generated = await this.tokenService.generateTokens(user.id);
1976
2061
  this.cookieManager.setRefreshToken(generated.refreshToken);
1977
2062
  await this.userService.updateLastLogin(user.id);
@@ -1982,7 +2067,7 @@ var AuthService = class AuthService2 {
1982
2067
  roles,
1983
2068
  permissions
1984
2069
  });
1985
- const { userId: _userId, roles: _roles, permissions: _permissions, ...tokens } = generated;
2070
+ const { userId: _userId, tokenFamily: _tokenFamily, roles: _roles, permissions: _permissions, ...tokens } = generated;
1986
2071
  return { ...tokens, user: sanitized };
1987
2072
  }
1988
2073
  async refreshTokens() {
@@ -1996,7 +2081,7 @@ var AuthService = class AuthService2 {
1996
2081
  permissions: generated.permissions
1997
2082
  });
1998
2083
  }
1999
- const { userId: _userId, roles: _roles, permissions: _permissions, ...tokens } = generated;
2084
+ const { userId: _userId, tokenFamily: _tokenFamily, roles: _roles, permissions: _permissions, ...tokens } = generated;
2000
2085
  return tokens;
2001
2086
  }
2002
2087
  async logoutUser(userId, authorization) {
@@ -2005,6 +2090,15 @@ var AuthService = class AuthService2 {
2005
2090
  this.cookieManager.clearSessionCookie();
2006
2091
  return { data: null, message: this.t("auth.success.logout") };
2007
2092
  }
2093
+ /**
2094
+ * Prune expired refresh sessions for every user. Login already prunes
2095
+ * opportunistically; expose this so consumers can also run it from a
2096
+ * scheduled job (cron / queue) to reclaim rows from users who never return.
2097
+ * Best-effort — safe to call repeatedly.
2098
+ */
2099
+ async pruneExpiredSessions() {
2100
+ await this.tokenService.deleteExpiredSessions();
2101
+ }
2008
2102
  async getUserProfile(userData) {
2009
2103
  const lang = this.i18nService.getCurrentLanguage();
2010
2104
  return {
@@ -2077,7 +2171,7 @@ var AuthService = class AuthService2 {
2077
2171
  this.userValidator.validatePasswordStrength(newPassword);
2078
2172
  await this.userService.update(userId, { password: newPassword });
2079
2173
  await this.tokenService.invalidateUserAccessTokens(userId);
2080
- await this.tokenService.revokeToken(userId);
2174
+ await this.tokenService.revokeAllForUser(userId);
2081
2175
  this.cookieManager.clearRefreshToken();
2082
2176
  this.cookieManager.clearSessionCookie();
2083
2177
  return { message: this.t("success.passwordChanged") };
@@ -2087,7 +2181,7 @@ var AuthService = class AuthService2 {
2087
2181
  this.userValidator.validatePasswordStrength(newPassword);
2088
2182
  await this.userService.update(userId, { password: newPassword });
2089
2183
  await this.tokenService.invalidateUserAccessTokens(userId);
2090
- await this.tokenService.revokeToken(userId);
2184
+ await this.tokenService.revokeAllForUser(userId);
2091
2185
  this.cookieManager.clearRefreshToken();
2092
2186
  this.cookieManager.clearSessionCookie();
2093
2187
  return { message: this.t("success.passwordReset") };
@@ -2146,63 +2240,33 @@ AuthGuard = __decorate12([
2146
2240
  ], AuthGuard);
2147
2241
  var isAuth = createGuard(AuthGuard);
2148
2242
 
2149
- // src/auth/AuthController.ts
2150
- import { Validate } from "najm-validation";
2151
- import { RateLimit } from "najm-rate";
2152
- import { createHash as createHash2 } from "crypto";
2153
-
2154
- // src/users/UserDto.ts
2155
- import { z } from "zod";
2156
- var emailField = z.string().email("Invalid email format");
2157
- var passwordField = z.string().min(8, "Password must be at least 8 characters");
2158
- var optionalDateField = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").nullable().optional();
2159
- var createUserDto = z.object({
2160
- name: z.string().max(100).optional(),
2161
- email: emailField,
2162
- password: passwordField,
2163
- roleId: z.string().min(1).optional(),
2164
- image: z.string().nullish(),
2165
- emailVerified: z.boolean().default(false),
2166
- status: z.enum(["active", "inactive", "pending"]).optional()
2167
- });
2168
- var updateUserDto = createUserDto.partial();
2169
- var userIdParam = z.object({
2170
- id: z.string().min(1, "User ID is required")
2171
- });
2172
- var loginDto = z.object({
2173
- email: emailField,
2174
- password: passwordField
2175
- });
2176
- var changePasswordDto = z.object({
2177
- currentPassword: passwordField,
2178
- newPassword: passwordField
2179
- });
2180
- var resetPasswordDto = z.object({
2181
- email: emailField
2182
- });
2183
- var confirmResetPasswordDto = z.object({
2184
- token: z.string().min(10, "Invalid reset token"),
2185
- newPassword: passwordField
2186
- });
2187
- var languageParam = z.object({
2188
- language: z.string().min(2)
2189
- });
2190
- var emailParam = z.object({
2191
- email: emailField
2192
- });
2193
- var userIdInParam = z.object({
2194
- userId: z.string().min(1, "User ID is required")
2195
- });
2196
- var assignRoleParams = z.object({
2197
- userId: z.string().min(1, "User ID is required"),
2198
- roleId: z.string().min(1)
2199
- });
2200
- var userListQuery = z.object({
2201
- limit: z.coerce.number().int().min(1).max(100).default(50),
2202
- offset: z.coerce.number().int().min(0).default(0)
2243
+ // src/roles/index.ts
2244
+ var roles_exports = {};
2245
+ __export(roles_exports, {
2246
+ ROLES: () => ROLES,
2247
+ ROLE_GROUPS: () => ROLE_GROUPS,
2248
+ Role: () => Role,
2249
+ RoleController: () => RoleController,
2250
+ RoleGuard: () => RoleGuard,
2251
+ RoleRepository: () => RoleRepository,
2252
+ RoleService: () => RoleService,
2253
+ RoleValidator: () => RoleValidator,
2254
+ assignRoleDto: () => assignRoleDto,
2255
+ createRoleDto: () => createRoleDto,
2256
+ defineRoles: () => defineRoles,
2257
+ isAdmin: () => isAdmin,
2258
+ isAdministrator: () => isAdministrator,
2259
+ roleIdParam: () => roleIdParam,
2260
+ updateRoleDto: () => updateRoleDto
2203
2261
  });
2204
2262
 
2205
- // src/auth/AuthController.ts
2263
+ // src/roles/defineRoles.ts
2264
+ import { composeGuards as composeGuards2, createGuard as createGuard3 } from "najm-guard";
2265
+
2266
+ // src/roles/RoleGuards.ts
2267
+ import { Service as Service3 } from "najm-core";
2268
+ import { GuardParams, User as User2 } from "najm-core";
2269
+ import { composeGuards, createGuard as createGuard2 } from "najm-guard";
2206
2270
  var __decorate13 = function(decorators, target, key, desc) {
2207
2271
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2208
2272
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -2217,156 +2281,431 @@ var __param4 = function(paramIndex, decorator) {
2217
2281
  decorator(target, key, paramIndex);
2218
2282
  };
2219
2283
  };
2220
- var _a8;
2221
- var hashKeyPart = /* @__PURE__ */ __name((value) => createHash2("sha256").update(value).digest("base64url").slice(0, 32), "hashKeyPart");
2222
- var cookieFingerprint = /* @__PURE__ */ __name(() => (ctx) => {
2223
- const ip = ctx.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? ctx.req.header("x-real-ip") ?? "unknown";
2224
- const cookie = ctx.req.raw.headers.get("cookie") ?? "";
2225
- const fingerprint = cookie ? hashKeyPart(cookie) : "none";
2226
- return `${ip}:${fingerprint}`;
2227
- }, "cookieFingerprint");
2228
- var ipAndEmail = /* @__PURE__ */ __name(async (ctx) => {
2229
- const ip = ctx.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? ctx.req.header("x-real-ip") ?? "unknown";
2230
- try {
2231
- const body = await ctx.req.json();
2232
- if (body?.email && typeof body.email === "string") {
2233
- const normalizedEmail = body.email.trim().toLowerCase();
2234
- if (normalizedEmail)
2235
- return `${ip}:${hashKeyPart(normalizedEmail)}`;
2236
- }
2237
- } catch {
2238
- }
2239
- return ip;
2240
- }, "ipAndEmail");
2241
- var AuthController = class AuthController2 {
2284
+ var RoleGuard = class RoleGuard2 {
2242
2285
  static {
2243
- __name(this, "AuthController");
2244
- }
2245
- authService;
2246
- constructor(authService) {
2247
- this.authService = authService;
2248
- }
2249
- async registerUser(body) {
2250
- return this.authService.registerUser(body);
2251
- }
2252
- async loginUser(body) {
2253
- return this.authService.loginUser(body);
2286
+ __name(this, "RoleGuard");
2254
2287
  }
2255
- async refreshTokens() {
2256
- return this.authService.refreshTokens();
2288
+ canActivate(allowedRoles, userRole) {
2289
+ if (!userRole)
2290
+ return false;
2291
+ const requiredRoles = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles];
2292
+ const hasRole = requiredRoles.some((r) => r.toLowerCase() === userRole.toLowerCase());
2293
+ if (hasRole) {
2294
+ return { role: userRole };
2295
+ }
2296
+ return false;
2257
2297
  }
2258
- async logoutUser(userId, authorization) {
2259
- return this.authService.logoutUser(userId, authorization);
2298
+ };
2299
+ __decorate13([
2300
+ __param4(0, GuardParams()),
2301
+ __param4(1, User2("role")),
2302
+ __metadata13("design:type", Function),
2303
+ __metadata13("design:paramtypes", [Object, String]),
2304
+ __metadata13("design:returntype", void 0)
2305
+ ], RoleGuard.prototype, "canActivate", null);
2306
+ RoleGuard = __decorate13([
2307
+ Service3()
2308
+ ], RoleGuard);
2309
+ var Role = createGuard2(RoleGuard);
2310
+ var isAdmin = composeGuards(isAuth(), Role(ROLES.ADMIN));
2311
+ var isAdministrator = composeGuards(isAuth(), Role(ROLE_GROUPS.ADMINISTRATORS));
2312
+
2313
+ // src/roles/defineRoles.ts
2314
+ var Role2 = createGuard3(RoleGuard);
2315
+ function defineRoles(roles, options) {
2316
+ const ROLES2 = roles;
2317
+ const superRoleKeys = options?.superRoles ?? [];
2318
+ function resolveRoleValues(keys) {
2319
+ return Array.from(new Set([...keys, ...superRoleKeys].map((key) => roles[key])));
2260
2320
  }
2261
- async changePassword(userId, body) {
2262
- return this.authService.changePassword(userId, body.currentPassword, body.newPassword);
2321
+ __name(resolveRoleValues, "resolveRoleValues");
2322
+ const guards2 = {};
2323
+ for (const [key, value] of Object.entries(roles)) {
2324
+ const name = `is${key.charAt(0).toUpperCase()}${key.slice(1).toLowerCase()}`;
2325
+ const allowedValues = resolveRoleValues([key]);
2326
+ guards2[name] = composeGuards2(isAuth(), Role2(allowedValues.length === 1 ? value : allowedValues));
2263
2327
  }
2264
- async userProfile(authorization) {
2265
- return this.authService.getMe(authorization);
2328
+ function createGroupGuard(keys) {
2329
+ const values = resolveRoleValues(keys);
2330
+ return composeGuards2(isAuth(), Role2(values));
2266
2331
  }
2267
- async forgotPassword(body) {
2268
- return this.authService.forgotPassword(body.email);
2332
+ __name(createGroupGuard, "createGroupGuard");
2333
+ function hasRole(userRole, ...keys) {
2334
+ if (!userRole)
2335
+ return false;
2336
+ const normalized = userRole.toLowerCase();
2337
+ return resolveRoleValues(keys).some((role) => role === normalized);
2269
2338
  }
2270
- async resetPassword(body) {
2271
- return this.authService.resetPassword(body.token, body.newPassword);
2339
+ __name(hasRole, "hasRole");
2340
+ function isInGroup(userRole, keys) {
2341
+ return hasRole(userRole, ...keys);
2272
2342
  }
2273
- };
2274
- __decorate13([
2275
- Post("/register"),
2276
- RateLimit({ limit: 5, window: "15m", key: ipAndEmail }),
2277
- Validate(createUserDto),
2278
- ResMsg("auth.success.register"),
2279
- __param4(0, Body()),
2280
- __metadata13("design:type", Function),
2281
- __metadata13("design:paramtypes", [Object]),
2282
- __metadata13("design:returntype", Promise)
2283
- ], AuthController.prototype, "registerUser", null);
2284
- __decorate13([
2285
- Post("/login"),
2286
- RateLimit({ limit: 5, window: "15m", key: ipAndEmail, message: "Too many login attempts. Please try again later." }),
2287
- Validate(loginDto),
2288
- ResMsg("auth.success.login"),
2289
- __param4(0, Body()),
2290
- __metadata13("design:type", Function),
2291
- __metadata13("design:paramtypes", [Object]),
2292
- __metadata13("design:returntype", Promise)
2293
- ], AuthController.prototype, "loginUser", null);
2294
- __decorate13([
2295
- Post("/refresh"),
2343
+ __name(isInGroup, "isInGroup");
2344
+ return { ROLES: ROLES2, createGroupGuard, hasRole, isInGroup, ...guards2 };
2345
+ }
2346
+ __name(defineRoles, "defineRoles");
2347
+
2348
+ // src/roles/RoleController.ts
2349
+ import { Controller } from "najm-core";
2350
+ import { Get, Post, Put, Delete, ResMsg } from "najm-core";
2351
+ import { Params, Body } from "najm-core";
2352
+ import { Validate } from "najm-validation";
2353
+
2354
+ // src/roles/RoleDto.ts
2355
+ import { z } from "zod";
2356
+ var nameField = z.string().min(2, "Name must be at least 2 characters").max(50, "Name too long");
2357
+ var descriptionField = z.string().max(255, "Description too long").optional();
2358
+ var createRoleDto = z.object({
2359
+ name: nameField,
2360
+ description: descriptionField
2361
+ });
2362
+ var updateRoleDto = createRoleDto.partial();
2363
+ var roleIdParam = z.object({
2364
+ id: z.string().length(5, "Role ID must be 5 characters")
2365
+ });
2366
+ var assignRoleDto = z.object({
2367
+ userId: z.string().length(8, "User ID must be 8 characters"),
2368
+ roleId: z.string().length(5, "Role ID must be 5 characters")
2369
+ });
2370
+
2371
+ // src/roles/RoleController.ts
2372
+ var __decorate14 = function(decorators, target, key, desc) {
2373
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2374
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2375
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2376
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2377
+ };
2378
+ var __metadata14 = function(k, v) {
2379
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2380
+ };
2381
+ var __param5 = function(paramIndex, decorator) {
2382
+ return function(target, key) {
2383
+ decorator(target, key, paramIndex);
2384
+ };
2385
+ };
2386
+ var _a8;
2387
+ var RoleController = class RoleController2 {
2388
+ static {
2389
+ __name(this, "RoleController");
2390
+ }
2391
+ roleService;
2392
+ constructor(roleService) {
2393
+ this.roleService = roleService;
2394
+ }
2395
+ async getRoles() {
2396
+ return this.roleService.getAll();
2397
+ }
2398
+ async getRole(params) {
2399
+ return this.roleService.getById(params.id);
2400
+ }
2401
+ async createRole(body) {
2402
+ return this.roleService.create(body);
2403
+ }
2404
+ async updateRole(params, body) {
2405
+ return this.roleService.update(params.id, body);
2406
+ }
2407
+ async deleteRole(params) {
2408
+ return this.roleService.delete(params.id);
2409
+ }
2410
+ };
2411
+ __decorate14([
2412
+ Get(),
2413
+ isAdmin(),
2414
+ ResMsg("roles.success.retrieved"),
2415
+ __metadata14("design:type", Function),
2416
+ __metadata14("design:paramtypes", []),
2417
+ __metadata14("design:returntype", Promise)
2418
+ ], RoleController.prototype, "getRoles", null);
2419
+ __decorate14([
2420
+ Get("/:id"),
2421
+ isAdmin(),
2422
+ Validate({ params: roleIdParam }),
2423
+ ResMsg("roles.success.retrieved"),
2424
+ __param5(0, Params()),
2425
+ __metadata14("design:type", Function),
2426
+ __metadata14("design:paramtypes", [Object]),
2427
+ __metadata14("design:returntype", Promise)
2428
+ ], RoleController.prototype, "getRole", null);
2429
+ __decorate14([
2430
+ Post(),
2431
+ isAdmin(),
2432
+ Validate(createRoleDto),
2433
+ ResMsg("roles.success.created"),
2434
+ __param5(0, Body()),
2435
+ __metadata14("design:type", Function),
2436
+ __metadata14("design:paramtypes", [Object]),
2437
+ __metadata14("design:returntype", Promise)
2438
+ ], RoleController.prototype, "createRole", null);
2439
+ __decorate14([
2440
+ Put("/:id"),
2441
+ isAdmin(),
2442
+ Validate({
2443
+ params: roleIdParam,
2444
+ body: updateRoleDto
2445
+ }),
2446
+ ResMsg("roles.success.updated"),
2447
+ __param5(0, Params()),
2448
+ __param5(1, Body()),
2449
+ __metadata14("design:type", Function),
2450
+ __metadata14("design:paramtypes", [Object, Object]),
2451
+ __metadata14("design:returntype", Promise)
2452
+ ], RoleController.prototype, "updateRole", null);
2453
+ __decorate14([
2454
+ Delete("/:id"),
2455
+ isAdmin(),
2456
+ Validate({ params: roleIdParam }),
2457
+ ResMsg("roles.success.deleted"),
2458
+ __param5(0, Params()),
2459
+ __metadata14("design:type", Function),
2460
+ __metadata14("design:paramtypes", [Object]),
2461
+ __metadata14("design:returntype", Promise)
2462
+ ], RoleController.prototype, "deleteRole", null);
2463
+ RoleController = __decorate14([
2464
+ Controller("/roles"),
2465
+ __metadata14("design:paramtypes", [typeof (_a8 = typeof RoleService !== "undefined" && RoleService) === "function" ? _a8 : Object])
2466
+ ], RoleController);
2467
+
2468
+ // src/auth/AuthController.ts
2469
+ import { Validate as Validate2 } from "najm-validation";
2470
+ import { RateLimit } from "najm-rate";
2471
+ import { createHash as createHash2 } from "crypto";
2472
+
2473
+ // src/users/UserDto.ts
2474
+ import { z as z2 } from "zod";
2475
+ var emailField = z2.string().email("Invalid email format");
2476
+ var passwordField = z2.string().min(8, "Password must be at least 8 characters");
2477
+ var optionalDateField = z2.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").nullable().optional();
2478
+ var createUserDto = z2.object({
2479
+ name: z2.string().max(100).optional(),
2480
+ email: emailField,
2481
+ password: passwordField,
2482
+ roleId: z2.string().min(1).optional(),
2483
+ image: z2.string().nullish(),
2484
+ emailVerified: z2.boolean().default(false),
2485
+ status: z2.enum(["active", "inactive", "pending"]).optional()
2486
+ });
2487
+ var updateUserDto = createUserDto.partial();
2488
+ var inviteUserDto = z2.object({
2489
+ name: z2.string().max(100).optional(),
2490
+ email: emailField,
2491
+ roleId: z2.string().min(1).optional(),
2492
+ image: z2.string().nullish()
2493
+ });
2494
+ var userIdParam = z2.object({
2495
+ id: z2.string().min(1, "User ID is required")
2496
+ });
2497
+ var loginDto = z2.object({
2498
+ email: emailField,
2499
+ password: passwordField
2500
+ });
2501
+ var changePasswordDto = z2.object({
2502
+ currentPassword: passwordField,
2503
+ newPassword: passwordField
2504
+ });
2505
+ var resetPasswordDto = z2.object({
2506
+ email: emailField
2507
+ });
2508
+ var confirmResetPasswordDto = z2.object({
2509
+ token: z2.string().min(10, "Invalid reset token"),
2510
+ newPassword: passwordField
2511
+ });
2512
+ var languageParam = z2.object({
2513
+ language: z2.string().min(2)
2514
+ });
2515
+ var emailParam = z2.object({
2516
+ email: emailField
2517
+ });
2518
+ var userIdInParam = z2.object({
2519
+ userId: z2.string().min(1, "User ID is required")
2520
+ });
2521
+ var assignRoleParams = z2.object({
2522
+ userId: z2.string().min(1, "User ID is required"),
2523
+ roleId: z2.string().min(1)
2524
+ });
2525
+ var userListQuery = z2.object({
2526
+ limit: z2.coerce.number().int().min(1).max(100).default(50),
2527
+ offset: z2.coerce.number().int().min(0).default(0)
2528
+ });
2529
+
2530
+ // src/auth/AuthController.ts
2531
+ var __decorate15 = function(decorators, target, key, desc) {
2532
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2533
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2534
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2535
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2536
+ };
2537
+ var __metadata15 = function(k, v) {
2538
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2539
+ };
2540
+ var __param6 = function(paramIndex, decorator) {
2541
+ return function(target, key) {
2542
+ decorator(target, key, paramIndex);
2543
+ };
2544
+ };
2545
+ var _a9;
2546
+ var hashKeyPart = /* @__PURE__ */ __name((value) => createHash2("sha256").update(value).digest("base64url").slice(0, 32), "hashKeyPart");
2547
+ var cookieFingerprint = /* @__PURE__ */ __name(() => (ctx) => {
2548
+ const ip = ctx.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? ctx.req.header("x-real-ip") ?? "unknown";
2549
+ const cookie = ctx.req.raw.headers.get("cookie") ?? "";
2550
+ const fingerprint = cookie ? hashKeyPart(cookie) : "none";
2551
+ return `${ip}:${fingerprint}`;
2552
+ }, "cookieFingerprint");
2553
+ var ipAndEmail = /* @__PURE__ */ __name(async (ctx) => {
2554
+ const ip = ctx.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? ctx.req.header("x-real-ip") ?? "unknown";
2555
+ try {
2556
+ const body = await ctx.req.json();
2557
+ if (body?.email && typeof body.email === "string") {
2558
+ const normalizedEmail = body.email.trim().toLowerCase();
2559
+ if (normalizedEmail)
2560
+ return `${ip}:${hashKeyPart(normalizedEmail)}`;
2561
+ }
2562
+ } catch {
2563
+ }
2564
+ return ip;
2565
+ }, "ipAndEmail");
2566
+ var AuthController = class AuthController2 {
2567
+ static {
2568
+ __name(this, "AuthController");
2569
+ }
2570
+ authService;
2571
+ constructor(authService) {
2572
+ this.authService = authService;
2573
+ }
2574
+ async registerUser(body) {
2575
+ return this.authService.registerUser(body);
2576
+ }
2577
+ async loginUser(body) {
2578
+ return this.authService.loginUser(body);
2579
+ }
2580
+ async inviteUser(body) {
2581
+ return this.authService.inviteUser(body);
2582
+ }
2583
+ async refreshTokens() {
2584
+ return this.authService.refreshTokens();
2585
+ }
2586
+ async logoutUser(userId, authorization) {
2587
+ return this.authService.logoutUser(userId, authorization);
2588
+ }
2589
+ async changePassword(userId, body) {
2590
+ return this.authService.changePassword(userId, body.currentPassword, body.newPassword);
2591
+ }
2592
+ async userProfile(authorization) {
2593
+ return this.authService.getMe(authorization);
2594
+ }
2595
+ async forgotPassword(body) {
2596
+ return this.authService.forgotPassword(body.email);
2597
+ }
2598
+ async resetPassword(body) {
2599
+ return this.authService.resetPassword(body.token, body.newPassword);
2600
+ }
2601
+ };
2602
+ __decorate15([
2603
+ Post2("/register"),
2604
+ RateLimit({ limit: 5, window: "15m", key: ipAndEmail }),
2605
+ Validate2(createUserDto),
2606
+ ResMsg2("auth.success.register"),
2607
+ __param6(0, Body2()),
2608
+ __metadata15("design:type", Function),
2609
+ __metadata15("design:paramtypes", [Object]),
2610
+ __metadata15("design:returntype", Promise)
2611
+ ], AuthController.prototype, "registerUser", null);
2612
+ __decorate15([
2613
+ Post2("/login"),
2614
+ RateLimit({ limit: 5, window: "15m", key: ipAndEmail, message: "Too many login attempts. Please try again later." }),
2615
+ Validate2(loginDto),
2616
+ ResMsg2("auth.success.login"),
2617
+ __param6(0, Body2()),
2618
+ __metadata15("design:type", Function),
2619
+ __metadata15("design:paramtypes", [Object]),
2620
+ __metadata15("design:returntype", Promise)
2621
+ ], AuthController.prototype, "loginUser", null);
2622
+ __decorate15([
2623
+ Post2("/invite"),
2624
+ isAdmin(),
2625
+ RateLimit({ limit: 20, window: "15m", key: "user" }),
2626
+ Validate2(inviteUserDto),
2627
+ ResMsg2("auth.success.accountInviteSent"),
2628
+ __param6(0, Body2()),
2629
+ __metadata15("design:type", Function),
2630
+ __metadata15("design:paramtypes", [Object]),
2631
+ __metadata15("design:returntype", Promise)
2632
+ ], AuthController.prototype, "inviteUser", null);
2633
+ __decorate15([
2634
+ Post2("/refresh"),
2296
2635
  RateLimit({ limit: 15, window: "15m", key: cookieFingerprint() }),
2297
- ResMsg("auth.success.tokenRefreshed"),
2298
- __metadata13("design:type", Function),
2299
- __metadata13("design:paramtypes", []),
2300
- __metadata13("design:returntype", Promise)
2636
+ ResMsg2("auth.success.tokenRefreshed"),
2637
+ __metadata15("design:type", Function),
2638
+ __metadata15("design:paramtypes", []),
2639
+ __metadata15("design:returntype", Promise)
2301
2640
  ], AuthController.prototype, "refreshTokens", null);
2302
- __decorate13([
2303
- Post("/logout"),
2641
+ __decorate15([
2642
+ Post2("/logout"),
2304
2643
  isAuth(),
2305
2644
  RateLimit({ limit: 10, window: "15m", key: "user" }),
2306
- __param4(0, User2("id")),
2307
- __param4(1, Headers("authorization")),
2308
- __metadata13("design:type", Function),
2309
- __metadata13("design:paramtypes", [String, String]),
2310
- __metadata13("design:returntype", Promise)
2645
+ __param6(0, User3("id")),
2646
+ __param6(1, Headers("authorization")),
2647
+ __metadata15("design:type", Function),
2648
+ __metadata15("design:paramtypes", [String, String]),
2649
+ __metadata15("design:returntype", Promise)
2311
2650
  ], AuthController.prototype, "logoutUser", null);
2312
- __decorate13([
2313
- Post("/change-password"),
2651
+ __decorate15([
2652
+ Post2("/change-password"),
2314
2653
  isAuth(),
2315
- Validate(changePasswordDto),
2316
- ResMsg("auth.success.passwordChanged"),
2317
- __param4(0, User2("id")),
2318
- __param4(1, Body()),
2319
- __metadata13("design:type", Function),
2320
- __metadata13("design:paramtypes", [String, Object]),
2321
- __metadata13("design:returntype", Promise)
2654
+ Validate2(changePasswordDto),
2655
+ ResMsg2("auth.success.passwordChanged"),
2656
+ __param6(0, User3("id")),
2657
+ __param6(1, Body2()),
2658
+ __metadata15("design:type", Function),
2659
+ __metadata15("design:paramtypes", [String, Object]),
2660
+ __metadata15("design:returntype", Promise)
2322
2661
  ], AuthController.prototype, "changePassword", null);
2323
- __decorate13([
2324
- Get("/me"),
2662
+ __decorate15([
2663
+ Get2("/me"),
2325
2664
  RateLimit({ limit: 30, window: "1m", key: cookieFingerprint() }),
2326
- ResMsg("auth.users.success.retrieved"),
2327
- __param4(0, Headers("authorization")),
2328
- __metadata13("design:type", Function),
2329
- __metadata13("design:paramtypes", [String]),
2330
- __metadata13("design:returntype", Promise)
2665
+ ResMsg2("auth.users.success.retrieved"),
2666
+ __param6(0, Headers("authorization")),
2667
+ __metadata15("design:type", Function),
2668
+ __metadata15("design:paramtypes", [String]),
2669
+ __metadata15("design:returntype", Promise)
2331
2670
  ], AuthController.prototype, "userProfile", null);
2332
- __decorate13([
2333
- Post("/forgot-password"),
2671
+ __decorate15([
2672
+ Post2("/forgot-password"),
2334
2673
  RateLimit({ limit: 3, window: "15m", key: ipAndEmail, message: "Too many password reset requests. Please try again later." }),
2335
- Validate(resetPasswordDto),
2336
- ResMsg("auth.success.passwordResetSent"),
2337
- __param4(0, Body()),
2338
- __metadata13("design:type", Function),
2339
- __metadata13("design:paramtypes", [Object]),
2340
- __metadata13("design:returntype", Promise)
2674
+ Validate2(resetPasswordDto),
2675
+ ResMsg2("auth.success.passwordResetSent"),
2676
+ __param6(0, Body2()),
2677
+ __metadata15("design:type", Function),
2678
+ __metadata15("design:paramtypes", [Object]),
2679
+ __metadata15("design:returntype", Promise)
2341
2680
  ], AuthController.prototype, "forgotPassword", null);
2342
- __decorate13([
2343
- Post("/reset-password"),
2681
+ __decorate15([
2682
+ Post2("/reset-password"),
2344
2683
  RateLimit({ limit: 5, window: "15m", key: "ip", message: "Too many password reset attempts. Please try again later." }),
2345
- Validate(confirmResetPasswordDto),
2346
- ResMsg("auth.success.passwordReset"),
2347
- __param4(0, Body()),
2348
- __metadata13("design:type", Function),
2349
- __metadata13("design:paramtypes", [Object]),
2350
- __metadata13("design:returntype", Promise)
2684
+ Validate2(confirmResetPasswordDto),
2685
+ ResMsg2("auth.success.passwordReset"),
2686
+ __param6(0, Body2()),
2687
+ __metadata15("design:type", Function),
2688
+ __metadata15("design:paramtypes", [Object]),
2689
+ __metadata15("design:returntype", Promise)
2351
2690
  ], AuthController.prototype, "resetPassword", null);
2352
- AuthController = __decorate13([
2353
- Controller("/auth"),
2354
- __metadata13("design:paramtypes", [typeof (_a8 = typeof AuthService !== "undefined" && AuthService) === "function" ? _a8 : Object])
2691
+ AuthController = __decorate15([
2692
+ Controller2("/auth"),
2693
+ __metadata15("design:paramtypes", [typeof (_a9 = typeof AuthService !== "undefined" && AuthService) === "function" ? _a9 : Object])
2355
2694
  ], AuthController);
2356
2695
 
2357
2696
  // src/auth/AuthResolver.ts
2358
- import { APP, Container, DI, Inject as Inject9, LOGGER, Meta, Service as Service3 } from "najm-core";
2697
+ import { APP, Container, DI, Inject as Inject9, LOGGER, Meta, Service as Service4 } from "najm-core";
2359
2698
  import { USER, ROLE, PERMISSIONS } from "najm-guard";
2360
- var __decorate14 = function(decorators, target, key, desc) {
2699
+ var __decorate16 = function(decorators, target, key, desc) {
2361
2700
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2362
2701
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2363
2702
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2364
2703
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2365
2704
  };
2366
- var __metadata14 = function(k, v) {
2705
+ var __metadata16 = function(k, v) {
2367
2706
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2368
2707
  };
2369
- var _a9;
2708
+ var _a10;
2370
2709
  var AuthResolver = class AuthResolver2 {
2371
2710
  static {
2372
2711
  __name(this, "AuthResolver");
@@ -2464,20 +2803,20 @@ var AuthResolver = class AuthResolver2 {
2464
2803
  await authService.warmupPasswordHash();
2465
2804
  }
2466
2805
  };
2467
- __decorate14([
2806
+ __decorate16([
2468
2807
  DI(),
2469
- __metadata14("design:type", typeof (_a9 = typeof Container !== "undefined" && Container) === "function" ? _a9 : Object)
2808
+ __metadata16("design:type", typeof (_a10 = typeof Container !== "undefined" && Container) === "function" ? _a10 : Object)
2470
2809
  ], AuthResolver.prototype, "container", void 0);
2471
- __decorate14([
2810
+ __decorate16([
2472
2811
  Inject9(APP),
2473
- __metadata14("design:type", Object)
2812
+ __metadata16("design:type", Object)
2474
2813
  ], AuthResolver.prototype, "app", void 0);
2475
- __decorate14([
2814
+ __decorate16([
2476
2815
  Inject9(LOGGER),
2477
- __metadata14("design:type", Object)
2816
+ __metadata16("design:type", Object)
2478
2817
  ], AuthResolver.prototype, "log", void 0);
2479
- AuthResolver = __decorate14([
2480
- Service3(),
2818
+ AuthResolver = __decorate16([
2819
+ Service4(),
2481
2820
  Meta({ layer: "plugin", order: 30 })
2482
2821
  ], AuthResolver);
2483
2822
 
@@ -2526,6 +2865,7 @@ __export(users_exports, {
2526
2865
  confirmResetPasswordDto: () => confirmResetPasswordDto,
2527
2866
  createUserDto: () => createUserDto,
2528
2867
  emailParam: () => emailParam,
2868
+ inviteUserDto: () => inviteUserDto,
2529
2869
  languageParam: () => languageParam,
2530
2870
  loginDto: () => loginDto,
2531
2871
  resetPasswordDto: () => resetPasswordDto,
@@ -2539,233 +2879,6 @@ __export(users_exports, {
2539
2879
  import { Controller as Controller3 } from "najm-core";
2540
2880
  import { Get as Get3, Post as Post3, Put as Put2, Delete as Delete2, ResMsg as ResMsg3 } from "najm-core";
2541
2881
  import { Params as Params2, Body as Body3, Query } from "najm-core";
2542
-
2543
- // src/roles/index.ts
2544
- var roles_exports = {};
2545
- __export(roles_exports, {
2546
- ROLES: () => ROLES,
2547
- ROLE_GROUPS: () => ROLE_GROUPS,
2548
- Role: () => Role,
2549
- RoleController: () => RoleController,
2550
- RoleGuard: () => RoleGuard,
2551
- RoleRepository: () => RoleRepository,
2552
- RoleService: () => RoleService,
2553
- RoleValidator: () => RoleValidator,
2554
- assignRoleDto: () => assignRoleDto,
2555
- createRoleDto: () => createRoleDto,
2556
- defineRoles: () => defineRoles,
2557
- isAdmin: () => isAdmin,
2558
- isAdministrator: () => isAdministrator,
2559
- roleIdParam: () => roleIdParam,
2560
- updateRoleDto: () => updateRoleDto
2561
- });
2562
-
2563
- // src/roles/defineRoles.ts
2564
- import { composeGuards as composeGuards2, createGuard as createGuard3 } from "najm-guard";
2565
-
2566
- // src/roles/RoleGuards.ts
2567
- import { Service as Service4 } from "najm-core";
2568
- import { GuardParams, User as User3 } from "najm-core";
2569
- import { composeGuards, createGuard as createGuard2 } from "najm-guard";
2570
- var __decorate15 = function(decorators, target, key, desc) {
2571
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2572
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2573
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2574
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2575
- };
2576
- var __metadata15 = function(k, v) {
2577
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2578
- };
2579
- var __param5 = function(paramIndex, decorator) {
2580
- return function(target, key) {
2581
- decorator(target, key, paramIndex);
2582
- };
2583
- };
2584
- var RoleGuard = class RoleGuard2 {
2585
- static {
2586
- __name(this, "RoleGuard");
2587
- }
2588
- canActivate(allowedRoles, userRole) {
2589
- if (!userRole)
2590
- return false;
2591
- const requiredRoles = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles];
2592
- const hasRole = requiredRoles.some((r) => r.toLowerCase() === userRole.toLowerCase());
2593
- if (hasRole) {
2594
- return { role: userRole };
2595
- }
2596
- return false;
2597
- }
2598
- };
2599
- __decorate15([
2600
- __param5(0, GuardParams()),
2601
- __param5(1, User3("role")),
2602
- __metadata15("design:type", Function),
2603
- __metadata15("design:paramtypes", [Object, String]),
2604
- __metadata15("design:returntype", void 0)
2605
- ], RoleGuard.prototype, "canActivate", null);
2606
- RoleGuard = __decorate15([
2607
- Service4()
2608
- ], RoleGuard);
2609
- var Role = createGuard2(RoleGuard);
2610
- var isAdmin = composeGuards(isAuth(), Role(ROLES.ADMIN));
2611
- var isAdministrator = composeGuards(isAuth(), Role(ROLE_GROUPS.ADMINISTRATORS));
2612
-
2613
- // src/roles/defineRoles.ts
2614
- var Role2 = createGuard3(RoleGuard);
2615
- function defineRoles(roles, options) {
2616
- const ROLES2 = roles;
2617
- const superRoleKeys = options?.superRoles ?? [];
2618
- function resolveRoleValues(keys) {
2619
- return Array.from(new Set([...keys, ...superRoleKeys].map((key) => roles[key])));
2620
- }
2621
- __name(resolveRoleValues, "resolveRoleValues");
2622
- const guards2 = {};
2623
- for (const [key, value] of Object.entries(roles)) {
2624
- const name = `is${key.charAt(0).toUpperCase()}${key.slice(1).toLowerCase()}`;
2625
- const allowedValues = resolveRoleValues([key]);
2626
- guards2[name] = composeGuards2(isAuth(), Role2(allowedValues.length === 1 ? value : allowedValues));
2627
- }
2628
- function createGroupGuard(keys) {
2629
- const values = resolveRoleValues(keys);
2630
- return composeGuards2(isAuth(), Role2(values));
2631
- }
2632
- __name(createGroupGuard, "createGroupGuard");
2633
- function hasRole(userRole, ...keys) {
2634
- if (!userRole)
2635
- return false;
2636
- const normalized = userRole.toLowerCase();
2637
- return resolveRoleValues(keys).some((role) => role === normalized);
2638
- }
2639
- __name(hasRole, "hasRole");
2640
- function isInGroup(userRole, keys) {
2641
- return hasRole(userRole, ...keys);
2642
- }
2643
- __name(isInGroup, "isInGroup");
2644
- return { ROLES: ROLES2, createGroupGuard, hasRole, isInGroup, ...guards2 };
2645
- }
2646
- __name(defineRoles, "defineRoles");
2647
-
2648
- // src/roles/RoleController.ts
2649
- import { Controller as Controller2 } from "najm-core";
2650
- import { Get as Get2, Post as Post2, Put, Delete, ResMsg as ResMsg2 } from "najm-core";
2651
- import { Params, Body as Body2 } from "najm-core";
2652
- import { Validate as Validate2 } from "najm-validation";
2653
-
2654
- // src/roles/RoleDto.ts
2655
- import { z as z2 } from "zod";
2656
- var nameField = z2.string().min(2, "Name must be at least 2 characters").max(50, "Name too long");
2657
- var descriptionField = z2.string().max(255, "Description too long").optional();
2658
- var createRoleDto = z2.object({
2659
- name: nameField,
2660
- description: descriptionField
2661
- });
2662
- var updateRoleDto = createRoleDto.partial();
2663
- var roleIdParam = z2.object({
2664
- id: z2.string().length(5, "Role ID must be 5 characters")
2665
- });
2666
- var assignRoleDto = z2.object({
2667
- userId: z2.string().length(8, "User ID must be 8 characters"),
2668
- roleId: z2.string().length(5, "Role ID must be 5 characters")
2669
- });
2670
-
2671
- // src/roles/RoleController.ts
2672
- var __decorate16 = function(decorators, target, key, desc) {
2673
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2674
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2675
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2676
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2677
- };
2678
- var __metadata16 = function(k, v) {
2679
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2680
- };
2681
- var __param6 = function(paramIndex, decorator) {
2682
- return function(target, key) {
2683
- decorator(target, key, paramIndex);
2684
- };
2685
- };
2686
- var _a10;
2687
- var RoleController = class RoleController2 {
2688
- static {
2689
- __name(this, "RoleController");
2690
- }
2691
- roleService;
2692
- constructor(roleService) {
2693
- this.roleService = roleService;
2694
- }
2695
- async getRoles() {
2696
- return this.roleService.getAll();
2697
- }
2698
- async getRole(params) {
2699
- return this.roleService.getById(params.id);
2700
- }
2701
- async createRole(body) {
2702
- return this.roleService.create(body);
2703
- }
2704
- async updateRole(params, body) {
2705
- return this.roleService.update(params.id, body);
2706
- }
2707
- async deleteRole(params) {
2708
- return this.roleService.delete(params.id);
2709
- }
2710
- };
2711
- __decorate16([
2712
- Get2(),
2713
- isAdmin(),
2714
- ResMsg2("roles.success.retrieved"),
2715
- __metadata16("design:type", Function),
2716
- __metadata16("design:paramtypes", []),
2717
- __metadata16("design:returntype", Promise)
2718
- ], RoleController.prototype, "getRoles", null);
2719
- __decorate16([
2720
- Get2("/:id"),
2721
- isAdmin(),
2722
- Validate2({ params: roleIdParam }),
2723
- ResMsg2("roles.success.retrieved"),
2724
- __param6(0, Params()),
2725
- __metadata16("design:type", Function),
2726
- __metadata16("design:paramtypes", [Object]),
2727
- __metadata16("design:returntype", Promise)
2728
- ], RoleController.prototype, "getRole", null);
2729
- __decorate16([
2730
- Post2(),
2731
- isAdmin(),
2732
- Validate2(createRoleDto),
2733
- ResMsg2("roles.success.created"),
2734
- __param6(0, Body2()),
2735
- __metadata16("design:type", Function),
2736
- __metadata16("design:paramtypes", [Object]),
2737
- __metadata16("design:returntype", Promise)
2738
- ], RoleController.prototype, "createRole", null);
2739
- __decorate16([
2740
- Put("/:id"),
2741
- isAdmin(),
2742
- Validate2({
2743
- params: roleIdParam,
2744
- body: updateRoleDto
2745
- }),
2746
- ResMsg2("roles.success.updated"),
2747
- __param6(0, Params()),
2748
- __param6(1, Body2()),
2749
- __metadata16("design:type", Function),
2750
- __metadata16("design:paramtypes", [Object, Object]),
2751
- __metadata16("design:returntype", Promise)
2752
- ], RoleController.prototype, "updateRole", null);
2753
- __decorate16([
2754
- Delete("/:id"),
2755
- isAdmin(),
2756
- Validate2({ params: roleIdParam }),
2757
- ResMsg2("roles.success.deleted"),
2758
- __param6(0, Params()),
2759
- __metadata16("design:type", Function),
2760
- __metadata16("design:paramtypes", [Object]),
2761
- __metadata16("design:returntype", Promise)
2762
- ], RoleController.prototype, "deleteRole", null);
2763
- RoleController = __decorate16([
2764
- Controller2("/roles"),
2765
- __metadata16("design:paramtypes", [typeof (_a10 = typeof RoleService !== "undefined" && RoleService) === "function" ? _a10 : Object])
2766
- ], RoleController);
2767
-
2768
- // src/users/UserController.ts
2769
2882
  import { Validate as Validate3 } from "najm-validation";
2770
2883
  var __decorate17 = function(decorators, target, key, desc) {
2771
2884
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -3577,7 +3690,7 @@ var revokeTokenDto = z4.object({
3577
3690
  });
3578
3691
 
3579
3692
  // src/ownership/scopedOwnership.ts
3580
- import { aliasedTable, eq as eq6, getTableColumns, sql as sql5 } from "drizzle-orm";
3693
+ import { aliasedTable, eq as eq6, getTableColumns, sql as sql4 } from "drizzle-orm";
3581
3694
  var DEFAULT_ADMIN_ROLES = ["admin"];
3582
3695
  var DRIZZLE_NAME = /* @__PURE__ */ Symbol.for("drizzle:Name");
3583
3696
  var DRIZZLE_BASE_NAME = /* @__PURE__ */ Symbol.for("drizzle:BaseName");
@@ -3714,7 +3827,7 @@ var OwnershipToken = class {
3714
3827
  return query;
3715
3828
  const rule = this._rules[role];
3716
3829
  if (!rule)
3717
- return query.where(sql5`1 = 0`);
3830
+ return query.where(sql4`1 = 0`);
3718
3831
  const { query: q, condition } = rule(uid, query);
3719
3832
  return q.where(condition);
3720
3833
  }
@@ -3729,7 +3842,7 @@ var OwnershipToken = class {
3729
3842
  return { query, condition: null };
3730
3843
  const rule = this._rules[role];
3731
3844
  if (!rule)
3732
- return { query, condition: sql5`1 = 0` };
3845
+ return { query, condition: sql4`1 = 0` };
3733
3846
  return rule(uid, query);
3734
3847
  }
3735
3848
  };
@@ -4088,7 +4201,7 @@ __name(Policy, "Policy");
4088
4201
 
4089
4202
  // src/ownership/OwnedDecorator.ts
4090
4203
  import "reflect-metadata";
4091
- import { sql as sql6, and as and3 } from "drizzle-orm";
4204
+ import { sql as sql5, and as and3 } from "drizzle-orm";
4092
4205
  import { Injectable as Injectable12, Inject as Inject12, DI as DI2, Container as Container2, REQUEST_ID } from "najm-core";
4093
4206
  import { USER as USER2 } from "najm-guard";
4094
4207
  var __decorate24 = function(decorators, target, key, desc) {
@@ -4177,7 +4290,7 @@ function Owned(token) {
4177
4290
  return query;
4178
4291
  const user = getUser(this);
4179
4292
  if (!user)
4180
- return query.where(sql6`1 = 0`);
4293
+ return query.where(sql5`1 = 0`);
4181
4294
  return token.applyScope(user.id, user.role, query);
4182
4295
  };
4183
4296
  },
@@ -4249,7 +4362,16 @@ var en_default = {
4249
4362
  passwordChanged: "Password changed successfully",
4250
4363
  passwordResetSent: "If that email exists, a reset link has been sent",
4251
4364
  passwordReset: "Password has been reset successfully",
4365
+ accountInviteSent: "Invitation sent successfully",
4252
4366
  tokenRefreshed: "Token refreshed successfully"
4367
+ },
4368
+ emails: {
4369
+ passwordReset: {
4370
+ subject: "Reset your password"
4371
+ },
4372
+ accountInvite: {
4373
+ subject: "You've been invited \u2014 set up your account"
4374
+ }
4253
4375
  }
4254
4376
  },
4255
4377
  users: {
@@ -4365,14 +4487,12 @@ var selectSchema = /* @__PURE__ */ __name((config) => {
4365
4487
  switch (dialect) {
4366
4488
  case "sqlite":
4367
4489
  return authSchema2;
4368
- case "mysql":
4369
- return authSchema3;
4370
4490
  case "pg":
4371
4491
  default:
4372
4492
  return authSchema;
4373
4493
  }
4374
4494
  }, "selectSchema");
4375
- var auth = /* @__PURE__ */ __name((config) => plugin("auth").version("1.0.0").depends(cache(), cookies(), i18n(), guards(), validation(config?.validation), rateLimit(config?.rateLimit), email()).requires("database").contributes(I18N_CONTRIBUTIONS, AUTH_LOCALES).services(auth_exports, users_exports, roles_exports, permissions_exports, tokens_exports, ScopeContext).config(AUTH_CONFIG, mergeConfig(config)).set(AUTH_SCHEMA, selectSchema(config)).set(AUTH_ENCRYPTION_KEY, config?.encryptionKey ?? null).build(), "auth");
4495
+ var auth = /* @__PURE__ */ __name((config) => plugin("auth").version("1.0.0").depends(cache(), cookies(), i18n(), guards(), validation(config?.validation), rateLimit(config?.rateLimit), email()).requires("database").contributes(I18N_CONTRIBUTIONS, AUTH_LOCALES).services(AUTH_MODULE, users_exports, roles_exports, permissions_exports, tokens_exports, ScopeContext).config(AUTH_CONFIG, mergeConfig(config)).set(AUTH_SCHEMA, selectSchema(config)).set(AUTH_ENCRYPTION_KEY, config?.encryptionKey ?? null).build(), "auth");
4376
4496
 
4377
4497
  // src/seed.ts
4378
4498
  var toSeedId = /* @__PURE__ */ __name((prefix, value) => {
@@ -4576,6 +4696,7 @@ export {
4576
4696
  formatDate,
4577
4697
  getAuthLocale,
4578
4698
  getAvatarFile,
4699
+ inviteUserDto,
4579
4700
  isAdmin,
4580
4701
  isAdministrator,
4581
4702
  isAuth,