@xkova/sdk 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +453 -0
- package/dist/abi.d.ts +10288 -0
- package/dist/abi.js +6645 -0
- package/dist/client.d.ts +80 -0
- package/dist/client.js +251 -0
- package/dist/cursor.d.ts +21 -0
- package/dist/cursor.js +28 -0
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +63 -0
- package/dist/generated/types.d.ts +13371 -0
- package/dist/generated/types.js +3 -0
- package/dist/idempotency.d.ts +25 -0
- package/dist/idempotency.js +46 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +8 -0
- package/dist/member-action-intents.d.ts +72 -0
- package/dist/member-action-intents.js +181 -0
- package/dist/member-bff.d.ts +535 -0
- package/dist/member-bff.js +1594 -0
- package/dist/member-resources.d.ts +203 -0
- package/dist/member-resources.js +56 -0
- package/dist/request-signing.d.ts +23 -0
- package/dist/request-signing.js +112 -0
- package/dist/resources/account-holders.d.ts +295 -0
- package/dist/resources/account-holders.js +166 -0
- package/dist/resources/base.d.ts +47 -0
- package/dist/resources/base.js +25 -0
- package/dist/resources/branding.d.ts +154 -0
- package/dist/resources/branding.js +34 -0
- package/dist/resources/chains.d.ts +6 -0
- package/dist/resources/chains.js +6 -0
- package/dist/resources/contacts.d.ts +55 -0
- package/dist/resources/contacts.js +22 -0
- package/dist/resources/escrows.d.ts +45 -0
- package/dist/resources/escrows.js +18 -0
- package/dist/resources/fee-schedules.d.ts +40 -0
- package/dist/resources/fee-schedules.js +15 -0
- package/dist/resources/index.d.ts +21 -0
- package/dist/resources/index.js +21 -0
- package/dist/resources/integration-gateways.d.ts +114 -0
- package/dist/resources/integration-gateways.js +27 -0
- package/dist/resources/onboarding.d.ts +18 -0
- package/dist/resources/onboarding.js +9 -0
- package/dist/resources/payment-requests.d.ts +115 -0
- package/dist/resources/payment-requests.js +27 -0
- package/dist/resources/payments.d.ts +182 -0
- package/dist/resources/payments.js +33 -0
- package/dist/resources/ramps.d.ts +163 -0
- package/dist/resources/ramps.js +44 -0
- package/dist/resources/rwa.d.ts +397 -0
- package/dist/resources/rwa.js +150 -0
- package/dist/resources/tenant.d.ts +136 -0
- package/dist/resources/tenant.js +42 -0
- package/dist/resources/tokenization.d.ts +394 -0
- package/dist/resources/tokenization.js +126 -0
- package/dist/resources/transactions.d.ts +8 -0
- package/dist/resources/transactions.js +6 -0
- package/dist/resources/wallets.d.ts +56 -0
- package/dist/resources/wallets.js +21 -0
- package/dist/resources/webhooks.d.ts +115 -0
- package/dist/resources/webhooks.js +43 -0
- package/dist/resources/workflows.d.ts +44 -0
- package/dist/resources/workflows.js +70 -0
- package/dist/resources/workspace-tokens.d.ts +6 -0
- package/dist/resources/workspace-tokens.js +6 -0
- package/dist/resources/workspaces.d.ts +25 -0
- package/dist/resources/workspaces.js +12 -0
- package/dist/retry.d.ts +21 -0
- package/dist/retry.js +44 -0
- package/dist/rwa-verify.d.ts +50 -0
- package/dist/rwa-verify.js +159 -0
- package/dist/webhook.d.ts +24 -0
- package/dist/webhook.js +56 -0
- package/llms.txt +84 -0
- package/package.json +62 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { XKOVAClient, XKOVAMutationOptions, XKOVARequestControl } from './client.js';
|
|
2
|
+
import type { XKOVAMemberSessionContext } from './resources/account-holders.js';
|
|
3
|
+
import type { Query, ReqBody } from './resources/base.js';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve these resources for each BFF request. Calls share the principal,
|
|
6
|
+
* application, and member session; API permission checks still apply.
|
|
7
|
+
* A resolved session does not grant fresh approval to sign an action.
|
|
8
|
+
*/
|
|
9
|
+
export declare class XKOVAMemberResources {
|
|
10
|
+
private readonly client;
|
|
11
|
+
private readonly context;
|
|
12
|
+
readonly accountHolderId: string;
|
|
13
|
+
constructor(client: XKOVAClient, context: Readonly<XKOVAMemberSessionContext>, accountHolderId: string);
|
|
14
|
+
get payments(): import("./resources/index.js").PaymentsResource;
|
|
15
|
+
get escrows(): import("./resources/index.js").EscrowsResource;
|
|
16
|
+
get ramps(): import("./resources/index.js").RampsResource;
|
|
17
|
+
get contacts(): import("./resources/index.js").ContactsResource;
|
|
18
|
+
get paymentRequests(): import("./resources/index.js").PaymentRequestsResource;
|
|
19
|
+
get wallets(): import("./resources/index.js").WalletsResource;
|
|
20
|
+
readonly self: {
|
|
21
|
+
actionReadiness: (control?: XKOVARequestControl) => Promise<{
|
|
22
|
+
authorization_method: "hosted_passkey" | "external_oidc";
|
|
23
|
+
setup_status: "passkey_required" | "recovery_acknowledgement_required" | "recovery_required" | "ready" | "externally_managed";
|
|
24
|
+
action_authorization_enabled: boolean;
|
|
25
|
+
requires_fresh_proof: true;
|
|
26
|
+
provider_status: "not_checked";
|
|
27
|
+
}>;
|
|
28
|
+
sandboxBankingContext: (control?: XKOVARequestControl) => Promise<{
|
|
29
|
+
account_holder_id: import("./generated/types.js").components["schemas"]["AccountHolderId"];
|
|
30
|
+
savings_bank_account_id: string;
|
|
31
|
+
checking_bank_account_id: string;
|
|
32
|
+
pool_bank_account_id: string;
|
|
33
|
+
settlement_bank_account_id: string;
|
|
34
|
+
destination_account: import("./generated/types.js").components["schemas"]["CuPayoutAccountRef"];
|
|
35
|
+
}>;
|
|
36
|
+
verification: (control?: XKOVARequestControl) => Promise<{
|
|
37
|
+
required: boolean;
|
|
38
|
+
status: string;
|
|
39
|
+
verified_via: string | null;
|
|
40
|
+
verified_at: string | null;
|
|
41
|
+
verification_expires_at: string | null;
|
|
42
|
+
required_kinds: ("kyc" | "kyb")[];
|
|
43
|
+
sessions: {
|
|
44
|
+
kind: "kyc" | "kyb";
|
|
45
|
+
status: string;
|
|
46
|
+
vendor: string | null;
|
|
47
|
+
vendor_detail_url: string | null;
|
|
48
|
+
initiated_at: string | null;
|
|
49
|
+
completed_at: string | null;
|
|
50
|
+
expires_at: string | null;
|
|
51
|
+
}[];
|
|
52
|
+
verification_url: string | null;
|
|
53
|
+
}>;
|
|
54
|
+
balance: (control?: XKOVARequestControl) => Promise<{
|
|
55
|
+
balances: import("./generated/types.js").components["schemas"]["AccountHolderSelfBalanceEntry"][];
|
|
56
|
+
}>;
|
|
57
|
+
wallets: (control?: XKOVARequestControl) => Promise<{
|
|
58
|
+
data: import("./generated/types.js").components["schemas"]["Wallet"][];
|
|
59
|
+
}>;
|
|
60
|
+
walletReadiness: (control?: XKOVARequestControl) => Promise<{
|
|
61
|
+
status: "not_registered" | "provisioning" | "ready";
|
|
62
|
+
wallets: import("./generated/types.js").components["schemas"]["Wallet"][];
|
|
63
|
+
}>;
|
|
64
|
+
ensureWalletProvisioning: (opts?: XKOVAMutationOptions) => Promise<{
|
|
65
|
+
status: "pending" | "processing" | "reconciling" | "retry" | "ready" | "paused" | "manual_review" | "cancelled";
|
|
66
|
+
}>;
|
|
67
|
+
bankBalance: (control?: XKOVARequestControl) => Promise<{
|
|
68
|
+
currency: "USD";
|
|
69
|
+
available_balance: import("./generated/types.js").components["schemas"]["Money"];
|
|
70
|
+
current_balance: import("./generated/types.js").components["schemas"]["Money"];
|
|
71
|
+
as_of: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
72
|
+
}>;
|
|
73
|
+
checkingBalance: (control?: XKOVARequestControl) => Promise<{
|
|
74
|
+
currency: "USD";
|
|
75
|
+
available_balance: import("./generated/types.js").components["schemas"]["Money"];
|
|
76
|
+
current_balance: import("./generated/types.js").components["schemas"]["Money"];
|
|
77
|
+
as_of: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
78
|
+
}>;
|
|
79
|
+
bankTransactions: (query?: Query<"listAccountHolderSelfBankTransactions">, control?: XKOVARequestControl) => Promise<{
|
|
80
|
+
data: {
|
|
81
|
+
transfer_ref: string;
|
|
82
|
+
amount: string;
|
|
83
|
+
currency: string;
|
|
84
|
+
status: "pending" | "posted" | "reversed" | "rejected";
|
|
85
|
+
from_account: "savings" | "checking" | "other";
|
|
86
|
+
to_account: "savings" | "checking" | "other";
|
|
87
|
+
memo?: string | null;
|
|
88
|
+
occurred_at: string;
|
|
89
|
+
}[];
|
|
90
|
+
}>;
|
|
91
|
+
createBankTransfer: (body: ReqBody<"createAccountHolderSelfBankTransfer">, opts?: XKOVAMutationOptions) => Promise<{
|
|
92
|
+
transfer_ref: string;
|
|
93
|
+
status: "pending" | "posted" | "reversed" | "rejected";
|
|
94
|
+
as_of: string;
|
|
95
|
+
}>;
|
|
96
|
+
consents: (control?: XKOVARequestControl) => Promise<{
|
|
97
|
+
account_holder_id: import("./generated/types.js").components["schemas"]["AccountHolderId"];
|
|
98
|
+
current_policies: import("./generated/types.js").components["schemas"]["CurrentPolicy"][];
|
|
99
|
+
accepted: import("./generated/types.js").components["schemas"]["Consent"][];
|
|
100
|
+
missing_consents: import("./generated/types.js").components["schemas"]["MissingConsent"][];
|
|
101
|
+
consents_current: boolean;
|
|
102
|
+
first_use_completed: boolean;
|
|
103
|
+
}>;
|
|
104
|
+
acceptConsents: (body: ReqBody<"acceptAccountHolderConsents">, opts?: XKOVAMutationOptions) => Promise<{
|
|
105
|
+
account_holder_id: import("./generated/types.js").components["schemas"]["AccountHolderId"];
|
|
106
|
+
current_policies: import("./generated/types.js").components["schemas"]["CurrentPolicy"][];
|
|
107
|
+
accepted: import("./generated/types.js").components["schemas"]["Consent"][];
|
|
108
|
+
missing_consents: import("./generated/types.js").components["schemas"]["MissingConsent"][];
|
|
109
|
+
consents_current: boolean;
|
|
110
|
+
first_use_completed: boolean;
|
|
111
|
+
}>;
|
|
112
|
+
requestDataExport: (opts?: XKOVAMutationOptions) => Promise<{
|
|
113
|
+
id: import("./generated/types.js").components["schemas"]["DataExportId"];
|
|
114
|
+
account_holder_id: import("./generated/types.js").components["schemas"]["AccountHolderId"];
|
|
115
|
+
state: "queued" | "processing" | "ready" | "expired" | "failed";
|
|
116
|
+
signed_url: string | null;
|
|
117
|
+
url_expires_at: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
118
|
+
requested_at: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
119
|
+
started_at: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
120
|
+
completed_at: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
121
|
+
failure_reason: string | null;
|
|
122
|
+
}>;
|
|
123
|
+
dataExport: (id: string, control?: XKOVARequestControl) => Promise<{
|
|
124
|
+
id: import("./generated/types.js").components["schemas"]["DataExportId"];
|
|
125
|
+
account_holder_id: import("./generated/types.js").components["schemas"]["AccountHolderId"];
|
|
126
|
+
state: "queued" | "processing" | "ready" | "expired" | "failed";
|
|
127
|
+
signed_url: string | null;
|
|
128
|
+
url_expires_at: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
129
|
+
requested_at: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
130
|
+
started_at: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
131
|
+
completed_at: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
132
|
+
failure_reason: string | null;
|
|
133
|
+
}>;
|
|
134
|
+
transactions: (query?: Query<"listAccountHolderSelfTransactions">, control?: XKOVARequestControl) => Promise<{
|
|
135
|
+
data: import("./generated/types.js").components["schemas"]["AccountHolderSelfTransaction"][];
|
|
136
|
+
next_cursor: string | null;
|
|
137
|
+
has_more: boolean;
|
|
138
|
+
}>;
|
|
139
|
+
avatar: () => Promise<{
|
|
140
|
+
url: string | null;
|
|
141
|
+
}>;
|
|
142
|
+
uploadAvatar: (image: Blob, control?: XKOVARequestControl) => Promise<{
|
|
143
|
+
url: string | null;
|
|
144
|
+
}>;
|
|
145
|
+
deleteAvatar: (opts?: XKOVAMutationOptions) => Promise<{
|
|
146
|
+
id: import("./generated/types.js").components["schemas"]["AccountHolderId"];
|
|
147
|
+
tenant_id?: string;
|
|
148
|
+
state: "active" | "held" | "closed" | "closed_uncooperative" | "provisioning_failed";
|
|
149
|
+
state_reason?: string | null;
|
|
150
|
+
state_changed_at?: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
151
|
+
tier: "tier_1" | "tier_2";
|
|
152
|
+
tier_2_uplift_status?: "not_requested" | "pending" | "passed" | "failed";
|
|
153
|
+
first_use_completed: boolean;
|
|
154
|
+
cip_payload?: {
|
|
155
|
+
name_hash?: string;
|
|
156
|
+
dob_hash?: string;
|
|
157
|
+
id_hash?: string;
|
|
158
|
+
address_hash?: string;
|
|
159
|
+
} | null;
|
|
160
|
+
cip_accepted_at?: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
161
|
+
merchant_role_state?: "none" | "pending_review" | "approved" | "rejected" | "revoked";
|
|
162
|
+
auto_save_contacts?: boolean;
|
|
163
|
+
created_at: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
164
|
+
updated_at?: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
165
|
+
email?: string | null;
|
|
166
|
+
first_name?: string | null;
|
|
167
|
+
last_name?: string | null;
|
|
168
|
+
phone?: string | null;
|
|
169
|
+
avatar_url?: string | null;
|
|
170
|
+
}>;
|
|
171
|
+
confirmAvatar: (body: ReqBody<"confirmAccountHolderAvatar">, opts?: XKOVAMutationOptions) => Promise<{
|
|
172
|
+
id: import("./generated/types.js").components["schemas"]["AccountHolderId"];
|
|
173
|
+
tenant_id?: string;
|
|
174
|
+
state: "active" | "held" | "closed" | "closed_uncooperative" | "provisioning_failed";
|
|
175
|
+
state_reason?: string | null;
|
|
176
|
+
state_changed_at?: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
177
|
+
tier: "tier_1" | "tier_2";
|
|
178
|
+
tier_2_uplift_status?: "not_requested" | "pending" | "passed" | "failed";
|
|
179
|
+
first_use_completed: boolean;
|
|
180
|
+
cip_payload?: {
|
|
181
|
+
name_hash?: string;
|
|
182
|
+
dob_hash?: string;
|
|
183
|
+
id_hash?: string;
|
|
184
|
+
address_hash?: string;
|
|
185
|
+
} | null;
|
|
186
|
+
cip_accepted_at?: import("./generated/types.js").components["schemas"]["TimestampUtc"] | null;
|
|
187
|
+
merchant_role_state?: "none" | "pending_review" | "approved" | "rejected" | "revoked";
|
|
188
|
+
auto_save_contacts?: boolean;
|
|
189
|
+
created_at: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
190
|
+
updated_at?: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
191
|
+
email?: string | null;
|
|
192
|
+
first_name?: string | null;
|
|
193
|
+
last_name?: string | null;
|
|
194
|
+
phone?: string | null;
|
|
195
|
+
avatar_url?: string | null;
|
|
196
|
+
}>;
|
|
197
|
+
presignAvatarUpload: (body: ReqBody<"presignAccountHolderAvatarUpload">, opts?: XKOVAMutationOptions) => Promise<{
|
|
198
|
+
upload_url: string;
|
|
199
|
+
object_key: string;
|
|
200
|
+
expires_at: import("./generated/types.js").components["schemas"]["TimestampUtc"];
|
|
201
|
+
}>;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve these resources for each BFF request. Calls share the principal,
|
|
3
|
+
* application, and member session; API permission checks still apply.
|
|
4
|
+
* A resolved session does not grant fresh approval to sign an action.
|
|
5
|
+
*/
|
|
6
|
+
export class XKOVAMemberResources {
|
|
7
|
+
client;
|
|
8
|
+
context;
|
|
9
|
+
accountHolderId;
|
|
10
|
+
constructor(client, context, accountHolderId) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
this.context = context;
|
|
13
|
+
this.accountHolderId = accountHolderId;
|
|
14
|
+
}
|
|
15
|
+
get payments() {
|
|
16
|
+
return this.client.payments;
|
|
17
|
+
}
|
|
18
|
+
get escrows() {
|
|
19
|
+
return this.client.escrows;
|
|
20
|
+
}
|
|
21
|
+
get ramps() {
|
|
22
|
+
return this.client.ramps;
|
|
23
|
+
}
|
|
24
|
+
get contacts() {
|
|
25
|
+
return this.client.contacts;
|
|
26
|
+
}
|
|
27
|
+
get paymentRequests() {
|
|
28
|
+
return this.client.paymentRequests;
|
|
29
|
+
}
|
|
30
|
+
get wallets() {
|
|
31
|
+
return this.client.wallets;
|
|
32
|
+
}
|
|
33
|
+
self = {
|
|
34
|
+
actionReadiness: (control) => this.client.accountHolders.selfActionReadiness({ ...this.context, accountHolderId: this.accountHolderId }, control),
|
|
35
|
+
sandboxBankingContext: (control) => this.client.accountHolders.selfSandboxBankingContext(this.context, control),
|
|
36
|
+
verification: (control) => this.client.accountHolders.selfVerification(this.context, control),
|
|
37
|
+
balance: (control) => this.client.accountHolders.selfBalance(this.context, control),
|
|
38
|
+
wallets: (control) => this.client.accountHolders.listSelfWallets(this.context, control),
|
|
39
|
+
walletReadiness: (control) => this.client.accountHolders.selfWalletReadiness(this.context, control),
|
|
40
|
+
ensureWalletProvisioning: (opts) => this.client.accountHolders.ensureSelfWalletProvisioning(this.context, opts),
|
|
41
|
+
bankBalance: (control) => this.client.accountHolders.selfBankBalance(this.context, control),
|
|
42
|
+
checkingBalance: (control) => this.client.accountHolders.selfCheckingBalance(this.context, control),
|
|
43
|
+
bankTransactions: (query, control) => this.client.accountHolders.listSelfBankTransactions(this.context, query, control),
|
|
44
|
+
createBankTransfer: (body, opts) => this.client.accountHolders.createSelfBankTransfer(this.context, body, opts),
|
|
45
|
+
consents: (control) => this.client.accountHolders.selfConsentState(this.context, control),
|
|
46
|
+
acceptConsents: (body, opts) => this.client.accountHolders.acceptSelfConsents(this.context, body, opts),
|
|
47
|
+
requestDataExport: (opts) => this.client.accountHolders.requestSelfDataExport(this.context, opts),
|
|
48
|
+
dataExport: (id, control) => this.client.accountHolders.selfDataExport(this.context, id, control),
|
|
49
|
+
transactions: (query, control) => this.client.accountHolders.listSelfTransactions(this.context, query, control),
|
|
50
|
+
avatar: () => this.client.accountHolders.avatarUrl(this.accountHolderId),
|
|
51
|
+
uploadAvatar: (image, control) => this.client.accountHolders.uploadAvatar(this.accountHolderId, image, control),
|
|
52
|
+
deleteAvatar: (opts) => this.client.accountHolders.deleteAvatar(this.accountHolderId, opts),
|
|
53
|
+
confirmAvatar: (body, opts) => this.client.accountHolders.confirmAvatar(this.accountHolderId, body, opts),
|
|
54
|
+
presignAvatarUpload: (body, opts) => this.client.accountHolders.presignAvatarUpload(this.accountHolderId, body, opts),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type XKOVARequestSigningAlgorithm = 'ecdsa-p256-sha256' | 'ed25519' | 'rsa-pss-sha256';
|
|
2
|
+
export interface XKOVARequestSignInput {
|
|
3
|
+
/** Exact RFC 9421 signature base bytes. */
|
|
4
|
+
readonly data: Uint8Array;
|
|
5
|
+
/** Algorithm registered for this credential. */
|
|
6
|
+
readonly algorithm: XKOVARequestSigningAlgorithm;
|
|
7
|
+
/** XKOVA signing-credential UUID serialized as the RFC 9421 keyid. */
|
|
8
|
+
readonly credentialId: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Asymmetric signer owned by the customer server.
|
|
12
|
+
*
|
|
13
|
+
* The callback may delegate to a KMS, HSM, or local key provider. It must
|
|
14
|
+
* return the signature format registered with XKOVA: 64-byte IEEE P1363 for
|
|
15
|
+
* P-256, 64-byte Ed25519, or RSA-PSS-SHA256 with a 32-byte salt.
|
|
16
|
+
*/
|
|
17
|
+
export interface XKOVARequestSigner {
|
|
18
|
+
credentialId: string;
|
|
19
|
+
algorithm: XKOVARequestSigningAlgorithm;
|
|
20
|
+
sign(input: XKOVARequestSignInput): Uint8Array | Promise<Uint8Array>;
|
|
21
|
+
/** Signature lifetime from 30 through 60 seconds. Defaults to 45. */
|
|
22
|
+
lifetimeSeconds?: number;
|
|
23
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
const SIGNATURE_LABEL = 'xkova';
|
|
3
|
+
const SIGNATURE_TAG = 'xkova-production-mutation-v1';
|
|
4
|
+
const DEFAULT_LIFETIME_SECONDS = 45;
|
|
5
|
+
const MIN_LIFETIME_SECONDS = 30;
|
|
6
|
+
const MAX_LIFETIME_SECONDS = 60;
|
|
7
|
+
const BASE_COMPONENTS = ['@method', '@authority', '@path', '@query', 'authorization'];
|
|
8
|
+
const CONDITIONAL_HEADERS = [
|
|
9
|
+
'content-digest',
|
|
10
|
+
'content-type',
|
|
11
|
+
'idempotency-key',
|
|
12
|
+
'x-member-session-id',
|
|
13
|
+
'x-xkova-publishable-key',
|
|
14
|
+
'x-account-holder-id',
|
|
15
|
+
'x-sender-wallet-id',
|
|
16
|
+
'x-sandbox-scenario',
|
|
17
|
+
];
|
|
18
|
+
/** @internal */
|
|
19
|
+
export function xkovaContentDigest(body) {
|
|
20
|
+
return `sha-256=:${createHash('sha256').update(body).digest('base64')}:`;
|
|
21
|
+
}
|
|
22
|
+
/** @internal */
|
|
23
|
+
export function validateXKOVARequestSigner(signer) {
|
|
24
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(signer.credentialId)) {
|
|
25
|
+
throw new Error('XKOVA request signer credentialId must be a UUID');
|
|
26
|
+
}
|
|
27
|
+
if (signer.algorithm !== 'ecdsa-p256-sha256' &&
|
|
28
|
+
signer.algorithm !== 'ed25519' &&
|
|
29
|
+
signer.algorithm !== 'rsa-pss-sha256') {
|
|
30
|
+
throw new Error(`Unsupported XKOVA request-signing algorithm: ${String(signer.algorithm)}`);
|
|
31
|
+
}
|
|
32
|
+
if (typeof signer.sign !== 'function') {
|
|
33
|
+
throw new Error('XKOVA request signer requires a sign callback');
|
|
34
|
+
}
|
|
35
|
+
const lifetime = signer.lifetimeSeconds ?? DEFAULT_LIFETIME_SECONDS;
|
|
36
|
+
if (!Number.isInteger(lifetime) ||
|
|
37
|
+
lifetime < MIN_LIFETIME_SECONDS ||
|
|
38
|
+
lifetime > MAX_LIFETIME_SECONDS) {
|
|
39
|
+
throw new Error('XKOVA request signer lifetimeSeconds must be an integer from 30 through 60');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
export async function signXKOVARequest(input) {
|
|
44
|
+
validateXKOVARequestSigner(input.signer);
|
|
45
|
+
const components = requiredComponents(input.headers, input.body.byteLength > 0);
|
|
46
|
+
const created = Math.floor(Date.now() / 1000);
|
|
47
|
+
const expires = created + (input.signer.lifetimeSeconds ?? DEFAULT_LIFETIME_SECONDS);
|
|
48
|
+
const nonce = randomBytes(18).toString('base64url');
|
|
49
|
+
const serializedParams = `(${components.map((component) => `"${component}"`).join(' ')})` +
|
|
50
|
+
`;created=${created};expires=${expires};nonce="${nonce}"` +
|
|
51
|
+
`;keyid="${input.signer.credentialId}"` +
|
|
52
|
+
`;tag="${SIGNATURE_TAG}"`;
|
|
53
|
+
const signatureInput = `${SIGNATURE_LABEL}=${serializedParams}`;
|
|
54
|
+
const lines = components.map((component) => {
|
|
55
|
+
const value = componentValue(component, input);
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
throw new Error(`XKOVA request-signing component is missing: ${component}`);
|
|
58
|
+
}
|
|
59
|
+
return `"${component}": ${value}`;
|
|
60
|
+
});
|
|
61
|
+
lines.push(`"@signature-params": ${serializedParams}`);
|
|
62
|
+
const data = new TextEncoder().encode(lines.join('\n'));
|
|
63
|
+
const returned = await input.signer.sign({
|
|
64
|
+
data,
|
|
65
|
+
algorithm: input.signer.algorithm,
|
|
66
|
+
credentialId: input.signer.credentialId,
|
|
67
|
+
});
|
|
68
|
+
if (!(returned instanceof Uint8Array)) {
|
|
69
|
+
throw new Error('XKOVA request signer must return Uint8Array signature bytes');
|
|
70
|
+
}
|
|
71
|
+
const signature = Uint8Array.from(returned);
|
|
72
|
+
assertSignatureEncoding(input.signer.algorithm, signature);
|
|
73
|
+
return {
|
|
74
|
+
signatureInput,
|
|
75
|
+
signature: `${SIGNATURE_LABEL}=:${Buffer.from(signature).toString('base64')}:`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function requiredComponents(headers, bodyPresent) {
|
|
79
|
+
const components = [...BASE_COMPONENTS];
|
|
80
|
+
for (const header of CONDITIONAL_HEADERS) {
|
|
81
|
+
if (headers.has(header) ||
|
|
82
|
+
(bodyPresent && (header === 'content-digest' || header === 'content-type'))) {
|
|
83
|
+
components.push(header);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return components;
|
|
87
|
+
}
|
|
88
|
+
function componentValue(component, input) {
|
|
89
|
+
switch (component) {
|
|
90
|
+
case '@method':
|
|
91
|
+
return input.method.toUpperCase();
|
|
92
|
+
case '@authority':
|
|
93
|
+
return input.url.host;
|
|
94
|
+
case '@path':
|
|
95
|
+
return input.url.pathname;
|
|
96
|
+
case '@query':
|
|
97
|
+
return input.url.search || '?';
|
|
98
|
+
default:
|
|
99
|
+
return input.headers.get(component) ?? undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function assertSignatureEncoding(algorithm, signature) {
|
|
103
|
+
if (signature.byteLength === 0) {
|
|
104
|
+
throw new Error('XKOVA request signer returned an empty signature');
|
|
105
|
+
}
|
|
106
|
+
if (algorithm === 'ecdsa-p256-sha256' && signature.byteLength !== 64) {
|
|
107
|
+
throw new Error('P-256 request signatures must use 64-byte IEEE P1363 encoding');
|
|
108
|
+
}
|
|
109
|
+
if (algorithm === 'ed25519' && signature.byteLength !== 64) {
|
|
110
|
+
throw new Error('Ed25519 request signatures must be 64 bytes');
|
|
111
|
+
}
|
|
112
|
+
}
|