From a05e276b4b8ae41c77ee834735638a61444e14c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 1 Oct 2025 23:09:35 +0200 Subject: [PATCH] feat: Implement one time login qr code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- NextcloudTalk/Login/LoginViewController.swift | 16 +++++++++++ .../Login/QRScannerViewController.swift | 13 +++++++-- .../Network/NCAPIControllerExtensions.swift | 28 +++++++++++++++++++ NextcloudTalk/en.lproj/Localizable.strings | 6 ++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/NextcloudTalk/Login/LoginViewController.swift b/NextcloudTalk/Login/LoginViewController.swift index da74e9af0..62af3279d 100644 --- a/NextcloudTalk/Login/LoginViewController.swift +++ b/NextcloudTalk/Login/LoginViewController.swift @@ -353,4 +353,20 @@ class LoginViewController: UIViewController, UITextFieldDelegate, CCCertificateD NCSettingsController.sharedInstance().addNewAccount(forUser: user, withToken: password, inServer: serverURL) delegate?.loginViewControllerDidFinish() } + + func qrScanner(_ scanner: QRScannerViewController, didScanNextcloudOnetimeLogin serverURL: String, user: String, onetimeToken: String) { + // We received a onetime login token and need to convert it to a permanent one. The token only allows to retrieve a permanent one, no other routes allowed + NCAPIController.sharedInstance().getAppPasswordOnetime(forServer: serverURL, withUsername: user, andOnetimeToken: onetimeToken) { [weak self] permanentAppToken in + guard let permanentAppToken else { + self?.showAlert( + title: NSLocalizedString("Could not login with QR code", comment: ""), + message: NSLocalizedString("The token might be used already or is expired. Please generate a new QR code and retry.", comment: "")) + + return + } + + NCSettingsController.sharedInstance().addNewAccount(forUser: user, withToken: permanentAppToken, inServer: serverURL) + self?.delegate?.loginViewControllerDidFinish() + } + } } diff --git a/NextcloudTalk/Login/QRScannerViewController.swift b/NextcloudTalk/Login/QRScannerViewController.swift index 2eed2045c..b46d64a46 100644 --- a/NextcloudTalk/Login/QRScannerViewController.swift +++ b/NextcloudTalk/Login/QRScannerViewController.swift @@ -8,6 +8,7 @@ import VisionKit @objc protocol QRScannerViewControllerDelegate: AnyObject { func qrScanner(_ scanner: QRScannerViewController, didScanNextcloudLogin serverURL: String, user: String, password: String) + func qrScanner(_ scanner: QRScannerViewController, didScanNextcloudOnetimeLogin serverURL: String, user: String, onetimeToken: String) } @objcMembers @@ -137,7 +138,10 @@ class QRScannerViewController: UIViewController, DataScannerViewControllerDelega guard case let .barcode(barcode) = item, let value = barcode.payloadStringValue else { return } - if let urlComponents = NSURLComponents(string: value), var path = urlComponents.path, urlComponents.scheme == "nc", urlComponents.host == "login" { + if let urlComponents = NSURLComponents(string: value), var path = urlComponents.path, urlComponents.scheme == "nc" { + let isOnetimeLogin = (urlComponents.host == "onetime-login") + guard urlComponents.host == "login" || isOnetimeLogin else { return } + if path.starts(with: "/") { path.removeFirst() } @@ -152,7 +156,12 @@ class QRScannerViewController: UIViewController, DataScannerViewControllerDelega scannerViewController?.stopScanning() self.dismiss(animated: true) - self.delegate?.qrScanner(self, didScanNextcloudLogin: serverUrl, user: user, password: password) + + if isOnetimeLogin { + self.delegate?.qrScanner(self, didScanNextcloudOnetimeLogin: serverUrl, user: user, onetimeToken: password) + } else { + self.delegate?.qrScanner(self, didScanNextcloudLogin: serverUrl, user: user, password: password) + } return } diff --git a/NextcloudTalk/Network/NCAPIControllerExtensions.swift b/NextcloudTalk/Network/NCAPIControllerExtensions.swift index b57988b16..81c99165a 100644 --- a/NextcloudTalk/Network/NCAPIControllerExtensions.swift +++ b/NextcloudTalk/Network/NCAPIControllerExtensions.swift @@ -1277,4 +1277,32 @@ import NextcloudKit completionBlock(thread) } } + + // MARK: - Core + + @nonobjc + func getAppPasswordOnetime(forServer server: String, withUsername username: String, andOnetimeToken onetimeToken: String, completionBlock: @escaping (_ permanentAppToken: String?) -> Void) { + let appPasswordRoute = "\(server)/ocs/v2.php/core/getapppassword-onetime" + + let credentialsString = "\(username):\(onetimeToken)" + let authHeader = "Basic \(credentialsString.data(using: .utf8)!.base64EncodedString())" + + let configuration = URLSessionConfiguration.default + let apiSessionManager = NCAPISessionManager(configuration: configuration) + apiSessionManager.requestSerializer.setValue(authHeader, forHTTPHeaderField: "Authorization") + + _ = apiSessionManager.get(appPasswordRoute, parameters: nil, progress: nil) { _, result in + if let resultDict = result as? [String: AnyObject], + let ocs = resultDict["ocs"] as? [String: AnyObject], + let data = ocs["data"] as? [String: AnyObject], + let apppassword = data["apppassword"] as? String { + + completionBlock(apppassword) + } + + completionBlock(nil) + } failure: { _, _ in + completionBlock(nil) + } + } } diff --git a/NextcloudTalk/en.lproj/Localizable.strings b/NextcloudTalk/en.lproj/Localizable.strings index ea07e9359..b9c946faf 100644 --- a/NextcloudTalk/en.lproj/Localizable.strings +++ b/NextcloudTalk/en.lproj/Localizable.strings @@ -712,6 +712,9 @@ /* No comment provided by engineer. */ "Could not leave conversation" = "Could not leave conversation"; +/* No comment provided by engineer. */ +"Could not login with QR code" = "Could not login with QR code"; + /* No comment provided by engineer. */ "Could not remove participant" = "Could not remove participant"; @@ -2093,6 +2096,9 @@ /* No comment provided by engineer. */ "The recording might include your voice, video from camera, and screen share. Your consent is required before joining the call." = "The recording might include your voice, video from camera, and screen share. Your consent is required before joining the call."; +/* No comment provided by engineer. */ +"The token might be used already or is expired. Please generate a new QR code and retry." = "The token might be used already or is expired. Please generate a new QR code and retry."; + /* No comment provided by engineer. */ "There is no account for user %@ in server %@ configured in this app." = "There is no account for user %1$@ in server %2$@ configured in this app.";