I'm trying to make it so that when a user has admin he should have access to the router. But unfortunately, it does not work, I'm getting an error that isAdmin is not defined, but I have it in the database.
function isAdminLogged(req, res, next) {
if (req.User.isAdmin==="true") return next();
res.redirect('/admin');
}
//ROUTES
app.get('/admin', isAdminLogged, (req, res) => {
res.render("admin", { title: "Admin Area" });
})
Also, I would love to make it when a user is an admin he can see a div in an index.hbs
<div class="home">
<h1>Home</h1>
Logout
</div>
{{#if user.isAdmin}}
<div>
<h1>Hello Guys</h1>
</div>
{{/if}}
I'm new to express and I'm trying my best to understand, thank you in advance!
I have built authentication, and using mongodb but EJS template instead of Handlebars. I think if you go through it then that will help you a lot.
Here is the link to my repo CAMP.
The information that you have provided may be not sufficient to solve your issue from my side.
I'm developing a web app where I need to give the user a unique ID for him, I searched and used the crypto module to generate UUID and it works, here is the code for that (I'm sending it with my res.render of products).
import Product from '../models/Products';
const crypto = require('crypto');
const getProducts = async (req, res) => {
const products = await Product.find().lean();
const idValueUser = crypto.randomUUID();
console.log(idValueUser);
res.render('products/products', { layout: 'products-lyt',
products: products,
userId: idValueUser});
};
module.exports = getProducts;
I'm passing to handlebars file the userId by the render and in the javascript of the file I pass that Id to the local storage:
<div class="container-fluid products-container">
<div class="products-columns">
{{#each products}}
{{> products-partial }}
{{/each}}
</div>
<p id="awadetest">{{userId}}</p>
</div>
<script>
var testeo = document.getElementById("awadetest").innerHTML;
console.log(testeo);
localStorage.setItem("test", testeo);
</script>
The Id pass correctly to the localStorage (which I'll send to the database by a form after) but the problem is that every time the user reload (make a petition to the page of products) the id change (logically) but I need a way to make that Id persist so I can identify the user after on the Database, someone knows a way to do that? or if exist a better way to identify the user that doesn't imply log in or use the IP, btw thanks
If is helpful for someone I let the solution what works for me (easier that i thought):
Basically is to made a validation on the local storage (or cookie, if you're using it) like this:
window.onload = function () {
var testeo = document.getElementById("awadetest").innerHTML;
if (localStorage.getItem("userId") == null){
localStorage.setItem("userId", testeo);
}
else{
return;
}
}
With this easily the code can identify if the browser already have a userId (I tested on different browsers and it works, generate different codes for every browser). And if someone is on the same "problem" that me (identify users after in a shopping cart) you can add to your "form" that is sent to DB the userId taking it from the local storage, hope it helps someone
hello guys i m new to loopback so can anyone help me with this basic. i had created a some code but don't know how this flow (from route to model)
here is route.js code for login
var path = require('path');
module.exports = function(app) {
app.get('/', function(req, res) {
res.render('login');
});
app.post('/login', function(req, res) {
//from here i should go to login model
});
};
here is my login.ejs
<form action="/login" method="post">
<input type="text" name="username" placeholder="username">
<input type="password" name="password" placeholder="password">
<input type="submit" name="" value="Submit">
</form>
now my question is that how i can use login model from route.js (url is like "login") i know i can use this type as describe below in route.js but i want that first it go to router and from then i go to login model more description eg it go through "/login" route from there it go to login model where i want to add insert login after that if it response success then it go to "/home" else it go to "/"
"var User = app.models.user;"
i what something like this in user.js (model)
module.exports = function(User) {
//here i want to accept login form method and insert it into dataabase
};
or this is not possible or it is incorrect way i don't know much so please help
what is different between if i use business login in router and model i m new so please help.
First go to the loopBack documentation and read it carefully how to create models and its control flow , surely you will get clear picture
https://loopback.io/doc/en/lb2/Project-layout-reference.html
i'm trying to get forms working in my express app. i have a middleware function that passes the csrf token, req.session._csrf, to res.locals.csrf_token, so the view can use it. now i'm trying to use the local variable in my view and i'm getting a forbidden error from my session middleware.
here's my form code - i'm using handlebars as my templating engine:
<form method='post' action='/api/entries' enctype='multipart/form-data' >
<input type='hidden' name='_csrf' value={{csrf_token}} />
<input class='foo' type='text' />
<input class='bar' type='text' />
<button id='submit' type='submit'> SUBMIT
</form>
i've tried referencing the csrf_token variable with and without the double curly braces and neither works. any ideas on what i am doing wrong? the Error: Forbidden happens before my route function for POSTing to /api/entries is even called. so i'm pretty sure the problem is that i'm doing something wrong with referencing the csrf token..
*edit:*in regards to the "req.session._csrf is deprecated, use req.csrfToken() instead" getting logged to the console, i did:
grep -r '_csrf' .
in my app directory. here was the output.. it doesn't look like i'm referencing it anywhere besides the view, where my hidden CSRF field is named "_csrf"..
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: var secret = req.session._csrfSecret;
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: req.session._csrfSecret = secret;
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: Object.defineProperty(req.session, '_csrf', {
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: console.warn('req.session._csrf is deprecated, use req.csrfToken() instead');
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: return (req.body && req.body._csrf)
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: || (req.query && req.query._csrf)
./v/home.hbs: <input type='hidden' name='_csrf' value={{csrf_token}} />
./v/show.hbs: <input type='hidden' name='_csrf' value={{csrf_token}} />
here is the entire error stack i'm getting when trying to POST to the /api/entries endpoint (i stupidly neglected to mention this before, but i'm using connect-redis for session middleware):
Error: Forbidden
at Object.exports.error (appFolder/node_modules/express/node_modules/connect/lib/utils.js:63:13)
at createToken (appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:82:55)
at Object.handle (appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:48:24)
at next (appFolder/node_modules/express/node_modules/connect/lib/proto.js:193:15)
at next (appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:318:9)
at appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:342:9
at appFolder/node_modules/connect-redis/lib/connect-redis.js:101:14
at try_callback (appFolder/node_modules/redis/index.js:580:9)
at RedisClient.return_reply (appFolder/node_modules/redis/index.js:670:13)
at ReplyParser.<anonymous> (appFolder/node_modules/redis/index.js:312:14)
edit 2: the error in connect-redis.js is a function trying to get the current session by the session ID and failing. don't know why this would be happening, my connect-redis setup looks correct. this is killing me
EDIT: If you don't need file uploads, don't use the multipart/form-data enctype. Switching to the default enctype would allow express.csrf() to parse the _csrf token.
In order to parse forms with the multipart/form-data enctype, you need use a multipart parser in your app configuration, or handle file uploads yourself. It's recommended to avoid using the included express.bodyParser() and instead use something like busboy or formidable on the routes you're expecting file uploads, to prevent an exploit.
If you go this route, your _csrf field will no longer be caught by express.csrf() because the form body will not be parsed until after the request passes that middleware. Set your form action to '/api/entries?_csrf={{csrf_token}}' to get around this.
var fs = require('fs');
var async = require('async');
var express = require('express');
var formidable = require('formidable');
var app = express();
app.use(express.urlencoded())
.use(express.json())
.use(express.cookieParser())
.use(express.session())
.use(express.csrf())
app.get('/upload', function(req, res) {
// File uploads ignored.
res.render('upload', {_csrf:req.csrfToken()});
});
app.post('/upload', function(req, res) {
// Explicitly handle uploads
var form = new formidable.IncomingForm();
form.uploadDir = 'temp';
var count = 0;
var maxAllowed = 10;
form.onPart = function(part) {
if (!part.filename) return form.handlePart(part);
count++;
// Ignore any more files.
if (count > maxAllowed) return part.resume();
form.handlePart(part);
};
form.parse(req, function(err, fields, files) {
// Process the files. If you don't need them, delete them.
// Note that you should still reap your temp directory on occasion.
async.map(Object.keys(files), function(key, cb) {
fs.unlink(files[key].path, cb);
}, function(err) {
res.end();
});
});
});
CSRF syntax has changed slightly in the latest versions of Express/Connect. You now want your middleware to look like this:
.use(express.csrf())
.use(function (req, res, next) {
res.cookie('XSRF-TOKEN', req.csrfToken());
res.locals.csrftoken = req.csrfToken();
next();
})
For testing your code, note that you first need to GET the form page in order to generate the CSRF token. Only then will your POST succeed. If it fails, you need to reload the page in the browser before trying to POST again.
I too hit this problem today and it's taken several hours for me to find a solution. Hopefully this answer helps someone with my exact problem. As #amagumori, I'm using redis for session handling and express 3.4.8, connect-redis 1.4.7.
Basically I was able to determine that the order of my express configuration affects the number of times a new token was issued. It seemed like everything being served out of public was creating a new token.
Specifically in my case I had to move the calls
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
above
app.use(express.csrf());
app.use(function(req, res, next){
res.locals.token = req.csrfToken();
next();
});
and tokens are issued as expected for sessions.
I would like to keep session across all the page. For this project, I am using expressJs, nodeJS as server side. AngularJS in front end.
I am not sure, how to handle session when view changes or url changes. Because I need to take care of both expressJS router or angularJs router.
What approach should I follow?
angularJS router
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/welcome', {templateUrl: 'partials/welcome.html', controller: 'MyCtrl2'});
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'MyCtrl2'});
$routeProvider.when('/signup', {templateUrl: 'partials/signup.html', controller: 'singupController'});
$routeProvider.otherwise({redirectTo: '/'});
}]);
Signup controller
myApp.controller('singupController',function($scope,$rootScope,$http){
$scope.doSingnup = function() {
var formData = {
'username' : this.username,
'password' : this.password,
'email' : null
};
var jdata = JSON.stringify(formData);
$http({method:'POST',url:'/signup',data:jdata})
.success(function(data,status,headers,config){
console.log(data);
}).
error(function(data,status,headers,config){
console.log(data)
});
}
})
ExpressJS router
module.exports = exports = function(app, db) {
var sessionHandler = new SessionHandler(db);
var contentHandler = new ContentHandler(db);
// Middleware to see if a user is logged in
app.use(sessionHandler.isLoggedInMiddleware);
app.get('/', contentHandler.displayMainPage);
app.post('/login', sessionHandler.handleLoginRequest);
app.get('/logout', sessionHandler.displayLogoutPage);
app.get("/welcome", sessionHandler.displayWelcomePage);
app.post('/signup', sessionHandler.handleSignup);
app.get('*', contentHandler.displayMainPage);
// Error handling middleware
app.use(ErrorHandler);
}
After signup, I would like to redirect to the login page. How can I do that in the above router. which one of the following should I use to change the view of app
1) $location of angularJS
2) redirect of ExpressJS
So i had the same problem and to be fair i might have read the approach somewhere i don't remember anymore.
Problem: Angular builds single page apps. After refresh, you loose scope and with it the authenticated user.
Approach
AngularJS modules offer a startup function called run which is called always when the page is loaded. Perfect for refresh/reload.
myApp.run(function ($rootScope, $location, myFactory) {
$http.get('/confirm-login')
.success(function (user) {
if (user && user.userId) {
$rootScope.user = user;
}
});
}
express-session saves the sessions for you and authenticates you with the sessionId your browser sends. So it always knows if you are authenticated or not.
router.get('/confirm-login', function (req, res) {
res.send(req.user)
}
);
All i had to do is, after refreshing and all dependencies were loaded, ask if i am authenticated and set $rootScope.user = authenticatedUserFromExpress;
There are two different concepts here - server side session state and the user state on the client side in Angular. In express you can use the session via req.session to manage session based data.
On the angular side, there is only scope in your controllers. If you want to keep track of some data across multiple controllers, you need to create a service to store the data in and inject the service into the controllers you need.
A typical lifecycle is to first check if there is data already in the service, if so use it. If not, wait for the data to be populated (by the user or app or whatever) then detect those changes and synchronize with your service.
signup controller
function SignupCtrl($scope, $http, $location) {
$scope.form = {}; // to capture data in form
$scope.errorMessage = ''; // to display error msg if have any
$scope.submitPost = function() { // this is to submit your form can't do on
//traditional way because it against angularjs SPA
$http.post('/signup', $scope.form).
success(function(data) { // if success then redirect to "/" status code 200
$location.path('/');
}).error(function(err) { // if error display error message status code 400
// the form can't be submitted until get the status code 200
$scope.errorMessage = err;
});
};
}
sessionHandler.handleSignup
this.handleSignup = function(req, res, next) {
"use strict";
// if you have a validate function pass the data from your
// Signup controller to the function in my case is validateSignup
// req.body is what you need
validateSignup(req.body, function(error, data) {
if(error) {
res.send(400, error.message); // if error send error message to angularjs
}else {
// do something else
// rmb to res.send(200)
}
});
}
validatesignup
function validateSignup(data,callback) {
"use strict"; // the data is req.body
//so now you can access your data on your form
// e.g you have 2 fields name="password" and name="confirmPassword on your form"
var pass = data.password,
comPass = data.confirmPassword;
if(pass != comPass){
callback(new Error('Password must match'), null);
// then show the error msg on the form by using
//angular ng-if like <div ng-if="errorMessage">{{errorMessage}}</div>
}else{
callback(null, data);
}
}
hope this help
Of all the answers here, I like #alknows's approach best. However, like the other answers that suggest you send a request to the server to get the current user data, there are a couple issues I take with them:
You have to deal with race conditions as a result of your AJAX ($http) call.
You're sending an unnecessary request to the server after it already rendered your index.html
I tried #alknow's approach and it worked out for me after I was able to resolve the many race conditions that came up as a result of my angular app controllers and config needing the current user to do their job. I try my best to avoid race conditions when appropriate, so I was a bit reluctant to continue with this approach. So I thought of a better approach: send the current user data down with your index.html and store it locally.
My Approach: Embed currentUser in index.html & store locally on client
In index.html on your server, make a script tag to hold whatever data you want to pass to the client:
```
<!--YOUR OTHER index.html stuff go above here-->
<script id="server-side-rendered-client-data" type="text/javascript">
var __ssr__CData = {
currentUser: { id: '12345', username: 'coolguy', etc: 'etc.' }
}
</script>
```
Then, as #alknows suggested, in app.js or wherever you initiate your angular app, add app.run(..., () => {...}). In app.run(), you will want to grab the server side rendered client data object, which I named obscurely __ssr_CData so that I am less likely to run into name collisions across the global namespace later in my other javascript:
var myAngularApp = angular.module("mainApp", ['ngRoute']);
myAngularApp.run(function ($rootScope) {
const currentUserFromServer = __ssr__CData.currentUser
const currentUserAccessTokenFromServer = __ssr__CData.accessToken
const currentUser =
CurrentUser.set(currentUserAccessTokenFromServer, currentUserFromServer)
$rootScope.currentUser = currentUser
});
As you know app.run() will be called whenever the page does a full reload. CurrentUser is a global class for managing my angular app's current user in the single page environment. So when I call CurrentUser.set(...) it stores the current user data in a place I can retrieve later in my angular app by calling CurrentUser.get(). So in any of your angular app controller's you can now retrieve the current user the server provided by simply doing this:
myAngularApp.controller('loginController',function($scope, $rootScope, $http){
//check if the user is already logged in:
var currentUser = CurrentUser.get()
if(currentUser) {
alert("HEY! You're already logged in as " +currentUser.username)
return $window.location.href = "/";
}
//there is no current user, so let user log in
//...
}
In that example, I made use of CurrentUser.get(), which I explained above, to get the previously stored current user from the server. I could have also retrieved that current user by accessing $rootScope.currentUser because I stored it there, too. It's up to you.
myAngularApp.controller('signupController',function($scope, $rootScope, $http){
//check if the user is already logged in:
var currentUser = CurrentUser.get()
if(currentUser) {
alert("HEY! You're already logged in as " +currentUser.username)
return $window.location.href = "/";
}
//there is no current user, so let user signup
//... you run your signup code after getting form data
$http({method:'POST',url:'/signup',data:jdata})
.success(function(data,status,headers,config){
//signup succeeded!
//set the current user locally just like in app.js
CurrentUser.set(data.newUser)
//send user to profile
return $window.location.href = "/profile";
})
.error(function(data,status,headers,config){
//something went wrong
console.log(data)
});
}
Now, after a new user has signed up, your server returned the new user from the AJAX call. We set that new user as the current user by calling CurrentUser.set(...) and send the user to their profile. You can now get the current user in the profile controller the same way you did to check if the current user existed in the login and signup controllers.
I hope this helps anyone who comes across this. For your reference, I'm using the client-sessions module to handle sessions on my server.