How to normalise user data from varying structures in Meteor? - meteor

Users in my Meteor app can create accounts 'manually' or with the accounts-facebook package.
If they create an account manually then in the database their email is stored like this:
emails: [address: 'hi#gmail.com', verified: false]
But if they use the Facebook login then its stored like this:
services: {
facebook: {
email: "james#gmail.com"
}
}
I have an user account page where I need to display the users email and allow them to change it. How can I deal with the varying structure?
I made this React component to display the users email. When I just had the default Meteor user profiles it worked, but now Ive added Facebook login it errors as props.user.emails doenst exist.
<div className="form-primary__row">
<label>Email:</label>
{props.user.emails.map((item, i) => {
return (
<input defaultValue={item.address} key={i} name="email" />
);
})}
</div>
This is my method for a user to update their email. It also worked when I just had Meteors accounts but won't work with Facebook.
Meteor.methods({
'user.updateEmail'({ email }) {
Meteor.users.update(
{ _id: Meteor.userId() },
{
$set: {
'emails.0.address': email,
'emails.0.verified': false,
},
},
);
},
});

One approach is to use Accounts.onCreated()
The function should return the user document (either the one passed in
or a newly-created object) with whatever modifications are desired.
The returned document is inserted directly into the Meteor.users
collection.
Accounts.onCreateUser(function (options, user) {
// if the account is created using the manual approach,
// simply return the user object that will be inserted into
// the Users collection.
if (!user.services.facebook) {
return user;
}
// if user is created using fb's API,
// manually set the emails array, then return the user object
// which will be inserted into the Users collection.
user.username = user.services.facebook.name;
user.emails = [{address: user.services.facebook.email}];
return user;
});
The above ensures that the emails array always contains the email, whichever the login method the user chooses to use.

Related

Server side meteor this.userprofile

I have the following code that renders the currentUsers' documents in a collection. However I want an admin belonging to the same organization to also be able to view and edit the collection. this.user.profile.organization does not work unfortunately. So, how would I allow the object to be available to admins from belonging to the same organization. EVery document that gets created gets the organization of the currentuser.
Meteor.publish('skills', function skillsPublication() {
return Skills.find({
owner: this.userId,
});
});
When you're on the server, you can always query the user document from the MongoDB database.
Meteor.publish('skills', function skillsPublication() {
const user = Meteor.users.findOne({ _id: this.userId })
// now you can do user.profile.organization
return Skills.find({
$or: [
{ owner: this.userId },
{ organization: user.profile.organization }
] // returns a document owned by the user or owned by the user's organization
})
})
Just a note, Meteor advises against using .profile field on your users collection, because that field is always published to the client. Meteor suggests that you use top-level keys on your user document instead.
For more info, read: https://guide.meteor.com/accounts.html#dont-use-profile

Retrieving data from custom useraccounts fields

I'm using the Atmosphere package meteor-useraccounts, but I cannot figure out how to retrieve data from custom made fields.
I made the field:
AccountsTemplates.addField({
_id: 'callsign',
type: 'text',
placeholder: {
signUp: "Callsign"
},
required: true,
});
and I would like to get the logged in user's callsign from the user's collection.
I tried to see if the registration pushes the data to the user's collection, but it seems not so. Is there a way to do this?
try hooking into the account creation, it should show you your values in options.profile. then you can save them to the user object or wherever you would like. put this code somewhere on the server:
Accounts.onCreateUser((options, user) => {
console.log('--------------------------------');
console.log('options:', options);
console.log('--------------------------------');
console.log('user:', user);
console.log('--------------------------------');
return user;
});
be sure to return the user object at the end.

How to fetch a list of 'FirebaseUser' programatically? [duplicate]

