@porulle/core 0.58.0 → 0.59.0

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.
@@ -11,5 +11,10 @@
11
11
  * better-call's `APIError` carries `name === "APIError"` and a numeric
12
12
  * `statusCode`; its `status` is a string such as `"UNAUTHORIZED"`.
13
13
  */
14
- export declare function isCredentialRejection(err: unknown): boolean;
14
+ export declare function isCredentialRejection(err: unknown): err is {
15
+ name: "APIError";
16
+ statusCode: number;
17
+ };
18
+ /** The HTTP status better-auth attached to a credential rejection, or null for anything else. */
19
+ export declare function credentialRejectionStatus(err: unknown): number | null;
15
20
  //# sourceMappingURL=auth-failure.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-failure.d.ts","sourceRoot":"","sources":["../../src/auth/auth-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAI3D"}
1
+ {"version":3,"file":"auth-failure.d.ts","sourceRoot":"","sources":["../../src/auth/auth-failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAInG;AAED,iGAAiG;AACjG,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAErE"}
@@ -17,3 +17,7 @@ export function isCredentialRejection(err) {
17
17
  const { name, statusCode } = err;
18
18
  return name === "APIError" && typeof statusCode === "number";
19
19
  }
20
+ /** The HTTP status better-auth attached to a credential rejection, or null for anything else. */
21
+ export function credentialRejectionStatus(err) {
22
+ return isCredentialRejection(err) ? err.statusCode : null;
23
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAc/C,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,yBAAuB,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAInE;AAKD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,GACrB,iBAAiB,CAuPnB"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAc/C,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,yBAAuB,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAInE;AAKD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,GACrB,iBAAiB,CAsQnB"}
@@ -1,6 +1,6 @@
1
1
  import { getCustomerPermissions, resolveActor } from "./actor.js";
2
2
  import { DEFAULT_ORG_ID } from "./org.js";
3
- import { isCredentialRejection } from "./auth-failure.js";
3
+ import { credentialRejectionStatus, isCredentialRejection } from "./auth-failure.js";
4
4
  import { isStrictOrgResolution } from "./strict-org-resolution.js";
5
5
  import { isIdentityFreeRoute } from "./identity-free-routes.js";
6
6
  function emptyToNull(value) {
@@ -182,14 +182,28 @@ export function authMiddleware(auth, config) {
182
182
  }
183
183
  }
184
184
  catch (err) {
185
- // An invalid, expired, or rate-limited key is a rejection: fall through
186
- // to anonymous. Anything else means the key was never checked.
185
+ // A rejection better-auth raised was an evaluated credential; anything else means the key
186
+ // was never checked, and is a fault. A rate-limited key is not a bad one: say so.
187
187
  if (!isCredentialRejection(err)) {
188
188
  reportAuthCheckFault(err, "api_key");
189
189
  throw err;
190
190
  }
191
+ if (credentialRejectionStatus(err) === 429) {
192
+ return c.json({ error: { code: "RATE_LIMITED", message: "Too many requests for this credential." } }, 429);
193
+ }
191
194
  }
192
195
  }
196
+ // A credential the caller PRESENTED and that did not verify is refused, not served as a guest.
197
+ // Falling through to anonymous gave a shopper whose token had expired a fresh guest cart in
198
+ // place of theirs, and told a broken client nothing. Absent credentials stay anonymous (guest
199
+ // checkout depends on it), and a stale session COOKIE is not "presented": browsers carry one on
200
+ // every public page, and refusing it would 401 logged-out browsing.
201
+ const presented = (c.req.header("x-api-key") ?? "").trim() !== "" || (c.req.header("authorization") ?? "").trim() !== "";
202
+ if (presented) {
203
+ const refused = c.json({ error: { code: "UNAUTHORIZED", message: "The presented credential could not be verified." } }, 401);
204
+ applyAuthenticateChallenge(refused);
205
+ return refused;
206
+ }
193
207
  if (!c.get("actor")) {
194
208
  // For anonymous requests in multi-store deployments, resolve the
195
209
  // store so catalog/search queries return the right store's data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.58.0",
3
+ "version": "0.59.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,8 +11,13 @@
11
11
  * better-call's `APIError` carries `name === "APIError"` and a numeric
12
12
  * `statusCode`; its `status` is a string such as `"UNAUTHORIZED"`.
13
13
  */
14
- export function isCredentialRejection(err: unknown): boolean {
14
+ export function isCredentialRejection(err: unknown): err is { name: "APIError"; statusCode: number } {
15
15
  if (typeof err !== "object" || err === null) return false;
16
16
  const { name, statusCode } = err as { name?: unknown; statusCode?: unknown };
17
17
  return name === "APIError" && typeof statusCode === "number";
18
18
  }
19
+
20
+ /** The HTTP status better-auth attached to a credential rejection, or null for anything else. */
21
+ export function credentialRejectionStatus(err: unknown): number | null {
22
+ return isCredentialRejection(err) ? err.statusCode : null;
23
+ }
@@ -4,7 +4,7 @@ import type { Actor } from "./types.js";
4
4
  import type { AuthInstance } from "./setup.js";
5
5
  import { getCustomerPermissions, resolveActor } from "./actor.js";
6
6
  import { DEFAULT_ORG_ID } from "./org.js";
7
- import { isCredentialRejection } from "./auth-failure.js";
7
+ import { credentialRejectionStatus, isCredentialRejection } from "./auth-failure.js";
8
8
  import { isStrictOrgResolution } from "./strict-org-resolution.js";
9
9
  import { isIdentityFreeRoute } from "./identity-free-routes.js";
10
10
 
@@ -208,15 +208,30 @@ export function authMiddleware(
208
208
  return;
209
209
  }
210
210
  } catch (err) {
211
- // An invalid, expired, or rate-limited key is a rejection: fall through
212
- // to anonymous. Anything else means the key was never checked.
211
+ // A rejection better-auth raised was an evaluated credential; anything else means the key
212
+ // was never checked, and is a fault. A rate-limited key is not a bad one: say so.
213
213
  if (!isCredentialRejection(err)) {
214
214
  reportAuthCheckFault(err, "api_key");
215
215
  throw err;
216
216
  }
217
+ if (credentialRejectionStatus(err) === 429) {
218
+ return c.json({ error: { code: "RATE_LIMITED", message: "Too many requests for this credential." } }, 429);
219
+ }
217
220
  }
218
221
  }
219
222
 
223
+ // A credential the caller PRESENTED and that did not verify is refused, not served as a guest.
224
+ // Falling through to anonymous gave a shopper whose token had expired a fresh guest cart in
225
+ // place of theirs, and told a broken client nothing. Absent credentials stay anonymous (guest
226
+ // checkout depends on it), and a stale session COOKIE is not "presented": browsers carry one on
227
+ // every public page, and refusing it would 401 logged-out browsing.
228
+ const presented = (c.req.header("x-api-key") ?? "").trim() !== "" || (c.req.header("authorization") ?? "").trim() !== "";
229
+ if (presented) {
230
+ const refused = c.json({ error: { code: "UNAUTHORIZED", message: "The presented credential could not be verified." } }, 401);
231
+ applyAuthenticateChallenge(refused);
232
+ return refused;
233
+ }
234
+
220
235
  if (!c.get("actor")) {
221
236
  // For anonymous requests in multi-store deployments, resolve the
222
237
  // store so catalog/search queries return the right store's data.