mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-19 08:03:50 +02:00
Add initial implementation of the LoginWizard.
This commit is contained in:
+1
-1
@@ -93,7 +93,7 @@ class AuthenticationService: NSObject {
|
||||
preferredLoginMode: loginFlows.loginMode,
|
||||
loginModeSupportedTypes: loginFlows.supportedLoginTypes)
|
||||
|
||||
let loginWizard = LoginWizard()
|
||||
let loginWizard = LoginWizard(client: client)
|
||||
self.loginWizard = loginWizard
|
||||
|
||||
if flow == .register {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// The result returned when querying a homeserver's available login flows.
|
||||
struct LoginFlowResult {
|
||||
let supportedLoginTypes: [MXLoginFlow]
|
||||
let ssoIdentityProviders: [SSOIdentityProvider]
|
||||
@@ -35,11 +36,17 @@ struct LoginFlowResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// The supported forms of login that a homeserver allows.
|
||||
enum LoginMode {
|
||||
/// The login mode hasn't been determined yet.
|
||||
case unknown
|
||||
/// The homeserver supports login with a password.
|
||||
case password
|
||||
/// The homeserver supports login via one or more SSO providers.
|
||||
case sso(ssoIdentityProviders: [SSOIdentityProvider])
|
||||
/// The homeserver supports login with either a password or via an SSO provider.
|
||||
case ssoAndPassword(ssoIdentityProviders: [SSOIdentityProvider])
|
||||
/// The homeserver only allows login with unsupported mechanisms. Use fallback instead.
|
||||
case unsupported
|
||||
|
||||
var ssoIdentityProviders: [SSOIdentityProvider]? {
|
||||
@@ -60,7 +67,7 @@ enum LoginMode {
|
||||
}
|
||||
}
|
||||
|
||||
var supportsSignModeScreen: Bool {
|
||||
var supportsPasswordFlow: Bool {
|
||||
switch self {
|
||||
case .password, .ssoAndPassword:
|
||||
return true
|
||||
@@ -69,3 +76,11 @@ enum LoginMode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Data obtained when calling `LoginWizard.resetPassword` that will be used
|
||||
/// when calling `LoginWizard.checkResetPasswordMailConfirmed`.
|
||||
struct ResetPasswordData {
|
||||
let newPassword: String
|
||||
let addThreePIDSessionID: String
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// An `Encodable` type that can be used as the parameters of a login request.
|
||||
protocol LoginParameters: DictionaryEncodable {
|
||||
var type: String { get }
|
||||
}
|
||||
|
||||
/// The parameters used for a login request with a token.
|
||||
struct LoginTokenParameters: LoginParameters {
|
||||
let type = kMXLoginFlowTypeToken
|
||||
let token: String
|
||||
}
|
||||
|
||||
/// The parameters used for a login request with an ID and password.
|
||||
struct LoginPasswordParameters: LoginParameters {
|
||||
let id: Identifier
|
||||
let password: String
|
||||
let type: String = kMXLoginFlowTypePassword
|
||||
let deviceDisplayName: String?
|
||||
let deviceID: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id = "identifier"
|
||||
case password
|
||||
case type
|
||||
case deviceDisplayName = "initial_device_display_name"
|
||||
case deviceID = "device_id"
|
||||
}
|
||||
|
||||
enum ThreePIDMedium: String { case email, msisdn }
|
||||
|
||||
enum Identifier: Encodable {
|
||||
case user(String)
|
||||
case thirdParty(medium: ThreePIDMedium, address: String)
|
||||
case phone(country: String, phone: String)
|
||||
|
||||
private enum Constants {
|
||||
static let typeKey = "type"
|
||||
static let userType = "m.id.user"
|
||||
static let thirdPartyType = "m.id.thirdparty"
|
||||
static let phoneType = "m.id.phone"
|
||||
|
||||
static let userKey = "user"
|
||||
|
||||
static let mediumKey = "medium"
|
||||
static let addressKey = "address"
|
||||
|
||||
static let countryKey = "country"
|
||||
static let phoneKey = "phone"
|
||||
}
|
||||
|
||||
var dictionary: [String: String] {
|
||||
switch self {
|
||||
case .user(let user):
|
||||
return [
|
||||
Constants.typeKey: Constants.userType,
|
||||
Constants.userKey: user
|
||||
]
|
||||
case .thirdParty(let medium, let address):
|
||||
return [
|
||||
Constants.typeKey: Constants.thirdPartyType,
|
||||
Constants.mediumKey: medium.rawValue,
|
||||
Constants.addressKey: address
|
||||
]
|
||||
case .phone(let country, let phone):
|
||||
return [
|
||||
Constants.typeKey: Constants.phoneType,
|
||||
Constants.countryKey: country,
|
||||
Constants.phoneKey: phone
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(dictionary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters used when checking the user has confirmed their email to reset their password.
|
||||
struct CheckResetPasswordParameters: DictionaryEncodable {
|
||||
/// Authentication parameters
|
||||
let auth: AuthenticationParameters
|
||||
|
||||
/// The new password
|
||||
let newPassword: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case auth
|
||||
case newPassword = "new_password"
|
||||
}
|
||||
|
||||
init(clientSecret: String, sessionID: String, newPassword: String) {
|
||||
self.auth = AuthenticationParameters.resetPasswordParameters(clientSecret: clientSecret, sessionID: sessionID)
|
||||
self.newPassword = newPassword
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,106 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Set of methods to be able to login to an existing account on a homeserver.
|
||||
///
|
||||
/// More documentation can be found in the file https://github.com/vector-im/element-android/blob/main/docs/signin.md
|
||||
class LoginWizard {
|
||||
struct State {
|
||||
/// For SSO session recovery
|
||||
var deviceId: String?
|
||||
var resetPasswordEmail: String?
|
||||
// var resetPasswordData: ResetPasswordData?
|
||||
var resetPasswordData: ResetPasswordData?
|
||||
|
||||
var clientSecret = UUID().uuidString
|
||||
var sendAttempt: UInt = 0
|
||||
}
|
||||
|
||||
// TODO
|
||||
let client: MXRestClient
|
||||
let sessionCreator: SessionCreator
|
||||
|
||||
private(set) var state: State
|
||||
|
||||
init(client: MXRestClient, sessionCreator: SessionCreator = SessionCreator()) {
|
||||
self.client = client
|
||||
self.sessionCreator = sessionCreator
|
||||
|
||||
self.state = State()
|
||||
}
|
||||
|
||||
// /// Get some information about a matrixId: displayName and avatar url
|
||||
// func profileInfo(for matrixID: String) async -> LoginProfileInfo {
|
||||
//
|
||||
// }
|
||||
|
||||
/// Login to the homeserver.
|
||||
/// - Parameters:
|
||||
/// - login: The login field. Can be a user name, or a msisdn (email or phone number) associated to the account.
|
||||
/// - password: The password of the account.
|
||||
/// - initialDeviceName: The initial device name.
|
||||
/// - deviceID: The device ID, optional. If not provided or nil, the server will generate one.
|
||||
/// - Returns: An `MXSession` if the login is successful.
|
||||
func login(login: String, password: String, initialDeviceName: String, deviceID: String? = nil) async throws -> MXSession {
|
||||
let parameters: LoginPasswordParameters
|
||||
|
||||
if MXTools.isEmailAddress(login) {
|
||||
parameters = LoginPasswordParameters(id: .thirdParty(medium: .email, address: login),
|
||||
password: password,
|
||||
deviceDisplayName: initialDeviceName,
|
||||
deviceID: deviceID)
|
||||
} else {
|
||||
parameters = LoginPasswordParameters(id: .user(login),
|
||||
password: password,
|
||||
deviceDisplayName: initialDeviceName,
|
||||
deviceID: deviceID)
|
||||
}
|
||||
|
||||
let credentials = try await client.login(parameters: parameters)
|
||||
return sessionCreator.createSession(credentials: credentials, client: client)
|
||||
}
|
||||
|
||||
/// Exchange a login token to an access token.
|
||||
/// - Parameter loginToken: A login token, obtained when login has happened in a WebView, using SSO.
|
||||
/// - Returns: An `MXSession` if the login is successful.
|
||||
func login(with token: String) async throws -> MXSession {
|
||||
let parameters = LoginTokenParameters(token: token)
|
||||
let credentials = try await client.login(parameters: parameters)
|
||||
return sessionCreator.createSession(credentials: credentials, client: client)
|
||||
}
|
||||
|
||||
// /// Login to the homeserver by sending a custom JsonDict.
|
||||
// /// The data should contain at least one entry `type` with a String value.
|
||||
// func loginCustom(data: Codable) async -> MXSession {
|
||||
//
|
||||
// }
|
||||
|
||||
/// Ask the homeserver to reset the user password. The password will not be
|
||||
/// reset until `checkResetPasswordMailConfirmed` is successfully called.
|
||||
/// - Parameters:
|
||||
/// - email: An email previously associated to the account the user wants the password to be reset.
|
||||
/// - newPassword: The desired new password
|
||||
func resetPassword(email: String, newPassword: String) async throws {
|
||||
let result = try await client.forgetPassword(for: email,
|
||||
clientSecret: state.clientSecret,
|
||||
sendAttempt: state.sendAttempt)
|
||||
|
||||
state.sendAttempt += 1
|
||||
state.resetPasswordData = ResetPasswordData(newPassword: newPassword, addThreePIDSessionID: result)
|
||||
}
|
||||
|
||||
/// Confirm the new password, once the user has checked their email.
|
||||
/// When this method succeeds, the account password will be effectively modified.
|
||||
func checkResetPasswordMailConfirmed() async throws {
|
||||
guard let resetPasswordData = state.resetPasswordData else {
|
||||
MXLog.error("[LoginWizard] resetPasswordMailConfirmed: Reset password data missing. Call resetPassword first.")
|
||||
throw LoginError.resetPasswordNotStarted
|
||||
}
|
||||
|
||||
let parameters = CheckResetPasswordParameters(clientSecret: state.clientSecret,
|
||||
sessionID: resetPasswordData.addThreePIDSessionID,
|
||||
newPassword: resetPasswordData.newPassword)
|
||||
|
||||
try await client.resetPassword(parameters: parameters)
|
||||
|
||||
state.resetPasswordData = nil
|
||||
}
|
||||
}
|
||||
|
||||
+3
-15
@@ -17,7 +17,7 @@
|
||||
import Foundation
|
||||
|
||||
/// The parameters used for registration requests.
|
||||
struct RegistrationParameters: Codable {
|
||||
struct RegistrationParameters: DictionaryEncodable {
|
||||
/// Authentication parameters
|
||||
var auth: AuthenticationParameters?
|
||||
|
||||
@@ -41,22 +41,10 @@ struct RegistrationParameters: Codable {
|
||||
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 registration requests.
|
||||
struct AuthenticationParameters: Codable {
|
||||
/// The data passed to the `auth` parameter in authentication requests.
|
||||
struct AuthenticationParameters: Encodable {
|
||||
/// The type of authentication taking place. The identifier from `MXLoginFlowType`.
|
||||
let type: String
|
||||
|
||||
|
||||
Reference in New Issue
Block a user