mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-20 16:42:44 +02:00
9c5aa0ca1e
Replace ReCaptcha navigation delegate with a WKUserContentController. Move callback property closures onto the MainActor. Show a loading indicator whilst waiting for the authentication service to start. Move nextUncompletedStage into FlowResult. Handle text field actions during authentication. Remove scroll view tweaks in server selection screen following EMS banner removal.
242 lines
9.8 KiB
Swift
242 lines
9.8 KiB
Swift
//
|
|
// Copyright 2022 New Vector Ltd
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// The parameters used for registration requests.
|
|
struct RegistrationParameters: Codable {
|
|
/// Authentication parameters
|
|
var auth: AuthenticationParameters?
|
|
|
|
/// The account username
|
|
var username: String?
|
|
|
|
/// The account password
|
|
var password: String?
|
|
|
|
/// Device name
|
|
var initialDeviceDisplayName: String?
|
|
|
|
/// Temporary flag to notify the server that we support MSISDN flow. Used to prevent old app
|
|
/// versions to end up in fallback because the HS returns the MSISDN flow which they don't support
|
|
var xShowMSISDN: Bool?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case auth
|
|
case username
|
|
case password
|
|
case initialDeviceDisplayName = "initial_device_display_name"
|
|
case xShowMSISDN = "x_show_msisdn"
|
|
}
|
|
|
|
/// The parameters as a JSON dictionary for use in MXRestClient.
|
|
func dictionary() throws -> [String: Any] {
|
|
let jsonData = try JSONEncoder().encode(self)
|
|
let object = try JSONSerialization.jsonObject(with: jsonData)
|
|
guard let dictionary = object as? [String: Any] else {
|
|
MXLog.error("[RegistrationParameters] dictionary: Unexpected type decoded \(type(of: object)). Expected a Dictionary.")
|
|
throw AuthenticationError.dictionaryError
|
|
}
|
|
|
|
return dictionary
|
|
}
|
|
}
|
|
|
|
/// The data passed to the `auth` parameter in authentication requests.
|
|
struct AuthenticationParameters: Codable {
|
|
/// The type of authentication taking place. The identifier from `MXLoginFlowType`.
|
|
let type: String
|
|
|
|
/// Note: session can be null for reset password request
|
|
var session: String?
|
|
|
|
/// parameter for "m.login.recaptcha" type
|
|
var captchaResponse: String?
|
|
|
|
/// parameter for "m.login.email.identity" type
|
|
var threePIDCredentials: ThreePIDCredentials?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case type
|
|
case session
|
|
case captchaResponse = "response"
|
|
case threePIDCredentials = "threepid_creds"
|
|
}
|
|
|
|
/// Creates the authentication parameters for a captcha step.
|
|
static func captchaParameters(session: String, captchaResponse: String) -> AuthenticationParameters {
|
|
AuthenticationParameters(type: kMXLoginFlowTypeRecaptcha, session: session, captchaResponse: captchaResponse)
|
|
}
|
|
|
|
/// Creates the authentication parameters for a third party ID step using an email address.
|
|
static func emailIdentityParameters(session: String, threePIDCredentials: ThreePIDCredentials) -> AuthenticationParameters {
|
|
AuthenticationParameters(type: kMXLoginFlowTypeEmailIdentity, session: session, threePIDCredentials: threePIDCredentials)
|
|
}
|
|
|
|
// Note that there is a bug in Synapse (needs investigation), but if we pass .msisdn,
|
|
// the homeserver answer with the login flow with MatrixError fields and not with a simple MatrixError 401.
|
|
/// Creates the authentication parameters for a third party ID step using a phone number.
|
|
static func msisdnIdentityParameters(session: String, threePIDCredentials: ThreePIDCredentials) -> AuthenticationParameters {
|
|
AuthenticationParameters(type: kMXLoginFlowTypeMSISDN, session: session, threePIDCredentials: threePIDCredentials)
|
|
}
|
|
|
|
/// Creates the authentication parameters for a password reset step.
|
|
static func resetPasswordParameters(clientSecret: String, sessionID: String) -> AuthenticationParameters {
|
|
AuthenticationParameters(type: kMXLoginFlowTypeEmailIdentity,
|
|
session: nil,
|
|
threePIDCredentials: ThreePIDCredentials(clientSecret: clientSecret, sessionID: sessionID))
|
|
}
|
|
}
|
|
|
|
/// The result from a registration screen's coordinator
|
|
enum AuthenticationRegistrationStageResult {
|
|
/// The screen completed with the associated registration result.
|
|
case completed(RegistrationResult)
|
|
/// The user would like to cancel the registration.
|
|
case cancel
|
|
}
|
|
|
|
/// The result from a response of a registration flow step.
|
|
enum RegistrationResult {
|
|
/// Registration has completed, creating an `MXSession` for the account.
|
|
case success(MXSession)
|
|
/// The request was successful but there are pending steps to complete.
|
|
case flowResponse(FlowResult)
|
|
}
|
|
|
|
/// The state of an authentication flow after a step has been completed.
|
|
struct FlowResult {
|
|
/// The stages in the flow that are yet to be completed.
|
|
let missingStages: [Stage]
|
|
/// The stages in the flow that have been completed.
|
|
let completedStages: [Stage]
|
|
|
|
/// A stage in the authentication flow.
|
|
enum Stage {
|
|
/// The stage with the type `m.login.recaptcha`.
|
|
case reCaptcha(isMandatory: Bool, siteKey: String)
|
|
|
|
/// The stage with the type `m.login.email.identity`.
|
|
case email(isMandatory: Bool)
|
|
|
|
/// The stage with the type `m.login.msisdn`.
|
|
case msisdn(isMandatory: Bool)
|
|
|
|
/// The stage with the type `m.login.dummy`.
|
|
///
|
|
/// This stage can be mandatory if there is no other stages. In this case the account cannot
|
|
/// be created by just sending a username and a password, the dummy stage has to be completed.
|
|
case dummy(isMandatory: Bool)
|
|
|
|
/// The stage with the type `m.login.terms`.
|
|
case terms(isMandatory: Bool, terms: MXLoginTerms?)
|
|
|
|
/// A stage of an unknown type.
|
|
case other(isMandatory: Bool, type: String, params: [AnyHashable: Any])
|
|
|
|
/// Whether the stage is mandatory.
|
|
var isMandatory: Bool {
|
|
switch self {
|
|
case .reCaptcha(let isMandatory, _):
|
|
return isMandatory
|
|
case .email(let isMandatory):
|
|
return isMandatory
|
|
case .msisdn(let isMandatory):
|
|
return isMandatory
|
|
case .dummy(let isMandatory):
|
|
return isMandatory
|
|
case .terms(let isMandatory, _):
|
|
return isMandatory
|
|
case .other(let isMandatory, _, _):
|
|
return isMandatory
|
|
}
|
|
}
|
|
|
|
/// Whether the stage is the dummy stage.
|
|
var isDummy: Bool {
|
|
guard case .dummy = self else { return false }
|
|
return true
|
|
}
|
|
}
|
|
|
|
/// Determines the next stage to be completed in the flow.
|
|
var nextUncompletedStage: Stage? {
|
|
if let emailStage = missingStages.first(where: { if case .email = $0 { return true } else { return false } }) {
|
|
return emailStage
|
|
}
|
|
if let termsStage = missingStages.first(where: { if case .terms = $0 { return true } else { return false } }) {
|
|
return termsStage
|
|
}
|
|
if let reCaptchaStage = missingStages.first(where: { if case .reCaptcha = $0 { return true } else { return false } }) {
|
|
return reCaptchaStage
|
|
}
|
|
if let msisdnStage = missingStages.first(where: { if case .msisdn = $0 { return true } else { return false } }) {
|
|
return msisdnStage
|
|
}
|
|
|
|
MXLog.failure("[FlowResult.Stage] nextUncompletedStage: The dummy stage should be handled silently and any other stages should trigger the fallback flow.")
|
|
return missingStages.first
|
|
}
|
|
|
|
var needsFallback : Bool {
|
|
missingStages.filter { $0.isMandatory }.contains { stage in
|
|
if case .other = stage { return true } else { return false }
|
|
}
|
|
}
|
|
}
|
|
|
|
extension MXAuthenticationSession {
|
|
/// The flows from the session mapped as a `FlowResult` value.
|
|
var flowResult: FlowResult {
|
|
let allFlowTypes = Set(flows.flatMap { $0.stages ?? [] }) // Using a Set here loses the order, but an order is forced during presentation anyway.
|
|
var missingStages = [FlowResult.Stage]()
|
|
var completedStages = [FlowResult.Stage]()
|
|
|
|
allFlowTypes.forEach { flow in
|
|
let isMandatory = flows.allSatisfy { $0.stages.contains(flow) }
|
|
|
|
let stage: FlowResult.Stage
|
|
switch flow {
|
|
case kMXLoginFlowTypeRecaptcha:
|
|
let parameters = params[flow] as? [AnyHashable: Any]
|
|
let publicKey = parameters?["public_key"] as? String
|
|
stage = .reCaptcha(isMandatory: isMandatory, siteKey: publicKey ?? "")
|
|
case kMXLoginFlowTypeDummy:
|
|
stage = .dummy(isMandatory: isMandatory)
|
|
case kMXLoginFlowTypeTerms:
|
|
let parameters = params[flow] as? [AnyHashable: Any]
|
|
let terms = MXLoginTerms(fromJSON: parameters)
|
|
stage = .terms(isMandatory: isMandatory, terms: terms)
|
|
case kMXLoginFlowTypeMSISDN:
|
|
stage = .msisdn(isMandatory: isMandatory)
|
|
case kMXLoginFlowTypeEmailIdentity:
|
|
stage = .email(isMandatory: isMandatory)
|
|
default:
|
|
let parameters = params[flow] as? [AnyHashable: Any]
|
|
stage = .other(isMandatory: isMandatory, type: flow, params: parameters ?? [:])
|
|
}
|
|
|
|
if let completed = completed, completed.contains(flow) {
|
|
completedStages.append(stage)
|
|
} else {
|
|
missingStages.append(stage)
|
|
}
|
|
}
|
|
|
|
return FlowResult(missingStages: missingStages, completedStages: completedStages)
|
|
}
|
|
}
|