nip42: add initial relay auth support
Lightning-Invoice: lnbc1pjcpaakpp5gjs4f626hf8w6slx84xz3wwhlf309z503rjutckdxv6wwg5ldavsdqqcqzpgxqrrs0fppqjaxxw43p7em4g59vedv7pzl76kt0qyjfsp5qcp9de7a7t8h6zs5mcssfaqp4exrnkehqtg2hf0ary3z5cjnasvs9qyyssq55523e4h3cazhkv7f8jqf5qp0n8spykls49crsu5t3m636u3yj4qdqjkdl2nxf6jet5t2r2pfrxmm8rjpqjd3ylrzqq89m4gqt5l6ycqf92c7h Closes: https://github.com/damus-io/damus/issues/940 Signed-off-by: Charlie Fish <contact@charlie.fish> Signed-off-by: William Casarin <jb55@jb55.com> Changelog-Added: Add NIP-42 relay auth support
This commit is contained in:
committed by
William Casarin
parent
4c37bfc128
commit
84cfeb1604
@@ -16,12 +16,14 @@ damus implements the following [Nostr Implementation Possibilities][nips]
|
|||||||
- [NIP-08: Mentions][nip08]
|
- [NIP-08: Mentions][nip08]
|
||||||
- [NIP-10: Reply conventions][nip10]
|
- [NIP-10: Reply conventions][nip10]
|
||||||
- [NIP-12: Generic tag queries (hashtags)][nip12]
|
- [NIP-12: Generic tag queries (hashtags)][nip12]
|
||||||
|
- [NIP-42: Authentication of clients to relays][nip42]
|
||||||
|
|
||||||
[nips]: https://github.com/nostr-protocol/nips
|
[nips]: https://github.com/nostr-protocol/nips
|
||||||
[nip01]: https://github.com/nostr-protocol/nips/blob/master/01.md
|
[nip01]: https://github.com/nostr-protocol/nips/blob/master/01.md
|
||||||
[nip08]: https://github.com/nostr-protocol/nips/blob/master/08.md
|
[nip08]: https://github.com/nostr-protocol/nips/blob/master/08.md
|
||||||
[nip10]: https://github.com/nostr-protocol/nips/blob/master/10.md
|
[nip10]: https://github.com/nostr-protocol/nips/blob/master/10.md
|
||||||
[nip12]: https://github.com/nostr-protocol/nips/blob/master/12.md
|
[nip12]: https://github.com/nostr-protocol/nips/blob/master/12.md
|
||||||
|
[nip42]: https://github.com/nostr-protocol/nips/blob/master/42.md
|
||||||
|
|
||||||
## Getting Started on Damus
|
## Getting Started on Damus
|
||||||
|
|
||||||
|
|||||||
@@ -420,6 +420,11 @@
|
|||||||
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; };
|
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; };
|
||||||
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
|
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
|
||||||
ADFE73552AD4793100EC7326 /* QRScanNSECView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADFE73542AD4793100EC7326 /* QRScanNSECView.swift */; };
|
ADFE73552AD4793100EC7326 /* QRScanNSECView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADFE73542AD4793100EC7326 /* QRScanNSECView.swift */; };
|
||||||
|
B501062D2B363036003874F5 /* AuthIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501062C2B363036003874F5 /* AuthIntegrationTests.swift */; };
|
||||||
|
B57B4C622B312BD700A232C0 /* ReconnectRelaysNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C612B312BD700A232C0 /* ReconnectRelaysNotify.swift */; };
|
||||||
|
B57B4C642B312BFA00A232C0 /* RelayAuthenticationDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C632B312BFA00A232C0 /* RelayAuthenticationDetail.swift */; };
|
||||||
|
B57B4C662B312C3700A232C0 /* NostrAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57B4C652B312C3700A232C0 /* NostrAuth.swift */; };
|
||||||
|
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B4D1422B37D47600844320 /* NdbExtensions.swift */; };
|
||||||
BA37598A2ABCCDE40018D73B /* ImageResizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3759892ABCCDE30018D73B /* ImageResizer.swift */; };
|
BA37598A2ABCCDE40018D73B /* ImageResizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3759892ABCCDE30018D73B /* ImageResizer.swift */; };
|
||||||
BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */; };
|
BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */; };
|
||||||
BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */; };
|
BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */; };
|
||||||
@@ -1237,6 +1242,11 @@
|
|||||||
9C83F89229A937B900136C08 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = "<group>"; };
|
9C83F89229A937B900136C08 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = "<group>"; };
|
||||||
9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; };
|
9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; };
|
||||||
ADFE73542AD4793100EC7326 /* QRScanNSECView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanNSECView.swift; sourceTree = "<group>"; };
|
ADFE73542AD4793100EC7326 /* QRScanNSECView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanNSECView.swift; sourceTree = "<group>"; };
|
||||||
|
B501062C2B363036003874F5 /* AuthIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthIntegrationTests.swift; sourceTree = "<group>"; usesTabs = 0; };
|
||||||
|
B57B4C612B312BD700A232C0 /* ReconnectRelaysNotify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReconnectRelaysNotify.swift; sourceTree = "<group>"; };
|
||||||
|
B57B4C632B312BFA00A232C0 /* RelayAuthenticationDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelayAuthenticationDetail.swift; sourceTree = "<group>"; };
|
||||||
|
B57B4C652B312C3700A232C0 /* NostrAuth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NostrAuth.swift; sourceTree = "<group>"; };
|
||||||
|
B5B4D1422B37D47600844320 /* NdbExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NdbExtensions.swift; sourceTree = "<group>"; usesTabs = 0; };
|
||||||
BA3759892ABCCDE30018D73B /* ImageResizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageResizer.swift; sourceTree = "<group>"; };
|
BA3759892ABCCDE30018D73B /* ImageResizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageResizer.swift; sourceTree = "<group>"; };
|
||||||
BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureProcessor.swift; sourceTree = "<group>"; };
|
BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureProcessor.swift; sourceTree = "<group>"; };
|
||||||
BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCaptureProcessor.swift; sourceTree = "<group>"; };
|
BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCaptureProcessor.swift; sourceTree = "<group>"; };
|
||||||
@@ -1899,6 +1909,7 @@
|
|||||||
D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */,
|
D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */,
|
||||||
D798D22B2B086C7400234419 /* NostrEvent+.swift */,
|
D798D22B2B086C7400234419 /* NostrEvent+.swift */,
|
||||||
D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */,
|
D7C6787D2B2D34CC00BCEAFB /* NIP98AuthenticatedRequest.swift */,
|
||||||
|
B57B4C652B312C3700A232C0 /* NostrAuth.swift */,
|
||||||
);
|
);
|
||||||
path = Nostr;
|
path = Nostr;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2040,6 +2051,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */,
|
4C9B0DED2A65A75F00CBDA21 /* AttrStringTestExtensions.swift */,
|
||||||
|
B5B4D1422B37D47600844320 /* NdbExtensions.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2076,6 +2088,7 @@
|
|||||||
4C12536B2A76D4B00004F4B8 /* RepostedNotify.swift */,
|
4C12536B2A76D4B00004F4B8 /* RepostedNotify.swift */,
|
||||||
4C4E137A2A76D5FB00BDD832 /* MuteThreadNotify.swift */,
|
4C4E137A2A76D5FB00BDD832 /* MuteThreadNotify.swift */,
|
||||||
4C4E137C2A76D63600BDD832 /* UnmuteThreadNotify.swift */,
|
4C4E137C2A76D63600BDD832 /* UnmuteThreadNotify.swift */,
|
||||||
|
B57B4C612B312BD700A232C0 /* ReconnectRelaysNotify.swift */,
|
||||||
);
|
);
|
||||||
path = Notify;
|
path = Notify;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2353,6 +2366,7 @@
|
|||||||
D71DC1EB2A9129C3006E207C /* PostViewTests.swift */,
|
D71DC1EB2A9129C3006E207C /* PostViewTests.swift */,
|
||||||
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */,
|
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */,
|
||||||
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */,
|
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */,
|
||||||
|
B501062C2B363036003874F5 /* AuthIntegrationTests.swift */,
|
||||||
);
|
);
|
||||||
path = damusTests;
|
path = damusTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2381,6 +2395,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4CE879542996BAB900F758CC /* RelayPaidDetail.swift */,
|
4CE879542996BAB900F758CC /* RelayPaidDetail.swift */,
|
||||||
|
B57B4C632B312BFA00A232C0 /* RelayAuthenticationDetail.swift */,
|
||||||
);
|
);
|
||||||
path = Detail;
|
path = Detail;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2831,6 +2846,7 @@
|
|||||||
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
||||||
F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */,
|
F757933A29D7AECD007DEAC1 /* ImagePicker.swift in Sources */,
|
||||||
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
||||||
|
B57B4C662B312C3700A232C0 /* NostrAuth.swift in Sources */,
|
||||||
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
||||||
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
|
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
|
||||||
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
|
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
|
||||||
@@ -2884,6 +2900,7 @@
|
|||||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||||
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
|
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
|
||||||
3A5E47C52A4A6CF400C0D090 /* Trie.swift in Sources */,
|
3A5E47C52A4A6CF400C0D090 /* Trie.swift in Sources */,
|
||||||
|
B57B4C642B312BFA00A232C0 /* RelayAuthenticationDetail.swift in Sources */,
|
||||||
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
|
4C216F382871EDE300040376 /* DirectMessageModel.swift in Sources */,
|
||||||
BA3759972ABCCF360018D73B /* CameraPreview.swift in Sources */,
|
BA3759972ABCCF360018D73B /* CameraPreview.swift in Sources */,
|
||||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */,
|
||||||
@@ -3187,6 +3204,7 @@
|
|||||||
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
4C75EFB528049D790006080F /* Relay.swift in Sources */,
|
||||||
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
|
||||||
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
||||||
|
B57B4C622B312BD700A232C0 /* ReconnectRelaysNotify.swift in Sources */,
|
||||||
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */,
|
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */,
|
||||||
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
|
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
|
||||||
4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */,
|
4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */,
|
||||||
@@ -3220,6 +3238,7 @@
|
|||||||
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */,
|
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */,
|
||||||
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
|
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
|
||||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */,
|
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */,
|
||||||
|
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */,
|
||||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
||||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
||||||
4C4F14A72A2A61A30045A0B9 /* NostrScriptTests.swift in Sources */,
|
4C4F14A72A2A61A30045A0B9 /* NostrScriptTests.swift in Sources */,
|
||||||
@@ -3235,6 +3254,7 @@
|
|||||||
75AD872B2AA23A460085EF2C /* Block+Tests.swift in Sources */,
|
75AD872B2AA23A460085EF2C /* Block+Tests.swift in Sources */,
|
||||||
F944F56E29EA9CCC0067B3BF /* DamusParseContentTests.swift in Sources */,
|
F944F56E29EA9CCC0067B3BF /* DamusParseContentTests.swift in Sources */,
|
||||||
3A5E47C72A4A76C800C0D090 /* TrieTests.swift in Sources */,
|
3A5E47C72A4A76C800C0D090 /* TrieTests.swift in Sources */,
|
||||||
|
B501062D2B363036003874F5 /* AuthIntegrationTests.swift in Sources */,
|
||||||
4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */,
|
4CB883AE2976FA9300DC99E7 /* FormatTests.swift in Sources */,
|
||||||
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */,
|
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */,
|
||||||
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
|
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
|
||||||
|
|||||||
@@ -458,6 +458,9 @@ struct ContentView: View {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onReceive(handle_notify(.disconnect_relays)) { () in
|
||||||
|
damus_state.pool.disconnect()
|
||||||
|
}
|
||||||
.onChange(of: scenePhase) { (phase: ScenePhase) in
|
.onChange(of: scenePhase) { (phase: ScenePhase) in
|
||||||
switch phase {
|
switch phase {
|
||||||
case .background:
|
case .background:
|
||||||
@@ -627,7 +630,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
guard let ndb = mndb else { return }
|
guard let ndb = mndb else { return }
|
||||||
|
|
||||||
let pool = RelayPool(ndb: ndb)
|
let pool = RelayPool(ndb: ndb, keypair: keypair)
|
||||||
let model_cache = RelayModelCache()
|
let model_cache = RelayModelCache()
|
||||||
let relay_filters = RelayFilters(our_pubkey: pubkey)
|
let relay_filters = RelayFilters(our_pubkey: pubkey)
|
||||||
let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey)
|
let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey)
|
||||||
@@ -914,6 +917,8 @@ func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: St
|
|||||||
}
|
}
|
||||||
case .notice:
|
case .notice:
|
||||||
break
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ class EventsModel: ObservableObject {
|
|||||||
break
|
break
|
||||||
case .ok:
|
case .ok:
|
||||||
break
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
case .eose:
|
case .eose:
|
||||||
let txn = NdbTxn(ndb: self.state.ndb)
|
let txn = NdbTxn(ndb: self.state.ndb)
|
||||||
load_profiles(context: "events_model", profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state, txn: txn)
|
load_profiles(context: "events_model", profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state, txn: txn)
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ class FollowersModel: ObservableObject {
|
|||||||
|
|
||||||
case .ok:
|
case .ok:
|
||||||
break
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -446,6 +446,8 @@ class HomeModel {
|
|||||||
|
|
||||||
case .ok:
|
case .ok:
|
||||||
break
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
}
|
}
|
||||||
progress += 1
|
progress += 1
|
||||||
break
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ class SearchHomeModel: ObservableObject {
|
|||||||
load_profiles(context: "universe", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state, txn: txn)
|
load_profiles(context: "universe", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state, txn: txn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
case .auth:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,6 +161,8 @@ func load_profiles<Y>(context: String, profiles_subid: String, relay_id: String,
|
|||||||
break
|
break
|
||||||
case .notice:
|
case .notice:
|
||||||
break
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,9 @@ func handle_subid_event(pool: RelayPool, relay_id: String, ev: NostrConnectionEv
|
|||||||
|
|
||||||
case .eose(let subid):
|
case .eose(let subid):
|
||||||
return (subid, true)
|
return (subid, true)
|
||||||
|
|
||||||
|
case .auth:
|
||||||
|
return (nil, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ class ZapsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.state.add_zap(zap: .zap(zap))
|
self.state.add_zap(zap: .zap(zap))
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
damus/Nostr/NostrAuth.swift
Normal file
14
damus/Nostr/NostrAuth.swift
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// NostrAuth.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Charlie Fish on 12/18/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
func make_auth_request(keypair: FullKeypair, challenge_string: String, relay: Relay) -> NostrEvent? {
|
||||||
|
let tags: [[String]] = [["relay", relay.descriptor.url.id],["challenge", challenge_string]]
|
||||||
|
let event = NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 22242, tags: tags)
|
||||||
|
return event
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ enum NostrRequest {
|
|||||||
case subscribe(NostrSubscribe)
|
case subscribe(NostrSubscribe)
|
||||||
case unsubscribe(String)
|
case unsubscribe(String)
|
||||||
case event(NostrEvent)
|
case event(NostrEvent)
|
||||||
|
case auth(NostrEvent)
|
||||||
|
|
||||||
var is_write: Bool {
|
var is_write: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -47,6 +48,8 @@ enum NostrRequest {
|
|||||||
return false
|
return false
|
||||||
case .event:
|
case .event:
|
||||||
return true
|
return true
|
||||||
|
case .auth:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ enum NostrResponse {
|
|||||||
case notice(String)
|
case notice(String)
|
||||||
case eose(String)
|
case eose(String)
|
||||||
case ok(CommandResult)
|
case ok(CommandResult)
|
||||||
|
/// An [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) `auth` challenge.
|
||||||
|
///
|
||||||
|
/// The associated type of this case is the challenge string sent by the server.
|
||||||
|
case auth(String)
|
||||||
|
|
||||||
var subid: String? {
|
var subid: String? {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -34,6 +38,8 @@ enum NostrResponse {
|
|||||||
return sub_id
|
return sub_id
|
||||||
case .notice:
|
case .notice:
|
||||||
return nil
|
return nil
|
||||||
|
case .auth(let challenge_string):
|
||||||
|
return challenge_string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +100,13 @@ enum NostrResponse {
|
|||||||
case NDB_TCE_NOTICE:
|
case NDB_TCE_NOTICE:
|
||||||
free(data)
|
free(data)
|
||||||
return .notice("")
|
return .notice("")
|
||||||
|
case NDB_TCE_AUTH:
|
||||||
|
defer { free(data) }
|
||||||
|
|
||||||
|
guard let challenge_string = sized_cstr(cstr: tce.subid, len: tce.subid_len) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .auth(challenge_string)
|
||||||
default:
|
default:
|
||||||
free(data)
|
free(data)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -57,6 +57,25 @@ enum RelayFlags: Int {
|
|||||||
case broken = 1
|
case broken = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RelayAuthenticationError {
|
||||||
|
/// Only a public key was provided in keypair to sign challenge.
|
||||||
|
///
|
||||||
|
/// A private key is required to sign `auth` challenge.
|
||||||
|
case no_private_key
|
||||||
|
/// No keypair was provided to sign challenge.
|
||||||
|
case no_key
|
||||||
|
}
|
||||||
|
enum RelayAuthenticationState: Equatable {
|
||||||
|
/// No `auth` request has been made from this relay
|
||||||
|
case none
|
||||||
|
/// We have received an `auth` challenge, but have not yet replied to the challenge
|
||||||
|
case pending
|
||||||
|
/// We have received an `auth` challenge and replied with an `auth` event
|
||||||
|
case verified
|
||||||
|
/// We received an `auth` challenge but failed to reply to the challenge
|
||||||
|
case error(RelayAuthenticationError)
|
||||||
|
}
|
||||||
|
|
||||||
struct Limitations: Codable {
|
struct Limitations: Codable {
|
||||||
let payment_required: Bool?
|
let payment_required: Bool?
|
||||||
|
|
||||||
@@ -85,6 +104,7 @@ struct RelayMetadata: Codable {
|
|||||||
class Relay: Identifiable {
|
class Relay: Identifiable {
|
||||||
let descriptor: RelayDescriptor
|
let descriptor: RelayDescriptor
|
||||||
let connection: RelayConnection
|
let connection: RelayConnection
|
||||||
|
var authentication_state: RelayAuthenticationState
|
||||||
|
|
||||||
var flags: Int
|
var flags: Int
|
||||||
|
|
||||||
@@ -92,6 +112,7 @@ class Relay: Identifiable {
|
|||||||
self.flags = 0
|
self.flags = 0
|
||||||
self.descriptor = descriptor
|
self.descriptor = descriptor
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
self.authentication_state = RelayAuthenticationState.none
|
||||||
}
|
}
|
||||||
|
|
||||||
var is_broken: Bool {
|
var is_broken: Bool {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ final class RelayConnection: ObservableObject {
|
|||||||
socket.send(.string(req))
|
socket.send(.string(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(_ req: NostrRequestType) {
|
func send(_ req: NostrRequestType, callback: ((String) -> Void)? = nil) {
|
||||||
switch req {
|
switch req {
|
||||||
case .typical(let req):
|
case .typical(let req):
|
||||||
guard let req = make_nostr_req(req) else {
|
guard let req = make_nostr_req(req) else {
|
||||||
@@ -105,9 +105,11 @@ final class RelayConnection: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
send_raw(req)
|
send_raw(req)
|
||||||
|
callback?(req)
|
||||||
|
|
||||||
case .custom(let req):
|
case .custom(let req):
|
||||||
send_raw(req)
|
send_raw(req)
|
||||||
|
callback?(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,9 +203,20 @@ func make_nostr_req(_ req: NostrRequest) -> String? {
|
|||||||
return make_nostr_unsubscribe_req(sub_id)
|
return make_nostr_unsubscribe_req(sub_id)
|
||||||
case .event(let ev):
|
case .event(let ev):
|
||||||
return make_nostr_push_event(ev: ev)
|
return make_nostr_push_event(ev: ev)
|
||||||
|
case .auth(let ev):
|
||||||
|
return make_nostr_auth_event(ev: ev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func make_nostr_auth_event(ev: NostrEvent) -> String? {
|
||||||
|
guard let event = encode_json(ev) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let encoded = "[\"AUTH\",\(event)]"
|
||||||
|
print(encoded)
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
func make_nostr_push_event(ev: NostrEvent) -> String? {
|
func make_nostr_push_event(ev: NostrEvent) -> String? {
|
||||||
guard let event = encode_json(ev) else {
|
guard let event = encode_json(ev) else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -31,13 +31,17 @@ class RelayPool {
|
|||||||
var seen: Set<SeenEvent> = Set()
|
var seen: Set<SeenEvent> = Set()
|
||||||
var counts: [String: UInt64] = [:]
|
var counts: [String: UInt64] = [:]
|
||||||
var ndb: Ndb
|
var ndb: Ndb
|
||||||
|
var keypair: Keypair?
|
||||||
|
var message_received_function: (((String, RelayDescriptor)) -> Void)?
|
||||||
|
var message_sent_function: (((String, Relay)) -> Void)?
|
||||||
|
|
||||||
private let network_monitor = NWPathMonitor()
|
private let network_monitor = NWPathMonitor()
|
||||||
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
||||||
private var last_network_status: NWPath.Status = .unsatisfied
|
private var last_network_status: NWPath.Status = .unsatisfied
|
||||||
|
|
||||||
init(ndb: Ndb) {
|
init(ndb: Ndb, keypair: Keypair? = nil) {
|
||||||
self.ndb = ndb
|
self.ndb = ndb
|
||||||
|
self.keypair = keypair
|
||||||
|
|
||||||
network_monitor.pathUpdateHandler = { [weak self] path in
|
network_monitor.pathUpdateHandler = { [weak self] path in
|
||||||
if (path.status == .satisfied || path.status == .requiresConnection) && self?.last_network_status != path.status {
|
if (path.status == .satisfied || path.status == .requiresConnection) && self?.last_network_status != path.status {
|
||||||
@@ -121,6 +125,7 @@ class RelayPool {
|
|||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
let _ = self.ndb.process_event(str)
|
let _ = self.ndb.process_event(str)
|
||||||
|
self.message_received_function?((str, desc))
|
||||||
})
|
})
|
||||||
let relay = Relay(descriptor: desc, connection: conn)
|
let relay = Relay(descriptor: desc, connection: conn)
|
||||||
self.relays.append(relay)
|
self.relays.append(relay)
|
||||||
@@ -244,7 +249,9 @@ class RelayPool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
relay.connection.send(req)
|
relay.connection.send(req, callback: { str in
|
||||||
|
self.message_sent_function?((str, relay))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +306,33 @@ class RelayPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle auth
|
||||||
|
if case let .nostr_event(nostrResponse) = event,
|
||||||
|
case let .auth(challenge_string) = nostrResponse {
|
||||||
|
if let relay = get_relay(relay_id) {
|
||||||
|
print("received auth request from \(relay.descriptor.url.id)")
|
||||||
|
relay.authentication_state = .pending
|
||||||
|
if let keypair {
|
||||||
|
if let fullKeypair = keypair.to_full() {
|
||||||
|
if let authRequest = make_auth_request(keypair: fullKeypair, challenge_string: challenge_string, relay: relay) {
|
||||||
|
send(.auth(authRequest), to: [relay_id], skip_ephemeral: false)
|
||||||
|
relay.authentication_state = .verified
|
||||||
|
} else {
|
||||||
|
print("failed to make auth request")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("keypair provided did not contain private key, can not sign auth request")
|
||||||
|
relay.authentication_state = .error(.no_private_key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("no keypair to reply to auth request")
|
||||||
|
relay.authentication_state = .error(.no_key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("no relay found for \(relay_id)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for handler in handlers {
|
for handler in handlers {
|
||||||
handler.callback(relay_id, event)
|
handler.callback(relay_id, event)
|
||||||
}
|
}
|
||||||
|
|||||||
26
damus/Notify/ReconnectRelaysNotify.swift
Normal file
26
damus/Notify/ReconnectRelaysNotify.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// ReconnectRelaysNotify.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Charlie Fish on 12/18/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct ReconnectRelaysNotify: Notify {
|
||||||
|
typealias Payload = ()
|
||||||
|
var payload: ()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotifyHandler {
|
||||||
|
static var disconnect_relays: NotifyHandler<ReconnectRelaysNotify> {
|
||||||
|
.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Notifications {
|
||||||
|
/// Reconnects all relays.
|
||||||
|
static var disconnect_relays: Notifications<ReconnectRelaysNotify> {
|
||||||
|
.init(.init(payload: ()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,9 @@ class SuggestedUsersViewModel: ObservableObject {
|
|||||||
|
|
||||||
case .ok:
|
case .ok:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
damus/Views/Relays/Detail/RelayAuthenticationDetail.swift
Normal file
35
damus/Views/Relays/Detail/RelayAuthenticationDetail.swift
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// RelayAuthenticationDetail.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Charlie Fish on 12/18/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct RelayAuthenticationDetail: View {
|
||||||
|
let state: RelayAuthenticationState
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
switch state {
|
||||||
|
case .none:
|
||||||
|
EmptyView()
|
||||||
|
case .pending:
|
||||||
|
Text(NSLocalizedString("Pending", comment: "Label to display that authentication to a server is pending."))
|
||||||
|
case .verified:
|
||||||
|
Text(NSLocalizedString("Authenticated", comment: "Label to display that authentication to a server has succeeded."))
|
||||||
|
.foregroundStyle(DamusColors.success)
|
||||||
|
case .error:
|
||||||
|
Text(NSLocalizedString("Error", comment: "Label to display that authentication to a server has failed."))
|
||||||
|
.foregroundStyle(DamusColors.danger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RelayAuthenticationDetail_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
RelayAuthenticationDetail(state: .none)
|
||||||
|
RelayAuthenticationDetail(state: .pending)
|
||||||
|
RelayAuthenticationDetail(state: .verified)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,6 +93,13 @@ struct RelayDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let authentication_state: RelayAuthenticationState = relay_object?.authentication_state,
|
||||||
|
authentication_state != .none {
|
||||||
|
Section(NSLocalizedString("Authentication", comment: "Header label to display authentication details for a given relay.")) {
|
||||||
|
RelayAuthenticationDetail(state: authentication_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let pubkey = nip11?.pubkey {
|
if let pubkey = nip11?.pubkey {
|
||||||
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
|
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
|
||||||
UserViewRow(damus_state: state, pubkey: pubkey)
|
UserViewRow(damus_state: state, pubkey: pubkey)
|
||||||
@@ -176,8 +183,12 @@ struct RelayDetailView: View {
|
|||||||
return attrString
|
return attrString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var relay_object: Relay? {
|
||||||
|
state.pool.get_relay(relay)
|
||||||
|
}
|
||||||
|
|
||||||
private var relay_connection: RelayConnection? {
|
private var relay_connection: RelayConnection? {
|
||||||
state.pool.get_relay(relay)?.connection
|
relay_object?.connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ struct SaveKeysView: View {
|
|||||||
break
|
break
|
||||||
case .ok:
|
case .ok:
|
||||||
break
|
break
|
||||||
|
case .auth:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ struct MainView: View {
|
|||||||
.onReceive(handle_notify(.logout)) { () in
|
.onReceive(handle_notify(.logout)) { () in
|
||||||
try? clear_keypair()
|
try? clear_keypair()
|
||||||
keypair = nil
|
keypair = nil
|
||||||
|
// We need to disconnect and reconnect to all relays when the user signs out
|
||||||
|
// This is to conform to NIP-42 and ensure we aren't persisting old connections
|
||||||
|
notify(.disconnect_relays)
|
||||||
}
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||||
orientationTracker.setDeviceMajorAxis()
|
orientationTracker.setDeviceMajorAxis()
|
||||||
|
|||||||
180
damusTests/AuthIntegrationTests.swift
Normal file
180
damusTests/AuthIntegrationTests.swift
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
//
|
||||||
|
// AuthIntegrationTests.swift
|
||||||
|
// damusTests
|
||||||
|
//
|
||||||
|
// Created by Charlie Fish on 12/22/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import damus
|
||||||
|
|
||||||
|
final class AuthIntegrationTests: XCTestCase {
|
||||||
|
func testAuthIntegrationFilterNostrWine() {
|
||||||
|
// Create relay pool and connect to `wss://filter.nostr.wine`
|
||||||
|
let relay_url = RelayURL("wss://filter.nostr.wine")!
|
||||||
|
var received_messages: [String] = []
|
||||||
|
var sent_messages: [String] = []
|
||||||
|
let keypair: Keypair = generate_new_keypair().to_keypair()
|
||||||
|
let pool = RelayPool(ndb: Ndb.test, keypair: keypair)
|
||||||
|
pool.message_received_function = { obj in
|
||||||
|
let str = obj.0
|
||||||
|
let descriptor = obj.1
|
||||||
|
|
||||||
|
if descriptor.url.id != relay_url.id {
|
||||||
|
XCTFail("The descriptor we recieved the message from should equal the relayURL")
|
||||||
|
}
|
||||||
|
|
||||||
|
received_messages.append(str)
|
||||||
|
}
|
||||||
|
pool.message_sent_function = { obj in
|
||||||
|
let str = obj.0
|
||||||
|
let relay = obj.1
|
||||||
|
|
||||||
|
if relay.descriptor.url.id != relay_url.id {
|
||||||
|
XCTFail("The descriptor we sent the message to should equal the relayURL")
|
||||||
|
}
|
||||||
|
|
||||||
|
sent_messages.append(str)
|
||||||
|
}
|
||||||
|
XCTAssertEqual(pool.relays.count, 0)
|
||||||
|
let relay_descriptor = RelayDescriptor.init(url: relay_url, info: .rw)
|
||||||
|
try! pool.add_relay(relay_descriptor)
|
||||||
|
XCTAssertEqual(pool.relays.count, 1)
|
||||||
|
let connection_expectation = XCTestExpectation(description: "Waiting for connection")
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
|
||||||
|
if pool.num_connected == 1 {
|
||||||
|
connection_expectation.fulfill()
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wait(for: [connection_expectation], timeout: 30.0)
|
||||||
|
XCTAssertEqual(pool.num_connected, 1)
|
||||||
|
// Assert that AUTH message has been received
|
||||||
|
XCTAssertTrue(received_messages.count >= 1, "expected recieved_messages to be >= 1")
|
||||||
|
let json_received = try! JSONSerialization.jsonObject(with: received_messages[0].data(using: .utf8)!, options: []) as! [Any]
|
||||||
|
XCTAssertEqual(json_received[0] as! String, "AUTH")
|
||||||
|
// Assert that we've replied with the AUTH response
|
||||||
|
XCTAssertEqual(sent_messages.count, 1)
|
||||||
|
let json_sent = try! JSONSerialization.jsonObject(with: sent_messages[0].data(using: .utf8)!, options: []) as! [Any]
|
||||||
|
XCTAssertEqual(json_sent[0] as! String, "AUTH")
|
||||||
|
let sent_msg = json_sent[1] as! [String: Any]
|
||||||
|
XCTAssertEqual(sent_msg["kind"] as! Int, 22242)
|
||||||
|
XCTAssertEqual((sent_msg["tags"] as! [[String]]).first { $0[0] == "challenge" }![1], json_received[1] as! String)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAuthIntegrationRelayDamusIo() {
|
||||||
|
// Create relay pool and connect to `wss://relay.damus.io`
|
||||||
|
let relay_url = RelayURL("wss://relay.damus.io")!
|
||||||
|
var received_messages: [String] = []
|
||||||
|
var sent_messages: [String] = []
|
||||||
|
let keypair: Keypair = generate_new_keypair().to_keypair()
|
||||||
|
let pool = RelayPool(ndb: Ndb.test, keypair: keypair)
|
||||||
|
pool.message_received_function = { obj in
|
||||||
|
let str = obj.0
|
||||||
|
let descriptor = obj.1
|
||||||
|
|
||||||
|
if descriptor.url.id != relay_url.id {
|
||||||
|
XCTFail("The descriptor we recieved the message from should equal the relayURL")
|
||||||
|
}
|
||||||
|
|
||||||
|
received_messages.append(str)
|
||||||
|
}
|
||||||
|
pool.message_sent_function = { obj in
|
||||||
|
let str = obj.0
|
||||||
|
let relay = obj.1
|
||||||
|
|
||||||
|
if relay.descriptor.url.id != relay_url.id {
|
||||||
|
XCTFail("The descriptor we sent the message to should equal the relayURL")
|
||||||
|
}
|
||||||
|
|
||||||
|
sent_messages.append(str)
|
||||||
|
}
|
||||||
|
XCTAssertEqual(pool.relays.count, 0)
|
||||||
|
let relay_descriptor = RelayDescriptor.init(url: relay_url, info: .rw)
|
||||||
|
try! pool.add_relay(relay_descriptor)
|
||||||
|
XCTAssertEqual(pool.relays.count, 1)
|
||||||
|
let connection_expectation = XCTestExpectation(description: "Waiting for connection")
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
|
||||||
|
if pool.num_connected == 1 {
|
||||||
|
connection_expectation.fulfill()
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wait(for: [connection_expectation], timeout: 30.0)
|
||||||
|
XCTAssertEqual(pool.num_connected, 1)
|
||||||
|
// Assert that no AUTH messages have been received
|
||||||
|
XCTAssertEqual(received_messages.count, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAuthIntegrationNostrWine() {
|
||||||
|
// Create relay pool and connect to `wss://nostr.wine`
|
||||||
|
let relay_url = RelayURL("wss://nostr.wine")!
|
||||||
|
var received_messages: [String] = []
|
||||||
|
var sent_messages: [String] = []
|
||||||
|
let keypair: Keypair = generate_new_keypair().to_keypair()
|
||||||
|
let pool = RelayPool(ndb: Ndb.test, keypair: keypair)
|
||||||
|
pool.message_received_function = { obj in
|
||||||
|
let str = obj.0
|
||||||
|
let descriptor = obj.1
|
||||||
|
|
||||||
|
if descriptor.url.id != relay_url.id {
|
||||||
|
XCTFail("The descriptor we recieved the message from should equal the relayURL")
|
||||||
|
}
|
||||||
|
|
||||||
|
received_messages.append(str)
|
||||||
|
}
|
||||||
|
pool.message_sent_function = { obj in
|
||||||
|
let str = obj.0
|
||||||
|
let relay = obj.1
|
||||||
|
|
||||||
|
if relay.descriptor.url.id != relay_url.id {
|
||||||
|
XCTFail("The descriptor we sent the message to should equal the relayURL")
|
||||||
|
}
|
||||||
|
|
||||||
|
sent_messages.append(str)
|
||||||
|
}
|
||||||
|
XCTAssertEqual(pool.relays.count, 0)
|
||||||
|
let relay_descriptor = RelayDescriptor.init(url: relay_url, info: .rw)
|
||||||
|
try! pool.add_relay(relay_descriptor)
|
||||||
|
XCTAssertEqual(pool.relays.count, 1)
|
||||||
|
let connection_expectation = XCTestExpectation(description: "Waiting for connection")
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
|
||||||
|
if pool.num_connected == 1 {
|
||||||
|
connection_expectation.fulfill()
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wait(for: [connection_expectation], timeout: 30.0)
|
||||||
|
XCTAssertEqual(pool.num_connected, 1)
|
||||||
|
// Assert that no AUTH messages have been received
|
||||||
|
XCTAssertEqual(received_messages.count, 0)
|
||||||
|
// Generate UUID for subscription_id
|
||||||
|
let uuid = UUID().uuidString
|
||||||
|
// Send `["REQ", subscription_id, {"kinds": [4]}]`
|
||||||
|
let subscribe = NostrSubscribe(filters: [
|
||||||
|
NostrFilter(kinds: [.dm])
|
||||||
|
], sub_id: uuid)
|
||||||
|
pool.send(NostrRequest.subscribe(subscribe))
|
||||||
|
// Wait for AUTH message to have been received & sent
|
||||||
|
let msg_expectation = XCTestExpectation(description: "Waiting for messages")
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
|
||||||
|
if received_messages.count >= 2 && sent_messages.count >= 2 {
|
||||||
|
msg_expectation.fulfill()
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wait(for: [msg_expectation], timeout: 30.0)
|
||||||
|
// Assert that AUTH message has been received
|
||||||
|
XCTAssertTrue(received_messages.count >= 1, "expected recieved_messages to be >= 1")
|
||||||
|
let json_received = try! JSONSerialization.jsonObject(with: received_messages[0].data(using: .utf8)!, options: []) as! [Any]
|
||||||
|
XCTAssertEqual(json_received[0] as! String, "AUTH")
|
||||||
|
// Assert that we've replied with the AUTH response
|
||||||
|
XCTAssertEqual(sent_messages.count, 2)
|
||||||
|
let json_sent = try! JSONSerialization.jsonObject(with: sent_messages[1].data(using: .utf8)!, options: []) as! [Any]
|
||||||
|
XCTAssertEqual(json_sent[0] as! String, "AUTH")
|
||||||
|
let sent_msg = json_sent[1] as! [String: Any]
|
||||||
|
XCTAssertEqual(sent_msg["kind"] as! Int, 22242)
|
||||||
|
XCTAssertEqual((sent_msg["tags"] as! [[String]]).first { $0[0] == "challenge" }![1], json_received[1] as! String)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,18 +13,7 @@ func generate_test_damus_state(
|
|||||||
mock_profile_info: [Pubkey: Profile]?
|
mock_profile_info: [Pubkey: Profile]?
|
||||||
) -> DamusState {
|
) -> DamusState {
|
||||||
// Create a unique temporary directory
|
// Create a unique temporary directory
|
||||||
var tempDir: String!
|
let ndb = Ndb.test
|
||||||
do {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let temp = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
||||||
try fileManager.createDirectory(at: temp, withIntermediateDirectories: true, attributes: nil)
|
|
||||||
tempDir = temp.absoluteString
|
|
||||||
} catch {
|
|
||||||
tempDir = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
print("opening \(tempDir!)")
|
|
||||||
let ndb = try! Ndb(path: tempDir)!
|
|
||||||
let our_pubkey = test_pubkey
|
let our_pubkey = test_pubkey
|
||||||
let pool = RelayPool(ndb: ndb)
|
let pool = RelayPool(ndb: ndb)
|
||||||
let settings = UserSettingsStore()
|
let settings = UserSettingsStore()
|
||||||
|
|||||||
@@ -17,6 +17,33 @@ final class RequestTests: XCTestCase {
|
|||||||
XCTAssertEqual(result, expectedResult)
|
XCTAssertEqual(result, expectedResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testMakeAuthRequest() {
|
||||||
|
let challenge_string = "8bc847dd-f2f6-4b3a-9c8a-71776ad9b071"
|
||||||
|
let url = RelayURL("wss://example.com")!
|
||||||
|
let relayInfo = RelayInfo(read: true, write: true)
|
||||||
|
let relayDescriptor = RelayDescriptor(url: url, info: relayInfo)
|
||||||
|
let relayConnection = RelayConnection(url: url) { _ in
|
||||||
|
} processEvent: { _ in
|
||||||
|
}
|
||||||
|
|
||||||
|
let relay = Relay(descriptor: relayDescriptor, connection: relayConnection)
|
||||||
|
let event = make_auth_request(keypair: FullKeypair.init(pubkey: Pubkey.empty, privkey: Privkey.empty), challenge_string: challenge_string, relay: relay)!
|
||||||
|
|
||||||
|
let result = make_nostr_auth_event(ev: event)
|
||||||
|
let json = try! JSONSerialization.jsonObject(with: result!.data(using: .utf8)!, options: []) as! [Any]
|
||||||
|
|
||||||
|
XCTAssertEqual(json[0] as! String, "AUTH")
|
||||||
|
let dictionary = json[1] as! [String: Any]
|
||||||
|
XCTAssertEqual(dictionary["content"] as! String, "")
|
||||||
|
XCTAssertEqual(dictionary["kind"] as! Int, 22242)
|
||||||
|
XCTAssertEqual(dictionary["sig"] as! String, String(repeating: "0", count: 128))
|
||||||
|
XCTAssertEqual(dictionary["pubkey"] as! String, String(repeating: "0", count: 64))
|
||||||
|
let tags = dictionary["tags"] as! [[String]]
|
||||||
|
XCTAssertEqual(tags.first { $0[0] == "relay" }![1], "wss://example.com")
|
||||||
|
XCTAssertEqual(tags.first { $0[0] == "challenge" }![1], challenge_string)
|
||||||
|
XCTAssertEqual(dictionary["id"] as! String, String(repeating: "0", count: 64))
|
||||||
|
}
|
||||||
|
|
||||||
/* FIXME: these tests depend on order of json fields which is undefined
|
/* FIXME: these tests depend on order of json fields which is undefined
|
||||||
func testMakePushEvent() {
|
func testMakePushEvent() {
|
||||||
let now = Int64(Date().timeIntervalSince1970)
|
let now = Int64(Date().timeIntervalSince1970)
|
||||||
|
|||||||
26
damusTests/Util/NdbExtensions.swift
Normal file
26
damusTests/Util/NdbExtensions.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// NdbExtensions.swift
|
||||||
|
// damusTests
|
||||||
|
//
|
||||||
|
// Created by Charlie Fish on 12/23/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
@testable import damus
|
||||||
|
|
||||||
|
extension Ndb {
|
||||||
|
static var test: Ndb {
|
||||||
|
var tempDir: String!
|
||||||
|
do {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
let temp = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||||
|
try fileManager.createDirectory(at: temp, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
tempDir = temp.absoluteString
|
||||||
|
} catch {
|
||||||
|
tempDir = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
print("opening \(tempDir!)")
|
||||||
|
return Ndb(path: tempDir)!
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4016,6 +4016,17 @@ int ndb_ws_event_from_json(const char *json, int len, struct ndb_tce *tce,
|
|||||||
tce->command_result.msg = json + tok->start;
|
tce->command_result.msg = json + tok->start;
|
||||||
tce->command_result.msglen = toksize(tok);
|
tce->command_result.msglen = toksize(tok);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
} else if (tok_len == 4 && !memcmp("AUTH", json + tok->start, 4)) {
|
||||||
|
tce->evtype = NDB_TCE_AUTH;
|
||||||
|
|
||||||
|
tok = &parser.toks[parser.i++];
|
||||||
|
if (tok->type != JSMN_STRING)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
tce->subid = json + tok->start;
|
||||||
|
tce->subid_len = toksize(tok);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ enum tce_type {
|
|||||||
NDB_TCE_OK = 0x2,
|
NDB_TCE_OK = 0x2,
|
||||||
NDB_TCE_NOTICE = 0x3,
|
NDB_TCE_NOTICE = 0x3,
|
||||||
NDB_TCE_EOSE = 0x4,
|
NDB_TCE_EOSE = 0x4,
|
||||||
|
NDB_TCE_AUTH = 0x5,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ndb_ingest_filter_action {
|
enum ndb_ingest_filter_action {
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ enum NScriptEventType: Int {
|
|||||||
case note = 2
|
case note = 2
|
||||||
case notice = 3
|
case notice = 3
|
||||||
case eose = 4
|
case eose = 4
|
||||||
|
case auth = 5
|
||||||
|
|
||||||
init(resp: NostrResponse) {
|
init(resp: NostrResponse) {
|
||||||
switch resp {
|
switch resp {
|
||||||
@@ -204,6 +205,8 @@ enum NScriptEventType: Int {
|
|||||||
self = .eose
|
self = .eose
|
||||||
case .ok:
|
case .ok:
|
||||||
self = .ok
|
self = .ok
|
||||||
|
case .auth:
|
||||||
|
self = .auth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user