Test for account partially logged into cloudkit? "Update Apple ID Settings" - swift

I've got an iOS app that is reliant on cloudkit for much of the functionality. However, I seem to be able to get in a state where the user is partially logged in. Basic cloudkit login checks pass, I can get my recordID, but I can't read or write to records like I can when I'm fully logged in.
Full details below - any ideas on how to identify this state without testing a read or write?
Upon launch of the phone, I get an 'Update Apple ID Settings' alert that states
"Some account services will not be available until you sign in again"
So, the issue is clearly related to that (which seems to be frequent with the simulator...). If I got to my settings and re-enter my password, all is well with the world. I can deal with this by error handling on an attempt to read or write, but I'd rather check in advance and warn the user appropriately.
How I check today:
At launch, I check to see if the user is logged in to cloudkit:
if FileManager.default.ubiquityIdentityToken != nil {
print("User logged in") // IT PASSES IN THIS STATE
}
else {
print("User is not logged in")
}
So far, so good. The test passes, the user is logged in. However, when I go to read or write, it is clear that I'm NOT logged in. For example, I'll get a CKErrorPermissionFailure when trying to write.
Additional note - in the current, 'partially'? logged in state, the below returns the correct recordID for my user:
let container = CKContainer.default()
container.fetchUserRecordID() { recordID, error in
Any ideas on how to programatically identify this partial state for cloudkit? Thanks!

I haven't found an answer that will address this, but wanted to document two things that could be of use
While the status check returns active, if I then try to fetch a record that requires you to be logged into iCloud, it fails and I can use that failure to determine logout status.
The other issue I've run into is a handful of users that have iCloud turned on but do NOT have iCloud Drive enabled for some reason. You can check for that with the approach below:
CKContainer.default().accountStatus { (accountstatus, error) in
switch accountstatus {
case .noAccount:
log.error("user logged out of iCloud OR iCloud Drive off")
case .restricted:
print("restricted")
case .available:
print("user logged into iCloud and iCloud drive")
case .couldNotDetermine:
log.warning("could not determine account status")
default:
log.error("New account status returned")
}
}

Related

KVO observe AVAudioSession's recordPermission doesn't work

My application uses the microphone's permission, which is requested in another framework, and in the main application, I wasn't able to observe when microphone permission changed. I tried using KVO's observer but the application doesn't receive any events when the microphone permission is changed.
private var permissionStatusObserver: NSKeyValueObservation?
private func observeRecordPermissionChange() {
    do {
      try audioSession.setActive(true)
      permissionStatusObserver = audioSession.observe(\.recordPermission) { [weak self] _, recordPermissions in
        print("recordPermission changed")
      }
    } catch {
      print("active audio failed \(error.localizedDescription)")
    }
  }
Not going to work.
There are 3 possibilities:
App never asked user for permissions yet. In this case you should present permissions to the user and wait for their response. In this case you need to define requestRecordPermission callback instead of listening to KVO.
App previously asked user for permissions, and user granted them. In this case you can proceed working with microphone.
App previously asked user for permissions, and user denied. Typically in this case apps show the message telling user to go to settings. And user needs to go to Settings -> Privacy -> Microphone and reenable the permissions, at which point the app will be restarted. So nothing to listen to as #cora mentioned.
Something like this:
switch AVAudioSession.sharedInstance().recordPermission {
case .granted:
// start recording
case .denied:
// Present message to user indicating that recording
// can't be performed until they change their preference
// under Settings -> Privacy -> Microphone
case . undetermined:
// Ask for permissions as explained below.
}
Or you can always ask for permissions like Apple tells us to:
// Request permission to record.
AVAudioSession.sharedInstance().requestRecordPermission { granted in
if granted {
// The user granted access. Present recording interface.
} else {
// Present message to user indicating that recording
// can't be performed until they change their preference
// under Settings -> Privacy -> Microphone
}
}
This is safe to do even if the permission is already granted (the callback will be back very quickly).

Can you validate whether a user has been recently authenticated on Firebase (Swift)?

Is there a way to check if a user has recently authenticated on Firebase to avoid this message when trying to delete a user: "This operation is sensitive and requires recent authentication. Log in again before retrying this request."
I have been playing around trying to compare lastSignInDate (below) to current time but there seems to be a large margin of error on this which can cause problems:
firebase.auth().currentUser.metadata.lastSignInTime
Are there any functions that can return a simple boolean as to whether a user has recently authenticated so the user.delete() function will work properly?
Thanks so much!
The best way to do this is by checking if the response has an error, like so:
let user = Auth.auth().currentUser
user.delete { error in
if let error = error {
if (error.code == "auth/requires-recent-login") {
// The user's credentials are too old. Prompt Login screen.
}
} else {
// ...
}
}
According to Firebase Documentation, There's no other approach to this other than comparing the current date with firebase.auth().currentUser.metadata.lastSignInDate (Only if you have the Admin SDK on your app, but you most probably do not need that for enabling a user to delete themselves).

firebase error when deleting user account "This operation is sensitive and requires recent authentication. Log in again before retrying this request."

When I want to delete a firebase user account in my application, the operation passes normally if the user has recently logged but after a period of time if I try to delete the user I get this error
"This operation is sensitive and requires recent authentication. Log in again before retrying this request."
Normally the firebase refresh the user session automatically but I didn't find why he want the user to log again and even the value of Auth.auth().currentUser is not nil. Thank you for your help !
this is my code to delete the user account :
#objc func deleteAccountAction(){
self.showProgressView()
let user = Auth.auth().currentUser
let id=Auth.auth().currentUser?.uid
self.refProducts.child(id!).removeValue { error, _ in
if(error != nil){
print("firebase remove error")
print(error?.localizedDescription.description ?? nil)
self.dismissHUD(isAnimated: true)
}
else{
self.refUsers.child(id!).removeValue { error, _ in
if(error != nil){
print("firebase remove error")
print("error while deleting user from firebase: "+error!.localizedDescription)
self.dismissHUD(isAnimated: true)
}
else {
user?.delete { error in
if error != nil {
print("error while deleting user:" + error!.localizedDescription)
self.dismissHUD(isAnimated: true)
} else {
UserDefaults.standard.removeObject(forKey: "USER_UID")
UserDefaults.standard.synchronize
self.dismissHUD(isAnimated: true)
let nextVC = self.storyboard?.instantiateViewController(withIdentifier: "StartingViewController") as! StartingViewController
nextVC.isAccoundDeleted=true
GlobalVar.user=nil
self.navigationController?.pushViewController(nextVC, animated: true)
}
}
}
}
}
}
}
For certain sensitive operations (such as changing the user's password, or deleting the user account), Firebase requires that the user has recently signed in. If the user hasn't signed in recently when you try to perform such an operation, Firebase throws the exception you get.
When you get this exception, you should ask the user to re-enter their credentials, and retry the operation.
From the documentation on handling errors:
[Deleting a user account] is a security sensitive operation that requires a recent login from the user. This error indicates the user has not signed in recently enough. To resolve, reauthenticate the user by invoking reauthenticateWithCredential:completion: on FIRUser.
In addition to Frank van's answer, time span for that is 5 minutes. After 5 minutes of login you cannot do such operations.
you can refer FIRAuthErrorCode (check out error code 17014 : FIRAuthErrorCodeRequiresRecentLogin = 17014)
Here is a workaround:
Make a call to a cloud function that performs the user account deletion and sign out the client. You must ensure proper guards for this cloud function so that it is not maliciously used.
Show courtesy to your users. Offer them with a proper dialog showing that this is irreversible and all relevant user data will be deleted permanently. Only call the function if the user accepts the consequences.
PSA edit:
Although this approach will work, it will have an improper risk imposed on your user profile and data. The requirement to have the user sign in again for the delete account procedure serves a higher objective which is an additional layer of protection for the user account. By bypassing it as the solution suggests you are imposing a risk that another actor using the device will be able to delete the account without any additional checks. it is a procedure that is irreversible so if it was done by mistake then there is no way to go back.
What you can do (its probably the most easy way) is to save login and password of the user in the local storage when they log in first time (for example using AsyncStorage) and once they want to delete the account log them in again without needing them to reenter credentials shortly before you delete the account. They wont even noticed you logged them in again before deleting the account.
Im not a security expert but the password and email would be stored localy without access to the outside world so I do not see any concerns about security issues there.
Once you delete the app, the local storage is gone anyways, at least with AsyncStorage.
Once you logout a user and use another account, the new account credentials would overwrite the old ones from the local storage.

Parse user not updating for just one specific user

I was wondering if anyone has seen a case where Parse User table doesn't update for a specific user. I have a pretty simple code:
PFUser.current()?["TorF"] = true
PFUser.current()?.saveInBackground(block: { (success, error) in
if error != nil {
print(error)
} else {
}
})
I have checks in other places of the app regarding whether there is a current PFUser, and my database shows that the user is logged in and PFUser.current() is correctly assigned to this user. The simple operation above works for all other users except for one specific user. Has anyone encountered something like this?
I found out that this was happening because the user had changed his password and therefore invalidated his access token. The place to check for whether the user's access token is valid is via Graph request.

CloudKit Login Missing Acount

When a user is not signed to Game Center a UI pops up in the app. If a user is logged in to iCloud the app gets the cloudkit user ID. However, I do not understand what happens if the user is not logged in to iCLoud at all. As far as I can tell the app does not promt the user. Is there a way to do this?
Thanks,
Henry
You have to test for that yourself and take the appropriate actions. For testing the status you can use the code below. At the line where the account status is logged you could show an alert where you point the user to the settings app.
container = CKContainer.defaultContainer()
database = container.publicCloudDatabase
container.accountStatusWithCompletionHandler({status, error in
if error != nil {
NSLog("Error: Initialising EVCloudKitDao - accountStatusWithCompletionHandler.\n\(error!.description)")
} else {
self.accountStatus = status
}
NSLog("Account status = \(status.hashValue) (0=CouldNotDetermine/1=Available/2=Restricted/3=NoAccount)")
})
NSLog("Container identifier = \(container.containerIdentifier)")
The code above is a snippet from EVCloudKitDao