I want to print the currently logged in user, by getting the username from my database.
In my model, the username property holds the following schema:
{
type: String,
required: true
}
index.js:
const users = require('../model/db');
router.get('/profiles/instructor', function (req, res, next) {
users.find({}, 'username', (err, doc)=>{
if(err){
console.log('err while finding on instructor at index.js => ' + err)
}else{
console.log(doc)
res.render('./profiles/instructor', {
title: 'Courstak | Instructor Profile',
name: doc
// etc......
})
ex.hbs:
<h5>{{name}}</h5>
The issue that I am having is when I open my website, it shows all of the users within the database, not just the currently logged in user:
Screenshot of error:
You mentioned in the comments of your OP that you're using the passport-local strategy, and it would appear that it very nicely connects up with express in the way you would expect, so req.user should have the information you are looking for, which would make your code like so (assuming you're following the documentation to properly use passport-local):
router.get('/profiles/instructor', function (req, res, next) {
res.render('./profiles/instructor', {
title: 'Courstak | Instructor Profile',
name: req.user.username
});
});
What I would recommend also is using some middleware such as connect-ensure-login to ensure that the user is logged in and has a valid session. You can add the middleware above like so:
var connectEnsureLogin = require('connect-ensure-login')
router.get('/profiles/instructor',connectEnsureLogin.ensureLoggedIn(), function (req, res, next) {
// ...
}
Related
I'm using React Admin for the first time, and my users (coming from mongoDB) are displayed just fine. The problem occurs when I click the "edit" (or "delete") button on a specific user: it says "GET http://localhost:3002/api/users/2a1a3a61-f73b-4a01-b609-ae4bb815f59e 404 (Not Found)"
I use "http://localhost:3002/api/users" to make the GET req to mongoDB: "app.use('/api/users', require('./api/GetUsers'))" and "2a1a3a61-f73b-4a01-b609-ae4bb815f59e" is the id of the user I clicked.
I remember when I first started testing React Admin, that I was using jsonplaceholder.typicode to get data and the edit function was working as well, although, of course, would not persist on refresh.
What am I missing? Is the problem the fact that my api's (http://localhost:3002/api/users) purpose is only getting the data and not post/put also, maybe?
/api/GetUsers
const express = require('express');
const mongoose = require('mongoose');
const ContactUser = require('../DB/ContactUser');
const router = express.Router();
const getUsers = async (req, res) => {
ContactUser.find()
.exec((err, user) => {
if(err){
res.json(err);
} else {
res.setHeader('Access-Control-Expose-Headers', 'Content-Range');
res.setHeader('Content-Range', 'users 0-20/20');
res.json(user);
}
})
};
router.route('/').get(getUsers);
module.exports = router;
/DB/ContactUser
const mongoose = require('mongoose');
const contactUser = new mongoose.Schema({
name: String,
email: String,
message: String,
id: String
});
module.exports = mongoose.model('contactUser', contactUser);
You're missing a second route to retrieve not a list of users, but a single user. It will use the id in the URL to find one user. Something like:
router.get('/:id', function(req, res) {
ContactUser
.findOne({ _id: req.params.id })
.exec((err, user) => err ? res.json(err) : res.json(user));
});
Not sure of a clean way to go about his. Let's say I have this endpoint:
GET /api/Books/
For the user on the webservice, this will return only the user's resources. It might look a little something like this:
exports.getBooks = function(req, res) {
// find all books for user
BookModel.find({ userId: req.user._id }, function(err, books) {
if (err)
res.send(err);
res.json(books);
});
};
The web service using the api needs the user to be logged in first. I can achieve this using a basic passport strategy to ensure authentication. But let's say I have an admin account that needs to see ALL books ever recorded. What's more is that the admin account and user accounts have completely different properties so assigning a Boolean for permissions is not enough. Using the same endpoint:
GET /api/Books
I see no reason to write another endpoint to achieve this. However the difference would look like this:
exports.getBooks = function(req, res) {
// find all books in the database
BookModel.find({}, function(err, books) {
if (err)
res.send(err);
res.json(books);
});
};
However I cannot come up with a clean way to achieve this while also using the passport middlewear as it is intended like so:
router.route('/books')
.post(authController.isAuthenticated, bookController.postBooks)
.get(authController.isAuthenticated, bookController.getBooks);
The function isAuthenticated will will only verify whether or not the user requesting resources has permission and does not change the way the controller behaves. I'm open to ideas.
ANSWER
The user #ZeroCho suggested to check user properties in req.user object to determine what should be sent back. This was more simple than I expected. In my implementation for passport.BasicAuth strategy, I check which table has a matching doc. Once the user is found in the common user or Admin user table all you do is add a property in the isMatch return object.
// Basic strategy for users
passport.use('basic', new BasicStrategy(
function(email, password, done) {
verifyUserPassword(email, password,
function(err, isMatch) {
if(err) { return done(err); }
// Password did not match
if(!isMatch) { return done(null, false); }
// Success
var userInfo = {
email: email,
isAdmin: isMatch.isAdmin || false,
businessID: isMatch.businessID || false
};
return done(null, userInfo);
});
})
);
Then you can check if .isAdmin or .businessID is valid in your requests.
Just separate your controller by if statement
exports.getBooks = function(req, res) {
if (req.user.isAdmin) { // or some other code to check user is admin
// find all books in the database
BookModel.find({}, function(err, books) {
if (err)
res.send(err);
res.json(books);
});
} else {
BookModel.find({ userId: req.user._id }, function(err, books) {
if (err)
res.send(err);
res.json(books);
});
}
};
I am very new to Node.js and MongoDB and am trying to piece together my own blogging application. I have a problem trying to query through my 'Blog' model for ones with a specific username. When I try to run:
var userBlogs = function(username) {
ub = Blog.find({author: username}).toArray();
ub = ub.reverse();
};
I get an error:
TypeError: Object #<Query> has no method 'toArray'
I know globals are bad but I've just been trying to get it to work. The Mongo documentation claims that a cursor is returned which can have the toArray() method called on it. I have no idea why it won't work.
Here is my schema/model creation:
var blogSchema = mongoose.Schema({
title: {type:String, required: true},
author: String,
content: {type:String, required: true},
timestamp: String
});
var Blog = mongoose.model('Blog', blogSchema);
Here are the /login and /readblog requests
app.get('/readblog', ensureAuthenticated, function(req, res) {
res.render('readblog', {user: req.user, blogs: ub})
})
app.get('/login', function(req, res){
res.render('login', { user: req.user, message: req.session.messages });
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login'}),
function(req, res) {
userBlogs(req.user.username);
res.redirect('/');
});
});
The end result is supposed to work with this Jade:
extends layout
block content
if blogs
for blog in blogs
h2= blog[title]
h4= blog[author]
p= blog[content]
h4= blog[timestamp]
a(href="/writeblog") Write a new blog
How can I get the query to output an array, or even work as an object?
The toArray function exists on the Cursor class from the Native MongoDB NodeJS driver (reference). The find method in MongooseJS returns a Query object (reference). There are a few ways you can do searches and return results.
As there are no synchronous calls in the NodeJS driver for MongoDB, you'll need to use an asynchronous pattern in all cases. Examples for MongoDB, which are often in JavaScript using the MongoDB Console imply that the native driver also supports similar functionality, which it does not.
var userBlogs = function(username, callback) {
Blog.find().where("author", username).
exec(function(err, blogs) {
// docs contains an array of MongooseJS Documents
// so you can return that...
// reverse does an in-place modification, so there's no reason
// to assign to something else ...
blogs.reverse();
callback(err, blogs);
});
};
Then, to call it:
userBlogs(req.user.username, function(err, blogs) {
if (err) {
/* panic! there was an error fetching the list of blogs */
return;
}
// do something with the blogs here ...
res.redirect('/');
});
You could also do sorting on a field (like a blog post date for example):
Blog.find().where("author", username).
sort("-postDate").exec(/* your callback function */);
The above code would sort in descending order based on a field called postDate (alternate syntax: sort({ postDate: -1}).
Try something along the lines of:
Blog.find({}).lean().exec(function (err, blogs) {
// ... do something awesome...
}
You should utilize the callback of find:
var userBlogs = function(username, next) {
Blog.find({author: username}, function(err, blogs) {
if (err) {
...
} else {
next(blogs)
}
})
}
Now you can get your blogs calling this function:
userBlogs(username, function(blogs) {
...
})
At the moment I have user profiles that are rendered using Handlbars as such:
exports.profile = function(req, res) {
User.findOne({username: req.params.username}).exec(function(err, user){
res.render('profile/view', {
user: req.user,
name: user.name,
username: user.username
});
});
};
On the rendered template at profile/view I would like to display an edit button if the user can edit the profile being viewed.
Edit your profile here
Additional information:
Every user us currently authenticated with passport-local strategy
Currently have some basic middleware on the route
Middleware
app.get('/:username', isAuth, user.profile);
function isAuth(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
req.flash('alert', 'First you need to log in');
res.redirect('/login');
}
I've looked into middleware for express such as connect-roles and I don't think its what I need?
What I'm thinking is that I should pass a boolean within the local variables of the res.render() method which then allow me to use handlebars to display the button or not.
{{#if profileOwner }}<a href="#">...
Any ideas?
The best way I think is creating an helper that check if userCan for you. Here the helper:
Handlebars.registerHelper('ifUserCan', function(action, options) {
var actions = action.split(/\s*,\s*/);
for (var i = 0; i < actions.length; i++) {
if (this.userCan(actions[i])) {
return options.fn(this);
}
}
return options.inverse(this);
});
You can use it as follow:
{{#ifUserCan 'show all store categories'}}
<li>{{__ 'Show categories'}}</li>
{{/ifUserCan}}
One way you could do this is by hydrating your template model with a profileOwner privilege as you suggested. Suppose you wanted to only allow users to edit their own profiles. Using connect-roles you could set up a rule like this:
user.use('profile owner', function (req, action) {
return req.isAuthenticated() &&
req.user.username === req.params.username;
})
And your route logic could then be:
exports.profile = function(req, res) {
User.findOne({username: req.params.username}).exec(function(err, user){
res.render('profile/view', {
user: req.user,
name: user.name,
username: user.username,
profileOwner: req.user.is('profile owner')
});
});
};
Then your mustache "if" syntax would look like this:
{{#profileOwner}}Tada{{/profileOwner}}
The negative case can be handled like so if you wish:
{{^profileOwner}}You are not a profile owner!{{/profileOwner}}
I am very new to Node.js and MongoDB and am trying to piece together my own blogging application. I have a problem trying to query through my 'Blog' model for ones with a specific username. When I try to run:
var userBlogs = function(username) {
ub = Blog.find({author: username}).toArray();
ub = ub.reverse();
};
I get an error:
TypeError: Object #<Query> has no method 'toArray'
I know globals are bad but I've just been trying to get it to work. The Mongo documentation claims that a cursor is returned which can have the toArray() method called on it. I have no idea why it won't work.
Here is my schema/model creation:
var blogSchema = mongoose.Schema({
title: {type:String, required: true},
author: String,
content: {type:String, required: true},
timestamp: String
});
var Blog = mongoose.model('Blog', blogSchema);
Here are the /login and /readblog requests
app.get('/readblog', ensureAuthenticated, function(req, res) {
res.render('readblog', {user: req.user, blogs: ub})
})
app.get('/login', function(req, res){
res.render('login', { user: req.user, message: req.session.messages });
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login'}),
function(req, res) {
userBlogs(req.user.username);
res.redirect('/');
});
});
The end result is supposed to work with this Jade:
extends layout
block content
if blogs
for blog in blogs
h2= blog[title]
h4= blog[author]
p= blog[content]
h4= blog[timestamp]
a(href="/writeblog") Write a new blog
How can I get the query to output an array, or even work as an object?
The toArray function exists on the Cursor class from the Native MongoDB NodeJS driver (reference). The find method in MongooseJS returns a Query object (reference). There are a few ways you can do searches and return results.
As there are no synchronous calls in the NodeJS driver for MongoDB, you'll need to use an asynchronous pattern in all cases. Examples for MongoDB, which are often in JavaScript using the MongoDB Console imply that the native driver also supports similar functionality, which it does not.
var userBlogs = function(username, callback) {
Blog.find().where("author", username).
exec(function(err, blogs) {
// docs contains an array of MongooseJS Documents
// so you can return that...
// reverse does an in-place modification, so there's no reason
// to assign to something else ...
blogs.reverse();
callback(err, blogs);
});
};
Then, to call it:
userBlogs(req.user.username, function(err, blogs) {
if (err) {
/* panic! there was an error fetching the list of blogs */
return;
}
// do something with the blogs here ...
res.redirect('/');
});
You could also do sorting on a field (like a blog post date for example):
Blog.find().where("author", username).
sort("-postDate").exec(/* your callback function */);
The above code would sort in descending order based on a field called postDate (alternate syntax: sort({ postDate: -1}).
Try something along the lines of:
Blog.find({}).lean().exec(function (err, blogs) {
// ... do something awesome...
}
You should utilize the callback of find:
var userBlogs = function(username, next) {
Blog.find({author: username}, function(err, blogs) {
if (err) {
...
} else {
next(blogs)
}
})
}
Now you can get your blogs calling this function:
userBlogs(username, function(blogs) {
...
})