How to log in through Meteor DDP? - meteor

Little BackGround: I have two app (app1 & app2). From app1 one user can register themselves. After logging in to app1 user can give their data in an input field which will be saved in a notes collection.
From app2 user can see their notes which I have done it through DDP and subscribe.
Now my task is:
from app2 users(app1 users who registered already) can log in if they are logged out from app1 and can see their own data. For that, I was trying to publish Meteor.users from app1 and trying to subscribe this from app2. but unfortunately, I am not able to fetch the users data.
here is the code in the server side in app1
app1/server/main.js
Meteor.publish('usersData', function () {
if (!this.userId) { //if user is not logged in app1 then publish
return Meteor.users.find({ });
} else {
this.ready();
}
});
from app2 i was trying to fetch the data through subscribe:
app2/clint/main.js
let conn = DDP.connect('http://localhost:3000/')
Notes = new Mongo.Collection('notes', conn)
UserData = new Mongo.Collection('usersData', conn)
Template.body.onCreated(function bodyOnCreated () {
conn.subscribe('usersData');
conn.subscribe('db1') // subscribing for users Notes and successed
})
with mesvin/Mongol-meteor-dev tool i could see the users data but could see the registered data in app2.
Please let me know how can i overcome this problem ?
Later on, I was trying to log in with ddp-login and it says the password is invalid.

Related

Firebase Web Admin

First of all, I am using nodejs for the backend. I use firebase hosting and firebase functions to deploy an express() app.
What I am trying to achieve is to make an admin website, which is connected to Firebase. so I have a route /admin/ like this:
adminApp.get("/", (request, response) => {
return response.redirect("/admin/login");
});
Here I basically want to check if a current user is logged in - or not.
I know firebase supports client side authentication using:
firebase.auth().onAuthStateChanged(user => {
if (user) {
} else {
}
});
And using
function login() {
var userEmail = document.getElementById("email").value;
var userPass = document.getElementById("password").value;
firebase.auth().signInWithEmailAndPassword(userEmail, userPass).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (error) {
document.getElementById('loginError').innerHTML = `Error signing in to firebase`;
}
});
}
However image this case:
Someone (not an admin) is visiting /admin/some_secret_website/ which he obviously does not have access to.
If I rely on client side authentication, it first loads the entire website and the scripts and then notices - hey I am not authenticated, let me redirect to /login. By then however anyone knows the source code of an admin page.
I'd rather have something like:
adminApp.get("/admin/some_secret_website", (request, response) => {
if (request.user) {
// user is authenticated we can check if the user is an admin and give access to the admin page
}
});
I know that you can get the user's token and validate that token using the AdminSDK, but the token must be send by the client code, meaning the website was already loaded.
I came across Authorized HTTPS Endpoint by firebase, but it only allows a middleware when using a bearer token.
Does anybody know how I can maintain a server side user object to not even return admin html to the browser but only allow access to admins?
Like Doug indicated, the way your admin website/webapp would function with Firebase Cloud Functions (which is effectively a Nodejs server) is that you get the request, then use the headers token to authenticate them against Firebase Auth. See this answer for a code snippet on this.
In your case, I'm thinking you would create a custom claim for an "administrator" group and use that to determine whether to send a pug templated page as a response upon authentication. As far as Authorization, your db rules will determine what said user can CRUD.

Meteor.js google account : filter email and force account choser

