matthewpalmer / Locksmith

A powerful, protocol-oriented library for working with the keychain in Swift.
MIT License
2.92k stars 266 forks source link

How to fix Locksmith.LocksmithError.interactionNotAllowed #189

Open rain-bason opened 6 years ago

rain-bason commented 6 years ago

We keep receiving crashing from our users with the error Locksmith.LocksmithError.interactionNotAllowed. Unfortunately we can't replicate in our tests. The code only performs simple task:

func updateUserAccessToken(forAccount account: String, token: String) {
    var userAccessToken = Locksmith.loadDataForUserAccount(userAccount: account) ?? [String: Any]()
    userAccessToken[“token”] = token
    try! Locksmith.updateData(data: userAccessToken, forUserAccount: account)
}

UPDATE: We're able to trace and replicate the issue. The error occurs when the app calls the Locksmith.updateData() while the app is locked. This happen in our app during login, we assume that during the login process the app suddenly locked and when the process finally done and we try to save the user's token we can't because of that error.

Anyone know how to fix this?

Thanks!

rain-bason commented 6 years ago

I fixed this by changing the accessible option to LocksmithAccessibleOption.afterFirstUnlock

jglanfield commented 6 years ago

Where exactly did you make that change?

rain-bason commented 6 years ago

You can easily change it if you're using a protocol based approached. While in my case I'm not using that approach. What I did is to create an extension as follow:

extension Locksmith {
    fileprivate static func loadDataForUserAccount(userAccount: String,
                                                   inService service: String = LocksmithDefaultService,
                                                   accessibleOption: LocksmithAccessibleOption) -> [String: Any]? {
        struct ReadRequest: GenericPasswordSecureStorable, ReadableSecureStorable {
            let service: String
            let account: String
            var accessible: LocksmithAccessibleOption?
        }

        let request = ReadRequest(service: service, account: userAccount, accessible: accessibleOption)
        return request.readFromSecureStore()?.data
    }

    fileprivate static func updateData(data: [String: Any],
                                       forUserAccount userAccount: String,
                                       inService service: String = LocksmithDefaultService,
                                       accessibleOption: LocksmithAccessibleOption) throws {

        struct UpdateRequest: GenericPasswordSecureStorable, CreateableSecureStorable {
            let service: String
            let account: String
            let data: [String: Any]
            var accessible: LocksmithAccessibleOption?
        }

        let request = UpdateRequest(service: service, account: userAccount, data: data, accessible: accessibleOption)
        return try request.updateInSecureStore()
    }
}

Then you can pass the accessible option when you call these methods.

NOTE I notice that if you access your "old/existing" data before implementing a different accessible option you will get a nil as you can't access them. If you must have the "old/existing" data you may need to handle that separately.

jglanfield commented 6 years ago

So, I tried the following, but I still get the interactionNotAllowed error:

struct Authentication: GenericPasswordSecureStorable, CreateableSecureStorable, ReadableSecureStorable {
    let accessToken: String
    let refreshToken: String

    let service = LocksmithDefaultService
    let account = "test-account"
    let accessible = LocksmithAccessibleOption.always

    var data: [String: Any] {
        return ["accessToken" : accessToken, "refreshToken" : refreshToken]
    }
}
jglanfield commented 6 years ago

Suppose I should use afterFirstUnlock.

varyP commented 4 years ago

Any way to differentiate between does not exist vs interactionNotAllowed ?