Migrating user information from Meteor to non-Meteor projects - meteor

I am migrating user documents in Mongo in my Meteor project to be used in a non-Meteor project (express backend).
I would like to know how to migrate the user data so that the users can continue to login using the same password in my non-Meteor app when I switch the backend away from Meteor.
Currently, the users in Mongo have this shape:
{
"_id" : "some id",
"createdAt" : date,
"services" : {
"password" : {
"bcrypt" : "some hash"
},
"resume" : {
"loginTokens" : [
{
"when" : date,
"hashedToken" : "some hash"
}
]
}
},
"username" : "some username"
}
My plan is to use bcrypt to hash the plain password when user tries to login in a non-Meteor app, and compare it with the value for services.password.bcrypt. Would it work? What approach should I take?

Related

msal acquireTokenSilent fails with Silent authentication was denied

I'm using msal-angular and I cant use MsalInterceptor since it handles each and every request while I would like it to handle only graph requests.
Therefore I'm trying to acquire a token by myself in my application.
I'm doing this like this:
configuration of MsalModule
MsalModule.forRoot({
auth: {
clientId: 'xxxxxx',
authority: 'https://login.microsoftonline.com/common',
redirectUri: 'my_url',
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false, // set to true for IE 11
},
},
{
popUp: true,
consentScopes:[
'user.read',
"calendars.read",
"calendars.read.shared",
"calendars.readwrite",
"group.read.all",
"openid",
"profile"
],
}),
I'm doing a login like this:
this.msalService.loginPopup({scopes: [
'user.read',
"calendars.read",
"calendars.read.shared",
"calendars.readwrite",
"group.read.all",
"openid",
"profile"
]})
I'm intercepting the login succes like this
this.broadcastService.subscribe('msal:loginSuccess' ...
and I'm trying to get a token like this
const result: AuthResponse = await this.msalService.acquireTokenSilent({scopes: [
'user.read',
"calendars.read",
"calendars.read.shared",
"calendars.readwrite",
"group.read.all",
"openid",
"profile"
]}).catch((error) => {
console.log(error);
});
and I'm still getting this error: Silent authentication was denied. The user must first sign in and if needed grant the client application access to the scope 'user.read calendars.read calendars.read.shared calendars.readwrite group.read.all openid profile'.
here is my app configuration on Azure portal
I really don't understand where does the problem come from.
Thanks for your help
[EDIT]
I've been able to get an access token by calling acquireTokenPopup when acquireTokenSilent fails like this
const result: AuthResponse = await this.msalService.acquireTokenSilent({
scopes: [
'user.read',
"calendars.read",
"calendars.read.shared",
"calendars.readwrite",
"group.read.all",
"openid",
"profile"
]
}).catch((error) => {
if (error.name === "InteractionRequiredAuthError") {
return this.msalService.acquireTokenPopup({
scopes: [
'user.read',
"calendars.read",
"calendars.read.shared",
"calendars.readwrite",
"group.read.all",
"openid",
"profile"
]
})
}
});
return result.accessToken;
}
It works but keeps flasing a popup.
Could someone explain me the reason ?
Thank you
You must enforce the consent popup to show up.
Try this
this.msalService.loginPopup({ prompt: 'consent'});
I have the similar problem and just use following code to get the exception.
this.broadcastService.subscribe('msal:acquireTokenFailure', (payload) => {
console.log(payload);
});
InteractionRequiredAuthError: Silent authentication was denied. The user must first sign in and if needed grant the client application access to the scope 'api://1cfd57f1-ad48-4396-b95c-7fa782a178b6/access_as_user openid profile'.
In the document, it says:
InteractionRequiredAuthError: Error class, extends ServerError to
represent server errors, which require an interactive call. This error
is thrown by acquireTokenSilent if the user is required to interact
with the server to provide credentials or consent for
authentication/authorization. Error codes include
"interaction_required", "login_required", and "consent_required"
For error handling in authentication flows with redirect methods (loginRedirect, acquireTokenRedirect), you'll need to register the callback.
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-handling-exceptions?tabs=javascript
So I add "this.msalService.loginRedirect()" to the callback method to solve my problem. I also not quite understand, why should grant the permission again, but when man check the implementation of "MsalGuard" (https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-angular/src/msal-guard.service.ts), it executes the same code "loginRedirect", if the exception is thrown by acquiring token.
By the way, I have also added Client Application ID to my API "Expose an API" in to authorize the client without to be asked to consent. The exception is fixed in my app finally.

