I have tried a couple of different things, and at this point I am stumped. I simply want to be able to access the user's email to present it in a view. However I have not been able to successfully present, much less retrieve, this information. Here are the two pieces of code I have tried with:
func getUsername() -> String? {
if(self.isAuth) {
return AWSMobileClient.default().username
} else {
return nil
}
}
and
func getUserEmail() -> String {
var returnValue = String()
AWSMobileClient.default().getUserAttributes { (attributes, error) in
if(error != nil){
print("ERROR: \(String(describing: error))")
}else{
if let attributesDict = attributes{
//print(attributesDict["email"])
self.name = attributesDict["name"]!
returnValue = attributesDict["name"]!
}
}
}
print("return value: \(returnValue)")
return returnValue
}
Does anyone know why this is not working?
After sign in try this:
AWSMobileClient.default().getTokens { (tokens, error) in
if let error = error {
print("error \(error)")
} else if let tokens = tokens {
let claims = tokens.idToken?.claims
print("claims \(claims)")
print("email? \(claims?["email"] as? String ?? "No email")")
}
}
I've tried getting the user attributes using AWSMobileClient getUserAttributes with no success. Also tried using AWSCognitoIdentityPool getDetails With no success. Might be an error from AWS Mobile Client, but we can still get attributes from the id token, as seen above.
If you are using Hosted UI, remember to give your hosted UI the correct scopes, for example:
let hostedUIOptions = HostedUIOptions(scopes: ["openid", "email", "profile"], identityProvider: "Google")
It is because it is an async function so will return but later than when the function actually ends with the value. Only way I found to do it is placing a while loop and then using an if condition.
Related
class UsersViewModel : ObservableObject {
#Published var users = [CurrentUser]()
init() {
fetchUserLists()
print(users)
}
func fetchUserLists() {
FirebaseManager.shared.firestore.collection("users")
.getDocuments { documentSnapshot, error in
if let error = error {
print("Error to get user lists")
return
}
//success
documentSnapshot?.documents.forEach({ snapshot in
let user = try? snapshot.data(as: CurrentUser.self)
if user?.uid != FirebaseManager.shared.auth.currentUser?.uid {
self.users.append(user!)
}
})
}
}
}
I'm trying to fetch all of users in my firestore database, but unfortunately my users array is empty. I don't know what my mistake is.
Please check my firestore screen shot, and give me tips!
Thank you!
You're having an issue with asynchronous code. Code is faster than the internet and you have to allow time for data to be retrieved from Firebase.
Additionally, Firebase data is only valid within the closure following the Firebase call. In this case your code is attempting to print an array before it's been filled.
Here's the issue
init() {
fetchUserLists() //<-takes time to complete
print(users) //this is called before fetchUserLists fills the array
}
here's the fetchUserLists function with where the print statement should be
func fetchUserLists() {
FirebaseManager.shared.firestore.collection("users").getDocuments { documentSnapshot, error in
if let error = error {
print("Error to get user lists")
return
}
documentSnapshot?.documents.forEach({ snapshot in
let user = try? snapshot.data(as: CurrentUser.self)
if user?.uid != FirebaseManager.shared.auth.currentUser?.uid {
self.users.append(user!)
}
})
print(self.users) //we are within the closure and the array is now populated
//this is a good spot to, for example, reload a tableview
// or update other UI elements that depend on the array data
}
}
I am currently building an app with an account system.
Firebase is very new to me, that's why I watched a lot of tutorials, and now its working fine.
I want to implement that the user can choose a unique username at the registration. My problem is, I really don't know how to check if this name is already taken.
I found some code for that, but that's not working, I will show you the code for the RegistrationService file.
I hope someone can explain to me how to implement this username verification. It should return an error if the username is already taken and do continue the registration if its a valid username.
Thank you!
import Combine
import Firebase
import FirebaseDatabase
import Foundation
enum RegistrationKeys: String {
case firstName
case lastname
case info
case username
}
protocol RegisterService {
func register(with details: RegistrationDetails) -> AnyPublisher<Void, Error>
}
final class RegisterServiceImpl: RegisterService {
func register(with details: RegistrationDetails) -> AnyPublisher<Void, Error> {
Deferred {
Future { promise in
Auth.auth()
.createUser(
withEmail: details.email,
password: details.password
) { res, error in
if let err = error {
promise(.failure(err))
} else {
// Success on User creation
if let uid = res?.user.uid {
let values =
[
RegistrationKeys.firstName.rawValue: details.firstName,
RegistrationKeys.lastname.rawValue: details.lastName,
RegistrationKeys.info.rawValue: details.info,
] as [String: Any]
let db = Database.database(url: "theurl")
Database.database(url: "the url")
.reference()
.child("usernames")
.child("\([RegistrationKeys.info.rawValue: details.username] as [String : Any])")
// here should be the check and then continue if its valid
db
.reference()
.child("users")
.child(uid)
.updateChildValues(values) { error, ref in
if let err = error {
promise(.failure(err))
} else {
promise(.success(()))
}
}
} else {
promise(.failure(NSError(domain: "Invalid user ID", code: 0, userInfo: nil)))
}
}
}
}
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
}
I can see two possibilities to solve your problem:
If the e-mail can serve as the username
Firebase authentication already sends back an error message in case the e-mail (the one used when creating the user) already exists. If the e-mail passed in the following function is not unique, an error will be thrown:
Auth.auth()
.createUser(
withEmail: details.email,
password: details.password
) { res, error in
if let err = error {
promise(.failure(err))
If an additional username besides the e-mail is required
If you need usernames in addition to the e-mails, you can store them under a node "usernames", like we see in your example. Personally, I would hash them instead of storing them plain.
The structure could simply be:
{
usernames: {
username_1: true,
username_2: true,
...
username_n: true
}
}
The example below checks to see if a new username exists and stores the result in the variable isUsernameTaken:
let db = Database.database(url: "the url").reference()
let newUsername = "seeIfItIsTaken"
db.child("usernames").child(newUsername).getData() { error, snapshot in
guard error == nil else {
print("Found error \(error)")
return
}
let isUsernameTaken = snapshot.exists()
}
Sorry, my subject isn't very specific.
I'm dealing with managing multiple users in my realm and having some problems. I have a user register with their email and name, then when realm logs in and creates a SyncUser, I create a new YpbUser object with the email, name, and SyncUser.current.identity in YpbUser.id.
When a user logs in, I want to use their email to look up whether there's an existing YpbUser for their email. I'm doing it this way so in the future if someone uses username/password and then uses Google auth (not yet implemented), those SyncUsers can get to the same YpbUser.
Below is my code for checking for an existing YpbUser, along with my realm setup code. The problem I'm having is that it usually, but not always, fails to find a YpbUser even if that user exists (I'm looking at the YpbUser list in realm studio and the user with that email is definitely there!)
Inspecting from within existingUser(for:in:), users is usually 0, but it sometimes non-zero.
I assume the issue lies somewhere in the fact that I'm pretty much just guessing on how to use SyncSubscription.observe.
Please help?
fileprivate func openRealmWithUser(user: SyncUser) {
DispatchQueue.main.async { [weak self] in
let config = user.configuration(realmURL: RealmConstants.realmURL, fullSynchronization: false, enableSSLValidation: true, urlPrefix: nil)
self?.realm = try! Realm(configuration: config)
let songSub = self?.realm?.objects(Song.self).subscribe()
let usersSub = self?.realm?.objects(YpbUser.self).subscribe()
self?.usersToken = usersSub?.observe(\.state, options: .initial) { state in
if !(self?.proposedUser.email.isEmpty)! {
self?.findYpbUser(in: (self?.realm)!)
}
}
self?.performSegue(withIdentifier: Storyboard.LoginToNewRequestSegue, sender: nil)
}
}
fileprivate func findYpbUser(in realm: Realm) {
if proposedUser != YpbUser() { // self.proposedUser.email gets set from the login method
let existingUser = YpbUser.existingUser(for: proposedUser, in: realm)
guard existingUser == nil else { // If we find the YpbUser, set as current:
try! realm.write {
pr("YpbUser found: \(existingUser!)")
YpbUser.current = existingUser }
return
}
pr("YpbUser not found.") // i.e., SyncUser set, but no YpbUser found. i.e., creating a new YpbUser
createNewYpbUser(for: proposedUser, in: realm)
}
}
extension YpbUser {
class func existingUser (for proposedUser: YpbUser, in realm: Realm) -> YpbUser? {
let users = realm.objects(YpbUser.self)
let usersWithThisEmail = users.filter("email = %#", proposedUser.email)
if let emailUser = usersWithThisEmail.first {
return emailUser
}
return nil
}
}
Q & A Style: See Answer Below
How Can I get the username from a user logged in with Cognito?
I've done this and my user is logged in, now what?
AWSAuthUIViewController.presentViewController(
with: self.navigationController!,
configuration: config, completionHandler: { (provider: AWSSignInProvider, error: Error?) in
if error == nil {
//get parameters
}
} else {
print(error as Any)
}
})
}
Prerequisites:
App registered with MobileHub
Cognito Setup in MobileHub
Mobilehub integrated with Swift Project using AWS SDK
If you're like me, you did this with little to no difficulty and now you're stuck trying to get the username and other parameters from the logged in user. There are a lot of answers, but thus far, I haven't stumbled upon one that gets you all the way there.
I was able to piece this together from various sources:
func getUsername() {
//to check if user is logged in with Cognito... not sure if this is necessary
let identityManager = AWSIdentityManager.default()
let identityProvider = identityManager.credentialsProvider.identityProvider.identityProviderName
if identityProvider == "cognito-identity.amazonaws.com" {
print("************LOGGED IN WITH COGNITO************")
let serviceConfiguration = AWSServiceConfiguration(region: .USWest2, credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: "YourClientID", clientSecret: "YourSecretKey", poolId: "YourPoolID")
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: "YourPoolName (typically formatted as YourAppName_userpoool_MOBILEHUB_12345678")
let pool = AWSCognitoIdentityUserPool(forKey: "YourPoolName")
// the following line doesn't seem to be necessary and isn't used so I've commented it out, but it is included in official documentation
// let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USWest2, identityPoolId: "YourPoolID", identityProviderManager:pool)
if let username = pool.currentUser()?.username {
print("Username Retrieved Successfully: \(username)")
} else {
print("Error getting username from current user - attempt to get user")
let user = pool.getUser()
let username = user.username
print("Username: \(username)")
}
}
}
To get your ClientID, Secret Key, and PoolID, check your awsconfiguration.json
To get your PoolName, login to MobileHub, and in your project's backend, go to User Sign in, click Email and Password, then click Edit in Cognito. The following page will have your Pool Name as "YourAppName_userpool_MOBILEHUB_12345678"
Edit: To get all of the attributes as well:
if let userFromPool = pool.currentUser() {
userFromPool.getDetails().continueOnSuccessWith(block: { (task) -> Any? in
DispatchQueue.main.async {
if let error = task.error as NSError? {
print("Error getting user attributes from Cognito: \(error)")
} else {
let response = task.result
if let userAttributes = response?.userAttributes {
print("user attributes found: \(userAttributes)")
for attribute in userAttributes {
if attribute.name == "email" {
if let email = attribute.value {
print("User Email: \(email)")
}
}
}
If you're using Cognito User Pools, you can use this:
import AWSUserPoolsSignIn
AWSCognitoUserPoolsSignInProvider.sharedInstance()
.getUserPool()
.currentUser()?
.username
I have been trying to implement Chris’ answer here: Can I make Firebase use a username login process? for the Facebook login but I can’t seem to get my head around it.
So far I’ve tried to set conditions on the textField but as Firebase observer works asynchronously, the conditions to check if the username exists in the database won’t work.
let usernameString = usernameTextField.text
let uid = FIRAuth.auth()?.currentUser?.uid
ref.runTransactionBlock({ (currentData: FIRMutableData) -> FIRTransactionResult in
if var post = currentData.value as? [String : AnyObject], let uid = FIRAuth.auth()?.currentUser?.uid {
let usernamesDictionary = post["usernames"] as! NSDictionary
for (key, _) in usernamesDictionary {
if key as? String == usernameString {
print("username not available: \(key)")
}
else if usernameString == "" {
print("Uh oh! Looks like you haven't set a username yet.")
}
else if key as? String != usernameString {
print("username available: \(key)")
print("All set to go!")
let setValue: NSDictionary = [usernameString!: uid]
post["usernames"] = setValue
currentData.value = post
}
}
return FIRTransactionResult.successWithValue(currentData)
}
return FIRTransactionResult.successWithValue(currentData)
}
Then I tried creating /usernames/ node in the database and set up rules as:
{
"rules": {
"usernames": {
".read": "auth != null",
".write": "newData.val() === auth.uid && !data.exists()"
}
}
}
Now that won’t let me set any username to the database. I get confused in creating rules but my whole point is that I need a sign up flow with the username data that’s unique for each user in the database.
While trying every answer I found in related posts, what worked for me the easy way i.e. without making Firebase rules play a part in it or creating a separate usernames node in the database was to not put an if/else condition inside the Firebase observer but instead to use the exists() method of FIRDataSnapshot.
Now here’s the trick, while I did try only the exists() method with a simple observer but that did not help me. What I did was first query usernames in order, then match the username with queryEqualToValue to filter the query:
refUsers.queryOrderedByChild("username").queryEqualToValue(usernameString).observeSingleEventOfType(.Value , withBlock: {
snapshot in
if !snapshot.exists() {
if usernameString == "" {
self.signupErrorAlert("Uh oh!", message: "Looks like you haven't set a username yet.")
}
else {
// Update database with a unique username.
}
}
else {
self.signupErrorAlert("Uh oh!", message: "\(usernameString!) is not available. Try another username.")
}
}) { error in
print(error.localizedDescription)
}
}
This is the first time out of most of the answers here that worked for me. But for now, I don’t know if this would scale. Post your experiences and best practices. They’ll be appreciated.