I'm working on a firebase+angularjs app and I'm using the simple email and password authentication and it's working properly.
I'm just wondering if I can add extra user data on the user table which is being used by firebase email+password auth, like I want to add billing info and other details concerning the user without creating extra node/table on firebase to store these extra data.
Firebase stores the email/password users in a separate location, that you don't have direct access to. You cannot expand the data in this location.
Since many application developers want to access the user data in their application code, it is a common practice to store all users under a /users node inside the application database itself. The disadvantage is that you have to do this yourself. But the positive side of this is that you can store any extra information if you want.
See the Firebase guide on storing user data for sample code. From there:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.onAuth(function(authData) {
if (authData && isNewUser) {
// save the user's profile into Firebase so we can list users,
// use them in Security and Firebase Rules, and show profiles
ref.child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData)
});
}
});
NOTE: This method only works if you are using Firebase Admin SDK and you need to have end point on your server to manage custom tokens
Firebase Admin SDK has an option to create custom tokens with additional claims object, which can contain arbitrary data. This might be useful to store some user related info, like whether the user is premium user or not.
Additional claims data is accessible using auth object.
example
var uid = "some-uid"; //this can be existing user UID
var additionalClaims = {
premiumAccount: true,
some-user-property: 'some-value'
};
admin.auth().createCustomToken(uid, additionalClaims)
.then(function(customToken) {
// Send token back to client
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
additionalClaims are also accessible in Firebase security rules.
for more info read Firebase Custom Tokens
A Firebase User has a fixed set of basic properties—a unique ID, a primary email address, a name and a photo URL—stored in the project's user database, that can be updated by the user (iOS, Android, web). You cannot add other properties to the Firebase User object directly; instead, you can store the additional properties in your Firebase Realtime Database.
Firebase has a fixed set of user properties which can be updated but not added on to.
However you can add small amounts of data with the help of serialization and deserialization using JSON.stringify() and JSON.parse()
And then use any one of the unused properties to store the string
either in DisplayName, or photoURL property.
Keep in mind the data that can be added has to be small in size and stored as a string.
And this can be only possible with using the method in the FIREBASE SDK and not the angularfire as illustrated below
var user = firebase.auth().currentUser;
user.updateProfile({
displayName: "Jane Q. User",
photoURL: "https://example.com/jane-q-user/profile.jpg"
}).then(function() {
// Update successful.
}, function(error) {
// An error happened.
});
You could store more json like data in the photoURL or displaYName variable in the form of string here.
My answer is not angular related but I searched quiet a bit to find out how to do it using Polymer and Polymerfire so I add this answer to help people get it done faster than i did.
I had to add a separate node to db as Frank van Puffelen mentioned.
Imports:
<link rel="import" href="../bower_components/polymerfire/firebase-app.html">
<link rel="import" href="../bower_components/polymerfire/firebase-auth.html">
<link rel="import" href="../bower_components/polymerfire/firebase-document.html">
Then place anywhere in your app a <firebase-app> component:
<firebase-app
name="yourAppName"
api-key= "{{yourApi}}"
auth-domain= "{{yourAuthDomain}}"
database-url= "{{yourDbUrl}}"
>
</firebase-app>
After that you will need to use <firebase-auth> and <firebase-document>:
Template :
<firebase-auth
id="auth"
app-name="yourAppName"
signed-in="{{signedIn}}"
user="{{user}}">
</firebase-auth>
<firebase-document
id="document"
app-name="yourAppName"
path="{{usersPath}}" // e.g "/users"
data="{{userDocument}}">
</firebase-document>
Script:
this._register = function(){
var formValid = this.querySelector('#register-form').validate();
var auth = this.querySelector('#auth');
if(formValid && this.passWordsIdentic){
//The actual registration
auth.createUserWithEmailAndPassword(this.email, this.password).then(function(user){
console.log('auth user registration succes');
//Example values
this.userDocument.uid = user.uid;
this.userDocument.email = user.email;
this.userDocument.firstName = this.firstName;
this.userDocument.lastName = this.lastName;
this.userDocument.userName = this.userName;
this.$.document.save(this.usersPath).then(() => {
console.log("custom user registration succes");
this.$.document.reset();
});
}.bind(this)).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
console.log('error: ', errorCode);
);
}
}
And that's it, you may want to take a look at this excellent google codelab which is a good introduction into using firebase with polymer.
Here is the code of registration where add the extra fields in the Users table
import { AngularFireAuth } from "#angular/fire/auth";
constructor(private firebaseAuth: AngularFireAuth){}
registration(data: any, password: any) {
return this.firebaseAuth.auth.createUserWithEmailAndPassword(data.Email, password)
.then(res => {
res.user.updateProfile({
displayName: `${data.DisplayName}`
})
data.UserId = res.user.uid;
data.PhoneNumbers = [{
NumberType: '',
NumberValue: ''
}];
data.PhotoUrl = '';
data.Addresses = [{
AddressLine1: '',
AddressLine2: '',
City: '',
State: '',
Country: '',
PostalCode: '',
AddressType: ''
}];
data.IsDeleted = false;
this.fireStore.doc(`users/${res.user.uid}`).set(data);
this.toastr.success('User has been register successfully!', 'Successfull!');
return true;
}).catch(err => {
switch (err.code) {
case 'auth/email-already-in-use':
this.toastr.error(`Email address ${data.Email} already in use.`, 'Error!');
break;
case 'auth/invalid-email':
this.toastr.error(`Email address ${data.Email} is invalid.`, 'Error!');
break;
case 'auth/operation-not-allowed':
this.toastr.error('Error during sign up.', 'Error!');
break;
case 'auth/weak-password':
this.toastr.error('Password is not strong enough. Add additional characters including special characters and numbers.', 'Error!');
break;
default:
this.toastr.error(err.message, 'Error!');
break;
}
});
}
Here's a swift version. Your user structure ("table") is like
--users:
-------abc,d#email,com:
---------------email:abc.d#email.com
---------------name: userName
etc.
After you pass the auth FIRAuth.auth()?.createUser you can set the users in database as below:
let ref = FIRDatabase.database().reference()
let rootChild = ref.child("users")
let changedEmailChild = u.email?.lowercased().replacingOccurrences(of: ".", with: ",", options: .literal, range: nil) // Email doesn't support "," firebase doesn't support "."
let userChild = rootChild.child(changedEmailChild!)
userChild.child("email").setValue(u.email)
userChild.child("name").setValue(signup.name)
Please note that method is changed in v4.0.0. Therefore, you need to use the below code to retrieve the user profile:
afAuth.authState.subscribe((user: firebase.User) => {
this.displayName = user.displayName;
this.email = user.email;
this.photoURL = user.photoURL;
});
The answer from Frank is good, but things are a little different in Angular6/Firebase5/Angularfire5:
Here is my click handler for signing in a user:
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then((e) => {
console.log("Log-In Success" + e.additionalUserInfo.profile.name);
if (e.additionalUserInfo.isNewUser)
this.addUserToDatabase(/*...*/);
}).catch((error) => {
console.log("Log-In Error: Google Sign-In failed");
});

Adding a field to the user document on new user creation (Meteor)

I'm trying to add a field, score, to users on creation.
I put this code in the server hook:
Accounts.onCreateUser(function(options, user) {
user.score = 1;
return user;
});
But when I try Meteor.user() in the console, I don't see the score object.
You're following the right approach for inserting the extra key, using onCreateUser is correct.
However, by default Meteor doesn't publish all the keys in the user object. On the client you will only see _id, emails, and profile. If you want to see score on the client you can either stuff it into the profile instead of at the root of the user document (easiest):
user.profile.score = 1; // in onCreateUser
or include the score key in a user publication on the server:
Meteor.publish('me',function(){
return Meteor.find({ _id: this._id },{ fields: { profile: 1, emails: 1, score: 1 }});
});
And subscribe to it on the client:
Meteor.subscribe('me');

Firebase / AngularFire create user information

I'm creating a new user with AngularFire. But when I sign the user up I also ask for first name and last name and I add that info after registration.
$firebaseSimpleLogin(fbRef).$createUser($scope.signupData.email, $scope.signupData.password).then(function (user) {
// Add additional information for current user
$firebase(fbRef.child('users').child(user.id).child("name")).$set({
first: $scope.signupData.first_name,
last: $scope.signupData.last_name
}).then(function () {
$rootScope.user = user;
});
});
The above code works, it creates node fin Firebase (users/user.id/ ...).
The problem
When I login with the new user I get the user default information: id, email, uid, etc. but no name. How can I associate that data automatically to the user?
You can't. Firebase hides the complexity of login management by storing the login details in its own datastore. This process knows nothing of your app's forge, which means it doesn't know if or where you're storing any additional user information. It returns the data that it does know about as a convenience (id, uid, email, md5_hash, provider, firebaseAuthToken).
It's up to your app to then take the [u]id and grab whatever app specific user information you need (such as first name, last name). For an Angular app, you'd want to have a UserProfile service which retrieves the data you're looking for once you get the authentication success broadcast.
Also, in your snippet, consider changing
.child(user.id)
to
.child(user.uid)
This will come in handy if you ever support Facebook/Twitter/Persona authentication later on. uid looks like "simplelogin:1" - it helps to avoid unlikely but possible id clashes across providers.
I have the same issue on this and feel like noone actually has a clear answer (2 years on). But here is the rough structure of how such a service could look like:
app.factory('Auth', function(FURL, $firebaseAuth, $firebaseObject, $rootScope, $window){
​
var ref = new Firebase(FURL);
var auth = $firebaseAuth(ref);
​
var Auth = {
user: {},
​
login: function(user){
return auth.$authWithPassword({
email: user.email,
password: user.password
});
},
​
signedIn: function(){
return !!Auth.user.provider;
},
​
logout: function(){
return auth.$unauth;
}
};
​
// When user auths, store auth data in the user object
auth.$onAuth(function(authData){
if(authData){
angular.copy(authData, Auth.user);
// Set the profile
Auth.user.profile = $firebaseObject(ref.child('profile').child(authData.uid));
Auth.user.profile.$loaded().then(function(profile){
$window.localStorage['gym-key'] = profile.gym.toString();
});
} else {
if(Auth.user && Auth.user.profile){
Auth.user.profile.$destroy();
}
​
}
});
​
return Auth;
});

Resources