Skip to content

Commit

Permalink
Merge pull request #370 from auth0/feature_autoclose_signup_forgot
Browse files Browse the repository at this point in the history
Auto close on success event behaviour and customization
  • Loading branch information
hzalaz authored Jan 17, 2017
2 parents 5b6dfad + 8c75bce commit 3b3631e
Show file tree
Hide file tree
Showing 16 changed files with 239 additions and 66 deletions.
2 changes: 1 addition & 1 deletion Lock.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
5BCDE1341DDDF12100AA2A6C /* EnterpriseActiveAuthPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseActiveAuthPresenterSpec.swift; sourceTree = "<group>"; };
5BCED4C51DD1FCF200E2CE8A /* EnterpriseDomainPresenterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseDomainPresenterSpec.swift; sourceTree = "<group>"; };
5F0FCF8F1E20117E00E3D53B /* ObserverStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverStore.swift; sourceTree = "<group>"; };
5F0FCF911E201CF300E3D53B /* ObserverStoreSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverStoreSpec.swift; sourceTree = "<group>"; };
5F0FCF911E201CF300E3D53B /* ObserverStoreSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ObserverStoreSpec.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
5F1456591D5130E80085DF9C /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Colors.swift; path = Lock/Colors.swift; sourceTree = SOURCE_ROOT; };
5F1C498D1D8360AA005B74FC /* Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Style.swift; path = Lock/Style.swift; sourceTree = SOURCE_ROOT; };
5F1C498F1D8360BF005B74FC /* ConnectionLoadingPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConnectionLoadingPresenter.swift; path = Lock/ConnectionLoadingPresenter.swift; sourceTree = SOURCE_ROOT; };
Expand Down
4 changes: 3 additions & 1 deletion Lock/DatabaseForgotPasswordPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ class DatabaseForgotPasswordPresenter: Presentable, Loggable {
self.logger.error("Failed with error \(error)")
} else {
let message = "We've just sent you an email to reset your password".i18n(key: "com.auth0.lock.database.forgot.success.message", comment: "forgot password email sent")
self.messagePresenter?.showSuccess(message)
if self.options.allow.contains(.Login) || !self.options.autoClose {
self.messagePresenter?.showSuccess(message)
}
guard self.options.allow.contains(.Login) else { return }
self.navigator.navigate(.root)
}
Expand Down
5 changes: 4 additions & 1 deletion Lock/DatabasePasswordInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Auth0
struct DatabasePasswordInteractor: PasswordRecoverable {

private var user: DatabaseUser
private let dispatcher: Dispatcher

var email: String? { return self.user.email }
var validEmail: Bool { return self.user.validEmail }
Expand All @@ -34,10 +35,11 @@ struct DatabasePasswordInteractor: PasswordRecoverable {
let connections: Connections
let emailValidator: InputValidator = EmailValidator()

init(connections: Connections, authentication: Authentication, user: DatabaseUser) {
init(connections: Connections, authentication: Authentication, user: DatabaseUser, dispatcher: Dispatcher) {
self.authentication = authentication
self.connections = connections
self.user = user
self.dispatcher = dispatcher
}

mutating func updateEmail(_ value: String?) throws {
Expand All @@ -55,6 +57,7 @@ struct DatabasePasswordInteractor: PasswordRecoverable {
.resetPassword(email: email, connection: connection)
.start {
guard case .success = $0 else { return callback(.emailNotSent) }
self.dispatcher.dispatch(result: .forgotPassword(email))
callback(nil)
}
}
Expand Down
8 changes: 7 additions & 1 deletion Lock/DatabasePresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ class DatabasePresenter: Presentable, Loggable {
button.inProgress = false
guard let error = error else {
self.logger.debug("Logged in!")
let message = "You have logged in successfully.".i18n(key: "com.auth0.lock.database.login.success.message", comment: "User logged in")
if !self.options.autoClose {
self.messagePresenter?.showSuccess(message)
}
return
}
if case DatabaseAuthenticatableError.multifactorRequired = error {
Expand Down Expand Up @@ -162,7 +166,9 @@ class DatabasePresenter: Presentable, Loggable {
if let databaseView = self.databaseView, self.options.allow.contains(.Login) {
self.showLogin(inView: databaseView, identifier: self.creator.identifier)
}
self.messagePresenter?.showSuccess(message)
if self.options.allow.contains(.Login) || !self.options.autoClose {
self.messagePresenter?.showSuccess(message)
}
}
return
}
Expand Down
1 change: 1 addition & 0 deletions Lock/Lock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public class Lock: NSObject {
var builder: OptionBuildable = self.optionsBuilder
closure(&builder)
self.optionsBuilder = builder
self.observerStore.options = self.options
_ = self.authentication.logging(enabled: self.options.logHttpRequest)
_ = self.webAuth.logging(enabled: self.options.logHttpRequest)
return self
Expand Down
1 change: 1 addition & 0 deletions Lock/LockOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct LockOptions: OptionBuildable {
var scope: String = "openid"
var parameters: [String : Any] = [:]
var allow: DatabaseMode = [.Login, .Signup, .ResetPassword]
var autoClose: Bool = true
var initialScreen: DatabaseScreen = .login
var usernameStyle: DatabaseIdentifierStyle = [.Username, .Email]
var customSignupFields: [CustomTextField] = []
Expand Down
23 changes: 19 additions & 4 deletions Lock/ObserverStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,38 @@ struct ObserverStore: Dispatcher {
var onFailure: (Error) -> () = { _ in }
var onCancel: () -> () = { }
var onSignUp: (String, [String: Any]) -> () = { _ in }
var onForgotPassword: (String) -> () = { _ in }
var options: Options = LockOptions()

weak var controller: UIViewController?

func dispatch(result: Result) {
let closure: () -> ()
switch result {
case .auth(let credentials):
closure = dismiss(from: controller?.presentingViewController, completion: { self.onAuth(credentials) })
if self.options.autoClose {
closure = dismiss(from: controller?.presentingViewController, completion: { self.onAuth(credentials) })
} else {
closure = { self.onAuth(credentials) }
}
case .error(let error):
closure = { self.onFailure(error) }
case .cancel:
closure = dismiss(from: controller?.presentingViewController, completion: { self.onCancel() })
case .signUp(let email, let attributes):
closure = { self.onSignUp(email, attributes) }
default:
closure = {}
if !self.options.allow.contains(.Login) && self.options.autoClose {
closure = dismiss(from: controller?.presentingViewController, completion: { self.onSignUp(email, attributes) })
} else {
closure = { self.onSignUp(email, attributes) }
}
case .forgotPassword(let email):
if !self.options.allow.contains(.Login) && self.options.autoClose {
closure = dismiss(from: controller?.presentingViewController, completion: { self.onForgotPassword(email) })
} else {
closure = { self.onForgotPassword(email) }
}
}

Queue.main.async(closure)
}

Expand Down
4 changes: 4 additions & 0 deletions Lock/OptionBuildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public protocol OptionBuildable: Options {
/// What database modes are allowed and must be at least one. By default all modes are allowed.
var allow: DatabaseMode { get set }

/// Should Lock close if only mode available. By default is true
var autoClose: Bool { get set }

/// Initial screen displayed by Lock when a database connection is available. By default is Login
var initialScreen: DatabaseScreen { get set }

Expand Down Expand Up @@ -87,6 +90,7 @@ internal extension OptionBuildable {
func validate() -> UnrecoverableError? {
guard !self.allow.isEmpty else { return UnrecoverableError.invalidOptions(cause: "Must allow at least one database mode") }
guard !self.usernameStyle.isEmpty else { return UnrecoverableError.invalidOptions(cause: "Must specify at least one username style") }
guard self.allow.contains(.Login) || self.closable || self.autoClose else { return UnrecoverableError.invalidOptions(cause: "Must enable autoclose or enable closable") }
return nil
}
}
Expand Down
1 change: 1 addition & 0 deletions Lock/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public protocol Options {
var scope: String { get }
var parameters: [String: Any] { get }
var allow: DatabaseMode { get }
var autoClose: Bool { get }
var initialScreen: DatabaseScreen { get }
var usernameStyle: DatabaseIdentifierStyle { get }
var customSignupFields: [CustomTextField] { get }
Expand Down
2 changes: 1 addition & 1 deletion Lock/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ struct Router: Navigable {
exit(withError: UnrecoverableError.clientWithNoConnections)
return nil
}
let interactor = DatabasePasswordInteractor(connections: connections, authentication: self.lock.authentication, user: self.user)
let interactor = DatabasePasswordInteractor(connections: connections, authentication: self.lock.authentication, user: self.user, dispatcher: lock.observerStore)
let presenter = DatabaseForgotPasswordPresenter(interactor: interactor, connections: connections, navigator: self, options: self.lock.options)
presenter.customLogger = self.lock.logger
return presenter
Expand Down
63 changes: 13 additions & 50 deletions LockTests/Interactors/DatabaseInteractorSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ class DatabaseInteractorSpec: QuickSpec {

describe("init") {

let options = LockOptions()
let dispatcher = ObserverStore()

it("should build with authentication") {
let database = DatabaseInteractor(connection: DatabaseConnection(name: "db", requiresUsername: true), authentication: authentication, user: User(), options: LockOptions(), dispatcher: ObserverStore())
let database = DatabaseInteractor(connection: DatabaseConnection(name: "db", requiresUsername: true), authentication: authentication, user: User(), options: options, dispatcher: dispatcher)
expect(database).toNot(beNil())
}

it("should have authentication object") {
let database = DatabaseInteractor(connection: DatabaseConnection(name: "db", requiresUsername: true), authentication: authentication, user: User(), options: LockOptions(), dispatcher: ObserverStore())
let database = DatabaseInteractor(connection: DatabaseConnection(name: "db", requiresUsername: true), authentication: authentication, user: User(), options: options, dispatcher: dispatcher)
expect(database.credentialAuth.authentication.clientId) == "CLIENT_ID"
expect(database.credentialAuth.authentication.url.host) == "samples.auth0.com"
expect(database.credentialAuth.oidc) == false
Expand All @@ -56,12 +59,16 @@ class DatabaseInteractorSpec: QuickSpec {
var database: DatabaseInteractor!
var user: User!
var options: OptionBuildable!
var dispatcher: ObserverStore!

beforeEach {
options = LockOptions()
dispatcher = ObserverStore()

Auth0Stubs.failUnknown()
user = User()
let db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good))
database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: LockOptions(), dispatcher: ObserverStore())
database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher)
}

describe("updateAttribute") {
Expand Down Expand Up @@ -559,7 +566,6 @@ class DatabaseInteractorSpec: QuickSpec {
}
}
}

}

describe("login OIDC Conformant") {
Expand Down Expand Up @@ -733,7 +739,6 @@ class DatabaseInteractorSpec: QuickSpec {
}
}
}

}

// MARK: - Signup
Expand Down Expand Up @@ -796,7 +801,7 @@ class DatabaseInteractorSpec: QuickSpec {
}
}

context("Auto log in after sign up") {
context("auto log in after sign up") {

var options = LockOptions()

Expand Down Expand Up @@ -1020,48 +1025,6 @@ class DatabaseInteractorSpec: QuickSpec {
}
}

context("auto login disabled") {

var options: LockOptions!
var newUserEmail: String?

beforeEach {
newUserEmail = nil
options = LockOptions()
options.loginAfterSignup = false
var dispatcher = ObserverStore()
dispatcher.onSignUp = { email, _ in
newUserEmail = email
}
let db = DatabaseConnection(name: connection, requiresUsername: true, usernameValidator: UsernameValidator(withLength: 1...15, characterSet: UsernameValidator.auth0), passwordValidator: PasswordPolicyValidator(policy: .good))
database = DatabaseInteractor(connection: db, authentication: authentication, user: user, options: options, dispatcher: dispatcher)
stub(condition: databaseLogin(identifier: email, password: password, connection: connection)) { _ in return Auth0Stubs.failure() }
stub(condition: databaseSignUp(email: email, username: username, password: password, connection: connection)) { _ in return Auth0Stubs.createdUser(email) }
}

it("should only signup user") {
try! database.update(.email, value: email)
try! database.update(.username, value: username)
try! database.update(.password(enforcePolicy: false), value: password)
waitUntil(timeout: 2) { done in
database.create { create, login in
expect(create).to(beNil())
expect(login).to(beNil())
done()
}
}
}

it("should dispatch signup event") {
try! database.update(.email, value: email)
try! database.update(.username, value: username)
try! database.update(.password(enforcePolicy: false), value: password)
database.create { _ in }
expect(newUserEmail).toEventually(equal(email))
}

}

describe("signup OIDC Conformnant") {

beforeEach {
Expand Down Expand Up @@ -1253,7 +1216,7 @@ class DatabaseInteractorSpec: QuickSpec {
}
}
}

it("should send parameters on login") {
let state = UUID().uuidString
var options = LockOptions()
Expand All @@ -1272,7 +1235,7 @@ class DatabaseInteractorSpec: QuickSpec {
}
}
}

}

}
Expand Down
11 changes: 6 additions & 5 deletions LockTests/Interactors/DatabasePasswordInteractorSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class DatabasePasswordInteractorSpec: QuickSpec {
}

describe("init") {
let database = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: User())
let database = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: User(), dispatcher: ObserverStore())

it("should build with authentication") {
expect(database).toNot(beNil())
Expand All @@ -54,12 +54,14 @@ class DatabasePasswordInteractorSpec: QuickSpec {
var user: User!
var connections: OfflineConnections!
var forgot: DatabasePasswordInteractor!
var dispatcher: Dispatcher!

beforeEach {
dispatcher = ObserverStore()
connections = OfflineConnections()
connections.database(name: connection, requiresUsername: true)
user = User()
forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user)
forgot = DatabasePasswordInteractor(connections: connections, authentication: authentication, user: user, dispatcher: dispatcher)
}

describe("updateEmail") {
Expand Down Expand Up @@ -102,7 +104,7 @@ class DatabasePasswordInteractorSpec: QuickSpec {
describe("request email") {

it("should fail if no db connection is found") {
forgot = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: user)
forgot = DatabasePasswordInteractor(connections: OfflineConnections(), authentication: authentication, user: user, dispatcher: dispatcher)
try! forgot.updateEmail(email)
waitUntil(timeout: 2) { done in
forgot.requestEmail { error in
Expand Down Expand Up @@ -142,8 +144,7 @@ class DatabasePasswordInteractorSpec: QuickSpec {
}
}
}

}
}

}
Loading

0 comments on commit 3b3631e

Please sign in to comment.