Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AuthenticationService and RegistrationWizard. #6056

Merged
merged 5 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Riot/Categories/MXHTTPClient+Async.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// 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

@available(iOS 13.0, *)
extension MXHTTPClient {
/// Errors thrown by the async extensions to `MXHTTPClient.`
enum ClientError: Error {
/// An unexpected response was received.
case invalidResponse
/// The error that occurred was missing from the closure.
case unknownError
}

/// An async version of `request(withMethod:path:parameters:success:failure:)`.
func request(withMethod method: String, path: String, parameters: [AnyHashable: Any]) async throws -> [AnyHashable: Any] {
try await withCheckedThrowingContinuation { continuation in
request(withMethod: method, path: path, parameters: parameters) { jsonDictionary in
guard let jsonDictionary = jsonDictionary else {
continuation.resume(with: .failure(ClientError.invalidResponse))
return
}

continuation.resume(with: .success(jsonDictionary))
} failure: { error in
continuation.resume(with: .failure(error ?? ClientError.unknownError))
}
}
}
}
142 changes: 142 additions & 0 deletions Riot/Categories/MXRestClient+Async.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//
// 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

@available(iOS 13.0, *)
extension MXRestClient {
/// Errors thrown by the async extensions to `MXRestClient.`
enum ClientError: Error {
/// An unexpected response was received.
case invalidResponse
/// The error that occurred was missing from the closure.
case unknownError
/// An error occurred whilst decoding the received JSON.
case decodingError
}

/// An async version of `wellKnow(_:failure:)`.
func wellKnown() async throws -> MXWellKnown {
try await withCheckedThrowingContinuation { continuation in
wellKnow { wellKnown in
guard let wellKnown = wellKnown else {
continuation.resume(with: .failure(ClientError.invalidResponse))
return
}

continuation.resume(with: .success(wellKnown))
} failure: { error in
continuation.resume(with: .failure(error ?? ClientError.unknownError))
}
}
}

/// An async version of `getRegisterSession(completion:)`.
func getRegisterSession() async throws -> MXAuthenticationSession {
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
try await withCheckedThrowingContinuation { continuation in
getRegisterSession { response in
guard let session = response.value else {
continuation.resume(with: .failure(response.error ?? ClientError.unknownError))
return
}

continuation.resume(with: .success(session))
}
}
}

/// An async version of `getLoginSession(completion:)`.
func getLoginSession() async throws -> MXAuthenticationSession {
try await withCheckedThrowingContinuation { continuation in
getLoginSession { response in
guard let session = response.value else {
continuation.resume(with: .failure(response.error ?? ClientError.unknownError))
return
}

continuation.resume(with: .success(session))
}
}
}

/// An async version of `isUsernameAvailable(_:completion:)`.
func isUsernameAvailable(_ username: String) async throws -> Bool {
try await withCheckedThrowingContinuation { continuation in
isUsernameAvailable(username) { response in
guard let availability = response.value else {
continuation.resume(with: .failure(response.error ?? ClientError.unknownError))
return
}

continuation.resume(with: .success(availability.available))
}
}
}

/// An async version of `register(parameters:completion:)`.
func register(parameters: [String: Any]) async throws -> MXLoginResponse {
try await withCheckedThrowingContinuation { continuation in
register(parameters: parameters) { response in
guard let jsonDictionary = response.value else {
continuation.resume(with: .failure(response.error ?? ClientError.unknownError))
return
}

guard let loginResponse = MXLoginResponse(fromJSON: jsonDictionary) else {
continuation.resume(with: .failure(ClientError.decodingError))
return
}

continuation.resume(with: .success(loginResponse))
}
}
}

/// An async version of both `requestToken(forEmail:isDuringRegistration:clientSecret:sendAttempt:nextLink:success:failure:)` and
/// `requestToken(forPhoneNumber:isDuringRegistration:countryCode:clientSecret:sendAttempt:nextLink:success:failure:)` depending
/// on the kind of third party ID is supplied to the `threePID` parameter.
func requestTokenDuringRegistration(for threePID: RegisterThreePID, clientSecret: String, sendAttempt: UInt) async throws -> RegistrationThreePIDTokenResponse {
try await withCheckedThrowingContinuation { continuation in
switch threePID {
case .email(let email):
requestToken(forEmail: email, isDuringRegistration: true, clientSecret: clientSecret, sendAttempt: sendAttempt, nextLink: nil) { sessionID in
guard let sessionID = sessionID else {
continuation.resume(with: .failure(ClientError.invalidResponse))
return
}

let response = RegistrationThreePIDTokenResponse(sessionID: sessionID)
continuation.resume(with: .success(response))
} failure: { error in
continuation.resume(with: .failure(error ?? ClientError.unknownError))
}

case .msisdn(let msisdn, let countryCode):
requestToken(forPhoneNumber: msisdn, isDuringRegistration: true, countryCode: countryCode, clientSecret: clientSecret, sendAttempt: sendAttempt, nextLink: nil) { sessionID, msisdn, submitURL in
guard let sessionID = sessionID else {
continuation.resume(with: .failure(ClientError.invalidResponse))
return
}

let response = RegistrationThreePIDTokenResponse(sessionID: sessionID, submitURL: submitURL, msisdn: msisdn)
continuation.resume(with: .success(response))
} failure: { error in
continuation.resume(with: .failure(error ?? ClientError.unknownError))
}
}
}
}
}
2 changes: 1 addition & 1 deletion Riot/Modules/MatrixKit/Models/Account/MXKAccount.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *cer

