mirror of
git://jb55.com/damus
synced 2024-09-30 00:40:45 +00:00
login: add nsec qr-scanning
- Allow scanning of QR codes, and if detects a nsec, will provide it to the login prompt. - If nsec is found, provides option to keep nsec in keychain; default is to not store - User stays logged in until they logout, or app is force-quit if nsec is not stored. damusApp.swift: Obtains keypair from the notification generated to allow login. LoginView.swift: New views allowing for adding and logic handling the QR reader in QRScanNSECView.swift to enable QR scan for nsec. QRScanNSECView.swift: New view to scan for QR code. The sparkling magnifying glass is enabled if the view calling the QR view changes the privKeyFound bound variable. Tipjar: npub1el277q4kesp8vhs7rq6qkwnhpxfp345u7tnuxykwr67d9wg0wvyslam5n0 Closes: https://github.com/damus-io/damus/issues/1291 Changelog-Added: Add QR scan nsec logins. Signed-off-by: Jericho Hasselbush <jericho@sal-et-lucem.com> Reviewed-by: William Casarin <jb55@jb55.com> Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
parent
cf243e39c9
commit
439f9974c5
@ -419,6 +419,7 @@
|
||||
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
||||
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; };
|
||||
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
|
||||
ADFE73552AD4793100EC7326 /* QRScanNSECView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADFE73542AD4793100EC7326 /* QRScanNSECView.swift */; };
|
||||
BA37598A2ABCCDE40018D73B /* ImageResizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA3759892ABCCDE30018D73B /* ImageResizer.swift */; };
|
||||
BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */; };
|
||||
BA37598E2ABCCE500018D73B /* VideoCaptureProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */; };
|
||||
@ -1113,6 +1114,7 @@
|
||||
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.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>"; };
|
||||
ADFE73542AD4793100EC7326 /* QRScanNSECView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanNSECView.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>"; };
|
||||
BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCaptureProcessor.swift; sourceTree = "<group>"; };
|
||||
@ -1685,6 +1687,7 @@
|
||||
4C3AC79E2833115300E1F516 /* FollowButtonView.swift */,
|
||||
4C3AC79C2833036D00E1F516 /* FollowingView.swift */,
|
||||
4C90BD17283A9EE5008EE7EF /* LoginView.swift */,
|
||||
ADFE73542AD4793100EC7326 /* QRScanNSECView.swift */,
|
||||
4C363A8D28236FE4006E126D /* NoteContentView.swift */,
|
||||
4C75EFAC28049CFB0006080F /* PostButton.swift */,
|
||||
4C75EFA327FA577B0006080F /* PostView.swift */,
|
||||
@ -2564,6 +2567,7 @@
|
||||
4C4793042A993DC000489948 /* midl.c in Sources */,
|
||||
4C4793012A993CDA00489948 /* mdb.c in Sources */,
|
||||
4CE9FBBA2A6B3C63007E485C /* nostrdb.c in Sources */,
|
||||
ADFE73552AD4793100EC7326 /* QRScanNSECView.swift in Sources */,
|
||||
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
|
||||
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */,
|
||||
4C32B9522A9AD44700DC3548 /* Message.swift in Sources */,
|
||||
|
@ -30,6 +30,13 @@ enum ParsedKey {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var is_priv: Bool {
|
||||
if case .priv = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginView: View {
|
||||
@ -37,6 +44,7 @@ struct LoginView: View {
|
||||
@State var is_pubkey: Bool = false
|
||||
@State var error: String? = nil
|
||||
@State private var credential_handler = CredentialHandler()
|
||||
@State private var shouldSaveKey: Bool = true
|
||||
var nav: NavigationCoordinator
|
||||
|
||||
func get_error(parsed_key: ParsedKey?) -> String? {
|
||||
@ -57,7 +65,7 @@ struct LoginView: View {
|
||||
SignInHeader()
|
||||
.padding(.top, 100)
|
||||
|
||||
SignInEntry(key: $key)
|
||||
SignInEntry(key: $key, shouldSaveKey: $shouldSaveKey)
|
||||
|
||||
let parsed = parse_key(key)
|
||||
|
||||
@ -83,7 +91,7 @@ struct LoginView: View {
|
||||
Button(action: {
|
||||
Task {
|
||||
do {
|
||||
try await process_login(p, is_pubkey: is_pubkey)
|
||||
try await process_login(p, is_pubkey: is_pubkey, shouldSaveKey: shouldSaveKey)
|
||||
} catch {
|
||||
self.error = error.localizedDescription
|
||||
}
|
||||
@ -168,7 +176,8 @@ enum LoginError: LocalizedError {
|
||||
}
|
||||
}
|
||||
|
||||
func process_login(_ key: ParsedKey, is_pubkey: Bool) async throws {
|
||||
func process_login(_ key: ParsedKey, is_pubkey: Bool, shouldSaveKey: Bool = true) async throws {
|
||||
if shouldSaveKey {
|
||||
switch key {
|
||||
case .priv(let priv):
|
||||
try handle_privkey(priv)
|
||||
@ -201,6 +210,7 @@ func process_login(_ key: ParsedKey, is_pubkey: Bool) async throws {
|
||||
try handle_privkey(privkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handle_privkey(_ privkey: Privkey) throws {
|
||||
try save_privkey(privkey: privkey)
|
||||
@ -213,7 +223,16 @@ func process_login(_ key: ParsedKey, is_pubkey: Bool) async throws {
|
||||
save_pubkey(pubkey: pk)
|
||||
}
|
||||
|
||||
guard let keypair = get_saved_keypair() else {
|
||||
func handle_transient_privkey(_ key: ParsedKey) -> Keypair? {
|
||||
if case let .priv(priv) = key, let pubkey = privkey_to_pubkey(privkey: priv) {
|
||||
return Keypair(pubkey: pubkey, privkey: priv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let keypair = shouldSaveKey ? get_saved_keypair() : handle_transient_privkey(key)
|
||||
|
||||
guard let keypair = keypair else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -265,11 +284,15 @@ func get_nip05_pubkey(id: String) async -> NIP05User? {
|
||||
struct KeyInput: View {
|
||||
let title: String
|
||||
let key: Binding<String>
|
||||
let shouldSaveKey: Binding<Bool>
|
||||
var privKeyFound: Binding<Bool>
|
||||
@State private var is_secured: Bool = true
|
||||
|
||||
init(_ title: String, key: Binding<String>) {
|
||||
init(_ title: String, key: Binding<String>, shouldSaveKey: Binding<Bool>, privKeyFound: Binding<Bool>) {
|
||||
self.title = title
|
||||
self.key = key
|
||||
self.shouldSaveKey = shouldSaveKey
|
||||
self.privKeyFound = privKeyFound
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -281,6 +304,8 @@ struct KeyInput: View {
|
||||
self.key.wrappedValue = pastedkey
|
||||
}
|
||||
}
|
||||
SignInScan(shouldSaveKey: shouldSaveKey, loginKey: key, privKeyFound: privKeyFound)
|
||||
|
||||
if is_secured {
|
||||
SecureField("", text: key)
|
||||
.nsecLoginStyle(key: key.wrappedValue, title: title)
|
||||
@ -323,17 +348,78 @@ struct SignInHeader: View {
|
||||
|
||||
struct SignInEntry: View {
|
||||
let key: Binding<String>
|
||||
|
||||
let shouldSaveKey: Binding<Bool>
|
||||
@State private var privKeyFound: Bool = false
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Enter your account key", comment: "Prompt for user to enter an account key to login.")
|
||||
.fontWeight(.medium)
|
||||
.padding(.top, 30)
|
||||
|
||||
KeyInput(NSLocalizedString("nsec1...", comment: "Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key."), key: key)
|
||||
KeyInput(NSLocalizedString("nsec1...", comment: "Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key."),
|
||||
key: key,
|
||||
shouldSaveKey: shouldSaveKey,
|
||||
privKeyFound: $privKeyFound)
|
||||
if privKeyFound {
|
||||
Toggle("Save Key in Secure Keychain", isOn: shouldSaveKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SignInScan: View {
|
||||
@State var showQR: Bool = false
|
||||
@State var qrkey: ParsedKey?
|
||||
@Binding var shouldSaveKey: Bool
|
||||
@Binding var loginKey: String
|
||||
@Binding var privKeyFound: Bool
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Button(action: { showQR.toggle() }, label: {
|
||||
Image(systemName: "qrcode.viewfinder")})
|
||||
.foregroundColor(.gray)
|
||||
|
||||
}
|
||||
.sheet(isPresented: $showQR, onDismiss: {
|
||||
if qrkey == nil { resetView() }}
|
||||
) {
|
||||
QRScanNSECView(showQR: $showQR,
|
||||
privKeyFound: $privKeyFound,
|
||||
codeScannerCompletion: { scannerCompletion($0) })
|
||||
}
|
||||
.onChange(of: showQR) { show in
|
||||
if showQR { resetView() }
|
||||
}
|
||||
}
|
||||
|
||||
func handleQRString(_ string: String) {
|
||||
qrkey = parse_key(string)
|
||||
if let key = qrkey, key.is_priv {
|
||||
loginKey = string
|
||||
privKeyFound = true
|
||||
shouldSaveKey = false
|
||||
generator.notificationOccurred(.success)
|
||||
}
|
||||
}
|
||||
|
||||
func scannerCompletion(_ result: Result<ScanResult, ScanError>) {
|
||||
switch result {
|
||||
case .success(let success):
|
||||
handleQRString(success.string)
|
||||
case .failure:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func resetView() {
|
||||
loginKey = ""
|
||||
qrkey = nil
|
||||
privKeyFound = false
|
||||
shouldSaveKey = true
|
||||
}
|
||||
}
|
||||
|
||||
struct CreateAccountPrompt: View {
|
||||
var nav: NavigationCoordinator
|
||||
|
66
damus/Views/QRScanNSECView.swift
Normal file
66
damus/Views/QRScanNSECView.swift
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// QRScanNSECView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Jericho Hasselbush on 9/29/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import VisionKit
|
||||
|
||||
struct QRScanNSECView: View {
|
||||
@Binding var showQR: Bool
|
||||
@Binding var privKeyFound: Bool
|
||||
var codeScannerCompletion: (Result<ScanResult, ScanError>) -> Void
|
||||
var body: some View {
|
||||
ZStack {
|
||||
ZStack {
|
||||
DamusGradient()
|
||||
}
|
||||
VStack {
|
||||
Text("Scan Your Private Key QR",
|
||||
comment: "Text to prompt scanning a QR code of a user's privkey to login to their profile.")
|
||||
.padding(.top, 50)
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
|
||||
Spacer()
|
||||
CodeScannerView(codeTypes: [.qr],
|
||||
scanMode: .continuous,
|
||||
scanInterval: 2.0,
|
||||
showViewfinder: false,
|
||||
simulatedData: "",
|
||||
shouldVibrateOnSuccess: false,
|
||||
isTorchOn: false,
|
||||
isGalleryPresented: .constant(false),
|
||||
videoCaptureDevice: .default(for: .video),
|
||||
completion: codeScannerCompletion)
|
||||
.scaledToFit()
|
||||
.frame(width: 300, height: 300)
|
||||
.cornerRadius(10)
|
||||
.overlay(RoundedRectangle(cornerRadius: 10).stroke(DamusColors.white, lineWidth: 5.0))
|
||||
.shadow(radius: 10)
|
||||
|
||||
Button(action: { showQR = false }) {
|
||||
VStack {
|
||||
Image(systemName: privKeyFound ? "sparkle.magnifyingglass" : "magnifyingglass")
|
||||
.font(privKeyFound ? .title : .title3)
|
||||
}}
|
||||
.padding(.top)
|
||||
.buttonStyle(GradientButtonStyle())
|
||||
|
||||
Spacer()
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
@State var showQR = true
|
||||
@State var privKeyFound = false
|
||||
@State var shouldSaveKey = true
|
||||
return QRScanNSECView(showQR: $showQR,
|
||||
privKeyFound: $privKeyFound,
|
||||
codeScannerCompletion: { _ in })
|
||||
}
|
@ -32,6 +32,9 @@ struct MainView: View {
|
||||
.onReceive(handle_notify(.login)) { notif in
|
||||
needs_setup = false
|
||||
keypair = get_saved_keypair()
|
||||
if keypair == nil, let tempkeypair = notif.to_full()?.to_keypair() {
|
||||
keypair = tempkeypair
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user