Notify User of New Signin with Firebase - Swift/Xcode - swift

I have looked all over and can't figure out how to notify a user when a new device signs into their account. I'm using Firebase Authentication. I'd like to setup some kind of notification system or email that tells the user someone has signed into their account from a different device so they can know for security reasons.
Ideas?
Also, how could you monitor information about what device (s) are signed into a specific account? For example, maybe the user is curious how many devices he has signed into his account, what type of device they are, what the name of those devices are, and where they are located (example: San Antonio, Texas).
Ideas?

yes, you can use keychain for this situation. First you should create an uniqueId. After you can save to the db uniqueId. This value will change if the user uses another device. I'm using third party framework for Keychain services. You can use framework. It's perfect.
https://github.com/kishikawakatsumi/KeychainAccess
final class DeviceManager {
static let shared = DeviceManager()
private init() { }
var uniqueDeviceId: String {
get {
let keychain = Keychain(service: KeyManager.keychainServiceName).accessibility(.always)
if let deviceId = keychain[KeyManager.keychainDeviceIdKey] {
return deviceId
}
let vendorId = UIDevice.current.identifierForVendor!.uuidString
keychain[KeyManager.keychainDeviceIdKey] = vendorId
return vendorId
}
}
}

Super simple; have a field in your user document that stores a device name along with its status.
You app will be observing this users document and when something changes, all of the users devices will be notified of that change.
Let me set this up; here's a basic Firestore structure
users
uid_0
userName: "Jay"
devices:
device_0: offline
device_1: offline
When the app starts, it will add an observer to this users document (using the uid as the documentId)
func observeUser() {
let usersCollection = self.db.collection("users")
usersCollection.document("uid_0").addSnapshotListener { (documentSnapshot, err) in
guard let document = documentSnapshot else {
print("Error fetching document: \(err!)")
return
}
let device = document.get("devices") as! [String: String]
print(device)
}
}
Now in the Firestore closure shown above, if a users device changes status, offline to online for example, it outputs all of the devices to console. You would take whatever action is needed when the device changes status.
Keep in mind that if a NEW device is added, that event will also fire so you could present a message in the UI "A new device was added!"
So then some testing code that toggles the device_0 status from offline to online. I have a button click that does self.status = !self.status and then calls the toggleStatus function
var status = false
func toggleStatus() {
var isOnline = ""
if self.status == false {
isOnline = "online"
} else {
isOnline = "offline"
}
let userCollection = self.db.collection("users")
let thisDevice = "device_0"
let devicesDict = [
"devices":
[thisDevice: isOnline] //sets device_0 to offline or online
]
let document = usersCollection.document("uid_0").setData(devicesDict, merge: true)
}
In a nutshell, when a user authenticates with a device for the first time, it would perhaps ask for a device name, or craft one from the devices mac address or something under the hood. That device name is stored in the users document/devices with it's online status.
The device name would be stored locally as well, in user defaults for example so it's automatically sent up to Firestore upon login.
The end result here is that if any users devices change status; offline to online or vice versa, or any device is added or removed all of the devices are notified of that event.

Related

Why does the display name of my users keep reverting in Firebase with swift?

I encountered the following error in my swift project: https://github.com/firebase/firebase-ios-sdk/issues/4393. To overcome this issue I change the display name to the characters before the # in a users email like so:
var displayName = user.email!
if let atRange = displayName.range(of: "#") {
displayName.removeSubrange(atRange.lowerBound.. < displayName.endIndex)
}
if user.displayName!.count < 2{
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = displayName
changeRequest?.commitChanges {(err) in
if let err = err{
print(err)
}
}
}
This works when I first log in but if I log out and then back in again the display name reverts back to Optional(""). Why would it be doing this?
Edit
I created a workaround where I run the above code not only when a user is first created, but every time a user logs in, this seems excessive though and there must be a reason why the display name keeps getting overwritten.
This is not an error, this is intended behaviour from Apple's side. By default, you should be updating the Firebase user with the appropriate display name rather than relying on 3rd party information since these are only available on first login.
Simply checking if the name exists on initial login and is longer than 0 characters, not null, etc. then you can update the name with the following:
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = displayName
changeRequest?.commitChanges { (error) in
// ...
}
This is also documented within the git-issue you posted https://github.com/firebase/firebase-ios-sdk/issues/4393#issuecomment-612193703
Ultimately, you are logging into your app as a Firebase user, not an apple user. Apple is only Authenticating the process, so you must update the Firebase user where possible as Apple is highly inconsistent across all platforms.

