@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.
- package/dist/auth/actor.d.ts.map +1 -1
- package/dist/auth/actor.js +8 -2
- package/dist/auth/auth-failure.d.ts +15 -0
- package/dist/auth/auth-failure.d.ts.map +1 -0
- package/dist/auth/auth-failure.js +19 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +22 -3
- package/package.json +1 -1
- package/src/auth/actor.ts +7 -2
- package/src/auth/auth-failure.ts +18 -0
- package/src/auth/middleware.ts +25 -8
package/dist/auth/actor.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/auth/actor.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
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"}
|
package/dist/auth/middleware.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
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
|
-
|
|
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
|
+
}
|
package/src/auth/middleware.ts
CHANGED
|
@@ -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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
c.
|
|
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
|
|
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
|
|