@param credentials user's credentials
*/
- (instancetype)initWithCredentials:(MXCredentials*)credentials;
- (nonnull instancetype)initWithCredentials:(MXCredentials*)credentials;

/**
Create a matrix session based on the provided store.
Expand Down
2 changes: 1 addition & 1 deletion Riot/Modules/MatrixKit/Models/Account/MXKAccount.m
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ + (UIColor*)presenceColor:(MXPresence)presence
}
}

- (instancetype)initWithCredentials:(MXCredentials*)credentials
- (nonnull instancetype)initWithCredentials:(MXCredentials*)credentials
{
if (self = [super init])
{
Expand Down
41 changes: 41 additions & 0 deletions Riot/Modules/Onboarding/AuthenticationCoordinatorState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// 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
import MatrixSDK

@available(iOS 14.0, *)
struct AuthenticationCoordinatorState {
// MARK: User choices
// var serverType: ServerType = .unknown
Anderas marked this conversation as resolved.
Show resolved Hide resolved
// var signMode: SignMode = .unknown
var resetPasswordEmail: String?

/// The homeserver address as returned by the server.
var homeserverAddress: String?
/// The homeserver address as input by the user (it can differ to the well-known request).
var homeserverAddressFromUser: String?
pixlwave marked this conversation as resolved.
Show resolved Hide resolved

/// For SSO session recovery
var deviceId: String?

// MARK: Network result
var loginMode: LoginMode = .unknown
/// Supported types for the login.
var loginModeSupportedTypes = [MXLoginFlow]()
var knownCustomHomeServersUrls = [String]()
var isForceLoginFallbackEnabled = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// 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

/// Errors that can be thrown from `AuthenticationService`, `RegistrationWizard` and `LoginWizard`.
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
enum AuthenticationError: String, Error {
// MARK: AuthenticationService
/// A failure to convert a struct into a dictionary.
case dictionaryError
case invalidHomeserver
case loginFlowNotCalled
case missingRegistrationWizard
case missingMXRestClient

// MARK: RegistrationWizard
case createAccountNotCalled
case noPendingThreePID
case missingThreePIDURL
case threePIDValidationFailure
case threePIDClientFailure
}

/// Represents an SSO Identity Provider as provided in a login flow.
struct SSOIdentityProvider: Identifiable {
/// The identifier field (id field in JSON) is the Identity Provider identifier used for the SSO Web page redirection `/login/sso/redirect/{idp_id}`.
let id: String
/// The name field is a human readable string intended to be printed by the client.
let name: String
/// The brand field is optional. It allows the client to style the login button to suit a particular brand.
let brand: String?
/// The icon field is an optional field that points to an icon representing the identity provider. If present then it must be an HTTPS URL to an image resource.
let iconURL: String?
}
29 changes: 29 additions & 0 deletions RiotSwiftUI/Modules/Authentication/Common/HomeserverAddress.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// 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

class HomeserverAddress {
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
/// Ensures the address contains a scheme, otherwise makes it `https`.
static func sanitize(_ address: String) -> String {
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
!address.contains("://") ? "https://\(address.lowercased())" : address.lowercased()
}

/// Strips the `https://` away from the address (but leaves `http://`) for display in labels.
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
static func displayable(_ address: String) -> String {
address.replacingOccurrences(of: "https://", with: "")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// 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

/// This class holds all pending data when creating a session, either by login or by register
class AuthenticationPendingData {
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
let homeserverAddress: String

// MARK: - Common

var clientSecret = UUID().uuidString
var sendAttempt: UInt = 0

// MARK: - For login

// var resetPasswordData: ResetPasswordData?
Anderas marked this conversation as resolved.
Show resolved Hide resolved

// MARK: - For registration

var currentSession: String?
pixlwave marked this conversation as resolved.
Show resolved Hide resolved
var isRegistrationStarted = false
var currentThreePIDData: ThreePIDData?

// MARK: - Setup

init(homeserverAddress: String) {
self.homeserverAddress = homeserverAddress
}
}
Loading