najm-auth 3.1.3 → 3.1.5

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
@@ -272,9 +272,13 @@ login, `AuthSessionService.establish()`, Google OAuth (which redirects with
272
272
  recovery — so verified-email OAuth linking cannot skip it. Marking a new
273
273
  requirement also revokes the user's current sessions.
274
274
 
275
- `withAuthCookiePersistence` recognizes the setup response on its own: it drops
276
- any session cookies the response carried, clears the remembered preference, and
277
- leaves the opaque setup cookie alone.
275
+ `withAuthCookiePersistence` recognizes logout and setup boundaries on its own.
276
+ After a successful logout it drops stale auth-cookie issuances and guarantees
277
+ exactly one deletion for each configured auth cookie. It preserves a valid
278
+ upstream deletion (including a custom cookie path), or synthesizes a canonical
279
+ deletion when one is missing. A setup response gets the same auth-cookie
280
+ deletions, clears the remembered preference, and leaves the opaque setup cookie
281
+ alone.
278
282
 
279
283
  ### Google Sign-In
280
284
 
@@ -721,7 +725,7 @@ limits are active when `auth()` is registered.
721
725
  | Route | Limit | Window | Key Strategy |
722
726
  |-------|-------|--------|--------------|
723
727
  | `POST /auth/register` | 5 | 15 minutes | IP |
724
- | `POST /auth/login` | 5 | 15 minutes | IP |
728
+ | `POST /auth/login` | 8 | 10 minutes | IP + hashed normalized identity |
725
729
  | `POST /auth/refresh` | 15 | 15 minutes | Cookie fingerprint |
726
730
  | `POST /auth/session/recover` | 120 | 1 minute | Cookie fingerprint |
727
731
  | `POST /auth/logout` | 10 | 15 minutes | User ID |
@@ -731,6 +735,23 @@ limits are active when `auth()` is registered.
731
735
 
732
736
  ### Customizing Rate Limits
733
737
 
738
+ The login route has strict environment overrides. Values are read when the
739
+ server imports `najm-auth`, so restart the process after changing them. Invalid
740
+ values fail startup rather than silently weakening the limiter.
741
+
742
+ ```bash
743
+ NAJM_AUTH_LOGIN_RATE_LIMIT_ENABLED=true
744
+ NAJM_AUTH_LOGIN_RATE_LIMIT=8
745
+ NAJM_AUTH_LOGIN_RATE_WINDOW=10m
746
+ ```
747
+
748
+ `NAJM_AUTH_LOGIN_RATE_LIMIT_ENABLED=false` disables only the login-route
749
+ limiter. Keep it enabled on public production deployments; a shorter window is
750
+ the safer setting for a disposable production-built demo.
751
+
752
+ The generic plugin configuration remains available for global limits and skip
753
+ rules:
754
+
734
755
  ```typescript
735
756
  auth({
736
757
  rateLimit: {
@@ -248,8 +248,8 @@ interface AuthCookiePersistenceOptions {
248
248
  * Najm's own setup response is recognized without this — supply it only to
249
249
  * cover an application-specific shape. Such a response may carry auth
250
250
  * cookies anyway, and persisting them would leave a half-authenticated
251
- * browser that skips the setup step on reload. Returning `true` strips them
252
- * and clears the stored choice.
251
+ * browser that skips the setup step on reload. Returning `true` replaces
252
+ * them with deletions and clears the stored choice.
253
253
  */
254
254
  isSetupResponse?: (payload: unknown) => boolean;
255
255
  }
@@ -1661,6 +1661,18 @@ function clearedRememberCookie(name, secure) {
1661
1661
  ].join("; ");
1662
1662
  }
1663
1663
  __name(clearedRememberCookie, "clearedRememberCookie");
1664
+ function clearedAuthCookie(name, secure) {
1665
+ return [
1666
+ `${name}=`,
1667
+ "Path=/",
1668
+ "HttpOnly",
1669
+ "SameSite=Lax",
1670
+ ...secure ? ["Secure"] : [],
1671
+ "Expires=Thu, 01 Jan 1970 00:00:00 GMT",
1672
+ "Max-Age=0"
1673
+ ].join("; ");
1674
+ }
1675
+ __name(clearedAuthCookie, "clearedAuthCookie");
1664
1676
  function withAuthCookiePersistence(handler, options = {}) {
1665
1677
  const {
1666
1678
  authCookieNames = DEFAULTS.authCookieNames,
@@ -1697,8 +1709,15 @@ function withAuthCookiePersistence(handler, options = {}) {
1697
1709
  const headers = new Headers(response.headers);
1698
1710
  const setCookies = headers.getSetCookie();
1699
1711
  headers.delete("set-cookie");
1712
+ const clearedAuthCookies = /* @__PURE__ */ new Set();
1700
1713
  for (const setCookie of setCookies) {
1701
- if (action.type === "setup" && authCookieNames.includes(cookieName(setCookie)) && !isDeletionCookie(setCookie)) {
1714
+ const name = cookieName(setCookie);
1715
+ const isAuthCookie = authCookieNames.includes(name);
1716
+ if ((action.type === "clear" || action.type === "setup") && isAuthCookie) {
1717
+ if (isDeletionCookie(setCookie) && !clearedAuthCookies.has(name)) {
1718
+ headers.append("set-cookie", setCookie);
1719
+ clearedAuthCookies.add(name);
1720
+ }
1702
1721
  continue;
1703
1722
  }
1704
1723
  headers.append(
@@ -1706,6 +1725,13 @@ function withAuthCookiePersistence(handler, options = {}) {
1706
1725
  action.type === "apply" && action.mode === "session" ? makeSessionCookie(setCookie, authCookieNames) : setCookie
1707
1726
  );
1708
1727
  }
1728
+ if (action.type === "clear" || action.type === "setup") {
1729
+ for (const name of authCookieNames) {
1730
+ if (!clearedAuthCookies.has(name)) {
1731
+ headers.append("set-cookie", clearedAuthCookie(name, secure));
1732
+ }
1733
+ }
1734
+ }
1709
1735
  headers.append(
1710
1736
  "set-cookie",
1711
1737
  action.type === "clear" || action.type === "setup" ? clearedRememberCookie(rememberCookieName, secure) : rememberCookie(rememberCookieName, action.mode, secure, maxAgeSeconds)
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as najm_core from 'najm-core';
2
2
  import { Container } from 'najm-core';
3
3
  import { ValidationPluginConfig } from 'najm-validation';
4
- import { RateLimitPluginConfig } from 'najm-rate';
4
+ import { RateLimitPluginConfig, TimeWindow } from 'najm-rate';
5
5
  import { EmailPluginConfig, EmailService } from 'najm-email';
6
6
  import { R as ResolvedIdentityConfig, I as IdentityConfig, T as TemporaryCredentialInput } from './ma-sNHnUGLO.js';
7
7
  export { D as DEFAULT_IDENTITY_PRESET, E as EXACT_TEMPORARY_CREDENTIAL_KIND, a as IDENTITY_PRESETS, b as IdentityNormalizer, c as IdentityPreset, d as IdentityPresetName, M as MOROCCAN_CIN_TEMPORARY_CREDENTIAL_KIND, e as TemporaryCredential, f as TemporaryCredentialKind, g as compactPhone, i as isMoroccanCin, h as isTemporaryCredentialKind, m as moroccanCinTemporaryCredential, j as moroccoIdentityPreset, n as normalizeMoroccanCin, k as normalizeMoroccanPhone, l as normalizeTunisianPhone, r as resolveTemporaryCredentialKind, t as toTemporaryCredential, o as tunisiaIdentityPreset } from './ma-sNHnUGLO.js';
@@ -705,7 +705,6 @@ declare class UserValidator {
705
705
  * Check if user exists by email
706
706
  */
707
707
  checkUserExistsByEmail(email: string): Promise<{
708
- password: string;
709
708
  id: string;
710
709
  name: string;
711
710
  createdAt: string;
@@ -714,8 +713,9 @@ declare class UserValidator {
714
713
  emailVerified: boolean;
715
714
  phone: string;
716
715
  phoneVerified: boolean;
716
+ password: string;
717
717
  image: string;
718
- status: "active" | "pending" | "inactive";
718
+ status: "active" | "inactive" | "pending";
719
719
  roleId: string;
720
720
  lastLogin: string;
721
721
  failedLoginAttempts: number;
@@ -727,7 +727,6 @@ declare class UserValidator {
727
727
  * Check if email exists in database
728
728
  */
729
729
  checkEmailExists(email: string): Promise<{
730
- password: string;
731
730
  id: string;
732
731
  name: string;
733
732
  createdAt: string;
@@ -736,8 +735,9 @@ declare class UserValidator {
736
735
  emailVerified: boolean;
737
736
  phone: string;
738
737
  phoneVerified: boolean;
738
+ password: string;
739
739
  image: string;
740
- status: "active" | "pending" | "inactive";
740
+ status: "active" | "inactive" | "pending";
741
741
  roleId: string;
742
742
  lastLogin: string;
743
743
  failedLoginAttempts: number;
@@ -1260,8 +1260,8 @@ declare const createUserDto: z.ZodObject<{
1260
1260
  emailVerified: z.ZodDefault<z.ZodBoolean>;
1261
1261
  status: z.ZodOptional<z.ZodEnum<{
1262
1262
  active: "active";
1263
- pending: "pending";
1264
1263
  inactive: "inactive";
1264
+ pending: "pending";
1265
1265
  }>>;
1266
1266
  }, z.core.$strip>;
1267
1267
  declare const updateUserDto: z.ZodObject<{
@@ -1273,8 +1273,8 @@ declare const updateUserDto: z.ZodObject<{
1273
1273
  emailVerified: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
1274
1274
  status: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
1275
1275
  active: "active";
1276
- pending: "pending";
1277
1276
  inactive: "inactive";
1277
+ pending: "pending";
1278
1278
  }>>>;
1279
1279
  }, z.core.$strip>;
1280
1280
  declare const registerDto: z.ZodObject<{
@@ -1655,7 +1655,6 @@ declare class AuthController {
1655
1655
  registerUser(body: RegisterDto): Promise<SanitizedUser>;
1656
1656
  loginUser(body: LoginDto): Promise<LoginResult>;
1657
1657
  inviteUser(body: InviteUserDto): Promise<Omit<{
1658
- password: string;
1659
1658
  id: string;
1660
1659
  name: string;
1661
1660
  createdAt: string;
@@ -1664,8 +1663,9 @@ declare class AuthController {
1664
1663
  emailVerified: boolean;
1665
1664
  phone: string;
1666
1665
  phoneVerified: boolean;
1666
+ password: string;
1667
1667
  image: string;
1668
- status: "active" | "pending" | "inactive";
1668
+ status: "active" | "inactive" | "pending";
1669
1669
  roleId: string;
1670
1670
  lastLogin: string;
1671
1671
  failedLoginAttempts: number;
@@ -1688,7 +1688,6 @@ declare class AuthController {
1688
1688
  message: string;
1689
1689
  }>;
1690
1690
  userProfile(authorization?: string): Promise<Omit<{
1691
- password: string;
1692
1691
  id: string;
1693
1692
  name: string;
1694
1693
  createdAt: string;
@@ -1697,8 +1696,9 @@ declare class AuthController {
1697
1696
  emailVerified: boolean;
1698
1697
  phone: string;
1699
1698
  phoneVerified: boolean;
1699
+ password: string;
1700
1700
  image: string;
1701
- status: "active" | "pending" | "inactive";
1701
+ status: "active" | "inactive" | "pending";
1702
1702
  roleId: string;
1703
1703
  lastLogin: string;
1704
1704
  failedLoginAttempts: number;
@@ -1786,6 +1786,24 @@ declare function createIdentityResolver(config?: IdentityConfig): IdentityResolv
1786
1786
  declare function normalizeAuthIdentifier(value: unknown): string | null;
1787
1787
  declare function isEmailIdentifier(value: string): boolean;
1788
1788
 
1789
+ declare const AUTH_LOGIN_RATE_LIMIT_ENV: {
1790
+ readonly enabled: "NAJM_AUTH_LOGIN_RATE_LIMIT_ENABLED";
1791
+ readonly limit: "NAJM_AUTH_LOGIN_RATE_LIMIT";
1792
+ readonly window: "NAJM_AUTH_LOGIN_RATE_WINDOW";
1793
+ };
1794
+ interface AuthLoginRateLimitConfig {
1795
+ enabled: boolean;
1796
+ limit: number;
1797
+ window: TimeWindow;
1798
+ }
1799
+ type AuthRateLimitEnvironment = Record<string, string | undefined>;
1800
+ declare const DEFAULT_AUTH_LOGIN_RATE_LIMIT: {
1801
+ readonly enabled: true;
1802
+ readonly limit: 8;
1803
+ readonly window: "10m";
1804
+ };
1805
+ declare function resolveAuthLoginRateLimitConfig(env?: AuthRateLimitEnvironment): AuthLoginRateLimitConfig;
1806
+
1789
1807
  interface RunAsUser {
1790
1808
  id: string;
1791
1809
  role?: string | null;
@@ -2747,4 +2765,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
2747
2765
  */
2748
2766
  declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
2749
2767
 
2750
- 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, AuthSessionService, type AuthUser, CREDENTIAL_SETUP_CODES, CREDENTIAL_SETUP_MODULE, 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 CredentialSetupChangeDto, type CredentialSetupCode, type CredentialSetupConfig, CredentialSetupController, type CredentialSetupOptions, type CredentialSetupPasswordOptions, type CredentialSetupPending, CredentialSetupRepository, CredentialSetupRequirementRepository, type CredentialSetupRequirementRow, CredentialSetupRequirementService, CredentialSetupService, type CredentialSetupSessionInfo, type CredentialSetupStarted, DEFAULT_CREDENTIAL_SETUP_COOKIE_NAME, DEFAULT_CREDENTIAL_SETUP_TTL_MS, type DefineRolesOptions, type EmailParam, EncryptionService, type GoogleOAuthConfig, IdentityConfig, type IdentityResolver, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, type LoginResult, NewPermission, NewRoleEntity, NewUser, type OAuthConfig, type OAuthProvider, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, PASSWORD_SETUP_PURPOSE, PasswordSetupService, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, type ProvisionUserWithPasswordInput, type ProvisionUserWithSetupInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, type ResetPasswordDto, type ResolvedCredentialSetupConfig, ResolvedIdentityConfig, 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, TemporaryCredentialInput, 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, authIdentityRateLimitKey, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createIdentityResolver, createPermissionDto, createRoleDto, createTokenDto, createUserDto, credentialSetupChangeDto, credentialSetupError, defaultCredentialSetupPasswordSchema, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmailIdentifier, isEmpty, isFile, isPath, join, languageParam, loginDto, normalizeAuthIdentifier, normalizeSetupPurpose, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
2768
+ export { AUTH_CONFIG, en as AUTH_EN, AUTH_LOCALES, AUTH_LOGIN_RATE_LIMIT_ENV, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthLoginRateLimitConfig, type AuthPluginConfig, AuthQueries, type AuthRateLimitEnvironment, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, AuthSessionService, type AuthUser, CREDENTIAL_SETUP_CODES, CREDENTIAL_SETUP_MODULE, 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 CredentialSetupChangeDto, type CredentialSetupCode, type CredentialSetupConfig, CredentialSetupController, type CredentialSetupOptions, type CredentialSetupPasswordOptions, type CredentialSetupPending, CredentialSetupRepository, CredentialSetupRequirementRepository, type CredentialSetupRequirementRow, CredentialSetupRequirementService, CredentialSetupService, type CredentialSetupSessionInfo, type CredentialSetupStarted, DEFAULT_AUTH_LOGIN_RATE_LIMIT, DEFAULT_CREDENTIAL_SETUP_COOKIE_NAME, DEFAULT_CREDENTIAL_SETUP_TTL_MS, type DefineRolesOptions, type EmailParam, EncryptionService, type GoogleOAuthConfig, IdentityConfig, type IdentityResolver, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, type LoginResult, NewPermission, NewRoleEntity, NewUser, type OAuthConfig, type OAuthProvider, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, PASSWORD_SETUP_PURPOSE, PasswordSetupService, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, type ProvisionUserWithPasswordInput, type ProvisionUserWithSetupInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, type ResetPasswordDto, type ResolvedCredentialSetupConfig, ResolvedIdentityConfig, 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, TemporaryCredentialInput, 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, authIdentityRateLimitKey, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createIdentityResolver, createPermissionDto, createRoleDto, createTokenDto, createUserDto, credentialSetupChangeDto, credentialSetupError, defaultCredentialSetupPasswordSchema, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmailIdentifier, isEmpty, isFile, isPath, join, languageParam, loginDto, normalizeAuthIdentifier, normalizeSetupPurpose, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, resolveAuthLoginRateLimitConfig, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
package/dist/index.js CHANGED
@@ -3583,6 +3583,75 @@ function getRequestIdentityResolver(context) {
3583
3583
  }
3584
3584
  __name(getRequestIdentityResolver, "getRequestIdentityResolver");
3585
3585
 
3586
+ // src/auth/authLoginRateLimitConfig.ts
3587
+ var AUTH_LOGIN_RATE_LIMIT_ENV = {
3588
+ enabled: "NAJM_AUTH_LOGIN_RATE_LIMIT_ENABLED",
3589
+ limit: "NAJM_AUTH_LOGIN_RATE_LIMIT",
3590
+ window: "NAJM_AUTH_LOGIN_RATE_WINDOW"
3591
+ };
3592
+ var DEFAULT_AUTH_LOGIN_RATE_LIMIT = {
3593
+ enabled: true,
3594
+ limit: 8,
3595
+ window: "10m"
3596
+ };
3597
+ function parseEnabled(raw) {
3598
+ if (raw === void 0 || raw.trim() === "") {
3599
+ return DEFAULT_AUTH_LOGIN_RATE_LIMIT.enabled;
3600
+ }
3601
+ const normalized = raw.trim().toLowerCase();
3602
+ if (normalized === "true")
3603
+ return true;
3604
+ if (normalized === "false")
3605
+ return false;
3606
+ throw new Error(`${AUTH_LOGIN_RATE_LIMIT_ENV.enabled} must be true or false`);
3607
+ }
3608
+ __name(parseEnabled, "parseEnabled");
3609
+ function parseLimit(raw) {
3610
+ if (raw === void 0 || raw.trim() === "") {
3611
+ return DEFAULT_AUTH_LOGIN_RATE_LIMIT.limit;
3612
+ }
3613
+ const normalized = raw.trim();
3614
+ if (!/^\d+$/.test(normalized)) {
3615
+ throw new Error(`${AUTH_LOGIN_RATE_LIMIT_ENV.limit} must be a positive safe integer`);
3616
+ }
3617
+ const limit = Number(normalized);
3618
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
3619
+ throw new Error(`${AUTH_LOGIN_RATE_LIMIT_ENV.limit} must be a positive safe integer`);
3620
+ }
3621
+ return limit;
3622
+ }
3623
+ __name(parseLimit, "parseLimit");
3624
+ function parseWindow(raw) {
3625
+ if (raw === void 0 || raw.trim() === "") {
3626
+ return DEFAULT_AUTH_LOGIN_RATE_LIMIT.window;
3627
+ }
3628
+ const normalized = raw.trim().toLowerCase();
3629
+ const match = normalized.match(/^([1-9]\d*)(s|m|h|d)$/);
3630
+ if (!match) {
3631
+ throw new Error(`${AUTH_LOGIN_RATE_LIMIT_ENV.window} must be a positive duration such as 30s, 10m, 1h, or 1d`);
3632
+ }
3633
+ const amount = Number(match[1]);
3634
+ const multiplier = {
3635
+ s: 1e3,
3636
+ m: 6e4,
3637
+ h: 36e5,
3638
+ d: 864e5
3639
+ }[match[2]];
3640
+ if (!Number.isSafeInteger(amount) || !Number.isSafeInteger(amount * multiplier)) {
3641
+ throw new Error(`${AUTH_LOGIN_RATE_LIMIT_ENV.window} is too large`);
3642
+ }
3643
+ return normalized;
3644
+ }
3645
+ __name(parseWindow, "parseWindow");
3646
+ function resolveAuthLoginRateLimitConfig(env = process.env) {
3647
+ return {
3648
+ enabled: parseEnabled(env[AUTH_LOGIN_RATE_LIMIT_ENV.enabled]),
3649
+ limit: parseLimit(env[AUTH_LOGIN_RATE_LIMIT_ENV.limit]),
3650
+ window: parseWindow(env[AUTH_LOGIN_RATE_LIMIT_ENV.window])
3651
+ };
3652
+ }
3653
+ __name(resolveAuthLoginRateLimitConfig, "resolveAuthLoginRateLimitConfig");
3654
+
3586
3655
  // src/users/UserDto.ts
3587
3656
  import { z } from "zod";
3588
3657
  var emailField = z.string().email("Invalid email format");
@@ -3692,6 +3761,7 @@ var authIdentityRateLimitKey = /* @__PURE__ */ __name(async (ctx) => {
3692
3761
  }
3693
3762
  return ip;
3694
3763
  }, "authIdentityRateLimitKey");
3764
+ var loginRateLimit = resolveAuthLoginRateLimitConfig();
3695
3765
  var AuthController = class AuthController2 {
3696
3766
  static {
3697
3767
  __name(this, "AuthController");
@@ -3748,7 +3818,13 @@ __decorate20([
3748
3818
  ], AuthController.prototype, "registerUser", null);
3749
3819
  __decorate20([
3750
3820
  Post("/login"),
3751
- RateLimit({ limit: 5, window: "15m", key: authIdentityRateLimitKey, message: "Too many login attempts. Please try again later." }),
3821
+ RateLimit({
3822
+ limit: loginRateLimit.limit,
3823
+ window: loginRateLimit.window,
3824
+ key: authIdentityRateLimitKey,
3825
+ message: "Too many login attempts. Please try again later.",
3826
+ skip: !loginRateLimit.enabled
3827
+ }),
3752
3828
  Validate(loginDto),
3753
3829
  ResMsg("auth.success.login"),
3754
3830
  __param5(0, Body()),
@@ -6885,6 +6961,7 @@ export {
6885
6961
  AUTH_CONFIG,
6886
6962
  en_default as AUTH_EN,
6887
6963
  AUTH_LOCALES,
6964
+ AUTH_LOGIN_RATE_LIMIT_ENV,
6888
6965
  AUTH_MODULE,
6889
6966
  AUTH_PERMISSIONS,
6890
6967
  AUTH_ROLE,
@@ -6911,6 +6988,7 @@ export {
6911
6988
  CredentialSetupRequirementRepository,
6912
6989
  CredentialSetupRequirementService,
6913
6990
  CredentialSetupService,
6991
+ DEFAULT_AUTH_LOGIN_RATE_LIMIT,
6914
6992
  DEFAULT_CREDENTIAL_SETUP_COOKIE_NAME,
6915
6993
  DEFAULT_CREDENTIAL_SETUP_TTL_MS,
6916
6994
  DEFAULT_IDENTITY_PRESET,
@@ -7007,6 +7085,7 @@ export {
7007
7085
  refreshTokenDto,
7008
7086
  registerDto,
7009
7087
  resetPasswordDto,
7088
+ resolveAuthLoginRateLimitConfig,
7010
7089
  resolveTemporaryCredentialKind,
7011
7090
  revokeTokenDto,
7012
7091
  roleIdParam,
@@ -235,7 +235,7 @@ declare const usersTable: drizzle_orm_pg_core.PgTableWithColumns<{
235
235
  tableName: "users";
236
236
  dataType: "string";
237
237
  columnType: "PgEnumColumn";
238
- data: "active" | "pending" | "inactive";
238
+ data: "active" | "inactive" | "pending";
239
239
  driverParam: string;
240
240
  notNull: false;
241
241
  hasDefault: true;
@@ -1308,7 +1308,7 @@ declare const authSchema: {
1308
1308
  tableName: "users";
1309
1309
  dataType: "string";
1310
1310
  columnType: "PgEnumColumn";
1311
- data: "active" | "pending" | "inactive";
1311
+ data: "active" | "inactive" | "pending";
1312
1312
  driverParam: string;
1313
1313
  notNull: false;
1314
1314
  hasDefault: true;
@@ -252,7 +252,7 @@ declare const usersTable: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
252
252
  tableName: "users";
253
253
  dataType: "string";
254
254
  columnType: "SQLiteText";
255
- data: "active" | "pending" | "inactive";
255
+ data: "active" | "inactive" | "pending";
256
256
  driverParam: string;
257
257
  notNull: false;
258
258
  hasDefault: true;
@@ -265,7 +265,7 @@ declare const usersTable: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
265
265
  generated: undefined;
266
266
  }, {}, {
267
267
  length: number;
268
- $type: "active" | "pending" | "inactive";
268
+ $type: "active" | "inactive" | "pending";
269
269
  }>;
270
270
  roleId: drizzle_orm_sqlite_core.SQLiteColumn<{
271
271
  name: "role_id";
@@ -1476,7 +1476,7 @@ declare const authSchema: {
1476
1476
  tableName: "users";
1477
1477
  dataType: "string";
1478
1478
  columnType: "SQLiteText";
1479
- data: "active" | "pending" | "inactive";
1479
+ data: "active" | "inactive" | "pending";
1480
1480
  driverParam: string;
1481
1481
  notNull: false;
1482
1482
  hasDefault: true;
@@ -1489,7 +1489,7 @@ declare const authSchema: {
1489
1489
  generated: undefined;
1490
1490
  }, {}, {
1491
1491
  length: number;
1492
- $type: "active" | "pending" | "inactive";
1492
+ $type: "active" | "inactive" | "pending";
1493
1493
  }>;
1494
1494
  roleId: drizzle_orm_sqlite_core.SQLiteColumn<{
1495
1495
  name: "role_id";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-auth",
3
- "version": "3.1.3",
3
+ "version": "3.1.5",
4
4
  "description": "Authentication and authorization library for najm framework",
5
5
  "type": "module",
6
6
  "files": [
@@ -97,7 +97,7 @@
97
97
  "najm-i18n": "^2.0.3",
98
98
  "najm-cache": "^2.0.2",
99
99
  "najm-email": "^2.0.2",
100
- "najm-rate": "^2.0.2",
100
+ "najm-rate": "^2.0.3",
101
101
  "najm-validation": "^2.0.2",
102
102
  "jsonwebtoken": "^9.0.3",
103
103
  "jose": "^6.1.3",