@oxyhq/core 21.0.0 → 21.0.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/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +47 -8
- package/dist/cjs/i18n/locales/en-US.json +7 -2
- package/dist/cjs/i18n/locales/es-ES.json +7 -2
- package/dist/cjs/i18n/locales/locales/en-US.json +7 -2
- package/dist/cjs/i18n/locales/locales/es-ES.json +7 -2
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/inference/OxyInferenceClient.js +330 -0
- package/dist/cjs/mixins/OxyServices.accounts.js +5 -72
- package/dist/cjs/mixins/OxyServices.inference.js +59 -0
- package/dist/cjs/mixins/OxyServices.utility.js +18 -6
- package/dist/cjs/mixins/index.js +6 -0
- package/dist/cjs/server/auth.js +76 -0
- package/dist/cjs/server/index.js +5 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +47 -8
- package/dist/esm/i18n/locales/en-US.json +7 -2
- package/dist/esm/i18n/locales/es-ES.json +7 -2
- package/dist/esm/i18n/locales/locales/en-US.json +7 -2
- package/dist/esm/i18n/locales/locales/es-ES.json +7 -2
- package/dist/esm/index.js +4 -0
- package/dist/esm/inference/OxyInferenceClient.js +325 -0
- package/dist/esm/mixins/OxyServices.accounts.js +5 -72
- package/dist/esm/mixins/OxyServices.inference.js +56 -0
- package/dist/esm/mixins/OxyServices.utility.js +18 -6
- package/dist/esm/mixins/index.js +6 -0
- package/dist/esm/server/auth.js +72 -0
- package/dist/esm/server/index.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +39 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/inference/OxyInferenceClient.d.ts +324 -0
- package/dist/types/mixins/OxyServices.accounts.d.ts +73 -95
- package/dist/types/mixins/OxyServices.inference.d.ts +95 -0
- package/dist/types/mixins/OxyServices.utility.d.ts +44 -13
- package/dist/types/mixins/index.d.ts +2 -1
- package/dist/types/server/auth.d.ts +80 -0
- package/dist/types/server/index.d.ts +2 -2
- package/package.json +2 -2
- package/src/HttpService.ts +50 -10
- package/src/__tests__/httpServiceUnwrapEnvelope.test.ts +115 -0
- package/src/i18n/locales/en-US.json +7 -2
- package/src/i18n/locales/es-ES.json +7 -2
- package/src/index.ts +19 -7
- package/src/inference/OxyInferenceClient.ts +590 -0
- package/src/inference/__tests__/OxyInferenceClient.test.ts +383 -0
- package/src/mixins/OxyServices.accounts.ts +75 -176
- package/src/mixins/OxyServices.inference.ts +57 -0
- package/src/mixins/OxyServices.utility.ts +58 -14
- package/src/mixins/__tests__/accounts.test.ts +57 -102
- package/src/mixins/__tests__/inferenceFactory.test.ts +58 -0
- package/src/mixins/__tests__/serviceAuth.test.ts +2 -0
- package/src/mixins/index.ts +8 -0
- package/src/server/__tests__/serviceTokenAttribution.test.ts +396 -0
- package/src/server/auth.ts +118 -0
- package/src/server/index.ts +6 -0
- package/src/session/__tests__/accountDialogShape.test.ts +118 -0
|
@@ -149,7 +149,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
149
149
|
* additionally checked for `aud`, `iss`, and `type` claims to prevent
|
|
150
150
|
* cross-token-type confusion attacks.
|
|
151
151
|
* - The backend's own `authMiddleware` uses `jwt.verify()` because it has
|
|
152
|
-
* direct access to `
|
|
152
|
+
* direct access to `ACCESS_TOKEN_SECRET`.
|
|
153
153
|
*
|
|
154
154
|
* **Why session-less user tokens are refused rather than trusted:**
|
|
155
155
|
* every user access token the Oxy API issues carries a `sessionId` (see
|
|
@@ -181,7 +181,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
181
181
|
* const oxy = new OxyServices({ baseURL: 'https://api.oxy.so' });
|
|
182
182
|
*
|
|
183
183
|
* // Protect all routes under /protected
|
|
184
|
-
* app.use('/protected', oxy.auth({ jwtSecret: process.env.
|
|
184
|
+
* app.use('/protected', oxy.auth({ jwtSecret: process.env.ACCESS_TOKEN_SECRET }));
|
|
185
185
|
*
|
|
186
186
|
* // Access user in route handler
|
|
187
187
|
* app.get('/protected/me', (req, res) => {
|
|
@@ -195,7 +195,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
195
195
|
* app.use('/public', oxy.auth({ optional: true }));
|
|
196
196
|
*
|
|
197
197
|
* // Require a specific scope on a service-token-protected route
|
|
198
|
-
* app.use('/internal/files', oxy.serviceAuth({ jwtSecret: process.env.
|
|
198
|
+
* app.use('/internal/files', oxy.serviceAuth({ jwtSecret: process.env.ACCESS_TOKEN_SECRET }), oxy.requireScope('files:write'));
|
|
199
199
|
* ```
|
|
200
200
|
*
|
|
201
201
|
* @param options Optional configuration
|
|
@@ -360,13 +360,19 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
360
360
|
return onError(error);
|
|
361
361
|
return res.status(401).json(error);
|
|
362
362
|
}
|
|
363
|
-
// Validate required service token fields
|
|
363
|
+
// Validate required service token fields. All of them are
|
|
364
|
+
// required, `ownerAccountId` included: an optional billing
|
|
365
|
+
// principal is one fallback away from being resolved from the
|
|
366
|
+
// delegated user, which is the exact confusion ADR 0007 forbids.
|
|
364
367
|
const appId = decoded.appId;
|
|
365
368
|
const credentialId = decoded.credentialId;
|
|
369
|
+
const ownerAccountId = decoded.ownerAccountId;
|
|
366
370
|
const environment = decoded.environment;
|
|
367
371
|
if (!appId ||
|
|
368
372
|
typeof credentialId !== 'string' ||
|
|
369
373
|
credentialId.length === 0 ||
|
|
374
|
+
typeof ownerAccountId !== 'string' ||
|
|
375
|
+
ownerAccountId.length === 0 ||
|
|
370
376
|
!isOxyServiceEnvironment(environment)) {
|
|
371
377
|
if (optional) {
|
|
372
378
|
req.userId = null;
|
|
@@ -405,6 +411,11 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
405
411
|
return onError(error);
|
|
406
412
|
return res.status(403).json(error);
|
|
407
413
|
}
|
|
414
|
+
// ATTRIBUTION ONLY. `req.userId` answers "on whose behalf", never
|
|
415
|
+
// "who pays": the billing principal stays `req.serviceApp
|
|
416
|
+
// .ownerAccountId`, which this branch does not touch. Read it
|
|
417
|
+
// through `getOxyBillingPrincipal` (`@oxyhq/core/server`), whose
|
|
418
|
+
// return type a user id cannot satisfy (ADR 0007).
|
|
408
419
|
req.userId = oxyUserId;
|
|
409
420
|
req.user = { id: oxyUserId };
|
|
410
421
|
req.serviceActingAs = { userId: oxyUserId, scopes: grant.scopes };
|
|
@@ -419,6 +430,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
419
430
|
appId,
|
|
420
431
|
appName: decoded.appName || 'unknown',
|
|
421
432
|
credentialId,
|
|
433
|
+
ownerAccountId,
|
|
422
434
|
scopes: Array.isArray(decoded.scopes) ? decoded.scopes : [],
|
|
423
435
|
environment,
|
|
424
436
|
};
|
|
@@ -735,7 +747,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
735
747
|
* @example
|
|
736
748
|
* ```typescript
|
|
737
749
|
* // Protect internal endpoints
|
|
738
|
-
* app.use('/internal', oxy.serviceAuth({ jwtSecret: process.env.
|
|
750
|
+
* app.use('/internal', oxy.serviceAuth({ jwtSecret: process.env.ACCESS_TOKEN_SECRET }));
|
|
739
751
|
*
|
|
740
752
|
* app.post('/internal/trigger', (req, res) => {
|
|
741
753
|
* console.log('Service app:', req.serviceApp);
|
|
@@ -773,7 +785,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
773
785
|
* ```typescript
|
|
774
786
|
* app.use(
|
|
775
787
|
* '/internal/files',
|
|
776
|
-
* oxy.serviceAuth({ jwtSecret: process.env.
|
|
788
|
+
* oxy.serviceAuth({ jwtSecret: process.env.ACCESS_TOKEN_SECRET }),
|
|
777
789
|
* oxy.requireScope('files:write'),
|
|
778
790
|
* );
|
|
779
791
|
* ```
|
package/dist/esm/mixins/index.js
CHANGED
|
@@ -32,6 +32,7 @@ import { OxyServicesChainsMixin } from './OxyServices.chains.js';
|
|
|
32
32
|
import { OxyServicesNodesMixin } from './OxyServices.nodes.js';
|
|
33
33
|
import { OxyServicesLinksMixin } from './OxyServices.links.js';
|
|
34
34
|
import { OxyServicesFollowGraphMixin } from './OxyServices.followGraph.js';
|
|
35
|
+
import { OxyServicesInferenceMixin } from './OxyServices.inference.js';
|
|
35
36
|
import { OxyServicesDeviceBootMixin } from './OxyServices.deviceBoot.js';
|
|
36
37
|
import { OxyServicesDeviceTransferMixin } from './OxyServices.deviceTransfer.js';
|
|
37
38
|
/**
|
|
@@ -96,6 +97,11 @@ const MIXIN_PIPELINE = [
|
|
|
96
97
|
// The user-owned follow graph (#809). One relationship per user and target,
|
|
97
98
|
// shared across applications, with per-application context on top.
|
|
98
99
|
OxyServicesFollowGraphMixin,
|
|
100
|
+
// The inference model catalogue (#972). Reads only, and deliberately no
|
|
101
|
+
// request/stream/receipt methods — the public inference edge those would
|
|
102
|
+
// call is workstream 4 and does not exist yet. See
|
|
103
|
+
// `docs/inference/README.md` for what is and is not built.
|
|
104
|
+
OxyServicesInferenceMixin,
|
|
99
105
|
// Device-first token mint: the client half of the zero-cookie transport
|
|
100
106
|
// (`mintFromDeviceSecret` → `POST /session/device/token`).
|
|
101
107
|
OxyServicesDeviceBootMixin,
|
package/dist/esm/server/auth.js
CHANGED
|
@@ -27,6 +27,78 @@ export function getOxyUserId(req) {
|
|
|
27
27
|
export function isOxyAuthenticated(req) {
|
|
28
28
|
return getOxyUserId(req) !== null;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* The billing principal of a request, or `null` when the request carries no
|
|
32
|
+
* verified service principal (an ordinary user session is not a billable
|
|
33
|
+
* machine principal — its account is resolved from the account graph, not from
|
|
34
|
+
* a token claim).
|
|
35
|
+
*
|
|
36
|
+
* Reads `req.serviceApp` and NOTHING else: not `req.userId`, not `req.user`,
|
|
37
|
+
* not `req.serviceActingAs`. That exclusivity is the invariant this function
|
|
38
|
+
* exists to hold, and `serviceTokenAttribution.test.ts` mutation-tests it.
|
|
39
|
+
*
|
|
40
|
+
* **It answers for the SERVICE-TOKEN lane only.** The API's machine-credential
|
|
41
|
+
* lane (`oxy_sk_*`, issue #972 §2.3) resolves the same five facts into its own
|
|
42
|
+
* `req.machineCredential`, deliberately never `req.serviceApp` — populating the
|
|
43
|
+
* latter would hand a self-serve third-party credential the lane that only
|
|
44
|
+
* platform-trusted applications may enter. So a machine-credential request has
|
|
45
|
+
* no billing principal HERE and resolves `null`, which fails closed: the caller
|
|
46
|
+
* must handle it, and `getRequiredOxyBillingPrincipal` throws rather than
|
|
47
|
+
* charging anyone. One accessor answering for both lanes belongs to the public
|
|
48
|
+
* inference edge that has to admit both, and it needs the machine principal's
|
|
49
|
+
* shape to move into this package first.
|
|
50
|
+
*/
|
|
51
|
+
export function getOxyBillingPrincipal(req) {
|
|
52
|
+
const serviceApp = req.serviceApp;
|
|
53
|
+
if (!serviceApp) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const accountId = normalizeId(serviceApp.ownerAccountId);
|
|
57
|
+
const applicationId = normalizeId(serviceApp.appId);
|
|
58
|
+
const credentialId = normalizeId(serviceApp.credentialId);
|
|
59
|
+
if (!accountId || !applicationId || !credentialId) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
accountId,
|
|
64
|
+
applicationId,
|
|
65
|
+
credentialId,
|
|
66
|
+
environment: serviceApp.environment,
|
|
67
|
+
scopes: serviceApp.scopes,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* {@link getOxyBillingPrincipal}, throwing when the request has none. Use on
|
|
72
|
+
* routes that have already required a service token.
|
|
73
|
+
*/
|
|
74
|
+
export function getRequiredOxyBillingPrincipal(req) {
|
|
75
|
+
const principal = getOxyBillingPrincipal(req);
|
|
76
|
+
if (!principal) {
|
|
77
|
+
throw new Error('Request has no verified Oxy service principal');
|
|
78
|
+
}
|
|
79
|
+
return principal;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The delegated end user of a service request, or `null`.
|
|
83
|
+
*
|
|
84
|
+
* Deliberately reads `req.serviceActingAs` — the grant-verified delegation —
|
|
85
|
+
* and not `req.userId`, which on a non-service request is the caller's own
|
|
86
|
+
* session identity and is not a delegation at all.
|
|
87
|
+
*/
|
|
88
|
+
export function getOxyDelegatedUserId(req) {
|
|
89
|
+
return normalizeId(req.serviceActingAs?.userId);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The whole attribution tuple for a service request: who pays, which
|
|
93
|
+
* application and credential, and optionally on whose behalf.
|
|
94
|
+
*/
|
|
95
|
+
export function getOxyRequestAttribution(req) {
|
|
96
|
+
const principal = getOxyBillingPrincipal(req);
|
|
97
|
+
if (!principal) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return { ...principal, delegatedUserId: getOxyDelegatedUserId(req) };
|
|
101
|
+
}
|
|
30
102
|
export function getRequiredOxyUserId(req) {
|
|
31
103
|
const userId = getOxyUserId(req);
|
|
32
104
|
if (!userId) {
|
package/dist/esm/server/index.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* app.use(createOxyRateLimit(oxy, { store: redisStore }));
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyUserId, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, OXY_SERVICE_ENVIRONMENTS, } from './auth.js';
|
|
17
|
+
export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyBillingPrincipal, getOxyDelegatedUserId, getOxyRequestAttribution, getOxyUserId, getRequiredOxyBillingPrincipal, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, OXY_SERVICE_ENVIRONMENTS, } from './auth.js';
|
|
18
18
|
export { createOxyRateLimit } from './rateLimit.js';
|
|
19
19
|
// SSRF-safe upstream fetch + URL validation (Node-only).
|
|
20
20
|
export { assertSafePublicUrl, isBlockedIp, safeFetch, SsrfRejection, UpstreamError, ALLOWED_PORTS, ALLOWED_PROTOCOLS, BLOCKED_HOSTNAMES, DEFAULT_USER_AGENT, MAX_REDIRECTS, MAX_URL_LENGTH, UPSTREAM_HEADERS_TIMEOUT_MS, } from './safeFetch.js';
|