In trying to code up a prototype, it struct me there's no way revert the request?
So, you add the entitlements of interest to your app's capabilities, and here is I check:
internal func avStatus(for media: AVMediaType) -> AVAuthorizationStatus {
let status = AVCaptureDevice.authorizationStatus(for: media)
switch status {
case .authorized: // The user has previously granted access to the microphone.
return .authorized
case .notDetermined: // The user has not yet been asked for microphone access.
return .notDetermined
case .denied: // The user has previously denied access.
return .denied
case .restricted: // The user can't grant access due to restrictions.
return .restricted
default:
Swift.print("Unknown AV status \(status) for .audio")
return status
}
}
I was thinking that a user action to request the use of the entitlement, and relinquish would be needed:
#objc #IBAction func audioVideoServicesPress(_ sender: AnyObject) {
let service = sender.title.components(separatedBy: " ").last
let media : AVMediaType = service == "Audio" ? .audio : .video
let status = self.avStatus(for: media)
guard ![.denied,.restricted].contains(status) else { return }
if status == .authorized {
print("how do we relinquish need in the a/v device")
}
else
{
AVCaptureDevice.requestAccess(for: media, completionHandler: { granted in
return
})
}
}
In other user actions, if they had disabled or denied, then I would route them to the proper settings app, but before I get there, how to undo the request?
This is no way to un-request access the a/v device?
I think that suggests I'm going about this all wrong?
There is nothing to "relinquish."
The user's button press is an intent. The user wants to perform some action that can be performed only if you have authorization. So if you have it or can get, your job now is to do it.
Okay, so either you have authorization or you don't. If authorization status is .undetermined, you might get it. If it's .authorized, you already did get it. In either of those cases, do what the user intends!
At the time of your print line, you have authorization so now go ahead and do whatever the user pressed the button intending to do.
Similarly, do not return in the completion handler for requesting access; instead, check granted and if true, do whatever the user pressed the button intending to do.
In any other case, you are hosed, so do nothing. The user's intent requires an authorization you don't have and cannot get. You might put up a dialog explaining why you can't do it, or send them off to where they can access their settings, but that's all you can do from here.
Related
I need to register my user's with both phone authentication and email authentication. I have successfully done both. However, with the way I've structured my code, it's a bit flimsy.
After a user registers with their phone number, they are automatically signed in. But before they gain access to the application I need them to register with their email as well but I can't do so until they are signed in with their phone.
Group {
if (self.session.session != nil) {
if user?.metadata.creationDate != user?.metadata.lastSignInDate {
AppView()
} else {
EmailRegisterView()
}
} else {
OnBoardingView()
}
}.onAppear(perform: {
session.listen()
})
With the code I have, if the user for some reason logs out of their account on the same day that they've created it, they will be taken to the EmailRegisterView() again which will cause problems.
Is there another way to redirect users to the EmailRegisterView() only if they are new users, and once they have finished there, send them to the AppView()?
Here is my code for the phone authentication...
func phoneRegister () {
let credential = PhoneAuthProvider.provider().credential(withVerificationID: self.verificationCode, verificationCode: self.code)
Auth.auth().signIn(with: credential) { (result, error) in
if error != nil{
self.alertMessage = (error?.localizedDescription)!
self.alert.toggle()
return
}
UserDefaults.standard.set(true, forKey: "status")
NotificationCenter.default.post(name: NSNotification.Name("statusChange"), object: nil)
}
}
Looks like you can add a key to user default and check the value every time the user open the app.
If the userdefault bool key, let's say IsUserRegistered, doesn't exist or is set to false, then you navigate to login page/register page and set the key to true. Otherwise, if the the key is set to true, you just display the home view.
I want to keep user logged and not need to show login form everytime they open the app. I am using MongoDB Realm for database and authentication.
Right now the login works fine but it's required everytime the app is opened.
this my login code
#objc func signUp() {
setLoading(true);
app.usernamePasswordProviderClient().registerEmail(username!, password: password!, completion: {[weak self](error) in
// Completion handlers are not necessarily called on the UI thread.
// This call to DispatchQueue.main.sync ensures that any changes to the UI,
// namely disabling the loading indicator and navigating to the next page,
// are handled on the UI thread:
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
print("Signup failed: \(error!)")
self!.errorLabel.text = "Signup failed: \(error!.localizedDescription)"
return
}
print("Signup successful!")
// Registering just registers. Now we need to sign in, but we can reuse the existing username and password.
self!.errorLabel.text = "Signup successful! Signing in..."
self!.signIn()
}
})
}
#objc func signIn() {
print("Log in as user: \(username!)");
setLoading(true);
app.login(withCredential: AppCredentials(username: username!, password: password!)) { [weak self](maybeUser, error) in
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
// Auth error: user already exists? Try logging in as that user.
print("Login failed: \(error!)");
self!.errorLabel.text = "Login failed: \(error!.localizedDescription)"
return
}
guard let user = maybeUser else {
fatalError("Invalid user object?")
}
print("Login succeeded!");
self?.navigationController?.pushViewController(hostingController, animated: true)
}
this is my app rootView where I want to check and keep the user logged in
struct AppRootView: View {
var body: some View {
AnyView {
// check if user has already logged in here and then route them accordingly
if auth.token != nil {
homeMainView()
} else {
LoginController()
}
}
}
}
how can I keep user login with MongoDB realm?
From what I understand*, once a user authenticates, they will stay authenticated 'logged in' on that device until they are manually logged out, keeping in mind that once they are logged out their access token remains active for 30 minutes.
Two things from the guide
The access token for a session expires after thirty minutes. However,
a new session can be started by retrieving a new access token from
MongoDB Realm using the refresh token. (Important ->) The SDKs automatically take
care of refreshing access tokens, so you do not need to worry about
this when implementing client applications.
and
MongoDB Realm handles the access tokens and refresh tokens that
comprise a user session automatically.
What we are doing, which appears to be working ok, is this: When the app opens, we call a func handleSignIn which checks to see if the app has a .currentUser. If so, then we configure Realm. If not, a login/signup view is presented. Here's a snippit
func handleSignIn() {
if let _ = gTaskApp.currentUser() {
print("user is logged in")
self.configRealmSync()
} else {
print("not logged in; present sign in/signup view")
with gTaskApp being a global reference to our app
let gTaskApp = RealmApp(id: Constants.REALM_APP_ID)
*This is a work in progress so please feel free to correct me
I am having trouble using this code from AWS documentation to check the user state. No matter where I place it prints nothing. I also have properly set up my project with the AWS iOS SDK. I have placed in the AppDelegate and in different view controller's viewDidLoad and viewDidAppear however it still prints nothing.
AWSMobileClient.default().addUserStateListener(self) { (userState, info) in
switch (userState) {
case .guest:
print("user is in guest mode.")
case .signedOut:
print("user signed out")
case .signedIn:
print("user is signed in.")
case .signedOutUserPoolsTokenInvalid:
print("need to login again.")
case .signedOutFederatedTokensInvalid:
print("user logged in via federation, but currently needs new tokens")
default:
print("unsupported")
}
}
When we fill value string section in Info.plist for "Privacy - Photo Library Additions Usage Description" and as the application try to copy video file to Photo Library iOS automatically ask authorization and everything goes ok after that.
But in that case it is not enough. We would like to do things if user do not want it.
We are doing it for camera usage as following;
func checkCameraAuthorizations(){
// Checks privacy authorizations and change aplication behaviour accordingly.
if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
cameraUsageAuthorized = true
} else {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
self.cameraUsageAuthorized = true
} else {
self.cameraUsageAuthorized = false
}
})
}
}
We are using cameraUsageAuthorized variable in several place in application about camera usage.
But we could not find a similar function for video file copy from application document directory to Photo Library.
Also, we are filling Privacy values by hand in Info.List. Is there anyway to do it programmatically?
I could not find write only permission but as much as I see following code takes both write and read permission to photo library.
if PHPhotoLibrary.authorizationStatus() == .authorized {
self.photoLibraryAuthorized = true
} else {
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
self.photoLibraryAuthorized = true
} else {
self.photoLibraryAuthorized = false
}
}
}
But you must fill Privacy - Photo Library Additions Usage Description and Privacy - Photo Library Usage Description values.
I still don't know how to fill permission value strings in Info.plist programmatically though.
I am using Firebase to log in users into my app, but when I am adding the capability to manage their account like changing their email, password and so on. The documentation says that if the user have not recently signed in they need to re-authenticate, but my question is: How can I check if the user have signed in recently or not? According to the docs the error will return FIRAuthErrorCodeCredentialTooOld, but how can I check this?
Swift 3
I had to do this yesterday when trying to delete a user. One thing to note is FIRAuthErrorCodeCredentialTooOld is now FIRAuthErrorCode.errorCodeRequiresRecentLogin
What I did was trigger a UIView to ask for log in details if that error is thrown. Since I was using email and password, that's what I collected from the user in my example.
private func deleteUser() {
//get the current user
guard let currentUser = FIRAuth.auth()?.currentUser else { return }
currentUser.delete { (error) in
if error == nil {
//currentUser is deleted
} else {
//this gets the error code
guard let errorCode = FIRAuthErrorCode(rawValue: error!._code) else { return }
if errorCode == FIRAuthErrorCode.errorCodeRequiresRecentLogin {
//create UIView to get user login information
let loginView = [yourLoginUIViewController]
self.present(loginView, animated: true, completion: nil)
}
}
}
Once I had the login information I ran this function to reauthenticate the user. In my case I ran it the loginView in the above code if the login in was successful:
func reauthenticateUserWith(email: String, password: String) {
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if error == nil {
//display UIView to delete user again
let deleteUserView = deleteUserView()
present(deleteUserView, animated: true, completion: nil)
} else {
//handle error
print(error!.localizedDescription)
}
}
}
The deleteUserView in my case calls deleteUser() on a button tap from the user. You can also use UIAlertController in place of the custom UIViews, but that's up to you.
Hope this helps.
Update for current Swift 5
let user = Auth.auth().currentUser
user?.delete { error in
if let error = error {
let authErr = AuthErrorCode(rawValue: error.code)
if authErr == .requiresRecentLogin {
// reauthenticate
}
// other error
} else {
// delete success
}
}
According to the documents, there is currently no way to check FIRAuthErrorCodeCredentialTooOld other than going through the deleting of the account or the other sensitive cases mentioned.
If you are like me and ended up here because you are trying to figure out how to handle removing someone from Auth and removing other user data in Cloud Firestore, Realtime Database, and/or Cloud Storage, then there is a better solution.
Check out the Delete User Data Extension from Firebase to handle this. In short, when a user profile is deleted from Auth, you can use this also to delete data associated with the uid from those other Firebase data storage tools.