@porulle/core 0.15.0 → 0.16.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.
@@ -1 +1 @@
1
- {"version":3,"file":"actor.d.ts","sourceRoot":"","sources":["../../src/auth/actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,eAAO,MAAM,kBAAkB,OAAO,CAAC;AACvC,eAAO,MAAM,mBAAmB,qBAAwC,CAAC;AAEzE,eAAO,MAAM,4BAA4B,0JAS/B,CAAC;AAEX,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAEvE;AAYD,8EAA8E;AAC9E,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,OAAsD,GAC9D,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAsEvB"}
1
+ {"version":3,"file":"actor.d.ts","sourceRoot":"","sources":["../../src/auth/actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,eAAO,MAAM,kBAAkB,OAAO,CAAC;AACvC,eAAO,MAAM,mBAAmB,qBAAwC,CAAC;AAEzE,eAAO,MAAM,4BAA4B,0JAS/B,CAAC;AAEX,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAEvE;AAYD,8EAA8E;AAC9E,wBAAsB,YAAY,CAChC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,OAAsD,GAC9D,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CA0EvB"}
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_ORG_ID } from "./org.js";
2
+ import { isCredentialRejection } from "./auth-failure.js";
2
3
  export const AUTH_COOKIE_PREFIX = "uc";
3
4
  export const SESSION_COOKIE_NAME = `${AUTH_COOKIE_PREFIX}.session_token`;
4
5
  export const DEFAULT_CUSTOMER_PERMISSIONS = [
@@ -29,8 +30,13 @@ export async function resolveActor(headers, auth, config, request = new Request(
29
30
  headers,
30
31
  }));
31
32
  }
32
- catch {
33
- return null;
33
+ catch (err) {
34
+ // A rejected session is anonymous. A session store that could not answer is
35
+ // a fault: reporting it as "no session" downgrades a signed-in caller
36
+ // silently and leaves nothing to debug.
37
+ if (isCredentialRejection(err))
38
+ return null;
39
+ throw err;
34
40
  }
35
41
  if (!session)
36
42
  return null;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Separates a credential better-auth evaluated and rejected from an auth check
3
+ * that failed to run. Only the first justifies telling a caller their
4
+ * credential is bad; the second is a fault and must surface as one.
5
+ *
6
+ * Matched structurally rather than with `instanceof APIError`. A cross-copy
7
+ * `instanceof` is false whenever two better-auth copies are resolved into one
8
+ * tree — which is precisely the fault this predicate has to stay honest about.
9
+ * `kernel/error-mapper.ts` reads `constructor?.name` for the same reason.
10
+ *
11
+ * better-call's `APIError` carries `name === "APIError"` and a numeric
12
+ * `statusCode`; its `status` is a string such as `"UNAUTHORIZED"`.
13
+ */
14
+ export declare function isCredentialRejection(err: unknown): boolean;
15
+ //# sourceMappingURL=auth-failure.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Separates a credential better-auth evaluated and rejected from an auth check
3
+ * that failed to run. Only the first justifies telling a caller their
4
+ * credential is bad; the second is a fault and must surface as one.
5
+ *
6
+ * Matched structurally rather than with `instanceof APIError`. A cross-copy
7
+ * `instanceof` is false whenever two better-auth copies are resolved into one
8
+ * tree — which is precisely the fault this predicate has to stay honest about.
9
+ * `kernel/error-mapper.ts` reads `constructor?.name` for the same reason.
10
+ *
11
+ * better-call's `APIError` carries `name === "APIError"` and a numeric
12
+ * `statusCode`; its `status` is a string such as `"UNAUTHORIZED"`.
13
+ */
14
+ export function isCredentialRejection(err) {
15
+ if (typeof err !== "object" || err === null)
16
+ return false;
17
+ const { name, statusCode } = err;
18
+ return name === "APIError" && typeof statusCode === "number";
19
+ }
@@ -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;AAY/C,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAK1D,wBAAgB,cAAc,CAC5B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,GACrB,iBAAiB,CAiMnB"}
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;AAa/C,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAK1D,wBAAgB,cAAc,CAC5B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,GACrB,iBAAiB,CAiNnB"}
@@ -1,5 +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
4
  import { isStrictOrgResolution } from "./strict-org-resolution.js";
4
5
  function emptyToNull(value) {
5
6
  return value == null || value === "" ? null : value;
@@ -31,7 +32,20 @@ export function authMiddleware(auth, config) {
31
32
  }
32
33
  }
33
34
  }
34
- const actor = await resolveActor(c.req.raw.headers, auth, config, c.req.raw);
35
+ // A caller told "Authentication required." goes and checks their
36
+ // credential. When the check never ran, that answer is a lie and there is
37
+ // nothing in the log to correct it. Report a fault as a fault.
38
+ const reportAuthCheckFault = (err, stage) => {
39
+ c.get("logger")?.error({ err, stage, path: c.req.path, method: c.req.method }, "auth check failed — the credential was never evaluated, so this is not an authentication failure");
40
+ };
41
+ let actor;
42
+ try {
43
+ actor = await resolveActor(c.req.raw.headers, auth, config, c.req.raw);
44
+ }
45
+ catch (err) {
46
+ reportAuthCheckFault(err, "session");
47
+ throw err;
48
+ }
35
49
  if (actor) {
36
50
  c.set("actor", actor);
37
51
  await next();
@@ -126,8 +140,13 @@ export function authMiddleware(auth, config) {
126
140
  return;
127
141
  }
128
142
  }
