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.
Related
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.
Let's say I have a collection of documents in the Cloud Firestore. I want people to observe these documents, but I don't want more than 100 people observing the same document at any point in time. How do I effectively track this?
I was initially thinking to track the amount of observers/listeners by managing an array of user IDs in each document. Effectively, users would add their ID to this array before observing/listening (or be turned away if this array was too large) and remove their IDs from this array when they stop. The problem with this is that I can't just stop the user from leaving if the call to delete this ID from the array fails. What if they terminate the app in some way and the call to remove their ID doesn't go through?
Is there a more reasonable solution to this problem? Could this be solved with the Realtime Database or Cloud Functions? Thanks!
I'm not sure, but try to google the Firebase access rules. I think that you can manage the access in this scope.
I talk about this rules.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
Sorry for answer in that style, i do not have the needed reputation to create comments
SOLVED! Big thanks to Renaud Tarnec for his original comment referencing documentation I had not yet seen. I was missing the keyword 'presence' in my Google searches. Please upvote his comment if you find this answer helpful!
SOLUTION: Using https://firebase.google.com/docs/firestore/solutions/presence
SWIFT:
var connectionReference: DatabaseReference = Database.database().reference(withPath: ".info/connected")
var personalReference: DatabaseReference?
var connectionIssued: Bool = false
func connect(room: String) {
// Remove any existing observations to ensure only one exists.
connectionReference.removeAllObservers()
// Start observing connection.
connectionReference.observe(.value) { (isConnected) in
if isConnected.value as! Bool {
// Connected!
// Use Bool connectionIssued to ensure this is run only once.
if !self.connectionIssued {
self.connectionIssued = true
self.personalReference = Database.database().reference(withPath: "OnlineUsers/\(userID)")
// Set onDisconnect before setting value.
self.personalReference!.onDisconnectRemoveValue()
self.personalReference!.setValue(room)
// Now the user is "online" and can proceed.
// Allow user to "enter" room.
} else {
// Connection already issued.
}
} else {
// The user has either disconnected from an active connection or they were already disconnected before connect() was called.
// Stop observing connection.
self.connectionReference.removeAllObservers()
// If the user disconnects after they have entered a room, kick them out.
if self.connectionIssued {
// User lost connection.
kickUserOutOfRoom()
self.connectionIssued = false
} else {
// User cannot log in.
}
}
}
}
// Call when users leave a room when still connected.
func leaveRoomManually() {
// Remove connection observation.
connectionReference.removeAllObservers()
// Attempt to remove "online" marker in database.
personalReference?.removeValue(completionBlock: { (error, reference) in
if error != nil {
// Removal failed, but that should be okay!
// onDisconnect will still be called later!
// This failure might result in ghost users if the user proceeds to join another room before disconnecting.
// Consider writing an onUpdate() cloud function in conjunction with the following onCreate() and onDelete() cloud functions to take care of that case.
} else {
// "Online" marker removed from database!
// We can now cancel the onDisconnect()
self.personalReference?.cancelDisconnectOperations()
}
})
leaveRoom()
}
CLOUD FUNCTIONS (javascript):
The following cloud functions update the respective room's guest count in Cloud Firestore.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const firestore = admin.firestore();
exports.userCameOnline = functions.database.ref('/OnlineUsers/{userID}').onCreate(
async (snapshot, context) => {
const room = snapshot.val();
const guestCountRef = firestore.doc(`Rooms/${room}`);
return guestCountRef.update({
Guests: admin.firestore.FieldValue.increment(1)
});
});
exports.userWentOffline = functions.database.ref('/OnlineUsers/{userID}').onDelete(
async (snapshot, context) => {
const room = snapshot.val();
const guestCountRef = firestore.doc(`Rooms/${room}`);
return guestCountRef.update({
Guests: admin.firestore.FieldValue.increment(-1)
});
});
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
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/
I am looking at the new user access control abilities with the introduction of Touch ID when accessing keychain.
Here's the scenario...
I have written a user secret to the keychain with the new access control object in the Keychain query.
Next time I attempt to return this secret, I will be presented with the Touch ID / Passcode interface as expected.
Next time I attempt to write the same data to the keychain, I want to first check if it already exists.
This also presents the Touch ID interface even if i specify the return data attribute to false. But this is not what I want.
Is this the expected behaviour or am I missing a specific attribute to disable the Touch ID interface?
I think what you are trying to achieve is possible by doing something similar to:
// --- Add this code to your save method
// Adds a new keychain item
let status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
if status == errSecSuccess {
println("Keychain Add: \(KeychainResultCode(rawValue: status)?.description)")
return true
} else if status == errSecDuplicateItem {
// perform an update
return self.update(key, data: data)
} else {
return false
}
Does the above works for your needs?