najm-auth 3.3.0 → 3.3.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/dist/index.d.ts CHANGED
@@ -1084,16 +1084,19 @@ declare class TokenService {
1084
1084
  tokenFamily: string;
1085
1085
  };
1086
1086
  private static readonly PREVIOUS_GRACE_SECONDS;
1087
+ private clearRefreshSessionCookies;
1088
+ private rejectRefreshSession;
1089
+ private readRefreshSessionCookie;
1087
1090
  /**
1088
1091
  * Read the refresh cookie and return the userId it belongs to.
1089
1092
  * Validates against current/previous token state with bounded recovery.
1090
1093
  * Throws if the cookie is missing, invalid, or outside the grace window.
1091
1094
  *
1092
- * Intentionally side-effect-free: unlike refreshTokens(), a mismatch here
1093
- * does NOT revoke the suspect family. This is a read path (e.g. GET
1094
- * /auth/me) a stray stale cookie on a read must not be able to destroy
1095
- * the active session. Reuse detection and revocation belong to the
1096
- * rotation path only.
1095
+ * Intentionally does not revoke on a mismatch: unlike refreshTokens(), this
1096
+ * is a read path (e.g. GET /auth/me), so a stray stale cookie must not be
1097
+ * able to destroy server-side session state. Definitively invalid browser
1098
+ * cookies are still cleared so a signed snapshot cannot cause a redirect
1099
+ * loop.
1097
1100
  */
1098
1101
  private resolveRefreshSessionFromCookie;
1099
1102
  resolveUserFromCookie(): Promise<string>;
package/dist/index.js CHANGED
@@ -1914,26 +1914,42 @@ var TokenService = class TokenService2 {
1914
1914
  return { userId: decoded.userId, tokenFamily: decoded.tokenFamily };
1915
1915
  }
1916
1916
  static PREVIOUS_GRACE_SECONDS = 120;
1917
+ clearRefreshSessionCookies() {
1918
+ this.cookieManager.clearRefreshToken();
1919
+ this.cookieManager.clearSessionCookie();
1920
+ }
1921
+ rejectRefreshSession(messageKey = "errors.refreshTokenInvalid") {
1922
+ this.clearRefreshSessionCookies();
1923
+ Err7(this.t(messageKey), 401);
1924
+ }
1925
+ readRefreshSessionCookie() {
1926
+ const refreshToken = this.cookieManager.getRefreshToken();
1927
+ if (!refreshToken) {
1928
+ this.rejectRefreshSession("errors.refreshTokenMissing");
1929
+ }
1930
+ try {
1931
+ return { refreshToken, ...this.verifyRefreshToken(refreshToken) };
1932
+ } catch (error) {
1933
+ this.clearRefreshSessionCookies();
1934
+ throw error;
1935
+ }
1936
+ }
1917
1937
  /**
1918
1938
  * Read the refresh cookie and return the userId it belongs to.
1919
1939
  * Validates against current/previous token state with bounded recovery.
1920
1940
  * Throws if the cookie is missing, invalid, or outside the grace window.
1921
1941
  *
1922
- * Intentionally side-effect-free: unlike refreshTokens(), a mismatch here
1923
- * does NOT revoke the suspect family. This is a read path (e.g. GET
1924
- * /auth/me) a stray stale cookie on a read must not be able to destroy
1925
- * the active session. Reuse detection and revocation belong to the
1926
- * rotation path only.
1942
+ * Intentionally does not revoke on a mismatch: unlike refreshTokens(), this
1943
+ * is a read path (e.g. GET /auth/me), so a stray stale cookie must not be
1944
+ * able to destroy server-side session state. Definitively invalid browser
1945
+ * cookies are still cleared so a signed snapshot cannot cause a redirect
1946
+ * loop.
1927
1947
  */
