@getstrata/core 2.2.29 → 2.2.31

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 2.2.31
4
+
5
+ Preserve JWT issuance precision across millisecond revocation cutoffs without changing expiration. Generated MFA setup rejects stale concurrent state changes and atomically commits credential revocation in SQLite/non-RLS as well as Postgres RLS. Adds real HTTP initial/replacement races, revocation rollback and withheld-success recovery regressions; intentional fresh replacement remains supported.
6
+
7
+ ## 2.2.30
8
+
9
+ Basic authentication resolution is reused only within the same Request and unchanged credentials, preventing middleware/controller lookups from consuming MFA recovery proofs twice. Adds real HTTP replay/concurrency and credential/guard invalidation regressions.
10
+
3
11
  ## 2.2.29
4
12
 
5
13
  MFA recovery-code persistence atomically claims the validated snapshot and rejects stale requests before credential issuance. Basic authentication fails closed and consumes under the verified RLS identifier scope. Adds native SQLite/Postgres/MySQL concurrency and restricted-role regressions.
@@ -26,6 +26,7 @@ declare class CompositeGuard implements AuthGuard {
26
26
  }
27
27
  declare class AuthManager {
28
28
  private readonly guard;
29
+ private basicResolutions;
29
30
  private readonly namedGuards;
30
31
  constructor(guard: AuthGuard);
31
32
  registerGuard(name: string, next: AuthGuard): this;
@@ -124,11 +124,11 @@ function signaturesMatch(left, right) {
124
124
  return timingSafeEqual(leftBuffer, rightBuffer);
125
125
  }
126
126
  function signJwt(payload, options = {}) {
127
- const now = Math.floor(Date.now() / 1000);
127
+ const issuedAt = Date.now() / 1000;
128
128
  const body = {
129
129
  ...payload,
130
- iat: now,
131
- exp: now + jwtTtlSeconds(options.ttlSeconds)
130
+ iat: issuedAt,
131
+ exp: Math.floor(issuedAt) + jwtTtlSeconds(options.ttlSeconds)
132
132
  };
133
133
  const header = encodeJson({ alg: "HS256", typ: "JWT" });
134
134
  const data = encodeJson(body);
@@ -292,11 +292,11 @@ function signaturesMatch(left, right) {
292
292
  return timingSafeEqual(leftBuffer, rightBuffer);
293
293
  }
294
294
  function signJwt(payload, options = {}) {
295
- const now = Math.floor(Date.now() / 1000);
295
+ const issuedAt = Date.now() / 1000;
296
296
  const body = {
297
297
  ...payload,
298
- iat: now,
299
- exp: now + jwtTtlSeconds(options.ttlSeconds)
298
+ iat: issuedAt,
299
+ exp: Math.floor(issuedAt) + jwtTtlSeconds(options.ttlSeconds)
300
300
  };
301
301
  const header = encodeJson({ alg: "HS256", typ: "JWT" });
302
302
  const data = encodeJson(body);
package/dist/index.js CHANGED
@@ -1920,11 +1920,11 @@ function signaturesMatch(left, right) {
1920
1920
  return timingSafeEqual2(leftBuffer, rightBuffer);
1921
1921
  }
1922
1922
  function signJwt(payload, options = {}) {
1923
- const now = Math.floor(Date.now() / 1000);
1923
+ const issuedAt = Date.now() / 1000;
1924
1924
  const body = {
1925
1925
  ...payload,
1926
- iat: now,
1927
- exp: now + jwtTtlSeconds(options.ttlSeconds)
1926
+ iat: issuedAt,
1927
+ exp: Math.floor(issuedAt) + jwtTtlSeconds(options.ttlSeconds)
1928
1928
  };
1929
1929
  const header = encodeJson({ alg: "HS256", typ: "JWT" });
1930
1930
  const data = encodeJson(body);
@@ -2117,6 +2117,7 @@ var SESSION_GUARD_NAMES = ["web", "session", "default"];
2117
2117
 
2118
2118
  class AuthManager {
2119
2119
  guard;
2120
+ basicResolutions = new WeakMap;
2120
2121
  namedGuards = new Map;
2121
2122
  constructor(guard) {
2122
2123
  this.guard = guard;
@@ -2128,6 +2129,7 @@ class AuthManager {
2128
2129
  throw new Error("Auth guard name must not be empty.");
2129
2130
  }
2130
2131
  this.namedGuards.set(trimmed, next);
2132
+ this.basicResolutions = new WeakMap;
2131
2133
  return this;
2132
2134
  }
2133
2135
  use(name = "default") {
@@ -2148,7 +2150,25 @@ class AuthManager {
2148
2150
  if (!request) {
2149
2151
  return { user: currentAuthUser(), credentialSource: null };
2150
2152
  }
2151
- return await this.authenticateRequest(request);
2153
+ if (authorizationScheme(request) !== "basic") {
2154
+ this.basicResolutions.delete(request);
2155
+ return await this.authenticateRequest(request);
2156
+ }
2157
+ const authorization = request.headers.get("authorization");
2158
+ const mfaCode = request.headers.get("x-mfa-code");
2159
+ const cached = this.basicResolutions.get(request);
2160
+ if (cached && cached.authorization === authorization && cached.mfaCode === mfaCode) {
2161
+ return await cached.result;
2162
+ }
2163
+ const entry = { authorization, mfaCode, result: this.authenticateRequest(request) };
2164
+ this.basicResolutions.set(request, entry);
2165
+ try {
2166
+ return await entry.result;
2167
+ } catch (error) {
2168
+ if (this.basicResolutions.get(request) === entry)
2169
+ this.basicResolutions.delete(request);
2170
+ throw error;
2171
+ }
2152
2172
  }
2153
2173
  user(request) {
2154
2174
  return this.resolve(request);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "2.2.29",
3
+ "version": "2.2.31",
4
4
  "description": "Strata Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",