@oxy.so/core 4.3.0 → 4.4.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/cjs/server/OxyServer.js +6 -0
- package/dist/cjs/server/aliaMachineCredential.js +66 -0
- package/dist/cjs/server/index.js +9 -2
- package/dist/cjs/server/namespaces.js +25 -1
- package/dist/esm/server/OxyServer.js +7 -1
- package/dist/esm/server/aliaMachineCredential.js +60 -0
- package/dist/esm/server/index.js +2 -1
- package/dist/esm/server/namespaces.js +23 -0
- package/dist/types/server/OxyServer.d.ts +4 -1
- package/dist/types/server/aliaMachineCredential.d.ts +37 -0
- package/dist/types/server/index.d.ts +3 -1
- package/dist/types/server/namespaces.d.ts +9 -0
- package/package.json +1 -1
- package/src/server/OxyServer.ts +7 -1
- package/src/server/aliaMachineCredential.ts +94 -0
- package/src/server/index.ts +14 -0
- package/src/server/namespaces.ts +21 -0
|
@@ -162,6 +162,12 @@ class OxyServer extends OxyServices_1.OxyServices {
|
|
|
162
162
|
this._serverAgency = new namespaces_1.ServerAgencyApi(this.context);
|
|
163
163
|
return this._serverAgency;
|
|
164
164
|
}
|
|
165
|
+
/** Application management, plus the receiver-bound Alia machine-key lookup. */
|
|
166
|
+
get apps() {
|
|
167
|
+
if (!this._serverApps)
|
|
168
|
+
this._serverApps = new namespaces_1.ServerAppsApi(this.context);
|
|
169
|
+
return this._serverApps;
|
|
170
|
+
}
|
|
165
171
|
/** Reputation reads, plus `award`. */
|
|
166
172
|
get reputation() {
|
|
167
173
|
if (!this._serverReputation)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OXY_ALIA_MACHINE_SCOPES = exports.OXY_ALIA_RESOURCE_APPLICATION_ID = void 0;
|
|
4
|
+
exports.isOxyAliaMachinePrincipal = isOxyAliaMachinePrincipal;
|
|
5
|
+
exports.getOxyAliaMachineCredentialBearer = getOxyAliaMachineCredentialBearer;
|
|
6
|
+
exports.createOxyAliaMachineCredentialAuth = createOxyAliaMachineCredentialAuth;
|
|
7
|
+
/** Stable resource identity, shared with the Oxy control plane. Not a display name. */
|
|
8
|
+
exports.OXY_ALIA_RESOURCE_APPLICATION_ID = '6a2f851751b784a86fd0e934';
|
|
9
|
+
exports.OXY_ALIA_MACHINE_SCOPES = ['alia:chat', 'inference:invoke'];
|
|
10
|
+
function id(value) {
|
|
11
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 128 && value.trim() === value;
|
|
12
|
+
}
|
|
13
|
+
/** Strict response boundary: extra user/tier fields cannot smuggle authority. */
|
|
14
|
+
function isOxyAliaMachinePrincipal(value) {
|
|
15
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
16
|
+
return false;
|
|
17
|
+
const p = value;
|
|
18
|
+
const keys = ['kind', 'audience', 'applicationId', 'credentialId', 'ownerAccountId', 'environment', 'scopes'];
|
|
19
|
+
return Object.keys(p).length === keys.length && keys.every(key => Object.prototype.hasOwnProperty.call(p, key))
|
|
20
|
+
&& p.kind === 'machine' && p.audience === exports.OXY_ALIA_RESOURCE_APPLICATION_ID
|
|
21
|
+
&& id(p.applicationId) && id(p.credentialId) && id(p.ownerAccountId)
|
|
22
|
+
&& typeof p.environment === 'string' && ['production', 'staging', 'development'].includes(p.environment)
|
|
23
|
+
&& Array.isArray(p.scopes) && p.scopes.length === 2
|
|
24
|
+
&& exports.OXY_ALIA_MACHINE_SCOPES.every(scope => p.scopes.includes(scope));
|
|
25
|
+
}
|
|
26
|
+
// Keep the forwarding credential out of enumerable request/principal objects,
|
|
27
|
+
// logs, response serialization and persistent SDK/session state. No cache.
|
|
28
|
+
const forwardingCredentials = new WeakMap();
|
|
29
|
+
/** Only for the request-scoped Oxy inference client; never for a user API. */
|
|
30
|
+
function getOxyAliaMachineCredentialBearer(req) {
|
|
31
|
+
const bound = forwardingCredentials.get(req);
|
|
32
|
+
return bound && req.machineCredential === bound.principal ? bound.bearer : null;
|
|
33
|
+
}
|
|
34
|
+
/** Opt into Alia's app-only chat lane on its two chat routes, never globally. */
|
|
35
|
+
function createOxyAliaMachineCredentialAuth(server, options) {
|
|
36
|
+
return async (req, res, next) => {
|
|
37
|
+
const auth = req.headers.authorization;
|
|
38
|
+
const token = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
|
39
|
+
if (!token.startsWith('oxy_sk_') || token.length > 2048) {
|
|
40
|
+
res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const existing = req;
|
|
44
|
+
if (existing.user || existing.userId || existing.serviceApp || existing.oxyRequester
|
|
45
|
+
|| req.machineCredential
|
|
46
|
+
|| req.headers['x-oxy-user-id'] !== undefined || req.headers['x-oxy-requester-assertion'] !== undefined) {
|
|
47
|
+
res.status(403).json({ error: 'MACHINE_USER_DELEGATION_UNSUPPORTED' });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const answer = await server.apps.introspectAliaMachineCredential(token);
|
|
52
|
+
if (!answer.active || !isOxyAliaMachinePrincipal(answer.principal) || answer.principal.environment !== options.environment) {
|
|
53
|
+
res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const principal = Object.freeze({ ...answer.principal, scopes: Object.freeze(['alia:chat', 'inference:invoke']) });
|
|
57
|
+
req.machineCredential = principal;
|
|
58
|
+
forwardingCredentials.set(req, { principal, bearer: token });
|
|
59
|
+
next();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Transport errors may contain request data; never expose or log them.
|
|
63
|
+
res.status(503).json({ error: 'MACHINE_CREDENTIAL_VERIFIER_UNAVAILABLE' });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.
|
|
20
|
-
exports.signInAgentAccount = exports.hasBoundedServiceTokenLifetime = exports.OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS = exports.requestWorkloadServiceToken = exports.canAttestWorkloadIdentity = exports.withoutNodeHttpObservation = exports.observeNodeHttp = exports.createEcosystemTraffic = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.registrableApex = exports.OXY_IDENTITY_CACHE_PREFIXES = exports.oxyUserByIdCacheKey = exports.evictOxyIdentityCache = exports.publishOxyUserInvalidation = exports.createOxyUserInvalidationHandler = exports.createLiveCapabilityTicketVerifier = exports.verifySecret = exports.verifyOxyRequesterAssertion = exports.signOxyRequesterAssertion = exports.readOxyRequesterAssertionKeyId = exports.oxyRequesterAssertionClaimsSchema = exports.OxyRequesterAssertionError = exports.OXY_REQUESTER_ASSERTION_TYPE = exports.OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS = exports.OXY_REQUESTER_ASSERTION_HEADER = exports.OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER = exports.createOxyRequesterAssertionAuth = exports.createOxyJwksKeyResolver = exports.verifyCapabilityTicket = void 0;
|
|
19
|
+
exports.inlineScriptCspHash = exports.formatOxyCspPolicy = exports.extractInlineScripts = exports.cspSourcesFor = exports.createOxySecurityHeaders = exports.buildOxyPagesHeaders = exports.buildOxyCspDirectives = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.OXY_SERVICE_ENVIRONMENTS = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getRequiredOxyBillingPrincipal = exports.getOxyUserId = exports.getOxyRequestAttribution = exports.getOxyDelegatedUserId = exports.getOxyBillingPrincipal = exports.getOxyAuthRefusal = exports.getOxyActor = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = exports.OXY_ACCOUNT_DELETED_EVENT_URI = exports.OxyAccountEventError = exports.OXY_ALIA_MACHINE_SCOPES = exports.OXY_ALIA_RESOURCE_APPLICATION_ID = exports.isOxyAliaMachinePrincipal = exports.getOxyAliaMachineCredentialBearer = exports.createOxyAliaMachineCredentialAuth = exports.ServerReputationApi = exports.ServerNotificationsApi = exports.ServerLinkedAccountsApi = exports.ServerAssetsApi = exports.ServerAgencyApi = exports.ServerAppsApi = exports.ANONYMOUS_SERVICE_TOKEN_RETRY_MS = exports.ServiceCredentialMismatchError = exports.OxyServer = void 0;
|
|
20
|
+
exports.signInAgentAccount = exports.hasBoundedServiceTokenLifetime = exports.OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS = exports.requestWorkloadServiceToken = exports.canAttestWorkloadIdentity = exports.withoutNodeHttpObservation = exports.observeNodeHttp = exports.createEcosystemTraffic = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.registrableApex = exports.OXY_IDENTITY_CACHE_PREFIXES = exports.oxyUserByIdCacheKey = exports.evictOxyIdentityCache = exports.publishOxyUserInvalidation = exports.createOxyUserInvalidationHandler = exports.createLiveCapabilityTicketVerifier = exports.verifySecret = exports.verifyOxyRequesterAssertion = exports.signOxyRequesterAssertion = exports.readOxyRequesterAssertionKeyId = exports.oxyRequesterAssertionClaimsSchema = exports.OxyRequesterAssertionError = exports.OXY_REQUESTER_ASSERTION_TYPE = exports.OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS = exports.OXY_REQUESTER_ASSERTION_HEADER = exports.OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER = exports.createOxyRequesterAssertionAuth = exports.createOxyJwksKeyResolver = exports.verifyCapabilityTicket = exports.readCapabilityAuthorization = exports.issueCapabilityTicket = exports.inputSatisfiesCapabilityLimits = exports.createCapabilityTicketMiddleware = exports.CapabilityTicketError = exports.OXY_CSP_BASELINE = void 0;
|
|
21
21
|
// The backend client: OxyServices plus the service-token lane, middleware,
|
|
22
22
|
// account events and the service-only namespace methods.
|
|
23
23
|
var OxyServer_1 = require("./OxyServer");
|
|
@@ -25,11 +25,18 @@ Object.defineProperty(exports, "OxyServer", { enumerable: true, get: function ()
|
|
|
25
25
|
Object.defineProperty(exports, "ServiceCredentialMismatchError", { enumerable: true, get: function () { return OxyServer_1.ServiceCredentialMismatchError; } });
|
|
26
26
|
Object.defineProperty(exports, "ANONYMOUS_SERVICE_TOKEN_RETRY_MS", { enumerable: true, get: function () { return OxyServer_1.ANONYMOUS_SERVICE_TOKEN_RETRY_MS; } });
|
|
27
27
|
var namespaces_1 = require("./namespaces");
|
|
28
|
+
Object.defineProperty(exports, "ServerAppsApi", { enumerable: true, get: function () { return namespaces_1.ServerAppsApi; } });
|
|
28
29
|
Object.defineProperty(exports, "ServerAgencyApi", { enumerable: true, get: function () { return namespaces_1.ServerAgencyApi; } });
|
|
29
30
|
Object.defineProperty(exports, "ServerAssetsApi", { enumerable: true, get: function () { return namespaces_1.ServerAssetsApi; } });
|
|
30
31
|
Object.defineProperty(exports, "ServerLinkedAccountsApi", { enumerable: true, get: function () { return namespaces_1.ServerLinkedAccountsApi; } });
|
|
31
32
|
Object.defineProperty(exports, "ServerNotificationsApi", { enumerable: true, get: function () { return namespaces_1.ServerNotificationsApi; } });
|
|
32
33
|
Object.defineProperty(exports, "ServerReputationApi", { enumerable: true, get: function () { return namespaces_1.ServerReputationApi; } });
|
|
34
|
+
var aliaMachineCredential_1 = require("./aliaMachineCredential");
|
|
35
|
+
Object.defineProperty(exports, "createOxyAliaMachineCredentialAuth", { enumerable: true, get: function () { return aliaMachineCredential_1.createOxyAliaMachineCredentialAuth; } });
|
|
36
|
+
Object.defineProperty(exports, "getOxyAliaMachineCredentialBearer", { enumerable: true, get: function () { return aliaMachineCredential_1.getOxyAliaMachineCredentialBearer; } });
|
|
37
|
+
Object.defineProperty(exports, "isOxyAliaMachinePrincipal", { enumerable: true, get: function () { return aliaMachineCredential_1.isOxyAliaMachinePrincipal; } });
|
|
38
|
+
Object.defineProperty(exports, "OXY_ALIA_RESOURCE_APPLICATION_ID", { enumerable: true, get: function () { return aliaMachineCredential_1.OXY_ALIA_RESOURCE_APPLICATION_ID; } });
|
|
39
|
+
Object.defineProperty(exports, "OXY_ALIA_MACHINE_SCOPES", { enumerable: true, get: function () { return aliaMachineCredential_1.OXY_ALIA_MACHINE_SCOPES; } });
|
|
33
40
|
var middleware_1 = require("./middleware");
|
|
34
41
|
Object.defineProperty(exports, "OxyAccountEventError", { enumerable: true, get: function () { return middleware_1.OxyAccountEventError; } });
|
|
35
42
|
Object.defineProperty(exports, "OXY_ACCOUNT_DELETED_EVENT_URI", { enumerable: true, get: function () { return middleware_1.OXY_ACCOUNT_DELETED_EVENT_URI; } });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ServerReputationApi = exports.ServerAgencyApi = exports.ServerLinkedAccountsApi = exports.ServerNotificationsApi = exports.ServerAssetsApi = void 0;
|
|
3
|
+
exports.ServerReputationApi = exports.ServerAgencyApi = exports.ServerLinkedAccountsApi = exports.ServerNotificationsApi = exports.ServerAssetsApi = exports.ServerAppsApi = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* The server-side halves of the namespaces: what exists only with a service
|
|
6
6
|
* credential. `OxyServer` returns these from its namespace getters, so
|
|
@@ -13,6 +13,8 @@ const notifications_1 = require("../api/notifications");
|
|
|
13
13
|
const linkedAccounts_1 = require("../api/linkedAccounts");
|
|
14
14
|
const agency_1 = require("../api/agency");
|
|
15
15
|
const reputation_1 = require("../api/reputation");
|
|
16
|
+
const apps_1 = require("../api/apps");
|
|
17
|
+
const aliaMachineCredential_1 = require("./aliaMachineCredential");
|
|
16
18
|
const OxyServices_errors_1 = require("../OxyServices.errors");
|
|
17
19
|
const errorUtils_1 = require("../utils/errorUtils");
|
|
18
20
|
const logger_1 = require("../logger");
|
|
@@ -30,6 +32,28 @@ function lane(ctx) {
|
|
|
30
32
|
}
|
|
31
33
|
return ctx.service;
|
|
32
34
|
}
|
|
35
|
+
class ServerAppsApi extends apps_1.AppsApi {
|
|
36
|
+
constructor(serverCtx) {
|
|
37
|
+
super(serverCtx);
|
|
38
|
+
this.serverCtx = serverCtx;
|
|
39
|
+
}
|
|
40
|
+
/** Live receiver-bound lookup using THIS resource server's own service token. */
|
|
41
|
+
async introspectAliaMachineCredential(token) {
|
|
42
|
+
if (!token.startsWith('oxy_sk_') || token.length > 2048)
|
|
43
|
+
return { active: false };
|
|
44
|
+
const answer = await lane(this.serverCtx).request('POST', '/internal/alia/machine-credentials/introspect', { token }, { cache: false, retry: false });
|
|
45
|
+
if (!answer || typeof answer !== 'object' || Array.isArray(answer))
|
|
46
|
+
throw new Error('Invalid machine credential verdict');
|
|
47
|
+
const record = answer;
|
|
48
|
+
if (record.active === false && Object.keys(record).length === 1)
|
|
49
|
+
return { active: false };
|
|
50
|
+
if (record.active !== true || Object.keys(record).length !== 2 || !(0, aliaMachineCredential_1.isOxyAliaMachinePrincipal)(record.principal)) {
|
|
51
|
+
throw new Error('Invalid machine credential verdict');
|
|
52
|
+
}
|
|
53
|
+
return { active: true, principal: record.principal };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ServerAppsApi = ServerAppsApi;
|
|
33
57
|
function chunk(items, size) {
|
|
34
58
|
const chunks = [];
|
|
35
59
|
for (let i = 0; i < items.length; i += size)
|
|
@@ -43,7 +43,7 @@ import { OxyServices } from '../OxyServices.js';
|
|
|
43
43
|
import { logger } from '../logger/index.js';
|
|
44
44
|
import { canAttestWorkloadIdentity, requestWorkloadServiceToken } from './workloadIdentity.js';
|
|
45
45
|
import { createOxyMiddleware, OXY_ACCOUNT_DELETED_EVENT_URI, OXY_JWT_ISSUER, OxyAccountEventError, parseJsonSegment, resolveServiceTokenPublicKey, } from './middleware.js';
|
|
46
|
-
import { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces.js';
|
|
46
|
+
import { ServerAppsApi, ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces.js';
|
|
47
47
|
/**
|
|
48
48
|
* How long a failed service-token mint sends session-less requests anonymous
|
|
49
49
|
* before the next attempt, under `serviceIdentity: 'when-anonymous'`.
|
|
@@ -158,6 +158,12 @@ export class OxyServer extends OxyServices {
|
|
|
158
158
|
this._serverAgency = new ServerAgencyApi(this.context);
|
|
159
159
|
return this._serverAgency;
|
|
160
160
|
}
|
|
161
|
+
/** Application management, plus the receiver-bound Alia machine-key lookup. */
|
|
162
|
+
get apps() {
|
|
163
|
+
if (!this._serverApps)
|
|
164
|
+
this._serverApps = new ServerAppsApi(this.context);
|
|
165
|
+
return this._serverApps;
|
|
166
|
+
}
|
|
161
167
|
/** Reputation reads, plus `award`. */
|
|
162
168
|
get reputation() {
|
|
163
169
|
if (!this._serverReputation)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Stable resource identity, shared with the Oxy control plane. Not a display name. */
|
|
2
|
+
export const OXY_ALIA_RESOURCE_APPLICATION_ID = '6a2f851751b784a86fd0e934';
|
|
3
|
+
export const OXY_ALIA_MACHINE_SCOPES = ['alia:chat', 'inference:invoke'];
|
|
4
|
+
function id(value) {
|
|
5
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 128 && value.trim() === value;
|
|
6
|
+
}
|
|
7
|
+
/** Strict response boundary: extra user/tier fields cannot smuggle authority. */
|
|
8
|
+
export function isOxyAliaMachinePrincipal(value) {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10
|
+
return false;
|
|
11
|
+
const p = value;
|
|
12
|
+
const keys = ['kind', 'audience', 'applicationId', 'credentialId', 'ownerAccountId', 'environment', 'scopes'];
|
|
13
|
+
return Object.keys(p).length === keys.length && keys.every(key => Object.prototype.hasOwnProperty.call(p, key))
|
|
14
|
+
&& p.kind === 'machine' && p.audience === OXY_ALIA_RESOURCE_APPLICATION_ID
|
|
15
|
+
&& id(p.applicationId) && id(p.credentialId) && id(p.ownerAccountId)
|
|
16
|
+
&& typeof p.environment === 'string' && ['production', 'staging', 'development'].includes(p.environment)
|
|
17
|
+
&& Array.isArray(p.scopes) && p.scopes.length === 2
|
|
18
|
+
&& OXY_ALIA_MACHINE_SCOPES.every(scope => p.scopes.includes(scope));
|
|
19
|
+
}
|
|
20
|
+
// Keep the forwarding credential out of enumerable request/principal objects,
|
|
21
|
+
// logs, response serialization and persistent SDK/session state. No cache.
|
|
22
|
+
const forwardingCredentials = new WeakMap();
|
|
23
|
+
/** Only for the request-scoped Oxy inference client; never for a user API. */
|
|
24
|
+
export function getOxyAliaMachineCredentialBearer(req) {
|
|
25
|
+
const bound = forwardingCredentials.get(req);
|
|
26
|
+
return bound && req.machineCredential === bound.principal ? bound.bearer : null;
|
|
27
|
+
}
|
|
28
|
+
/** Opt into Alia's app-only chat lane on its two chat routes, never globally. */
|
|
29
|
+
export function createOxyAliaMachineCredentialAuth(server, options) {
|
|
30
|
+
return async (req, res, next) => {
|
|
31
|
+
const auth = req.headers.authorization;
|
|
32
|
+
const token = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
|
33
|
+
if (!token.startsWith('oxy_sk_') || token.length > 2048) {
|
|
34
|
+
res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const existing = req;
|
|
38
|
+
if (existing.user || existing.userId || existing.serviceApp || existing.oxyRequester
|
|
39
|
+
|| req.machineCredential
|
|
40
|
+
|| req.headers['x-oxy-user-id'] !== undefined || req.headers['x-oxy-requester-assertion'] !== undefined) {
|
|
41
|
+
res.status(403).json({ error: 'MACHINE_USER_DELEGATION_UNSUPPORTED' });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const answer = await server.apps.introspectAliaMachineCredential(token);
|
|
46
|
+
if (!answer.active || !isOxyAliaMachinePrincipal(answer.principal) || answer.principal.environment !== options.environment) {
|
|
47
|
+
res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const principal = Object.freeze({ ...answer.principal, scopes: Object.freeze(['alia:chat', 'inference:invoke']) });
|
|
51
|
+
req.machineCredential = principal;
|
|
52
|
+
forwardingCredentials.set(req, { principal, bearer: token });
|
|
53
|
+
next();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Transport errors may contain request data; never expose or log them.
|
|
57
|
+
res.status(503).json({ error: 'MACHINE_CREDENTIAL_VERIFIER_UNAVAILABLE' });
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
package/dist/esm/server/index.js
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
// The backend client: OxyServices plus the service-token lane, middleware,
|
|
18
18
|
// account events and the service-only namespace methods.
|
|
19
19
|
export { OxyServer, ServiceCredentialMismatchError, ANONYMOUS_SERVICE_TOKEN_RETRY_MS } from './OxyServer.js';
|
|
20
|
-
export { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces.js';
|
|
20
|
+
export { ServerAppsApi, ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces.js';
|
|
21
|
+
export { createOxyAliaMachineCredentialAuth, getOxyAliaMachineCredentialBearer, isOxyAliaMachinePrincipal, OXY_ALIA_RESOURCE_APPLICATION_ID, OXY_ALIA_MACHINE_SCOPES, } from './aliaMachineCredential.js';
|
|
21
22
|
export { OxyAccountEventError, OXY_ACCOUNT_DELETED_EVENT_URI } from './middleware.js';
|
|
22
23
|
export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyActor, getOxyAuthRefusal, getOxyBillingPrincipal, getOxyDelegatedUserId, getOxyRequestAttribution, getOxyUserId, getRequiredOxyBillingPrincipal, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, OXY_SERVICE_ENVIRONMENTS, } from './auth.js';
|
|
23
24
|
export { createOxyRateLimit } from './rateLimit.js';
|
|
@@ -10,6 +10,8 @@ import { NotificationsApi } from '../api/notifications.js';
|
|
|
10
10
|
import { LinkedAccountsApi } from '../api/linkedAccounts.js';
|
|
11
11
|
import { AgencyApi } from '../api/agency.js';
|
|
12
12
|
import { ReputationApi } from '../api/reputation.js';
|
|
13
|
+
import { AppsApi } from '../api/apps.js';
|
|
14
|
+
import { isOxyAliaMachinePrincipal } from './aliaMachineCredential.js';
|
|
13
15
|
import { ServiceAssetMetadataError, ServiceLinkedDownloadUrlError } from '../OxyServices.errors.js';
|
|
14
16
|
import { extractErrorStatus } from '../utils/errorUtils.js';
|
|
15
17
|
import { logger } from '../logger/index.js';
|
|
@@ -27,6 +29,27 @@ function lane(ctx) {
|
|
|
27
29
|
}
|
|
28
30
|
return ctx.service;
|
|
29
31
|
}
|
|
32
|
+
export class ServerAppsApi extends AppsApi {
|
|
33
|
+
constructor(serverCtx) {
|
|
34
|
+
super(serverCtx);
|
|
35
|
+
this.serverCtx = serverCtx;
|
|
36
|
+
}
|
|
37
|
+
/** Live receiver-bound lookup using THIS resource server's own service token. */
|
|
38
|
+
async introspectAliaMachineCredential(token) {
|
|
39
|
+
if (!token.startsWith('oxy_sk_') || token.length > 2048)
|
|
40
|
+
return { active: false };
|
|
41
|
+
const answer = await lane(this.serverCtx).request('POST', '/internal/alia/machine-credentials/introspect', { token }, { cache: false, retry: false });
|
|
42
|
+
if (!answer || typeof answer !== 'object' || Array.isArray(answer))
|
|
43
|
+
throw new Error('Invalid machine credential verdict');
|
|
44
|
+
const record = answer;
|
|
45
|
+
if (record.active === false && Object.keys(record).length === 1)
|
|
46
|
+
return { active: false };
|
|
47
|
+
if (record.active !== true || Object.keys(record).length !== 2 || !isOxyAliaMachinePrincipal(record.principal)) {
|
|
48
|
+
throw new Error('Invalid machine credential verdict');
|
|
49
|
+
}
|
|
50
|
+
return { active: true, principal: record.principal };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
30
53
|
function chunk(items, size) {
|
|
31
54
|
const chunks = [];
|
|
32
55
|
for (let i = 0; i < items.length; i += size)
|
|
@@ -2,7 +2,7 @@ import { OxyServices, type OxyConfig } from '../OxyServices';
|
|
|
2
2
|
import type { HttpMethod } from '../client/context';
|
|
3
3
|
import type { RequestOptions } from '../HttpService';
|
|
4
4
|
import { type OxyAccountEvent, type OxyAccountEventFeedPage, type OxyMiddleware, type ServiceActingAsVerification, type VerifyAccountEventOptions } from './middleware';
|
|
5
|
-
import { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi } from './namespaces';
|
|
5
|
+
import { ServerAppsApi, ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi } from './namespaces';
|
|
6
6
|
export interface OxyServerConfig extends OxyConfig {
|
|
7
7
|
/** This service's application credential. Omit to use workload identity (ADR 0026). */
|
|
8
8
|
serviceAuth?: {
|
|
@@ -87,6 +87,7 @@ export declare class OxyServer extends OxyServices {
|
|
|
87
87
|
private _serverNotifications?;
|
|
88
88
|
private _serverLinkedAccounts?;
|
|
89
89
|
private _serverAgency?;
|
|
90
|
+
private _serverApps?;
|
|
90
91
|
private _serverReputation?;
|
|
91
92
|
/** Files, plus the service-only metadata and linked-URL lookups. */
|
|
92
93
|
get assets(): ServerAssetsApi;
|
|
@@ -96,6 +97,8 @@ export declare class OxyServer extends OxyServices {
|
|
|
96
97
|
get linkedAccounts(): ServerLinkedAccountsApi;
|
|
97
98
|
/** Delegated capabilities, plus `introspectRequesterAssertion`. */
|
|
98
99
|
get agency(): ServerAgencyApi;
|
|
100
|
+
/** Application management, plus the receiver-bound Alia machine-key lookup. */
|
|
101
|
+
get apps(): ServerAppsApi;
|
|
99
102
|
/** Reputation reads, plus `award`. */
|
|
100
103
|
get reputation(): ServerReputationApi;
|
|
101
104
|
/**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Request, RequestHandler } from 'express';
|
|
2
|
+
import type { OxyServiceEnvironment } from './auth';
|
|
3
|
+
/** Stable resource identity, shared with the Oxy control plane. Not a display name. */
|
|
4
|
+
export declare const OXY_ALIA_RESOURCE_APPLICATION_ID = "6a2f851751b784a86fd0e934";
|
|
5
|
+
export declare const OXY_ALIA_MACHINE_SCOPES: readonly ["alia:chat", "inference:invoke"];
|
|
6
|
+
/** App-only assistant turn: no human subject, service tier or delegated authority. */
|
|
7
|
+
export interface OxyAliaMachinePrincipal {
|
|
8
|
+
readonly kind: 'machine';
|
|
9
|
+
readonly audience: typeof OXY_ALIA_RESOURCE_APPLICATION_ID;
|
|
10
|
+
readonly applicationId: string;
|
|
11
|
+
readonly credentialId: string;
|
|
12
|
+
readonly ownerAccountId: string;
|
|
13
|
+
readonly environment: OxyServiceEnvironment;
|
|
14
|
+
readonly scopes: readonly ['alia:chat', 'inference:invoke'];
|
|
15
|
+
}
|
|
16
|
+
export type AliaMachineCredentialIntrospection = {
|
|
17
|
+
active: false;
|
|
18
|
+
} | {
|
|
19
|
+
active: true;
|
|
20
|
+
principal: OxyAliaMachinePrincipal;
|
|
21
|
+
};
|
|
22
|
+
export interface OxyAliaMachineCredentialHost {
|
|
23
|
+
readonly apps: {
|
|
24
|
+
introspectAliaMachineCredential(token: string): Promise<AliaMachineCredentialIntrospection>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface OxyAliaMachineRequest extends Request {
|
|
28
|
+
machineCredential?: OxyAliaMachinePrincipal;
|
|
29
|
+
}
|
|
30
|
+
/** Strict response boundary: extra user/tier fields cannot smuggle authority. */
|
|
31
|
+
export declare function isOxyAliaMachinePrincipal(value: unknown): value is OxyAliaMachinePrincipal;
|
|
32
|
+
/** Only for the request-scoped Oxy inference client; never for a user API. */
|
|
33
|
+
export declare function getOxyAliaMachineCredentialBearer(req: Request): string | null;
|
|
34
|
+
/** Opt into Alia's app-only chat lane on its two chat routes, never globally. */
|
|
35
|
+
export declare function createOxyAliaMachineCredentialAuth(server: OxyAliaMachineCredentialHost, options: {
|
|
36
|
+
environment: OxyServiceEnvironment;
|
|
37
|
+
}): RequestHandler;
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export { OxyServer, ServiceCredentialMismatchError, ANONYMOUS_SERVICE_TOKEN_RETRY_MS } from './OxyServer';
|
|
18
18
|
export type { OxyServerConfig, ServiceTokenResponse } from './OxyServer';
|
|
19
|
-
export { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces';
|
|
19
|
+
export { ServerAppsApi, ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces';
|
|
20
|
+
export { createOxyAliaMachineCredentialAuth, getOxyAliaMachineCredentialBearer, isOxyAliaMachinePrincipal, OXY_ALIA_RESOURCE_APPLICATION_ID, OXY_ALIA_MACHINE_SCOPES, } from './aliaMachineCredential';
|
|
21
|
+
export type { AliaMachineCredentialIntrospection, OxyAliaMachinePrincipal, OxyAliaMachineRequest, OxyAliaMachineCredentialHost, } from './aliaMachineCredential';
|
|
20
22
|
export type { CapabilityTicketGrant, CapabilityTicketIntrospection, CreateExecutionAuthorizationInput, ServiceCapabilityCatalog, } from './namespaces';
|
|
21
23
|
export { OxyAccountEventError, OXY_ACCOUNT_DELETED_EVENT_URI } from './middleware';
|
|
22
24
|
export type { AuthMiddlewareOptions, OxyAccountEvent, OxyAccountEventFeedItem, OxyAccountEventFeedPage, OxyMiddleware, ServiceActingAsVerification, ServiceApp, VerifyAccountEventOptions, } from './middleware';
|
|
@@ -5,12 +5,21 @@
|
|
|
5
5
|
* `oxy.assets`.
|
|
6
6
|
*/
|
|
7
7
|
import { type ForegroundExecutionAuthorizationInput, type ActorRef, type AppCapabilityCatalog, type AutonomyLevel, type CapabilityTicketClaims, type CapabilityTicketRequest, type GrantLimit, type PolicyDecision, type ResourceRef, type CreateOxyNotificationRequest, type AwardReputationInput, type ReputationTransaction, type ServiceLinkedAccountListResponse } from '@oxy.so/contracts';
|
|
8
|
+
import type { OxyContext } from '../client/context';
|
|
8
9
|
import type { Notification, ServiceAssetMetadata, ServiceAssetMetadataBySha, ServiceLinkedDownloadUrl } from '../models/interfaces';
|
|
9
10
|
import { AssetsApi } from '../api/assets';
|
|
10
11
|
import { NotificationsApi } from '../api/notifications';
|
|
11
12
|
import { LinkedAccountsApi } from '../api/linkedAccounts';
|
|
12
13
|
import { AgencyApi, type CapabilityExecutionAuthorization, type RequesterAssertionGrant, type RequesterAssertionIntrospection } from '../api/agency';
|
|
13
14
|
import { ReputationApi } from '../api/reputation';
|
|
15
|
+
import { AppsApi } from '../api/apps';
|
|
16
|
+
import { type AliaMachineCredentialIntrospection } from './aliaMachineCredential';
|
|
17
|
+
export declare class ServerAppsApi extends AppsApi {
|
|
18
|
+
private readonly serverCtx;
|
|
19
|
+
constructor(serverCtx: OxyContext);
|
|
20
|
+
/** Live receiver-bound lookup using THIS resource server's own service token. */
|
|
21
|
+
introspectAliaMachineCredential(token: string): Promise<AliaMachineCredentialIntrospection>;
|
|
22
|
+
}
|
|
14
23
|
export declare class ServerAssetsApi extends AssetsApi {
|
|
15
24
|
/**
|
|
16
25
|
* Resolve many asset ids to their content-addressed metadata (`sha256`,
|
package/package.json
CHANGED
package/src/server/OxyServer.ts
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
type VerifyAccountEventOptions,
|
|
60
60
|
} from './middleware';
|
|
61
61
|
import {
|
|
62
|
+
ServerAppsApi,
|
|
62
63
|
ServerAgencyApi,
|
|
63
64
|
ServerAssetsApi,
|
|
64
65
|
ServerLinkedAccountsApi,
|
|
@@ -217,6 +218,7 @@ export class OxyServer extends OxyServices {
|
|
|
217
218
|
private _serverNotifications?: ServerNotificationsApi;
|
|
218
219
|
private _serverLinkedAccounts?: ServerLinkedAccountsApi;
|
|
219
220
|
private _serverAgency?: ServerAgencyApi;
|
|
221
|
+
private _serverApps?: ServerAppsApi;
|
|
220
222
|
private _serverReputation?: ServerReputationApi;
|
|
221
223
|
|
|
222
224
|
/** Files, plus the service-only metadata and linked-URL lookups. */
|
|
@@ -239,6 +241,11 @@ export class OxyServer extends OxyServices {
|
|
|
239
241
|
if (!this._serverAgency) this._serverAgency = new ServerAgencyApi(this.context);
|
|
240
242
|
return this._serverAgency;
|
|
241
243
|
}
|
|
244
|
+
/** Application management, plus the receiver-bound Alia machine-key lookup. */
|
|
245
|
+
override get apps(): ServerAppsApi {
|
|
246
|
+
if (!this._serverApps) this._serverApps = new ServerAppsApi(this.context);
|
|
247
|
+
return this._serverApps;
|
|
248
|
+
}
|
|
242
249
|
/** Reputation reads, plus `award`. */
|
|
243
250
|
override get reputation(): ServerReputationApi {
|
|
244
251
|
if (!this._serverReputation) this._serverReputation = new ServerReputationApi(this.context);
|
|
@@ -695,4 +702,3 @@ export class OxyServer extends OxyServices {
|
|
|
695
702
|
return appId;
|
|
696
703
|
}
|
|
697
704
|
}
|
|
698
|
-
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Request, RequestHandler } from 'express';
|
|
2
|
+
import type { OxyServiceEnvironment } from './auth';
|
|
3
|
+
|
|
4
|
+
/** Stable resource identity, shared with the Oxy control plane. Not a display name. */
|
|
5
|
+
export const OXY_ALIA_RESOURCE_APPLICATION_ID = '6a2f851751b784a86fd0e934';
|
|
6
|
+
export const OXY_ALIA_MACHINE_SCOPES = ['alia:chat', 'inference:invoke'] as const;
|
|
7
|
+
|
|
8
|
+
/** App-only assistant turn: no human subject, service tier or delegated authority. */
|
|
9
|
+
export interface OxyAliaMachinePrincipal {
|
|
10
|
+
readonly kind: 'machine';
|
|
11
|
+
readonly audience: typeof OXY_ALIA_RESOURCE_APPLICATION_ID;
|
|
12
|
+
readonly applicationId: string;
|
|
13
|
+
readonly credentialId: string;
|
|
14
|
+
readonly ownerAccountId: string;
|
|
15
|
+
readonly environment: OxyServiceEnvironment;
|
|
16
|
+
readonly scopes: readonly ['alia:chat', 'inference:invoke'];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type AliaMachineCredentialIntrospection =
|
|
20
|
+
| { active: false }
|
|
21
|
+
| { active: true; principal: OxyAliaMachinePrincipal };
|
|
22
|
+
|
|
23
|
+
export interface OxyAliaMachineCredentialHost {
|
|
24
|
+
readonly apps: {
|
|
25
|
+
introspectAliaMachineCredential(token: string): Promise<AliaMachineCredentialIntrospection>;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface OxyAliaMachineRequest extends Request {
|
|
30
|
+
machineCredential?: OxyAliaMachinePrincipal;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function id(value: unknown): value is string {
|
|
34
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 128 && value.trim() === value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Strict response boundary: extra user/tier fields cannot smuggle authority. */
|
|
38
|
+
export function isOxyAliaMachinePrincipal(value: unknown): value is OxyAliaMachinePrincipal {
|
|
39
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
40
|
+
const p = value as Record<string, unknown>;
|
|
41
|
+
const keys = ['kind', 'audience', 'applicationId', 'credentialId', 'ownerAccountId', 'environment', 'scopes'];
|
|
42
|
+
return Object.keys(p).length === keys.length && keys.every(key => Object.prototype.hasOwnProperty.call(p, key))
|
|
43
|
+
&& p.kind === 'machine' && p.audience === OXY_ALIA_RESOURCE_APPLICATION_ID
|
|
44
|
+
&& id(p.applicationId) && id(p.credentialId) && id(p.ownerAccountId)
|
|
45
|
+
&& typeof p.environment === 'string' && ['production', 'staging', 'development'].includes(p.environment)
|
|
46
|
+
&& Array.isArray(p.scopes) && p.scopes.length === 2
|
|
47
|
+
&& OXY_ALIA_MACHINE_SCOPES.every(scope => (p.scopes as unknown[]).includes(scope));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Keep the forwarding credential out of enumerable request/principal objects,
|
|
51
|
+
// logs, response serialization and persistent SDK/session state. No cache.
|
|
52
|
+
const forwardingCredentials = new WeakMap<Request, { principal: OxyAliaMachinePrincipal; bearer: string }>();
|
|
53
|
+
|
|
54
|
+
/** Only for the request-scoped Oxy inference client; never for a user API. */
|
|
55
|
+
export function getOxyAliaMachineCredentialBearer(req: Request): string | null {
|
|
56
|
+
const bound = forwardingCredentials.get(req);
|
|
57
|
+
return bound && (req as OxyAliaMachineRequest).machineCredential === bound.principal ? bound.bearer : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Opt into Alia's app-only chat lane on its two chat routes, never globally. */
|
|
61
|
+
export function createOxyAliaMachineCredentialAuth(
|
|
62
|
+
server: OxyAliaMachineCredentialHost,
|
|
63
|
+
options: { environment: OxyServiceEnvironment },
|
|
64
|
+
): RequestHandler {
|
|
65
|
+
return async (req, res, next) => {
|
|
66
|
+
const auth = req.headers.authorization;
|
|
67
|
+
const token = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
|
68
|
+
if (!token.startsWith('oxy_sk_') || token.length > 2048) {
|
|
69
|
+
res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const existing = req as Request & { user?: unknown; userId?: unknown; serviceApp?: unknown; oxyRequester?: unknown };
|
|
73
|
+
if (existing.user || existing.userId || existing.serviceApp || existing.oxyRequester
|
|
74
|
+
|| (req as OxyAliaMachineRequest).machineCredential
|
|
75
|
+
|| req.headers['x-oxy-user-id'] !== undefined || req.headers['x-oxy-requester-assertion'] !== undefined) {
|
|
76
|
+
res.status(403).json({ error: 'MACHINE_USER_DELEGATION_UNSUPPORTED' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const answer = await server.apps.introspectAliaMachineCredential(token);
|
|
81
|
+
if (!answer.active || !isOxyAliaMachinePrincipal(answer.principal) || answer.principal.environment !== options.environment) {
|
|
82
|
+
res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const principal = Object.freeze({ ...answer.principal, scopes: Object.freeze(['alia:chat', 'inference:invoke'] as const) });
|
|
86
|
+
(req as OxyAliaMachineRequest).machineCredential = principal;
|
|
87
|
+
forwardingCredentials.set(req, { principal, bearer: token });
|
|
88
|
+
next();
|
|
89
|
+
} catch {
|
|
90
|
+
// Transport errors may contain request data; never expose or log them.
|
|
91
|
+
res.status(503).json({ error: 'MACHINE_CREDENTIAL_VERIFIER_UNAVAILABLE' });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -20,12 +20,26 @@
|
|
|
20
20
|
export { OxyServer, ServiceCredentialMismatchError, ANONYMOUS_SERVICE_TOKEN_RETRY_MS } from './OxyServer';
|
|
21
21
|
export type { OxyServerConfig, ServiceTokenResponse } from './OxyServer';
|
|
22
22
|
export {
|
|
23
|
+
ServerAppsApi,
|
|
23
24
|
ServerAgencyApi,
|
|
24
25
|
ServerAssetsApi,
|
|
25
26
|
ServerLinkedAccountsApi,
|
|
26
27
|
ServerNotificationsApi,
|
|
27
28
|
ServerReputationApi,
|
|
28
29
|
} from './namespaces';
|
|
30
|
+
export {
|
|
31
|
+
createOxyAliaMachineCredentialAuth,
|
|
32
|
+
getOxyAliaMachineCredentialBearer,
|
|
33
|
+
isOxyAliaMachinePrincipal,
|
|
34
|
+
OXY_ALIA_RESOURCE_APPLICATION_ID,
|
|
35
|
+
OXY_ALIA_MACHINE_SCOPES,
|
|
36
|
+
} from './aliaMachineCredential';
|
|
37
|
+
export type {
|
|
38
|
+
AliaMachineCredentialIntrospection,
|
|
39
|
+
OxyAliaMachinePrincipal,
|
|
40
|
+
OxyAliaMachineRequest,
|
|
41
|
+
OxyAliaMachineCredentialHost,
|
|
42
|
+
} from './aliaMachineCredential';
|
|
29
43
|
export type {
|
|
30
44
|
CapabilityTicketGrant,
|
|
31
45
|
CapabilityTicketIntrospection,
|
package/src/server/namespaces.ts
CHANGED
|
@@ -18,6 +18,8 @@ import { NotificationsApi } from '../api/notifications';
|
|
|
18
18
|
import { LinkedAccountsApi } from '../api/linkedAccounts';
|
|
19
19
|
import { AgencyApi, type CapabilityExecutionAuthorization, type RequesterAssertionGrant, type RequesterAssertionIntrospection } from '../api/agency';
|
|
20
20
|
import { ReputationApi } from '../api/reputation';
|
|
21
|
+
import { AppsApi } from '../api/apps';
|
|
22
|
+
import { isOxyAliaMachinePrincipal, type AliaMachineCredentialIntrospection } from './aliaMachineCredential';
|
|
21
23
|
import { ServiceAssetMetadataError, ServiceLinkedDownloadUrlError } from '../OxyServices.errors';
|
|
22
24
|
import { extractErrorStatus } from '../utils/errorUtils';
|
|
23
25
|
import { logger } from '../logger';
|
|
@@ -39,6 +41,25 @@ function lane(ctx: OxyContext): ServiceLane {
|
|
|
39
41
|
return ctx.service;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
export class ServerAppsApi extends AppsApi {
|
|
45
|
+
constructor(private readonly serverCtx: OxyContext) { super(serverCtx); }
|
|
46
|
+
/** Live receiver-bound lookup using THIS resource server's own service token. */
|
|
47
|
+
async introspectAliaMachineCredential(token: string): Promise<AliaMachineCredentialIntrospection> {
|
|
48
|
+
if (!token.startsWith('oxy_sk_') || token.length > 2048) return { active: false };
|
|
49
|
+
const answer = await lane(this.serverCtx).request<unknown>(
|
|
50
|
+
'POST', '/internal/alia/machine-credentials/introspect', { token },
|
|
51
|
+
{ cache: false, retry: false },
|
|
52
|
+
);
|
|
53
|
+
if (!answer || typeof answer !== 'object' || Array.isArray(answer)) throw new Error('Invalid machine credential verdict');
|
|
54
|
+
const record = answer as Record<string, unknown>;
|
|
55
|
+
if (record.active === false && Object.keys(record).length === 1) return { active: false };
|
|
56
|
+
if (record.active !== true || Object.keys(record).length !== 2 || !isOxyAliaMachinePrincipal(record.principal)) {
|
|
57
|
+
throw new Error('Invalid machine credential verdict');
|
|
58
|
+
}
|
|
59
|
+
return { active: true, principal: record.principal };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
function chunk<T>(items: T[], size: number): T[][] {
|
|
43
64
|
const chunks: T[][] = [];
|
|
44
65
|
for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size));
|