How to check if user's email is verified?

I am making an application where the user can sign in whether their email is verified or not. However, if their email is not verified, they will only have access to one page and if it is verified they will have access to the entire application (AppView).
So far with the code I have, I am able to create a user and sign them in, giving them access to the full application. But I'm not sure how to show the other view if their email is not verified.
Here is my code for checking if a user is signed in:
func listen () {
// monitor authentication changes using firebase
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// if we have a user, create a new user model
print("Got user: \(user)")
self.isLoggedIn = true
self.session = User(
uid: user.uid,
displayName: user.displayName, email: "")
} else {
// if we don't have a user, set our session to nil
self.isLoggedIn = false
self.session = nil
}
}
}
And here is where I choose which view it should show:
struct ContentView: View {
#EnvironmentObject var session: SessionStore
func getUser () {
session.listen()
}
var body: some View {
Group {
if (session.session != nil) {
// I need to check here if the user is verified or not
AppView() //full application view
} else {
OnBoardingView()
}
}.onAppear(perform: getUser)
}
}
You can see if the user is verified by checking the User.isEmailVerified property. The main problem you may encounter is that this property is not immediately refreshed after you click the link in the email, as the iOS has no way of knowing that you clicked the link.
The client will automatically pick up the new value for isEmailVerified when it refreshes the token, which automatically happens every hour, when the user signs in explicitly again, or when you explicitly force it to get a new ID token.
Common ways to make this flow run smoothly for your users are to:
Force a ID token refresh when the iOS regains focus.
Force a Id token refresh periodically, while on a "waiting until you verified your email address" screen.,
Show a button to the user "I verified my email address" and then refresh the ID token.
Alternatively you can sign the user out after sending them the email, and then check when they sign in again. While this is simpler to implement, the above flow is more user friendly.

How to manage acces control at login

