@xkova/sdk 0.1.0-alpha.6 → 0.1.0-alpha.7
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 +69 -0
- package/dist/generated/types.d.ts +123 -2
- package/dist/member-bff.d.ts +46 -2
- package/dist/member-bff.js +59 -2
- package/dist/member-resources.d.ts +7 -0
- package/dist/resources/account-holders.d.ts +7 -0
- package/dist/resources/branding.d.ts +10 -0
- package/dist/resources/branding.js +2 -0
- package/dist/resources/escrows.d.ts +3 -0
- package/dist/resources/payment-requests.d.ts +4 -0
- package/dist/resources/tenant.d.ts +15 -0
- package/dist/resources/tenant.js +6 -1
- package/dist/resources/tokenization.d.ts +15 -0
- package/dist/resources/wallets.d.ts +2 -0
- package/examples/postgres-checkpoint-store.mjs +116 -0
- package/examples/postgres-checkpoints.sql +9 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -124,6 +124,10 @@ const balance = await xkova.accountHolders.selfBalance({
|
|
|
124
124
|
});
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
`selfBalance` can return a partial answer. A chain not known right now has no
|
|
128
|
+
entries in `balances` and a null `through_block` in `data_freshness`, so check
|
|
129
|
+
that before treating a missing token as zero or adding balances together.
|
|
130
|
+
|
|
127
131
|
## Register a Hosted Auth application
|
|
128
132
|
|
|
129
133
|
Hosted Auth provides an optional XKOVA-hosted sign-in flow. Register an
|
|
@@ -216,6 +220,26 @@ proofs, and manages session refresh and logout. Implement its session,
|
|
|
216
220
|
authorization-transaction, and direct proof-exchange store contracts in your
|
|
217
221
|
own database. It does not include a web framework or database adapter.
|
|
218
222
|
|
|
223
|
+
`runExclusive` is an exclusivity boundary, not an all-or-nothing SQL
|
|
224
|
+
transaction. Every awaited `put`, `replace` and `delete` must commit before
|
|
225
|
+
returning, and remain committed if later callback work throws. Wrapping the
|
|
226
|
+
whole callback in a rollback transaction can erase a staged proof after its
|
|
227
|
+
one-time provider code was consumed. The same rule protects refresh-attempt
|
|
228
|
+
checkpoints. Reconcile an uncertain write acknowledgement using the exact
|
|
229
|
+
record and attempt identity, rather than starting a new operation.
|
|
230
|
+
|
|
231
|
+
The package includes [a PostgreSQL checkpoint-store example](examples/postgres-checkpoint-store.mjs)
|
|
232
|
+
and its [application-owned table definition](examples/postgres-checkpoints.sql).
|
|
233
|
+
Install `pg` in your backend and apply the table definition through your own
|
|
234
|
+
migration process. Instantiate separate stores with namespaces such as
|
|
235
|
+
`authorization`, `sessions` and `exchange`, one shared pool and an existing
|
|
236
|
+
32-byte application encryption key. The example uses session advisory locks
|
|
237
|
+
across replicas, independent autocommit checkpoints, authenticated encryption
|
|
238
|
+
bound to namespace/reference, exact replacement without upsert, and connection
|
|
239
|
+
discard when lock release is uncertain. Do not use a transaction-pooling proxy
|
|
240
|
+
for session locks. Own key rotation, pool deadlines and expired-record cleanup
|
|
241
|
+
in your application; do not log records or encryption material.
|
|
242
|
+
|
|
219
243
|
Authorization, Account Center, and action transactions expire within ten
|
|
220
244
|
minutes of starting the request, or at the server's expiry if sooner. Late,
|
|
221
245
|
expired, or excessively long-lived responses are rejected. Keep your BFF's
|
|
@@ -246,6 +270,51 @@ To exchange a proof without a browser callback, call `establishSession` with an
|
|
|
246
270
|
`exchangeId`. Persist that ID before the first attempt and reuse it on retries.
|
|
247
271
|
The store serializes attempts and preserves the exchange and session references.
|
|
248
272
|
|
|
273
|
+
### Enroll an external identity-provider member
|
|
274
|
+
|
|
275
|
+
For a registered OIDC connection, the confidential backend can call
|
|
276
|
+
`memberAuth.enrollExternalMember({ externalSub, applicationConfigVersion,
|
|
277
|
+
idempotencyKey })`. Use the exact opaque subject from a fully verified provider
|
|
278
|
+
proof, the final read-back application configuration version, and one persisted
|
|
279
|
+
operation key. Never link by email. The API derives issuer namespace, tenant,
|
|
280
|
+
workspace and application from the registered connection and its bound
|
|
281
|
+
`member_auth` API principal. The header selector and body selector must match.
|
|
282
|
+
An existing issuer/subject membership is returned without replacing its identity,
|
|
283
|
+
status or wallet bindings. A held or closed member remains held or closed.
|
|
284
|
+
|
|
285
|
+
Enrollment creates or finds a membership, not an authenticated session or wallet.
|
|
286
|
+
The normal nonce-bound provider-proof exchange remains required. For callback
|
|
287
|
+
flows use `completeAuthorizationCallback`'s optional `ensureMemberEnrollment`
|
|
288
|
+
hook. It runs after the one-time provider proof is durably staged, with a stable
|
|
289
|
+
`idempotencyKey` preserved across retries. Verify the staged proof's signature,
|
|
290
|
+
issuer, audience, algorithm, expiry and nonce, then call `enrollExternalMember`
|
|
291
|
+
with its exact `sub`. Return that result from the hook. The helper checks that
|
|
292
|
+
the resulting authenticated session resolves to the enrolled account holder.
|
|
293
|
+
Do not put a fallible enrollment call inside `exchangeProviderCode`, before
|
|
294
|
+
the provider result has been staged. A version conflict requires reviewing the
|
|
295
|
+
changed registration and starting a new authorization flow, not blind retries.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const completion = await memberAuth.completeAuthorizationCallback({
|
|
299
|
+
...callbackInput,
|
|
300
|
+
ensureMemberEnrollment: async ({ proofToken, idempotencyKey }) => {
|
|
301
|
+
// This application function performs full JOSE and exact nonce validation.
|
|
302
|
+
const verified = await verifyRegisteredProviderProof(proofToken);
|
|
303
|
+
return memberAuth.enrollExternalMember({
|
|
304
|
+
externalSub: verified.sub,
|
|
305
|
+
applicationConfigVersion: registration.config_version,
|
|
306
|
+
idempotencyKey,
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The verifier and `registration` are application prerequisites in this example,
|
|
313
|
+
not SDK-provided provider validation or discovery functions. The public endpoint
|
|
314
|
+
is `POST /v1/member-auth/enrollments`; it accepts no caller-selected account
|
|
315
|
+
holder, tenant, issuer namespace, email or permissions. Enrollment alone never
|
|
316
|
+
qualifies fresh action assurance, signing authority or settlement.
|
|
317
|
+
|
|
249
318
|
### Account Center and action callbacks
|
|
250
319
|
|
|
251
320
|
Account Center accepts a same-origin `applicationReturnUri` and preserves it
|
|
@@ -44,7 +44,7 @@ export interface paths {
|
|
|
44
44
|
};
|
|
45
45
|
get?: never;
|
|
46
46
|
put?: never;
|
|
47
|
-
/** Confirm
|
|
47
|
+
/** Confirm this tenant's brand logo upload */
|
|
48
48
|
post: operations["confirmBrandingLogo"];
|
|
49
49
|
/** Remove the brand logo */
|
|
50
50
|
delete: operations["deleteBrandingLogo"];
|
|
@@ -3029,6 +3029,23 @@ export interface paths {
|
|
|
3029
3029
|
patch?: never;
|
|
3030
3030
|
trace?: never;
|
|
3031
3031
|
};
|
|
3032
|
+
"/member-auth/enrollments": {
|
|
3033
|
+
parameters: {
|
|
3034
|
+
query?: never;
|
|
3035
|
+
header?: never;
|
|
3036
|
+
path?: never;
|
|
3037
|
+
cookie?: never;
|
|
3038
|
+
};
|
|
3039
|
+
get?: never;
|
|
3040
|
+
put?: never;
|
|
3041
|
+
/** Enroll an external member for the registered identity provider */
|
|
3042
|
+
post: operations["enrollExternalMember"];
|
|
3043
|
+
delete?: never;
|
|
3044
|
+
options?: never;
|
|
3045
|
+
head?: never;
|
|
3046
|
+
patch?: never;
|
|
3047
|
+
trace?: never;
|
|
3048
|
+
};
|
|
3032
3049
|
"/tenant/member-auth/applications": {
|
|
3033
3050
|
parameters: {
|
|
3034
3051
|
query?: never;
|
|
@@ -3091,7 +3108,7 @@ export interface paths {
|
|
|
3091
3108
|
cookie?: never;
|
|
3092
3109
|
};
|
|
3093
3110
|
get?: never;
|
|
3094
|
-
/** Register or update the calling backend's
|
|
3111
|
+
/** Register or update the calling backend's member identity provider */
|
|
3095
3112
|
put: operations["configureMemberAuthOidcApplication"];
|
|
3096
3113
|
post?: never;
|
|
3097
3114
|
delete?: never;
|
|
@@ -3702,6 +3719,8 @@ export type webhooks = Record<string, never>;
|
|
|
3702
3719
|
export interface components {
|
|
3703
3720
|
schemas: {
|
|
3704
3721
|
TenantBranding: {
|
|
3722
|
+
logo_upload_limits: components["schemas"]["LogoUploadLimits"];
|
|
3723
|
+
has_logo_override: boolean;
|
|
3705
3724
|
resolved: {
|
|
3706
3725
|
brand_name: string;
|
|
3707
3726
|
legal_name: string;
|
|
@@ -4038,8 +4057,10 @@ export interface components {
|
|
|
4038
4057
|
};
|
|
4039
4058
|
LedgerList: components["schemas"]["CursorPage"] & {
|
|
4040
4059
|
data?: components["schemas"]["LedgerEntry"][];
|
|
4060
|
+
data_freshness: components["schemas"]["DataFreshness"][];
|
|
4041
4061
|
};
|
|
4042
4062
|
WalletBalanceResult: {
|
|
4063
|
+
data_freshness: components["schemas"]["DataFreshness"][];
|
|
4043
4064
|
chain_id: number;
|
|
4044
4065
|
wallet: string;
|
|
4045
4066
|
token: string;
|
|
@@ -4308,6 +4329,7 @@ export interface components {
|
|
|
4308
4329
|
has_more: boolean;
|
|
4309
4330
|
};
|
|
4310
4331
|
EscrowCreate: {
|
|
4332
|
+
notify_recipient?: boolean;
|
|
4311
4333
|
recipient_ref: string;
|
|
4312
4334
|
/** Format: uuid */
|
|
4313
4335
|
token_deployment_id: string;
|
|
@@ -4318,6 +4340,8 @@ export interface components {
|
|
|
4318
4340
|
metadata?: Record<string, never>;
|
|
4319
4341
|
};
|
|
4320
4342
|
EscrowCreateResponse: {
|
|
4343
|
+
/** @enum {string} */
|
|
4344
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
4321
4345
|
escrow: components["schemas"]["Escrow"];
|
|
4322
4346
|
claim_token: string;
|
|
4323
4347
|
/** Format: uri */
|
|
@@ -4327,6 +4351,8 @@ export interface components {
|
|
|
4327
4351
|
signing_expires_at?: components["schemas"]["TimestampUtc"] | null;
|
|
4328
4352
|
};
|
|
4329
4353
|
Escrow: {
|
|
4354
|
+
/** @enum {string} */
|
|
4355
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
4330
4356
|
id: components["schemas"]["EscrowId"];
|
|
4331
4357
|
chain: components["schemas"]["ChainDescriptor"];
|
|
4332
4358
|
fingerprint: string;
|
|
@@ -4342,6 +4368,7 @@ export interface components {
|
|
|
4342
4368
|
on_chain_payment_id?: string | null;
|
|
4343
4369
|
create_tx_hash?: string | null;
|
|
4344
4370
|
release_tx_hash?: string | null;
|
|
4371
|
+
data_freshness?: components["schemas"]["DataFreshness"][];
|
|
4345
4372
|
fee_accruals?: components["schemas"]["EscrowFeeAccrual"][];
|
|
4346
4373
|
net_amount?: components["schemas"]["Money"] | null;
|
|
4347
4374
|
claimed_at?: components["schemas"]["TimestampUtc"] | null;
|
|
@@ -4368,6 +4395,10 @@ export interface components {
|
|
|
4368
4395
|
has_more: boolean;
|
|
4369
4396
|
};
|
|
4370
4397
|
PaymentRequestCreate: {
|
|
4398
|
+
notify_recipient?: boolean;
|
|
4399
|
+
require_member_payer?: boolean;
|
|
4400
|
+
/** Format: uri */
|
|
4401
|
+
notification_return_uri?: string;
|
|
4371
4402
|
payer: {
|
|
4372
4403
|
account_holder_id?: components["schemas"]["AccountHolderId"] | null;
|
|
4373
4404
|
/** Format: email */
|
|
@@ -4380,6 +4411,8 @@ export interface components {
|
|
|
4380
4411
|
memo?: string | null;
|
|
4381
4412
|
};
|
|
4382
4413
|
PaymentRequest: {
|
|
4414
|
+
/** @enum {string} */
|
|
4415
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
4383
4416
|
id?: components["schemas"]["PaymentRequestId"];
|
|
4384
4417
|
requester_id?: components["schemas"]["AccountHolderId"];
|
|
4385
4418
|
payer_id?: components["schemas"]["AccountHolderId"] | null;
|
|
@@ -4466,6 +4499,9 @@ export interface components {
|
|
|
4466
4499
|
amount: string;
|
|
4467
4500
|
/** Format: uuid */
|
|
4468
4501
|
token_deployment_id: string;
|
|
4502
|
+
token_address: string;
|
|
4503
|
+
token_symbol: string;
|
|
4504
|
+
chain: components["schemas"]["ChainDescriptor"];
|
|
4469
4505
|
/** @enum {string} */
|
|
4470
4506
|
status: "pending" | "submitted" | "settled" | "requires_review";
|
|
4471
4507
|
transaction_hash: string | null;
|
|
@@ -4497,6 +4533,7 @@ export interface components {
|
|
|
4497
4533
|
};
|
|
4498
4534
|
AccountHolderSelfBalance: {
|
|
4499
4535
|
balances: components["schemas"]["AccountHolderSelfBalanceEntry"][];
|
|
4536
|
+
data_freshness: components["schemas"]["DataFreshness"][];
|
|
4500
4537
|
};
|
|
4501
4538
|
MemberPaymentRecovery: {
|
|
4502
4539
|
payment: components["schemas"]["Payment"];
|
|
@@ -5014,6 +5051,9 @@ export interface components {
|
|
|
5014
5051
|
raw_contract: string | null;
|
|
5015
5052
|
raw_function: string | null;
|
|
5016
5053
|
raw_args: string[] | null;
|
|
5054
|
+
failure_reason_code: string | null;
|
|
5055
|
+
failure_reason_detail: string | null;
|
|
5056
|
+
failure_retryable: boolean;
|
|
5017
5057
|
/** Format: date-time */
|
|
5018
5058
|
created_at: string;
|
|
5019
5059
|
/** Format: date-time */
|
|
@@ -5230,6 +5270,20 @@ export interface components {
|
|
|
5230
5270
|
expires_at: components["schemas"]["TimestampUtc"];
|
|
5231
5271
|
action_readiness: components["schemas"]["MemberActionReadiness"];
|
|
5232
5272
|
};
|
|
5273
|
+
ExternalMemberEnrollment: {
|
|
5274
|
+
publishable_key: string;
|
|
5275
|
+
application_config_version: number;
|
|
5276
|
+
external_sub: string;
|
|
5277
|
+
};
|
|
5278
|
+
ExternalMemberEnrollmentResult: {
|
|
5279
|
+
account_holder_id: components["schemas"]["AccountHolderId"];
|
|
5280
|
+
/** Format: uuid */
|
|
5281
|
+
application_id: string;
|
|
5282
|
+
publishable_key: string;
|
|
5283
|
+
application_config_version: number;
|
|
5284
|
+
provider_namespace: string;
|
|
5285
|
+
created: boolean;
|
|
5286
|
+
};
|
|
5233
5287
|
MemberAuthApplicationSummary: {
|
|
5234
5288
|
/** Format: uuid */
|
|
5235
5289
|
id: string;
|
|
@@ -5242,6 +5296,15 @@ export interface components {
|
|
|
5242
5296
|
name: string;
|
|
5243
5297
|
/** @enum {string} */
|
|
5244
5298
|
provider_kind: "xkova_hosted" | "oidc" | "signed_launch";
|
|
5299
|
+
provider_namespace: string;
|
|
5300
|
+
/** Format: uri */
|
|
5301
|
+
issuer: string | null;
|
|
5302
|
+
audience: string | null;
|
|
5303
|
+
/** Format: uri */
|
|
5304
|
+
jwks_uri: string | null;
|
|
5305
|
+
allowed_jwt_algorithms: ("RS256" | "PS256" | "ES256" | "EdDSA")[];
|
|
5306
|
+
/** @enum {string} */
|
|
5307
|
+
proof_binding_mode: "authorization_nonce" | "server_assertion_jti";
|
|
5245
5308
|
enrollment_mode: components["schemas"]["MemberAuthEnrollmentMode"];
|
|
5246
5309
|
is_default: boolean;
|
|
5247
5310
|
/** @enum {string} */
|
|
@@ -5460,6 +5523,17 @@ export interface components {
|
|
|
5460
5523
|
};
|
|
5461
5524
|
/** @example cact_1A2b3C4d5E6f7G8h9I0jK1 */
|
|
5462
5525
|
CorporateActionId: string;
|
|
5526
|
+
LogoUploadLimits: {
|
|
5527
|
+
/** @example 5242880 */
|
|
5528
|
+
max_bytes: number;
|
|
5529
|
+
/** @example 4096 */
|
|
5530
|
+
max_width: number;
|
|
5531
|
+
/** @example 4096 */
|
|
5532
|
+
max_height: number;
|
|
5533
|
+
/** @example 32000000 */
|
|
5534
|
+
max_total_frame_pixels: number;
|
|
5535
|
+
content_types: ("image/png" | "image/jpeg" | "image/webp" | "image/gif")[];
|
|
5536
|
+
};
|
|
5463
5537
|
TenantBrandingTheme: {
|
|
5464
5538
|
brand_name: string | null;
|
|
5465
5539
|
legal_name: string | null;
|
|
@@ -5633,6 +5707,10 @@ export interface components {
|
|
|
5633
5707
|
user_op_hash?: string | null;
|
|
5634
5708
|
counterparty?: string | null;
|
|
5635
5709
|
};
|
|
5710
|
+
DataFreshness: {
|
|
5711
|
+
chain_id: number;
|
|
5712
|
+
through_block: string | null;
|
|
5713
|
+
};
|
|
5636
5714
|
/** @example cnt_1A2b3C4d5E6f7G8h9I0jK1 */
|
|
5637
5715
|
ContactId: string;
|
|
5638
5716
|
/** @example fnd_1A2b3C4d5E6f7G8h9I0jK1 */
|
|
@@ -7101,6 +7179,7 @@ export interface operations {
|
|
|
7101
7179
|
401: components["responses"]["Unauthorized"];
|
|
7102
7180
|
403: components["responses"]["Forbidden"];
|
|
7103
7181
|
404: components["responses"]["NotFound"];
|
|
7182
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
7104
7183
|
};
|
|
7105
7184
|
};
|
|
7106
7185
|
getWalletBalance: {
|
|
@@ -7128,6 +7207,7 @@ export interface operations {
|
|
|
7128
7207
|
401: components["responses"]["Unauthorized"];
|
|
7129
7208
|
403: components["responses"]["Forbidden"];
|
|
7130
7209
|
404: components["responses"]["NotFound"];
|
|
7210
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
7131
7211
|
};
|
|
7132
7212
|
};
|
|
7133
7213
|
listContacts: {
|
|
@@ -8584,6 +8664,7 @@ export interface operations {
|
|
|
8584
8664
|
};
|
|
8585
8665
|
401: components["responses"]["Unauthorized"];
|
|
8586
8666
|
403: components["responses"]["Forbidden"];
|
|
8667
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
8587
8668
|
};
|
|
8588
8669
|
};
|
|
8589
8670
|
getMemberPaymentRecovery: {
|
|
@@ -9856,6 +9937,7 @@ export interface operations {
|
|
|
9856
9937
|
"application/json": components["schemas"]["ErrorResponse"];
|
|
9857
9938
|
};
|
|
9858
9939
|
};
|
|
9940
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
9859
9941
|
};
|
|
9860
9942
|
};
|
|
9861
9943
|
getRwaDocument: {
|
|
@@ -10078,6 +10160,7 @@ export interface operations {
|
|
|
10078
10160
|
401: components["responses"]["Unauthorized"];
|
|
10079
10161
|
403: components["responses"]["Forbidden"];
|
|
10080
10162
|
404: components["responses"]["NotFound"];
|
|
10163
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
10081
10164
|
};
|
|
10082
10165
|
};
|
|
10083
10166
|
listRwaTokenHolders: {
|
|
@@ -10105,6 +10188,7 @@ export interface operations {
|
|
|
10105
10188
|
401: components["responses"]["Unauthorized"];
|
|
10106
10189
|
403: components["responses"]["Forbidden"];
|
|
10107
10190
|
404: components["responses"]["NotFound"];
|
|
10191
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
10108
10192
|
};
|
|
10109
10193
|
};
|
|
10110
10194
|
listRwaTokenInstances: {
|
|
@@ -10133,6 +10217,7 @@ export interface operations {
|
|
|
10133
10217
|
403: components["responses"]["Forbidden"];
|
|
10134
10218
|
404: components["responses"]["NotFound"];
|
|
10135
10219
|
422: components["responses"]["UnprocessableEntity"];
|
|
10220
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
10136
10221
|
};
|
|
10137
10222
|
};
|
|
10138
10223
|
createRwaTokenDistribution: {
|
|
@@ -10164,6 +10249,7 @@ export interface operations {
|
|
|
10164
10249
|
403: components["responses"]["Forbidden"];
|
|
10165
10250
|
404: components["responses"]["NotFound"];
|
|
10166
10251
|
422: components["responses"]["UnprocessableEntity"];
|
|
10252
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
10167
10253
|
};
|
|
10168
10254
|
};
|
|
10169
10255
|
getRwaTokenDistribution: {
|
|
@@ -12366,6 +12452,37 @@ export interface operations {
|
|
|
12366
12452
|
401: components["responses"]["Unauthorized"];
|
|
12367
12453
|
};
|
|
12368
12454
|
};
|
|
12455
|
+
enrollExternalMember: {
|
|
12456
|
+
parameters: {
|
|
12457
|
+
query?: never;
|
|
12458
|
+
header: {
|
|
12459
|
+
"Idempotency-Key": components["parameters"]["idempotencyKey"];
|
|
12460
|
+
"X-XKOVA-Publishable-Key": string;
|
|
12461
|
+
};
|
|
12462
|
+
path?: never;
|
|
12463
|
+
cookie?: never;
|
|
12464
|
+
};
|
|
12465
|
+
requestBody: {
|
|
12466
|
+
content: {
|
|
12467
|
+
"application/json": components["schemas"]["ExternalMemberEnrollment"];
|
|
12468
|
+
};
|
|
12469
|
+
};
|
|
12470
|
+
responses: {
|
|
12471
|
+
200: {
|
|
12472
|
+
headers: {
|
|
12473
|
+
[name: string]: unknown;
|
|
12474
|
+
};
|
|
12475
|
+
content: {
|
|
12476
|
+
"application/json": components["schemas"]["ExternalMemberEnrollmentResult"];
|
|
12477
|
+
};
|
|
12478
|
+
};
|
|
12479
|
+
401: components["responses"]["Unauthorized"];
|
|
12480
|
+
403: components["responses"]["Forbidden"];
|
|
12481
|
+
409: components["responses"]["Conflict"];
|
|
12482
|
+
422: components["responses"]["UnprocessableEntity"];
|
|
12483
|
+
429: components["responses"]["TooManyRequests"];
|
|
12484
|
+
};
|
|
12485
|
+
};
|
|
12369
12486
|
listMemberAuthApplications: {
|
|
12370
12487
|
parameters: {
|
|
12371
12488
|
query?: {
|
|
@@ -13099,6 +13216,7 @@ export interface operations {
|
|
|
13099
13216
|
"application/json": components["schemas"]["ErrorResponse"];
|
|
13100
13217
|
};
|
|
13101
13218
|
};
|
|
13219
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
13102
13220
|
};
|
|
13103
13221
|
};
|
|
13104
13222
|
listRwaAttesters: {
|
|
@@ -13253,6 +13371,7 @@ export interface operations {
|
|
|
13253
13371
|
"application/json": components["schemas"]["ErrorResponse"];
|
|
13254
13372
|
};
|
|
13255
13373
|
};
|
|
13374
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
13256
13375
|
};
|
|
13257
13376
|
};
|
|
13258
13377
|
describeRwaTokenFields: {
|
|
@@ -13330,6 +13449,7 @@ export interface operations {
|
|
|
13330
13449
|
"application/json": components["schemas"]["ErrorResponse"];
|
|
13331
13450
|
};
|
|
13332
13451
|
};
|
|
13452
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
13333
13453
|
};
|
|
13334
13454
|
};
|
|
13335
13455
|
getRwaSchemaBinding: {
|
|
@@ -13625,6 +13745,7 @@ export interface operations {
|
|
|
13625
13745
|
404: components["responses"]["NotFound"];
|
|
13626
13746
|
409: components["responses"]["Conflict"];
|
|
13627
13747
|
422: components["responses"]["UnprocessableEntity"];
|
|
13748
|
+
503: components["responses"]["ServiceUnavailable"];
|
|
13628
13749
|
};
|
|
13629
13750
|
};
|
|
13630
13751
|
getRwaCorporateAction: {
|
package/dist/member-bff.d.ts
CHANGED
|
@@ -34,6 +34,14 @@ export interface XKOVAMemberSessionMaterial extends XKOVAMemberContext {
|
|
|
34
34
|
/** Confidential refresh credential, BFF storage only. */
|
|
35
35
|
readonly refreshToken: string;
|
|
36
36
|
}
|
|
37
|
+
export interface XKOVAExternalMemberEnrollment {
|
|
38
|
+
readonly accountHolderId: string;
|
|
39
|
+
readonly applicationId: string;
|
|
40
|
+
readonly publishableKey: string;
|
|
41
|
+
readonly applicationConfigVersion: number;
|
|
42
|
+
readonly providerNamespace: string;
|
|
43
|
+
readonly created: boolean;
|
|
44
|
+
}
|
|
37
45
|
export interface XKOVABrowserMemberProjection {
|
|
38
46
|
readonly signedIn: true;
|
|
39
47
|
readonly accountHolderId: string;
|
|
@@ -49,8 +57,12 @@ export interface StoredXKOVAMemberSession<TApplicationData = unknown> extends XK
|
|
|
49
57
|
}
|
|
50
58
|
/**
|
|
51
59
|
* `runExclusive` must serialize refresh and logout for the same reference
|
|
52
|
-
* across all BFF instances. Use a
|
|
53
|
-
* a process-local mutex cannot coordinate multiple instances.
|
|
60
|
+
* across all BFF instances. Use a distributed or session-level database lock;
|
|
61
|
+
* a process-local mutex cannot coordinate multiple instances. This lock is not
|
|
62
|
+
* a surrounding rollback transaction: every awaited put/replace/delete must be
|
|
63
|
+
* durable before resolving and must survive a later error from work(). In
|
|
64
|
+
* particular, a pending refresh-attempt checkpoint cannot roll back after the
|
|
65
|
+
* provider/API has acted. replace is exact-existing-only, never an upsert.
|
|
54
66
|
*/
|
|
55
67
|
export interface XKOVAMemberSessionStore<TApplicationData = unknown> {
|
|
56
68
|
put(reference: string, session: StoredXKOVAMemberSession<TApplicationData>): Promise<void>;
|
|
@@ -169,6 +181,11 @@ export type XKOVAMemberAuthorizationTransaction<TApplicationData = unknown> = {
|
|
|
169
181
|
* every BFF instance. `replace` and `delete` must target that exact state. A
|
|
170
182
|
* production store must encrypt the transaction because it can briefly hold a
|
|
171
183
|
* confidential provider proof while XKOVA session exchange is retried.
|
|
184
|
+
* Every awaited write must commit independently of a later work() exception.
|
|
185
|
+
* Never wrap the entire callback in a rollback transaction: that can erase the
|
|
186
|
+
* staged proof after its one-time provider code has already been consumed.
|
|
187
|
+
* On uncertain write acknowledgement, reconcile the same exact record; never
|
|
188
|
+
* re-exchange the provider code or invent a replacement operation identity.
|
|
172
189
|
*/
|
|
173
190
|
export interface XKOVAMemberAuthorizationTransactionStore<TApplicationData = unknown> {
|
|
174
191
|
put(stateHash: string, transaction: XKOVAMemberAuthorizationTransaction<TApplicationData>): Promise<void>;
|
|
@@ -198,6 +215,10 @@ export type XKOVAMemberProofExchangeTransaction<TApplicationData = unknown> = {
|
|
|
198
215
|
* Durable transaction store for a confidential proof exchange that does not
|
|
199
216
|
* use the browser callback helper. Pending records contain a provider proof
|
|
200
217
|
* and customer application data, so production adapters must encrypt them.
|
|
218
|
+
* Serialize the exact reference across replicas without a surrounding rollback
|
|
219
|
+
* transaction. Awaited put/replace/delete checkpoints must remain committed if
|
|
220
|
+
* the later exchange fails. Reconcile an uncertain acknowledgement against the
|
|
221
|
+
* same reference and attempt identity before retrying.
|
|
201
222
|
*/
|
|
202
223
|
export interface XKOVAMemberProofExchangeTransactionStore<TApplicationData = unknown> {
|
|
203
224
|
put(exchangeHash: string, transaction: XKOVAMemberProofExchangeTransaction<TApplicationData>): Promise<void>;
|
|
@@ -321,6 +342,18 @@ export declare class XKOVAMemberBffClient {
|
|
|
321
342
|
exchangeProof(proofToken: string, options: {
|
|
322
343
|
readonly attemptId: string;
|
|
323
344
|
}): Promise<XKOVAMemberSessionMaterial>;
|
|
345
|
+
/**
|
|
346
|
+
* BFF-only membership provisioning after validating the provider identity or
|
|
347
|
+
* resolving it from a trusted directory. This creates no login session or
|
|
348
|
+
* wallet and does not substitute for genuine provider-proof verification.
|
|
349
|
+
* Save one idempotencyKey before the first attempt and retain the same subject,
|
|
350
|
+
* selector and observed configuration version during response-loss recovery.
|
|
351
|
+
*/
|
|
352
|
+
enrollExternalMember(input: {
|
|
353
|
+
readonly externalSub: string;
|
|
354
|
+
readonly applicationConfigVersion: number;
|
|
355
|
+
readonly idempotencyKey: string;
|
|
356
|
+
}): Promise<XKOVAExternalMemberEnrollment>;
|
|
324
357
|
refresh(refreshToken: string, options: {
|
|
325
358
|
readonly attemptId: string;
|
|
326
359
|
}): Promise<XKOVAMemberSessionMaterial>;
|
|
@@ -426,6 +459,17 @@ export declare class XKOVAMemberBffClient {
|
|
|
426
459
|
readonly transactionStore: XKOVAMemberAuthorizationTransactionStore<TApplicationData>;
|
|
427
460
|
readonly sessionStore: XKOVAMemberSessionStore<TApplicationData>;
|
|
428
461
|
readonly exchangeProviderCode: (input: XKOVAMemberProviderCodeExchangeInput) => XKOVAMemberProviderCodeExchangeResult | Promise<XKOVAMemberProviderCodeExchangeResult>;
|
|
462
|
+
/**
|
|
463
|
+
* Optional OIDC enrollment, invoked only after the validated provider proof
|
|
464
|
+
* is durably staged. Validate/extract its exact subject on your backend and
|
|
465
|
+
* call enrollExternalMember with this stable idempotency key. Do not perform
|
|
466
|
+
* enrollment RPC inside exchangeProviderCode: failure there can otherwise
|
|
467
|
+
* require reusing an already-consumed provider authorization code.
|
|
468
|
+
*/
|
|
469
|
+
readonly ensureMemberEnrollment?: (input: {
|
|
470
|
+
readonly proofToken: string;
|
|
471
|
+
readonly idempotencyKey: string;
|
|
472
|
+
}) => Promise<XKOVAExternalMemberEnrollment>;
|
|
429
473
|
readonly applicationData: TApplicationData;
|
|
430
474
|
readonly applicationSessionSeconds?: number;
|
|
431
475
|
}): Promise<EstablishedXKOVAMemberSession<TApplicationData>>;
|
package/dist/member-bff.js
CHANGED
|
@@ -79,6 +79,48 @@ export class XKOVAMemberBffClient {
|
|
|
79
79
|
proof_token: proofToken,
|
|
80
80
|
}), 'proof exchange');
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* BFF-only membership provisioning after validating the provider identity or
|
|
84
|
+
* resolving it from a trusted directory. This creates no login session or
|
|
85
|
+
* wallet and does not substitute for genuine provider-proof verification.
|
|
86
|
+
* Save one idempotencyKey before the first attempt and retain the same subject,
|
|
87
|
+
* selector and observed configuration version during response-loss recovery.
|
|
88
|
+
*/
|
|
89
|
+
async enrollExternalMember(input) {
|
|
90
|
+
if (typeof input.externalSub !== 'string' ||
|
|
91
|
+
input.externalSub.length === 0 ||
|
|
92
|
+
input.externalSub.length > 512 ||
|
|
93
|
+
/\u0000/u.test(input.externalSub) ||
|
|
94
|
+
!/\S/u.test(input.externalSub))
|
|
95
|
+
throw new TypeError('externalSub must be an exact nonempty provider subject of at most 512 characters');
|
|
96
|
+
assertPositiveInteger(input.applicationConfigVersion, 'applicationConfigVersion');
|
|
97
|
+
if (!/^[A-Za-z0-9._:-]{1,255}$/u.test(input.idempotencyKey))
|
|
98
|
+
throw new TypeError('idempotencyKey must be a retained opaque operation key of at most 255 characters');
|
|
99
|
+
const value = await this.recoverablePostJson('/member-auth/enrollments', {
|
|
100
|
+
publishable_key: this.options.publishableKey,
|
|
101
|
+
application_config_version: input.applicationConfigVersion,
|
|
102
|
+
external_sub: input.externalSub,
|
|
103
|
+
}, undefined, input.idempotencyKey);
|
|
104
|
+
const result = requireObject(value, 'external member enrollment');
|
|
105
|
+
const applicationId = requireString(result, 'application_id', 'external member enrollment');
|
|
106
|
+
assertUuid(applicationId, 'enrollment application id');
|
|
107
|
+
const publishableKey = requireString(result, 'publishable_key', 'external member enrollment');
|
|
108
|
+
const applicationConfigVersion = requirePositiveInteger(result, 'application_config_version', 'external member enrollment');
|
|
109
|
+
const providerNamespace = requireString(result, 'provider_namespace', 'external member enrollment');
|
|
110
|
+
if (publishableKey !== this.options.publishableKey ||
|
|
111
|
+
applicationConfigVersion !== input.applicationConfigVersion ||
|
|
112
|
+
!providerNamespace.startsWith('oidc:') ||
|
|
113
|
+
typeof result.created !== 'boolean')
|
|
114
|
+
throw new Error('XKOVA external member enrollment returned a different application or configuration');
|
|
115
|
+
return {
|
|
116
|
+
accountHolderId: requireString(result, 'account_holder_id', 'external member enrollment'),
|
|
117
|
+
applicationId,
|
|
118
|
+
publishableKey,
|
|
119
|
+
applicationConfigVersion,
|
|
120
|
+
providerNamespace,
|
|
121
|
+
created: result.created,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
82
124
|
async refresh(refreshToken, options) {
|
|
83
125
|
if (refreshToken.length < 20) {
|
|
84
126
|
throw new TypeError('member refresh token is malformed');
|
|
@@ -759,6 +801,10 @@ export class XKOVAMemberBffClient {
|
|
|
759
801
|
if (transaction.completionMode !== 'provider_proof') {
|
|
760
802
|
throw new Error('member authorization transaction is not a provider-proof flow');
|
|
761
803
|
}
|
|
804
|
+
if (input.ensureMemberEnrollment &&
|
|
805
|
+
transaction.status === 'pending' &&
|
|
806
|
+
transaction.proofKind !== 'oidc')
|
|
807
|
+
throw new Error('external member enrollment is available only for an OIDC proof flow');
|
|
762
808
|
if (!sameCallback(callback, new URL(transaction.redirectUri))) {
|
|
763
809
|
throw new Error('member authorization callback does not match its redirect URI');
|
|
764
810
|
}
|
|
@@ -821,6 +867,14 @@ export class XKOVAMemberBffClient {
|
|
|
821
867
|
},
|
|
822
868
|
});
|
|
823
869
|
}
|
|
870
|
+
const enrollment = input.ensureMemberEnrollment
|
|
871
|
+
? await input.ensureMemberEnrollment({
|
|
872
|
+
proofToken,
|
|
873
|
+
idempotencyKey: `enroll:${transaction.exchangeAttemptId}`,
|
|
874
|
+
})
|
|
875
|
+
: undefined;
|
|
876
|
+
if (enrollment && enrollment.publishableKey !== this.options.publishableKey)
|
|
877
|
+
throw new Error('member enrollment does not belong to this application');
|
|
824
878
|
const established = await this.persistSessionWithAttempt({
|
|
825
879
|
proofToken,
|
|
826
880
|
attemptId: transaction.exchangeAttemptId,
|
|
@@ -828,6 +882,7 @@ export class XKOVAMemberBffClient {
|
|
|
828
882
|
sessionStore: input.sessionStore,
|
|
829
883
|
applicationData,
|
|
830
884
|
applicationExpiresAt,
|
|
885
|
+
...(enrollment !== undefined && { expectedAccountHolderId: enrollment.accountHolderId }),
|
|
831
886
|
});
|
|
832
887
|
await input.transactionStore.replace(stateHash, {
|
|
833
888
|
status: 'completed',
|
|
@@ -1014,6 +1069,9 @@ export class XKOVAMemberBffClient {
|
|
|
1014
1069
|
const material = await this.exchangeProof(input.proofToken, {
|
|
1015
1070
|
attemptId: input.attemptId,
|
|
1016
1071
|
});
|
|
1072
|
+
if (input.expectedAccountHolderId !== undefined &&
|
|
1073
|
+
material.accountHolderId !== input.expectedAccountHolderId)
|
|
1074
|
+
throw new Error('XKOVA proof exchange returned a different member from the enrolled identity');
|
|
1017
1075
|
return this.persistSessionMaterial({
|
|
1018
1076
|
material,
|
|
1019
1077
|
reference: input.reference,
|
|
@@ -1064,8 +1122,7 @@ export class XKOVAMemberBffClient {
|
|
|
1064
1122
|
...(idempotencyKey !== undefined && { idempotencyKey }),
|
|
1065
1123
|
});
|
|
1066
1124
|
}
|
|
1067
|
-
async recoverablePostJson(path, body, sessionId) {
|
|
1068
|
-
const idempotencyKey = generateIdempotencyKey();
|
|
1125
|
+
async recoverablePostJson(path, body, sessionId, idempotencyKey = generateIdempotencyKey()) {
|
|
1069
1126
|
try {
|
|
1070
1127
|
return await this.postJson(path, body, sessionId, idempotencyKey);
|
|
1071
1128
|
}
|
|
@@ -52,6 +52,9 @@ export declare class XKOVAMemberResources {
|
|
|
52
52
|
credit: {
|
|
53
53
|
amount: string;
|
|
54
54
|
token_deployment_id: string;
|
|
55
|
+
token_address: string;
|
|
56
|
+
token_symbol: string;
|
|
57
|
+
chain: import("./generated/types.js").components["schemas"]["ChainDescriptor"];
|
|
55
58
|
status: "pending" | "submitted" | "settled" | "requires_review";
|
|
56
59
|
transaction_hash: string | null;
|
|
57
60
|
} | null;
|
|
@@ -64,6 +67,9 @@ export declare class XKOVAMemberResources {
|
|
|
64
67
|
credit: {
|
|
65
68
|
amount: string;
|
|
66
69
|
token_deployment_id: string;
|
|
70
|
+
token_address: string;
|
|
71
|
+
token_symbol: string;
|
|
72
|
+
chain: import("./generated/types.js").components["schemas"]["ChainDescriptor"];
|
|
67
73
|
status: "pending" | "submitted" | "settled" | "requires_review";
|
|
68
74
|
transaction_hash: string | null;
|
|
69
75
|
} | null;
|
|
@@ -103,6 +109,7 @@ export declare class XKOVAMemberResources {
|
|
|
103
109
|
}>;
|
|
104
110
|
balance: (control?: XKOVARequestControl) => Promise<{
|
|
105
111
|
balances: import("./generated/types.js").components["schemas"]["AccountHolderSelfBalanceEntry"][];
|
|
112
|
+
data_freshness: import("./generated/types.js").components["schemas"]["DataFreshness"][];
|
|
106
113
|
}>;
|
|
107
114
|
wallets: (control?: XKOVARequestControl) => Promise<{
|
|
108
115
|
data: import("./generated/types.js").components["schemas"]["Wallet"][];
|
|
@@ -152,6 +152,9 @@ export declare class AccountHoldersResource extends Resource {
|
|
|
152
152
|
credit: {
|
|
153
153
|
amount: string;
|
|
154
154
|
token_deployment_id: string;
|
|
155
|
+
token_address: string;
|
|
156
|
+
token_symbol: string;
|
|
157
|
+
chain: import("../generated/types.js").components["schemas"]["ChainDescriptor"];
|
|
155
158
|
status: "pending" | "submitted" | "settled" | "requires_review";
|
|
156
159
|
transaction_hash: string | null;
|
|
157
160
|
} | null;
|
|
@@ -164,12 +167,16 @@ export declare class AccountHoldersResource extends Resource {
|
|
|
164
167
|
credit: {
|
|
165
168
|
amount: string;
|
|
166
169
|
token_deployment_id: string;
|
|
170
|
+
token_address: string;
|
|
171
|
+
token_symbol: string;
|
|
172
|
+
chain: import("../generated/types.js").components["schemas"]["ChainDescriptor"];
|
|
167
173
|
status: "pending" | "submitted" | "settled" | "requires_review";
|
|
168
174
|
transaction_hash: string | null;
|
|
169
175
|
} | null;
|
|
170
176
|
}>;
|
|
171
177
|
selfBalance(context: XKOVAMemberSessionContext, control?: XKOVARequestControl): Promise<{
|
|
172
178
|
balances: import("../generated/types.js").components["schemas"]["AccountHolderSelfBalanceEntry"][];
|
|
179
|
+
data_freshness: import("../generated/types.js").components["schemas"]["DataFreshness"][];
|
|
173
180
|
}>;
|
|
174
181
|
listSelfWallets(context: XKOVAMemberSessionContext, control?: XKOVARequestControl): Promise<{
|
|
175
182
|
data: import("../generated/types.js").components["schemas"]["Wallet"][];
|
|
@@ -2,6 +2,8 @@ import { Resource, type ReqBody, type Query } from './base.js';
|
|
|
2
2
|
import type { XKOVAMutationOptions } from '../client.js';
|
|
3
3
|
export declare class BrandingResource extends Resource {
|
|
4
4
|
get(): Promise<{
|
|
5
|
+
logo_upload_limits: import("../generated/types.js").components["schemas"]["LogoUploadLimits"];
|
|
6
|
+
has_logo_override: boolean;
|
|
5
7
|
resolved: {
|
|
6
8
|
brand_name: string;
|
|
7
9
|
legal_name: string;
|
|
@@ -28,6 +30,8 @@ export declare class BrandingResource extends Resource {
|
|
|
28
30
|
theme: import("../generated/types.js").components["schemas"]["TenantBrandingTheme"];
|
|
29
31
|
}>;
|
|
30
32
|
upsert(body: ReqBody<'upsertBranding'>, opts?: XKOVAMutationOptions): Promise<{
|
|
33
|
+
logo_upload_limits: import("../generated/types.js").components["schemas"]["LogoUploadLimits"];
|
|
34
|
+
has_logo_override: boolean;
|
|
31
35
|
resolved: {
|
|
32
36
|
brand_name: string;
|
|
33
37
|
legal_name: string;
|
|
@@ -94,12 +98,16 @@ export declare class BrandingResource extends Resource {
|
|
|
94
98
|
last_synced_at: string;
|
|
95
99
|
created_at: string;
|
|
96
100
|
}>;
|
|
101
|
+
/** Upload bytes to the returned URL, then confirm its opaque key as the same tenant. */
|
|
97
102
|
logoUploadUrl(body: ReqBody<'brandingLogoUploadUrl'>, opts?: XKOVAMutationOptions): Promise<{
|
|
98
103
|
upload_url: string;
|
|
99
104
|
object_key: string;
|
|
100
105
|
expires_at: string;
|
|
101
106
|
}>;
|
|
107
|
+
/** Publish a newly issued upload. Saved logo URLs or keys are not upload keys. */
|
|
102
108
|
confirmLogo(body: ReqBody<'confirmBrandingLogo'>, opts?: XKOVAMutationOptions): Promise<{
|
|
109
|
+
logo_upload_limits: import("../generated/types.js").components["schemas"]["LogoUploadLimits"];
|
|
110
|
+
has_logo_override: boolean;
|
|
103
111
|
resolved: {
|
|
104
112
|
brand_name: string;
|
|
105
113
|
legal_name: string;
|
|
@@ -126,6 +134,8 @@ export declare class BrandingResource extends Resource {
|
|
|
126
134
|
theme: import("../generated/types.js").components["schemas"]["TenantBrandingTheme"];
|
|
127
135
|
}>;
|
|
128
136
|
deleteLogo(opts?: XKOVAMutationOptions): Promise<{
|
|
137
|
+
logo_upload_limits: import("../generated/types.js").components["schemas"]["LogoUploadLimits"];
|
|
138
|
+
has_logo_override: boolean;
|
|
129
139
|
resolved: {
|
|
130
140
|
brand_name: string;
|
|
131
141
|
legal_name: string;
|
|
@@ -22,9 +22,11 @@ export class BrandingResource extends Resource {
|
|
|
22
22
|
verifyEmailDomain(id, opts) {
|
|
23
23
|
return this.postJson(`/branding/email-domains/${this.enc(id)}/verify`, undefined, opts);
|
|
24
24
|
}
|
|
25
|
+
/** Upload bytes to the returned URL, then confirm its opaque key as the same tenant. */
|
|
25
26
|
logoUploadUrl(body, opts) {
|
|
26
27
|
return this.postJson('/branding/logo/upload-url', body, opts);
|
|
27
28
|
}
|
|
29
|
+
/** Publish a newly issued upload. Saved logo URLs or keys are not upload keys. */
|
|
28
30
|
confirmLogo(body, opts) {
|
|
29
31
|
return this.postJson('/branding/logo', body, opts);
|
|
30
32
|
}
|
|
@@ -7,6 +7,7 @@ export declare class EscrowsResource extends Resource {
|
|
|
7
7
|
has_more: boolean;
|
|
8
8
|
}>;
|
|
9
9
|
create(body: ReqBody<'createEscrow'>, opts?: XKOVAMutationOptions): Promise<{
|
|
10
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
10
11
|
escrow: import("../generated/types.js").components["schemas"]["Escrow"];
|
|
11
12
|
claim_token: string;
|
|
12
13
|
claim_url: string;
|
|
@@ -15,6 +16,7 @@ export declare class EscrowsResource extends Resource {
|
|
|
15
16
|
signing_expires_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
16
17
|
}>;
|
|
17
18
|
retrieve(id: string, query?: Query<'getEscrow'>): Promise<{
|
|
19
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
18
20
|
id: import("../generated/types.js").components["schemas"]["EscrowId"];
|
|
19
21
|
chain: import("../generated/types.js").components["schemas"]["ChainDescriptor"];
|
|
20
22
|
fingerprint: string;
|
|
@@ -28,6 +30,7 @@ export declare class EscrowsResource extends Resource {
|
|
|
28
30
|
on_chain_payment_id?: string | null;
|
|
29
31
|
create_tx_hash?: string | null;
|
|
30
32
|
release_tx_hash?: string | null;
|
|
33
|
+
data_freshness?: import("../generated/types.js").components["schemas"]["DataFreshness"][];
|
|
31
34
|
fee_accruals?: import("../generated/types.js").components["schemas"]["EscrowFeeAccrual"][];
|
|
32
35
|
net_amount?: import("../generated/types.js").components["schemas"]["Money"] | null;
|
|
33
36
|
claimed_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
@@ -11,6 +11,7 @@ export declare class PaymentRequestsResource extends Resource {
|
|
|
11
11
|
has_more: boolean;
|
|
12
12
|
}>;
|
|
13
13
|
create(body: ReqBody<'createPaymentRequest'>, opts?: XKOVAMutationOptions): Promise<{
|
|
14
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
14
15
|
id?: import("../generated/types.js").components["schemas"]["PaymentRequestId"];
|
|
15
16
|
requester_id?: import("../generated/types.js").components["schemas"]["AccountHolderId"];
|
|
16
17
|
payer_id?: import("../generated/types.js").components["schemas"]["AccountHolderId"] | null;
|
|
@@ -26,6 +27,7 @@ export declare class PaymentRequestsResource extends Resource {
|
|
|
26
27
|
updated_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"];
|
|
27
28
|
}>;
|
|
28
29
|
retrieve(id: string): Promise<{
|
|
30
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
29
31
|
id?: import("../generated/types.js").components["schemas"]["PaymentRequestId"];
|
|
30
32
|
requester_id?: import("../generated/types.js").components["schemas"]["AccountHolderId"];
|
|
31
33
|
payer_id?: import("../generated/types.js").components["schemas"]["AccountHolderId"] | null;
|
|
@@ -82,6 +84,7 @@ export declare class PaymentRequestsResource extends Resource {
|
|
|
82
84
|
finalized_at: import("../generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
83
85
|
}>;
|
|
84
86
|
cancel(id: string, opts?: XKOVAMutationOptions): Promise<{
|
|
87
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
85
88
|
id?: import("../generated/types.js").components["schemas"]["PaymentRequestId"];
|
|
86
89
|
requester_id?: import("../generated/types.js").components["schemas"]["AccountHolderId"];
|
|
87
90
|
payer_id?: import("../generated/types.js").components["schemas"]["AccountHolderId"] | null;
|
|
@@ -97,6 +100,7 @@ export declare class PaymentRequestsResource extends Resource {
|
|
|
97
100
|
updated_at?: import("../generated/types.js").components["schemas"]["TimestampUtc"];
|
|
98
101
|
}>;
|
|
99
102
|
decline(id: string, body?: ReqBody<'declinePaymentRequest'>, opts?: XKOVAMutationOptions): Promise<{
|
|
103
|
+
notification_status?: "queued" | "sent" | "failed" | "cancelled";
|
|
100
104
|
id?: import("../generated/types.js").components["schemas"]["PaymentRequestId"];
|
|
101
105
|
requester_id?: import("../generated/types.js").components["schemas"]["AccountHolderId"];
|
|
102
106
|
payer_id?: import("../generated/types.js").components["schemas"]["AccountHolderId"] | null;
|
|
@@ -47,6 +47,12 @@ export declare class TenantResource extends Resource {
|
|
|
47
47
|
publishable_key: string;
|
|
48
48
|
name: string;
|
|
49
49
|
provider_kind: "xkova_hosted" | "oidc" | "signed_launch";
|
|
50
|
+
provider_namespace: string;
|
|
51
|
+
issuer: string | null;
|
|
52
|
+
audience: string | null;
|
|
53
|
+
jwks_uri: string | null;
|
|
54
|
+
allowed_jwt_algorithms: ("RS256" | "PS256" | "ES256" | "EdDSA")[];
|
|
55
|
+
proof_binding_mode: "authorization_nonce" | "server_assertion_jti";
|
|
50
56
|
enrollment_mode: import("../generated/types.js").components["schemas"]["MemberAuthEnrollmentMode"];
|
|
51
57
|
is_default: boolean;
|
|
52
58
|
status: "active" | "principal_disabled";
|
|
@@ -73,6 +79,12 @@ export declare class TenantResource extends Resource {
|
|
|
73
79
|
publishable_key: string;
|
|
74
80
|
name: string;
|
|
75
81
|
provider_kind: "xkova_hosted" | "oidc" | "signed_launch";
|
|
82
|
+
provider_namespace: string;
|
|
83
|
+
issuer: string | null;
|
|
84
|
+
audience: string | null;
|
|
85
|
+
jwks_uri: string | null;
|
|
86
|
+
allowed_jwt_algorithms: ("RS256" | "PS256" | "ES256" | "EdDSA")[];
|
|
87
|
+
proof_binding_mode: "authorization_nonce" | "server_assertion_jti";
|
|
76
88
|
enrollment_mode: import("../generated/types.js").components["schemas"]["MemberAuthEnrollmentMode"];
|
|
77
89
|
is_default: boolean;
|
|
78
90
|
status: "active" | "principal_disabled";
|
|
@@ -90,6 +102,9 @@ export declare class TenantResource extends Resource {
|
|
|
90
102
|
created_at: import("../generated/types.js").components["schemas"]["TimestampUtc"];
|
|
91
103
|
updated_at: import("../generated/types.js").components["schemas"]["TimestampUtc"];
|
|
92
104
|
}>;
|
|
105
|
+
/** Disables the exact Hosted Auth or OIDC member application. */
|
|
106
|
+
disableMemberAuthApplication(id: string, opts?: XKOVAMutationOptions): Promise<void>;
|
|
107
|
+
/** Existing Hosted Auth convenience name. */
|
|
93
108
|
disableHostedApplication(id: string, opts?: XKOVAMutationOptions): Promise<void>;
|
|
94
109
|
configureOidcApplication(body: ReqBody<'configureMemberAuthOidcApplication'>, opts?: XKOVAMutationOptions): Promise<{
|
|
95
110
|
id: string;
|
package/dist/resources/tenant.js
CHANGED
|
@@ -15,9 +15,14 @@ export class TenantResource extends Resource {
|
|
|
15
15
|
updateHostedApplication(id, body, opts) {
|
|
16
16
|
return this.patchJson(`/tenant/member-auth/applications/${this.enc(id)}`, body, opts);
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
/** Disables the exact Hosted Auth or OIDC member application. */
|
|
19
|
+
disableMemberAuthApplication(id, opts) {
|
|
19
20
|
return this.deleteJson(`/tenant/member-auth/applications/${this.enc(id)}`, opts);
|
|
20
21
|
}
|
|
22
|
+
/** Existing Hosted Auth convenience name. */
|
|
23
|
+
disableHostedApplication(id, opts) {
|
|
24
|
+
return this.disableMemberAuthApplication(id, opts);
|
|
25
|
+
}
|
|
21
26
|
configureOidcApplication(body, opts) {
|
|
22
27
|
return this.putJson('/tenant/member-auth/application', body, opts);
|
|
23
28
|
}
|
|
@@ -186,6 +186,9 @@ export declare class TokenizationResource extends Resource {
|
|
|
186
186
|
raw_contract: string | null;
|
|
187
187
|
raw_function: string | null;
|
|
188
188
|
raw_args: string[] | null;
|
|
189
|
+
failure_reason_code: string | null;
|
|
190
|
+
failure_reason_detail: string | null;
|
|
191
|
+
failure_retryable: boolean;
|
|
189
192
|
created_at: string;
|
|
190
193
|
updated_at: string;
|
|
191
194
|
}>;
|
|
@@ -222,6 +225,9 @@ export declare class TokenizationResource extends Resource {
|
|
|
222
225
|
raw_contract: string | null;
|
|
223
226
|
raw_function: string | null;
|
|
224
227
|
raw_args: string[] | null;
|
|
228
|
+
failure_reason_code: string | null;
|
|
229
|
+
failure_reason_detail: string | null;
|
|
230
|
+
failure_retryable: boolean;
|
|
225
231
|
created_at: string;
|
|
226
232
|
updated_at: string;
|
|
227
233
|
}>;
|
|
@@ -261,6 +267,9 @@ export declare class TokenizationResource extends Resource {
|
|
|
261
267
|
raw_contract: string | null;
|
|
262
268
|
raw_function: string | null;
|
|
263
269
|
raw_args: string[] | null;
|
|
270
|
+
failure_reason_code: string | null;
|
|
271
|
+
failure_reason_detail: string | null;
|
|
272
|
+
failure_retryable: boolean;
|
|
264
273
|
created_at: string;
|
|
265
274
|
updated_at: string;
|
|
266
275
|
}>;
|
|
@@ -292,6 +301,9 @@ export declare class TokenizationResource extends Resource {
|
|
|
292
301
|
raw_contract: string | null;
|
|
293
302
|
raw_function: string | null;
|
|
294
303
|
raw_args: string[] | null;
|
|
304
|
+
failure_reason_code: string | null;
|
|
305
|
+
failure_reason_detail: string | null;
|
|
306
|
+
failure_retryable: boolean;
|
|
295
307
|
created_at: string;
|
|
296
308
|
updated_at: string;
|
|
297
309
|
}>;
|
|
@@ -323,6 +335,9 @@ export declare class TokenizationResource extends Resource {
|
|
|
323
335
|
raw_contract: string | null;
|
|
324
336
|
raw_function: string | null;
|
|
325
337
|
raw_args: string[] | null;
|
|
338
|
+
failure_reason_code: string | null;
|
|
339
|
+
failure_reason_detail: string | null;
|
|
340
|
+
failure_retryable: boolean;
|
|
326
341
|
created_at: string;
|
|
327
342
|
updated_at: string;
|
|
328
343
|
}>;
|
|
@@ -16,6 +16,7 @@ export declare class WalletsResource extends Resource {
|
|
|
16
16
|
updated_at: import("../generated/types.js").components["schemas"]["TimestampUtc"];
|
|
17
17
|
}>;
|
|
18
18
|
balance(id: string, query: Query<'getWalletBalance'>): Promise<{
|
|
19
|
+
data_freshness: import("../generated/types.js").components["schemas"]["DataFreshness"][];
|
|
19
20
|
chain_id: number;
|
|
20
21
|
wallet: string;
|
|
21
22
|
token: string;
|
|
@@ -34,6 +35,7 @@ export declare class WalletsResource extends Resource {
|
|
|
34
35
|
has_more: boolean;
|
|
35
36
|
} & {
|
|
36
37
|
data?: import("../generated/types.js").components["schemas"]["LedgerEntry"][];
|
|
38
|
+
data_freshness: import("../generated/types.js").components["schemas"]["DataFreshness"][];
|
|
37
39
|
}>;
|
|
38
40
|
decommission(id: string, opts?: XKOVAMutationOptions): Promise<{
|
|
39
41
|
id: import("../generated/types.js").components["schemas"]["WalletId"];
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
/** Install pg separately. The application owns the pool, schema, retention and key rotation. */
|
|
5
|
+
export class PostgresCheckpointStore {
|
|
6
|
+
#context = new AsyncLocalStorage();
|
|
7
|
+
#key;
|
|
8
|
+
|
|
9
|
+
constructor(pool, namespace, encryptionKey) {
|
|
10
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(namespace)) throw new Error('Invalid store namespace');
|
|
11
|
+
if (!Buffer.isBuffer(encryptionKey) || encryptionKey.length !== 32)
|
|
12
|
+
throw new Error('A 32-byte application encryption key is required');
|
|
13
|
+
this.pool = pool;
|
|
14
|
+
this.namespace = namespace;
|
|
15
|
+
this.#key = Buffer.from(encryptionKey);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#binding(reference) {
|
|
19
|
+
return JSON.stringify([this.namespace, reference]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#encrypt(reference, record) {
|
|
23
|
+
const iv = randomBytes(12);
|
|
24
|
+
const cipher = createCipheriv('aes-256-gcm', this.#key, iv);
|
|
25
|
+
cipher.setAAD(Buffer.from(this.#binding(reference)));
|
|
26
|
+
const bytes = Buffer.concat([cipher.update(JSON.stringify(record), 'utf8'), cipher.final()]);
|
|
27
|
+
return Buffer.concat([iv, cipher.getAuthTag(), bytes]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#decrypt(reference, bytes) {
|
|
31
|
+
if (!Buffer.isBuffer(bytes) || bytes.length < 29) throw new Error('Invalid stored envelope');
|
|
32
|
+
const decipher = createDecipheriv('aes-256-gcm', this.#key, bytes.subarray(0, 12));
|
|
33
|
+
decipher.setAAD(Buffer.from(this.#binding(reference)));
|
|
34
|
+
decipher.setAuthTag(bytes.subarray(12, 28));
|
|
35
|
+
return JSON.parse(
|
|
36
|
+
Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]).toString('utf8'),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#db() {
|
|
41
|
+
return this.#context.getStore()?.client ?? this.pool;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async put(reference, record) {
|
|
45
|
+
await this.#db().query(
|
|
46
|
+
'INSERT INTO xkova_helper_checkpoints(namespace, reference, envelope, expires_at) VALUES ($1,$2,$3,$4)',
|
|
47
|
+
[this.namespace, reference, this.#encrypt(reference, record), this.#expiry(record)],
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async get(reference) {
|
|
52
|
+
const result = await this.#db().query(
|
|
53
|
+
'SELECT envelope FROM xkova_helper_checkpoints WHERE namespace=$1 AND reference=$2 AND expires_at > now()',
|
|
54
|
+
[this.namespace, reference],
|
|
55
|
+
);
|
|
56
|
+
return result.rowCount ? this.#decrypt(reference, result.rows[0].envelope) : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async replace(reference, record) {
|
|
60
|
+
const result = await this.#db().query(
|
|
61
|
+
'UPDATE xkova_helper_checkpoints SET envelope=$3, expires_at=$4 WHERE namespace=$1 AND reference=$2',
|
|
62
|
+
[this.namespace, reference, this.#encrypt(reference, record), this.#expiry(record)],
|
|
63
|
+
);
|
|
64
|
+
if (result.rowCount !== 1) throw new Error('Cannot replace a missing checkpoint');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async delete(reference) {
|
|
68
|
+
await this.#db().query(
|
|
69
|
+
'DELETE FROM xkova_helper_checkpoints WHERE namespace=$1 AND reference=$2',
|
|
70
|
+
[this.namespace, reference],
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#expiry(record) {
|
|
75
|
+
const value = record.applicationExpiresAt ?? record.expiresAt;
|
|
76
|
+
if (!Number.isSafeInteger(value)) throw new Error('Invalid checkpoint expiry');
|
|
77
|
+
return new Date(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A session lock, with separate autocommit checkpoints, never an outer SQL transaction. */
|
|
81
|
+
async runExclusive(reference, work) {
|
|
82
|
+
const existing = this.#context.getStore();
|
|
83
|
+
if (existing) {
|
|
84
|
+
if (existing.reference !== reference) throw new Error('Nested references are unsupported');
|
|
85
|
+
return work();
|
|
86
|
+
}
|
|
87
|
+
const client = await this.pool.connect();
|
|
88
|
+
let locked = false;
|
|
89
|
+
let discard = false;
|
|
90
|
+
try {
|
|
91
|
+
await client.query("SET lock_timeout = '5s'");
|
|
92
|
+
await client.query("SET statement_timeout = '10s'");
|
|
93
|
+
await client.query('SELECT pg_advisory_lock(hashtextextended($1, 0))', [
|
|
94
|
+
this.#binding(reference),
|
|
95
|
+
]);
|
|
96
|
+
locked = true;
|
|
97
|
+
return await this.#context.run({ client, reference }, work);
|
|
98
|
+
} finally {
|
|
99
|
+
try {
|
|
100
|
+
if (locked) {
|
|
101
|
+
const result = await client.query(
|
|
102
|
+
'SELECT pg_advisory_unlock(hashtextextended($1, 0)) AS released',
|
|
103
|
+
[this.#binding(reference)],
|
|
104
|
+
);
|
|
105
|
+
if (result.rows[0]?.released !== true) throw new Error('Checkpoint lock was lost');
|
|
106
|
+
}
|
|
107
|
+
await client.query('RESET lock_timeout');
|
|
108
|
+
await client.query('RESET statement_timeout');
|
|
109
|
+
} catch {
|
|
110
|
+
// A connection with an uncertain lock must never return to the pool.
|
|
111
|
+
discard = true;
|
|
112
|
+
}
|
|
113
|
+
client.release(discard);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Apply through your application's normal migration process, to its own database.
|
|
2
|
+
CREATE TABLE xkova_helper_checkpoints (
|
|
3
|
+
namespace text NOT NULL,
|
|
4
|
+
reference text NOT NULL,
|
|
5
|
+
envelope bytea NOT NULL,
|
|
6
|
+
expires_at timestamptz NOT NULL,
|
|
7
|
+
PRIMARY KEY (namespace, reference)
|
|
8
|
+
);
|
|
9
|
+
CREATE INDEX xkova_helper_checkpoints_expiry ON xkova_helper_checkpoints (expires_at);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xkova/sdk",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
4
|
"description": "TypeScript SDK for the XKOVA API.",
|
|
5
5
|
"homepage": "https://docs.xkova.com",
|
|
6
6
|
"repository": {
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"files": [
|
|
48
48
|
"dist",
|
|
49
|
-
"llms.txt"
|
|
49
|
+
"llms.txt",
|
|
50
|
+
"examples"
|
|
50
51
|
],
|
|
51
52
|
"dependencies": {
|
|
52
53
|
"viem": "^2.21.0"
|