Accounts can't connect to the users in my mongo DB user collections

I'm building a custom account system with meteor accounts package when i register a new user accounts is loggin him directly (a behavior that i does'nt asked) and meteor toys shows one user in collection (despite the others that i have added previously) this is because i didn't publish users. When i'm creating a new user with existing username it returns an error but also redirect and the user is logged in please i need help with this
// Meteor server side method for register
Meteor.methods({
'users.register'( data ) {
try {
user = Accounts.createUser({
username: data.username,
password: data.password,
});
return {
"userId": user
};
} catch (e) {
throw e;
}
},
});
// register call on register.js (client side register page)
Meteor.call('users.register', registerable, ( error ) => {
if( error ){
if( error.error == 403 ){
usernameExistMessage.style.display = "inline";
} else {
usernameExistMessage.style.display = "none";
}
}
});
// Users in the mongo collection
{ "_id" : "7re7XPXoxrs6oYckN", "createdAt" : ISODate("2019-01-18T16:51:31.742Z"), "services" : { "password" : { "bcrypt" : "$2a$10$8NZwd1gPMpJgMs8P47DF.uHWrvBLilTBcp6D0q6877HcDJOfJKaz6" }, "resume" : { "loginTokens" : [ ] } }, "username" : "test" }
{ "_id" : "96rc3NYXr35HNB6uZ", "createdAt" : ISODate("2019-01-18T16:52:07.421Z"), "services" : { "password" : { "bcrypt" : "$2a$10$Y3Bb6B/o3MwnFevNGpqtROMcH833qtHF.OEx6Qg5xbJrwXRU2u.2q" }, "resume" : { "loginTokens" : [ ] } }, "username" : "username" }
User auto logged in after creation of a new account even if the user already exist in mongo user collection
My meteor package file
meteor-base#1.4.0 # Packages every Meteor app needs to have
mobile-experience#1.0.5 # Packages for a great mobile UX
mongo#1.6.0 # The database Meteor supports right now
blaze-html-templates#1.0.4 # Compile .html files into Meteor Blaze views
reactive-var#1.0.11 # Reactive variable for tracker
tracker#1.2.0 # Meteor's client-side reactive programming library
standard-minifier-css#1.5.2 # CSS minifier run for production mode
standard-minifier-js#2.4.0 # JS minifier run for production mode
es5-shim#4.8.0 # ECMAScript 5 compatibility for older browsers
ecmascript#0.12.4 # Enable ECMAScript2015+ syntax in app code
shell-server#0.4.0 # Server-side component of the `meteor shell` command
accounts-password
kadira:flow-router
kadira:blaze-layout
fastclick
less#2.8.0 # Leaner CSS language
aldeed:simple-schema
aldeed:collection2
alanning:roles
meteortoys:allthings
I solved my issue there was some wrong imports and a template events bad handling

How to access Meteor.users() fields from the client?