I been looking for control what a kind of user can see in my app, this is a scholar project. I'm using Swift and Firebase Authentication. I have two kinds of users: Model and Client. In my app I have some views for the Model and other for the Client. What I want to do is that once they log in, in to the app show just the views for their kind of user. I don't know how to verify if the user that is trying to sign in is a Model or a Client.
#IBAction func signInTapped(_ sender: UIButton) {
if validateFields(){
Auth.auth().signIn(withEmail: emailTxt.text!, password: passTxt.text!, completion:{
(user, error) in
if let u = user {
//User is found
}else{
//Error
}
})
}
}
I know that the code need to be where is the comment "User is found" but I don't know if I need to modify something into the the Firebase Console
Create a Firebase Database or Firestore to your project.
Now when you authenticate a user you should also create a userobject in your databse. In this object you can create a field to store whether your user is a model or a client.
Now once the user has signed in, you can download this userobject from the database, check whether the user is a model or client, and send the user to their corresponding views.
You can use custom claims.
You set them using the Admin SDK
// Javascript
admin.auth().setCustomUserClaims(uid, {model: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
Then in client SDK just read the claim.
user.getIDTokenResult(completion: { (result, error) in
guard let model = result?.claims?["model"] as? NSNumber else {
// Something
}
if model.boolValue {
// Show something else
} else {
// Something else again
}
})
Shamelessly copied from Firebase Docs

HealthKit unauthorized versus 0 records in set

IOS 9.2.1, Swift 2.1
I'm trying to give the user a reasonable error message when accessing HealthKit and 0 records are returned for a query.
It could be that there were no records within the selected time range or it could be that the user has disallowed access to that particular dataset inside health. In both case the "storage.requestAuthorizationToShareTypes" provides a "success" value of true.
Is there a way to get the HKHealthKit store give me a code that indicates that the access has been disabled?
My code below
Thanks Mike
import Foundation
import HealthKit
// Interface to the HealthKit
class HealthKitIF {
let storage = HKHealthStore()
var stepsEnabled = false
var bgEnabled = false
var hkSupported = false
init () {
self.checkAuthorization()
}
func checkAuthorization () -> Bool {
// Default to assuming that we're authorized
var isEnabled = true
if (NSClassFromString("HKHealthStore") != nil) { hkSupported = true }
// Do we have access to HealthKit on this device?
if ((hkSupported) && (HKHealthStore.isHealthDataAvailable())) {
// We have to request each data type explicitly
// Ask for BG
var readingsSet = Set<HKObjectType>()
readingsSet.insert(HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)!)
readingsSet.insert(HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)!)
storage.requestAuthorizationToShareTypes(nil, readTypes: readingsSet) { (success, error) -> Void in
isEnabled = success
self.bgEnabled = success
}
}
else
{
isEnabled = false
}
return isEnabled
}
Your app should not present an error message when there are no results for a query. HealthKit is designed to keep the user's read authorization choices private by not differentiating between unauthorized access and no data. It may, however, be helpful to include a reminder somewhere in your app or on your support pages that describes how the user can adjust their Health privacy settings if they are not seeing expected behavior in your app.
From the HKHealthStore class reference:
To help prevent possible leaks of sensitive health information, your
app cannot determine whether a user has granted permission to read
data. If you are not given permission, it simply appears as if there
is no data of the requested type in the HealthKit store. If your app
is given share permission but not read permission, you see only the
data that your app has written to the store. Data from other sources
remains hidden.

Swift & Parse.com find by specific column not ID

Reference: http://blog.parse.com/2014/06/06/building-apps-with-parse-and-swift/
I'm trying to find a columns value: userPassword, based in the userName column. Using the above reference from Parse it shows that to get data from parse you should use:
var query = PFQuery(className: "GameScore")
query.getObjectInBackgroundWithId(gameScore.objectId) {
(scoreAgain: PFObject!, error: NSError!) -> Void in
if !error {
NSLog("%#", scoreAgain.objectForKey("playerName") as NSString)
} else {
NSLog("%#", error)
}
}
However, as you can see it is looking for (gameScore.objectId) - The problem is I do not know this value as the user isnt entering a complex parse generated ID. They're entering their chosen username. In the rows I have userName and Password set. How do I search the rows for the userPassword so I can verify it based on their specified userName.
Thanks in advance
Why are you querying the database for a username and password. Adding a new user is very simple with Parse. Taken directly from their docs:
Query User table on Parse
You can query the user table first, using a PFQuery:
PFQuery *query = [PFUser query];
[query whereKey:#"username" equalTo:username];
Adding New User
The idea of user accounts that let people access their information and share it with others in a secure manner is at the core of any social app. Whether your app creates its own sharing environment or integrates with existing social networks, you will need to add functionality to let people manage their accounts in your app.
We provide a specialized user class called PFUser that automatically handles much of the functionality required for user account management.
First make sure to include our SDK libraries from your .h file:
#import <Parse/Parse.h>
Then add this code into your app, for example in the viewDidLoad method (or inside another method that gets called when you run your app):
func myMethod() {
var user = PFUser()
user.username = "myUsername"
user.password = "myPassword"
user.email = "email#example.com"
// other fields can be set just like with PFObject
user["phone"] = "415-392-0202"
user.signUpInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
// Hooray! Let them use the app now.
} else {
let errorString = error.userInfo["error"] as NSString
// Show the errorString somewhere and let the user try again.
}
}
}
This call will asynchronously create a new user in your Parse app. Before it does this, it checks to make sure that both the username and email are unique. It also securely hashes the password in the cloud.
You can learn more about Users, including how to verify emails and handle read and write permissions to data, by visiting our docs.
Run your app. A new object of the class User will be sent to the Parse Cloud and saved. When you're ready, click the button below to test if a User was created.
Further
I created a tutorial about connecting to parse if you still wish to go down the route of querying the server manually:
http://ios-blog.co.uk/tutorials/swift-create-user-sign-up-based-app-with-parse-com-using-pfuser/