In my Meteor.js application, I'm using the accounts-google package in order to be connected with a google account. I have two questions about it.
First, is there a simple way to filter the account used? I would like that the users can connect only with google accounts belonging to my company. Our google account mails end with #mycompany.com. So it would be a simple mail filtering.
I already done that with some post log in hooks but I was wondering if there was a simpler way for doing it.
My second question is how to force the opening of the google account choser. For now, if I try to connect with a wrong google account, and if I only added this account (like in gmail, drive, etc), the google choser doesn't pop and automatically connect with this wrong account. So, in this case, the user is totally blocked (my application disconnect him if he tries to log in with a wrong account but the google account module doesn't propose him to connect with another account).
Thank you for your help.
In order to restrict signup/login to your domain, simply do on the server:
var checkEmailAgainstAllowed = function(email) {
var allowedDomains = ['mycompanydomain.com'];
var allowedEmails = ['otheruser#fromotherdomain.com','anotheruser#fromanotherdomain.com'];
var domain = email.replace(/.*#/,'').toLowerCase();
email = email.toLowerCase();
return _.contains(allowedEmails, email) || _.contains(allowedDomains, domain);
};
Accounts.config({
restrictCreationByEmailDomain: function(email) {
if (!email) {
throw new Meteor.Error(403,'This email address is not allowed');
}
if (!checkEmailAgainstAllowed(email)) {
throw new Meteor.Error(403,'This email domain is not allowed');
}
return true;
}
});
And to login, you'll need on the client:
Meteor.loginWithGoogle({
forceApprovalPrompt: true, //this is what you want, to rerequest approval each time that prompts the google login prompt
loginStyle : "redirect", //or not, depending on your need
requestPermissions : ['profile', 'email'],
requestOfflineToken: true
}, function (err) {
if (err)
// set a session variable to display later if there is a login error
Session.set('loginError', 'reason: ' + err.reason + ' message: ' + err.message || 'Unknown error');
});
Side note:
Alternatively, you can set up your routes so that every time a new route is called, you login, and every time a route is destroyed or on windows's unload, you call logout. This causes login/logout roundtrip everytime the route changes, but you'll make sure that the new user always has a fresh session
Edit:
When you log out of your meteor app, you don't log out of google. That's how oauth works. So, basically, if you want a meteor log out to also log the user out of their google account, so that the next time they come back, they need to provide credentials again, you should do:
Meteor.logout(function(e) {
if (e) {
console.log("Could not log the user out")
} else {
window.location.replace('https://accounts.google.com/Logout');
}
});
This uses the callback of Meteor.logout() so that when the logout is successfull, the user is redirected to google's central account logout url where the user is also logged out of all google services.

DDP.connect and Meteor.users

I am in the process to try to separate out the mobile from the desktop part of my application and thought I try DDP.connect as a means for the mobile application to share data with the desktop application.
My first hurdle is concerning Meteor internal collections and publications.
How am I supposed to authenticate users? I know I can call the login method to authenticate a user, but that still doesn't give me all the other nice reactive features I am used to with Meteor.users
Is this supposed to work, and if so what is the pattern.
Thanks
This is what integrated completely with a remote server (except code refresh, which forgets user session)
if (Meteor.isClient) {
Meteor.connection = DDP.connect('http://remote.site.com');
Accounts.connection = Meteor.connection;
Meteor.users = new Meteor.Collection('users');
SomeCollection = new Meteor.Collection('remote_collection');
Meteor.connection.subscribe('users');
Meteor.connection.subscribe('remote_collection');
// rest if the code just as always
}
This way you can use login directly (via accounts-base, accounts-passed, etc) and don't need to call a login method. Just add accounts-ui and include {{>loginButtons}} and it works
I had a similar problem. I wanted to have two different front-ends (although both are for desktop) to the same back-end, so they could use same database, publications, and methods. After looking through Meteor's source code (version 1.1.0.3) I've managed to do this as follows.
1) Start back-end server project.
$ meteor --port 3100
2) In front-end project(s), put following in server/server.config.js.
var backendUrl = process.env.BACKEND_URL;
if (backendUrl) {
__meteor_runtime_config__.BACKEND_URL = backendUrl;
__meteor_runtime_config__.ACCOUNTS_CONNECTION_URL = backendUrl;
console.log('config', __meteor_runtime_config__);
}
3) In front-end project(s), put following in client/lib/client.connection.js. APS is just a namespace for my application. Be sure to have this loaded before you use subscriptions or methods (that's why it's in lib folder).
if (typeof APS == 'undefined') APS = {};
var backendUrl = __meteor_runtime_config__.BACKEND_URL;
if (backendUrl) {
APS.backendConnection = DDP.connect(backendUrl);
Meteor.connection = APS.backendConnection;
_.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', 'disconnect'], function(name) {
Meteor[name] = _.bind(Meteor.connection[name], Meteor.connection);
});
console.log('connected to backend', APS.backendConnection);
}
4) Start front-end server with BACKEND_URL environment variable pointing to your back-end server.
$ BACKEND_URL=http://192.168.33.10:3100 meteor
That's all. Refresh on client works OK. And we don't have to fiddle with Accounts.*.
UPDATE: Just found a problem with my solution. When calling server methods, this.userId is always null. This is because Meteor.connection and Accounts.connection were two separate connections, despite to the same BACKEND_URL. Upon authentication, user ID gets associated only with the latter. Fixed client.connection.js is as follows.
if (typeof APS == 'undefined') APS = {};
var backendUrl = __meteor_runtime_config__.BACKEND_URL;
if (backendUrl) {
APS.originalConnection = Meteor.connection;
// Accounts is already connected to our BACKEND_URL
APS.backendConnection = Accounts.connection;
// Reusing same (authenticated) connection for method calls and subscriptions
Meteor.connection = APS.backendConnection;
_.each(['subscribe', 'methods', 'call', 'apply', 'status', 'reconnect', 'disconnect'], function(name) {
Meteor[name] = _.bind(Meteor.connection[name], Meteor.connection);
});
console.log('Connected to backend', APS.backendConnection);
}
You can authenticate using code like this:
var connection = DDP.connect("<url>")
To authenticate
connection.call("login", {"password":"qwerty","user":{"username":"user_1"}});
to get the user, add this on the other server)
Meteor.methods({
whoami: function() { return Meteor.user() }
});
Then you can run further commands as if you were authenticated, like this to get who's logged in
console.log(connection.call("whoami");
User account creation/Authentication:
In client.js, create a DDP connection and set it to Accounts.connection
Accounts.connection = Meteor.remoteConnection;
Create an Accounts.users collection in the client and subscribe its contents from the external server as below.
Accounts.users = new Meteor.Collection('users', {connection: Meteor.remoteConnection});
Meteor.remoteConnection.subscribe('users');
Now call the login method required as below and set the token returned in the localStorage. This works for all the internal clicks and routing.
Meteor.loginWithPassword(login_email, login_password, function(err) {
submit_button.button("reset");
if (err)
{
console.log(err);
pageSession.set("errorMessage", err.message);
return false;
}else{
console.log("logged in as "+Meteor.userId());
var token = Accounts._storedLoginToken();
localStorage.setItem('_storedLoginToken', token);
}
});
The problem with the above code is that, the token is reset after every manual client refresh. The result object contains the below signed in information. We have to take the token and login with token for every external client refresh.
id:"5RigABaSzbARHv9ZD"
token:"MItg8P59gsl_T5OXtaWRSjUnETqzns0hGEV26xWYxj7"
tokenExpires:Thu Jul 20 2017 12:46:31 GMT+0530 (India Standard Time)
In client.js, start-up call the loginwithtoken function with the returned token as below, whenever there is no user available.
var user = Meteor.user();
var token = localStorage.getItem('_storedLoginToken');
if(user==null){
console.log("Token"+token +user);
if(token)
Meteor.loginWithToken(token, function(err){
// this is going to throw error if we logged out
if(!err) {
console.log('logged in !!!! ',token);
}
});
}
Meteor throws an error while logging in with the token,
Error logging in with token: Error: You've been logged out by the server. Please log in again. [403]
To overcome this issue, we have to write a tracker function to track the logged in session and login again if required. This is basically a hack suggested in meteor forums.
Tracker.autorun(function () { var user = Meteor.user(); var token
= localStorage.getItem('_storedLoginToken'); if(user==null){ console.log("Token"+token +user); if(token)
Meteor.loginWithToken(token, function(err){
// this is going to throw error if we logged out
if(!err) {
console.log('logged in !!!! ',token); }
}); } });
Reset the localStorage if user navigates to the login path. In Layout.js,
if(path=='/login')
localStorage.setItem('_storedLoginToken',null);

Linking Robomongo to an automatic email sending service?

I have an application running on meteor.js and mongo.db. I am using robomongo as a tool for mongo.db. Now I'd like to do the following:
1. Somebody registers with my service (adding email to db)
2. I want to send an automatic welcome email to that person.
Is there any possibility how to do it?
You need an email server (SMTP), and then use the meteor email library. If you don't have an email server and don't want to create one, use a commercial solution. (Example)
Full working example you can find here: http://meteorpad.com/pad/iNMBHtNsv7XKHeq44
Notice it creates new users from within Meteor app, but the same effect will be when you use Robomongo or any other way of updating MongoDB.
First install package Email to be able to use Email.send.
In below example I assume that adding new user to collection Meteor.users should fire sending "invitation" email.
In very similar way you can detect if email was added to user object
(user.emails.length was changed) and then send email.
Then take a look at code:
// SERVER SIDE CODE:
Meteor.startup(function () {
// clean users on app resetart
// Meteor.users.remove({});
if(Meteor.users.find().count() === 0){
console.log("Create users");
Accounts.createUser({
username:"userA",
email:"userA#example.com",
profile:{
invitationEmailSend:false
}
}) ;
Accounts.createUser({
username:"userB",
email:"userB#example.com",
profile:{
invitationEmailSend:false
}
})
}
Meteor.users.find().observe({
added:function(user){
console.log(user.username, user.profile.invitationEmailSend)
if(!user.profile.invitationEmailSend){
Email.send({
from: "from#mailinator.com",
to: user.emails[0].address,
subject: "Welcome",
text: "Welcome !"
});
// set flag 'invitationEmailSend' to true, so email won't be send twice in the future ( ex. during restart of app)
Meteor.users.update({_id:user._id},{$set:{"profile.invitationEmailSend":true}});
}
}
})
});
Above code will send email to users who don't have flag equal to true in profile.invitationEmailSend. After e-mail is sent server updates user document in db and set user.profile.invitationEmailSend to true.
Whenever you add users to mongoDB (using Robomongo or any other way), then added function is executed and e-mail is send only to new users.

Publishing Meteor Users to Admin

I'm trying to publish all emails and "roles" to "admin" users (using the meteor-roles meteorite package), and the server knows what it's trying to publish, but for some reason the client is not picking up the data. Here's the piece of code involved:
Server Code:
Meteor.publish("directory", function() {
if(Roles.userIsInRole(this.userId, 'admin')) {
//next line shows that it outputs that there are 2 documents in this
console.log(Meteor.users.find({}, {fields:{emails:1, roles:1}}).count());
return Meteor.users.find({}, {fields:{emails:1, roles:1}});
} else {
return {};
}
}
Client Code:
Meteor.subscribe("directory");
Directory = new Meteor.Collection("directory");
//and then to simulate my use for this collection
console.log(Directory.find().count());
//which outputs 0
Why isn't the client getting the documents? (and I am definitely logging in with an account with role "admin")
Thanks!
EDIT and SOLUTION!
Okay, so I figured out what I was doing wrong. I just need to publish "directory" on the server, and subscribe on the client. Then, all user data goes into the Meteor.users collection, and I shouldn't define my own "Directory=new Meteor.Collection('directory')". I can then access the data via Meteor.users. Cheers!
Server: use same code as above
Client:
Meteor.subscribe("directory");
console.log(Meteor.users.find().count()); //outputs 2, yay!
Your collection should probably be defined on the beginning so that client knows where to write!
Also, your Meteor.subscribe("directory"); runs once when the app is loaded. You should make it reactive.
Directory = new Meteor.Collection("directory");
Deps.autorun(function(){
Meteor.subscribe("directory");
});

Resources