1928
1948
  async resolveRefreshSessionFromCookie() {
1929
- const refreshToken = this.cookieManager.getRefreshToken();
1930
- if (!refreshToken) {
1931
- Err7(this.t("errors.refreshTokenMissing"), 401);
1932
- }
1933
- const { userId, tokenFamily } = this.verifyRefreshToken(refreshToken);
1949
+ const { refreshToken, userId, tokenFamily } = this.readRefreshSessionCookie();
1934
1950
  const stored = await this.tokenRepository.getByFamily(tokenFamily);
1935
1951
  if (!stored || stored.userId !== userId) {
1936
- Err7(this.t("errors.refreshTokenInvalid"), 401);
1952
+ this.rejectRefreshSession();
1937
1953
  }
1938
1954
  const presentedHash = this.hashToken(refreshToken);
1939
1955
  if (presentedHash === stored.token) {
@@ -1943,7 +1959,7 @@ var TokenService = class TokenService2 {
1943
1959
  if (canRecover) {
1944
1960
  return { userId, tokenFamily };
1945
1961
  }
1946
- Err7(this.t("errors.refreshTokenInvalid"), 401);
1962
+ this.rejectRefreshSession();
1947
1963
  }
1948
1964
  async resolveUserFromCookie() {
1949
1965
  return (await this.resolveRefreshSessionFromCookie()).userId;
@@ -2143,14 +2159,10 @@ var TokenService = class TokenService2 {
2143
2159
  * Compares provided token with hashed version in database
2144
2160
  */
2145
2161
  async refreshTokens() {
2146
- const refreshToken = this.cookieManager.getRefreshToken();
2147
- if (!refreshToken) {
2148
- Err7(this.t("errors.refreshTokenMissing"), 401);
2149
- }
2150
- const { userId, tokenFamily } = this.verifyRefreshToken(refreshToken);
2162
+ const { refreshToken, userId, tokenFamily } = this.readRefreshSessionCookie();
2151
2163
  const stored = await this.tokenRepository.getByFamily(tokenFamily);
2152
2164
  if (!stored || stored.userId !== userId) {
2153
- Err7(this.t("errors.refreshTokenInvalid"), 401);
2165
+ this.rejectRefreshSession();
2154
2166
  }
2155
2167
  const presentedHash = this.hashToken(refreshToken);
2156
2168
  await this.requireActiveRefreshUser(userId, tokenFamily);
@@ -2166,19 +2178,18 @@ var TokenService = class TokenService2 {
2166
2178
  return this.rotateTokens(userId, tokenFamily, stored.token);
2167
2179
  }
2168
2180
  await this.revokeSuspectRefreshFamily(userId, tokenFamily);
2169
- Err7(this.t("errors.refreshTokenInvalid"), 401);
2181
+ this.rejectRefreshSession();
2170
2182
  }
2171
2183
  async requireActiveRefreshUser(userId, tokenFamily) {
2172
2184
  const user = await this.tokenRepository.getUser(userId);
2173
2185
  if (!user || user.status !== "active") {
2174
2186
  await this.revokeFamily(tokenFamily);
2175
- Err7(this.t("errors.refreshTokenInvalid"), 401);
2187
+ this.rejectRefreshSession();
2176
2188
  }
2177
2189
  const requirement = await this.credentialSetupRequirements?.find(userId, PASSWORD_SETUP_PURPOSE);
2178
2190
  if (requirement?.required) {
2179
2191
  await this.revokeFamily(tokenFamily);
2180
- this.cookieManager.clearRefreshToken();
2181
- this.cookieManager.clearSessionCookie();
2192
+ this.clearRefreshSessionCookies();
2182
2193
  Err7(this.t("errors.credentialSetupRequired"), 403);
2183
2194
  }
2184
2195
  return user;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-auth",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "description": "Authentication and authorization library for najm framework",
5
5
  "type": "module",
6
6
  "files": [
@@ -91,13 +91,13 @@
91
91
  "dependencies": {
92
92
  "bcryptjs": "^3.0.2",
93
93
  "najm-cookies": "^2.0.2",
94
- "najm-core": "^2.0.5",
95
- "najm-database": "^2.0.3",
94
+ "najm-core": "^2.0.6",
95
+ "najm-database": "^2.0.4",
96
96
  "najm-guard": "^2.0.2",
97
- "najm-i18n": "^2.0.3",
98
- "najm-cache": "^2.1.0",
97
+ "najm-i18n": "^2.1.1",
98
+ "najm-cache": "^2.1.2",
99
99
  "najm-email": "^2.0.2",
100
- "najm-rate": "^2.1.0",
100
+ "najm-rate": "^2.1.1",
101
101
  "najm-validation": "^2.0.2",
102
102
  "jsonwebtoken": "^9.0.3",
103
103
  "jose": "^6.1.3",