@bsv/wallet-toolbox-client 1.7.17 → 1.7.19
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.
|
@@ -77,7 +77,8 @@ class WalletPermissionsManager {
|
|
|
77
77
|
onBasketAccessRequested: [],
|
|
78
78
|
onCertificateAccessRequested: [],
|
|
79
79
|
onSpendingAuthorizationRequested: [],
|
|
80
|
-
onGroupedPermissionRequested: []
|
|
80
|
+
onGroupedPermissionRequested: [],
|
|
81
|
+
onCounterpartyPermissionRequested: []
|
|
81
82
|
};
|
|
82
83
|
/**
|
|
83
84
|
* We queue parallel requests for the same resource so that only one
|
|
@@ -93,6 +94,9 @@ class WalletPermissionsManager {
|
|
|
93
94
|
/** Cache recently confirmed permissions to avoid repeated lookups. */
|
|
94
95
|
this.permissionCache = new Map();
|
|
95
96
|
this.recentGrants = new Map();
|
|
97
|
+
this.manifestCache = new Map();
|
|
98
|
+
this.manifestFetchInProgress = new Map();
|
|
99
|
+
this.pactEstablishedCache = new Map();
|
|
96
100
|
this.underlying = underlyingWallet;
|
|
97
101
|
this.adminOriginator = this.normalizeOriginator(adminOriginator) || adminOriginator;
|
|
98
102
|
// Default all config options to true unless specified
|
|
@@ -316,9 +320,8 @@ class WalletPermissionsManager {
|
|
|
316
320
|
const { originator, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
317
321
|
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
318
322
|
// --- Validation: Ensure granted permissions are a subset of what was requested ---
|
|
319
|
-
if (params.granted.spendingAuthorization &&
|
|
320
|
-
|
|
321
|
-
throw new Error('Granted spending authorization does not match the original request.');
|
|
323
|
+
if (params.granted.spendingAuthorization && !requestedPermissions.spendingAuthorization) {
|
|
324
|
+
throw new Error('Granted spending authorization was not part of the original request.');
|
|
322
325
|
}
|
|
323
326
|
if ((_a = params.granted.protocolPermissions) === null || _a === void 0 ? void 0 : _a.some(g => { var _a; return !((_a = requestedPermissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.find(r => deepEqual(r, g))); })) {
|
|
324
327
|
throw new Error('Granted protocol permissions are not a subset of the original request.');
|
|
@@ -410,6 +413,74 @@ class WalletPermissionsManager {
|
|
|
410
413
|
}
|
|
411
414
|
this.activeRequests.delete(requestID);
|
|
412
415
|
}
|
|
416
|
+
async dismissGroupedPermission(requestID) {
|
|
417
|
+
const matching = this.activeRequests.get(requestID);
|
|
418
|
+
if (!matching) {
|
|
419
|
+
throw new Error('Request ID not found.');
|
|
420
|
+
}
|
|
421
|
+
for (const p of matching.pending) {
|
|
422
|
+
p.resolve(true);
|
|
423
|
+
}
|
|
424
|
+
this.activeRequests.delete(requestID);
|
|
425
|
+
}
|
|
426
|
+
async grantCounterpartyPermission(params) {
|
|
427
|
+
var _a;
|
|
428
|
+
const matching = this.activeRequests.get(params.requestID);
|
|
429
|
+
if (!matching) {
|
|
430
|
+
throw new Error('Request ID not found.');
|
|
431
|
+
}
|
|
432
|
+
const originalRequest = matching.request;
|
|
433
|
+
const { originator, counterparty, permissions: requestedPermissions, displayOriginator } = originalRequest;
|
|
434
|
+
const originLookupValues = this.buildOriginatorLookupValues(displayOriginator, originator);
|
|
435
|
+
if ((_a = params.granted.protocols) === null || _a === void 0 ? void 0 : _a.some(g => !requestedPermissions.protocols.find(r => deepEqual(r, g)))) {
|
|
436
|
+
throw new Error('Granted protocol permissions are not a subset of the original request.');
|
|
437
|
+
}
|
|
438
|
+
const expiry = params.expiry || 0;
|
|
439
|
+
for (const p of params.granted.protocols || []) {
|
|
440
|
+
const token = await this.findProtocolToken(originator, false, p.protocolID, counterparty, true, originLookupValues);
|
|
441
|
+
if (token) {
|
|
442
|
+
const request = {
|
|
443
|
+
type: 'protocol',
|
|
444
|
+
originator,
|
|
445
|
+
privileged: false,
|
|
446
|
+
protocolID: p.protocolID,
|
|
447
|
+
counterparty,
|
|
448
|
+
reason: p.description
|
|
449
|
+
};
|
|
450
|
+
await this.renewPermissionOnChain(token, request, expiry);
|
|
451
|
+
this.markRecentGrant(request);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
const request = {
|
|
455
|
+
type: 'protocol',
|
|
456
|
+
originator,
|
|
457
|
+
privileged: false,
|
|
458
|
+
protocolID: p.protocolID,
|
|
459
|
+
counterparty,
|
|
460
|
+
reason: p.description
|
|
461
|
+
};
|
|
462
|
+
await this.createPermissionOnChain(request, expiry);
|
|
463
|
+
this.markRecentGrant(request);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
this.markPactEstablished(originator, counterparty);
|
|
467
|
+
for (const p of matching.pending) {
|
|
468
|
+
p.resolve(true);
|
|
469
|
+
}
|
|
470
|
+
this.activeRequests.delete(params.requestID);
|
|
471
|
+
}
|
|
472
|
+
async denyCounterpartyPermission(requestID) {
|
|
473
|
+
const matching = this.activeRequests.get(requestID);
|
|
474
|
+
if (!matching) {
|
|
475
|
+
throw new Error('Request ID not found.');
|
|
476
|
+
}
|
|
477
|
+
const err = new Error('The user has denied the request for permission.');
|
|
478
|
+
err.code = 'ERR_PERMISSION_DENIED';
|
|
479
|
+
for (const p of matching.pending) {
|
|
480
|
+
p.reject(err);
|
|
481
|
+
}
|
|
482
|
+
this.activeRequests.delete(requestID);
|
|
483
|
+
}
|
|
413
484
|
/* ---------------------------------------------------------------------
|
|
414
485
|
* 3) THE "ENSURE" METHODS: CHECK IF PERMISSION EXISTS, OTHERWISE PROMPT
|
|
415
486
|
* --------------------------------------------------------------------- */
|
|
@@ -728,6 +799,469 @@ class WalletPermissionsManager {
|
|
|
728
799
|
usageType: 'generic'
|
|
729
800
|
});
|
|
730
801
|
}
|
|
802
|
+
validateCounterpartyPermissions(raw) {
|
|
803
|
+
if (!raw || !Array.isArray(raw.protocols) || raw.protocols.length === 0)
|
|
804
|
+
return null;
|
|
805
|
+
const validProtocols = raw.protocols.filter((p) => {
|
|
806
|
+
return (Array.isArray(p === null || p === void 0 ? void 0 : p.protocolID) &&
|
|
807
|
+
p.protocolID[0] === 2 &&
|
|
808
|
+
typeof p.protocolID[1] === 'string' &&
|
|
809
|
+
typeof (p === null || p === void 0 ? void 0 : p.description) === 'string');
|
|
810
|
+
});
|
|
811
|
+
if (validProtocols.length === 0)
|
|
812
|
+
return null;
|
|
813
|
+
return {
|
|
814
|
+
description: typeof raw.description === 'string' ? raw.description : undefined,
|
|
815
|
+
protocols: validProtocols
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
async fetchManifestPermissions(originator) {
|
|
819
|
+
const cached = this.manifestCache.get(originator);
|
|
820
|
+
if (cached && Date.now() - cached.fetchedAt < WalletPermissionsManager.MANIFEST_CACHE_TTL_MS) {
|
|
821
|
+
return {
|
|
822
|
+
groupPermissions: cached.groupPermissions,
|
|
823
|
+
counterpartyPermissions: cached.counterpartyPermissions
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
const inProgress = this.manifestFetchInProgress.get(originator);
|
|
827
|
+
if (inProgress) {
|
|
828
|
+
return inProgress;
|
|
829
|
+
}
|
|
830
|
+
const fetchPromise = (async () => {
|
|
831
|
+
var _a, _b;
|
|
832
|
+
try {
|
|
833
|
+
const proto = originator.startsWith('localhost:') ? 'http' : 'https';
|
|
834
|
+
const response = await fetch(`${proto}://${originator}/manifest.json`);
|
|
835
|
+
if (response.ok) {
|
|
836
|
+
const manifest = await response.json();
|
|
837
|
+
const groupPermissions = ((_a = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _a === void 0 ? void 0 : _a.groupPermissions) || null;
|
|
838
|
+
const counterpartyPermissions = this.validateCounterpartyPermissions((_b = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _b === void 0 ? void 0 : _b.counterpartyPermissions);
|
|
839
|
+
this.manifestCache.set(originator, { groupPermissions, counterpartyPermissions, fetchedAt: Date.now() });
|
|
840
|
+
return { groupPermissions, counterpartyPermissions };
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
catch (e) { }
|
|
844
|
+
const result = { groupPermissions: null, counterpartyPermissions: null };
|
|
845
|
+
this.manifestCache.set(originator, { ...result, fetchedAt: Date.now() });
|
|
846
|
+
return result;
|
|
847
|
+
})();
|
|
848
|
+
this.manifestFetchInProgress.set(originator, fetchPromise);
|
|
849
|
+
try {
|
|
850
|
+
return await fetchPromise;
|
|
851
|
+
}
|
|
852
|
+
finally {
|
|
853
|
+
this.manifestFetchInProgress.delete(originator);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
async fetchManifestGroupPermissions(originator) {
|
|
857
|
+
const { groupPermissions } = await this.fetchManifestPermissions(originator);
|
|
858
|
+
return groupPermissions;
|
|
859
|
+
}
|
|
860
|
+
async filterAlreadyGrantedPermissions(originator, groupPermissions) {
|
|
861
|
+
const permissionsToRequest = {
|
|
862
|
+
description: groupPermissions.description,
|
|
863
|
+
protocolPermissions: [],
|
|
864
|
+
basketAccess: [],
|
|
865
|
+
certificateAccess: []
|
|
866
|
+
};
|
|
867
|
+
if (groupPermissions.spendingAuthorization) {
|
|
868
|
+
const hasAuth = await this.hasSpendingAuthorization({
|
|
869
|
+
originator,
|
|
870
|
+
satoshis: groupPermissions.spendingAuthorization.amount
|
|
871
|
+
});
|
|
872
|
+
if (!hasAuth) {
|
|
873
|
+
permissionsToRequest.spendingAuthorization = groupPermissions.spendingAuthorization;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
for (const p of groupPermissions.protocolPermissions || []) {
|
|
877
|
+
const hasPerm = await this.hasProtocolPermission({
|
|
878
|
+
originator,
|
|
879
|
+
privileged: false,
|
|
880
|
+
protocolID: p.protocolID,
|
|
881
|
+
counterparty: p.counterparty || 'self'
|
|
882
|
+
});
|
|
883
|
+
if (!hasPerm) {
|
|
884
|
+
permissionsToRequest.protocolPermissions.push(p);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
for (const b of groupPermissions.basketAccess || []) {
|
|
888
|
+
const hasAccess = await this.hasBasketAccess({
|
|
889
|
+
originator,
|
|
890
|
+
basket: b.basket
|
|
891
|
+
});
|
|
892
|
+
if (!hasAccess) {
|
|
893
|
+
permissionsToRequest.basketAccess.push(b);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
for (const c of groupPermissions.certificateAccess || []) {
|
|
897
|
+
const hasAccess = await this.hasCertificateAccess({
|
|
898
|
+
originator,
|
|
899
|
+
privileged: false,
|
|
900
|
+
verifier: c.verifierPublicKey,
|
|
901
|
+
certType: c.type,
|
|
902
|
+
fields: c.fields
|
|
903
|
+
});
|
|
904
|
+
if (!hasAccess) {
|
|
905
|
+
permissionsToRequest.certificateAccess.push(c);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return permissionsToRequest;
|
|
909
|
+
}
|
|
910
|
+
hasAnyPermissionsToRequest(permissions) {
|
|
911
|
+
var _a, _b, _c, _d, _e, _f;
|
|
912
|
+
return !!(permissions.spendingAuthorization ||
|
|
913
|
+
((_b = (_a = permissions.protocolPermissions) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 ||
|
|
914
|
+
((_d = (_c = permissions.basketAccess) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0 ||
|
|
915
|
+
((_f = (_e = permissions.certificateAccess) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) > 0);
|
|
916
|
+
}
|
|
917
|
+
hasGroupedPermissionRequestedHandlers() {
|
|
918
|
+
const handlers = this.callbacks.onGroupedPermissionRequested || [];
|
|
919
|
+
return handlers.some(h => typeof h === 'function');
|
|
920
|
+
}
|
|
921
|
+
hasCounterpartyPermissionRequestedHandlers() {
|
|
922
|
+
const handlers = this.callbacks.onCounterpartyPermissionRequested || [];
|
|
923
|
+
return handlers.some(h => typeof h === 'function');
|
|
924
|
+
}
|
|
925
|
+
async hasPactEstablished(originator, counterparty) {
|
|
926
|
+
var _a;
|
|
927
|
+
if (counterparty === 'self' || counterparty === 'anyone') {
|
|
928
|
+
return true;
|
|
929
|
+
}
|
|
930
|
+
const cacheKey = `${originator}:${counterparty}`;
|
|
931
|
+
if (this.pactEstablishedCache.has(cacheKey)) {
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
934
|
+
const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
|
|
935
|
+
if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
const firstProtocol = counterpartyPermissions.protocols[0];
|
|
939
|
+
const hasToken = await this.hasProtocolPermission({
|
|
940
|
+
originator,
|
|
941
|
+
privileged: false,
|
|
942
|
+
protocolID: firstProtocol.protocolID,
|
|
943
|
+
counterparty
|
|
944
|
+
});
|
|
945
|
+
if (hasToken) {
|
|
946
|
+
this.pactEstablishedCache.set(cacheKey, Date.now());
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
markPactEstablished(originator, counterparty) {
|
|
952
|
+
const cacheKey = `${originator}:${counterparty}`;
|
|
953
|
+
this.pactEstablishedCache.set(cacheKey, Date.now());
|
|
954
|
+
}
|
|
955
|
+
async maybeRequestPact(currentRequest) {
|
|
956
|
+
var _a;
|
|
957
|
+
if (!this.config.seekGroupedPermission) {
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
960
|
+
if (!this.hasCounterpartyPermissionRequestedHandlers()) {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
if (currentRequest.type !== 'protocol') {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
if (currentRequest.privileged) {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
const [level] = currentRequest.protocolID;
|
|
970
|
+
if (level !== 2) {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
const originator = currentRequest.originator;
|
|
974
|
+
const counterparty = currentRequest.counterparty;
|
|
975
|
+
if (!counterparty || counterparty === 'self' || counterparty === 'anyone') {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
if (!/^[0-9a-fA-F]{66}$/.test(counterparty)) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
if (await this.hasPactEstablished(originator, counterparty)) {
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
const { counterpartyPermissions } = await this.fetchManifestPermissions(originator);
|
|
985
|
+
if (!((_a = counterpartyPermissions === null || counterpartyPermissions === void 0 ? void 0 : counterpartyPermissions.protocols) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
const protocolsToRequest = [];
|
|
989
|
+
for (const p of counterpartyPermissions.protocols) {
|
|
990
|
+
const hasPerm = await this.hasProtocolPermission({
|
|
991
|
+
originator,
|
|
992
|
+
privileged: false,
|
|
993
|
+
protocolID: p.protocolID,
|
|
994
|
+
counterparty
|
|
995
|
+
});
|
|
996
|
+
if (!hasPerm) {
|
|
997
|
+
protocolsToRequest.push(p);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (protocolsToRequest.length === 0) {
|
|
1001
|
+
this.markPactEstablished(originator, counterparty);
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
const permissionsToRequest = {
|
|
1005
|
+
description: counterpartyPermissions.description,
|
|
1006
|
+
protocols: protocolsToRequest
|
|
1007
|
+
};
|
|
1008
|
+
const key = `pact:${originator}:${counterparty}`;
|
|
1009
|
+
const existing = this.activeRequests.get(key);
|
|
1010
|
+
if (existing) {
|
|
1011
|
+
const existingRequest = existing.request;
|
|
1012
|
+
for (const p of permissionsToRequest.protocols) {
|
|
1013
|
+
if (!existingRequest.permissions.protocols.find(x => deepEqual(x, p))) {
|
|
1014
|
+
existingRequest.permissions.protocols.push(p);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
await new Promise((resolve, reject) => {
|
|
1018
|
+
existing.pending.push({ resolve, reject });
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
await new Promise(async (resolve, reject) => {
|
|
1023
|
+
this.activeRequests.set(key, {
|
|
1024
|
+
request: {
|
|
1025
|
+
originator,
|
|
1026
|
+
counterparty,
|
|
1027
|
+
permissions: permissionsToRequest,
|
|
1028
|
+
displayOriginator: currentRequest.displayOriginator
|
|
1029
|
+
},
|
|
1030
|
+
pending: [{ resolve, reject }]
|
|
1031
|
+
});
|
|
1032
|
+
await this.callEvent('onCounterpartyPermissionRequested', {
|
|
1033
|
+
requestID: key,
|
|
1034
|
+
originator,
|
|
1035
|
+
counterparty,
|
|
1036
|
+
permissions: permissionsToRequest
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
this.markPactEstablished(originator, counterparty);
|
|
1041
|
+
const satisfied = await this.hasProtocolPermission({
|
|
1042
|
+
originator,
|
|
1043
|
+
privileged: false,
|
|
1044
|
+
protocolID: currentRequest.protocolID,
|
|
1045
|
+
counterparty
|
|
1046
|
+
});
|
|
1047
|
+
return satisfied ? true : null;
|
|
1048
|
+
}
|
|
1049
|
+
async maybeRequestPeerGroupedLevel2ProtocolPermissions(currentRequest) {
|
|
1050
|
+
var _a, _b;
|
|
1051
|
+
if (!this.config.seekGroupedPermission) {
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
if (!this.hasGroupedPermissionRequestedHandlers()) {
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
if (currentRequest.type !== 'protocol') {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
const [level] = currentRequest.protocolID;
|
|
1061
|
+
if (level !== 2) {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
const originator = currentRequest.originator;
|
|
1065
|
+
const privileged = (_a = currentRequest.privileged) !== null && _a !== void 0 ? _a : false;
|
|
1066
|
+
const counterparty = (_b = currentRequest.counterparty) !== null && _b !== void 0 ? _b : 'self';
|
|
1067
|
+
const groupPermissions = await this.fetchManifestGroupPermissions(originator);
|
|
1068
|
+
if (!groupPermissions) {
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
const normalizeManifestCounterparty = (cp) => {
|
|
1072
|
+
if (cp === '')
|
|
1073
|
+
return counterparty;
|
|
1074
|
+
return cp !== null && cp !== void 0 ? cp : 'self';
|
|
1075
|
+
};
|
|
1076
|
+
const manifestLevel2ForThisPeer = (groupPermissions.protocolPermissions || [])
|
|
1077
|
+
.filter(p => { var _a, _b; return ((_b = (_a = p.protocolID) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 0) === 2; })
|
|
1078
|
+
.map(p => ({
|
|
1079
|
+
protocolID: p.protocolID,
|
|
1080
|
+
counterparty: normalizeManifestCounterparty(p.counterparty),
|
|
1081
|
+
description: p.description
|
|
1082
|
+
}))
|
|
1083
|
+
.filter(p => p.counterparty === counterparty);
|
|
1084
|
+
const isCurrentRequestInManifest = manifestLevel2ForThisPeer.some(p => deepEqual(p.protocolID, currentRequest.protocolID));
|
|
1085
|
+
if (!isCurrentRequestInManifest) {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
const permissionsToRequest = {
|
|
1089
|
+
protocolPermissions: []
|
|
1090
|
+
};
|
|
1091
|
+
for (const p of manifestLevel2ForThisPeer) {
|
|
1092
|
+
const hasPerm = await this.hasProtocolPermission({
|
|
1093
|
+
originator,
|
|
1094
|
+
privileged,
|
|
1095
|
+
protocolID: p.protocolID,
|
|
1096
|
+
counterparty: p.counterparty
|
|
1097
|
+
});
|
|
1098
|
+
if (!hasPerm) {
|
|
1099
|
+
permissionsToRequest.protocolPermissions.push({
|
|
1100
|
+
protocolID: p.protocolID,
|
|
1101
|
+
counterparty: p.counterparty,
|
|
1102
|
+
description: p.description
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
const key = `group-peer:${originator}:${privileged}:${counterparty}`;
|
|
1110
|
+
const existing = this.activeRequests.get(key);
|
|
1111
|
+
if (existing) {
|
|
1112
|
+
const existingRequest = existing.request;
|
|
1113
|
+
if (!existingRequest.permissions.protocolPermissions) {
|
|
1114
|
+
existingRequest.permissions.protocolPermissions = [];
|
|
1115
|
+
}
|
|
1116
|
+
for (const p of permissionsToRequest.protocolPermissions || []) {
|
|
1117
|
+
if (!existingRequest.permissions.protocolPermissions.find(x => deepEqual(x, p))) {
|
|
1118
|
+
existingRequest.permissions.protocolPermissions.push(p);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
await new Promise((resolve, reject) => {
|
|
1122
|
+
existing.pending.push({ resolve, reject });
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
await new Promise(async (resolve, reject) => {
|
|
1127
|
+
const permissions = permissionsToRequest;
|
|
1128
|
+
this.activeRequests.set(key, {
|
|
1129
|
+
request: {
|
|
1130
|
+
originator,
|
|
1131
|
+
permissions,
|
|
1132
|
+
displayOriginator: currentRequest.displayOriginator
|
|
1133
|
+
},
|
|
1134
|
+
pending: [{ resolve, reject }]
|
|
1135
|
+
});
|
|
1136
|
+
await this.callEvent('onGroupedPermissionRequested', {
|
|
1137
|
+
requestID: key,
|
|
1138
|
+
originator,
|
|
1139
|
+
permissions
|
|
1140
|
+
});
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
|
|
1144
|
+
return satisfied ? true : null;
|
|
1145
|
+
}
|
|
1146
|
+
async checkSpecificPermissionAfterGroupFlow(request) {
|
|
1147
|
+
var _a, _b, _c;
|
|
1148
|
+
switch (request.type) {
|
|
1149
|
+
case 'protocol':
|
|
1150
|
+
return await this.hasProtocolPermission({
|
|
1151
|
+
originator: request.originator,
|
|
1152
|
+
privileged: (_a = request.privileged) !== null && _a !== void 0 ? _a : false,
|
|
1153
|
+
protocolID: request.protocolID,
|
|
1154
|
+
counterparty: (_b = request.counterparty) !== null && _b !== void 0 ? _b : 'self'
|
|
1155
|
+
});
|
|
1156
|
+
case 'basket':
|
|
1157
|
+
return await this.hasBasketAccess({
|
|
1158
|
+
originator: request.originator,
|
|
1159
|
+
basket: request.basket
|
|
1160
|
+
});
|
|
1161
|
+
case 'certificate':
|
|
1162
|
+
return await this.hasCertificateAccess({
|
|
1163
|
+
originator: request.originator,
|
|
1164
|
+
privileged: (_c = request.privileged) !== null && _c !== void 0 ? _c : false,
|
|
1165
|
+
verifier: request.certificate.verifier,
|
|
1166
|
+
certType: request.certificate.certType,
|
|
1167
|
+
fields: request.certificate.fields
|
|
1168
|
+
});
|
|
1169
|
+
case 'spending':
|
|
1170
|
+
return await this.hasSpendingAuthorization({
|
|
1171
|
+
originator: request.originator,
|
|
1172
|
+
satoshis: request.spending.satoshis
|
|
1173
|
+
});
|
|
1174
|
+
default:
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
isRequestIncludedInGroupPermissions(request, groupPermissions) {
|
|
1179
|
+
var _a, _b, _c, _d;
|
|
1180
|
+
switch (request.type) {
|
|
1181
|
+
case 'protocol': {
|
|
1182
|
+
if (request.privileged)
|
|
1183
|
+
return false;
|
|
1184
|
+
const pid = request.protocolID;
|
|
1185
|
+
if (!pid)
|
|
1186
|
+
return false;
|
|
1187
|
+
const cp = (_a = request.counterparty) !== null && _a !== void 0 ? _a : 'self';
|
|
1188
|
+
return !!((_b = groupPermissions.protocolPermissions) === null || _b === void 0 ? void 0 : _b.some(p => {
|
|
1189
|
+
var _a;
|
|
1190
|
+
const manifestCp = p.counterparty === '' ? cp : ((_a = p.counterparty) !== null && _a !== void 0 ? _a : 'self');
|
|
1191
|
+
return deepEqual(p.protocolID, pid) && manifestCp === cp;
|
|
1192
|
+
}));
|
|
1193
|
+
}
|
|
1194
|
+
case 'basket': {
|
|
1195
|
+
const basket = request.basket;
|
|
1196
|
+
if (!basket)
|
|
1197
|
+
return false;
|
|
1198
|
+
return !!((_c = groupPermissions.basketAccess) === null || _c === void 0 ? void 0 : _c.some(b => b.basket === basket));
|
|
1199
|
+
}
|
|
1200
|
+
case 'certificate': {
|
|
1201
|
+
if (request.privileged)
|
|
1202
|
+
return false;
|
|
1203
|
+
const cert = request.certificate;
|
|
1204
|
+
if (!cert)
|
|
1205
|
+
return false;
|
|
1206
|
+
return !!((_d = groupPermissions.certificateAccess) === null || _d === void 0 ? void 0 : _d.some(c => {
|
|
1207
|
+
const fieldsA = new Set(c.fields || []);
|
|
1208
|
+
const fieldsB = new Set(cert.fields || []);
|
|
1209
|
+
if (fieldsA.size !== fieldsB.size)
|
|
1210
|
+
return false;
|
|
1211
|
+
for (const f of fieldsA)
|
|
1212
|
+
if (!fieldsB.has(f))
|
|
1213
|
+
return false;
|
|
1214
|
+
return c.type === cert.certType && c.verifierPublicKey === cert.verifier;
|
|
1215
|
+
}));
|
|
1216
|
+
}
|
|
1217
|
+
case 'spending':
|
|
1218
|
+
return !!groupPermissions.spendingAuthorization;
|
|
1219
|
+
default:
|
|
1220
|
+
return false;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async maybeRequestGroupedPermissions(currentRequest) {
|
|
1224
|
+
if (!this.config.seekGroupedPermission) {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
const originator = currentRequest.originator;
|
|
1228
|
+
const groupPermissions = await this.fetchManifestGroupPermissions(originator);
|
|
1229
|
+
if (!groupPermissions) {
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
if (!this.isRequestIncludedInGroupPermissions(currentRequest, groupPermissions)) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
|
|
1236
|
+
if (!this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
const key = `group:${originator}`;
|
|
1240
|
+
if (this.activeRequests.has(key)) {
|
|
1241
|
+
await new Promise((resolve, reject) => {
|
|
1242
|
+
this.activeRequests.get(key).pending.push({ resolve, reject });
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
await new Promise(async (resolve, reject) => {
|
|
1247
|
+
this.activeRequests.set(key, {
|
|
1248
|
+
request: {
|
|
1249
|
+
originator,
|
|
1250
|
+
permissions: permissionsToRequest,
|
|
1251
|
+
displayOriginator: currentRequest.displayOriginator
|
|
1252
|
+
},
|
|
1253
|
+
pending: [{ resolve, reject }]
|
|
1254
|
+
});
|
|
1255
|
+
await this.callEvent('onGroupedPermissionRequested', {
|
|
1256
|
+
requestID: key,
|
|
1257
|
+
originator,
|
|
1258
|
+
permissions: permissionsToRequest
|
|
1259
|
+
});
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
const satisfied = await this.checkSpecificPermissionAfterGroupFlow(currentRequest);
|
|
1263
|
+
return satisfied ? true : null;
|
|
1264
|
+
}
|
|
731
1265
|
/**
|
|
732
1266
|
* A central method that triggers the permission request flow.
|
|
733
1267
|
* - It checks if there's already an active request for the same key
|
|
@@ -736,13 +1270,25 @@ class WalletPermissionsManager {
|
|
|
736
1270
|
* and return a promise that resolves once permission is granted or rejects if denied.
|
|
737
1271
|
*/
|
|
738
1272
|
async requestPermissionFlow(r) {
|
|
739
|
-
var _a;
|
|
1273
|
+
var _a, _b, _c;
|
|
740
1274
|
const normalizedOriginator = this.normalizeOriginator(r.originator) || r.originator;
|
|
741
1275
|
const preparedRequest = {
|
|
742
1276
|
...r,
|
|
743
1277
|
originator: normalizedOriginator,
|
|
744
|
-
displayOriginator: (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : r.originator
|
|
1278
|
+
displayOriginator: (_c = (_a = r.displayOriginator) !== null && _a !== void 0 ? _a : (_b = r.previousToken) === null || _b === void 0 ? void 0 : _b.rawOriginator) !== null && _c !== void 0 ? _c : r.originator
|
|
745
1279
|
};
|
|
1280
|
+
const pactResult = await this.maybeRequestPact(preparedRequest);
|
|
1281
|
+
if (pactResult !== null) {
|
|
1282
|
+
return pactResult;
|
|
1283
|
+
}
|
|
1284
|
+
const peerGroupResult = await this.maybeRequestPeerGroupedLevel2ProtocolPermissions(preparedRequest);
|
|
1285
|
+
if (peerGroupResult !== null) {
|
|
1286
|
+
return peerGroupResult;
|
|
1287
|
+
}
|
|
1288
|
+
const groupResult = await this.maybeRequestGroupedPermissions(preparedRequest);
|
|
1289
|
+
if (groupResult !== null) {
|
|
1290
|
+
return groupResult;
|
|
1291
|
+
}
|
|
746
1292
|
const key = this.buildRequestKey(preparedRequest);
|
|
747
1293
|
// If there's already a queue for the same resource, we piggyback on it
|
|
748
1294
|
const existingQueue = this.activeRequests.get(key);
|
|
@@ -2358,80 +2904,17 @@ class WalletPermissionsManager {
|
|
|
2358
2904
|
return this.underlying.isAuthenticated(...args);
|
|
2359
2905
|
}
|
|
2360
2906
|
async waitForAuthentication(...args) {
|
|
2361
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2362
2907
|
let [_, originator] = args;
|
|
2363
2908
|
if (this.config.seekGroupedPermission && originator) {
|
|
2364
2909
|
const { normalized: normalizedOriginator } = this.prepareOriginator(originator);
|
|
2365
2910
|
originator = normalizedOriginator;
|
|
2366
2911
|
// 1. Fetch manifest.json from the originator
|
|
2367
|
-
|
|
2368
|
-
try {
|
|
2369
|
-
const proto = originator.startsWith('localhost:') ? 'http' : 'https';
|
|
2370
|
-
const response = await fetch(`${proto}://${originator}/manifest.json`);
|
|
2371
|
-
if (response.ok) {
|
|
2372
|
-
const manifest = await response.json();
|
|
2373
|
-
if ((_a = manifest === null || manifest === void 0 ? void 0 : manifest.babbage) === null || _a === void 0 ? void 0 : _a.groupPermissions) {
|
|
2374
|
-
groupPermissions = manifest.babbage.groupPermissions;
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
catch (e) {
|
|
2379
|
-
// Ignore fetch/parse errors, just proceed without group permissions.
|
|
2380
|
-
}
|
|
2912
|
+
const groupPermissions = await this.fetchManifestGroupPermissions(originator);
|
|
2381
2913
|
if (groupPermissions) {
|
|
2382
2914
|
// 2. Filter out already-granted permissions
|
|
2383
|
-
const permissionsToRequest =
|
|
2384
|
-
protocolPermissions: [],
|
|
2385
|
-
basketAccess: [],
|
|
2386
|
-
certificateAccess: []
|
|
2387
|
-
};
|
|
2388
|
-
if (groupPermissions.spendingAuthorization) {
|
|
2389
|
-
const hasAuth = await this.hasSpendingAuthorization({
|
|
2390
|
-
originator,
|
|
2391
|
-
satoshis: groupPermissions.spendingAuthorization.amount
|
|
2392
|
-
});
|
|
2393
|
-
if (!hasAuth) {
|
|
2394
|
-
permissionsToRequest.spendingAuthorization = groupPermissions.spendingAuthorization;
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
for (const p of groupPermissions.protocolPermissions || []) {
|
|
2398
|
-
const hasPerm = await this.hasProtocolPermission({
|
|
2399
|
-
originator,
|
|
2400
|
-
privileged: false, // Privilege is never allowed here
|
|
2401
|
-
protocolID: p.protocolID,
|
|
2402
|
-
counterparty: p.counterparty || 'self'
|
|
2403
|
-
});
|
|
2404
|
-
if (!hasPerm) {
|
|
2405
|
-
permissionsToRequest.protocolPermissions.push(p);
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
for (const b of groupPermissions.basketAccess || []) {
|
|
2409
|
-
const hasAccess = await this.hasBasketAccess({
|
|
2410
|
-
originator,
|
|
2411
|
-
basket: b.basket
|
|
2412
|
-
});
|
|
2413
|
-
if (!hasAccess) {
|
|
2414
|
-
permissionsToRequest.basketAccess.push(b);
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
for (const c of groupPermissions.certificateAccess || []) {
|
|
2418
|
-
const hasAccess = await this.hasCertificateAccess({
|
|
2419
|
-
originator,
|
|
2420
|
-
privileged: false, // Privilege is never allowed here for security
|
|
2421
|
-
verifier: c.verifierPublicKey,
|
|
2422
|
-
certType: c.type,
|
|
2423
|
-
fields: c.fields
|
|
2424
|
-
});
|
|
2425
|
-
if (!hasAccess) {
|
|
2426
|
-
permissionsToRequest.certificateAccess.push(c);
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2915
|
+
const permissionsToRequest = await this.filterAlreadyGrantedPermissions(originator, groupPermissions);
|
|
2429
2916
|
// 3. If any permissions are left to request, start the flow
|
|
2430
|
-
|
|
2431
|
-
((_c = (_b = permissionsToRequest.protocolPermissions) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 0 ||
|
|
2432
|
-
((_e = (_d = permissionsToRequest.basketAccess) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0) > 0 ||
|
|
2433
|
-
((_g = (_f = permissionsToRequest.certificateAccess) === null || _f === void 0 ? void 0 : _f.length) !== null && _g !== void 0 ? _g : 0) > 0;
|
|
2434
|
-
if (hasRequests) {
|
|
2917
|
+
if (this.hasAnyPermissionsToRequest(permissionsToRequest)) {
|
|
2435
2918
|
const key = `group:${originator}`;
|
|
2436
2919
|
if (this.activeRequests.has(key)) {
|
|
2437
2920
|
// Another call is already waiting, piggyback on it
|
|
@@ -2646,6 +3129,7 @@ class WalletPermissionsManager {
|
|
|
2646
3129
|
}
|
|
2647
3130
|
}
|
|
2648
3131
|
exports.WalletPermissionsManager = WalletPermissionsManager;
|
|
3132
|
+
WalletPermissionsManager.MANIFEST_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
2649
3133
|
/** How long a cached permission remains valid (5 minutes). */
|
|
2650
3134
|
WalletPermissionsManager.CACHE_TTL_MS = 5 * 60 * 1000;
|
|
2651
3135
|
/** Window during which freshly granted permissions are auto-allowed (except spending). */
|