@oxy.so/core 7.21.1 → 7.23.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/README.md +1 -1
- package/dist/cjs/api/billing.js +3 -2
- package/dist/cjs/server/OxyServer.js +29 -0
- package/dist/esm/api/billing.js +4 -3
- package/dist/esm/server/OxyServer.js +29 -0
- package/dist/types/api/billing.d.ts +3 -2
- package/dist/types/server/OxyServer.d.ts +16 -0
- package/package.json +2 -2
- package/src/api/billing.ts +11 -8
- package/src/server/OxyServer.ts +29 -0
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ The package exposes two public entry points:
|
|
|
31
31
|
- `@oxy.so/core/session` — `SessionClient`, the account dialog controller, auth-state stores, cold boot
|
|
32
32
|
- `@oxy.so/core/crypto` — `KeyManager`, `SignatureService`, `RecoveryPhraseService` and the primitives under them
|
|
33
33
|
- `@oxy.so/core/civic`, `@oxy.so/core/inference`, `@oxy.so/core/logger`
|
|
34
|
-
- `@oxy.so/core/server` — `OxyServer` (workload service tokens, `middleware.*`, `withUser` / `asUser`, `appPermissionsFor`, `accountAccessFor`, `applicationAccessFor`) and the Express helpers (`createOxyRateLimit`, `createOxyAuthMiddleware`, `getOxyUserId`, `getRequiredOxyUserId`, `createOxyCors`, `createOxySocketCors`, `createOxySecurityHeaders`, `buildOxyCspDirectives`, `safeFetch`, `verifySecret`, and request types)
|
|
34
|
+
- `@oxy.so/core/server` — `OxyServer` (workload service tokens, `middleware.*`, `withUser` / `asUser`, `isFirstPartyServiceUrl`, `appPermissionsFor`, `accountAccessFor`, `applicationAccessFor`) and the Express helpers (`createOxyRateLimit`, `createOxyAuthMiddleware`, `getOxyUserId`, `getRequiredOxyUserId`, `createOxyCors`, `createOxySocketCors`, `createOxySecurityHeaders`, `buildOxyCspDirectives`, `safeFetch`, `verifySecret`, and request types)
|
|
35
35
|
|
|
36
36
|
Heavy pieces have their own entries so an app ships only what it uses; React Native and browser bundles never import Express. The full API map is [docs/api.mdx](./docs/api.mdx).
|
|
37
37
|
|
package/dist/cjs/api/billing.js
CHANGED
|
@@ -20,8 +20,9 @@ class BillingApi {
|
|
|
20
20
|
constructor(ctx) {
|
|
21
21
|
this.ctx = ctx;
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/** The payment provider's customer portal for one named source the caller pays for. */
|
|
24
|
+
async openProductSubscriptionPortal(request) {
|
|
25
|
+
return (0, client_1.parseContract)(client_1.productSubscriptionPortalResponseCheck, await this.ctx.request('POST', '/billing/product-subscriptions/portal', (0, client_1.parseContract)(client_1.productSubscriptionPortalRequestCheck, request), { cache: false }));
|
|
25
26
|
}
|
|
26
27
|
async startProductPlanCheckout(request) {
|
|
27
28
|
return (0, client_1.parseContract)(client_1.personalPlanCheckoutResultCheck, await this.ctx.request('POST', '/billing/checkout/product-plan', (0, client_1.parseContract)(client_1.productPlanCheckoutRequestCheck, request), { cache: false }));
|
|
@@ -49,6 +49,7 @@ const OxyServices_1 = require("../OxyServices");
|
|
|
49
49
|
const HttpService_1 = require("../HttpService");
|
|
50
50
|
const authenticatedFetch_1 = require("../authenticatedFetch");
|
|
51
51
|
const requestSignal_1 = require("../utils/requestSignal");
|
|
52
|
+
const officialOrigins_1 = require("../utils/officialOrigins");
|
|
52
53
|
const errorUtils_1 = require("../utils/errorUtils");
|
|
53
54
|
const logger_1 = require("../logger");
|
|
54
55
|
const workloadIdentity_1 = require("./workloadIdentity");
|
|
@@ -193,6 +194,34 @@ class OxyServer extends OxyServices_1.OxyServices {
|
|
|
193
194
|
applicationAccessFor: (req, applicationId, options) => this.applicationAccessFor(req, applicationId, options),
|
|
194
195
|
});
|
|
195
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Whether `url` names Oxy's own API — the first-party service this server is
|
|
199
|
+
* configured for (`baseURL`), when that origin is an official Oxy origin.
|
|
200
|
+
*
|
|
201
|
+
* For a URL a caller chose (a customer webhook endpoint, say) that may name
|
|
202
|
+
* Oxy itself: `true` sends it through this server
|
|
203
|
+
* (`createServiceClient({ baseURL: oxy.baseURL, audience: 'oxy-api' }).fetch`),
|
|
204
|
+
* `false` through `safeFetch`. Inside the VPC `api.oxy.so` resolves to a
|
|
205
|
+
* private address, which `safeFetch` refuses by design.
|
|
206
|
+
*
|
|
207
|
+
* The comparison is by exact origin (scheme, host and port): a look-alike
|
|
208
|
+
* (`api.oxy.so.evil.com`), another official host, a credential-carrying URL
|
|
209
|
+
* or anything unparseable is `false`, and so goes on to `safeFetch`'s
|
|
210
|
+
* checks. Never decide first-party by hostname suffix or branding.
|
|
211
|
+
*/
|
|
212
|
+
isFirstPartyServiceUrl(url) {
|
|
213
|
+
let target;
|
|
214
|
+
try {
|
|
215
|
+
target = new URL(String(url));
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password)
|
|
221
|
+
return false;
|
|
222
|
+
const own = new URL(this.baseURL).origin;
|
|
223
|
+
return target.origin === own && (0, officialOrigins_1.isOfficialWebOrigin)(own);
|
|
224
|
+
}
|
|
196
225
|
/** A product SDK transport authenticated only by this process's workload. */
|
|
197
226
|
createServiceClient(options) {
|
|
198
227
|
const origin = new URL(options.baseURL);
|
package/dist/esm/api/billing.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `payment.controller`, `wallet.routes` in `packages/api`); `Date` columns
|
|
10
10
|
* arrive as ISO-8601 strings.
|
|
11
11
|
*/
|
|
12
|
-
import { cancelProductSubscriptionCheck, parseContract, personalPlanCatalogueCheck, personalPlanCheckoutRequestCheck, personalPlanCheckoutResultCheck, productPlanCatalogueCheck, productPlanCheckoutRequestCheck, productSubscriptionCancellationResultCheck,
|
|
12
|
+
import { cancelProductSubscriptionCheck, parseContract, personalPlanCatalogueCheck, personalPlanCheckoutRequestCheck, personalPlanCheckoutResultCheck, productPlanCatalogueCheck, productPlanCheckoutRequestCheck, productSubscriptionCancellationResultCheck, productSubscriptionPortalRequestCheck, productSubscriptionPortalResponseCheck, productSubscriptionsResponseCheck, subjectProductAccessCheck, subjectProductAccessQueryCheck, subscriptionCreditGrantsResponseCheck, } from '@oxy.so/contracts/client';
|
|
13
13
|
const enc = encodeURIComponent;
|
|
14
14
|
const SUBSCRIPTION_TTL = 2 * 60 * 1000;
|
|
15
15
|
const WALLET_TTL = 60 * 1000;
|
|
@@ -17,8 +17,9 @@ export class BillingApi {
|
|
|
17
17
|
constructor(ctx) {
|
|
18
18
|
this.ctx = ctx;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/** The payment provider's customer portal for one named source the caller pays for. */
|
|
21
|
+
async openProductSubscriptionPortal(request) {
|
|
22
|
+
return parseContract(productSubscriptionPortalResponseCheck, await this.ctx.request('POST', '/billing/product-subscriptions/portal', parseContract(productSubscriptionPortalRequestCheck, request), { cache: false }));
|
|
22
23
|
}
|
|
23
24
|
async startProductPlanCheckout(request) {
|
|
24
25
|
return parseContract(personalPlanCheckoutResultCheck, await this.ctx.request('POST', '/billing/checkout/product-plan', parseContract(productPlanCheckoutRequestCheck, request), { cache: false }));
|
|
@@ -46,6 +46,7 @@ import { OxyServices } from '../OxyServices.js';
|
|
|
46
46
|
import { HttpService } from '../HttpService.js';
|
|
47
47
|
import { createAuthenticatedFetch } from '../authenticatedFetch.js';
|
|
48
48
|
import { awaitRequestSignal } from '../utils/requestSignal.js';
|
|
49
|
+
import { isOfficialWebOrigin } from '../utils/officialOrigins.js';
|
|
49
50
|
import { createCancelledError, extractErrorStatus } from '../utils/errorUtils.js';
|
|
50
51
|
import { logger } from '../logger/index.js';
|
|
51
52
|
import { canAttestWorkloadIdentity, requestWorkloadServiceToken, } from './workloadIdentity.js';
|
|
@@ -189,6 +190,34 @@ export class OxyServer extends OxyServices {
|
|
|
189
190
|
applicationAccessFor: (req, applicationId, options) => this.applicationAccessFor(req, applicationId, options),
|
|
190
191
|
});
|
|
191
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Whether `url` names Oxy's own API — the first-party service this server is
|
|
195
|
+
* configured for (`baseURL`), when that origin is an official Oxy origin.
|
|
196
|
+
*
|
|
197
|
+
* For a URL a caller chose (a customer webhook endpoint, say) that may name
|
|
198
|
+
* Oxy itself: `true` sends it through this server
|
|
199
|
+
* (`createServiceClient({ baseURL: oxy.baseURL, audience: 'oxy-api' }).fetch`),
|
|
200
|
+
* `false` through `safeFetch`. Inside the VPC `api.oxy.so` resolves to a
|
|
201
|
+
* private address, which `safeFetch` refuses by design.
|
|
202
|
+
*
|
|
203
|
+
* The comparison is by exact origin (scheme, host and port): a look-alike
|
|
204
|
+
* (`api.oxy.so.evil.com`), another official host, a credential-carrying URL
|
|
205
|
+
* or anything unparseable is `false`, and so goes on to `safeFetch`'s
|
|
206
|
+
* checks. Never decide first-party by hostname suffix or branding.
|
|
207
|
+
*/
|
|
208
|
+
isFirstPartyServiceUrl(url) {
|
|
209
|
+
let target;
|
|
210
|
+
try {
|
|
211
|
+
target = new URL(String(url));
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password)
|
|
217
|
+
return false;
|
|
218
|
+
const own = new URL(this.baseURL).origin;
|
|
219
|
+
return target.origin === own && isOfficialWebOrigin(own);
|
|
220
|
+
}
|
|
192
221
|
/** A product SDK transport authenticated only by this process's workload. */
|
|
193
222
|
createServiceClient(options) {
|
|
194
223
|
const origin = new URL(options.baseURL);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `payment.controller`, `wallet.routes` in `packages/api`); `Date` columns
|
|
10
10
|
* arrive as ISO-8601 strings.
|
|
11
11
|
*/
|
|
12
|
-
import type { PersonalPlanCatalogue, PersonalPlanCheckoutRequest, PersonalPlanCheckoutResult, ProductPlanCatalogue, ProductPlanCheckoutRequest, ProductSubscriptionCancellationResult,
|
|
12
|
+
import type { PersonalPlanCatalogue, PersonalPlanCheckoutRequest, PersonalPlanCheckoutResult, ProductPlanCatalogue, ProductPlanCheckoutRequest, ProductSubscriptionCancellationResult, ProductSubscriptionPortalRequest, ProductSubscriptionPortalResponse, ProductSubscriptionSummary, SubjectProductAccess, SubjectProductAccessQuery, SubscriptionCreditGrant } from '@oxy.so/contracts';
|
|
13
13
|
import type { OxyContext } from '../client/context';
|
|
14
14
|
/** Subscription tier. */
|
|
15
15
|
export type SubscriptionPlan = 'basic' | 'pro' | 'business';
|
|
@@ -98,7 +98,8 @@ export interface WalletTransactionsPage {
|
|
|
98
98
|
export declare class BillingApi {
|
|
99
99
|
protected readonly ctx: OxyContext;
|
|
100
100
|
constructor(ctx: OxyContext);
|
|
101
|
-
|
|
101
|
+
/** The payment provider's customer portal for one named source the caller pays for. */
|
|
102
|
+
openProductSubscriptionPortal(request: ProductSubscriptionPortalRequest): Promise<ProductSubscriptionPortalResponse>;
|
|
102
103
|
startProductPlanCheckout(request: ProductPlanCheckoutRequest): Promise<PersonalPlanCheckoutResult>;
|
|
103
104
|
productPlans(productId: string): Promise<ProductPlanCatalogue>;
|
|
104
105
|
startPersonalPlanCheckout(request: PersonalPlanCheckoutRequest): Promise<PersonalPlanCheckoutResult>;
|
|
@@ -196,6 +196,22 @@ export declare class OxyServer extends OxyServices {
|
|
|
196
196
|
*/
|
|
197
197
|
readonly middleware: OxyMiddleware;
|
|
198
198
|
constructor(config: OxyServerConfig);
|
|
199
|
+
/**
|
|
200
|
+
* Whether `url` names Oxy's own API — the first-party service this server is
|
|
201
|
+
* configured for (`baseURL`), when that origin is an official Oxy origin.
|
|
202
|
+
*
|
|
203
|
+
* For a URL a caller chose (a customer webhook endpoint, say) that may name
|
|
204
|
+
* Oxy itself: `true` sends it through this server
|
|
205
|
+
* (`createServiceClient({ baseURL: oxy.baseURL, audience: 'oxy-api' }).fetch`),
|
|
206
|
+
* `false` through `safeFetch`. Inside the VPC `api.oxy.so` resolves to a
|
|
207
|
+
* private address, which `safeFetch` refuses by design.
|
|
208
|
+
*
|
|
209
|
+
* The comparison is by exact origin (scheme, host and port): a look-alike
|
|
210
|
+
* (`api.oxy.so.evil.com`), another official host, a credential-carrying URL
|
|
211
|
+
* or anything unparseable is `false`, and so goes on to `safeFetch`'s
|
|
212
|
+
* checks. Never decide first-party by hostname suffix or branding.
|
|
213
|
+
*/
|
|
214
|
+
isFirstPartyServiceUrl(url: string | URL): boolean;
|
|
199
215
|
/** A product SDK transport authenticated only by this process's workload. */
|
|
200
216
|
createServiceClient(options: ServiceClientOptions): ServiceHttpClient;
|
|
201
217
|
/** Live service authority; human sessions and keys have a separate resource contract. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.23.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",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"dependencies": {
|
|
182
182
|
"@noble/ciphers": "^2.4.0",
|
|
183
183
|
"@noble/hashes": "^2.4.0",
|
|
184
|
-
"@oxy.so/contracts": "6.
|
|
184
|
+
"@oxy.so/contracts": "6.9.0",
|
|
185
185
|
"@oxy.so/protocol": "^1.6.1",
|
|
186
186
|
"@oxy.so/telemetry": "^2.1.0",
|
|
187
187
|
"@scure/bip39": "^2.4.0",
|
package/src/api/billing.ts
CHANGED
|
@@ -17,7 +17,8 @@ import type {
|
|
|
17
17
|
ProductPlanCatalogue,
|
|
18
18
|
ProductPlanCheckoutRequest,
|
|
19
19
|
ProductSubscriptionCancellationResult,
|
|
20
|
-
|
|
20
|
+
ProductSubscriptionPortalRequest,
|
|
21
|
+
ProductSubscriptionPortalResponse,
|
|
21
22
|
ProductSubscriptionSummary,
|
|
22
23
|
SubjectProductAccess,
|
|
23
24
|
SubjectProductAccessQuery,
|
|
@@ -32,7 +33,8 @@ import {
|
|
|
32
33
|
productPlanCatalogueCheck,
|
|
33
34
|
productPlanCheckoutRequestCheck,
|
|
34
35
|
productSubscriptionCancellationResultCheck,
|
|
35
|
-
|
|
36
|
+
productSubscriptionPortalRequestCheck,
|
|
37
|
+
productSubscriptionPortalResponseCheck,
|
|
36
38
|
productSubscriptionsResponseCheck,
|
|
37
39
|
subjectProductAccessCheck,
|
|
38
40
|
subjectProductAccessQueryCheck,
|
|
@@ -136,15 +138,16 @@ export interface WalletTransactionsPage {
|
|
|
136
138
|
export class BillingApi {
|
|
137
139
|
constructor(protected readonly ctx: OxyContext) {}
|
|
138
140
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
/** The payment provider's customer portal for one named source the caller pays for. */
|
|
142
|
+
async openProductSubscriptionPortal(
|
|
143
|
+
request: ProductSubscriptionPortalRequest,
|
|
144
|
+
): Promise<ProductSubscriptionPortalResponse> {
|
|
142
145
|
return parseContract(
|
|
143
|
-
|
|
146
|
+
productSubscriptionPortalResponseCheck,
|
|
144
147
|
await this.ctx.request<unknown>(
|
|
145
148
|
'POST',
|
|
146
|
-
'/billing/product-subscriptions/
|
|
147
|
-
parseContract(
|
|
149
|
+
'/billing/product-subscriptions/portal',
|
|
150
|
+
parseContract(productSubscriptionPortalRequestCheck, request),
|
|
148
151
|
{ cache: false },
|
|
149
152
|
),
|
|
150
153
|
);
|
package/src/server/OxyServer.ts
CHANGED
|
@@ -66,6 +66,7 @@ import { OxyServices, type OxyConfig } from '../OxyServices';
|
|
|
66
66
|
import { HttpService } from '../HttpService';
|
|
67
67
|
import { createAuthenticatedFetch } from '../authenticatedFetch';
|
|
68
68
|
import { awaitRequestSignal } from '../utils/requestSignal';
|
|
69
|
+
import { isOfficialWebOrigin } from '../utils/officialOrigins';
|
|
69
70
|
import { createCancelledError, extractErrorStatus } from '../utils/errorUtils';
|
|
70
71
|
import type { HttpMethod, ServiceLane } from '../client/context';
|
|
71
72
|
import type { RequestOptions, ResponsePayload } from '../HttpService';
|
|
@@ -410,6 +411,34 @@ export class OxyServer extends OxyServices {
|
|
|
410
411
|
});
|
|
411
412
|
}
|
|
412
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Whether `url` names Oxy's own API — the first-party service this server is
|
|
416
|
+
* configured for (`baseURL`), when that origin is an official Oxy origin.
|
|
417
|
+
*
|
|
418
|
+
* For a URL a caller chose (a customer webhook endpoint, say) that may name
|
|
419
|
+
* Oxy itself: `true` sends it through this server
|
|
420
|
+
* (`createServiceClient({ baseURL: oxy.baseURL, audience: 'oxy-api' }).fetch`),
|
|
421
|
+
* `false` through `safeFetch`. Inside the VPC `api.oxy.so` resolves to a
|
|
422
|
+
* private address, which `safeFetch` refuses by design.
|
|
423
|
+
*
|
|
424
|
+
* The comparison is by exact origin (scheme, host and port): a look-alike
|
|
425
|
+
* (`api.oxy.so.evil.com`), another official host, a credential-carrying URL
|
|
426
|
+
* or anything unparseable is `false`, and so goes on to `safeFetch`'s
|
|
427
|
+
* checks. Never decide first-party by hostname suffix or branding.
|
|
428
|
+
*/
|
|
429
|
+
isFirstPartyServiceUrl(url: string | URL): boolean {
|
|
430
|
+
let target: URL;
|
|
431
|
+
try {
|
|
432
|
+
target = new URL(String(url));
|
|
433
|
+
} catch {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
if (!['http:', 'https:'].includes(target.protocol) || target.username || target.password)
|
|
437
|
+
return false;
|
|
438
|
+
const own = new URL(this.baseURL).origin;
|
|
439
|
+
return target.origin === own && isOfficialWebOrigin(own);
|
|
440
|
+
}
|
|
441
|
+
|
|
413
442
|
/** A product SDK transport authenticated only by this process's workload. */
|
|
414
443
|
createServiceClient(options: ServiceClientOptions): ServiceHttpClient {
|
|
415
444
|
const origin = new URL(options.baseURL);
|