damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 749364189676eda0465b0a9799227b0fdf31d226
parent 937009167f7814f860575f6d29a60a382fcfc12e
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 18 Nov 2022 17:51:01 -0800

Initial NIP05 code

Also add the ability to login with a NIP05 id

Diffstat:
Mdamus/ContentView.swift | 2+-
Mdamus/Views/LoginView.swift | 161++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
2 files changed, 119 insertions(+), 44 deletions(-)

diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -9,7 +9,7 @@ import SwiftUI import Starscream import Kingfisher -let BOOTSTRAP_RELAYS = [ +var BOOTSTRAP_RELAYS = [ "wss://relay.damus.io", "wss://nostr-relay.wlvs.space", "wss://nostr.oxtr.dev", diff --git a/damus/Views/LoginView.swift b/damus/Views/LoginView.swift @@ -11,14 +11,19 @@ enum ParsedKey { case pub(String) case priv(String) case hex(String) - + case nip05(String) + var is_pub: Bool { if case .pub = self { return true } + + if case .nip05 = self { + return true + } return false } - + var is_hex: Bool { if case .hex = self { return true @@ -31,19 +36,68 @@ struct LoginView: View { @State var key: String = "" @State var is_pubkey: Bool = false @State var error: String? = nil - + func get_error(parsed_key: ParsedKey?) -> String? { if self.error != nil { return self.error } - + if !key.isEmpty && parsed_key == nil { return "Invalid key" } - + return nil } - + + func process_login(_ key: ParsedKey, is_pubkey: Bool) -> Bool { + switch key { + case .priv(let priv): + save_privkey(privkey: priv) + guard let pk = privkey_to_pubkey(privkey: priv) else { + return false + } + save_pubkey(pubkey: pk) + + case .pub(let pub): + clear_saved_privkey() + save_pubkey(pubkey: pub) + + case .nip05(let id): + Task.init { + guard let nip05 = await get_nip05_pubkey(id: id) else { + self.error = "Could not fetch pubkey" + return + } + + for relay in nip05.relays { + if !(BOOTSTRAP_RELAYS.contains { $0 == relay }) { + BOOTSTRAP_RELAYS.append(relay) + } + } + save_pubkey(pubkey: nip05.pubkey) + + notify(.login, ()) + } + + + case .hex(let hexstr): + if is_pubkey { + clear_saved_privkey() + save_pubkey(pubkey: hexstr) + } else { + save_privkey(privkey: hexstr) + guard let pk = privkey_to_pubkey(privkey: hexstr) else { + return false + } + save_pubkey(pubkey: pk) + } + } + + notify(.login, ()) + return true +} + + var body: some View { ZStack(alignment: .top) { DamusGradient() @@ -52,15 +106,15 @@ struct LoginView: View { .foregroundColor(.white) .font(.title) .padding() - + Text("Enter your account key to login:") .foregroundColor(.white) .padding() - + KeyInput("nsec1...", key: $key) - + let parsed = parse_key(key) - + if parsed?.is_hex ?? false { Text("This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key.") .font(.subheadline.bold()) @@ -68,19 +122,19 @@ struct LoginView: View { PubkeySwitch(isOn: $is_pubkey) .padding() } - + if let error = get_error(parsed_key: parsed) { Text(error) .foregroundColor(.red) .padding() } - + if parsed?.is_pub ?? false { Text("This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective.") .foregroundColor(.white) .padding() } - + if let p = parsed { DamusWhiteButton("Login") { if !process_login(p, is_pubkey: self.is_pubkey) { @@ -113,10 +167,15 @@ func parse_key(_ thekey: String) -> ParsedKey? { if key.count > 0 && key.first! == "@" { key = String(key.dropFirst()) } + if hex_decode(key) != nil { return .hex(key) } - + + if (key.contains { $0 == "@" }) { + return .nip05(key) + } + if let bech_key = decode_bech32_key(key) { switch bech_key { case .pub(let pk): @@ -125,49 +184,65 @@ func parse_key(_ thekey: String) -> ParsedKey? { return .priv(sec) } } - + return nil } -func process_login(_ key: ParsedKey, is_pubkey: Bool) -> Bool { - switch key { - case .priv(let priv): - save_privkey(privkey: priv) - guard let pk = privkey_to_pubkey(privkey: priv) else { - return false - } - save_pubkey(pubkey: pk) - - case .pub(let pub): - clear_saved_privkey() - save_pubkey(pubkey: pub) - - case .hex(let hexstr): - if is_pubkey { - clear_saved_privkey() - save_pubkey(pubkey: hexstr) - } else { - save_privkey(privkey: hexstr) - guard let pk = privkey_to_pubkey(privkey: hexstr) else { - return false - } - save_pubkey(pubkey: pk) +struct NIP05Result: Decodable { + let names: Dictionary<String, String> + let relays: Dictionary<String, [String]>? +} + +struct NIP05User { + let pubkey: String + let relays: [String] +} + +func get_nip05_pubkey(id: String) async -> NIP05User? { + let parts = id.components(separatedBy: "@") + + guard parts.count == 2 else { + return nil + } + + let user = parts[0] + let host = parts[1] + + guard let url = URL(string: "https://\(host)/.well-known/nostr.json?name=\(user)") else { + return nil + } + + guard let (data, _) = try? await URLSession.shared.data(for: URLRequest(url: url)) else { + return nil + } + + guard let json: NIP05Result = decode_data(data) else { + return nil + } + + guard let pubkey = json.names[user] else { + return nil + } + + var relays: [String] = [] + if let rs = json.relays { + if let rs = rs[user] { + relays = rs } } - - notify(.login, ()) - return true + + return NIP05User(pubkey: pubkey, relays: relays) } struct KeyInput: View { let title: String let key: Binding<String> - + init(_ title: String, key: Binding<String>) { self.title = title self.key = key } - + var body: some View { TextField("", text: key) .placeholder(when: key.wrappedValue.isEmpty) {