I am relatively new to iOS software development.
I am trying to add security for user objects in my parse app.
But have no idea on how to add it to the project.
Should it be like this?
func logInViewController() {
PFUser.logInWithUsernameInBackground("myname", password: "mypass") {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
// Do stuff after successful login.
let user = PFUser.logInWithUsername("my_username", password: "my_password")
user.username = "my_new_username" // attempt to change username
user.save() // This succeeds, since the user was authenticated on the device.
// Get the user from a non-authenticated method.
let query = PFUser.query()
let userAgain = query!.getObjectWithId(user.objectId!) as! PFUser
userAgain.username = "another_username"
// This will crash, sinse the PFUser is not authenticated
userAgain.save()
} else {
// The login failed. Check error to see why.
}
let currentUser = PFUser.currentUser()
if currentUser != nil {
// Do stuff with the user
} else {
// Show the signup or login screen.
}
}
}
or like this?
func userOnlyNameChange() {
let user = PFUser.logInWithUsername("my_username", password: "my_password")
user.username = "my_new_username" // attempt to change username
user.save() // This succeeds, since the user was authenticated on the device.
// Get the user from a non-authenticated method.
let query = PFUser.query()
let userAgain = query!.getObjectWithId(user.objectId!) as! PFUser
userAgain.username = "another_username"
// This will crash, sinse the PFUser is not authenticated
userAgain.save()
}
or have I just done it completely wrong?
Related
I have a problem with Facebook api. After login and success unwrapping user token I have a problem with taking user profile: it's nil.
func updateLoginStatus() {
if let _ = AccessToken.current {
let currentUser = UserProfile.current
userName.text = currentUser?.fullName
userPictureView = UserProfile.PictureView(frame: blankUserImage.frame, profile: currentUser)
userPictureView!.clipsToBounds = true
userPictureView!.layer.cornerRadius = userPictureView!.bounds.width/2
popUpView.addSubview(userPictureView!)
isLogged = true
updateLabels()
} else {
isLogged = false
userPictureView?.removeFromSuperview()
updateLabels()
}
}
Even if I try to get profile manual by using let userProfile = UserProfile(userId: (AccessToken.current?.userId)!) it also take nil at all parameters, but it have initialized user. Funny part is that with that UserProfile.current it have take user picture. What it can be? How I can get full user name by other methods?
This problem happend in android too.
You should load profile (listen profile change in android) after you got access token.
I write a sample and just tested in swift 3, Facebook iOS SDK 4.21.0.
Then you can got updatedUser.
if let _ = FBSDKAccessToken.current() {
if let currentUser = FBSDKProfile.current() {
print("current user got: \(currentUser)")
} else {
print("current user not got")
FBSDKProfile.loadCurrentProfile(completion: {
profile, error in
if let updatedUser = profile {
print("finally got user: \(updatedUser)")
} else {
print("current user still not got")
}
})
}
}
I'm trying to login a user by using the username or email. Right now my code works to log the user in by only Username. I'm using a "PFUser.logInWithUsername", but I'd also like to login with the users email. I'm trying to change my code to allow the user to have the choice to either email or a username. Here is my code.
#IBAction func LogInButton(_ sender: AnyObject) {
// login functions
PFUser.logInWithUsername(inBackground: UsernameOrEmail.text!, password: Password.text!) { (user:PFUser?, error:Error?) -> Void in
if error == nil {
// remember user or save in App Memeory did the user login or not
UserDefaults.standard.set(user!.username, forKey: "username")
UserDefaults.standard.synchronize()
// call login function from AppDelegate.swift class
let appDelegate : AppDelegate = UIApplication.shared.delegate as! AppDelegate
// Delay the dismissal by 5 seconds
let delay = 1.0 * Double(NSEC_PER_SEC)
var time = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: time, execute: {
appDelegate.login()
})
} else {
}
You can use a regex pattern to detect if the user enters an email. If the detection returns false, then you "know" that the user entered their username.
This is what I use in my applications(works ok):
//A function that returns true or false based on the input. True if email, false if something else.
func isValidEmail(email:String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailTest.evaluate(with: email)
}
//check if user enters email or not:
if isValidEmail(email: user!.username){
//email adress detected
}else{
//username detected
}
EDIT:
As I understand your problem the function above will solve your problem. I have provided a some code for you to test out.
I do not know what PFUser is capable of, but I assume there is a function for login with username and another for email.
//A function that returns true or false based on the input. True if email, false if something else.
func isValidEmail(email:String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailTest.evaluate(with: email)
}
#IBAction func LogInButton(_ sender: AnyObject) {
//check if user enters username or email
if let usercredentials = UsernameOrEmail.text { //get the username from textfield
if isValidEmail(email: usercredentials){
//user did enter his email as login credential
PFUser.logInWithEmail(inBackground: usercredentials, password: Password.text!) { (user:PFUser?, error:Error?) -> Void in
if error == nil {
//do your login stuff here
} else {
}
}else{
//user did enter his username as login credential
PFUser.logInWithUsername(inBackground: usercredentials, password: Password.text!) { (user:PFUser?, error:Error?) -> Void in
if error == nil {
//do your login stuff here
} else {
}
}else{
//textfield not accessible
}
}
I am having an issue using Xcode 6.2 and Swift where I have a tuple that I am returning from a function. I have a subfunction that is running inside the function that is authenticating a user into our datastore and then returning the authentication tokens. If the user does not authenticate, i.e., has an error, then I am returning that correctly to the calling function. If the user passes authentication then I am only passing nil back to the calling controller even though I am making the same variable assignments. Here is the code that we are using:
func login(email:String, password:String) -> (uid: String?, provider: String?, error: NSError?) {
var errorStatement: NSError?
var provider: String?
var testResult: String?
var authData: FAuthData
ref.authUser(email, password: password) {
error, authData in
if error != nil {
// an error occured while attempting login
println("error is: \(error)")
errorStatement = error
testResult = "failed"
provider = "Error"
} else {
// user is logged in, check authData for data
testResult = "passed"
provider = authData.provider
userUID = authData.uid
println("User uid = \(userUID) and provider = \(provider)")
}
}
return (testResult, provider, errorStatement)
}
I am getting perfectly what I would expect when it has an error, but nothing when it is fine. I know this is going to be some simple fix but I cannot seem to find it. Thanks for any help as I am new to this language.
Here is the corrected code after I implemented the completion as suggested by #rdelmar
func login(email:String?, password:String?, completion: (result: String?, errorDesc: String?) -> Void) {
var errorStatement: String?
var testResult: String?
ref.authUser(email, password: password) {
error, authData in
if error != nil {
// an error occured while attempting login
println("error is: \(error)")
println(error.domain)
errorStatement = "errpor"//error.localizedDescription
testResult = "failed"
completion(result: testResult, errorDesc: errorStatement) //send the calling function the attached information
} else {
// user is l ogged in, check authData for data
testResult = "passed"
userUID = authData.uid
completion(result: testResult, errorDesc: errorStatement)//send the calling function the attached information
}
}
}
I'm using Parse.com, I'd like to integrate third-party authentication( except facebook and twitter), Sina weibo for example, I found no way to achieve this:
After my app authenticated by Sina Weibo App, I got an uid and an access token, First I query the uid in Parse data, if not found, I signup with a random username and password, then my PFUser.currentUser() is not nil
while what if the uid exist? how to become a user now?
Here is my code:
func upsertUser(userInfo: ISSPlatformUser!){
var query = PFQuery(className: "TokenStorage")
query.whereKey("wb_uid", equalTo: userInfo.uid())
query.findObjectsInBackgroundWithBlock({
(objects: [AnyObject]!, error: NSError!) in
println(objects)
if( objects.count == 0 ){
println("this account not register ")
self.newUser(userInfo)
}else{
println("already register")
var tokenData = objects[0] as PFObject
var user = tokenData.objectForKey("user") as PFUser
var accessToken = userInfo.credential().token()
println("accessToken:" + accessToken)
println("user:")
println(user)
if accessToken != tokenData.objectForKey("accessToken") as NSString {
tokenData.setObject(accessToken, forKey: "accessToken")
}
tokenData.saveInBackgroundWithBlock({
(succeed: Bool!, error: NSError!) in
if succeed! {
var sessionToken = user.sessionToken
println("sessionToken:")
println(sessionToken)
println("currentUser:")
println(PFUser.currentUser() )
PFUser.becomeInBackground(sessionToken, block: nil)
}
})
}
})
}
func newUser(userInfo: ISSPlatformUser!){
println("new user ")
var user = PFUser()
var s = NSMutableData(length: 24)!
SecRandomCopyBytes(kSecRandomDefault, UInt(s.length), UnsafeMutablePointer<UInt8>(s.mutableBytes))
let base64str = s.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.allZeros)
user.username = "wb_" + userInfo.uid()
user.password = base64str
user.signUpInBackgroundWithBlock({
(succeed: Bool!, error: NSError!) in
if succeed! {
var ts = PFObject(className: "TokenStorage")
ts.setObject(userInfo.uid(), forKey: "wb_uid")
ts.setObject(userInfo.credential().token(), forKey: "accessToken")
ts.setObject(user, forKey: "user")
var acl = PFACL()
acl.setPublicReadAccess(true)
acl.setPublicWriteAccess(false)
ts.ACL = acl
ts.saveInBackgroundWithBlock(nil)
}
})
}
The session token is nil that can not become a user, which make me frustrated.
One solution could be to sign up your user with a password you know. For instance, to create a unique password per user, you can encode the userInfo.uid() with a constant string you keep safe.
This way, if you find that a user is already registered, you can log in with username/password.
Let us know if you found a better solution.
I have already submitted a question on how to retrieve an objectId (swift - retrieve objectId field causes fatal error) but that seems to only apply to objects during a save because I have spent a long time (days) trying to retrieve the objectId from a saved row in a Parse cloud DB with absolutely no luck. I've mimicked the code in the Parse object retrieval documentation (https://parse.com/docs/ios_guide#objects-retrieving/iOS), using the special values provided for objectId's:
let objectId = gameScore.objectId
I was getting a crash every time but now the app just stops after printing the successful login message. Here is the code and sequence of events.
I signup a new user (let's call him vin).
The signup code:
#IBAction func signUp(sender: AnyObject) {
if countElements(self.userNameTextField.text) > 0 && countElements(self.passWordTextField.text) > 0 {
var megabyker = PFUser()
user.username = self.userNameTextField.text
user.password = self.passWordTextField.text
user.signUpInBackgroundWithBlock{
(succeeded:Bool!, error:NSError!)->Void in
if error == nil {
println("Sign Up successfull")
//associate current user for parse remote push notifications
var installation:PFInstallation = PFInstallation.currentInstallation()
installation.addUniqueObject("goride", forKey: "channels")
installation["user"] = PFUser.currentUser()
installation.saveInBackground()
//default row for allData table
//save default settings row for GUEST user
var alldata:PFObject = PFObject(className: "allData")
alldata["divvy_routes"] = "ON"
alldata["sortBy"] = "distance"
alldata["totalRides"] = 0
alldata["username"] = user.username
//self.useGuestUser = "USER"
alldata["user"] = PFUser.currentUser()
alldata.saveInBackgroundWithBlock{(success:Bool!, error:NSError!) ->Void in
if success != nil {
NSLog("%#","OK-alldata REAL data saved")
self.allDataSignUpObjectId = alldata.objectId
println("printing out objectId var in ALLDATA REAL section: ")
println(self.allDataSignUpObjectId)
self.performSegueWithIdentifier("go-to-main-menu", sender: self)
}
else
{
NSLog("%#",error)
}
}
} else {
let alert = UIAlertView()
alert.title = "Invalid Signup"
alert.message = "Sorry, a username (unique) and a password are required to create a new account."
alert.addButtonWithTitle("OK")
alert.show()
}
}
}
else
{
let alert = UIAlertView()
alert.title = "Invalid Signup"
alert.message = "Sorry, a username (unique) and a password are required to create a new account."
alert.addButtonWithTitle("OK")
alert.show()
}
}
The signup code works fine. It creates a new user and row in the User table and new default row in the allData table. The allData table has a user pointer column to associate it with the User table. I grab the objectId and assign it to an instance variable because I want to pass it to another viewcontroller to use:
self.allDataSignUpObjectId = alldata.objectId
println(self.allDataSignUpObjectId)
And here is a screenshot of the ParseDB where you can see the row has been saved in the user table for the new user. His objectId is 9vRF8BvdlZ and the row in the allData table has an objectId of DEaj0iWZ3X. This is the objectId I am trying to get (DEaj0iWZ3X).
allData table:
Now here is the allData where the new user's default setting row is inserted and it's automatically linked to the Vin's user table:
I then go to the Settings view controller and Save as the newly created user:
#IBAction func updateSettings(sender: AnyObject) {
var alldata:PFObject = PFObject(className:"allData")
var user = PFUser.currentUser()
var query = PFQuery(className:"allData")
query.whereKey("user", equalTo: user)
var id = allDataPass
println(id)
query.getObjectInBackgroundWithId(id) {
(alldata: PFObject!, error: NSError!) -> Void in
if error != nil {
NSLog("%#", error)
} else {
alldata["routes"] = self.RoutesSetting
alldata.saveInBackground()
}
}
}
It saves with no problems and the updated time in the row updates.
But when I logback in as Vin (after resetting the contents and settings on the simulator to get a fresh start), I get to LOGIN but it just stops (not crash, just stops and won't get past the login screen).
Here's the console printout:
2015-02-26 22:19:01.732 MegaByke[51906:6996084] Location Services Not Determined, must ask user
2015-02-26 22:19:01.734 MegaByke[51906:6996084] location services enabled: true
Location services success - <MegaByke.AppDelegate: 0x7ff4b145bd80>
2015-02-26 22:19:15.700 MegaByke[51906:6996084] Reachability Flag Status: -R -----l- networkStatusForFlags
<PFUser: 0x7ff4b167f830, objectId: 9vRF8BvdlZ, localId: (null)> {
username = vin;
}
Login successfull
objectId preFETCH
nil
Here is my login code:
#IBAction func login(sender: AnyObject) {
switch reachability!.currentReachabilityStatus().value{
case NotReachable.value:
var alert = UIAlertView(title: "No Internet Connection", message: "In order to login, you must have internet connection. You can still login as a Guest", delegate: nil, cancelButtonTitle: "Dismiss")
alert.show()
default:
if countElements(self.userNameTextField.text) > 0 && countElements(self.passWordTextField.text) > 0 {
PFUser.logInWithUsernameInBackground(userNameTextField.text, password: passWordTextField.text){
(user:PFUser!, error:NSError!)->Void in
if user != nil
{
println(user)
println("Login successfull")
//associate current user for parse remote push notifications
var installation:PFInstallation = PFInstallation.currentInstallation()
installation.addUniqueObject("goride", forKey: "channels")
installation["user"] = PFUser.currentUser()
installation.saveInBackground()
var user = PFUser.currentUser()
var query = PFQuery(className:"allData")
query.whereKey("user", equalTo: user)
// Retrieve the most recent one
query.orderByDescending("createdAt")
query.limit = 1;
//assign objectId
var alldata = PFObject(className:"allData")
println("objectId preFETCH")
println(alldata.objectId)
//refresh object
alldata.fetch()
let id = alldata.objectId
println("objectId postFETCH")
println(id)
query.getObjectInBackgroundWithId(id) {
(alldata: PFObject!, error: NSError!) -> Void in
if error == nil {
//Load parse data from cloud
NSLog("%#", alldata)
self.performSegueWithIdentifier("go-to-main-menu", sender: self)
} else {
NSLog("%#", error)
}
}
}
else { // Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
}
}
else{
println(user)
let alert = UIAlertView()
alert.title = "Invalid Login"
alert.message = "Sorry, that username and/or password is incorrect. Please enter a valid combination."
alert.addButtonWithTitle("OK")
alert.show()
println("Login failed")
}
}
}
The println that's supposed to occur after the fetch never gets to the console so I'm assuming I have not successfully retrieved the objectId. I have also checked Parse's user forum and no help there.