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.
Files changed (50) hide show
  1. package/dist/2019-ATLjawsU.cjs +8 -0
  2. package/dist/{2019-Cp3uYhyY.cjs.map → 2019-ATLjawsU.cjs.map} +1 -1
  3. package/dist/{2019-CLMqIAfQ.js → 2019-BfjzDRje.js} +1667 -1721
  4. package/dist/{2019-CLMqIAfQ.js.map → 2019-BfjzDRje.js.map} +1 -1
  5. package/dist/{browser-nUQt1cnB.js → browser-CKTczilW.js} +2 -2
  6. package/dist/{browser-nUQt1cnB.js.map → browser-CKTczilW.js.map} +1 -1
  7. package/dist/{browser-D6cNVl0v.cjs → browser-GOg6KKOV.cjs} +2 -2
  8. package/dist/{browser-D6cNVl0v.cjs.map → browser-GOg6KKOV.cjs.map} +1 -1
  9. package/dist/cjs/holosphere.cjs +1 -1
  10. package/dist/esm/holosphere.js +25 -21
  11. package/dist/{index-CoAjtqsD.js → index-BdnrGafX.js} +2 -2
  12. package/dist/{index-CoAjtqsD.js.map → index-BdnrGafX.js.map} +1 -1
  13. package/dist/{index-BN_uoxQK.js → index-C3Cag0SV.js} +2558 -495
  14. package/dist/index-C3Cag0SV.js.map +1 -0
  15. package/dist/index-ChpSfdYS.cjs +29 -0
  16. package/dist/index-ChpSfdYS.cjs.map +1 -0
  17. package/dist/{index-DJjGSwXG.cjs → index-D_QecZNu.cjs} +2 -2
  18. package/dist/{index-DJjGSwXG.cjs.map → index-D_QecZNu.cjs.map} +1 -1
  19. package/dist/{index-Z5TstN1e.js → index-nMC3dWZ5.js} +2 -2
  20. package/dist/{index-Z5TstN1e.js.map → index-nMC3dWZ5.js.map} +1 -1
  21. package/dist/{index-Cp3tI53z.cjs → index-z5HWfWMu.cjs} +2 -2
  22. package/dist/{index-Cp3tI53z.cjs.map → index-z5HWfWMu.cjs.map} +1 -1
  23. package/dist/{indexeddb-storage-CZK5A7XH.cjs → indexeddb-storage-DWSeL-YF.cjs} +2 -2
  24. package/dist/{indexeddb-storage-CZK5A7XH.cjs.map → indexeddb-storage-DWSeL-YF.cjs.map} +1 -1
  25. package/dist/{indexeddb-storage-bpA01pAU.js → indexeddb-storage-dx01N0ET.js} +2 -2
  26. package/dist/{indexeddb-storage-bpA01pAU.js.map → indexeddb-storage-dx01N0ET.js.map} +1 -1
  27. package/dist/{memory-storage-BqhmytP_.js → memory-storage-BPIfkpcf.js} +2 -2
  28. package/dist/{memory-storage-BqhmytP_.js.map → memory-storage-BPIfkpcf.js.map} +1 -1
  29. package/dist/{memory-storage-B1k8Jszd.cjs → memory-storage-CKUGDq2d.cjs} +2 -2
  30. package/dist/{memory-storage-B1k8Jszd.cjs.map → memory-storage-CKUGDq2d.cjs.map} +1 -1
  31. package/package.json +3 -1
  32. package/scripts/test-ndk-direct.js +104 -0
  33. package/src/crypto/key-store.js +356 -0
  34. package/src/crypto/lens-keys.js +205 -0
  35. package/src/crypto/secp256k1.js +181 -18
  36. package/src/federation/handshake.js +317 -23
  37. package/src/federation/hologram.js +25 -17
  38. package/src/federation/registry.js +779 -59
  39. package/src/index.js +416 -27
  40. package/src/lib/federation-methods.js +308 -4
  41. package/src/storage/nostr-async.js +144 -86
  42. package/src/storage/nostr-client.js +77 -18
  43. package/src/storage/nostr-wrapper.js +4 -1
  44. package/src/storage/unified-storage.js +5 -4
  45. package/src/subscriptions/manager.js +1 -1
  46. package/vitest.config.js +6 -1
  47. package/dist/2019-Cp3uYhyY.cjs +0 -8
  48. package/dist/index-BN_uoxQK.js.map +0 -1
  49. package/dist/index-V8EHMYEY.cjs +0 -29
  50. 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