Implement login with scanned QR code flows

This commit is contained in:
Stefan Ceriu
2022-10-11 15:56:37 +03:00
committed by Stefan Ceriu
parent bbd3470659
commit fa3866ea76
19 changed files with 442 additions and 139 deletions
@@ -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