-
Notifications
You must be signed in to change notification settings - Fork 110
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
1Password Support for Database Connections #422
Conversation
Test Notes:Must have 1Password app installed on device. info.plistAdd following to the app's <key>LSApplicationQueriesSchemes</key>
<array>
<string>org-appextension-feature-password-management</string>
</array> OptionsBy default the bundler identifier will be used, If you want to share credentials with a website that uses Auth0, then use the name of the site e.g. .withOptions {
$0.passwordManagerAppIdentifier = "www.website.com"
} |
5723f1b
to
1379367
Compare
Additional thoughts, currently the password recipe will match the DB password policy, although this feels incredibly weak for anything < fair. Given that if you are using a password manager you are mostly setting it and forgetting it. Perhaps the default should be fair+ ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the 1password button is a bit bigger than I'd like
Lock/LockOnePasswordExtension.h
Outdated
@@ -0,0 +1,210 @@ | |||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd keep the name without the Lock
prefix and also I hope this is no part of the public headers right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it's not, they recommend renaming it so I did that 'just in case'. I made it an isolated commit so easy to revert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably makes sense for apps to avoid clashing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But here we don't need to rename it since it won't be a public header
Lock/OptionBuildable.swift
Outdated
@@ -90,6 +90,9 @@ public protocol OptionBuildable: Options { | |||
|
|||
/// Specify the passwordless method, send a passcode or send a magic link. By default is .emailCode | |||
var passwordlessMethod: PasswordlessMethod { get set } | |||
|
|||
/// Enable and specify the 1Password identifier to use, typically you will want to use the app's bundle identifier. However you may have a website and want to ensure 1Password works with both by using an identifier like `www.mysite.com` | |||
var enableOnePasswordWithIdentifier: String? { get set } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's split this in two
/// Show password manager button when user credentials are needed, e.g. during Sign Up, and allow storing/retrieving credentials from the password manager
var showPasswordManager: Bool { get set }
/// Application identifier used by the password manager, if you also have a web app we recommend adding your site url. By default is the application bundle identifier
var passwordManagerAppIdentifier: String { get set }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I merged it into one, as I look at it from the perspective of if I am going to enable it then I will want to use it, so why not in streamline into one option? However I have no strong feelings either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First I'd like it to be enabled by default and ideally some people won't set the app identifier and prefer to use the bundle identifier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought perhaps passwordManagerAppIdentifier
should be optional as Bundle.main.bundleIdentifier
is an optional. Although the user has bigger issues in their app if their bundleIdentifier is nil. So forced it.
We should probably add another option for the Password Title in 1Password, by default it will parse the identifier however it's designed to parse website urls so looks weird.
We could of course just pass the CFBundleDisplayName
on the store
method as it would also be strange not to use the same name as the app for 1Password.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've set it to use display name as title for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can have passwordManagerDisplayName
(Default bundle display name) or improve the options and have a struct to configure the password manager with the three properties inside
Lock/PasswordPolicy.swift
Outdated
return [ AppExtensionGeneratedPasswordMinLengthKey: "8", | ||
AppExtensionGeneratedPasswordRequireDigitsKey: true, | ||
AppExtensionGeneratedPasswordRequireSymbolsKey: true ] | ||
case Auth0.excellent.rawValue: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a comment and yes use excellent for every rule even if there is no rule
e4348a2
to
d3468ad
Compare
373a933
to
c3ef57e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the 1pass icon seems too big compared to the field. I'd try to leave around 3-4 pt of padding in the edges
Lock/OnePassword.swift
Outdated
@@ -71,3 +71,19 @@ class OnePassword: PasswordManager { | |||
} | |||
} | |||
} | |||
|
|||
public struct PasswordManagerConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't use the same PasswordManager instead of creating yet another type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not see any requirement or need for PasswordManager Protocol to be a Public API. It also utilises internal types such as InputField
...
It is debatable to use a struct vs properties for 2-3 values anyway. So can always revert to properties.
Although another type is not a big deal either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use the OnePassword type and configure it, the protocol can still be private (and implemented by OnePassword) since we are not allowing any extra extensibility.
Lock/OptionBuildable.swift
Outdated
|
||
/// Application identifier used by the password manager, if you also have a web app we recommend adding your site url. By default is the application bundle identifier | ||
var passwordManagerAppIdentifier: String { get set } | ||
/// Configuration to be used by the password manager. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we explain a bit better what it means in the code comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also we need to explain its attributes
Lock/OnePassword.swift
Outdated
@@ -71,3 +71,19 @@ class OnePassword: PasswordManager { | |||
} | |||
} | |||
} | |||
|
|||
public struct PasswordManagerConfig { | |||
public var isEnabled: Bool = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think enabled is fine
README.md
Outdated
|
||
```swift | ||
.withOptions { | ||
$0.passwordManager = PasswordManagerConfig(appIdentifier: "www.myapp.com", displayName: "My App") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having https://github.com/auth0/Lock.swift/pull/422/files#diff-04c6e90faac2675aa89e2176d2eec7d8R371 why not follow the same approach?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did originally then changed it, will revert.
Lock/ClassicRouter.swift
Outdated
@@ -52,6 +52,10 @@ struct ClassicRouter: Router { | |||
let authentication = self.lock.authentication | |||
let interactor = DatabaseInteractor(connection: database, authentication: authentication, user: self.user, options: self.lock.options, dispatcher: lock.observerStore) | |||
let presenter = DatabasePresenter(interactor: interactor, connection: database, navigator: self, options: self.lock.options) | |||
if self.lock.options.passwordManager.isEnabled, OnePassword.isAvailable() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Below I suggested to join the config with the object itself so we could just ask it if its enabled (user settings + app availability in the same query).
Ideally we could just assign it all the time and ask it if its enabled when trying to use it since we will do the same but with the optional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure there is any real life advantage, if OnePassword is not available on device (still needs checked), there is not much point in initialising the manager just to perform a bool check vs an optional check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less code, encapsulation and simplicity. The presenter will always have a password manager and will always ask it if it should display the button. All the thing is done in the presenter and the router only needs to inject it.
Lock/LockOnePasswordExtension.h
Outdated
@@ -0,0 +1,210 @@ | |||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But here we don't need to rename it since it won't be a public header
Lock/OnePassword.swift
Outdated
} | ||
} | ||
|
||
public struct PasswordManagerConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just merge the password manager and the config its not necessary to add an extra type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also let's add code comments to the attributes
Lock/OnePassword.swift
Outdated
} | ||
|
||
public struct PasswordManagerConfig { | ||
public var isEnabled: Bool = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets use enabled
Lock/OptionBuildable.swift
Outdated
@@ -90,6 +90,9 @@ public protocol OptionBuildable: Options { | |||
|
|||
/// Specify the passwordless method, send a passcode or magic link. By default is .code | |||
var passwordlessMethod: PasswordlessMethod { get set } | |||
|
|||
/// Configuration to be used by the password manager. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets explain it a bit more
0753496
to
b4234ad
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cocojoe also please update the screenshots showing the button, if needed, so I can see how it looks now
Lock/DatabaseOnlyView.swift
Outdated
@@ -108,9 +109,15 @@ class DatabaseOnlyView: UIView, DatabaseView { | |||
self.layoutSecondaryButton(self.allowedModes.contains(.ResetPassword)) | |||
self.form = form | |||
|
|||
if passwordManager.enabled, passwordManager.isAvailable() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably I wasn't clear before but ideally the passwordManager.isAvaliable()
could be encapsulated inside the passwordManager.enabled
Lock/DatabaseOnlyView.swift
Outdated
@@ -85,7 +86,7 @@ class DatabaseOnlyView: UIView, DatabaseView { | |||
private let separatorIndex = 2 | |||
private let socialIndex = 1 | |||
|
|||
func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String? = nil, authCollectionView: AuthCollectionView? = nil) { | |||
func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String? = nil, authCollectionView: AuthCollectionView? = nil, passwordManager: PasswordManager) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a design stand, passing the password manager is sort of breaching the separation of concerns of the view and presenter. Ideally the view should only know that it should show the button (just a simple true or false) then the rest of the work should be in the presenter (the wiring)
Lock/OnePassword.swift
Outdated
public var displayName: String | ||
|
||
weak var controller: UIViewController? | ||
var fields: [String: InputField] = [:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why I didnt spot this before but I am not too convinced about this mapping. AFAIK the dict will strong retain the field and thats a thing we want to avoid. Also mixing something from the view here is sort of a code smell, maybe we need to work this around by either having closures but maybe that is not enough either
Lock/OnePassword.swift
Outdated
|
||
public init() { | ||
self.appIdentifier = Bundle.main.bundleIdentifier! | ||
self.displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No force-wrap please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather use here a default like <No App>
. This wont be ever execute since Xcode will fail without the bundle name but its better than a force-wrap
@@ -119,6 +119,36 @@ class PasswordPolicySpec: QuickSpec { | |||
|
|||
} | |||
|
|||
describe("one password extension") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there some way we can test this in a better way?
7ac54e9
to
4243a0b
Compare
Lock/DatabasePresenter.swift
Outdated
@@ -146,6 +149,23 @@ class DatabasePresenter: Presentable, Loggable { | |||
view.secondaryButton?.onPress = { button in | |||
self.navigator.navigate(.forgotPassword) | |||
} | |||
|
|||
if let identifyField = view.identityField, let passwordField = view.passwordField { | |||
passwordManager.onUpdate = { identifier, password in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good but in the end we end up having the same retain behavior with a different implementation
Lock/InputField.swift
Outdated
button.color = color | ||
container.addSubview(button) | ||
|
||
self.textFieldRightPadding?.constant = -50 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Being picky but can't we disable this constraint and add a new one between field and button?
|
||
public init() { | ||
self.appIdentifier = Bundle.main.bundleIdentifier.verbatim() | ||
self.displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName").verbatim() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LOL, nice trick
f9f42b4
to
f8ce528
Compare
@hzalaz this can be merged |
} | ||
} | ||
view.passwordManagerButton?.onPress = { _ in | ||
self.passwordManager.login { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are retaining the presenter here
} | ||
} | ||
view.passwordManagerButton?.onPress = { _ in | ||
self.passwordManager.store(withPolicy: passwordPolicyValidator?.policy.onePasswordRules(), identifier: self.creator.identifier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are retaining the presenter here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For both, there is no strong reference to the block which is why I didn't modify in the last pass. Quick checked in DatabasePresenter.swift
with:
deinit {
print("dealloc")
}
@hzalaz I can't merge as the codecov is a bit weird, I set the 3rd Party OnePasswordExtension .h/.m to be ignored in CodeCov |
f8ce528
to
127c358
Compare
Added PasswordManager Protocol Added 1Password Swift Facade Added IconButton component OnePassword Assets Update PodSpec
PasswordManager Added to Classic Router with Extension check PasswordPolicy extension to convert existing policy into 1Password password Recipe PasswordManager added to Database Signup, will store username when available
127c358
to
5eb27eb
Compare
Update README PaswordManager Section
Update Mocks Tidied up UITests as got broke in rebase (Snapshot)
5eb27eb
to
8f862f5
Compare
All PasswordPolicy levels supported in Sign Up password generation
Added Lock options
showPasswordManager
,passwordManagerAppIdentifier
Embedded OnePassword ObjC Extension into Lock