@youam/sdk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +7 -2
- package/dist/cli/index.cjs +1179 -195
- package/dist/index.cjs +1787 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +399 -19
- package/dist/index.d.ts +399 -19
- package/dist/index.js +1779 -133
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SDK configuration (matching Python config.py).
|
|
3
|
-
*
|
|
4
|
-
* Priority (highest wins): constructor arg > env var > default.
|
|
5
|
-
* TOML config file support is Python-only for now (unnecessary complexity for TS MVP).
|
|
6
|
-
*/
|
|
7
1
|
declare class SDKConfig {
|
|
8
2
|
readonly name: string;
|
|
9
3
|
readonly relayUrl: string;
|
|
@@ -13,7 +7,9 @@ declare class SDKConfig {
|
|
|
13
7
|
readonly displayName: string;
|
|
14
8
|
readonly transportType: string;
|
|
15
9
|
readonly trustPolicy: string;
|
|
16
|
-
|
|
10
|
+
relayDomain: string;
|
|
11
|
+
/** The domain the caller chose, if any; null when only derived. */
|
|
12
|
+
readonly explicitRelayDomain: string | null;
|
|
17
13
|
constructor(options: {
|
|
18
14
|
name: string;
|
|
19
15
|
relayUrl?: string | null;
|
|
@@ -34,6 +30,12 @@ declare class SDKConfig {
|
|
|
34
30
|
declare const UAM_VERSION = "0.1";
|
|
35
31
|
/** Maximum envelope size in bytes (64 KB). */
|
|
36
32
|
declare const MAX_ENVELOPE_SIZE = 65536;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum allowed skew, in seconds, between an inbound envelope's signed
|
|
35
|
+
* timestamp and the receiver's clock (either direction). Also the TTL of
|
|
36
|
+
* the inbound replay cache. Matches Python MAX_ENVELOPE_AGE.
|
|
37
|
+
*/
|
|
38
|
+
declare const MAX_ENVELOPE_AGE = 300;
|
|
37
39
|
/** All UAM message types. */
|
|
38
40
|
declare enum MessageType {
|
|
39
41
|
MESSAGE = "message",
|
|
@@ -106,6 +108,18 @@ declare class InvalidContactCardError extends UAMError {
|
|
|
106
108
|
declare class KeyPinningError extends UAMError {
|
|
107
109
|
constructor(message?: string);
|
|
108
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* The relay refused a message sent over WebSocket.
|
|
113
|
+
*
|
|
114
|
+
* `code` is the relay's error code (e.g. "rate_limited"), `detail` its
|
|
115
|
+
* explanation. Over HTTP the same refusal is an HTTP error.
|
|
116
|
+
*/
|
|
117
|
+
declare class SendRefusedError extends UAMError {
|
|
118
|
+
readonly messageId: string;
|
|
119
|
+
readonly code: string;
|
|
120
|
+
readonly detail: string | undefined;
|
|
121
|
+
constructor(messageId: string, code: string, detail?: string);
|
|
122
|
+
}
|
|
109
123
|
|
|
110
124
|
/**
|
|
111
125
|
* UAM address parsing and validation.
|
|
@@ -376,6 +390,119 @@ declare function createContactCard(address: string, displayName: string, relay:
|
|
|
376
390
|
*/
|
|
377
391
|
declare function verifyContactCard(card: ContactCard): void;
|
|
378
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Signed owner objects: profile, canonical form, hash and schemas.
|
|
395
|
+
*
|
|
396
|
+
* Mirrors uam.protocol.signed in the Python SDK; both are checked against
|
|
397
|
+
* the same vectors (tests/vectors/signed/). Every owner object (key log
|
|
398
|
+
* entries, links, invites, redemptions, grants) is a JSON body in a
|
|
399
|
+
* restricted profile, hashed as
|
|
400
|
+
*
|
|
401
|
+
* h = base64url(SHA-256("UAM-<type>-v1\n" || JCS(body))) (no padding)
|
|
402
|
+
*
|
|
403
|
+
* The profile: printable ASCII strings, integers within +/-(2^53-1), no
|
|
404
|
+
* floats, no nulls, no duplicate keys, and no members a type doesn't define
|
|
405
|
+
* (new ones go under `ext`, with `crit` naming those a verifier must know).
|
|
406
|
+
* A body that breaks any rule is refused, never repaired.
|
|
407
|
+
*/
|
|
408
|
+
|
|
409
|
+
type Body = Record<string, unknown>;
|
|
410
|
+
/** A signed-object body outside the profile or its type's schema. */
|
|
411
|
+
declare class SignedObjectError extends UAMError {
|
|
412
|
+
constructor(message?: string);
|
|
413
|
+
}
|
|
414
|
+
/** Parse a body, refusing anything outside the profile. */
|
|
415
|
+
declare function parseBody(text: string | Uint8Array): Body;
|
|
416
|
+
/** RFC 8785 canonical form (inside the profile: sorted keys, no whitespace). */
|
|
417
|
+
declare function canonical(body: Body): Uint8Array;
|
|
418
|
+
/** The object's hash; the body is validated first. */
|
|
419
|
+
declare function objectHash(body: Body): string;
|
|
420
|
+
/** Refuse a body outside the profile or its type's schema. */
|
|
421
|
+
declare function validate(body: unknown): asserts body is Body;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* did:key for the two key types owner objects carry (as the Python SDK).
|
|
425
|
+
*
|
|
426
|
+
* Ed25519 (an agent's key, `did:key:z6Mk...`, multicodec 0xed) and P-256
|
|
427
|
+
* (a passkey, `did:key:zDn...`, multicodec 0x1200, compressed point).
|
|
428
|
+
*/
|
|
429
|
+
|
|
430
|
+
type DidKeyKind = "ed25519" | "p256";
|
|
431
|
+
/** A did:key string that isn't one of the two supported key types. */
|
|
432
|
+
declare class InvalidDidKeyError extends UAMError {
|
|
433
|
+
constructor(message?: string);
|
|
434
|
+
}
|
|
435
|
+
/** `did:key:z...` for an Ed25519 key (32 bytes) or a compressed P-256 point (33 bytes). */
|
|
436
|
+
declare function encodeDidKey(kind: DidKeyKind, publicKey: Uint8Array): string;
|
|
437
|
+
/** The key type and raw public key in a did:key string. */
|
|
438
|
+
declare function decodeDidKey(did: string): {
|
|
439
|
+
kind: DidKeyKind;
|
|
440
|
+
key: Uint8Array;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Passkeys (WebAuthn): registration, assertions and the approval challenge.
|
|
445
|
+
*
|
|
446
|
+
* Mirrors uam.protocol.webauthn in the Python SDK; both are checked against
|
|
447
|
+
* the same vectors (tests/vectors/webauthn/).
|
|
448
|
+
*
|
|
449
|
+
* - registration: `webauthn.create`, attestation format `none` only, user
|
|
450
|
+
* present and verified, ES256 only;
|
|
451
|
+
* - assertion: `webauthn.get`, the exact challenge, the owner origin exactly,
|
|
452
|
+
* no cross-origin frame, rpIdHash = SHA-256(RP ID), user present and
|
|
453
|
+
* verified, backup eligibility unchanged, an ES256 signature over
|
|
454
|
+
* `authenticatorData || SHA-256(clientDataJSON)` (high-S accepted), and,
|
|
455
|
+
* where the stored counter is known, a counter that went up whenever either
|
|
456
|
+
* count is non-zero (synced passkeys report 0 throughout; a count that
|
|
457
|
+
* drops to 0 may be a clone).
|
|
458
|
+
*
|
|
459
|
+
* ES256 only: a passkey's did:key must never look like an agent key.
|
|
460
|
+
*/
|
|
461
|
+
|
|
462
|
+
/** A registration or assertion that fails a check. */
|
|
463
|
+
declare class WebAuthnError extends UAMError {
|
|
464
|
+
constructor(message?: string);
|
|
465
|
+
}
|
|
466
|
+
/** A registered passkey: its id, algorithm, compressed P-256 key and flags. */
|
|
467
|
+
interface Credential {
|
|
468
|
+
readonly id: Uint8Array;
|
|
469
|
+
readonly alg: number;
|
|
470
|
+
readonly publicKey: Uint8Array;
|
|
471
|
+
readonly backupEligible: boolean;
|
|
472
|
+
readonly signCount: number;
|
|
473
|
+
}
|
|
474
|
+
declare function credentialDid(credential: Credential): string;
|
|
475
|
+
/** Check a `webauthn.create` response; the passkey it registers. */
|
|
476
|
+
declare function verifyRegistration(args: {
|
|
477
|
+
attestationObject: Uint8Array;
|
|
478
|
+
clientDataJSON: Uint8Array;
|
|
479
|
+
challenge: Uint8Array;
|
|
480
|
+
origin: string;
|
|
481
|
+
rpId: string;
|
|
482
|
+
}): Credential;
|
|
483
|
+
/**
|
|
484
|
+
* Check a `webauthn.get` response from `credential`; its new sign count.
|
|
485
|
+
*
|
|
486
|
+
* `checkCounter: false` is for a verifier without the stored counter (an
|
|
487
|
+
* agent checking an approval offline); the relay always checks it.
|
|
488
|
+
*/
|
|
489
|
+
declare function verifyAssertion(credential: Credential, args: {
|
|
490
|
+
authenticatorData: Uint8Array;
|
|
491
|
+
clientDataJSON: Uint8Array;
|
|
492
|
+
signature: Uint8Array;
|
|
493
|
+
challenge: Uint8Array;
|
|
494
|
+
origin: string;
|
|
495
|
+
rpId: string;
|
|
496
|
+
checkCounter?: boolean;
|
|
497
|
+
}): number;
|
|
498
|
+
/**
|
|
499
|
+
* The challenge one Face ID signs to approve `items` (type, hash) as shown.
|
|
500
|
+
*
|
|
501
|
+
* `screen` is the exact text the page showed; the challenge commits to its
|
|
502
|
+
* SHA-256, so the page can't show one thing and get another signed.
|
|
503
|
+
*/
|
|
504
|
+
declare function approvalChallenge(items: Array<[string, string]>, screen: string): Uint8Array;
|
|
505
|
+
|
|
379
506
|
/**
|
|
380
507
|
* UAM Protocol -- Universal Agent Messaging protocol library.
|
|
381
508
|
*
|
|
@@ -386,6 +513,7 @@ type index_Address = Address;
|
|
|
386
513
|
type index_ContactCard = ContactCard;
|
|
387
514
|
type index_DecryptionError = DecryptionError;
|
|
388
515
|
declare const index_DecryptionError: typeof DecryptionError;
|
|
516
|
+
type index_DidKeyKind = DidKeyKind;
|
|
389
517
|
type index_EncryptionError = EncryptionError;
|
|
390
518
|
declare const index_EncryptionError: typeof EncryptionError;
|
|
391
519
|
type index_EnvelopeTooLargeError = EnvelopeTooLargeError;
|
|
@@ -394,42 +522,58 @@ type index_InvalidAddressError = InvalidAddressError;
|
|
|
394
522
|
declare const index_InvalidAddressError: typeof InvalidAddressError;
|
|
395
523
|
type index_InvalidContactCardError = InvalidContactCardError;
|
|
396
524
|
declare const index_InvalidContactCardError: typeof InvalidContactCardError;
|
|
525
|
+
type index_InvalidDidKeyError = InvalidDidKeyError;
|
|
526
|
+
declare const index_InvalidDidKeyError: typeof InvalidDidKeyError;
|
|
397
527
|
type index_InvalidEnvelopeError = InvalidEnvelopeError;
|
|
398
528
|
declare const index_InvalidEnvelopeError: typeof InvalidEnvelopeError;
|
|
399
529
|
type index_KeyPinningError = KeyPinningError;
|
|
400
530
|
declare const index_KeyPinningError: typeof KeyPinningError;
|
|
401
531
|
type index_Keypair = Keypair;
|
|
532
|
+
declare const index_MAX_ENVELOPE_AGE: typeof MAX_ENVELOPE_AGE;
|
|
402
533
|
declare const index_MAX_ENVELOPE_SIZE: typeof MAX_ENVELOPE_SIZE;
|
|
403
534
|
type index_MessageEnvelope = MessageEnvelope;
|
|
404
535
|
type index_MessageType = MessageType;
|
|
405
536
|
declare const index_MessageType: typeof MessageType;
|
|
406
537
|
type index_Seed = Seed;
|
|
538
|
+
type index_SendRefusedError = SendRefusedError;
|
|
539
|
+
declare const index_SendRefusedError: typeof SendRefusedError;
|
|
407
540
|
type index_SignatureError = SignatureError;
|
|
408
541
|
declare const index_SignatureError: typeof SignatureError;
|
|
409
542
|
type index_SignatureVerificationError = SignatureVerificationError;
|
|
410
543
|
declare const index_SignatureVerificationError: typeof SignatureVerificationError;
|
|
544
|
+
type index_SignedObjectError = SignedObjectError;
|
|
545
|
+
declare const index_SignedObjectError: typeof SignedObjectError;
|
|
411
546
|
type index_SigningKey = SigningKey;
|
|
412
547
|
type index_UAMError = UAMError;
|
|
413
548
|
declare const index_UAMError: typeof UAMError;
|
|
414
549
|
declare const index_UAM_VERSION: typeof UAM_VERSION;
|
|
415
550
|
type index_VerifyKey = VerifyKey;
|
|
551
|
+
type index_WebAuthnError = WebAuthnError;
|
|
552
|
+
declare const index_WebAuthnError: typeof WebAuthnError;
|
|
553
|
+
declare const index_approvalChallenge: typeof approvalChallenge;
|
|
416
554
|
declare const index_b64Decode: typeof b64Decode;
|
|
417
555
|
declare const index_b64Encode: typeof b64Encode;
|
|
556
|
+
declare const index_canonical: typeof canonical;
|
|
418
557
|
declare const index_canonicalize: typeof canonicalize;
|
|
419
558
|
declare const index_contactCardFromDict: typeof contactCardFromDict;
|
|
420
559
|
declare const index_contactCardToDict: typeof contactCardToDict;
|
|
421
560
|
declare const index_createContactCard: typeof createContactCard;
|
|
422
561
|
declare const index_createEnvelope: typeof createEnvelope;
|
|
562
|
+
declare const index_credentialDid: typeof credentialDid;
|
|
563
|
+
declare const index_decodeDidKey: typeof decodeDidKey;
|
|
423
564
|
declare const index_decryptPayload: typeof decryptPayload;
|
|
424
565
|
declare const index_decryptPayloadAnonymous: typeof decryptPayloadAnonymous;
|
|
425
566
|
declare const index_deserializeSigningKey: typeof deserializeSigningKey;
|
|
426
567
|
declare const index_deserializeVerifyKey: typeof deserializeVerifyKey;
|
|
568
|
+
declare const index_encodeDidKey: typeof encodeDidKey;
|
|
427
569
|
declare const index_encryptPayload: typeof encryptPayload;
|
|
428
570
|
declare const index_encryptPayloadAnonymous: typeof encryptPayloadAnonymous;
|
|
429
571
|
declare const index_fromWireDict: typeof fromWireDict;
|
|
430
572
|
declare const index_generateKeypair: typeof generateKeypair;
|
|
431
573
|
declare const index_generateNonce: typeof generateNonce;
|
|
574
|
+
declare const index_objectHash: typeof objectHash;
|
|
432
575
|
declare const index_parseAddress: typeof parseAddress;
|
|
576
|
+
declare const index_parseBody: typeof parseBody;
|
|
433
577
|
declare const index_publicKeyFingerprint: typeof publicKeyFingerprint;
|
|
434
578
|
declare const index_serializeSigningKey: typeof serializeSigningKey;
|
|
435
579
|
declare const index_serializeVerifyKey: typeof serializeVerifyKey;
|
|
@@ -438,11 +582,13 @@ declare const index_sodiumReady: typeof sodiumReady;
|
|
|
438
582
|
declare const index_toWireDict: typeof toWireDict;
|
|
439
583
|
declare const index_utcTimestamp: typeof utcTimestamp;
|
|
440
584
|
declare const index_validateEnvelopeSize: typeof validateEnvelopeSize;
|
|
585
|
+
declare const index_verifyAssertion: typeof verifyAssertion;
|
|
441
586
|
declare const index_verifyContactCard: typeof verifyContactCard;
|
|
442
587
|
declare const index_verifyEnvelope: typeof verifyEnvelope;
|
|
588
|
+
declare const index_verifyRegistration: typeof verifyRegistration;
|
|
443
589
|
declare const index_verifySignature: typeof verifySignature;
|
|
444
590
|
declare namespace index {
|
|
445
|
-
export { type index_Address as Address, type index_ContactCard as ContactCard, index_DecryptionError as DecryptionError, index_EncryptionError as EncryptionError, index_EnvelopeTooLargeError as EnvelopeTooLargeError, index_InvalidAddressError as InvalidAddressError, index_InvalidContactCardError as InvalidContactCardError, index_InvalidEnvelopeError as InvalidEnvelopeError, index_KeyPinningError as KeyPinningError, type index_Keypair as Keypair, index_MAX_ENVELOPE_SIZE as MAX_ENVELOPE_SIZE, type index_MessageEnvelope as MessageEnvelope, index_MessageType as MessageType, type index_Seed as Seed, index_SignatureError as SignatureError, index_SignatureVerificationError as SignatureVerificationError, type index_SigningKey as SigningKey, index_UAMError as UAMError, index_UAM_VERSION as UAM_VERSION, type index_VerifyKey as VerifyKey, index_b64Decode as b64Decode, index_b64Encode as b64Encode, index_canonicalize as canonicalize, index_contactCardFromDict as contactCardFromDict, index_contactCardToDict as contactCardToDict, index_createContactCard as createContactCard, index_createEnvelope as createEnvelope, index_decryptPayload as decryptPayload, index_decryptPayloadAnonymous as decryptPayloadAnonymous, index_deserializeSigningKey as deserializeSigningKey, index_deserializeVerifyKey as deserializeVerifyKey, index_encryptPayload as encryptPayload, index_encryptPayloadAnonymous as encryptPayloadAnonymous, index_fromWireDict as fromWireDict, index_generateKeypair as generateKeypair, index_generateNonce as generateNonce, index_parseAddress as parseAddress, index_publicKeyFingerprint as publicKeyFingerprint, index_serializeSigningKey as serializeSigningKey, index_serializeVerifyKey as serializeVerifyKey, index_signMessage as signMessage, index_sodiumReady as sodiumReady, index_toWireDict as toWireDict, index_utcTimestamp as utcTimestamp, index_validateEnvelopeSize as validateEnvelopeSize, index_verifyContactCard as verifyContactCard, index_verifyEnvelope as verifyEnvelope, index_verifySignature as verifySignature };
|
|
591
|
+
export { type index_Address as Address, type index_ContactCard as ContactCard, index_DecryptionError as DecryptionError, type index_DidKeyKind as DidKeyKind, index_EncryptionError as EncryptionError, index_EnvelopeTooLargeError as EnvelopeTooLargeError, index_InvalidAddressError as InvalidAddressError, index_InvalidContactCardError as InvalidContactCardError, index_InvalidDidKeyError as InvalidDidKeyError, index_InvalidEnvelopeError as InvalidEnvelopeError, index_KeyPinningError as KeyPinningError, type index_Keypair as Keypair, index_MAX_ENVELOPE_AGE as MAX_ENVELOPE_AGE, index_MAX_ENVELOPE_SIZE as MAX_ENVELOPE_SIZE, type index_MessageEnvelope as MessageEnvelope, index_MessageType as MessageType, type Credential as PasskeyCredential, type index_Seed as Seed, index_SendRefusedError as SendRefusedError, index_SignatureError as SignatureError, index_SignatureVerificationError as SignatureVerificationError, type Body as SignedBody, index_SignedObjectError as SignedObjectError, type index_SigningKey as SigningKey, index_UAMError as UAMError, index_UAM_VERSION as UAM_VERSION, type index_VerifyKey as VerifyKey, index_WebAuthnError as WebAuthnError, index_approvalChallenge as approvalChallenge, index_b64Decode as b64Decode, index_b64Encode as b64Encode, index_canonical as canonical, index_canonicalize as canonicalize, index_contactCardFromDict as contactCardFromDict, index_contactCardToDict as contactCardToDict, index_createContactCard as createContactCard, index_createEnvelope as createEnvelope, index_credentialDid as credentialDid, index_decodeDidKey as decodeDidKey, index_decryptPayload as decryptPayload, index_decryptPayloadAnonymous as decryptPayloadAnonymous, index_deserializeSigningKey as deserializeSigningKey, index_deserializeVerifyKey as deserializeVerifyKey, index_encodeDidKey as encodeDidKey, index_encryptPayload as encryptPayload, index_encryptPayloadAnonymous as encryptPayloadAnonymous, index_fromWireDict as fromWireDict, index_generateKeypair as generateKeypair, index_generateNonce as generateNonce, index_objectHash as objectHash, index_parseAddress as parseAddress, index_parseBody as parseBody, index_publicKeyFingerprint as publicKeyFingerprint, index_serializeSigningKey as serializeSigningKey, index_serializeVerifyKey as serializeVerifyKey, index_signMessage as signMessage, index_sodiumReady as sodiumReady, index_toWireDict as toWireDict, index_utcTimestamp as utcTimestamp, index_validateEnvelopeSize as validateEnvelopeSize, validate as validateSignedObject, index_verifyAssertion as verifyAssertion, index_verifyContactCard as verifyContactCard, index_verifyEnvelope as verifyEnvelope, index_verifyRegistration as verifyRegistration, index_verifySignature as verifySignature };
|
|
446
592
|
}
|
|
447
593
|
|
|
448
594
|
/**
|
|
@@ -474,13 +620,31 @@ declare class KeyManager {
|
|
|
474
620
|
* Store the relay token alongside the keypair.
|
|
475
621
|
*/
|
|
476
622
|
saveToken(name: string, token: string): void;
|
|
623
|
+
/**
|
|
624
|
+
* Store the address `relayUrl` gave this agent.
|
|
625
|
+
*/
|
|
626
|
+
saveAddress(name: string, address: string, relayUrl: string): void;
|
|
627
|
+
/**
|
|
628
|
+
* Load the address saved for `relayUrl`.
|
|
629
|
+
*
|
|
630
|
+
* Returns null if there is none: the file is missing, unreadable or
|
|
631
|
+
* malformed, or it belongs to another relay.
|
|
632
|
+
*/
|
|
633
|
+
loadAddress(name: string, relayUrl: string): string | null;
|
|
477
634
|
/**
|
|
478
635
|
* Load a previously saved token, or return null.
|
|
479
636
|
*
|
|
480
637
|
* Checks UAM_TOKEN env var first, then file-based storage,
|
|
481
|
-
* then legacy .api_key files for backward compatibility.
|
|
638
|
+
* then legacy .api_key files for backward compatibility. With `relayUrl`,
|
|
639
|
+
* a saved token that belongs to another relay counts as none, so it is
|
|
640
|
+
* only offered to the relay that issued it.
|
|
641
|
+
*/
|
|
642
|
+
loadToken(name: string, relayUrl?: string): string | null;
|
|
643
|
+
/**
|
|
644
|
+
* True if the saved address says this agent's files are another relay's.
|
|
645
|
+
* A missing or damaged file says nothing.
|
|
482
646
|
*/
|
|
483
|
-
|
|
647
|
+
private _savedForAnotherRelay;
|
|
484
648
|
/** Set file permissions to 0o600 (owner read/write only). */
|
|
485
649
|
private _setPermissions;
|
|
486
650
|
/** Warn if key file permissions are too permissive. */
|
|
@@ -512,6 +676,30 @@ interface ReceivedMessage {
|
|
|
512
676
|
readonly mediaType?: string;
|
|
513
677
|
readonly verified: boolean;
|
|
514
678
|
}
|
|
679
|
+
/** What a relay can say it did with a sent message (as the Python SDK). */
|
|
680
|
+
declare const SEND_STATUSES: readonly ["delivered", "stored", "relayed", "queued", "duplicate", "dropped", "pending", "accepted", "unknown"];
|
|
681
|
+
type SendStatus = (typeof SEND_STATUSES)[number];
|
|
682
|
+
/**
|
|
683
|
+
* What Agent.send() returns.
|
|
684
|
+
*
|
|
685
|
+
* `status` is one of SEND_STATUSES: "pending" means the recipient only
|
|
686
|
+
* takes messages from approved contacts and the relay kept nothing.
|
|
687
|
+
* `delivered` is true only when the relay says the recipient has it.
|
|
688
|
+
*/
|
|
689
|
+
interface SendResult {
|
|
690
|
+
readonly messageId: string;
|
|
691
|
+
readonly status: SendStatus;
|
|
692
|
+
readonly delivered: boolean;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* True when a relay's features include `name` at `version` or later.
|
|
696
|
+
*
|
|
697
|
+
* "name" is version 1; "name:N" means version N or later.
|
|
698
|
+
*/
|
|
699
|
+
declare function supports(features: Iterable<string>, name: string, version?: number): boolean;
|
|
700
|
+
/** What to tell whoever sent a message the relay answered "pending". */
|
|
701
|
+
declare const PENDING_NOTE: string;
|
|
702
|
+
declare function pendingNote(to: string): string;
|
|
515
703
|
|
|
516
704
|
/**
|
|
517
705
|
* SQLite-backed local contact storage (HAND-03).
|
|
@@ -583,6 +771,14 @@ declare class ContactBook {
|
|
|
583
771
|
getRelayUrls(address: string): string[] | null;
|
|
584
772
|
/**
|
|
585
773
|
* Add or update a contact (upsert).
|
|
774
|
+
*
|
|
775
|
+
* Refuses to replace the key of a contact whose trust_state is "pinned"
|
|
776
|
+
* or "verified" with a different key, unless `force: true` is passed (for
|
|
777
|
+
* a deliberate key rotation, which is logged). Matches the Python SDK's
|
|
778
|
+
* ContactBook.add_contact.
|
|
779
|
+
*
|
|
780
|
+
* @throws {KeyPinningError} If a pinned/verified contact's key would be
|
|
781
|
+
* replaced by a different key without `force`.
|
|
586
782
|
*/
|
|
587
783
|
addContact(address: string, publicKey: string, options?: {
|
|
588
784
|
displayName?: string | null;
|
|
@@ -590,6 +786,7 @@ declare class ContactBook {
|
|
|
590
786
|
trustSource?: string | null;
|
|
591
787
|
relay?: string | null;
|
|
592
788
|
relays?: string[] | null;
|
|
789
|
+
force?: boolean;
|
|
593
790
|
}): void;
|
|
594
791
|
/**
|
|
595
792
|
* Return all contacts sorted by last_seen DESC.
|
|
@@ -666,7 +863,7 @@ interface AgentLike {
|
|
|
666
863
|
readonly signingKey: Uint8Array;
|
|
667
864
|
};
|
|
668
865
|
readonly _transport: {
|
|
669
|
-
send(envelope: Record<string, unknown>): Promise<
|
|
866
|
+
send(envelope: Record<string, unknown>): Promise<unknown>;
|
|
670
867
|
} | null;
|
|
671
868
|
}
|
|
672
869
|
declare class HandshakeManager {
|
|
@@ -694,6 +891,16 @@ declare class HandshakeManager {
|
|
|
694
891
|
private _handleRequest;
|
|
695
892
|
/**
|
|
696
893
|
* Process a handshake.accept: store the sender as pinned (TOFU).
|
|
894
|
+
*
|
|
895
|
+
* Only honoured when:
|
|
896
|
+
* 1. we previously sent this address a handshake.request
|
|
897
|
+
* (trust_state === "handshake-sent"); anything else is an unsolicited
|
|
898
|
+
* accept and is dropped. This also means an accept can never
|
|
899
|
+
* downgrade a trusted/verified contact to pinned;
|
|
900
|
+
* 2. the payload decrypts with our key and the sender's key, and parses
|
|
901
|
+
* as JSON. Otherwise it is dropped without pinning;
|
|
902
|
+
* 3. an embedded contact_card, if present, is well-formed, correctly
|
|
903
|
+
* self-signed, and bound to the envelope sender (throws otherwise).
|
|
697
904
|
*/
|
|
698
905
|
private _handleAccept;
|
|
699
906
|
/**
|
|
@@ -718,8 +925,13 @@ declare abstract class TransportBase {
|
|
|
718
925
|
abstract connect(): Promise<void>;
|
|
719
926
|
/** Close the connection. */
|
|
720
927
|
abstract disconnect(): Promise<void>;
|
|
721
|
-
/**
|
|
722
|
-
|
|
928
|
+
/**
|
|
929
|
+
* Send a message envelope to the relay.
|
|
930
|
+
*
|
|
931
|
+
* Resolves with the relay's answer (`{status, ...}`), or null when none
|
|
932
|
+
* came in time: the message may still have arrived.
|
|
933
|
+
*/
|
|
934
|
+
abstract send(envelope: Record<string, unknown>): Promise<Record<string, unknown> | null>;
|
|
723
935
|
/** Retrieve pending messages from the relay. */
|
|
724
936
|
abstract receive(limit?: number): Promise<Record<string, unknown>[]>;
|
|
725
937
|
/**
|
|
@@ -731,6 +943,55 @@ declare abstract class TransportBase {
|
|
|
731
943
|
abstract listen(callback: (msg: Record<string, unknown>) => Promise<void>): Promise<void>;
|
|
732
944
|
}
|
|
733
945
|
|
|
946
|
+
/**
|
|
947
|
+
* Inbound (from address, message id) pairs kept in a SQLite file.
|
|
948
|
+
*
|
|
949
|
+
* Opens itself on first use, so an Agent driven without connect() still has
|
|
950
|
+
* replay protection. Safe to share between processes: the insert is the
|
|
951
|
+
* check (INSERT OR IGNORE), in WAL mode.
|
|
952
|
+
*/
|
|
953
|
+
declare class PersistentReplayCache {
|
|
954
|
+
readonly capacity: number;
|
|
955
|
+
private readonly _path;
|
|
956
|
+
private readonly _now;
|
|
957
|
+
private _db;
|
|
958
|
+
private _opening;
|
|
959
|
+
private _count;
|
|
960
|
+
private _lastPurge;
|
|
961
|
+
private _createdAt;
|
|
962
|
+
private readonly _overflow;
|
|
963
|
+
/**
|
|
964
|
+
* @param path - The SQLite file
|
|
965
|
+
* @param options.now - The clock, in ms since the epoch
|
|
966
|
+
*/
|
|
967
|
+
constructor(path: string, options?: {
|
|
968
|
+
capacity?: number;
|
|
969
|
+
now?: () => number;
|
|
970
|
+
});
|
|
971
|
+
/** Open the file now, rather than on first use. */
|
|
972
|
+
open(): Promise<void>;
|
|
973
|
+
/**
|
|
974
|
+
* Return true if the envelope must be dropped as a replay. Otherwise record
|
|
975
|
+
* the pair and return false.
|
|
976
|
+
*
|
|
977
|
+
* @param signedAt - The envelope's signed time, in ms since the epoch. It
|
|
978
|
+
* matters only when the cache is full.
|
|
979
|
+
*/
|
|
980
|
+
seenOrRecord(fromAddress: string, messageId: string, signedAt: number): Promise<boolean>;
|
|
981
|
+
/** When this cache file was first created, in ms since the epoch. */
|
|
982
|
+
createdAt(): Promise<number>;
|
|
983
|
+
/** True if an envelope signed at `signedAt` (ms) predates this cache. */
|
|
984
|
+
signedBeforeCache(signedAt: number): Promise<boolean>;
|
|
985
|
+
/** Number of ids currently held on disk. */
|
|
986
|
+
count(): Promise<number>;
|
|
987
|
+
/** Close the file; the next call opens it again. */
|
|
988
|
+
close(): Promise<void>;
|
|
989
|
+
private _open;
|
|
990
|
+
private _connect;
|
|
991
|
+
private _purge;
|
|
992
|
+
private _countRows;
|
|
993
|
+
}
|
|
994
|
+
|
|
734
995
|
/**
|
|
735
996
|
* Agent class -- the primary SDK interface (SDK-01).
|
|
736
997
|
*
|
|
@@ -756,6 +1017,23 @@ declare class Agent {
|
|
|
756
1017
|
readonly _contactBook: ContactBook;
|
|
757
1018
|
/** @internal */
|
|
758
1019
|
readonly _handshake: HandshakeManager;
|
|
1020
|
+
/**
|
|
1021
|
+
* @internal Wall clock in ms since the epoch, used by the inbound
|
|
1022
|
+
* freshness/expiry checks and the replay cache. Replaceable in tests.
|
|
1023
|
+
*/
|
|
1024
|
+
_clock: () => number;
|
|
1025
|
+
/**
|
|
1026
|
+
* @internal Inbound replay cache, keyed by (from address, message id).
|
|
1027
|
+
* Kept on disk under the data dir, so it outlives restarts and is shared by
|
|
1028
|
+
* every process using it; capacity from UAM_REPLAY_CACHE_CAPACITY.
|
|
1029
|
+
*/
|
|
1030
|
+
readonly _replayCache: PersistentReplayCache;
|
|
1031
|
+
/** Whether our relay has said it writes relay_meta. */
|
|
1032
|
+
private _relayStamps;
|
|
1033
|
+
/** Until when, by _monotonic, to take the relay's last no as still true. */
|
|
1034
|
+
private _relayNoUntil;
|
|
1035
|
+
/** @internal Monotonic clock in ms. Replaceable in tests. */
|
|
1036
|
+
_monotonic: () => number;
|
|
759
1037
|
/**
|
|
760
1038
|
* Create an Agent. No I/O happens here -- call connect() to initialize.
|
|
761
1039
|
*/
|
|
@@ -791,17 +1069,25 @@ declare class Agent {
|
|
|
791
1069
|
*/
|
|
792
1070
|
close(): Promise<void>;
|
|
793
1071
|
/**
|
|
794
|
-
* Send an encrypted, signed message.
|
|
1072
|
+
* Send an encrypted, signed message.
|
|
1073
|
+
*
|
|
1074
|
+
* Returns the message id with what the relay did with it: `status`
|
|
1075
|
+
* "pending" means the recipient only takes approved contacts, and the
|
|
1076
|
+
* message was not kept (don't resend it). `delivered` is true only when
|
|
1077
|
+
* the relay says the recipient has it.
|
|
795
1078
|
*/
|
|
796
1079
|
send(toAddress: string, message: string, options?: {
|
|
797
1080
|
threadId?: string;
|
|
798
1081
|
attachments?: Array<Record<string, unknown>>;
|
|
799
|
-
}): Promise<
|
|
1082
|
+
}): Promise<SendResult>;
|
|
800
1083
|
/**
|
|
801
1084
|
* Retrieve, decrypt, and verify pending messages.
|
|
802
1085
|
*
|
|
803
1086
|
* Returns a list of ReceivedMessage data objects.
|
|
804
|
-
*
|
|
1087
|
+
* Drops messages with invalid signatures or decryption errors. A message
|
|
1088
|
+
* that fails processing for any other reason (malformed envelope, bad
|
|
1089
|
+
* handshake payload, ...) is logged and dropped; it never aborts the
|
|
1090
|
+
* rest of the batch.
|
|
805
1091
|
*/
|
|
806
1092
|
inbox(limit?: number): Promise<ReceivedMessage[]>;
|
|
807
1093
|
/** List pending handshake requests. */
|
|
@@ -857,6 +1143,10 @@ declare class Agent {
|
|
|
857
1143
|
/**
|
|
858
1144
|
* Resolve a recipient's public key, checking contact book first.
|
|
859
1145
|
*
|
|
1146
|
+
* Pure lookup: never writes to the contact book. The contact row is
|
|
1147
|
+
* created by _initiateHandshake (as "handshake-sent") once the request
|
|
1148
|
+
* has gone out, giving the lifecycle no row -> handshake-sent -> pinned.
|
|
1149
|
+
*
|
|
860
1150
|
* TOFU-01: Known contacts always use stored key (zero network I/O).
|
|
861
1151
|
* TOFU-03: Race-condition double-check after network resolve.
|
|
862
1152
|
* Raises KeyPinningError if a pinned contact's key doesn't match.
|
|
@@ -869,6 +1159,26 @@ declare class Agent {
|
|
|
869
1159
|
* Returns null for invalid messages or handshake protocol messages.
|
|
870
1160
|
*/
|
|
871
1161
|
private _processInbound;
|
|
1162
|
+
/**
|
|
1163
|
+
* Recipient, freshness, expiry and replay checks for a signature-verified
|
|
1164
|
+
* inbound envelope. Returns false (and logs) when it must be dropped.
|
|
1165
|
+
* Matches the Python SDK's _process_inbound checks.
|
|
1166
|
+
*/
|
|
1167
|
+
private _passesInboundChecks;
|
|
1168
|
+
/** True if a message too old to be live is stored mail, not a replay. */
|
|
1169
|
+
private _wasStored;
|
|
1170
|
+
/**
|
|
1171
|
+
* True if our relay writes relay_meta, so a sender can't have.
|
|
1172
|
+
*
|
|
1173
|
+
* Asked only when a stale message carries a stamp. A yes is remembered for
|
|
1174
|
+
* good, a no for RELAY_NO_MEMORY_MS: a relay can be upgraded while this
|
|
1175
|
+
* agent runs. A failed lookup isn't remembered, because the relay has
|
|
1176
|
+
* already marked this mail delivered and the next message should get a
|
|
1177
|
+
* real answer.
|
|
1178
|
+
*/
|
|
1179
|
+
private _relayStampsMail;
|
|
1180
|
+
/** The relay's features; a body that doesn't list any is an empty list. */
|
|
1181
|
+
private _fetchRelayFeatures;
|
|
872
1182
|
/**
|
|
873
1183
|
* Sweep expired pending handshakes and send receipt.failed (HAND-03).
|
|
874
1184
|
*/
|
|
@@ -883,10 +1193,80 @@ declare class Agent {
|
|
|
883
1193
|
/**
|
|
884
1194
|
* Register with the relay server.
|
|
885
1195
|
*
|
|
886
|
-
* Calls POST /api/v1/register with agent name and public key.
|
|
887
|
-
*
|
|
1196
|
+
* Calls POST /api/v1/register with agent name and public key. If the
|
|
1197
|
+
* address exists (409), repeats it with a proof that this agent holds the
|
|
1198
|
+
* key. Stores the returned API key on disk.
|
|
888
1199
|
*/
|
|
889
1200
|
private _registerWithRelay;
|
|
1201
|
+
/** This agent's address on the relay, for signing a proof. */
|
|
1202
|
+
private _addressOnRelay;
|
|
1203
|
+
/**
|
|
1204
|
+
* True if the relay's URL fits `domain`, so a proof can be signed for it.
|
|
1205
|
+
*
|
|
1206
|
+
* A proof names an address on one relay, so it is only signed for a domain
|
|
1207
|
+
* this URL belongs to. A relay on this machine may use any domain.
|
|
1208
|
+
*/
|
|
1209
|
+
private _relayMayUse;
|
|
1210
|
+
private _postRegistration;
|
|
1211
|
+
/**
|
|
1212
|
+
* Sign a statement that this agent holds the key for `address`.
|
|
1213
|
+
*
|
|
1214
|
+
* The signed string must stay byte-identical to the Python SDK's.
|
|
1215
|
+
*/
|
|
1216
|
+
private _registrationProof;
|
|
1217
|
+
private _ownershipNotProven;
|
|
1218
|
+
/** Remember `address` for this relay, if the key folder allows it. */
|
|
1219
|
+
private _saveAddress;
|
|
1220
|
+
/** Saving is best effort: a key folder we can't write doesn't stop us. */
|
|
1221
|
+
private _tryToSave;
|
|
1222
|
+
/** Take `address` as ours; its domain is this relay's real domain. */
|
|
1223
|
+
private _useAddress;
|
|
1224
|
+
/**
|
|
1225
|
+
* The address the stored token belongs to.
|
|
1226
|
+
*
|
|
1227
|
+
* An explicit domain settles it. Otherwise the relay chooses the domain,
|
|
1228
|
+
* and it need not be the relay's hostname: use the address saved for this
|
|
1229
|
+
* relay, or ask the relay once and keep the answer.
|
|
1230
|
+
*/
|
|
1231
|
+
private _storedAddress;
|
|
1232
|
+
/** Ask the relay for its domain; null if it cannot say. */
|
|
1233
|
+
private _fetchRelayDomain;
|
|
890
1234
|
}
|
|
891
1235
|
|
|
892
|
-
|
|
1236
|
+
/**
|
|
1237
|
+
* Receiver-side webhook signature verification (HOOK-03).
|
|
1238
|
+
*
|
|
1239
|
+
* Small utility for webhook receivers to verify that incoming webhook
|
|
1240
|
+
* payloads were signed by the UAM relay. Uses HMAC-SHA256 with the
|
|
1241
|
+
* agent's webhook secret, which the relay returns when the webhook is set
|
|
1242
|
+
* (PUT /api/v1/agents/{address}/webhook). Setting the same URL again
|
|
1243
|
+
* returns it again.
|
|
1244
|
+
*
|
|
1245
|
+
* Matches Python webhook_verify.py API surface.
|
|
1246
|
+
*
|
|
1247
|
+
* Usage:
|
|
1248
|
+
*
|
|
1249
|
+
* import { verifyWebhookSignature } from "@youam/sdk";
|
|
1250
|
+
*
|
|
1251
|
+
* const payload = Buffer.from(requestBody);
|
|
1252
|
+
* const signature = request.headers["x-uam-signature"];
|
|
1253
|
+
* const secret = "your-webhook-secret";
|
|
1254
|
+
*
|
|
1255
|
+
* if (verifyWebhookSignature(payload, signature, secret)) {
|
|
1256
|
+
* // payload is authentic
|
|
1257
|
+
* }
|
|
1258
|
+
*/
|
|
1259
|
+
/**
|
|
1260
|
+
* Verify an HMAC-SHA256 webhook signature.
|
|
1261
|
+
*
|
|
1262
|
+
* @param payload - Raw request body bytes
|
|
1263
|
+
* @param signatureHeader - "sha256=<hex>" format (X-UAM-Signature header)
|
|
1264
|
+
* @param secret - The agent's webhook secret
|
|
1265
|
+
* @returns `true` if valid, `false` otherwise.
|
|
1266
|
+
*
|
|
1267
|
+
* Uses `crypto.timingSafeEqual()` for constant-time comparison
|
|
1268
|
+
* to prevent timing attacks.
|
|
1269
|
+
*/
|
|
1270
|
+
declare function verifyWebhookSignature(payload: Buffer | Uint8Array, signatureHeader: string, secret: string): boolean;
|
|
1271
|
+
|
|
1272
|
+
export { Agent, ContactBook, PENDING_NOTE, type ReceivedMessage, SEND_STATUSES, type SendResult, type SendStatus, pendingNote, index as protocol, supports, verifyWebhookSignature };
|