holosphere 2.0.0-alpha13 → 2.0.0-alpha15
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/dist/2019-ATLjawsU.cjs +8 -0
- package/dist/{2019-Cp3uYhyY.cjs.map → 2019-ATLjawsU.cjs.map} +1 -1
- package/dist/{2019-CLMqIAfQ.js → 2019-BfjzDRje.js} +1667 -1721
- package/dist/{2019-CLMqIAfQ.js.map → 2019-BfjzDRje.js.map} +1 -1
- package/dist/{browser-nUQt1cnB.js → browser-CKTczilW.js} +2 -2
- package/dist/{browser-nUQt1cnB.js.map → browser-CKTczilW.js.map} +1 -1
- package/dist/{browser-D6cNVl0v.cjs → browser-GOg6KKOV.cjs} +2 -2
- package/dist/{browser-D6cNVl0v.cjs.map → browser-GOg6KKOV.cjs.map} +1 -1
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +25 -21
- package/dist/{index-CoAjtqsD.js → index-BdnrGafX.js} +2 -2
- package/dist/{index-CoAjtqsD.js.map → index-BdnrGafX.js.map} +1 -1
- package/dist/{index-BN_uoxQK.js → index-C3Cag0SV.js} +2558 -495
- package/dist/index-C3Cag0SV.js.map +1 -0
- package/dist/index-ChpSfdYS.cjs +29 -0
- package/dist/index-ChpSfdYS.cjs.map +1 -0
- package/dist/{index-DJjGSwXG.cjs → index-D_QecZNu.cjs} +2 -2
- package/dist/{index-DJjGSwXG.cjs.map → index-D_QecZNu.cjs.map} +1 -1
- package/dist/{index-Z5TstN1e.js → index-nMC3dWZ5.js} +2 -2
- package/dist/{index-Z5TstN1e.js.map → index-nMC3dWZ5.js.map} +1 -1
- package/dist/{index-Cp3tI53z.cjs → index-z5HWfWMu.cjs} +2 -2
- package/dist/{index-Cp3tI53z.cjs.map → index-z5HWfWMu.cjs.map} +1 -1
- package/dist/{indexeddb-storage-CZK5A7XH.cjs → indexeddb-storage-DWSeL-YF.cjs} +2 -2
- package/dist/{indexeddb-storage-CZK5A7XH.cjs.map → indexeddb-storage-DWSeL-YF.cjs.map} +1 -1
- package/dist/{indexeddb-storage-bpA01pAU.js → indexeddb-storage-dx01N0ET.js} +2 -2
- package/dist/{indexeddb-storage-bpA01pAU.js.map → indexeddb-storage-dx01N0ET.js.map} +1 -1
- package/dist/{memory-storage-BqhmytP_.js → memory-storage-BPIfkpcf.js} +2 -2
- package/dist/{memory-storage-BqhmytP_.js.map → memory-storage-BPIfkpcf.js.map} +1 -1
- package/dist/{memory-storage-B1k8Jszd.cjs → memory-storage-CKUGDq2d.cjs} +2 -2
- package/dist/{memory-storage-B1k8Jszd.cjs.map → memory-storage-CKUGDq2d.cjs.map} +1 -1
- package/package.json +3 -1
- package/scripts/test-ndk-direct.js +104 -0
- package/src/crypto/key-store.js +356 -0
- package/src/crypto/lens-keys.js +205 -0
- package/src/crypto/secp256k1.js +181 -18
- package/src/federation/handshake.js +317 -23
- package/src/federation/hologram.js +25 -17
- package/src/federation/registry.js +779 -59
- package/src/index.js +416 -27
- package/src/lib/federation-methods.js +308 -4
- package/src/storage/nostr-async.js +144 -86
- package/src/storage/nostr-client.js +77 -18
- package/src/storage/nostr-wrapper.js +4 -1
- package/src/storage/unified-storage.js +5 -4
- package/src/subscriptions/manager.js +1 -1
- package/vitest.config.js +6 -1
- package/dist/2019-Cp3uYhyY.cjs +0 -8
- package/dist/index-BN_uoxQK.js.map +0 -1
- package/dist/index-V8EHMYEY.cjs +0 -29
- package/dist/index-V8EHMYEY.cjs.map +0 -1
|
@@ -80,13 +80,17 @@ export function withFederationMethods(Base) {
|
|
|
80
80
|
} = options;
|
|
81
81
|
|
|
82
82
|
// Normalize source and target to { holonId, authorPubKey } format
|
|
83
|
+
// Helper function to detect if a string is a pubkey (64-char hex)
|
|
84
|
+
const isPubkey = (str) => typeof str === 'string' && /^[0-9a-f]{64}$/i.test(str);
|
|
85
|
+
|
|
86
|
+
// When the holon ID is a pubkey, use it as the authorPubKey for cross-holosphere federation
|
|
83
87
|
const normalizedSource = typeof source === 'string'
|
|
84
|
-
? { holonId: source, authorPubKey: this.client.publicKey }
|
|
85
|
-
: { holonId: source.holonId, authorPubKey: source.authorPubKey || this.client.publicKey };
|
|
88
|
+
? { holonId: source, authorPubKey: isPubkey(source) ? source : this.client.publicKey }
|
|
89
|
+
: { holonId: source.holonId, authorPubKey: source.authorPubKey || (isPubkey(source.holonId) ? source.holonId : this.client.publicKey) };
|
|
86
90
|
|
|
87
91
|
const normalizedTarget = typeof target === 'string'
|
|
88
|
-
? { holonId: target, authorPubKey: this.client.publicKey }
|
|
89
|
-
: { holonId: target.holonId, authorPubKey: target.authorPubKey || this.client.publicKey };
|
|
92
|
+
? { holonId: target, authorPubKey: isPubkey(target) ? target : this.client.publicKey }
|
|
93
|
+
: { holonId: target.holonId, authorPubKey: target.authorPubKey || (isPubkey(target.holonId) ? target.holonId : this.client.publicKey) };
|
|
90
94
|
|
|
91
95
|
// Validation
|
|
92
96
|
if (normalizedSource.holonId === normalizedTarget.holonId &&
|
|
@@ -861,6 +865,306 @@ export function withFederationMethods(Base) {
|
|
|
861
865
|
|
|
862
866
|
return results;
|
|
863
867
|
}
|
|
868
|
+
|
|
869
|
+
// === UNIFIED ACCESS CONTROL ===
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Unified access verification method.
|
|
873
|
+
* Single entry point for checking all access permissions (read, write, etc.).
|
|
874
|
+
* Replaces and unifies the previous separate access checking methods.
|
|
875
|
+
*
|
|
876
|
+
* @param {string} targetHolonId - The holon being accessed
|
|
877
|
+
* @param {string} lensName - The lens being accessed
|
|
878
|
+
* @param {string} actorPubKey - The actor's public key
|
|
879
|
+
* @param {string} permission - Permission to check ('read' or 'write')
|
|
880
|
+
* @param {Object} [options={}] - Options
|
|
881
|
+
* @param {string} [options.actingAsHolon] - The holon the actor is acting on behalf of
|
|
882
|
+
* @param {string} [options.capabilityToken] - Explicit capability token to verify
|
|
883
|
+
* @returns {Promise<Object>} { allowed: boolean, via: string, reason?: string, grant?: Object }
|
|
884
|
+
*
|
|
885
|
+
* @example
|
|
886
|
+
* // Check if current user can read from a holon
|
|
887
|
+
* const result = await hs.canAccess(targetHolonId, 'quests', myPubKey, 'read');
|
|
888
|
+
* if (result.allowed) {
|
|
889
|
+
* console.log('Access granted via:', result.via);
|
|
890
|
+
* }
|
|
891
|
+
*
|
|
892
|
+
* @example
|
|
893
|
+
* // Check write access with explicit capability
|
|
894
|
+
* const result = await hs.canAccess(targetHolonId, 'quests', myPubKey, 'write', {
|
|
895
|
+
* capabilityToken: myCapToken
|
|
896
|
+
* });
|
|
897
|
+
*/
|
|
898
|
+
async canAccess(targetHolonId, lensName, actorPubKey, permission, options = {}) {
|
|
899
|
+
const { actingAsHolon = null, capabilityToken = null } = options;
|
|
900
|
+
|
|
901
|
+
// 1. Owner check - actor owns the target holon
|
|
902
|
+
if (actorPubKey === targetHolonId) {
|
|
903
|
+
return { allowed: true, via: 'owner' };
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// 2. Explicit capability token verification
|
|
907
|
+
if (capabilityToken) {
|
|
908
|
+
try {
|
|
909
|
+
const valid = await this.verifyCapability(capabilityToken, permission, {
|
|
910
|
+
holonId: targetHolonId,
|
|
911
|
+
lensName,
|
|
912
|
+
});
|
|
913
|
+
if (valid) {
|
|
914
|
+
return { allowed: true, via: 'capability' };
|
|
915
|
+
}
|
|
916
|
+
} catch (err) {
|
|
917
|
+
console.log('[canAccess] Capability verification failed:', err.message);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// 3. Membership in target holon
|
|
922
|
+
try {
|
|
923
|
+
const members = await this.get(targetHolonId, 'users');
|
|
924
|
+
if (members && Array.isArray(members)) {
|
|
925
|
+
const isMember = members.some(m =>
|
|
926
|
+
m.pubKey === actorPubKey ||
|
|
927
|
+
m.id === actorPubKey ||
|
|
928
|
+
m.nostrPubKey === actorPubKey
|
|
929
|
+
);
|
|
930
|
+
if (isMember) {
|
|
931
|
+
return { allowed: true, via: 'membership' };
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
} catch (err) {
|
|
935
|
+
// Users lens might not exist or be empty - continue to check grants
|
|
936
|
+
console.log('[canAccess] Could not read users lens:', err.message);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// 4. Access grant via federation (unified check)
|
|
940
|
+
// First check using the new unified findAccessGrant
|
|
941
|
+
const scope = { holonId: targetHolonId, lensName };
|
|
942
|
+
|
|
943
|
+
// Check if actingAsHolon is specified
|
|
944
|
+
if (actingAsHolon) {
|
|
945
|
+
// Verify the actor is a member of the acting holon
|
|
946
|
+
const actorHolons = await this.getHolonsForPubKey(actorPubKey);
|
|
947
|
+
const isMemberOfActingHolon = actorHolons.some(h => h.id === actingAsHolon);
|
|
948
|
+
|
|
949
|
+
if (!isMemberOfActingHolon) {
|
|
950
|
+
return { allowed: false, via: 'none', reason: 'Not a member of the acting holon' };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Check if actingAsHolon has an access grant
|
|
954
|
+
const grant = await registry.findAccessGrant(
|
|
955
|
+
this.client,
|
|
956
|
+
this.config.appName,
|
|
957
|
+
actingAsHolon,
|
|
958
|
+
scope,
|
|
959
|
+
permission,
|
|
960
|
+
{ direction: 'inbound' }
|
|
961
|
+
);
|
|
962
|
+
|
|
963
|
+
if (grant) {
|
|
964
|
+
return {
|
|
965
|
+
allowed: true,
|
|
966
|
+
via: 'federation',
|
|
967
|
+
grant,
|
|
968
|
+
reason: `Acting as ${actingAsHolon} with ${permission} grant`,
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
} else {
|
|
972
|
+
// Check all holons the actor is a member of
|
|
973
|
+
const actorHolons = await this.getHolonsForPubKey(actorPubKey);
|
|
974
|
+
|
|
975
|
+
for (const holon of actorHolons) {
|
|
976
|
+
const grant = await registry.findAccessGrant(
|
|
977
|
+
this.client,
|
|
978
|
+
this.config.appName,
|
|
979
|
+
holon.id,
|
|
980
|
+
scope,
|
|
981
|
+
permission,
|
|
982
|
+
{ direction: 'inbound' }
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
if (grant) {
|
|
986
|
+
return {
|
|
987
|
+
allowed: true,
|
|
988
|
+
via: 'federation',
|
|
989
|
+
grant,
|
|
990
|
+
reason: `Member of ${holon.name || holon.id} which has ${permission} grant`,
|
|
991
|
+
viaHolon: holon.id,
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return { allowed: false, via: 'none', reason: 'No access' };
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// === CROSS-HOLON WRITE ACCESS ===
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Check if a writer can write to a target holon's lens.
|
|
1004
|
+
* This is a backward-compatible wrapper that delegates to canAccess().
|
|
1005
|
+
*
|
|
1006
|
+
* @param {string} targetHolonId - The holon being written to
|
|
1007
|
+
* @param {string} lensName - The lens being written to
|
|
1008
|
+
* @param {string} writerPubKey - The writer's public key
|
|
1009
|
+
* @param {Object} [options={}] - Options
|
|
1010
|
+
* @param {string} [options.actingAsHolon] - The holon the writer is acting on behalf of
|
|
1011
|
+
* @param {string} [options.capabilityToken] - Explicit capability token to verify
|
|
1012
|
+
* @returns {Promise<Object>} { canWrite: boolean, reason: string, accessType: string }
|
|
1013
|
+
*
|
|
1014
|
+
* @example
|
|
1015
|
+
* // Check if current user can write to another holon
|
|
1016
|
+
* const result = await hs.canWrite(targetHolonId, 'quests', myPubKey);
|
|
1017
|
+
* if (result.canWrite) {
|
|
1018
|
+
* console.log('Access granted:', result.accessType);
|
|
1019
|
+
* }
|
|
1020
|
+
*/
|
|
1021
|
+
async canWrite(targetHolonId, lensName, writerPubKey, options = {}) {
|
|
1022
|
+
// Delegate to unified canAccess method
|
|
1023
|
+
const result = await this.canAccess(targetHolonId, lensName, writerPubKey, 'write', options);
|
|
1024
|
+
|
|
1025
|
+
// Convert to legacy format for backward compatibility
|
|
1026
|
+
return {
|
|
1027
|
+
canWrite: result.allowed,
|
|
1028
|
+
reason: result.reason || result.via,
|
|
1029
|
+
accessType: result.via,
|
|
1030
|
+
viaHolon: result.viaHolon,
|
|
1031
|
+
grant: result.grant,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/**
|
|
1036
|
+
* Check if an actor can read from a target holon's lens.
|
|
1037
|
+
* Convenience wrapper around canAccess() for read operations.
|
|
1038
|
+
*
|
|
1039
|
+
* @param {string} targetHolonId - The holon being read from
|
|
1040
|
+
* @param {string} lensName - The lens being read from
|
|
1041
|
+
* @param {string} readerPubKey - The reader's public key
|
|
1042
|
+
* @param {Object} [options={}] - Options
|
|
1043
|
+
* @param {string} [options.actingAsHolon] - The holon the reader is acting on behalf of
|
|
1044
|
+
* @param {string} [options.capabilityToken] - Explicit capability token to verify
|
|
1045
|
+
* @returns {Promise<Object>} { canRead: boolean, reason: string, accessType: string }
|
|
1046
|
+
*/
|
|
1047
|
+
async canRead(targetHolonId, lensName, readerPubKey, options = {}) {
|
|
1048
|
+
// Delegate to unified canAccess method
|
|
1049
|
+
const result = await this.canAccess(targetHolonId, lensName, readerPubKey, 'read', options);
|
|
1050
|
+
|
|
1051
|
+
return {
|
|
1052
|
+
canRead: result.allowed,
|
|
1053
|
+
reason: result.reason || result.via,
|
|
1054
|
+
accessType: result.via,
|
|
1055
|
+
viaHolon: result.viaHolon,
|
|
1056
|
+
grant: result.grant,
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Get all holons that a public key is a member of.
|
|
1062
|
+
* Searches federation registry and users lenses.
|
|
1063
|
+
*
|
|
1064
|
+
* @param {string} pubKey - The public key to look up
|
|
1065
|
+
* @returns {Promise<Array>} Array of { id, name } for each holon
|
|
1066
|
+
*/
|
|
1067
|
+
async getHolonsForPubKey(pubKey) {
|
|
1068
|
+
const results = [];
|
|
1069
|
+
|
|
1070
|
+
// The user's own holon (their pubkey is also a holon)
|
|
1071
|
+
results.push({ id: pubKey, name: 'Personal Holon' });
|
|
1072
|
+
|
|
1073
|
+
// Check federation registry for holons where user has membership
|
|
1074
|
+
try {
|
|
1075
|
+
const reg = await registry.getFederationRegistry(this.client, this.config.appName);
|
|
1076
|
+
if (reg && reg.federatedWith) {
|
|
1077
|
+
for (const partner of reg.federatedWith) {
|
|
1078
|
+
// Check if this partner's holon has the user as a member
|
|
1079
|
+
try {
|
|
1080
|
+
const members = await this.get(partner.pubKey, 'users');
|
|
1081
|
+
if (members && Array.isArray(members)) {
|
|
1082
|
+
const isMember = members.some(m =>
|
|
1083
|
+
m.pubKey === pubKey ||
|
|
1084
|
+
m.id === pubKey ||
|
|
1085
|
+
m.nostrPubKey === pubKey
|
|
1086
|
+
);
|
|
1087
|
+
if (isMember) {
|
|
1088
|
+
results.push({ id: partner.pubKey, name: partner.alias || partner.pubKey });
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
} catch (err) {
|
|
1092
|
+
// Skip holons we can't read
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
console.warn('[getHolonsForPubKey] Error reading federation registry:', err.message);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
return results;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Grant write access to a federated holon for a specific lens.
|
|
1105
|
+
* Members of the granted holon will be able to write to this lens.
|
|
1106
|
+
*
|
|
1107
|
+
* @param {string} holonId - The holon to grant write access to
|
|
1108
|
+
* @param {string} lensName - The lens to grant write access for (or '*' for all)
|
|
1109
|
+
* @param {Object} [options={}] - Grant options
|
|
1110
|
+
* @param {number} [options.expiresAt] - Optional expiration timestamp
|
|
1111
|
+
* @returns {Promise<boolean>} Success indicator
|
|
1112
|
+
*
|
|
1113
|
+
* @example
|
|
1114
|
+
* // Grant holon B write access to quests lens
|
|
1115
|
+
* await hs.grantWriteAccess('holon-b-pubkey', 'quests');
|
|
1116
|
+
*/
|
|
1117
|
+
async grantWriteAccess(holonId, lensName, options = {}) {
|
|
1118
|
+
return registry.grantWriteAccessToHolon(
|
|
1119
|
+
this.client,
|
|
1120
|
+
this.config.appName,
|
|
1121
|
+
holonId,
|
|
1122
|
+
lensName,
|
|
1123
|
+
options
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Revoke write access from a federated holon for a specific lens.
|
|
1129
|
+
*
|
|
1130
|
+
* @param {string} holonId - The holon to revoke write access from
|
|
1131
|
+
* @param {string} lensName - The lens to revoke write access for
|
|
1132
|
+
* @returns {Promise<boolean>} Success indicator
|
|
1133
|
+
*/
|
|
1134
|
+
async revokeWriteAccess(holonId, lensName) {
|
|
1135
|
+
return registry.revokeWriteAccess(
|
|
1136
|
+
this.client,
|
|
1137
|
+
this.config.appName,
|
|
1138
|
+
holonId,
|
|
1139
|
+
lensName
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Get write grants for a specific holon.
|
|
1145
|
+
*
|
|
1146
|
+
* @param {string} holonId - The holon to get write grants for
|
|
1147
|
+
* @returns {Promise<Array>} Array of write grants { lensName, grantedAt, expiresAt }
|
|
1148
|
+
*/
|
|
1149
|
+
async getWriteGrantsForHolon(holonId) {
|
|
1150
|
+
return registry.getWriteGrantsForHolon(
|
|
1151
|
+
this.client,
|
|
1152
|
+
this.config.appName,
|
|
1153
|
+
holonId
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Get all write grants issued to federated holons.
|
|
1159
|
+
*
|
|
1160
|
+
* @returns {Promise<Array>} Array of { holonId, alias, writeGrants }
|
|
1161
|
+
*/
|
|
1162
|
+
async getAllWriteGrants() {
|
|
1163
|
+
return registry.getAllWriteGrants(
|
|
1164
|
+
this.client,
|
|
1165
|
+
this.config.appName
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
864
1168
|
};
|
|
865
1169
|
}
|
|
866
1170
|
|