I want to access users emails in from the client,
here is what I have done, in the server side:
Meteor.publish('userList', function (){
return Meteor.users.find({}, {fields:{emails: 1, profile: 1}});
});
In the client side:
Template.usersManagement.onCreated(function () {
var self = this;
self.autorun(function() {
self.subscribe('userList');
});
});
And the template helper to retrieve the users:
Template.usersManagement.helpers({
allUsers: function() {
console.log(Meteor.users.find({}).fetch())
return Meteor.users.find({}).fetch()
}
})
In the usersManagement template:
{{#each allUsers}}
<h1>{{this.emails.address}}</h1>
<h1>{{this.profile.name}}</h1>
{{/each}}
The users names are displayed but the email isn't and no errors showing in the console.
Here is the look on how the users stored in the database:
{
"_id" : "m7admvZc32Jr3SeiE",
"createdAt" : ISODate("2017-12-27T21:24:48.927Z"),
"services" : {
"password" : {
"bcrypt" : "$2a$10$wv6KsRp6s91A.0mHH89Q0eT3jrZmJjKJhw8SIH9c8c8OpwMrXyGMC"
}
},
"emails" : [
{
"address" : "222#222.com",
"verified" : false
}
],
"profile" : {
"name" : "222",
"createdAt" : ISODate("2017-12-27T21:24:48.707Z"),
"updatedAt" : ISODate("2017-12-27T21:24:48.707Z"),
"group" : "admin"
},
"status" : {
"online" : false
}
}
My question is, how can I retrieve the emails of the users?
EDIT
Here is how the emails are retrieved (from the console):
There is another field (0) below the emails. I tried this.emails.0.address it didn't work (desperate attempt)
Your desperate attempt almost nailed it, try this:
this.emails.[0].address
An item in the Meteor.users collection store emails as an array, for you to be able to store more than one email-address per user. So since it is so, the right way would be:
<h1>{{this.emails[0].address}}</h1>

Meteor Google oAuth to refresh accessToken without re-login

This is my first post. I will try and be complete. I am creating a app in Meteor which uses google oAuth. I am using the following packages:
accounts-ui
accounts-google
service-configuration
google-config-ui
In my login.js I have:
Template.login.events({
'click #googleLoginButton': function() {
Meteor.loginWithGoogle(
{ requestPermissions: ['email', 'profile'],
requestOfflineToken: 'true'
}
);
}
});
I get the following in mongo:
db.users.find({}).pretty();
{
"_id" : "9TjGEjEj4ocFhwHtS",
"createdAt" : ISODate("2017-10-11T17:38:07.400Z"),
"services" : {
"google" : {
"accessToken" : "ya29.-REDACTED-T9z",
"idToken" : "eyJhbGcj-REDACTED-LTg",
"scope" : [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
],
"id" : "107113228066746203535",
"email" : "XXXXXXy#XXXX.com",
"verified_email" : true,
"name" : "XXX XXX",
"given_name" : "XXX",
"family_name" : "XXX",
"picture" : "https://lh4.googleusercontent.com/.../photo.jpg",
"locale" : "en",
"gender" : "male",
"refreshToken" : "1/HG-REDACTED-oLq0USutc"
},
"resume" : {
"loginTokens" : [
{
"when" : ISODate("2017-10-11T21:47:38.471Z"),
"hashedToken" : "2lmOK-REDACTED-ptAyDnWo="
}
]
}
},
"profile" : {
"name" : "XXX XXX"
}
}
In the Server/init.js I have: (I am passing the google project info via settings.json)
configureGoogle = function(config) {
ServiceConfiguration.configurations.remove({
service: "google"
});
ServiceConfiguration.configurations.insert({
service: "google",
clientId: googleConfig.clientId,
secret: googleConfig.secret
});
return;
};
if (googleConfig) {
console.log('Got settings for google', googleConfig)
configureGoogle(googleConfig);
}
So things seem to be working. I can login / out and when I login I get new tokens. I use the accessToken for api calls in other places. The problem I have is that after a hour the token expires and the API calls start to fail.
I would like to refresh the accessToken just before it expires and cant seem to figure out how. I would also like to force a logout after 24 hours so the token does not refresh forever.
Any assistance if appreciated.
Update #1
Thanks to Derek Brown below for pointing me in the right direction. That got me to looking for a meteor google api. I found this one: percolate:google-api
and according to it doc it does what I was looking for:
If the user's access token has expired, it will transparently call the exchangeRefreshToken method to get a new refresh token.
I then ran into a error where the expiresAt Didnt exist in my mongodb and things were not working. I then found this post: Google-API which suggested:
server/publish.js
Meteor.publish(null, function() {
return Meteor.users.find(this.userId, { fields: {
'services.google.accessToken': 1,
'services.google.expiresAt': 1
}});
});
I also made one change from code above:
client/login.js
Template.login.events({
'click #googleLoginButton': function() {
Meteor.loginWithGoogle(
{ requestPermissions: ['email', 'profile'],
requestOfflineToken: 'true',
forceApprovalPrompt: 'true' //<==== This is the change
}
);
}
});
This added with no additional code changed this added the expiresAt and populated it.
Im doing final testing now. I will update.

Meteor Accounts via external services dont set user.username

I'm a complete newbie, and I've been fiddling with the Meteor 1.0 sample todo list app to connect google oauth to it.
When I do so the page no longer renders properly because {{username}} is not set at all.
https://docs.meteor.com/#/full/meteor_users says "username: a unique String identifying the user." but the oauth stuff doesn't create one for you.
Connect service to existing meteor account talks about linking an already existing account to another service, but in this case I just want to use the external service.
https://stackoverflow.com/questions/25182903/meteor-facebook-registration uses onCreateUser() to manually set
user.username = user.services.facebook.name
but this isn't portable across services nor to guarantee uniqueness.
https://github.com/aldeed/meteor-collection2 defines the User schema so that username is mandatory.
When I dump the users table (some fields removed) the google account doesn't have a username, and there is no field that can really take on that value automatically as there could be a clash. Email could be used but I'd rather the username wasn't the email address. Do I just force the user to specify a desired username?
meteor:PRIMARY> db.users.find()
{
"_id" : "YNWt2cATMsKFG7od6",
"createdAt" : ISODate("2014-11-05T11:08:00.406Z"),
"services" : {
"password" : {
},
},
"username" : “a_user”
}
{
"_id" : "CyQsJqcez3kWTRHyQ",
"createdAt" : ISODate("2014-11-05T12:09:40.139Z"),
"profile" : {
"name" : “Alice User”
},
"services" : {
"google" : {
"email" : “a_user#example.com",
"family_name" : “User”,
"gender" : “female",
"given_name" : "Alice”,
"id" : "1115",
"name" : “Alice User,
}
}
}
What is the correct way of handling this?
This is how I did it myself with facebook and google
Accounts.onCreateUser(function (options, user) {
if (options && options.profile) {
user.profile = options.profile;
}
if (user.services) {
var service = _.pairs(user.services)[0];
var serviceName = service[0];
var serviceData = service[1];
console.log("serviceName", serviceName)
if (serviceName == "facebook") {
user.emails = [
{"address": serviceData.email, "verified": true}
];
user.profile = {"first_name": serviceData.first_name, "last_name": serviceData.last_name, "avatar": getFbPicture(serviceData.id)};
}
else if (serviceName == "google") {
user.emails = [
{"address": serviceData.email, "verified": true}
];
user.profile = {"first_name": serviceData.given_name, "last_name": serviceData.family_name, "avatar": getGooglePicture(serviceData.id)};
}
}
console.log("user created :", user)
return user;
});
I do not use username but I use email so that I'm sure that it will be unique. After that
I could allow the user to set his username or display name like Stackoverflow or other services do.
However you could use the email as username and again let the user change it later.
In my application, I am using this to handle the same problem.
username = user.services.facebook.name
user.username=generateUsername(username)
generateUsername = function(username) {
var count;
username = username.toLowerCase().trim().replace(" ", "");
count = Meteor.users.find({"profile.un": username}).count();
if (count === 0) {
return username;
}
else {
return username + (count + 1)
}
This is will create a unique username. After successful signup you can allow the users to change the username and check your db for its existence.
In my application I use
if(user.services.facebook)
this.user = user.services.facebook.name
if(user.services.google)
this.user = user.services.google.name

Resources