najm-auth 1.1.44 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -503,11 +503,13 @@ await seedAuthData({
503
503
 
504
504
  ---
505
505
 
506
- ## Rate Limiting
507
-
508
- Auth routes have built-in rate limiting to prevent brute force attacks.
509
-
510
- | Route | Limit | Window | Key Strategy |
506
+ ## Rate Limiting
507
+
508
+ Auth routes have built-in rate limiting to prevent brute force attacks.
509
+ The auth plugin registers `najm-rate` as a dependency, so these decorator-level
510
+ limits are active when `auth()` is registered.
511
+
512
+ | Route | Limit | Window | Key Strategy |
511
513
  |-------|-------|--------|--------------|
512
514
  | `POST /auth/register` | 5 | 15 minutes | IP |
513
515
  | `POST /auth/login` | 5 | 15 minutes | IP |
@@ -575,9 +577,22 @@ throw new HttpError(403, 'Insufficient permissions for this action');
575
577
 
576
578
  ---
577
579
 
578
- ## Security Considerations
579
-
580
- ### Password Reset Tokens
580
+ ## Security Considerations
581
+
582
+ ### Security Defaults
583
+
584
+ - JWT access and refresh secrets are required and must pass minimum strength
585
+ checks.
586
+ - Refresh tokens rotate by session family and suspected family compromise does
587
+ not revoke unrelated user sessions.
588
+ - Password reset and password change revoke existing user sessions.
589
+ - Login uses a dummy password hash for missing users to reduce timing leaks.
590
+ - Forgot-password responses avoid email enumeration.
591
+ - Auth routes register `najm-rate` and ship route-level brute-force limits.
592
+ - Session cookies are signed, short-lived, and checked against session version
593
+ invalidation.
594
+
595
+ ### Password Reset Tokens
581
596
 
582
597
  ⚠️ **Current behavior:** Reset tokens use JWT expiry (default 1h) for single-use validation. To add database-backed single-use tokens:
583
598
 
@@ -138,7 +138,7 @@ function decodeToken(token) {
138
138
  }
139
139
  __name(decodeToken, "decodeToken");
140
140
  function isTokenExpired(decoded) {
141
- if (!decoded.exp) return false;
141
+ if (!decoded.exp) return true;
142
142
  return Date.now() / 1e3 >= decoded.exp;
143
143
  }
144
144
  __name(isTokenExpired, "isTokenExpired");
package/dist/index.d.ts CHANGED
@@ -63,6 +63,10 @@ interface AuthConfig {
63
63
  frontendUrl: string;
64
64
  /** Registration mode: 'active' auto-activates, 'pending' requires admin approval (default: 'active') */
65
65
  registrationMode: 'active' | 'pending';
66
+ /** When true, users with emailVerified=false are blocked from logging in (default: false) */
67
+ requireVerifiedEmail: boolean;
68
+ /** Cookie path for the refresh token. Scope to the refresh endpoint to limit exposure (default: '/') */
69
+ refreshCookiePath: string;
66
70
  /** Per-account lockout settings */
67
71
  lockout: LockoutConfig;
68
72
  /** Bcrypt work factor (default: 10) */
@@ -112,6 +116,10 @@ type AuthPluginConfig = {
112
116
  frontendUrl?: string;
113
117
  /** Registration mode: 'active' auto-activates new users, 'pending' requires admin approval (default: 'active') */
114
118
  registrationMode?: 'active' | 'pending';
119
+ /** Block login for users whose email is not verified (default: false) */
120
+ requireVerifiedEmail?: boolean;
121
+ /** Cookie path for the refresh token (default: '/'). Set e.g. '/auth' to keep it off unrelated routes. */
122
+ refreshCookiePath?: string;
115
123
  /** Per-account lockout settings */
116
124
  lockout?: Partial<LockoutConfig>;
117
125
  /** Bcrypt work factor (default: 10, valid range: 4-31) */
@@ -199,7 +207,8 @@ var auth = {
199
207
  unauthorized: "Unauthorized access",
200
208
  sessionExpired: "Session has expired",
201
209
  accountLocked: "Account is temporarily locked. Please try again later.",
202
- accountInactive: "Account is inactive. Please contact support."
210
+ accountInactive: "Account is inactive. Please contact support.",
211
+ emailNotVerified: "Please verify your email address before signing in."
203
212
  },
204
213
  success: {
205
214
  login: "Login successful",
@@ -242,7 +251,9 @@ var roles = {
242
251
  notFound: "Role not found",
243
252
  exists: "Role already exists",
244
253
  nameRequired: "Role name is required",
245
- cannotDeleteSystem: "Cannot delete system role"
254
+ cannotDeleteSystem: "Cannot delete system role",
255
+ cannotRenameSystem: "Cannot rename the system admin role",
256
+ roleInUse: "Cannot delete a role that is assigned to users"
246
257
  },
247
258
  success: {
248
259
  created: "Role created successfully",
@@ -302,6 +313,7 @@ declare const AUTH_LOCALES: {
302
313
  sessionExpired: string;
303
314
  accountLocked: string;
304
315
  accountInactive: string;
316
+ emailNotVerified: string;
305
317
  };
306
318
  success: {
307
319
  login: string;
@@ -345,6 +357,8 @@ declare const AUTH_LOCALES: {
345
357
  exists: string;
346
358
  nameRequired: string;
347
359
  cannotDeleteSystem: string;
360
+ cannotRenameSystem: string;
361
+ roleInUse: string;
348
362
  };
349
363
  success: {
350
364
  created: string;
@@ -406,6 +420,12 @@ interface SessionCookieData {
406
420
  };
407
421
  roles: string[];
408
422
  permissions: string[];
423
+ /**
424
+ * Per-user session version captured when the cookie was written. Lets the
425
+ * fast-path reader reject a cookie whose session has since been invalidated
426
+ * (password change/reset, logout-all) without hitting the database.
427
+ */
428
+ sessionVersion: number;
409
429
  /** Epoch ms when the cookie was written */
410
430
  iat: number;
411
431
  }
@@ -413,6 +433,7 @@ declare class CookieManager {
413
433
  private config;
414
434
  private cookieService;
415
435
  private get cookieName();
436
+ private get refreshCookiePath();
416
437
  private get sessionCookieName();
417
438
  private get sessionMaxAge();
418
439
  private get sessionSecret();
@@ -565,6 +586,9 @@ declare class RoleRepository {
565
586
  db: TDb;
566
587
  private schema;
567
588
  private get roles();
589
+ private get users();
590
+ /** True if any user currently references this role (blocks deletion). */
591
+ hasUsers(roleId: string): Promise<boolean>;
568
592
  getAll(): Promise<RoleEntity[]>;
569
593
  getById(id: string): Promise<RoleEntity | undefined>;
570
594
  getByName(name: string): Promise<RoleEntity | undefined>;
@@ -625,6 +649,7 @@ declare class RoleValidator {
625
649
  declare class RoleService {
626
650
  private roleRepository;
627
651
  private roleValidator;
652
+ private t;
628
653
  constructor(roleRepository: RoleRepository, roleValidator: RoleValidator);
629
654
  getAll(): Promise<{
630
655
  id: string;
@@ -654,14 +679,17 @@ declare class RoleService {
654
679
  createdAt: string;
655
680
  updatedAt: string;
656
681
  }>;
657
- update(id: any, data: any): Promise<{
682
+ update(id: string, data: {
683
+ name?: string;
684
+ description?: string;
685
+ }): Promise<{
658
686
  id: string;
659
687
  name: string;
660
688
  description: string;
661
689
  createdAt: string;
662
690
  updatedAt: string;
663
691
  }>;
664
- delete(id: any): Promise<{
692
+ delete(id: string): Promise<{
665
693
  id: string;
666
694
  name: string;
667
695
  description: string;
@@ -831,6 +859,12 @@ declare class TokenService {
831
859
  getTokenExpire(token: string): number | undefined;
832
860
  decodeAccessToken(token: string): JwtPayload | null;
833
861
  private signAccessToken;
862
+ /**
863
+ * Current per-user session version (0 when never invalidated). The signed
864
+ * session cookie stamps this so a fast-path reader can reject a cookie whose
865
+ * session was invalidated after it was written.
866
+ */
867
+ getSessionVersion(userId: string): Promise<number>;
834
868
  /**
835
869
  * Generate access token with unique jti for blacklist support.
836
870
  * Includes roles/permissions for client-side RBAC/PBAC.
@@ -855,6 +889,7 @@ declare class TokenService {
855
889
  tokenFamily: string;
856
890
  roles: string[];
857
891
  permissions: string[];
892
+ sessionVersion: number;
858
893
  accessToken: string;
859
894
  refreshToken: string;
860
895
  accessTokenExpiresAt: number;
@@ -888,6 +923,7 @@ declare class TokenService {
888
923
  tokenFamily: string;
889
924
  roles: string[];
890
925
  permissions: string[];
926
+ sessionVersion: number;
891
927
  accessToken: string;
892
928
  refreshToken: string;
893
929
  accessTokenExpiresAt: number;
@@ -995,6 +1031,12 @@ declare const updateUserDto: z.ZodObject<{
995
1031
  inactive: "inactive";
996
1032
  }>>>;
997
1033
  }, z.core.$strip>;
1034
+ declare const registerDto: z.ZodObject<{
1035
+ name: z.ZodOptional<z.ZodString>;
1036
+ email: z.ZodString;
1037
+ password: z.ZodString;
1038
+ image: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1039
+ }, z.core.$strip>;
998
1040
  declare const inviteUserDto: z.ZodObject<{
999
1041
  name: z.ZodOptional<z.ZodString>;
1000
1042
  email: z.ZodString;
@@ -1038,6 +1080,7 @@ declare const userListQuery: z.ZodObject<{
1038
1080
  }, z.core.$strip>;
1039
1081
  type CreateUserDto = z.infer<typeof createUserDto>;
1040
1082
  type UpdateUserDto = z.infer<typeof updateUserDto>;
1083
+ type RegisterDto = z.infer<typeof registerDto>;
1041
1084
  type InviteUserDto = z.infer<typeof inviteUserDto>;
1042
1085
  type UserIdParam = z.infer<typeof userIdParam>;
1043
1086
  type LoginDto = z.infer<typeof loginDto>;
@@ -1080,7 +1123,7 @@ declare class AuthService {
1080
1123
  private nextLockoutUntil;
1081
1124
  private getDummyHash;
1082
1125
  warmupPasswordHash(): Promise<void>;
1083
- registerUser(body: CreateUserDto): Promise<SanitizedUser>;
1126
+ registerUser(body: RegisterDto): Promise<SanitizedUser>;
1084
1127
  /**
1085
1128
  * Admin-initiated account creation. The user is created with a random,
1086
1129
  * unusable password (the schema requires one) and then emailed a one-time
@@ -1090,7 +1133,9 @@ declare class AuthService {
1090
1133
  * Email is best-effort: a send failure logs a warning but never rolls back
1091
1134
  * account creation (and with the console provider, nothing is actually sent).
1092
1135
  */
1093
- inviteUser(body: ProvisionUserInput): Promise<SanitizedUser>;
1136
+ inviteUser(body: ProvisionUserInput): Promise<SanitizedUser & {
1137
+ emailSent: boolean;
1138
+ }>;
1094
1139
  /**
1095
1140
  * Create a login for a person record. The branch is intentional and is the
1096
1141
  * single rule callers rely on:
@@ -1147,11 +1192,32 @@ declare class AuthService {
1147
1192
  declare class AuthController {
1148
1193
  private authService;
1149
1194
  constructor(authService: AuthService);
1150
- registerUser(body: CreateUserDto): Promise<SanitizedUser>;
1195
+ registerUser(body: RegisterDto): Promise<SanitizedUser>;
1151
1196
  loginUser(body: LoginDto): Promise<TokenPair & {
1152
1197
  user: SanitizedUser;
1153
1198
  }>;
1154
- inviteUser(body: InviteUserDto): Promise<SanitizedUser>;
1199
+ inviteUser(body: InviteUserDto): Promise<Omit<{
1200
+ id: string;
1201
+ name: string;
1202
+ createdAt: string;
1203
+ updatedAt: string;
1204
+ email: string;
1205
+ emailVerified: boolean;
1206
+ phone: string;
1207
+ phoneVerified: boolean;
1208
+ password: string;
1209
+ image: string;
1210
+ status: "active" | "pending" | "inactive";
1211
+ roleId: string;
1212
+ lastLogin: string;
1213
+ failedLoginAttempts: number;
1214
+ lockoutUntil: string;
1215
+ }, "password" | "failedLoginAttempts" | "lockoutUntil"> & {
1216
+ role?: string | null;
1217
+ permissions?: string[];
1218
+ } & {
1219
+ emailSent: boolean;
1220
+ }>;
1155
1221
  refreshTokens(): Promise<TokenPair>;
1156
1222
  logoutUser(userId: string, authorization?: string): Promise<{
1157
1223
  data: any;
@@ -2131,4 +2197,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
2131
2197
  */
2132
2198
  declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
2133
2199
 
2134
- export { AUTH_CONFIG, en as AUTH_EN, AUTH_LOCALES, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthPluginConfig, AuthQueries, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, type AuthUser, Can, CanCreate, CanDelete, CanList, CanRead, CanUpdate, type ChainableGuard, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type DefineRolesOptions, type EmailParam, EncryptionService, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, NewPermission, NewRoleEntity, NewUser, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type ResetPasswordDto, type ResourceAccessor, type ResourceGuards, type ResourceGuardsOptions, type RevokeTokenDto, Role, RoleController, RoleEntity, RoleGuard, type RoleIdParam, type RoleInput, RolePermission, RoleRepository, RoleService, type RoleType, RoleValidator, type RunAsUser, type SanitizedUser, ScopeContext, type ScopeResult, type SeedAuthDataConfig, type SeedAuthDataResult, type SeedUserConfig, type SessionCookieData, TOKEN_STATUS, TOKEN_TYPE, type TokenIdParam, type TokenPair, TokenRepository, TokenService, USER_STATUS, type UpdatePermissionDto, type UpdateRoleDto, type UpdateTokenDto, type UpdateUserDto, User, UserController, type UserIdInParam, type UserIdParam, type UserListQuery, UserRepository, UserService, UserValidator, type UserWithPermissions, type VerifyTokenDto, assignPermissionDto, assignRoleDto, assignRoleParams, auth$1 as auth, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createPermissionDto, createRoleDto, createTokenDto, createUserDto, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmpty, isFile, isPath, join, languageParam, loginDto, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, resetPasswordDto, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
2200
+ export { AUTH_CONFIG, en as AUTH_EN, AUTH_LOCALES, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthPluginConfig, AuthQueries, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, type AuthUser, Can, CanCreate, CanDelete, CanList, CanRead, CanUpdate, type ChainableGuard, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type DefineRolesOptions, type EmailParam, EncryptionService, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, NewPermission, NewRoleEntity, NewUser, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, type ResetPasswordDto, type ResourceAccessor, type ResourceGuards, type ResourceGuardsOptions, type RevokeTokenDto, Role, RoleController, RoleEntity, RoleGuard, type RoleIdParam, type RoleInput, RolePermission, RoleRepository, RoleService, type RoleType, RoleValidator, type RunAsUser, type SanitizedUser, ScopeContext, type ScopeResult, type SeedAuthDataConfig, type SeedAuthDataResult, type SeedUserConfig, type SessionCookieData, TOKEN_STATUS, TOKEN_TYPE, type TokenIdParam, type TokenPair, TokenRepository, TokenService, USER_STATUS, type UpdatePermissionDto, type UpdateRoleDto, type UpdateTokenDto, type UpdateUserDto, User, UserController, type UserIdInParam, type UserIdParam, type UserListQuery, UserRepository, UserService, UserValidator, type UserWithPermissions, type VerifyTokenDto, assignPermissionDto, assignRoleDto, assignRoleParams, auth$1 as auth, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createPermissionDto, createRoleDto, createTokenDto, createUserDto, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmpty, isFile, isPath, join, languageParam, loginDto, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };