Got an error in Express, cant send request - mongodb

controllers/userController.js
import User from '../models/userModel.js'
import asyncHandler from 'express-async-handler'
import generateToken from '../utils/generateToken.js'
// #desc Auth user & get token
// #route POST /api/users/login
// #access Public
const authUser = asyncHandler(async(req, res) => {
const { email, password } = req.body
const user = await User.findOne({ email })
if(user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: generateToken(user._id)
})
} else {
res.status(401)
throw new Error('Invalid email or Password')
}
})
// #desc Get user Profile
// #route GET /api/users/login
// #access Private
const getUserProfile = asyncHandler(async(req, res) => {
// res.json(req.user)
const user = await User.findById(req.user._id)
console.log('user', user)
if (user) {
res.json(user)
} else {
res.status(404)
throw new Error('User not Found')
}
})
export { authUser, getUserProfile }
middleware/errorMiddleWare.js
const notFound = (req, res, next) => {
const error = new Error(`Not Found - ${req.originalUrl}`)
res.status(404)
next(error)
}
const errorHandler = (err, req, res, next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode
res.status(statusCode)
res.json({
message: err.message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack
})
}
export { notFound, errorHandler }
middleware/authMiddleware.js
import jwt from 'jsonwebtoken'
import asyncHandler from 'express-async-handler'
import User from '../models/userModel.js'
const protect = asyncHandler(async(req, res, next) => {
let token
if(req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
try {
token = req.headers.authorization.split(' ')[1]
const decoded = await jwt.verify(token, process.env.JWT_SECRET)
req.user = await User.findById(decoded.id).select('-password')
next()
} catch (error) {
res.status(401)
throw new Error('Not Authorized, token failed')
}
}
if(!token) {
res.status(401)
throw new Error('Not Authorized')
}
next()
})
export { protect }
routes/userRoutes.js
import express from 'express'
const router = express.Router()
import { authUser, getUserProfile } from '../controllers/userController.js'
import { protect } from '../middleware/authMiddleware.js'
router.post('/login', authUser)
router.route('/profile').get(protect, getUserProfile)
export default router
I got an error in userController.js, error from my errorMiddleware.
Scenario :
If I send a response from "if statement". (after User.findById)
But if I send response before "if statement", it work (is not Good). But why? and how can I solve this (to send a response after using User.findById) ?
I got an Error in server console when I used scenario 1 or 2.
version
node 14.12.0
express 4.17.1

Done, I forgot to delete next() in middleware/authMiddleware.js to protect getUserProfile.

Related

Persist session id in passport-saml login login callback

I'm using passport-saml and express-session. I login with my original session id but when the idp response reach the login callback handler, I have another sessionId. Also, since my browser has the session cookie with the original session id, it cannot use the new session id in the login callback, so I cannot authenticate.
interface SamlProvider {
name: string;
config: SamlConfig;
}
const providers: SamlProvider[] = [
{
name: process.env.SAML_ENTITY_ID_1!,
config: {
path: "/login/callback",
entryPoint: process.env.SAML_SSO_ENDPOINT_1,
issuer: process.env.SAML_ENTITY_ID_1,
cert: process.env.SAML_CERT_1!,
...(process.env.NODE_ENV === "production" && { protocol: "https" }),
disableRequestedAuthnContext: true,
},
},
{
name: process.env.SAML_ENTITY_ID_2!,
config: {
path: "/login/callback",
entryPoint: process.env.SAML_SSO_ENDPOINT_2,
issuer: process.env.SAML_ENTITY_ID_2,
cert: process.env.SAML_CERT_2!,
...(process.env.NODE_ENV === "production" && { protocol: "https" }),
disableRequestedAuthnContext: true,
},
},
];
export const samlStrategy = (sessionStore: session.Store) =>
new MultiSamlStrategy(
{
passReqToCallback: true, // makes req available in callback
getSamlOptions: function (request, done) {
// Find the provider
const relayState = request.query.RelayState || request.body.RelayState;
const provider = providers.find((p) => p.name === relayState);
if (!provider) {
return done(Error("saml identity provider not found"));
}
return done(null, provider.config);
},
},
async function (
req: Request,
profile: Profile | null | undefined,
done: VerifiedCallback
) {
if (profile && profile.nameID) {
const { nameID, nameIDFormat } = profile;
const email = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
] as string;
const firstName = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
] as string;
const lastName = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
] as string;
// Check if user is in risk database
const user = await myUserService.getByEmail(email);
if (!user) return done(new UserNotFoundError());
// If user has existing session, delete that existing session
sessionStore.all!((err: any, obj: any) => {
const sessions = obj as Array<{
sid: string;
passport?: { user?: { email?: string } };
}>;
const existingSess = sessions.find(
(sess) =>
sess.passport &&
sess.passport.user &&
sess.passport.user.email &&
sess.passport.user.email === email
);
if (existingSess && existingSess.sid) {
sessionStore.destroy(existingSess.sid, (err: any) => {
console.error(err);
return done(Error("failed to delete existing user session"));
});
}
});
return done(null, { nameID, nameIDFormat, email, firstName, lastName });
}
return done(Error("invalid saml response"));
}
);
Here's my login and login callback
app.post("/login/callback", async function (req, res, next) {
passport.authenticate("saml", (err: any, user: ISessionUser) => {
if (err) {
// TODO: Handle specific errors
logger.info({ label: "SAML Authenticate Error:", error: err });
return next(err);
} else {
req.logIn(user, (err) => {
if (err) {
logger.info({ label: "Login Error:", data: err });
return next(err);
}
res.redirect("/");
});
}
})(req, res, next);
});
app.get(
"/auth/saml/login",
passport.authenticate("saml", { failureRedirect: "/", failureFlash: true }),
function (req, res) {
res.redirect("/");
}
);
I experienced a similar issue using Microsoft 365 for authentication. The answer was to pass a randomly-generated nonce to the authentication request - this gets passed back to your app in the callback request. With SAML I think it depends on the provider whether they support such a flow, but it is good practice. You can also use a cookie to maintain state in your app, instead of, or additional to, the session id.

Axios response interceptor for refreshing token keeps firing in Vue 3

