@yeying-community/web3-bs 1.0.5 → 1.0.6
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 +41 -6
- package/dist/auth/central.d.ts +76 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/ucan.d.ts +18 -2
- package/dist/web3-bs.esm.js +540 -46
- package/dist/web3-bs.esm.js.map +1 -1
- package/dist/web3-bs.umd.js +551 -45
- package/dist/web3-bs.umd.js.map +1 -1
- package/package.json +3 -2
package/dist/web3-bs.umd.js
CHANGED
|
@@ -199,12 +199,12 @@
|
|
|
199
199
|
return () => provider.removeListener?.('chainChanged', handler);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
function normalizeBaseUrl$
|
|
202
|
+
function normalizeBaseUrl$2(baseUrl) {
|
|
203
203
|
return baseUrl.replace(/\/+$/, '');
|
|
204
204
|
}
|
|
205
|
-
function joinUrl$
|
|
205
|
+
function joinUrl$2(baseUrl, path) {
|
|
206
206
|
const trimmed = path.replace(/^\/+/, '');
|
|
207
|
-
return `${normalizeBaseUrl$
|
|
207
|
+
return `${normalizeBaseUrl$2(baseUrl)}/${trimmed}`;
|
|
208
208
|
}
|
|
209
209
|
const DEFAULT_TOKEN_KEY = 'authToken';
|
|
210
210
|
let cachedAccessToken = null;
|
|
@@ -215,10 +215,10 @@
|
|
|
215
215
|
function shouldStoreToken(options) {
|
|
216
216
|
return options?.storeToken !== false;
|
|
217
217
|
}
|
|
218
|
-
function resolveFetcher(options) {
|
|
218
|
+
function resolveFetcher$1(options) {
|
|
219
219
|
return options?.fetcher || fetch;
|
|
220
220
|
}
|
|
221
|
-
function resolveCredentials(options) {
|
|
221
|
+
function resolveCredentials$1(options) {
|
|
222
222
|
return options?.credentials ?? 'include';
|
|
223
223
|
}
|
|
224
224
|
function readStoredToken(options) {
|
|
@@ -358,11 +358,11 @@
|
|
|
358
358
|
async function loginWithChallenge(options = {}) {
|
|
359
359
|
const provider = options.provider || (await requireProvider());
|
|
360
360
|
const address = await resolveAddress$1(provider, options.address);
|
|
361
|
-
const fetcher = resolveFetcher(options);
|
|
362
|
-
const credentials = resolveCredentials(options);
|
|
361
|
+
const fetcher = resolveFetcher$1(options);
|
|
362
|
+
const credentials = resolveCredentials$1(options);
|
|
363
363
|
const baseUrl = options.baseUrl || '/api/v1/public/auth';
|
|
364
|
-
const challengeUrl = joinUrl$
|
|
365
|
-
const verifyUrl = joinUrl$
|
|
364
|
+
const challengeUrl = joinUrl$2(baseUrl, options.challengePath || 'challenge');
|
|
365
|
+
const verifyUrl = joinUrl$2(baseUrl, options.verifyPath || 'verify');
|
|
366
366
|
const challengeBody = {
|
|
367
367
|
address,
|
|
368
368
|
};
|
|
@@ -426,10 +426,10 @@
|
|
|
426
426
|
return refreshInFlight;
|
|
427
427
|
}
|
|
428
428
|
const task = (async () => {
|
|
429
|
-
const fetcher = resolveFetcher(options);
|
|
430
|
-
const credentials = resolveCredentials(options);
|
|
429
|
+
const fetcher = resolveFetcher$1(options);
|
|
430
|
+
const credentials = resolveCredentials$1(options);
|
|
431
431
|
const baseUrl = options.baseUrl || '/api/v1/public/auth';
|
|
432
|
-
const refreshUrl = joinUrl$
|
|
432
|
+
const refreshUrl = joinUrl$2(baseUrl, options.refreshPath || 'refresh');
|
|
433
433
|
const refreshRes = await fetcher(refreshUrl, {
|
|
434
434
|
method: 'POST',
|
|
435
435
|
headers: {
|
|
@@ -458,10 +458,10 @@
|
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
async function logout(options = {}) {
|
|
461
|
-
const fetcher = resolveFetcher(options);
|
|
462
|
-
const credentials = resolveCredentials(options);
|
|
461
|
+
const fetcher = resolveFetcher$1(options);
|
|
462
|
+
const credentials = resolveCredentials$1(options);
|
|
463
463
|
const baseUrl = options.baseUrl || '/api/v1/public/auth';
|
|
464
|
-
const logoutUrl = joinUrl$
|
|
464
|
+
const logoutUrl = joinUrl$2(baseUrl, options.logoutPath || 'logout');
|
|
465
465
|
const logoutRes = await fetcher(logoutUrl, {
|
|
466
466
|
method: 'POST',
|
|
467
467
|
headers: {
|
|
@@ -484,8 +484,8 @@
|
|
|
484
484
|
return { response: payload };
|
|
485
485
|
}
|
|
486
486
|
async function authFetch(input, init = {}, options = {}) {
|
|
487
|
-
const fetcher = resolveFetcher(options);
|
|
488
|
-
const credentials = resolveCredentials(options);
|
|
487
|
+
const fetcher = resolveFetcher$1(options);
|
|
488
|
+
const credentials = resolveCredentials$1(options);
|
|
489
489
|
const retryOnUnauthorized = options.retryOnUnauthorized !== false;
|
|
490
490
|
const performRequest = async (tokenOverride) => {
|
|
491
491
|
const headers = new Headers(init.headers || {});
|
|
@@ -517,6 +517,8 @@
|
|
|
517
517
|
const DEFAULT_UCAN_TTL = 5 * 60 * 1000;
|
|
518
518
|
const DB_NAME = 'yeying-web3';
|
|
519
519
|
const DB_STORE = 'ucan-sessions';
|
|
520
|
+
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
521
|
+
const DID_KEY_ED25519_MULTICODEC = new Uint8Array([0xed, 0x01]);
|
|
520
522
|
const textEncoder = new TextEncoder();
|
|
521
523
|
function toBase64Url(data) {
|
|
522
524
|
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
@@ -526,6 +528,70 @@
|
|
|
526
528
|
}
|
|
527
529
|
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
528
530
|
}
|
|
531
|
+
function normalizeActionExpression(raw) {
|
|
532
|
+
const normalized = String(raw || '').trim().toLowerCase().replace(/\|/g, ',');
|
|
533
|
+
if (!normalized)
|
|
534
|
+
return '';
|
|
535
|
+
const parts = normalized
|
|
536
|
+
.split(',')
|
|
537
|
+
.map(part => part.trim())
|
|
538
|
+
.filter(Boolean);
|
|
539
|
+
if (!parts.length)
|
|
540
|
+
return '';
|
|
541
|
+
return Array.from(new Set(parts)).join(',');
|
|
542
|
+
}
|
|
543
|
+
function getCapabilityResource(cap) {
|
|
544
|
+
if (!cap || typeof cap !== 'object')
|
|
545
|
+
return '';
|
|
546
|
+
const withValue = typeof cap.with === 'string' ? cap.with.trim() : '';
|
|
547
|
+
if (withValue)
|
|
548
|
+
return withValue;
|
|
549
|
+
return typeof cap.resource === 'string' ? cap.resource.trim() : '';
|
|
550
|
+
}
|
|
551
|
+
function getCapabilityAction(cap) {
|
|
552
|
+
if (!cap || typeof cap !== 'object')
|
|
553
|
+
return '';
|
|
554
|
+
const canValue = typeof cap.can === 'string' ? cap.can.trim() : '';
|
|
555
|
+
if (canValue)
|
|
556
|
+
return normalizeActionExpression(canValue);
|
|
557
|
+
const actionValue = typeof cap.action === 'string' ? cap.action.trim() : '';
|
|
558
|
+
return normalizeActionExpression(actionValue);
|
|
559
|
+
}
|
|
560
|
+
function normalizeUcanCapability(cap, options = {}) {
|
|
561
|
+
const includeLegacyAliases = options.includeLegacyAliases !== false;
|
|
562
|
+
const resource = getCapabilityResource(cap);
|
|
563
|
+
const action = getCapabilityAction(cap);
|
|
564
|
+
if (!resource || !action)
|
|
565
|
+
return null;
|
|
566
|
+
const normalized = {
|
|
567
|
+
with: resource,
|
|
568
|
+
can: action,
|
|
569
|
+
};
|
|
570
|
+
if (includeLegacyAliases) {
|
|
571
|
+
normalized.resource = resource;
|
|
572
|
+
normalized.action = action;
|
|
573
|
+
}
|
|
574
|
+
if (cap && Object.prototype.hasOwnProperty.call(cap, 'nb')) {
|
|
575
|
+
normalized.nb = cap.nb;
|
|
576
|
+
}
|
|
577
|
+
return normalized;
|
|
578
|
+
}
|
|
579
|
+
function normalizeUcanCapabilities(caps, options = {}) {
|
|
580
|
+
const includeLegacyAliases = options.includeLegacyAliases !== false;
|
|
581
|
+
const seen = new Set();
|
|
582
|
+
const result = [];
|
|
583
|
+
for (const cap of caps || []) {
|
|
584
|
+
const normalized = normalizeUcanCapability(cap, { includeLegacyAliases });
|
|
585
|
+
if (!normalized)
|
|
586
|
+
continue;
|
|
587
|
+
const key = `${normalized.with}|${normalized.can}`;
|
|
588
|
+
if (seen.has(key))
|
|
589
|
+
continue;
|
|
590
|
+
seen.add(key);
|
|
591
|
+
result.push(normalized);
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
529
595
|
function encodeJson(value) {
|
|
530
596
|
return toBase64Url(textEncoder.encode(JSON.stringify(value)));
|
|
531
597
|
}
|
|
@@ -539,6 +605,123 @@
|
|
|
539
605
|
function normalizeExpiry(exp, fallbackMs) {
|
|
540
606
|
return Date.now() + fallbackMs;
|
|
541
607
|
}
|
|
608
|
+
function isSessionExpired(expiresAt, nowMs = Date.now()) {
|
|
609
|
+
return typeof expiresAt === 'number' && nowMs >= expiresAt;
|
|
610
|
+
}
|
|
611
|
+
function encodeBase58(bytes) {
|
|
612
|
+
if (bytes.length === 0)
|
|
613
|
+
return '';
|
|
614
|
+
let value = 0n;
|
|
615
|
+
for (const byte of bytes) {
|
|
616
|
+
value = (value << 8n) + BigInt(byte);
|
|
617
|
+
}
|
|
618
|
+
let encoded = '';
|
|
619
|
+
while (value > 0n) {
|
|
620
|
+
const mod = Number(value % 58n);
|
|
621
|
+
encoded = `${BASE58_ALPHABET[mod]}${encoded}`;
|
|
622
|
+
value /= 58n;
|
|
623
|
+
}
|
|
624
|
+
let leadingZeroCount = 0;
|
|
625
|
+
while (leadingZeroCount < bytes.length && bytes[leadingZeroCount] === 0) {
|
|
626
|
+
leadingZeroCount += 1;
|
|
627
|
+
}
|
|
628
|
+
if (leadingZeroCount > 0) {
|
|
629
|
+
encoded = `${'1'.repeat(leadingZeroCount)}${encoded}`;
|
|
630
|
+
}
|
|
631
|
+
return encoded || '1';
|
|
632
|
+
}
|
|
633
|
+
function ensureWebCrypto() {
|
|
634
|
+
if (typeof crypto === 'undefined' || !crypto.subtle) {
|
|
635
|
+
throw new Error('WebCrypto not available for UCAN session');
|
|
636
|
+
}
|
|
637
|
+
return crypto;
|
|
638
|
+
}
|
|
639
|
+
function parseSessionId(options) {
|
|
640
|
+
return options.id || DEFAULT_SESSION_ID;
|
|
641
|
+
}
|
|
642
|
+
function isLocalSessionRecord(record) {
|
|
643
|
+
return Boolean(record?.source === 'local' || record?.privateKeyJwk);
|
|
644
|
+
}
|
|
645
|
+
async function buildDidKey(publicKey) {
|
|
646
|
+
const webCrypto = ensureWebCrypto();
|
|
647
|
+
const raw = new Uint8Array(await webCrypto.subtle.exportKey('raw', publicKey));
|
|
648
|
+
const prefixed = new Uint8Array(DID_KEY_ED25519_MULTICODEC.length + raw.length);
|
|
649
|
+
prefixed.set(DID_KEY_ED25519_MULTICODEC, 0);
|
|
650
|
+
prefixed.set(raw, DID_KEY_ED25519_MULTICODEC.length);
|
|
651
|
+
return `did:key:z${encodeBase58(prefixed)}`;
|
|
652
|
+
}
|
|
653
|
+
async function importLocalPrivateKey(privateKeyJwk) {
|
|
654
|
+
const webCrypto = ensureWebCrypto();
|
|
655
|
+
return await webCrypto.subtle.importKey('jwk', privateKeyJwk, 'Ed25519', true, ['sign']);
|
|
656
|
+
}
|
|
657
|
+
async function loadLocalSessionFromRecord(id, record) {
|
|
658
|
+
if (!record || !isLocalSessionRecord(record) || !record.privateKeyJwk) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
if (isSessionExpired(record.expiresAt)) {
|
|
662
|
+
await deleteSessionRecord(id);
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
const privateKey = await importLocalPrivateKey(record.privateKeyJwk);
|
|
667
|
+
return {
|
|
668
|
+
id: record.id || id,
|
|
669
|
+
did: record.did,
|
|
670
|
+
createdAt: record.createdAt,
|
|
671
|
+
expiresAt: record.expiresAt,
|
|
672
|
+
source: 'local',
|
|
673
|
+
privateKey,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
catch {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function shouldKeepRootForSession(root, did, nowMs) {
|
|
681
|
+
if (!root)
|
|
682
|
+
return false;
|
|
683
|
+
if (root.aud && root.aud !== did)
|
|
684
|
+
return false;
|
|
685
|
+
if (isRootExpired(root, nowMs))
|
|
686
|
+
return false;
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
async function createLocalSession(options, record) {
|
|
690
|
+
const webCrypto = ensureWebCrypto();
|
|
691
|
+
const sessionId = parseSessionId(options);
|
|
692
|
+
if (!options.forceNew) {
|
|
693
|
+
const existing = await loadLocalSessionFromRecord(sessionId, record);
|
|
694
|
+
if (existing)
|
|
695
|
+
return existing;
|
|
696
|
+
}
|
|
697
|
+
const pair = (await webCrypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']));
|
|
698
|
+
const [privateKeyJwk, publicKeyJwk, did] = await Promise.all([
|
|
699
|
+
webCrypto.subtle.exportKey('jwk', pair.privateKey),
|
|
700
|
+
webCrypto.subtle.exportKey('jwk', pair.publicKey),
|
|
701
|
+
buildDidKey(pair.publicKey),
|
|
702
|
+
]);
|
|
703
|
+
const createdAt = Date.now();
|
|
704
|
+
const expiresAt = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
|
|
705
|
+
const root = shouldKeepRootForSession(record?.root, did, createdAt) ? record?.root : undefined;
|
|
706
|
+
await writeSessionRecord({
|
|
707
|
+
id: sessionId,
|
|
708
|
+
did,
|
|
709
|
+
createdAt,
|
|
710
|
+
expiresAt,
|
|
711
|
+
source: 'local',
|
|
712
|
+
privateKeyJwk,
|
|
713
|
+
publicKeyJwk,
|
|
714
|
+
root,
|
|
715
|
+
});
|
|
716
|
+
return {
|
|
717
|
+
id: sessionId,
|
|
718
|
+
did,
|
|
719
|
+
createdAt,
|
|
720
|
+
expiresAt,
|
|
721
|
+
source: 'local',
|
|
722
|
+
privateKey: pair.privateKey,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
542
725
|
function openDb() {
|
|
543
726
|
if (typeof indexedDB === 'undefined') {
|
|
544
727
|
return Promise.reject(new Error('IndexedDB not available'));
|
|
@@ -601,17 +784,19 @@
|
|
|
601
784
|
}
|
|
602
785
|
}
|
|
603
786
|
async function getUcanSession(id = DEFAULT_SESSION_ID, provider) {
|
|
787
|
+
const record = await readSessionRecord(id);
|
|
604
788
|
const walletProvider = provider || (typeof window !== 'undefined'
|
|
605
789
|
? await getProvider({ preferYeYing: true })
|
|
606
790
|
: null);
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
791
|
+
if (walletProvider) {
|
|
792
|
+
try {
|
|
793
|
+
return await requestWalletUcanSession(walletProvider, { id });
|
|
794
|
+
}
|
|
795
|
+
catch {
|
|
796
|
+
return await loadLocalSessionFromRecord(id, record);
|
|
797
|
+
}
|
|
614
798
|
}
|
|
799
|
+
return await loadLocalSessionFromRecord(id, record);
|
|
615
800
|
}
|
|
616
801
|
async function requestWalletUcanSession(provider, options) {
|
|
617
802
|
const sessionId = options.id || DEFAULT_SESSION_ID;
|
|
@@ -636,6 +821,7 @@
|
|
|
636
821
|
did: result.did,
|
|
637
822
|
createdAt,
|
|
638
823
|
expiresAt,
|
|
824
|
+
source: 'wallet',
|
|
639
825
|
root: existing?.root,
|
|
640
826
|
};
|
|
641
827
|
if (nextRecord.root && nextRecord.root.aud && nextRecord.root.aud !== nextRecord.did) {
|
|
@@ -647,6 +833,7 @@
|
|
|
647
833
|
did: result.did,
|
|
648
834
|
createdAt,
|
|
649
835
|
expiresAt,
|
|
836
|
+
source: 'wallet',
|
|
650
837
|
signer: async (signingInput, payload) => {
|
|
651
838
|
const signatureResult = (await provider.request({
|
|
652
839
|
method: 'yeying_ucan_sign',
|
|
@@ -669,13 +856,20 @@
|
|
|
669
856
|
};
|
|
670
857
|
}
|
|
671
858
|
async function createUcanSession(options = {}) {
|
|
859
|
+
const sessionId = parseSessionId(options);
|
|
860
|
+
const record = await readSessionRecord(sessionId);
|
|
672
861
|
const provider = options.provider || (typeof window !== 'undefined'
|
|
673
862
|
? await getProvider({ preferYeYing: true })
|
|
674
863
|
: null);
|
|
675
|
-
if (
|
|
676
|
-
|
|
864
|
+
if (provider) {
|
|
865
|
+
try {
|
|
866
|
+
return await requestWalletUcanSession(provider, { ...options, id: sessionId });
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
// fallback to local ed25519 session
|
|
870
|
+
}
|
|
677
871
|
}
|
|
678
|
-
return await
|
|
872
|
+
return await createLocalSession({ ...options, id: sessionId }, record);
|
|
679
873
|
}
|
|
680
874
|
async function clearUcanSession(id = DEFAULT_SESSION_ID) {
|
|
681
875
|
await deleteSessionRecord(id);
|
|
@@ -686,6 +880,7 @@
|
|
|
686
880
|
const expiresAt = record?.expiresAt ?? null;
|
|
687
881
|
const did = record?.did || root.aud;
|
|
688
882
|
const nextRecord = {
|
|
883
|
+
...(record || {}),
|
|
689
884
|
id,
|
|
690
885
|
did,
|
|
691
886
|
createdAt,
|
|
@@ -699,7 +894,9 @@
|
|
|
699
894
|
return record?.root || null;
|
|
700
895
|
}
|
|
701
896
|
function capsEqual(a, b) {
|
|
702
|
-
|
|
897
|
+
const left = normalizeUcanCapabilities(a, { includeLegacyAliases: false });
|
|
898
|
+
const right = normalizeUcanCapabilities(b, { includeLegacyAliases: false });
|
|
899
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
703
900
|
}
|
|
704
901
|
function isRootExpired(root, nowMs) {
|
|
705
902
|
return Boolean(root.exp && nowMs > root.exp);
|
|
@@ -774,9 +971,13 @@
|
|
|
774
971
|
const nonce = options.nonce || randomNonce(8);
|
|
775
972
|
const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_SESSION_TTL);
|
|
776
973
|
const nbf = options.notBeforeMs;
|
|
974
|
+
const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
|
|
975
|
+
if (!normalizedCapabilities.length) {
|
|
976
|
+
throw new Error('Missing UCAN capabilities');
|
|
977
|
+
}
|
|
777
978
|
const statementPayload = {
|
|
778
979
|
aud: session.did,
|
|
779
|
-
cap:
|
|
980
|
+
cap: normalizedCapabilities,
|
|
780
981
|
exp,
|
|
781
982
|
};
|
|
782
983
|
if (nbf)
|
|
@@ -799,7 +1000,7 @@
|
|
|
799
1000
|
type: 'siwe',
|
|
800
1001
|
iss: `did:pkh:eth:${address.toLowerCase()}`,
|
|
801
1002
|
aud: session.did,
|
|
802
|
-
cap:
|
|
1003
|
+
cap: normalizedCapabilities,
|
|
803
1004
|
exp,
|
|
804
1005
|
nbf,
|
|
805
1006
|
siwe: {
|
|
@@ -848,11 +1049,15 @@
|
|
|
848
1049
|
}));
|
|
849
1050
|
if (!issuer)
|
|
850
1051
|
throw new Error('Missing UCAN session key');
|
|
1052
|
+
const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
|
|
1053
|
+
if (!normalizedCapabilities.length) {
|
|
1054
|
+
throw new Error('Missing UCAN capabilities');
|
|
1055
|
+
}
|
|
851
1056
|
const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
|
|
852
1057
|
const payload = {
|
|
853
1058
|
iss: issuer.did,
|
|
854
1059
|
aud: options.audience,
|
|
855
|
-
cap:
|
|
1060
|
+
cap: normalizedCapabilities,
|
|
856
1061
|
exp,
|
|
857
1062
|
nbf: options.notBeforeMs,
|
|
858
1063
|
prf: await resolveProofs(options, issuer),
|
|
@@ -866,11 +1071,15 @@
|
|
|
866
1071
|
}));
|
|
867
1072
|
if (!issuer)
|
|
868
1073
|
throw new Error('Missing UCAN session key');
|
|
1074
|
+
const normalizedCapabilities = normalizeUcanCapabilities(options.capabilities);
|
|
1075
|
+
if (!normalizedCapabilities.length) {
|
|
1076
|
+
throw new Error('Missing UCAN capabilities');
|
|
1077
|
+
}
|
|
869
1078
|
const exp = normalizeExpiry(undefined, options.expiresInMs ?? DEFAULT_UCAN_TTL);
|
|
870
1079
|
const payload = {
|
|
871
1080
|
iss: issuer.did,
|
|
872
1081
|
aud: options.audience,
|
|
873
|
-
cap:
|
|
1082
|
+
cap: normalizedCapabilities,
|
|
874
1083
|
exp,
|
|
875
1084
|
nbf: options.notBeforeMs,
|
|
876
1085
|
prf: await resolveProofs(options, issuer),
|
|
@@ -903,6 +1112,291 @@
|
|
|
903
1112
|
});
|
|
904
1113
|
}
|
|
905
1114
|
|
|
1115
|
+
const DEFAULT_BASE_URL = '/api/v1/public/auth/central';
|
|
1116
|
+
const DEFAULT_ISSUER_PATH = 'issuer';
|
|
1117
|
+
const DEFAULT_SESSION_PATH = 'session';
|
|
1118
|
+
const DEFAULT_ISSUE_PATH = 'issue';
|
|
1119
|
+
const DEFAULT_SESSION_TOKEN_KEY = 'centralUcanSessionToken';
|
|
1120
|
+
let cachedCentralSessionToken = null;
|
|
1121
|
+
function normalizeBaseUrl$1(baseUrl) {
|
|
1122
|
+
return baseUrl.replace(/\/+$/, '');
|
|
1123
|
+
}
|
|
1124
|
+
function joinUrl$1(baseUrl, path) {
|
|
1125
|
+
const trimmed = path.replace(/^\/+/, '');
|
|
1126
|
+
return `${normalizeBaseUrl$1(baseUrl)}/${trimmed}`;
|
|
1127
|
+
}
|
|
1128
|
+
function resolveBaseUrl(options) {
|
|
1129
|
+
return options?.baseUrl || DEFAULT_BASE_URL;
|
|
1130
|
+
}
|
|
1131
|
+
function resolveFetcher(options) {
|
|
1132
|
+
return options?.fetcher || fetch;
|
|
1133
|
+
}
|
|
1134
|
+
function resolveCredentials(options) {
|
|
1135
|
+
return options?.credentials ?? 'include';
|
|
1136
|
+
}
|
|
1137
|
+
function resolveSessionTokenKey(options) {
|
|
1138
|
+
return options?.sessionTokenStorageKey || DEFAULT_SESSION_TOKEN_KEY;
|
|
1139
|
+
}
|
|
1140
|
+
function shouldStoreSessionToken(options) {
|
|
1141
|
+
return options?.storeSessionToken !== false;
|
|
1142
|
+
}
|
|
1143
|
+
function parseObject(value) {
|
|
1144
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1145
|
+
return {};
|
|
1146
|
+
}
|
|
1147
|
+
return value;
|
|
1148
|
+
}
|
|
1149
|
+
function parseEnvelopeData(payload) {
|
|
1150
|
+
const root = parseObject(payload);
|
|
1151
|
+
if (Object.prototype.hasOwnProperty.call(root, 'data')) {
|
|
1152
|
+
return parseObject(root.data);
|
|
1153
|
+
}
|
|
1154
|
+
return root;
|
|
1155
|
+
}
|
|
1156
|
+
function parseStringField(obj, keys) {
|
|
1157
|
+
for (const key of keys) {
|
|
1158
|
+
const value = obj[key];
|
|
1159
|
+
if (typeof value === 'string') {
|
|
1160
|
+
return value;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return undefined;
|
|
1164
|
+
}
|
|
1165
|
+
function parseNumberField(obj, keys) {
|
|
1166
|
+
for (const key of keys) {
|
|
1167
|
+
const value = obj[key];
|
|
1168
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
1169
|
+
return value;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return undefined;
|
|
1173
|
+
}
|
|
1174
|
+
function parseCapabilitiesField(obj, keys) {
|
|
1175
|
+
for (const key of keys) {
|
|
1176
|
+
const value = obj[key];
|
|
1177
|
+
if (!Array.isArray(value))
|
|
1178
|
+
continue;
|
|
1179
|
+
const caps = value
|
|
1180
|
+
.filter(item => item && typeof item === 'object')
|
|
1181
|
+
.map(item => normalizeUcanCapability(item))
|
|
1182
|
+
.filter((cap) => Boolean(cap));
|
|
1183
|
+
return caps;
|
|
1184
|
+
}
|
|
1185
|
+
return undefined;
|
|
1186
|
+
}
|
|
1187
|
+
function readStoredSessionToken(options) {
|
|
1188
|
+
if (!shouldStoreSessionToken(options))
|
|
1189
|
+
return null;
|
|
1190
|
+
if (typeof localStorage === 'undefined')
|
|
1191
|
+
return null;
|
|
1192
|
+
const key = resolveSessionTokenKey(options);
|
|
1193
|
+
return localStorage.getItem(key);
|
|
1194
|
+
}
|
|
1195
|
+
function persistSessionToken(token, options) {
|
|
1196
|
+
cachedCentralSessionToken = token;
|
|
1197
|
+
if (!shouldStoreSessionToken(options))
|
|
1198
|
+
return;
|
|
1199
|
+
if (typeof localStorage === 'undefined')
|
|
1200
|
+
return;
|
|
1201
|
+
const key = resolveSessionTokenKey(options);
|
|
1202
|
+
if (!token) {
|
|
1203
|
+
localStorage.removeItem(key);
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
localStorage.setItem(key, token);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
async function parseJsonBody(response) {
|
|
1210
|
+
const text = await response.text();
|
|
1211
|
+
if (!text)
|
|
1212
|
+
return null;
|
|
1213
|
+
try {
|
|
1214
|
+
return JSON.parse(text);
|
|
1215
|
+
}
|
|
1216
|
+
catch {
|
|
1217
|
+
return { raw: text };
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
function getCentralSessionToken(options) {
|
|
1221
|
+
if (cachedCentralSessionToken)
|
|
1222
|
+
return cachedCentralSessionToken;
|
|
1223
|
+
const stored = readStoredSessionToken(options);
|
|
1224
|
+
if (stored) {
|
|
1225
|
+
cachedCentralSessionToken = stored;
|
|
1226
|
+
}
|
|
1227
|
+
return stored;
|
|
1228
|
+
}
|
|
1229
|
+
function setCentralSessionToken(token, options) {
|
|
1230
|
+
persistSessionToken(token, options);
|
|
1231
|
+
}
|
|
1232
|
+
function clearCentralSessionToken(options) {
|
|
1233
|
+
cachedCentralSessionToken = null;
|
|
1234
|
+
if (typeof localStorage === 'undefined')
|
|
1235
|
+
return;
|
|
1236
|
+
const key = resolveSessionTokenKey(options);
|
|
1237
|
+
localStorage.removeItem(key);
|
|
1238
|
+
}
|
|
1239
|
+
async function getCentralIssuerInfo(options = {}) {
|
|
1240
|
+
const fetcher = resolveFetcher(options);
|
|
1241
|
+
const credentials = resolveCredentials(options);
|
|
1242
|
+
const url = joinUrl$1(resolveBaseUrl(options), options.issuerPath || DEFAULT_ISSUER_PATH);
|
|
1243
|
+
const response = await fetcher(url, {
|
|
1244
|
+
method: 'GET',
|
|
1245
|
+
headers: {
|
|
1246
|
+
accept: 'application/json',
|
|
1247
|
+
},
|
|
1248
|
+
credentials,
|
|
1249
|
+
});
|
|
1250
|
+
const payload = await parseJsonBody(response);
|
|
1251
|
+
if (!response.ok) {
|
|
1252
|
+
throw new Error(`Central issuer request failed: ${response.status} ${JSON.stringify(payload)}`);
|
|
1253
|
+
}
|
|
1254
|
+
const data = parseEnvelopeData(payload);
|
|
1255
|
+
return {
|
|
1256
|
+
enabled: typeof data.enabled === 'boolean' ? data.enabled : undefined,
|
|
1257
|
+
issuerDid: parseStringField(data, ['issuerDid']),
|
|
1258
|
+
defaultAudience: parseStringField(data, ['defaultAudience']),
|
|
1259
|
+
defaultCapabilities: parseCapabilitiesField(data, ['defaultCapabilities']),
|
|
1260
|
+
response: payload,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
async function createCentralSession(options) {
|
|
1264
|
+
const subject = String(options?.subject || '').trim();
|
|
1265
|
+
if (!subject) {
|
|
1266
|
+
throw new Error('Missing subject');
|
|
1267
|
+
}
|
|
1268
|
+
const fetcher = resolveFetcher(options);
|
|
1269
|
+
const credentials = resolveCredentials(options);
|
|
1270
|
+
const url = joinUrl$1(resolveBaseUrl(options), options.sessionPath || DEFAULT_SESSION_PATH);
|
|
1271
|
+
const response = await fetcher(url, {
|
|
1272
|
+
method: 'POST',
|
|
1273
|
+
headers: {
|
|
1274
|
+
'Content-Type': 'application/json',
|
|
1275
|
+
accept: 'application/json',
|
|
1276
|
+
},
|
|
1277
|
+
credentials,
|
|
1278
|
+
body: JSON.stringify({
|
|
1279
|
+
subject,
|
|
1280
|
+
sessionTtlMs: options.sessionTtlMs,
|
|
1281
|
+
}),
|
|
1282
|
+
});
|
|
1283
|
+
const payload = await parseJsonBody(response);
|
|
1284
|
+
if (!response.ok) {
|
|
1285
|
+
throw new Error(`Central session request failed: ${response.status} ${JSON.stringify(payload)}`);
|
|
1286
|
+
}
|
|
1287
|
+
const data = parseEnvelopeData(payload);
|
|
1288
|
+
const sessionToken = parseStringField(data, ['sessionToken']);
|
|
1289
|
+
if (!sessionToken) {
|
|
1290
|
+
throw new Error('Central session response missing sessionToken');
|
|
1291
|
+
}
|
|
1292
|
+
persistSessionToken(sessionToken, options);
|
|
1293
|
+
return {
|
|
1294
|
+
subject: parseStringField(data, ['subject']) || subject,
|
|
1295
|
+
sessionToken,
|
|
1296
|
+
expiresAt: parseNumberField(data, ['expiresAt']),
|
|
1297
|
+
issuerDid: parseStringField(data, ['issuerDid']),
|
|
1298
|
+
response: payload,
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
async function issueCentralUcan(options = {}) {
|
|
1302
|
+
const sessionToken = options.sessionToken || getCentralSessionToken(options);
|
|
1303
|
+
if (!sessionToken) {
|
|
1304
|
+
throw new Error('Missing central session token');
|
|
1305
|
+
}
|
|
1306
|
+
const normalizedCapabilities = options.capabilities
|
|
1307
|
+
? normalizeUcanCapabilities(options.capabilities)
|
|
1308
|
+
: undefined;
|
|
1309
|
+
const fetcher = resolveFetcher(options);
|
|
1310
|
+
const credentials = resolveCredentials(options);
|
|
1311
|
+
const url = joinUrl$1(resolveBaseUrl(options), options.issuePath || DEFAULT_ISSUE_PATH);
|
|
1312
|
+
const response = await fetcher(url, {
|
|
1313
|
+
method: 'POST',
|
|
1314
|
+
headers: {
|
|
1315
|
+
'Content-Type': 'application/json',
|
|
1316
|
+
accept: 'application/json',
|
|
1317
|
+
Authorization: `Bearer ${sessionToken}`,
|
|
1318
|
+
},
|
|
1319
|
+
credentials,
|
|
1320
|
+
body: JSON.stringify({
|
|
1321
|
+
audience: options.audience,
|
|
1322
|
+
capabilities: normalizedCapabilities,
|
|
1323
|
+
expiresInMs: options.expiresInMs,
|
|
1324
|
+
ttlMs: options.ttlMs,
|
|
1325
|
+
}),
|
|
1326
|
+
});
|
|
1327
|
+
const payload = await parseJsonBody(response);
|
|
1328
|
+
if (!response.ok) {
|
|
1329
|
+
throw new Error(`Central UCAN issue failed: ${response.status} ${JSON.stringify(payload)}`);
|
|
1330
|
+
}
|
|
1331
|
+
const data = parseEnvelopeData(payload);
|
|
1332
|
+
const ucan = parseStringField(data, ['ucan']);
|
|
1333
|
+
if (!ucan) {
|
|
1334
|
+
throw new Error('Central UCAN response missing ucan');
|
|
1335
|
+
}
|
|
1336
|
+
return {
|
|
1337
|
+
ucan,
|
|
1338
|
+
issuerDid: parseStringField(data, ['issuerDid']),
|
|
1339
|
+
subject: parseStringField(data, ['subject']),
|
|
1340
|
+
audience: parseStringField(data, ['audience']),
|
|
1341
|
+
capabilities: parseCapabilitiesField(data, ['capabilities']),
|
|
1342
|
+
exp: parseNumberField(data, ['exp']),
|
|
1343
|
+
nbf: parseNumberField(data, ['nbf']),
|
|
1344
|
+
iat: parseNumberField(data, ['iat']),
|
|
1345
|
+
response: payload,
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
async function createAndIssueCentralUcan(options) {
|
|
1349
|
+
const session = await createCentralSession({
|
|
1350
|
+
...options,
|
|
1351
|
+
subject: options.subject,
|
|
1352
|
+
sessionTtlMs: options.sessionTtlMs,
|
|
1353
|
+
});
|
|
1354
|
+
const issue = await issueCentralUcan({
|
|
1355
|
+
...options,
|
|
1356
|
+
sessionToken: session.sessionToken,
|
|
1357
|
+
audience: options.audience,
|
|
1358
|
+
capabilities: options.capabilities,
|
|
1359
|
+
expiresInMs: options.expiresInMs,
|
|
1360
|
+
ttlMs: options.ttlMs,
|
|
1361
|
+
});
|
|
1362
|
+
return { session, issue };
|
|
1363
|
+
}
|
|
1364
|
+
async function authCentralUcanFetch(input, init = {}, options = {}) {
|
|
1365
|
+
const fetcher = resolveFetcher(options);
|
|
1366
|
+
const credentials = resolveCredentials(options);
|
|
1367
|
+
let token = options.ucan || null;
|
|
1368
|
+
if (!token) {
|
|
1369
|
+
let sessionToken = options.sessionToken || getCentralSessionToken(options);
|
|
1370
|
+
if (!sessionToken) {
|
|
1371
|
+
if (!options.subject) {
|
|
1372
|
+
throw new Error('Missing central session token or subject');
|
|
1373
|
+
}
|
|
1374
|
+
const session = await createCentralSession({
|
|
1375
|
+
...options,
|
|
1376
|
+
subject: options.subject,
|
|
1377
|
+
sessionTtlMs: options.sessionTtlMs,
|
|
1378
|
+
});
|
|
1379
|
+
sessionToken = session.sessionToken;
|
|
1380
|
+
}
|
|
1381
|
+
const issued = await issueCentralUcan({
|
|
1382
|
+
...options,
|
|
1383
|
+
sessionToken,
|
|
1384
|
+
audience: options.audience,
|
|
1385
|
+
capabilities: options.capabilities,
|
|
1386
|
+
expiresInMs: options.expiresInMs,
|
|
1387
|
+
ttlMs: options.ttlMs,
|
|
1388
|
+
});
|
|
1389
|
+
token = issued.ucan;
|
|
1390
|
+
}
|
|
1391
|
+
const headers = new Headers(init.headers || {});
|
|
1392
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
1393
|
+
return fetcher(input, {
|
|
1394
|
+
...init,
|
|
1395
|
+
headers,
|
|
1396
|
+
credentials,
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
|
|
906
1400
|
function normalizeBaseUrl(baseUrl) {
|
|
907
1401
|
return baseUrl.replace(/\/+$/, '');
|
|
908
1402
|
}
|
|
@@ -1139,25 +1633,19 @@
|
|
|
1139
1633
|
if (!options.appId)
|
|
1140
1634
|
return null;
|
|
1141
1635
|
const action = normalizeAction(options.appAction) || DEFAULT_APP_ACTION;
|
|
1636
|
+
const resource = `app:all:${sanitizeAppId(options.appId)}`;
|
|
1142
1637
|
return {
|
|
1143
|
-
|
|
1638
|
+
with: resource,
|
|
1639
|
+
can: action,
|
|
1640
|
+
resource,
|
|
1144
1641
|
action,
|
|
1145
1642
|
};
|
|
1146
1643
|
}
|
|
1147
1644
|
function hasAppCapability(caps) {
|
|
1148
|
-
return (caps || []).some(cap =>
|
|
1645
|
+
return (caps || []).some(cap => getCapabilityResource(cap).startsWith('app:'));
|
|
1149
1646
|
}
|
|
1150
1647
|
function dedupeCapabilities(caps) {
|
|
1151
|
-
|
|
1152
|
-
return (caps || []).filter(cap => {
|
|
1153
|
-
if (!cap || !cap.resource || !cap.action)
|
|
1154
|
-
return false;
|
|
1155
|
-
const key = `${cap.resource}|${cap.action}`;
|
|
1156
|
-
if (seen.has(key))
|
|
1157
|
-
return false;
|
|
1158
|
-
seen.add(key);
|
|
1159
|
-
return true;
|
|
1160
|
-
});
|
|
1648
|
+
return normalizeUcanCapabilities(caps);
|
|
1161
1649
|
}
|
|
1162
1650
|
function ensureAppCapability(caps, options) {
|
|
1163
1651
|
const appCap = buildAppCapability(options);
|
|
@@ -1177,7 +1665,13 @@
|
|
|
1177
1665
|
return undefined;
|
|
1178
1666
|
}
|
|
1179
1667
|
function buildCapsKey(caps) {
|
|
1180
|
-
|
|
1668
|
+
const canonical = (caps || [])
|
|
1669
|
+
.map(cap => ({
|
|
1670
|
+
with: getCapabilityResource(cap),
|
|
1671
|
+
can: getCapabilityAction(cap),
|
|
1672
|
+
}))
|
|
1673
|
+
.filter(cap => Boolean(cap.with && cap.can));
|
|
1674
|
+
return JSON.stringify(canonical);
|
|
1181
1675
|
}
|
|
1182
1676
|
function buildTokenCacheKey(issuer, audience, caps) {
|
|
1183
1677
|
return `${issuer.did}|${audience}|${buildCapsKey(caps)}`;
|
|
@@ -1355,10 +1849,14 @@
|
|
|
1355
1849
|
}
|
|
1356
1850
|
|
|
1357
1851
|
exports.WebDavClient = WebDavClient;
|
|
1852
|
+
exports.authCentralUcanFetch = authCentralUcanFetch;
|
|
1358
1853
|
exports.authFetch = authFetch;
|
|
1359
1854
|
exports.authUcanFetch = authUcanFetch;
|
|
1360
1855
|
exports.clearAccessToken = clearAccessToken;
|
|
1856
|
+
exports.clearCentralSessionToken = clearCentralSessionToken;
|
|
1361
1857
|
exports.clearUcanSession = clearUcanSession;
|
|
1858
|
+
exports.createAndIssueCentralUcan = createAndIssueCentralUcan;
|
|
1859
|
+
exports.createCentralSession = createCentralSession;
|
|
1362
1860
|
exports.createDelegationUcan = createDelegationUcan;
|
|
1363
1861
|
exports.createInvocationUcan = createInvocationUcan;
|
|
1364
1862
|
exports.createRootUcan = createRootUcan;
|
|
@@ -1367,6 +1865,10 @@
|
|
|
1367
1865
|
exports.getAccessToken = getAccessToken;
|
|
1368
1866
|
exports.getAccounts = getAccounts;
|
|
1369
1867
|
exports.getBalance = getBalance;
|
|
1868
|
+
exports.getCapabilityAction = getCapabilityAction;
|
|
1869
|
+
exports.getCapabilityResource = getCapabilityResource;
|
|
1870
|
+
exports.getCentralIssuerInfo = getCentralIssuerInfo;
|
|
1871
|
+
exports.getCentralSessionToken = getCentralSessionToken;
|
|
1370
1872
|
exports.getChainId = getChainId;
|
|
1371
1873
|
exports.getOrCreateUcanRoot = getOrCreateUcanRoot;
|
|
1372
1874
|
exports.getPreferredAccount = getPreferredAccount;
|
|
@@ -1376,14 +1878,18 @@
|
|
|
1376
1878
|
exports.initDappSession = initDappSession;
|
|
1377
1879
|
exports.initWebDavStorage = initWebDavStorage;
|
|
1378
1880
|
exports.isYeYingProvider = isYeYingProvider;
|
|
1881
|
+
exports.issueCentralUcan = issueCentralUcan;
|
|
1379
1882
|
exports.loginWithChallenge = loginWithChallenge;
|
|
1380
1883
|
exports.logout = logout;
|
|
1884
|
+
exports.normalizeUcanCapabilities = normalizeUcanCapabilities;
|
|
1885
|
+
exports.normalizeUcanCapability = normalizeUcanCapability;
|
|
1381
1886
|
exports.onAccountsChanged = onAccountsChanged;
|
|
1382
1887
|
exports.onChainChanged = onChainChanged;
|
|
1383
1888
|
exports.refreshAccessToken = refreshAccessToken;
|
|
1384
1889
|
exports.requestAccounts = requestAccounts;
|
|
1385
1890
|
exports.requireProvider = requireProvider;
|
|
1386
1891
|
exports.setAccessToken = setAccessToken;
|
|
1892
|
+
exports.setCentralSessionToken = setCentralSessionToken;
|
|
1387
1893
|
exports.signMessage = signMessage;
|
|
1388
1894
|
exports.storeUcanRoot = storeUcanRoot;
|
|
1389
1895
|
exports.watchAccounts = watchAccounts;
|