diff --git a/Lock/CDNLoaderInteractor.swift b/Lock/CDNLoaderInteractor.swift index 71bf5a388..feb4a769c 100644 --- a/Lock/CDNLoaderInteractor.swift +++ b/Lock/CDNLoaderInteractor.swift @@ -76,9 +76,11 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable { let json = try NSJSONSerialization.JSONObjectWithData(jsonp.dataUsingEncoding(NSUTF8StringEncoding)!, options: []) as? JSONObject self.logger.debug("Client configuration is \(json)") let info = ClientInfo(json: json) - if let auth0 = info.auth0, let connection = auth0.connections.first { - let requiresUsername = connection.booleanValue(forKey: "requires_username") - connections.database(name: connection.name, requiresUsername: requiresUsername, usernameValidator: connection.usernameValidation) + if let auth0 = info.auth0 { + auth0.connections.forEach { connection in + let requiresUsername = connection.booleanValue(forKey: "requires_username") + connections.database(name: connection.name, requiresUsername: requiresUsername, usernameValidator: connection.usernameValidation) + } } info.oauth2.forEach { strategy in strategy.connections.forEach { connections.social(name: $0.name, style: AuthStyle.style(forStrategy: strategy.name, connectionName: $0.name)) } @@ -150,7 +152,7 @@ private struct ConnectionInfo { func booleanValue(forKey key: String, defaultValue: Bool = false) -> Bool { return json[key] as? Bool ?? defaultValue } - var usernameValidation: InputValidator { + var usernameValidation: UsernameValidator { let validation = json["validation"] as? JSONObject let username = validation?["username"] as? JSONObject switch (username?["min"], username?["max"]) { diff --git a/Lock/ConnectionBuildable.swift b/Lock/ConnectionBuildable.swift index 43f21db20..f5cdd7a86 100644 --- a/Lock/ConnectionBuildable.swift +++ b/Lock/ConnectionBuildable.swift @@ -36,7 +36,7 @@ public protocol ConnectionBuildable: Connections { - parameter usernameValidator: custom validator for username. Connection must allow username - important: Only **ONE** database connection can be used so subsequent calls will override the previous value */ - mutating func database(name name: String, requiresUsername: Bool, usernameValidator: InputValidator) + mutating func database(name name: String, requiresUsername: Bool, usernameValidator: UsernameValidator) /** Adds a new social connection @@ -66,7 +66,7 @@ public extension ConnectionBuildable { - parameter usernameValidator: custom validator for username. Connection must allow username and defaults to 1..15 characters - important: Only **ONE** database connection can be used so subsequent calls will override the previous value */ - public mutating func database(name name: String, requiresUsername: Bool, usernameValidator: InputValidator = UsernameValidator()) { + public mutating func database(name name: String, requiresUsername: Bool, usernameValidator: UsernameValidator = UsernameValidator()) { self.database(name: name, requiresUsername: requiresUsername, usernameValidator: usernameValidator) } diff --git a/Lock/Connections.swift b/Lock/Connections.swift index 0efea5552..5cc9ed50a 100644 --- a/Lock/Connections.swift +++ b/Lock/Connections.swift @@ -41,9 +41,9 @@ public protocol Connections { public struct DatabaseConnection { public let name: String public let requiresUsername: Bool - public let usernameValidator: InputValidator + public let usernameValidator: UsernameValidator - public init(name: String, requiresUsername: Bool, usernameValidator: InputValidator = UsernameValidator()) { + public init(name: String, requiresUsername: Bool, usernameValidator: UsernameValidator = UsernameValidator()) { self.name = name self.requiresUsername = requiresUsername self.usernameValidator = usernameValidator diff --git a/Lock/DatabaseForgotPasswordPresenter.swift b/Lock/DatabaseForgotPasswordPresenter.swift index 68c76f3ec..ce27c2ecf 100644 --- a/Lock/DatabaseForgotPasswordPresenter.swift +++ b/Lock/DatabaseForgotPasswordPresenter.swift @@ -49,7 +49,7 @@ class DatabaseForgotPasswordPresenter: Presentable, Loggable { try self.interactor.updateEmail(input.text) input.showValid() } catch let error as InputValidationError { - input.showError(error.localizedMessage) + input.showError(error.localizedMessage(withConnection: self.database)) } catch { input.showError() } @@ -80,4 +80,4 @@ class DatabaseForgotPasswordPresenter: Presentable, Loggable { } return view } -} \ No newline at end of file +} diff --git a/Lock/DatabasePresenter.swift b/Lock/DatabasePresenter.swift index e73dd8f65..883e0c94b 100644 --- a/Lock/DatabasePresenter.swift +++ b/Lock/DatabasePresenter.swift @@ -193,7 +193,7 @@ class DatabasePresenter: Presentable, Loggable { try self.authenticator.update(attr, value: input.text) input.showValid() } catch let error as InputValidationError { - input.showError(error.localizedMessage) + input.showError(error.localizedMessage(withConnection: self.database)) } catch { input.showError() } @@ -205,4 +205,4 @@ private func safariBuilder(forURL url: NSURL, navigator: Navigable) -> (UIAlertA let safari = SFSafariViewController(URL: url) navigator.present(safari) } -} \ No newline at end of file +} diff --git a/Lock/InputValidationError.swift b/Lock/InputValidationError.swift index 0ec6a109b..2a247918c 100644 --- a/Lock/InputValidationError.swift +++ b/Lock/InputValidationError.swift @@ -28,10 +28,11 @@ enum InputValidationError: ErrorType { case NotAUsername case NotAOneTimePassword - var localizedMessage: String { + func localizedMessage(withConnection connection: DatabaseConnection) -> String { switch self { case .NotAUsername: - return "Can only contain between 1 to 15 alphanumeric characters and \'_\'.".i18n(key: "com.auth0.lock.input.username.error", comment: "invalid username") + let format = "Can only contain between %d to %d alphanumeric characters and \'_\'.".i18n(key: "com.auth0.lock.input.username.error", comment: "invalid username") + return String(format: format, connection.usernameValidator.min, connection.usernameValidator.max) case .NotAnEmailAddress: return "Must be a valid email address".i18n(key: "com.auth0.lock.input.email.error", comment: "invalid email") case .MustNotBeEmpty: @@ -40,4 +41,4 @@ enum InputValidationError: ErrorType { return "Must be a valid numeric code".i18n(key: "com.auth0.lock.input.otp.error", comment: "invalid otp") } } -} \ No newline at end of file +} diff --git a/Lock/MultifactorPresenter.swift b/Lock/MultifactorPresenter.swift index 0d3cecba4..4356918a2 100644 --- a/Lock/MultifactorPresenter.swift +++ b/Lock/MultifactorPresenter.swift @@ -47,7 +47,7 @@ class MultifactorPresenter: Presentable, Loggable { try self.interactor.setMultifactorCode(input.text) input.showValid() } catch let error as InputValidationError { - input.showError(error.localizedMessage) + input.showError(error.localizedMessage(withConnection: self.database)) } catch { input.showError() } @@ -75,4 +75,4 @@ class MultifactorPresenter: Presentable, Loggable { view.primaryButton?.onPress = action return view } -} \ No newline at end of file +} diff --git a/Lock/OfflineConnections.swift b/Lock/OfflineConnections.swift index 1661c7ccc..2cff86d9a 100644 --- a/Lock/OfflineConnections.swift +++ b/Lock/OfflineConnections.swift @@ -24,11 +24,12 @@ import Foundation struct OfflineConnections: ConnectionBuildable { - private (set) var database: DatabaseConnection? = nil + private (set) var databases: [DatabaseConnection] = [] + var database: DatabaseConnection? { return self.databases.first } private (set) var oauth2: [OAuth2Connection] = [] - mutating func database(name name: String, requiresUsername: Bool, usernameValidator: InputValidator = UsernameValidator()) { - self.database = DatabaseConnection(name: name, requiresUsername: requiresUsername, usernameValidator: usernameValidator) + mutating func database(name name: String, requiresUsername: Bool, usernameValidator: UsernameValidator = UsernameValidator()) { + self.databases.append(DatabaseConnection(name: name, requiresUsername: requiresUsername, usernameValidator: usernameValidator)) } mutating func social(name name: String, style: AuthStyle) { @@ -46,9 +47,7 @@ struct OfflineConnections: ConnectionBuildable { func select(byNames names: [String]) -> OfflineConnections { var connections = OfflineConnections() - if let database = self.database where isWhitelisted(connectionName: database.name, inList: names) { - connections.database = database - } + connections.databases = self.databases.filter { isWhitelisted(connectionName: $0.name, inList: names) } connections.oauth2 = self.oauth2.filter { isWhitelisted(connectionName: $0.name, inList: names) } return connections } diff --git a/Lock/Validators.swift b/Lock/Validators.swift index 3fd8f9c6f..e40b6df3a 100644 --- a/Lock/Validators.swift +++ b/Lock/Validators.swift @@ -22,16 +22,11 @@ import Foundation -public protocol InputValidator { +protocol InputValidator { func validate(value: String?) -> ErrorType? } -public func usernameLength(atLeast min: Int, upTo max: Int) -> InputValidator { - guard min < max else { return UsernameValidator() } - return UsernameValidator(range: min...max) -} - -struct OneTimePasswordValidator: InputValidator { +public class OneTimePasswordValidator: InputValidator { func validate(value: String?) -> ErrorType? { guard let value = value?.trimmed where !value.isEmpty else { return InputValidationError.MustNotBeEmpty } guard value.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet()) != nil else { return InputValidationError.NotAOneTimePassword } @@ -39,18 +34,21 @@ struct OneTimePasswordValidator: InputValidator { } } -struct NonEmptyValidator: InputValidator { +public class NonEmptyValidator: InputValidator { func validate(value: String?) -> ErrorType? { guard let value = value?.trimmed where !value.isEmpty else { return InputValidationError.MustNotBeEmpty } return nil } } -struct UsernameValidator: InputValidator { +public class UsernameValidator: InputValidator { let invalidSet: NSCharacterSet let range: Range + var min: Int { return self.range.startIndex } + var max: Int { return self.range.endIndex - 1 } + init(range: Range = 1...15) { let set = NSMutableCharacterSet() set.formUnionWithCharacterSet(NSCharacterSet.alphanumericCharacterSet()) @@ -67,7 +65,7 @@ struct UsernameValidator: InputValidator { } } -struct EmailValidator: InputValidator { +public class EmailValidator: InputValidator { let predicate: NSPredicate init() { diff --git a/LockTests/Interactors/CDNLoaderInteractorSpec.swift b/LockTests/Interactors/CDNLoaderInteractorSpec.swift index 24d4b3304..010779bc1 100644 --- a/LockTests/Interactors/CDNLoaderInteractorSpec.swift +++ b/LockTests/Interactors/CDNLoaderInteractorSpec.swift @@ -141,7 +141,7 @@ class CDNLoaderInteractorSpec: QuickSpec { expect(connections?.database).toEventuallyNot(beNil()) expect(connections?.database?.name).toEventually(equal(databaseConnection)) expect(connections?.database?.requiresUsername).toEventually(beFalsy()) - let validator = connections?.database?.usernameValidator as? UsernameValidator + let validator = connections?.database?.usernameValidator expect(validator?.range.startIndex) == 10 expect(validator?.range.endIndex) == 201 } @@ -152,7 +152,7 @@ class CDNLoaderInteractorSpec: QuickSpec { expect(connections?.database).toEventuallyNot(beNil()) expect(connections?.database?.name).toEventually(equal(databaseConnection)) expect(connections?.database?.requiresUsername).toEventually(beFalsy()) - let validator = connections?.database?.usernameValidator as? UsernameValidator + let validator = connections?.database?.usernameValidator expect(validator?.range.startIndex) == 9 expect(validator?.range.endIndex) == 101 } diff --git a/LockTests/Models/OfflineConnectionsSpec.swift b/LockTests/Models/OfflineConnectionsSpec.swift index c718c1c8f..ecd7fc2c4 100644 --- a/LockTests/Models/OfflineConnectionsSpec.swift +++ b/LockTests/Models/OfflineConnectionsSpec.swift @@ -97,8 +97,18 @@ class OfflineConnectionsSpec: QuickSpec { expect(filtered.oauth2).to(beEmpty()) } + it("should select by name database connection") { + var connections = OfflineConnections() + connections.database(name: connection, requiresUsername: false) + connections.database(name: "another-connection", requiresUsername: false) + connections.social(name: "facebook", style: .Facebook) + let filtered = connections.select(byNames: [connection]) + expect(filtered.database?.name) == connection + expect(filtered.oauth2).to(beEmpty()) + } + } } -} \ No newline at end of file +} diff --git a/LockTests/Presenters/AuthPresenterSpec.swift b/LockTests/Presenters/AuthPresenterSpec.swift index b74b4e4ba..e11993836 100644 --- a/LockTests/Presenters/AuthPresenterSpec.swift +++ b/LockTests/Presenters/AuthPresenterSpec.swift @@ -61,25 +61,25 @@ class AuthPresenterSpec: QuickSpec { } it("should return view with expanded mode for single connection") { - let connections = OfflineConnections(database: nil, oauth2: mockConnections(count: 1)) + let connections = OfflineConnections(databases: [], oauth2: mockConnections(count: 1)) presenter = AuthPresenter(connections: connections, interactor: interactor, customStyle: [:]) expect(presenter.newViewToEmbed(withInsets: UIEdgeInsetsZero).mode).to(beExpandedMode()) } it("should return view with expanded mode and signup flag") { - let connections = OfflineConnections(database: nil, oauth2: mockConnections(count: 1)) + let connections = OfflineConnections(databases: [], oauth2: mockConnections(count: 1)) presenter = AuthPresenter(connections: connections, interactor: interactor, customStyle: [:]) expect(presenter.newViewToEmbed(withInsets: UIEdgeInsetsZero, isLogin: false).mode).to(beExpandedMode(isLogin: false)) } it("should return view with expanded mode for two connections") { - let connections = OfflineConnections(database: nil, oauth2: mockConnections(count: 2)) + let connections = OfflineConnections(databases: [], oauth2: mockConnections(count: 2)) presenter = AuthPresenter(connections: connections, interactor: interactor, customStyle: [:]) expect(presenter.newViewToEmbed(withInsets: UIEdgeInsetsZero).mode).to(beExpandedMode()) } it("should return view with compact mode for more than three connecitons") { - let connections = OfflineConnections(database: nil, oauth2: mockConnections(count: Int(arc4random_uniform(10)) + 3)) + let connections = OfflineConnections(databases: [], oauth2: mockConnections(count: Int(arc4random_uniform(10)) + 3)) presenter = AuthPresenter(connections: connections, interactor: interactor, customStyle: [:]) expect(presenter.newViewToEmbed(withInsets: UIEdgeInsetsZero).mode).to(beCompactMode()) } @@ -114,4 +114,4 @@ class AuthPresenterSpec: QuickSpec { } } -} \ No newline at end of file +} diff --git a/LockTests/UsernameValidatorSpec.swift b/LockTests/UsernameValidatorSpec.swift index e0a001f35..1356ad9fb 100644 --- a/LockTests/UsernameValidatorSpec.swift +++ b/LockTests/UsernameValidatorSpec.swift @@ -28,18 +28,6 @@ class UsernameValidatorSpec: QuickSpec { override func spec() { - it("should return validator using builder") { - let validator = usernameLength(atLeast: 1, upTo: 20) as? UsernameValidator - expect(validator?.range.startIndex) == 1 - expect(validator?.range.endIndex) == 21 - } - - it("should return validator using builder with default values when invalid") { - let validator = usernameLength(atLeast: 100, upTo: 20) as? UsernameValidator - expect(validator?.range.startIndex) == 1 - expect(validator?.range.endIndex) == 16 - } - context("default") { var validator: UsernameValidator { return UsernameValidator() }