How to manage acces control at login - swift

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

Related

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.

Associate user information from Cognito with AWS Amplify GraphQL

I am on xcode 11.4, Swift 4. The goal is to:
sign up a new user in Cognito User Pool, and then save an associated user record using Amplify GraphQL.
CRUD the user's record after signing in with Cognito User Pool.
The problem is I do not know how to associate Cognito with Amplify GraphQL. For example, in Google Firebase auth and Firestore, I would get a unique user id UID after signing up, then I would create an associated user record in Firestore with the key as this UID. Then on user signin/authentication, I can get this UID from firebase auth and find the associated record in firestore.
Currently with the AWS stack, I created a user model in schema.graphql as:
type User #model #auth(rules: [{ allow: owner, ownerField: "id", operations: [create, update, delete]}]){
id: ID!
firstName : String
lastName : String
handle : String
email : String!
}
So that only authenticated user can create, update and delete. Next somewhere in SignUpController I create a new user:
AWSMobileClient.default().signUp( username: email
, password: password
, userAttributes: ["email": email]) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
self.showAlert(msg: "You already have an account. Please go back and press log in")
case .unconfirmed:
break
case .unknown:
self.showAlert(msg: "Network error")
}
} else if let error = error { ... }
And then confirm the user w/ code:
AWSMobileClient.default().confirmSignUp(username: email, confirmationCode: code) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
// This is where I need to create an associated user account
break
case .unconfirmed:
self.showAlert(title: "Error", msg: "User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
case .unknown:
self.showAlert(title: "Error", msg: "Network error")
}
} else { //if let error = error {
self.showAlert(title: "Error", msg: "Network error")
}
Right now my solution in case .confirmed is to sign in immediately, and then fetch the user's client token via:
class CognitoPoolProvider : AWSCognitoUserPoolsAuthProviderAsync {
/// this token may not be what you want ...
func getLatestAuthToken(_ callback: #escaping (String?, Error?) -> Void) {
AWSMobileClient.default().getTokens { (token, error) in
if let error = error {
callback(nil,error)
}
callback(token?.accessToken?.tokenString, error)
}
}
}
This turns out to be the wrong solution, since the user's client token changes all the time.
Overall, this is a standard hello-world problem, and there should be a standard out of box solution provided by AWS. I search the docs and github, but cannot find a satisfactory answer.
The right way is DON'T TRUST CLIENT for creating associate user information from Cognito, you have to do it at server side.
You should create a new Lambda Post Confirmation Trigger for Cognito and code it to create an associate account. You can use event.userName or create custom attribute uuid type likes custom:id to link your associate account.
Ref: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html

Firebase new user: Identity Toolkit API error

I'm using Firebase auth with swift 4.
I create a new user like this
Auth.auth().createUser(withEmail: emailTextField.text!, password: "abc123!") { (authResult, error) in
// [START_EXCLUDE]
guard let email = authResult?.user.email, error == nil else {
print(error!)
print("NO!!!!!!")
return
}
print("\(email) created")
// [END_EXCLUDE]
guard let user = authResult?.user else { return }
}
But I get this error:
domain = usageLimits;
extendedHelp = "https://console.developers.google.com";
message = "Identity Toolkit API has not been used in project *** before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/identitytoolkit.googleapis.com/overview?project=**** then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.";
reason = accessNotConfigured;
I have enabled identity toolkit API through this link, and it still doesn't work.
I have enabled email and password sign in providers in firebase console.
I deleted firebase project and remade it, and redownloaded the googleservice.plist.
It still doesn't work. I've been hours on it!
Thanks so much,
I have enabled identity toolkit API through this link, and it still doesn't work.
I went back to there, disabled the API, then re-enabled it, and it worked.

Making public realms on Realm Object Server in Swift

I am trying to make a public realm that all users will have read permissions to. The realm team mentioned this capability in this webinar, but I am having trouble finding any documentation on how to do it.
Here is a nice image from the webinar that illustrates the types of realms that can be made in the object server. I am having trouble finding out how to make a public realm.
Here are directions for doing what you want. Be aware that this feature has changed slightly since that webinar was presented. (We also need to improve our documentation, so thank you for asking this question!)
Create an admin user. You can do this by creating a regular user, going to the web dashboard, and editing the user so that it is an administrator. If the user is an admin, upon logging in its isAdmin property should be true.
Using your admin user, open a global Realm. A global Realm is one whose path doesn't contain ~, for example /myGlobalRealm versus /~/eachUserHasTheirOwnCopy. Note that this Realm is completely inaccessible to other users by default. If the Realm doesn't yet exist, the Realm Object Server will automatically create it. This will be your public Realm.
Create an RLMSyncPermissionValue to grant read permissions to all users. This means specifying the path to your public Realm (/myGlobalRealm), as well as a user ID of *.
Then call -[RLMSyncUser applyPermission:callback:] on your admin user with your new permission value, and ensure that the server properly set the permission.
Try opening your public Realm using a different user, and make sure it works.
I hope this helps.
Swift Solution
You can run this once in your simulator to create a global realm with default read/write permissions.
SyncUser.logIn(with: .usernamePassword(username: "ADMIN USERNAME", password: "ADMIN PASSWORD!"), server: URL(string: "Your Server URL")! { (user, error) in
guard user != nil else{
print(error)
return
}
do{
let globalRealm = try Realm(configuration: Realm.Configuration(syncConfiguration: SyncConfiguration(user: user!, realmURL: URL(string: "realm://<YOUR SERVER>:9080/GlobalRealm")!), readOnly: false))
}catch{
print(error)
}
let permission = SyncPermissionValue(realmPath: "/GlobalRealm", userID: "*", accessLevel: SyncAccessLevel.write)
user!.applyPermission(permission) { error in
if let error = error{
print(error)
}else{
user!.retrievePermissions { permissions, error in
if let error = error {
print("error getting permissions")
}else{
print("SUCCESS!")
}
}
}
}
}
Here is a server function to write a public realm:
const Realm = require('realm');
const server_url = 'realm://<Your Server URL>:9080'
const realmName = 'PublicRealm'
const REALM_ADMIN_TOKEN = "YOUR REALM ADMIN TOKEN"
const adminUser = Realm.Sync.User.adminUser(REALM_ADMIN_TOKEN);
var newRealm = new Realm({
sync: {
user: adminUser,
url: server_url + '/' + realmName
},
});
Paste the code into the function editor in the realm dashboard and run the function to create a public realm. You can modify the public realm by changing the properties in the realm constructor.

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/