129
- catch {
130
- // invalid, expired, or rate-limited key fall through
143
+ catch (err) {
144
+ // An invalid, expired, or rate-limited key is a rejection: fall through
145
+ // to anonymous. Anything else means the key was never checked.
146
+ if (!isCredentialRejection(err)) {
147
+ reportAuthCheckFault(err, "api_key");
148
+ throw err;
149
+ }
131
150
  }
132
151
  }
133
152
  if (!c.get("actor")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
package/src/auth/actor.ts CHANGED
@@ -2,6 +2,7 @@ import type { AuthSessionLike, CommerceConfig } from "../config/types.js";
2
2
  import type { Actor } from "./types.js";
3
3
  import type { AuthInstance } from "./setup.js";
4
4
  import { DEFAULT_ORG_ID } from "./org.js";
5
+ import { isCredentialRejection } from "./auth-failure.js";
5
6
 
6
7
  export const AUTH_COOKIE_PREFIX = "uc";
7
8
  export const SESSION_COOKIE_NAME = `${AUTH_COOKIE_PREFIX}.session_token`;
@@ -43,8 +44,12 @@ export async function resolveActor(
43
44
  session = (await auth.api.getSession({
44
45
  headers,
45
46
  })) as AuthSessionLike | null;
46
- } catch {
47
- return null;
47
+ } catch (err) {
48
+ // A rejected session is anonymous. A session store that could not answer is
49
+ // a fault: reporting it as "no session" downgrades a signed-in caller
50
+ // silently and leaves nothing to debug.
51
+ if (isCredentialRejection(err)) return null;
52
+ throw err;
48
53
  }
49
54
 
50
55
  if (!session) return null;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Separates a credential better-auth evaluated and rejected from an auth check
3
+ * that failed to run. Only the first justifies telling a caller their
4
+ * credential is bad; the second is a fault and must surface as one.
5
+ *
6
+ * Matched structurally rather than with `instanceof APIError`. A cross-copy
7
+ * `instanceof` is false whenever two better-auth copies are resolved into one
8
+ * tree — which is precisely the fault this predicate has to stay honest about.
9
+ * `kernel/error-mapper.ts` reads `constructor?.name` for the same reason.
10
+ *
11
+ * better-call's `APIError` carries `name === "APIError"` and a numeric
12
+ * `statusCode`; its `status` is a string such as `"UNAUTHORIZED"`.
13
+ */
14
+ export function isCredentialRejection(err: unknown): boolean {
15
+ if (typeof err !== "object" || err === null) return false;
16
+ const { name, statusCode } = err as { name?: unknown; statusCode?: unknown };
17
+ return name === "APIError" && typeof statusCode === "number";
18
+ }
@@ -4,6 +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
8
  import { isStrictOrgResolution } from "./strict-org-resolution.js";
8
9
 
9
10
  function emptyToNull(value: string | null | undefined): string | null {
@@ -43,12 +44,23 @@ export function authMiddleware(
43
44
  }
44
45
  }
45
46
 
46
- const actor = await resolveActor(
47
- c.req.raw.headers,
48
- auth,
49
- config,
50
- c.req.raw,
51
- );
47
+ // A caller told "Authentication required." goes and checks their
48
+ // credential. When the check never ran, that answer is a lie and there is
49
+ // nothing in the log to correct it. Report a fault as a fault.
50
+ const reportAuthCheckFault = (err: unknown, stage: string): void => {
51
+ c.get("logger")?.error(
52
+ { err, stage, path: c.req.path, method: c.req.method },
53
+ "auth check failed — the credential was never evaluated, so this is not an authentication failure",
54
+ );
55
+ };
56
+
57
+ let actor: Actor | null;
58
+ try {
59
+ actor = await resolveActor(c.req.raw.headers, auth, config, c.req.raw);
60
+ } catch (err) {
61
+ reportAuthCheckFault(err, "session");
62
+ throw err;
63
+ }
52
64
  if (actor) {
53
65
  c.set("actor", actor);
54
66
  await next();
@@ -153,8 +165,13 @@ export function authMiddleware(
153
165
  await next();
154
166
  return;
155
167
  }
156
- } catch {
157
- // invalid, expired, or rate-limited key fall through
168
+ } catch (err) {
169
+ // An invalid, expired, or rate-limited key is a rejection: fall through
170
+ // to anonymous. Anything else means the key was never checked.
171
+ if (!isCredentialRejection(err)) {
172
+ reportAuthCheckFault(err, "api_key");
173
+ throw err;
174
+ }
158
175
  }
159
176
  }
160
177