I'm trying to implement a refresh token with Vue 3 and Java for backend. It is working but interceptor keeps firing.
The logic: On every request there's a JWT Authorization header that authenticates the user. If that expires, there's a cookie endpoint in place ready to refresh the JWT.
I am using axios and interceptor response to check if the client gets a 401 to try and refresh the JWT. The cookie may be valid or not.
The problem is that the interceptor to refresh the JWT never stops firing, and I think I have something wrong with the synchronization of the requests. Below is my code:
Api.js:
import axios from "axios";
const instance = axios.create({
baseURL: "MY_URL",
});
export default instance;
token.service.js:
class TokenService {
getLocalRefreshToken() {
const user = JSON.parse(localStorage.getItem("user"));
return user?.refreshToken;
}
getLocalAccessToken() {
const user = JSON.parse(localStorage.getItem("user"));
return user?.accessToken;
}
updateLocalAccessToken(token) {
let user = JSON.parse(localStorage.getItem("user"));
user.accessToken = token;
localStorage.setItem("user", JSON.stringify(user));
}
getUser() {
return JSON.parse(localStorage.getItem("user"));
}
setUser(user) {
// eslint-disable-next-line no-console
console.log(JSON.stringify(user));
localStorage.setItem("user", JSON.stringify(user));
}
removeUser() {
localStorage.removeItem("user");
}
}
export default new TokenService();
setupInterceptors.js:
import axiosInstance from "./api";
import TokenService from "./token.service";
const setup = (store) => {
axiosInstance.interceptors.request.use(
(config) => {
const token = TokenService.getLocalAccessToken();
if (token) {
config.headers["Authorization"] = 'Bearer ' + token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.eject()
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/login" && err.response) {
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
const rs = await axiosInstance.post("/auth/refreshtoken", {
refreshToken: TokenService.getLocalRefreshToken(),
});
const { accessToken } = rs.data;
store.dispatch("auth/refreshToken", accessToken);
TokenService.updateLocalAccessToken(accessToken);
return axiosInstance(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
};
export default setup;
try this out and make sure you use another instance of Axios for the refresh token request
// to be used by the interceprot
firstAxiosInstance = axios.create({ baseURL: MY_URL });
//to be used by the refresh token API call
const secondAxiosInstance = axios.create({ baseURL: MY_URL});
firstAxiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
// this is the original request that failed
const originalConfig = err.config;
// decoding the refresh token at this point to get its expiry time
const decoded = jwt.decode(localStorage.getItem('refreshToken'));
// check if the refresh token has expired upon which logout user
if (decoded.exp < Date.now() / 1000) {
store.commit('logout');
router.push('/');
}
// get new access token and resend request if refresh token is valid
if (decoded.exp > Date.now() / 1000) {
if (err.response.status === 401) {
originalConfig._retry = true;
try {
const rs = await requestService.post('/api-v1/token/refresh/', {
refresh: localStorage.getItem('refreshToken'),
});
store.commit('update_aceess_token', rs.data);
err.config.headers.Authorization = `Bearer ${rs.data.access}`;
return new Promise((resolve, reject) => {
requestService
.request(originalConfig)
.then((response) => {
resolve(response);
})
.catch((e) => {
reject(e);
});
});
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
},
);
try clean el token authorization before send request refresh, by example
in mutations(vuex)
clearAccessToken(state) {
state.access_token = ''
TokenService.removeAccessTokenApi();
},
For me it was fixed by not using the same axios instance for the refresh token request.

Uncaught (in promise) TypeError: Cannot use 'in' operator to search for 'validateStatus' in

I am getting ** Uncaught (in promise) TypeError: Cannot use 'in' operator to search for 'validateStatus' in 5f8425a33a14f026f80133ed** where 5f8425a33a14f026f80133ed is the id passed to the axios url
I want to display the services based on the user id. My url works perfectly in postman but when i access it from the veux store it gives an error.
services.js (store)
import axios from 'axios';
const state = {
services : {},
status: '',
error: null
};
const getters = {
services : state => { return state.services }
};
const actions = {
async fetchServices({commit}, userId) {
let res = await axios.get('http://localhost:5000/api/services/displayUser' , userId)
commit('setProducts', res.data)
return res;
}
};
const mutations = {
setProducts (state, items) {
state.services= items
},
};
export default {
state,
actions,
mutations,
getters
};
This is how I am calling the action :
computed: {
...mapGetters(["services"]),
},
methods: {
...mapActions(["fetchServices"]),
getData(){
this.fetchServices(this.user._id)
},
},
async created() {
await this.getProfile();
await this.getData();
}
The axios route is defined as
router.get('/displayUser', (req,res) => {
const query = user = req.body ;
Services.find(query)
.exec((err, services) => res.json(services))
})
the error screenshot :
Error screenshot
GET request should not have a body. Either use query params, indicate an id in a path, or use POST request.
In case of query params this may look like this:
let res = await axios.get('http://localhost:5000/api/services/displayUser' , { params: { userId })
router.get('/displayUser', (req,res) => {
const query = user = req.query;
Services.find(query)
.exec((err, services) => res.json(services))
})
This worked for me too:
In front end: Vue Js
let res = axios.get("http://localhost:3000/api/v1/role/getRoleByName",
{ params: { roleName: "name of role you want to send as params" },
});
In back end: Node Js
router.get('/getRoleByName', (req,res)=>{
let roleName = req.query.roleName;
roleModule.getRoleByName(roleName).then(data =>{
response.json(res,data)
}
).catch(err=> {
response.badRequest(res, err);
})
});
it's a silly mistake axios.post req.
async addTodo({ commit }, title) {
try {
const res = await axios.post(BASE_URL, { title, complete: false });
commit("newTodo", res.data);
} catch (err) {
console.log(err.message);
}
},

How can I call req.flash() from inside the mongoose save function?

Upon finding duplicate user entries in my database, I want to flash a message to the view using req.flash(). However, the only parameters allowed in the User.save() function are errors and the results of the save. How can I get the request object in there too so that if there is
an error (i.e. duplicate entry), the user is notified?
requires
const express = require('express')
const router = express.Router()
const mongoose = require('mongoose')
const Quote = require('../models/quotes')
const User = require('../models/users')
const ObjectId = mongoose.Types.ObjectId;
const expressValidator = require('express-validator')
const flash = require('express-flash-messages')
const passport = require('passport')
const bcrypt = require('bcrypt')
const saltRounds = 10
get request:
/* route handling for SIGNUP page. */
router.get('/signup', (req, res, next) => {
const flashMessages = res.locals.getMessages()
console.log('flash:', flashMessages)
if (flashMessages.error) {
console.log('flash:', flashMessages)
res.render('signup', {
showErrors: true,
signupErrors: flashMessages.error
})
} else {
console.log('no flash errors detected.')
res.render('signup')
}
})
post request:
/* route handling for submission to SIGNUP page */
// this is all just front end validation, with no relation to the database. I think flash messages use that
router.post('/signup/users', (req, res, next) => {
req.checkBody('username', 'Username field cannot be empty.').notEmpty()
req.checkBody('username', 'Username must be between 4-30 characters long.').len(4, 30)
req.checkBody('email', 'The email you entered is invalid, please try again.').isEmail()
req.checkBody('email', 'Email address must be between 4-100 characters long, please try again.').len(4, 100)
req.checkBody('password', 'Password must be between 8-100 characters long.').len(8, 100)
// req.checkBody('password', 'Password must include one lowercase character, one uppercase character, a number, and a special character.').matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.* )(?=.*[^a-zA-Z0-9]).{8,}$/, 'i')
req.checkBody('passwordMatch', 'Password must be between 8-100 characters long.').len(8, 100)
req.checkBody('passwordMatch', 'Passwords do not match, please try again.').equals(req.body.password)
// Additional validation to ensure username is alphanumeric with underscores and dashes
req.checkBody('username', 'Username can only contain letters, numbers, or underscores.').matches(/^[A-Za-z0-9_-]+$/, 'i')
const errors = req.validationErrors()
if(errors || flashMessages.error) {
res.render('signup', {
errors: errors,
showErrors: true,
signupErrors: flashMessages.error
})
} else {
let password = req.body.password
bcrypt.hash(password, saltRounds, (err, hash) => {
user = new User()
user.username = req.body.username
user.email = req.body.email
user.password = hash
// PROBLEM STARTS HERE
user.save((err, result) => {
if(err) {
// add flash message here to let user know something was wrong!
const flashMessages = res.locals.getMessages()
console.log('flash', flashMessages)
console.log("Your error: ", err.message)
if (err.message.indexOf("duplicate key error") > -1) {
req.flash('signupErrors', "Username already in use.")
console.log(req.flash("hi"))
res.redirect('/signup')
console.log("Made it down here.")
} else {
req.flash('signupErrors', "There was a problem with your registration.")
console.log("Made it wayyyyyy down here.")
res.redirect('/signup')
}
// AND ENDS HERE
} else {
User.find({}).sort({ _id:-1 }).limit(1)
.exec((err, newuser) => {
if (err) {
console.log(err)
} else {
// logins user through passport function
req.login(newuser[0], (err) => {
if (err) {
console.log("Login error 1: " + err)
console.log("Login error 2: " + newuser[0])
console.log("Login error 3: " + newuser[0]._id)
} else {
console.log("Login sucess BULK: " + newuser[0])
console.log("Login success ._id: " + newuser[0]._id)
res.redirect('/home')
}
})
}
})
.catch(next)
}
})
})
}
})
/* route handling for submission to LOGIN page */
router.post('/login/users', passport.authenticate('local', {
successRedirect: '/profile',
failureRedirect: '/login',
failureFlash: true
}))

REST service exception handling in Angular2

First, I must mention that I'm a beginner in Angular and I'm kind of stucked with my sample code.
I created some simple login app which prompts for username and password, calls login REST service (written in Java) that returns some token at login success or throws an exception at login failure.
Here's some of my code.
Login component:
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { AuthenticationService } from '../_services/index';
#Component({
moduleId: module.id,
templateUrl: 'login.component.html'
})
export class LoginComponent implements OnInit {
model: any = {};
error = '';
constructor(
private router: Router,
private authenticationService: AuthenticationService) { }
ngOnInit() {
// reset login status
this.authenticationService.logout();
}
login() {
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(result => {
if (result === true) {
this.router.navigate(['/']);
} else {
this.error = 'Login failed!';
}
},
err => {
this.error = 'Login failed!';
});
}
}
Authentication service:
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import { Observable } from 'rxjs';
import { CookieService } from 'angular2-cookie/core';
import { CookieOptionsArgs } from 'angular2-cookie/services/cookie-options-args.model';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthenticationService {
public token: string;
constructor(private http: Http, private cookieService: CookieService) {
// set token if saved in cookie
this.token = cookieService.get('token');
}
login(username, password): Observable<boolean> {
return this.http.post('http://localhost:9081/MyApp/login?username=' + username + '&password=' + password, new RequestOptions({}))
.map((response: Response) => {
// login successful if there's a token in the response
let token = response.text();
if (token !== '-1') {
// set token
this.token = token;
// store token in cookie to keep user logged
let opts: CookieOptionsArgs = {
path: '/'
};
this.cookieService.put('token', token, opts);
// return true to indicate successful login
return true;
} else {
// return false to indicate failed login
return false;
}
});
}
logout(): void {
// clear token, remove cookie to log user out
this.token= null;
this.cookieService.remove('token');
}
}
Everything works as expected. When login is successful, token is returned and I'm redirected to a "home" page. Otherwise, a "Login falied" message appears on a login page and no redirection occurs. What bothers me is that I don't exactly know why login fails: is it because username doesn't exist or is it maybe because password is wrong. What is the proper way to handle exceptions thrown by REST service? I assume that authentication service is the right place but I don't exactly know how to do it. I tried to extract some info from request object but request mapping doesn't happen if exception is thrown.
Thanks for help!
It seems you're looking for catching the exception occuring on error login in AuthenticationService . If it's the case add .catch section after .map, like in this subject :
best practives catching error Angualr 2
.catch((error: any) => { //catch Errors here using catch block
if (error.status === 500) {
// Display your message error here
}
else if (error.status === 400) {
// Display your message error here
}
});
i have implemented my code this way :
login(email: string, password: string): Observable<boolean> {
return new Observable(observer => {
var data = { email: email, password: password };
this.http.post(this.server_url + '/auth/authenticate', data).subscribe(x => {
var result = {
email: x.json().email,
token: x.json().token,
roles: x.json().roles.map(x => x.name)
}
localStorage.setItem(this._userKey, JSON.stringify(result));
observer.next(true);
observer.complete();
}, er => {
if (er.status == 401) {
observer.next(false);
observer.complete();
} else {
console.log(er);
observer.error(er);
observer.complete();
}
});
});
}
so it handle three possibilities :
if cridential is OK it returns true
if credential is wrong return false (remember your server must
return 401 status !)
otherwise there is problem in server and throw error
and in handler i got :
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(result => {
if (result == true) {
this.router.navigate(['/home']);
} else {
this.error = 'Username or password is incorrect';
this.loading = false;
}
}, err => {
this.error = 'Unexpected error occured.. please contact the administrator..';
this.loading = false;
});
}