mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-19 16:13:42 +02:00
Implement login with scanned QR code flows
This commit is contained in:
committed by
Stefan Ceriu
parent
bbd3470659
commit
fa3866ea76
+137
-16
@@ -25,15 +25,19 @@ import ZXingObjC
|
||||
|
||||
class QRLoginService: NSObject, QRLoginServiceProtocol {
|
||||
private let client: AuthenticationRestClient
|
||||
private let sessionCreator: SessionCreatorProtocol
|
||||
private var isCameraReady = false
|
||||
private lazy var zxCapture = ZXCapture()
|
||||
|
||||
private let cameraAccessManager = CameraAccessManager()
|
||||
|
||||
private var rendezvousService: RendezvousService?
|
||||
|
||||
init(client: AuthenticationRestClient,
|
||||
mode: QRLoginServiceMode,
|
||||
state: QRLoginServiceState = .initial) {
|
||||
self.client = client
|
||||
self.sessionCreator = SessionCreator()
|
||||
self.mode = mode
|
||||
self.state = state
|
||||
super.init()
|
||||
@@ -72,16 +76,9 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
|
||||
}
|
||||
|
||||
func generateQRCode() async throws -> QRLoginCode {
|
||||
let transport = QRLoginRendezvousTransportDetails(type: "http.v1",
|
||||
uri: "")
|
||||
let rendezvous = QRLoginRendezvous(transport: transport,
|
||||
algorithm: "m.rendezvous.v1.curve25519-aes-sha256",
|
||||
key: "")
|
||||
return QRLoginCode(user: client.credentials.userId,
|
||||
initiator: .new,
|
||||
rendezvous: rendezvous)
|
||||
fatalError("Not implemented")
|
||||
}
|
||||
|
||||
|
||||
func scannerView() -> AnyView {
|
||||
let frame = UIScreen.main.bounds
|
||||
let view = UIView(frame: frame)
|
||||
@@ -109,6 +106,8 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
|
||||
}
|
||||
|
||||
func stopScanning(destroy: Bool) {
|
||||
zxCapture.delegate = nil
|
||||
|
||||
guard zxCapture.running else {
|
||||
return
|
||||
}
|
||||
@@ -120,20 +119,21 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func processScannedQR(_ data: Data) {
|
||||
state = .connectingToDevice
|
||||
do {
|
||||
let code = try JSONDecoder().decode(QRLoginCode.self, from: data)
|
||||
MXLog.debug("[QRLoginService] processScannedQR: \(code)")
|
||||
// TODO: implement
|
||||
} catch {
|
||||
guard let code = try? JSONDecoder().decode(QRLoginCode.self, from: data) else {
|
||||
state = .failed(error: .invalidQR)
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
await processQRLoginCode(code)
|
||||
}
|
||||
}
|
||||
|
||||
func confirmCode() {
|
||||
switch state {
|
||||
case .waitingForConfirmation(let code):
|
||||
case .waitingForConfirmation:
|
||||
// TODO: implement
|
||||
break
|
||||
default:
|
||||
@@ -143,11 +143,19 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
|
||||
|
||||
func restart() {
|
||||
state = .initial
|
||||
|
||||
Task {
|
||||
await declineRendezvous()
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
stopScanning(destroy: false)
|
||||
state = .initial
|
||||
|
||||
Task {
|
||||
await declineRendezvous()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -155,6 +163,119 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@MainActor
|
||||
private func processQRLoginCode(_ code: QRLoginCode) async {
|
||||
MXLog.debug("[QRLoginService] processQRLoginCode: \(code)")
|
||||
state = .connectingToDevice
|
||||
|
||||
guard let uri = code.rendezvous.transport?.uri,
|
||||
let rendezvousURL = URL(string: uri),
|
||||
let key = code.rendezvous.key else {
|
||||
MXLog.debug("[QRLoginService] QR code invalid")
|
||||
state = .failed(error: .invalidQR)
|
||||
return
|
||||
}
|
||||
|
||||
let transport = RendezvousTransport(baseURL: BuildSettings.rendezvousServerBaseURL,
|
||||
rendezvousURL: rendezvousURL)
|
||||
let rendezvousService = RendezvousService(transport: transport)
|
||||
self.rendezvousService = rendezvousService
|
||||
|
||||
MXLog.debug("[QRLoginService] Joining the rendezvous at \(rendezvousURL)")
|
||||
guard case .success(let validationCode) = await rendezvousService.joinRendezvous(withInterlocutorPublicKey: key) else {
|
||||
await teardownRendezvous(state: .failed(error: .rendezvousFailed))
|
||||
return
|
||||
}
|
||||
|
||||
state = .waitingForConfirmation(validationCode)
|
||||
|
||||
MXLog.debug("[QRLoginService] Requesting login")
|
||||
guard let requestData = try? JSONEncoder().encode(QRLoginRendezvousPayload(type: .loginStart, intent: .loginStart)),
|
||||
case .success = await rendezvousService.send(data: requestData) else {
|
||||
await teardownRendezvous(state: .failed(error: .rendezvousFailed))
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.debug("[QRLoginService] Waiting for available protocols")
|
||||
guard case let .success(data) = await rendezvousService.receive(),
|
||||
let responsePayload = try? JSONDecoder().decode(QRLoginRendezvousPayload.self, from: data) else {
|
||||
await teardownRendezvous(state: .failed(error: .rendezvousFailed))
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.debug("[QRLoginService] Received available protocols \(responsePayload)")
|
||||
guard let protocols = responsePayload.protocols,
|
||||
protocols.contains(.loginToken) else {
|
||||
await teardownRendezvous(state: .failed(error: .rendezvousFailed))
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.debug("[QRLoginService] Request login with `login_token`")
|
||||
guard let requestData = try? JSONEncoder().encode(QRLoginRendezvousPayload(type: .loginProgress, protocol: .loginToken)),
|
||||
case .success = await rendezvousService.send(data: requestData) else {
|
||||
await teardownRendezvous(state: .failed(error: .rendezvousFailed))
|
||||
return
|
||||
}
|
||||
|
||||
state = .waitingForRemoteSignIn
|
||||
|
||||
MXLog.debug("[QRLoginService] Waiting for the login token")
|
||||
guard case let .success(data) = await rendezvousService.receive(),
|
||||
let responsePayload = try? JSONDecoder().decode(QRLoginRendezvousPayload.self, from: data),
|
||||
let login_token = responsePayload.loginToken else {
|
||||
await teardownRendezvous(state: .failed(error: .rendezvousFailed))
|
||||
return
|
||||
}
|
||||
MXLog.debug("[QRLoginService] Received login token \(responsePayload)")
|
||||
|
||||
MXLog.debug("[QRLoginService] Logging in with the login token")
|
||||
guard let credentials = try? await client.login(parameters: LoginTokenParameters(token: login_token)) else {
|
||||
await teardownRendezvous(state: .failed(error: .rendezvousFailed))
|
||||
return
|
||||
}
|
||||
MXLog.debug("[QRLoginService] Got acess token")
|
||||
|
||||
let session = sessionCreator.createSession(credentials: credentials, client: client, removeOtherAccounts: false)
|
||||
|
||||
MXLog.debug("[QRLoginService] Created session")
|
||||
|
||||
MXLog.debug("[QRLoginService] No E2EE support. Inform the interlocutor of finishing")
|
||||
guard let requestData = try? JSONEncoder().encode(QRLoginRendezvousPayload(type: .loginFinish, outcome: .success)),
|
||||
case .success = await rendezvousService.send(data: requestData) else {
|
||||
await teardownRendezvous(state: .failed(error: .rendezvousFailed))
|
||||
return
|
||||
}
|
||||
|
||||
state = .completed(session: session)
|
||||
}
|
||||
|
||||
private func declineRendezvous() async {
|
||||
guard let requestData = try? JSONEncoder().encode(QRLoginRendezvousPayload(type: .loginFinish, outcome: .declined)) else {
|
||||
return
|
||||
}
|
||||
|
||||
_ = await rendezvousService?.send(data: requestData)
|
||||
|
||||
await teardownRendezvous()
|
||||
}
|
||||
|
||||
private func teardownRendezvous(state: QRLoginServiceState? = nil) async {
|
||||
// Stop listening for changes, try deleting the resource
|
||||
_ = await rendezvousService?.tearDown()
|
||||
|
||||
// Try setting the new state, if necessary
|
||||
if let state = state {
|
||||
switch self.state {
|
||||
case .completed:
|
||||
return
|
||||
case .initial:
|
||||
return
|
||||
default:
|
||||
self.state = state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ZXCaptureDelegate
|
||||
|
||||
Reference in New Issue
Block a user