@oxy.so/core 1.3.1 → 1.5.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.
@@ -28,7 +28,7 @@ export type { CommonsDeliveryFacts, CommonsDeliveryPlatform, CommonsDeliveryRout
28
28
  export type { ServiceTokenResponse, OAuthUserInfoResponse, OAuthTokenExchangeResult, } from './mixins/OxyServices.auth';
29
29
  export type { CommonsSignInHandle, CommonsSignInStatus, CommonsSignInPurpose, CommonsOAuthContext, CommonsApprovalInfo, CommonsApprovalSubjectAccount, CommonsSignInActionResult, CommonsOAuthFinalizeResult, CommonsDeliveryResult, } from './mixins/OxyServices.auth';
30
30
  export type { PushTokenPlatform, RegisterPushTokenInput, } from './mixins/OxyServices.notifications';
31
- export type { ServiceApp, ServiceActingAsVerification } from './mixins/OxyServices.utility';
31
+ export type { ServiceApp, ServiceActingAsVerification, OxyAuthRefusal } from './mixins/OxyServices.utility';
32
32
  export type { ContactDiscoveryMatch, ContactDiscoveryResponse, } from './mixins/OxyServices.contacts';
33
33
  export type { InitDeviceTransferResult, DeviceTransferOutcome, } from './mixins/OxyServices.deviceTransfer';
34
34
  export type { BulkFollowEntry, BulkFollowResult, BulkUnfollowEntry, BulkUnfollowResult, FollowMutationResult, ViewerGraph, } from './mixins/OxyServices.user';
@@ -39,7 +39,7 @@ export type { CanonicalUserHandleInput, UserHandleInput } from './utils/userHand
39
39
  export { normalizeProfileLinks } from './utils/profileLinks';
40
40
  export type { ProfileLink, ProfileLinkMetadata } from './utils/profileLinks';
41
41
  export type { PublicApplication, ConnectedApp, ConnectedMcpClient, } from './mixins/OxyServices.connectedApps';
42
- export type { AccountCapabilityPolicy, AvailableCapabilityCatalog, CapabilityExecutionAuthorization, CreateDelegationGrantInput, DelegationCatalogBinding, DelegationGrantView, PutAccountCapabilityPolicyInput, UpdateDelegationGrantInput, } from './mixins/OxyServices.agency';
42
+ export type { AccountCapabilityPolicy, AvailableCapabilityCatalog, CapabilityExecutionAuthorization, CreateDelegationGrantInput, DelegationCatalogBinding, DelegationGrantView, PutAccountCapabilityPolicyInput, RequesterAssertionGrant, RequesterAssertionIntrospection, UpdateDelegationGrantInput, } from './mixins/OxyServices.agency';
43
43
  export type { StoreCategory, StoreRating, StoreListingSummary, StoreListingDetail, StoreScreenshot, StoreScreenshotPlatform, StoreReview, StoreOwnReview, WriteStoreReviewInput, StoreListingStatus, PublisherListing, WriteListingInput, AddScreenshotInput, UpdateScreenshotInput, StorePage, StorePageOptions, StoreReviewsOptions, } from './mixins/OxyServices.store';
44
44
  export type { AccountKind, AccountCategoryId, AccountRelationship, AccountRole, AccountMemberStatus, AccountMemberSource, AccountMember, AccountNode, ListAccountsOptions, CreateAccountInput, UpdateAccountInput, ProvisionChannelInput, ProvisionChannelMemberInput, ProvisionChannelResult, InviteAccountMemberInput, UpdateAccountMemberInput, TransferAccountOwnershipInput, AccountSuccessResult, SwitchAccountResult, Application, ApplicationType, ApplicationStatus, ApplicationCredential, ApplicationCredentialType, ApplicationCredentialStatus, ApplicationEnvironment, CreateApplicationInput, UpdateApplicationInput, CreateApplicationCredentialInput, RotateApplicationCredentialInput, ApplicationCredentialWithSecret, RotateApplicationCredentialResult, ApplicationUsagePeriod, ApplicationUsageSummary, ApplicationUsageByDay, ApplicationUsageByEndpoint, ApplicationUsageStats, } from './mixins/OxyServices.accounts';
45
45
  export { ACCOUNT_CATEGORY_IDS, MAX_ACCOUNT_CATEGORIES, SELECTABLE_ACCOUNT_CATEGORY_IDS, isSelectableAccountCategoryId, kindAcceptsAccountCategories, } from './mixins/OxyServices.accounts';
@@ -69,6 +69,23 @@ export interface CapabilityExecutionAuthorization {
69
69
  createdAt: string;
70
70
  updatedAt: string;
71
71
  }
72
+ /** A present-requester assertion minted for a product backend (ADR 0025). */
73
+ export interface RequesterAssertionGrant {
74
+ assertion: string;
75
+ expiresAt: string;
76
+ requesterAccountId: string;
77
+ agentId: string;
78
+ }
79
+ /** Oxy's live answer to the audience that consumed an assertion (ADR 0025). */
80
+ export interface RequesterAssertionIntrospection {
81
+ active: boolean;
82
+ requesterAccountId?: string;
83
+ agentId?: string;
84
+ applicationId?: string;
85
+ credentialId?: string;
86
+ jti?: string;
87
+ expiresAt?: string;
88
+ }
72
89
  export declare function OxyServicesAgencyMixin<T extends typeof OxyServicesBase>(Base: T): {
73
90
  new (...args: any[]): {
74
91
  listAvailableCapabilityCatalogs(accountId: string): Promise<AvailableCapabilityCatalog[]>;
@@ -82,6 +99,30 @@ export declare function OxyServicesAgencyMixin<T extends typeof OxyServicesBase>
82
99
  listCapabilityExecutionAuthorizations(ownerAccountId: string): Promise<CapabilityExecutionAuthorization[]>;
83
100
  revokeCapabilityExecutionAuthorization(authorizationId: string, ownerAccountId: string): Promise<void>;
84
101
  listCapabilityAuditEvents(accountId: string): Promise<AuditEvent[]>;
102
+ /**
103
+ * Product backend → Oxy: trade the signed-in requester's access token for a
104
+ * one-use assertion naming `agentId` (ADR 0025). Authenticated with THIS
105
+ * instance's service credential (`configureServiceAuth`). `subjectToken` is
106
+ * sent to Oxy only, in the body; never forward it anywhere else.
107
+ *
108
+ * Not retried: a refusal is an answer, and the caller mints per turn.
109
+ */
110
+ mintRequesterAssertion(input: {
111
+ agentId: string;
112
+ subjectToken: string;
113
+ }): Promise<RequesterAssertionGrant>;
114
+ /**
115
+ * Audience → Oxy: verify, live-revalidate and CONSUME an assertion. A second
116
+ * call with the same assertion answers `active: false`. Authenticated with
117
+ * the audience's own service credential.
118
+ */
119
+ introspectRequesterAssertion(input: {
120
+ assertion: string;
121
+ presenter: {
122
+ applicationId: string;
123
+ credentialId: string;
124
+ };
125
+ }): Promise<RequesterAssertionIntrospection>;
85
126
  httpService: import("../HttpService").HttpService;
86
127
  cloudURL: string;
87
128
  config: import("../OxyServices.base").OxyConfig;
@@ -58,6 +58,43 @@ export interface ServiceApp {
58
58
  /** Test/live isolation (F2.0): which `ApplicationCredential.environment` minted this token. */
59
59
  environment: OxyServiceEnvironment;
60
60
  }
61
+ /**
62
+ * Why a request's credential was refused, recorded on the request itself.
63
+ *
64
+ * The middleware answers a fixed, deliberately uninformative body to the
65
+ * client; this is the other half of that trade. A refusal is ALWAYS observable
66
+ * to the HOST: it is logged at `warn` with a stable `code`, handed to
67
+ * `onRefusal`, and left on `req.oxyAuthRefusal` for the host's own logs.
68
+ *
69
+ * It exists because the opposite cost was measured: Oxy served
70
+ * `{"keys":[]}` from `/.well-known/jwks.json` with no Ed25519 signing key
71
+ * bound, every service token failed `Oxy service-token key set is unavailable`,
72
+ * and on the OPTIONAL path that failure fell straight through to `next()`.
73
+ * The host then answered its own generic 401 and logged nothing at all, so the
74
+ * one fact that named the fault — an empty key set — existed nowhere. Hours.
75
+ *
76
+ * **Nothing here is ever sent to a client, and `reason` never contains the
77
+ * token, a signature, a secret or a session id.** It carries the SDK's own
78
+ * failure message (`kid` unknown, audience mismatch, key set unavailable) plus
79
+ * the ids already considered safe to log elsewhere in this middleware.
80
+ */
81
+ export interface OxyAuthRefusal {
82
+ /** Stable, greppable code. Matches the `code` a non-optional refusal answers. */
83
+ code: string;
84
+ /** Which credential lane refused. */
85
+ stage: 'token' | 'service-token' | 'session';
86
+ /** Human-readable cause. Never a token, signature, secret or session id. */
87
+ reason: string;
88
+ /** The status a NON-optional mount would have answered with. */
89
+ status: number;
90
+ /**
91
+ * `true` when the middleware was mounted with `optional: true`, so this
92
+ * refusal did not produce a response — the request continued unauthenticated
93
+ * and whatever the host does next (typically its own generic 401) is the
94
+ * only thing the client sees.
95
+ */
96
+ optional: boolean;
97
+ }
61
98
  /**
62
99
  * Options for oxyClient.auth() middleware
63
100
  */
@@ -70,6 +107,17 @@ interface AuthMiddlewareOptions {
70
107
  loadUser?: boolean;
71
108
  /** Optional auth - attach user if token present but don't block (default: false) */
72
109
  optional?: boolean;
110
+ /**
111
+ * Called whenever a PRESENTED credential is refused, on the optional path as
112
+ * well as the blocking one. Runs before the response (if any) and after
113
+ * `req.oxyAuthRefusal` is set, so a host can raise its own alert or carry the
114
+ * code into its request log. Throwing from it is swallowed — an observer must
115
+ * not be able to turn a refusal into a 500.
116
+ *
117
+ * Refusal is about a credential that was offered and rejected: a request with
118
+ * NO `Authorization` header is not refused and does not reach this.
119
+ */
120
+ onRefusal?: (refusal: OxyAuthRefusal) => void;
73
121
  /**
74
122
  * Public JWKS endpoint used for Ed25519 service-token verification. Defaults
75
123
  * to `/.well-known/jwks.json` on this Oxy client's configured API origin.
@@ -362,6 +410,7 @@ interface AuthReq {
362
410
  userId: string;
363
411
  scopes: string[];
364
412
  };
413
+ oxyAuthRefusal?: OxyAuthRefusal;
365
414
  }
366
415
  interface AuthRes {
367
416
  status(code: number): AuthRes;
@@ -1,8 +1,9 @@
1
1
  import type { NextFunction, Request, RequestHandler, Response } from 'express';
2
2
  import type { OxyServices } from '../OxyServices';
3
+ import type { OxyAuthRefusal } from '../mixins/OxyServices.utility';
3
4
  import { OXY_SERVICE_ENVIRONMENTS, type OxyServiceEnvironment } from '../utils/oxyServiceEnvironment';
4
5
  export { OXY_SERVICE_ENVIRONMENTS };
5
- export type { OxyServiceEnvironment };
6
+ export type { OxyServiceEnvironment, OxyAuthRefusal };
6
7
  export interface OxyRequestUser {
7
8
  id: string;
8
9
  _id?: string;
@@ -35,6 +36,12 @@ export interface OxyAuthRequest extends Request {
35
36
  sessionId?: string | null;
36
37
  serviceApp?: OxyServiceAppContext;
37
38
  serviceActingAs?: OxyServiceActingAsContext;
39
+ /**
40
+ * Why the presented credential was refused, when one was and the request
41
+ * still reached the handler (the optional path). Read it with
42
+ * {@link getOxyAuthRefusal} — never put it in a response body.
43
+ */
44
+ oxyAuthRefusal?: OxyAuthRefusal;
38
45
  }
39
46
  export interface OxyAuthenticatedRequest extends OxyAuthRequest {
40
47
  userId: string;
@@ -125,6 +132,17 @@ export declare function getOxyDelegatedUserId(req: Request): string | null;
125
132
  */
126
133
  export declare function getOxyRequestAttribution(req: Request): OxyRequestAttribution | null;
127
134
  export declare function getRequiredOxyUserId(req: Request): string;
135
+ /**
136
+ * Why the credential on this request was refused, or `null` when none was
137
+ * presented (or it was accepted).
138
+ *
139
+ * Set by `oxy.auth()` — including the `optional: true` mount the composed
140
+ * helpers use, where a refusal otherwise leaves no trace: the request simply
141
+ * arrives unauthenticated and the host answers its own generic 401. Hosts log
142
+ * this beside their own 401; it is diagnostic and **must not be put into a
143
+ * response body**, which is exactly what it is kept out of.
144
+ */
145
+ export declare function getOxyAuthRefusal(req: Request): OxyAuthRefusal | null;
128
146
  export declare function requireOxyAuth(req: Request, res: Response, next: NextFunction): void;
129
147
  export declare function createOptionalOxyAuth(oxy: OxyServices, options?: OxyAuthMiddlewareOptions): RequestHandler;
130
148
  export declare function createOxyAuthMiddleware(oxy: OxyServices, options?: OxyAuthMiddlewareOptions): RequestHandler;
@@ -14,8 +14,8 @@
14
14
  * app.use(createOxyRateLimit(oxy, { store: redisStore }));
15
15
  * ```
16
16
  */
17
- export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyBillingPrincipal, getOxyDelegatedUserId, getOxyRequestAttribution, getOxyUserId, getRequiredOxyBillingPrincipal, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, OXY_SERVICE_ENVIRONMENTS, } from './auth';
18
- export type { OxyAuthenticatedRequest, OxyAuthMiddlewareOptions, OxyAuthRequest, OxyBillingPrincipal, OxyRequestAttribution, OxyRequestUser, OxyServiceActingAsContext, OxyServiceAppContext, OxyServiceEnvironment, } from './auth';
17
+ export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyAuthRefusal, getOxyBillingPrincipal, getOxyDelegatedUserId, getOxyRequestAttribution, getOxyUserId, getRequiredOxyBillingPrincipal, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, OXY_SERVICE_ENVIRONMENTS, } from './auth';
18
+ export type { OxyAuthenticatedRequest, OxyAuthRefusal, OxyAuthMiddlewareOptions, OxyAuthRequest, OxyBillingPrincipal, OxyRequestAttribution, OxyRequestUser, OxyServiceActingAsContext, OxyServiceAppContext, OxyServiceEnvironment, } from './auth';
19
19
  export { createOxyRateLimit } from './rateLimit';
20
20
  export type { OxyRateLimitOptions } from './rateLimit';
21
21
  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';
@@ -26,6 +26,8 @@ export { buildOxyCspDirectives, buildOxyPagesHeaders, createOxySecurityHeaders,
26
26
  export type { OxyCspDirective, OxyCspExtensions, OxyPagesHeadersOptions, OxySecurityHeadersOptions, } from './securityHeaders';
27
27
  export { CapabilityTicketError, createCapabilityTicketMiddleware, inputSatisfiesCapabilityLimits, issueCapabilityTicket, readCapabilityAuthorization, verifyCapabilityTicket, } from './capabilityTicket';
28
28
  export type { CapabilityTicketMiddlewareOptions, CapabilityTicketRequest, CapabilityTicketSigningOptions, CapabilityTicketVerificationOptions, } from './capabilityTicket';
29
+ export { createOxyJwksKeyResolver, createOxyRequesterAssertionAuth, OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER, OXY_REQUESTER_ASSERTION_HEADER, OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS, OXY_REQUESTER_ASSERTION_TYPE, OxyRequesterAssertionError, oxyRequesterAssertionClaimsSchema, readOxyRequesterAssertionKeyId, signOxyRequesterAssertion, verifyOxyRequesterAssertion, } from './requesterAssertion';
30
+ export type { OxyJwksKeyResolverOptions, OxyRequesterAssertionAuthOptions, OxyRequesterAssertionClaims, OxyRequesterAssertionErrorCode, OxyRequesterAssertionIntrospection, OxyRequesterAssertionIntrospector, OxyRequesterAssertionRequest, OxyRequesterAssertionVerificationOptions, OxyRequesterContext, } from './requesterAssertion';
29
31
  export { verifySecret } from './verifySecret';
30
32
  export { createOxyUserInvalidationHandler, publishOxyUserInvalidation, } from './userInvalidation';
31
33
  export type { OxyInvalidationPublisher, OxyUserInvalidationHandlerOptions, } from './userInvalidation';
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Present-requester assertions (ADR 0025).
3
+ *
4
+ * A first-party product backend holding a signed-in person's live Oxy session
5
+ * trades it with Oxy (`OxyServices.mintRequesterAssertion`) for a one-use,
6
+ * short-lived `OXY-REQUESTER+JWT`, and sends that — never the person's bearer —
7
+ * to the audience service (Alia) beside its own service token. The audience
8
+ * mounts {@link createOxyRequesterAssertionAuth} after `createOxyAuthMiddleware`
9
+ * and gets `req.userId` from the verified, introspected and consumed assertion.
10
+ *
11
+ * Signing and stateless verification live here so Oxy (the signer) and every
12
+ * verifier share one implementation of the format.
13
+ */
14
+ import { type KeyObject } from 'node:crypto';
15
+ import type { RequestHandler } from 'express';
16
+ import { z } from 'zod';
17
+ import type { OxyAuthRequest } from './auth';
18
+ export declare const OXY_REQUESTER_ASSERTION_TYPE = "OXY-REQUESTER+JWT";
19
+ /** The request header an audience reads the assertion from. */
20
+ export declare const OXY_REQUESTER_ASSERTION_HEADER = "x-oxy-requester-assertion";
21
+ /** Oxy signs for 120 s; no verifier accepts a lifetime above this ceiling. */
22
+ export declare const OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS = 300;
23
+ export declare const OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER = "https://api.oxy.so";
24
+ export declare const oxyRequesterAssertionClaimsSchema: z.ZodObject<{
25
+ iss: z.ZodString;
26
+ aud: z.ZodString;
27
+ /** The requester account. */
28
+ sub: z.ZodString;
29
+ jti: z.ZodString;
30
+ iat: z.ZodNumber;
31
+ exp: z.ZodNumber;
32
+ /** The application the assertion was minted for, and the only valid presenter. */
33
+ azp: z.ZodString;
34
+ /** The exact credential of that application. */
35
+ cid: z.ZodString;
36
+ /** The one native agent this requester may reach with it. */
37
+ agentId: z.ZodString;
38
+ }, "strict", z.ZodTypeAny, {
39
+ exp: number;
40
+ sub: string;
41
+ aud: string;
42
+ iss: string;
43
+ iat: number;
44
+ jti: string;
45
+ azp: string;
46
+ cid: string;
47
+ agentId: string;
48
+ }, {
49
+ exp: number;
50
+ sub: string;
51
+ aud: string;
52
+ iss: string;
53
+ iat: number;
54
+ jti: string;
55
+ azp: string;
56
+ cid: string;
57
+ agentId: string;
58
+ }>;
59
+ export type OxyRequesterAssertionClaims = z.infer<typeof oxyRequesterAssertionClaimsSchema>;
60
+ export type OxyRequesterAssertionErrorCode = 'malformed' | 'unknown_key' | 'invalid_signature' | 'invalid_claims' | 'wrong_issuer' | 'wrong_audience' | 'not_yet_valid' | 'expired' | 'ttl_exceeded';
61
+ export declare class OxyRequesterAssertionError extends Error {
62
+ readonly code: OxyRequesterAssertionErrorCode;
63
+ constructor(code: OxyRequesterAssertionErrorCode);
64
+ }
65
+ /** Signs an assertion. Oxy is the only intended caller. */
66
+ export declare function signOxyRequesterAssertion(claims: OxyRequesterAssertionClaims, signing: {
67
+ readonly keyId: string;
68
+ readonly privateKey: KeyObject;
69
+ }): string;
70
+ /** Reads the `kid` without trusting anything else in the token. */
71
+ export declare function readOxyRequesterAssertionKeyId(token: string): string;
72
+ export interface OxyRequesterAssertionVerificationOptions {
73
+ readonly publicKey: KeyObject | undefined;
74
+ readonly issuer: string;
75
+ readonly audience: string;
76
+ readonly now?: Date;
77
+ }
78
+ /**
79
+ * Stateless verification: header, signature, claim shape, issuer, audience and
80
+ * lifetime. It does NOT prove the assertion is unused or that the requester's
81
+ * session is still live — only introspection does.
82
+ */
83
+ export declare function verifyOxyRequesterAssertion(token: string, options: OxyRequesterAssertionVerificationOptions): OxyRequesterAssertionClaims;
84
+ export interface OxyJwksKeyResolverOptions {
85
+ readonly jwksUrl: string;
86
+ readonly fetch?: typeof fetch;
87
+ readonly clock?: () => number;
88
+ }
89
+ /**
90
+ * Cached Ed25519 JWKS resolver. An unknown `kid` refreshes at most once a
91
+ * minute, so a flood of forged key ids cannot turn into a flood of fetches. A
92
+ * failed refresh keeps serving keys that are still inside their cache window.
93
+ */
94
+ export declare function createOxyJwksKeyResolver(options: OxyJwksKeyResolverOptions): (keyId: string) => Promise<KeyObject | undefined>;
95
+ export interface OxyRequesterAssertionIntrospection {
96
+ readonly active: boolean;
97
+ readonly requesterAccountId?: string;
98
+ readonly agentId?: string;
99
+ readonly applicationId?: string;
100
+ readonly credentialId?: string;
101
+ readonly jti?: string;
102
+ readonly expiresAt?: string;
103
+ }
104
+ export interface OxyRequesterAssertionIntrospector {
105
+ introspectRequesterAssertion(input: {
106
+ assertion: string;
107
+ presenter: {
108
+ applicationId: string;
109
+ credentialId: string;
110
+ };
111
+ }): Promise<OxyRequesterAssertionIntrospection>;
112
+ getBaseURL(): string;
113
+ }
114
+ /** What the audience learns about the present requester. */
115
+ export interface OxyRequesterContext {
116
+ readonly userId: string;
117
+ readonly agentId: string;
118
+ readonly applicationId: string;
119
+ readonly credentialId: string;
120
+ readonly jti: string;
121
+ readonly expiresAt: string;
122
+ }
123
+ export interface OxyRequesterAssertionRequest extends OxyAuthRequest {
124
+ oxyRequester?: OxyRequesterContext;
125
+ }
126
+ export interface OxyRequesterAssertionAuthOptions {
127
+ /** This service's audience name, e.g. `alia`. */
128
+ readonly audience: string;
129
+ /** Defaults to `https://api.oxy.so`. */
130
+ readonly issuer?: string;
131
+ /** Defaults to `/capabilities/.well-known/jwks.json` on the introspector's origin. */
132
+ readonly jwksUrl?: string;
133
+ readonly resolvePublicKey?: (keyId: string) => Promise<KeyObject | undefined>;
134
+ readonly now?: () => Date;
135
+ /** Observability hook. Receives ids only, never the assertion. */
136
+ readonly onRejected?: (event: {
137
+ readonly code: string;
138
+ readonly status: number;
139
+ readonly applicationId: string | null;
140
+ }) => void;
141
+ }
142
+ /**
143
+ * Accepts an `X-Oxy-Requester-Assertion` presented beside a VERIFIED service
144
+ * token. Mount it after `createOxyAuthMiddleware`. Without the header it does
145
+ * nothing, so it composes with every other lane.
146
+ *
147
+ * `introspector` must be an `OxyServices` configured with THIS service's own
148
+ * credential (`configureServiceAuth`): Oxy only lets the audience's application
149
+ * consume an assertion.
150
+ */
151
+ export declare function createOxyRequesterAssertionAuth(introspector: OxyRequesterAssertionIntrospector, options: OxyRequesterAssertionAuthOptions): RequestHandler;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
package/src/index.ts CHANGED
@@ -81,7 +81,7 @@ export type {
81
81
  PushTokenPlatform,
82
82
  RegisterPushTokenInput,
83
83
  } from './mixins/OxyServices.notifications';
84
- export type { ServiceApp, ServiceActingAsVerification } from './mixins/OxyServices.utility';
84
+ export type { ServiceApp, ServiceActingAsVerification, OxyAuthRefusal } from './mixins/OxyServices.utility';
85
85
  export type {
86
86
  ContactDiscoveryMatch,
87
87
  ContactDiscoveryResponse,
@@ -136,6 +136,8 @@ export type {
136
136
  DelegationCatalogBinding,
137
137
  DelegationGrantView,
138
138
  PutAccountCapabilityPolicyInput,
139
+ RequesterAssertionGrant,
140
+ RequesterAssertionIntrospection,
139
141
  UpdateDelegationGrantInput,
140
142
  } from './mixins/OxyServices.agency';
141
143
 
@@ -97,6 +97,25 @@ export interface CapabilityExecutionAuthorization {
97
97
  updatedAt: string;
98
98
  }
99
99
 
100
+ /** A present-requester assertion minted for a product backend (ADR 0025). */
101
+ export interface RequesterAssertionGrant {
102
+ assertion: string;
103
+ expiresAt: string;
104
+ requesterAccountId: string;
105
+ agentId: string;
106
+ }
107
+
108
+ /** Oxy's live answer to the audience that consumed an assertion (ADR 0025). */
109
+ export interface RequesterAssertionIntrospection {
110
+ active: boolean;
111
+ requesterAccountId?: string;
112
+ agentId?: string;
113
+ applicationId?: string;
114
+ credentialId?: string;
115
+ jti?: string;
116
+ expiresAt?: string;
117
+ }
118
+
100
119
  function query(path: string, key: string, value: string): string {
101
120
  return `${path}?${key}=${encodeURIComponent(value)}`;
102
121
  }
@@ -273,5 +292,62 @@ export function OxyServicesAgencyMixin<T extends typeof OxyServicesBase>(Base: T
273
292
  throw this.handleError(error);
274
293
  }
275
294
  }
295
+
296
+ /**
297
+ * Product backend → Oxy: trade the signed-in requester's access token for a
298
+ * one-use assertion naming `agentId` (ADR 0025). Authenticated with THIS
299
+ * instance's service credential (`configureServiceAuth`). `subjectToken` is
300
+ * sent to Oxy only, in the body; never forward it anywhere else.
301
+ *
302
+ * Not retried: a refusal is an answer, and the caller mints per turn.
303
+ */
304
+ async mintRequesterAssertion(input: {
305
+ agentId: string;
306
+ subjectToken: string;
307
+ }): Promise<RequesterAssertionGrant> {
308
+ try {
309
+ const serviceToken = await (this as unknown as { getServiceToken(): Promise<string> }).getServiceToken();
310
+ return await this.makeRequest<RequesterAssertionGrant>(
311
+ 'POST',
312
+ '/internal/native-agents/requester-assertions',
313
+ { agentId: input.agentId, subjectToken: input.subjectToken },
314
+ {
315
+ cache: false,
316
+ retry: false,
317
+ timeout: 5000,
318
+ headers: { Authorization: `Bearer ${serviceToken}` },
319
+ },
320
+ );
321
+ } catch (error) {
322
+ throw this.handleError(error);
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Audience → Oxy: verify, live-revalidate and CONSUME an assertion. A second
328
+ * call with the same assertion answers `active: false`. Authenticated with
329
+ * the audience's own service credential.
330
+ */
331
+ async introspectRequesterAssertion(input: {
332
+ assertion: string;
333
+ presenter: { applicationId: string; credentialId: string };
334
+ }): Promise<RequesterAssertionIntrospection> {
335
+ try {
336
+ const serviceToken = await (this as unknown as { getServiceToken(): Promise<string> }).getServiceToken();
337
+ return await this.makeRequest<RequesterAssertionIntrospection>(
338
+ 'POST',
339
+ '/internal/native-agents/requester-assertions/introspect',
340
+ { assertion: input.assertion, presenter: input.presenter },
341
+ {
342
+ cache: false,
343
+ retry: false,
344
+ timeout: 5000,
345
+ headers: { Authorization: `Bearer ${serviceToken}` },
346
+ },
347
+ );
348
+ } catch (error) {
349
+ throw this.handleError(error);
350
+ }
351
+ }
276
352
  };
277
353
  }