Skip to content

Commit

Permalink
Added support for rootAttributes (#557)
Browse files Browse the repository at this point in the history
* Added support for rootAttributes
* Added Tests
* Added info to README
  • Loading branch information
cocojoe authored Jul 23, 2019
1 parent a7e3a11 commit 642d33e
Show file tree
Hide file tree
Showing 17 changed files with 101 additions and 33 deletions.
10 changes: 6 additions & 4 deletions App/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,15 @@ func applyDefaultOptions(_ options: inout OptionBuildable) {

if #available(iOS 10, *) {
options.customSignupFields = [
CustomTextField(name: "first_name", placeholder: "First Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle), contentType: .givenName),
CustomTextField(name: "last_name", placeholder: "Last Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle), contentType: .familyName)
CustomTextField(name: "given_name", placeholder: "First Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle), contentType: .givenName),
CustomTextField(name: "family_name", placeholder: "Last Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle), contentType: .familyName),
CustomTextField(name: "nickname", placeholder: "Nick Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle), contentType: .nickname)
]
} else {
options.customSignupFields = [
CustomTextField(name: "first_name", placeholder: "First Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle)),
CustomTextField(name: "last_name", placeholder: "Last Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle))
CustomTextField(name: "given_name", placeholder: "First Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle)),
CustomTextField(name: "family_name", placeholder: "Last Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle)),
CustomTextField(name: "nickname", placeholder: "Nick Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle))
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
github "auth0/Auth0.swift" ~> 1.15
github "auth0/Auth0.swift" ~> 1.16
4 changes: 2 additions & 2 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
github "AliSoftware/OHHTTPStubs" "7.0.0"
github "Quick/Nimble" "v8.0.1"
github "Quick/Nimble" "v8.0.2"
github "Quick/Quick" "v2.1.0"
github "auth0/Auth0.swift" "1.15.0"
github "auth0/Auth0.swift" "1.16.0"
github "auth0/SimpleKeychain" "0.9.0"
github "emaloney/CleanroomLogger" "6.0.2"
4 changes: 4 additions & 0 deletions Lock.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
5B568F821E4B6506004B3D98 /* PasswordlessAuthenticatableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B568F811E4B6506004B3D98 /* PasswordlessAuthenticatableError.swift */; };
5B5F9F9F1E4B3FBE00EAB9EE /* PasswordlessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5F9F9E1E4B3FBE00EAB9EE /* PasswordlessView.swift */; };
5B64AC4F1F668CD0000A8842 /* InputFieldSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B64AC4D1F668B18000A8842 /* InputFieldSpec.swift */; };
5B675EC122E5D7C000B05831 /* UserStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B675EC022E5D7C000B05831 /* UserStorage.swift */; };
5B6E02C01EA77B4E00B28579 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6E02BF1EA77B4E00B28579 /* Extensions.swift */; };
5B889AEE1E700FCA00C9FBAF /* CountryCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B889AED1E700FCA00C9FBAF /* CountryCodes.swift */; };
5B8B795B1E8BAE5E00D0C57E /* passwordless_country_codes.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5B8B795A1E8BAE5E00D0C57E /* passwordless_country_codes.plist */; };
Expand Down Expand Up @@ -282,6 +283,7 @@
5B5F9F9E1E4B3FBE00EAB9EE /* PasswordlessView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordlessView.swift; sourceTree = "<group>"; };
5B64AC4D1F668B18000A8842 /* InputFieldSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputFieldSpec.swift; sourceTree = "<group>"; };
5B6631511DDB990B001CB043 /* EnterpriseDomainInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterpriseDomainInteractorSpec.swift; sourceTree = "<group>"; };
5B675EC022E5D7C000B05831 /* UserStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserStorage.swift; sourceTree = "<group>"; };
5B6E02BF1EA77B4E00B28579 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
5B889AED1E700FCA00C9FBAF /* CountryCodes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryCodes.swift; sourceTree = "<group>"; };
5B8B795A1E8BAE5E00D0C57E /* passwordless_country_codes.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = passwordless_country_codes.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -715,6 +717,7 @@
children = (
5FEEE8161DB6AC6C00B4DFED /* PasswordPolicy */,
5F2496B71D665AC500A1C6E2 /* UserAttribute.swift */,
5B675EC022E5D7C000B05831 /* UserStorage.swift */,
5B011CDD1E13E16500543F12 /* NotificationStatus.swift */,
5B2826421E32297E00E48467 /* NativeHandler.swift */,
5B568F7F1E4B64D2004B3D98 /* Passwordless.swift */,
Expand Down Expand Up @@ -1223,6 +1226,7 @@
5B0971801DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift in Sources */,
5FC434861D1DF769005188BC /* View.swift in Sources */,
5FDB41CE1D2C79FD00166B67 /* Operations.swift in Sources */,
5B675EC122E5D7C000B05831 /* UserStorage.swift in Sources */,
5B889AEE1E700FCA00C9FBAF /* CountryCodes.swift in Sources */,
5B1FD96D1E4E2B6B0055C1AC /* PasswordlessActivity.swift in Sources */,
5B55F3C91E24273D00B75CF5 /* UnrecoverableErrorView.swift in Sources */,
Expand Down
6 changes: 4 additions & 2 deletions Lock/CustomTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ public struct CustomTextField {

let name: String
let placeholder: String
let storage: UserStorage
let icon: LazyImage?
let keyboardType: UIKeyboardType
let autocorrectionType: UITextAutocorrectionType
let secure: Bool
let contentType: UITextContentType?
let validation: (String?) -> Error?

public init(name: String, placeholder: String, icon: LazyImage? = nil, keyboardType: UIKeyboardType = .default, autocorrectionType: UITextAutocorrectionType = .default, secure: Bool = false, contentType: UITextContentType? = nil, validation: @escaping (String?) -> Error? = nonEmpty) {
public init(name: String, placeholder: String, storage: UserStorage = .userMetadata, icon: LazyImage? = nil, keyboardType: UIKeyboardType = .default, autocorrectionType: UITextAutocorrectionType = .default, secure: Bool = false, contentType: UITextContentType? = nil, validation: @escaping (String?) -> Error? = nonEmpty) {
self.name = name
self.placeholder = placeholder
self.icon = icon
Expand All @@ -42,10 +43,11 @@ public struct CustomTextField {
self.secure = secure
self.contentType = contentType
self.validation = validation
self.storage = storage
}

var type: InputField.InputType {
return .custom(name: name, placeholder: placeholder, icon: icon, keyboardType: keyboardType, autocorrectionType: self.autocorrectionType, secure: secure, contentType: contentType)
return .custom(name: name, placeholder: placeholder, storage: storage, icon: icon, keyboardType: keyboardType, autocorrectionType: self.autocorrectionType, secure: secure, contentType: contentType)
}
}

Expand Down
12 changes: 9 additions & 3 deletions Lock/DatabaseInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,14 @@ struct DatabaseInteractor: DatabaseAuthenticatable, DatabaseUserCreator, Loggabl
} else {
error = nil
}
case .custom(let name):
case .custom(let name, let storage):
let field = self.customFields[name]
error = field?.validation(value)
self.user.additionalAttributes[name] = value
if case .rootAttribute = storage {
self.user.rootAttributes[name] = value
} else {
self.user.additionalAttributes[name] = value
}
self.user.validAdditionalAttribute(name, valid: error == nil)
}

Expand Down Expand Up @@ -118,6 +122,7 @@ struct DatabaseInteractor: DatabaseAuthenticatable, DatabaseUserCreator, Loggabl

let username = connection.requiresUsername ? self.username : nil
let metadata: [String: String]? = self.user.additionalAttributes.isEmpty ? nil : self.user.additionalAttributes
let rootAttributes: [String: String]? = self.user.rootAttributes.isEmpty ? nil : self.user.rootAttributes

let login = self.credentialAuth.request(withIdentifier: email, password: password, options: self.options)
self.credentialAuth
Expand All @@ -127,7 +132,8 @@ struct DatabaseInteractor: DatabaseAuthenticatable, DatabaseUserCreator, Loggabl
username: username,
password: password,
connection: databaseName,
userMetadata: metadata
userMetadata: metadata,
rootAttributes: rootAttributes
)
.start {
switch $0 {
Expand Down
4 changes: 2 additions & 2 deletions Lock/DatabasePresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ class DatabasePresenter: Presentable, Loggable {
attribute = .password(enforcePolicy: self.currentScreen == .signup)
case .username:
attribute = .username
case .custom(let name, _, _, _, _, _, _):
attribute = .custom(name: name)
case .custom(let name, _, let storage, _, _, _, _, _):
attribute = .custom(name: name, storage: storage)
default:
return
}
Expand Down
14 changes: 7 additions & 7 deletions Lock/InputField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ class InputField: UIView, Stylable {
case password
case phone
case oneTimePassword
case custom(name: String, placeholder: String, icon: LazyImage?, keyboardType: UIKeyboardType, autocorrectionType: UITextAutocorrectionType, secure: Bool, contentType: UITextContentType?)
case custom(name: String, placeholder: String, storage: UserStorage, icon: LazyImage?, keyboardType: UIKeyboardType, autocorrectionType: UITextAutocorrectionType, secure: Bool, contentType: UITextContentType?)

var placeholder: String? {
switch self {
Expand All @@ -286,7 +286,7 @@ class InputField: UIView, Stylable {
return "Phone Number".i18n(key: "com.auth0.lock.input.phone.placeholder", comment: "Phone placeholder")
case .oneTimePassword:
return "Code".i18n(key: "com.auth0.lock.input.otp.placeholder", comment: "OTP placeholder")
case .custom(_, let placeholder, _, _, _, _, _):
case .custom(_, let placeholder, _, _, _, _, _, _):
return placeholder
}
}
Expand All @@ -296,7 +296,7 @@ class InputField: UIView, Stylable {
return true
}

if case .custom(_, _, _, _, _, let secure, _) = self {
if case .custom(_, _, _, _, _, _, let secure, _) = self {
return secure
}
return false
Expand All @@ -316,7 +316,7 @@ class InputField: UIView, Stylable {
return lazyImage(named: "ic_phone")
case .oneTimePassword:
return lazyImage(named: "ic_lock")
case .custom(_, _, let icon, _, _, _, _):
case .custom(_, _, _, let icon, _, _, _, _):
return icon
}
}
Expand All @@ -335,7 +335,7 @@ class InputField: UIView, Stylable {
return .phonePad
case .oneTimePassword:
return .decimalPad
case .custom(_, _, _, let keyboardType, _, _, _):
case .custom(_, _, _, _, let keyboardType, _, _, _):
return keyboardType
}
}
Expand Down Expand Up @@ -369,14 +369,14 @@ class InputField: UIView, Stylable {
}
#endif
return nil
case .custom(_, _, _, _, _, _, let contentType):
case .custom(_, _, _, _, _, _, _, let contentType):
return contentType
}
}

var autocorrectionType: UITextAutocorrectionType {
switch self {
case .custom(_, _, _, _, let autocorrectionType, _, _):
case .custom(_, _, _, _, _, let autocorrectionType, _, _):
return autocorrectionType
default:
return .no
Expand Down
3 changes: 3 additions & 0 deletions Lock/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ protocol DatabaseUser {
var password: String? { get set }
var identifier: String? { get }
var additionalAttributes: [String: String] { get set }
var rootAttributes: [String: String] { get set }

var validEmail: Bool { get set }
var validUsername: Bool { get set }
Expand All @@ -51,6 +52,7 @@ class User: DatabaseUser, PasswordlessUser {
var password: String?
var additionalAttributes: [String: String] = [:]
var additionalAttributesStatus: [String: Bool] = [:]
var rootAttributes: [String: String] = [:]
var countryCode: CountryCode?

var validEmail: Bool = false
Expand All @@ -71,6 +73,7 @@ class User: DatabaseUser, PasswordlessUser {
self.password = nil
self.additionalAttributesStatus = [:]
self.additionalAttributes = [:]
self.rootAttributes = [:]
}

func validAdditionalAttribute(_ name: String) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion Lock/UserAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ enum UserAttribute {
case username
case password(enforcePolicy: Bool)
case emailOrUsername
case custom(name: String)
case custom(name: String, storage: UserStorage)
}
28 changes: 28 additions & 0 deletions Lock/UserStorage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// UserStorage.swift
//
// Copyright (c) 2019 Auth0 (http://auth0.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

public enum UserStorage {
case userMetadata
case rootAttribute
}
4 changes: 2 additions & 2 deletions LockTests/InputFieldSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class InputFieldSpec: QuickSpec {
}

it("should assign custom type") {
input.type = .custom(name: "test", placeholder: "", icon: nil, keyboardType: .twitter, autocorrectionType: .no, secure: false, contentType: nil)
input.type = .custom(name: "test", placeholder: "", storage: .userMetadata, icon: nil, keyboardType: .twitter, autocorrectionType: .no, secure: false, contentType: nil)
expect(text.keyboardType) == UIKeyboardType.twitter
}
}
Expand Down Expand Up @@ -182,7 +182,7 @@ class InputFieldSpec: QuickSpec {

if #available(iOS 10.0, *) {
it("should assign custom type") {
input.type = .custom(name: "test", placeholder: "", icon: nil, keyboardType: .default, autocorrectionType: .no, secure: false, contentType: .name)
input.type = .custom(name: "test", placeholder: "", storage: .userMetadata, icon: nil, keyboardType: .default, autocorrectionType: .no, secure: false, contentType: .name)
expect(text.textContentType) == UITextContentType.name
}
}
Expand Down
20 changes: 15 additions & 5 deletions LockTests/Interactors/DatabaseInteractorSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -350,28 +350,38 @@ class DatabaseInteractorSpec: QuickSpec {
}

it("should always store value") {
let _ = try? database.update(.custom(name: "first_name"), value: "Auth0")
let _ = try? database.update(.custom(name: "first_name", storage: .userMetadata), value: "Auth0")
expect(user.additionalAttributes["first_name"]) == "Auth0"
}

it("should raise error if value is empty") {
expect{ try database.update(.custom(name: "first_name"), value: "") }.to(throwError(InputValidationError.mustNotBeEmpty))
expect{ try database.update(.custom(name: "first_name", storage: .userMetadata), value: "") }.to(throwError(InputValidationError.mustNotBeEmpty))
}

it("should raise error if password is only spaces") {
expect{ try database.update(.custom(name: "first_name"), value: " ") }.to(throwError(InputValidationError.mustNotBeEmpty))
expect{ try database.update(.custom(name: "first_name", storage: .userMetadata), value: " ") }.to(throwError(InputValidationError.mustNotBeEmpty))
}

it("should raise error if password is nil") {
expect{ try database.update(.custom(name: "first_name"), value: nil) }.to(throwError(InputValidationError.mustNotBeEmpty))
expect{ try database.update(.custom(name: "first_name", storage: .userMetadata), value: nil) }.to(throwError(InputValidationError.mustNotBeEmpty))
}

it("should raise error for custom validation") {
var options = LockOptions()
let error = NSError(domain: "com.auth0", code: -99999, userInfo: [:])
options.customSignupFields = [CustomTextField(name: "first_name", placeholder: "First Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle), validation: { _ in return error })]
database = DatabaseInteractor(connection: DatabaseConnection(name: connection, requiresUsername: true), authentication: authentication, user: user, options: options, dispatcher: ObserverStore())
expect{ try database.update(.custom(name: "first_name"), value: nil) }.to(throwError(error))
expect{ try database.update(.custom(name: "first_name", storage: .userMetadata), value: nil) }.to(throwError(error))
}

context("root attributes") {

it("should store root attribute") {
let _ = try? database.update(.custom(name: "family_name", storage: .rootAttribute), value: "Doe")
expect(user.rootAttributes["family_name"]) == "Doe"
}


}

}
Expand Down
6 changes: 6 additions & 0 deletions LockTests/Models/UserSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ class UserSpec: QuickSpec {
user.reset()
expect(user.additionalAttributes["first_name"]).to(beNil())
}

it("should clear root attributes") {
user.rootAttributes["family_name"] = "Doe"
user.reset()
expect(user.rootAttributes["family_name"]).to(beNil())
}
}

describe("additional attributes") {
Expand Down
2 changes: 1 addition & 1 deletion LockTests/Presenters/DatabasePresenterSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ class DatabasePresenterSpec: QuickSpec {

it("should custom field") {
let name = "Auth0"
let input = mockInput(.custom(name: "first_name", placeholder: "Name", icon: LazyImage(name: "ic_auth0", bundle: Lock.bundle), keyboardType: .default, autocorrectionType: .no, secure: false, contentType: nil), value: name)
let input = mockInput(.custom(name: "first_name", placeholder: "Name", storage: .userMetadata, icon: LazyImage(name: "ic_auth0", bundle: Lock.bundle), keyboardType: .default, autocorrectionType: .no, secure: false, contentType: nil), value: name)
view.form?.onValueChange(input)
expect(interactor.custom["first_name"]) == name
}
Expand Down
Loading

0 comments on commit 642d33e

Please sign in to comment.