Can't recreate facebook user when deleted - passport.js

I created a user using passport-facebook
api.get('/facebook', passport.authenticate('facebook', {
scope: 'email'
}))
Deleted the user
DELETE FROM users WHERE email = ?
goto #1 for recreating the user
But, when I tried #3 error occurs
Failed to deserialize user out of session
So I tried to delete the session on db
delete from sessions where session_id = 'p8TaEeb-GH87lbi-a5hpmY6YX6me6yHf'
And I tried #3 it works.
I put some logs to check passport-facebook works.
passport.use(new FacebookStrategy({
clientID: config.FACEBOOK_CLIENT_ID,
clientSecret: config.FACEBOOK_CLIENT_SECRET,
callbackURL: '/api/auth/facebook/callback',
profileFields: ['id', 'name', 'email', 'displayName', 'photos']
}, async (accessToken, refreshToken, profile, done) => {
//log for checking passport-facebook works
console.log('Is it works??')
const type = 'facebook'
const nickname = profile.displayName
const email = profile.emails[0].value
const thumbnail = profile.photos[0].value
try {
let [existsUser] = await conn.query(`SELECT * FROM users WHERE email = ?`, [email])
existsUser = existsUser[0]
// is user already exists
if (existsUser) return done(null, existsUser)
const fields = { nickname, email, thumbnail, type }
await conn.query(`INSERT INTO users SET ?`, fields)
let [createdUser] = await conn.query(`SELECT * FROM users WHERE email = ?`, [email])
createdUser = createdUser[0]
return done(null, createdUser)
} catch (err) {
return done(err)
}
}))
})
But, log didn't appeared so seems to be FacebookStrategy not works when I do #3
Here's my passport.deserializeUser
passport.deserializeUser(async (id, done) => {
const sql = 'SELECT * FROM users WHERE id = ?'
const fields = [id]
try {
const [user] = await conn.query(sql, fields)
return done(null, user[0])
} catch (err) {
return done(err)
}
})
Here's my session option
const sessionOpt = {
name: config.SESSION_COOKIE_KEY,
secret: config.SESSION_SECRET_KEY,
resave: false,
saveUninitialized: true,
store: new MySQLStore(mysqlOpt),
cookie: {
maxAge: expiry,
httpOnly: true
},
unset: 'destroy'
}
Here's my full code
https://github.com/qkreltms/relay-novel-server/blob/develop/src/config/passport.js
Any ideas?

https://github.com/jaredhanson/passport/issues/6#issuecomment-4857287
I added if statement when user doesn't exists and return done(null, false)
passport.deserializeUser(async (id, done) => {
const sql = 'SELECT * FROM users WHERE id = ?'
const fields = [id]
try {
const [user] = await conn.query(sql, fields)
if (user.length === 0) return done(null, false)
return done(null, user[0])
} catch (err) {
return done(err)
}
})

Related

Swagger UI: Basic Auth not working with Express/Postgres app

I'm building a back end with Express and Postgres, with Passport.js to authenticate users and Swagger UI Express to generate the API documentation. However, I'm having trouble with Passport's basic strategy, which I need to implement to allow users to log in on the Swagger UI and test certain endpoints.
This is my Passport.js configuration (click here for full repo):
// ./routers/auth.js
const passport = require("passport");
const authenticate = async (email, password, done) => {
try {
const result = await pool.query("SELECT users.id AS id, users.email AS email, users.password AS password, carts.id AS cart_id FROM users JOIN carts ON carts.user_id = users.id WHERE email = $1", [email]);
if (result.rows.length === 0) return done(null, false);
const passwordMatch = await bcrypt.compare(password, result.rows[0].password);
if (!passwordMatch) return done(null, false);
return done(null, { id: result.rows[0].id, email: result.rows[0].email, cartId: result.rows[0].cart_id });
} catch(err) {
return done(err);
}
}
const LocalStrategy = require("passport-local").Strategy;
passport.use(new LocalStrategy({ usernameField: "email" }, authenticate));
const { BasicStrategy } = require("passport-http");
passport.use(new BasicStrategy({ usernameField: "email" }, authenticate));
passport.serializeUser((user, done) => {
return done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const result = await pool.query("SELECT users.id AS id, users.email AS email, carts.id AS cart_id FROM users JOIN carts ON carts.user_id = users.id WHERE users.id = $1", [id]);
if (result.rows.length === 0) return done(null, false);
return done(null, { id: result.rows[0].id, email: result.rows[0].email, cartId: result.rows[0].cart_id });
} catch(err) {
return done(err);
}
});
I've been looking around the Internet for potential solutions, including adding an authorization header in my Swagger options for Swagger UI Express, but nothing I've found has worked so far. Help with this would be greatly appreciated.

Passport facebook doesn't create new user and times out

I'm using passport with multiple strategies (JWT, Google and Facebook). All work fine except Facebook, when an existing user tries to login. In this case it just times out and I get a 504.
It tries to call https://api.example.com/users/facebook/callback/?code=... before I get the timeout error.
I tried the exact same logic from my Google strategy, where everything works fine, but it doesn't help. I tried different online tutorials but none of them worked either.
So what am I doing wrong?
Passport.js config - Facebook code block
function(passport) {
passport.serializeUser((user, cb) => {
cb(null, user);
});
passport.deserializeUser((user, cb) => {
cb(null, user);
});
passport.use(new FacebookStrategy({
proxy: true,
clientID: keys.facebook.clientID,
clientSecret: keys.facebook.clientSecret,
callbackURL: "https://api.example.com/users/facebook/callback",
profileFields: ['id', 'displayName', 'email']
},
async (accessToken, refreshToken, profile, done) => {
const { email, first_name } = profile._json;
try {
const oldUser = await User.findOne({ email: email });
if (oldUser) {
return done(null, oldUser);
}
} catch (err) {
console.log(err);
return done(null, false);
}
// register user
try {
const newUser = await new User({
facebook: true,
email: email,
name: first_name,
verified: true,
}).save();
done(null, newUser);
} catch (err) {
console.log(err);
return done(null, false);
}
}
))
}
User auth route
// FACEBOOK
router.get("/facebook", passport.authenticate("facebook"));
router.get("/facebook/callback", generalTooManyRequests, passport.authenticate("facebook"), (req, res) => {
const referer = req.cookies["Origin"]
let redirectURL
// login did NOT work!
if (!req.user) {
redirectURL = "https://app.example.com/login/fehler-facebook"
if (referer === "website") {
redirectURL = "https://example.com/login/?fehler-facebook"
}
res.redirect(redirectURL)
}
// login did work!
else {
redirectURL = "https://app.example.com/callback/token="
if (referer === "website") {
redirectURL = "https://example.com/callback/?token="
}
const tokenObject = utils.issueJWT(req.user);
res.redirect(redirectURL + tokenObject.token)
}
});

How can I add a simple middleware that will verify JWT and be ensure that is authorized?

I have the following middleware that works for authentication with JWT and passport.js. The thing is that I also need somehow verify for all controllers if the user is admin or not. I am using this passport.js middleware for authentication:
if (typeof app == "function") {
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function (user, done) {
done(null, JSON.stringify(user));
});
passport.deserializeUser(function (user, done) {
done(null, JSON.parse(user));
});
var opts = {};
opts.jwtFromRequest = passportJwtExctract.fromAuthHeaderAsBearerToken();
opts.secretOrKey = process.env.JWT_SECRET;
passport.use(
new passportJwtStrategy(opts, async (jwt_payload, done) => {
var user = await User.findByPk(jwt_payload.id);
if (user === null) {
return done(null, false, {
message: "Sorry, we couldn't find an account.",
});
}
done(null, user);
await User.update(
{ last_signin_date: "now()" },
{
where: {
id: user.id,
},
}
);
return;
})
);
passport.use(
new passportLocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
function (username, password, done) {
process.nextTick(async function () {
var valid =
validator.isEmail(username) && validator.isLength(password, 8);
if (!valid) {
return done(null, false, {
message: "Incorrect username or password",
});
}
username = username.toLowerCase();
let user = await User.findOne({ where: { email: username } });
user = user.toJSON();
if (user === undefined) {
return done(null, false, {
message: "Sorry, we couldn't find an account with that email.",
});
}
var hashed_password = await bcrypt.hash(password, user.salt);
if (hashed_password == user.password) {
delete user.password;
delete user.salt;
user.user_mode = process.env.USER_MODE;
user.token = jwtLib.sign(user, process.env.JWT_SECRET);
//l('done user', user)
done(null, user);
await User.update(
{ last_signin_date: "now()" },
{
where: {
id: user.id,
},
}
);
return;
}
return done(null, false, {
message: "Sorry, that password isn't right.",
});
});
}
)
);
}
How can I verify JWT correctly for all related requests and be sure that the user is admin? Something like the bellow option.
Common.ensureAuthenticated("Administrator"),
you can investigate about Outh2 authentication where in JWT token you can claim number of parameter according to need and at time of verification you can validate it and extract and use it everywhere you want !
For admin and different different role you can define "actor" as key and its role in respect to priority as value and create a check actor flag at run time !
I'm assuming your application starts from index.js In index.js, you can use a middleware before initiating your routes.
For example:
const express = require('express');
const app = express();
const router = require('./src/router'); // Your router file (router.js)
app.use(AuthMiddleware); // This is how you use your middleware
app.use('/', router);

Shall I use req.user or req.session.passport

I am using Passport for logging users in Nodejs. But when I tried to access the user by req.user, I get: Promise { pending }
However, I can get the users id with req.session.passport.user.id - Is it fine to do it that way?
This is the promise, how can I improve it?
const getUserBy = async (what, type) => {
const poolConnection = mysql.createPool({
host : 'localhost',
user : 'root',
password: 'password',
port: 3306,
database: 'mydb'
});
//FIND THE USER AND RETURN IT
return new Promise(function (resolve, reject){
poolConnection.getConnection((err, con) => {
if(err) resolve(false)
con.query("SELECT * FROM thetable WHERE " + type + " = ? LIMIT 1", [what], (err, row) => {
if(err) resolve(false)
//If there were no rows found
if(row.length != 1) resolve(null)
//Resolve the user found
resolve(row[0])
})
})
})
}
/* INITIALIZE PASSPORT TO RETURN */
const initialize = (passport) => {
const authenticateUser = async (email, password, done) => {
const user = await getUserBy(email, "EMAIL")
if(user == false) return done(null, false, {message: "Unable to find this user"});
if(user == null) return done(null, false, {message: "This account and password don't match"});
//Check if the password match the database
const foundPass = await bcrypt.compare(password, user.password);
if(!foundPass) return done(null, false, { message: "This account and password don't match" })
return done(null, user)
}
passport.use(new LocalStratergy({ usernameField: 'email' }, authenticateUser));
passport.serializeUser((user, done) => done(null, user.id))
passport.deserializeUser((id, done) => done(null, getUserBy(id, "ID")))
}
I am not very sure if I should get the user using a promise, but it was the only way I made it work.

Failed to serialize user into session with passportjs and cookie-session

I'm trying to get authentication with Google+ OAuth. To achieve this I'm using passportjs with Google strategy (passport-google-oauth20 module) but I'm stuck in an error while passport tries to serialize the user into a session (using cookie-session).
The error comes after login in the Google site.
The code:
passport.serializeUser((user, done) => {
console.log('serialize ' + (user.id == undefined ? null : user.id));
console.log(user);
return done(null, (user.id == undefined ? null : user.id));
});
passport.deserializeUser((id, done) => {
console.log('dserialize id ' + id);
db.connect((err, client, don) => {
if (err) throw err
client.query('SELECT * FROM "AppUsers" WHERE "googleId" = $1', [id], (err, res) => {
don();
if (err) {
console.log(err.stack);
} else {
console.log(res.rows[0]);
if (res.rows[0]) {return done(null, res.rows[0]);}
else {return done(null, null);}
}
});
});
});
Edit:
async function checkGoogle(profile) {
const client = await db.connect();
try {
const { rows } = await client.query('SELECT * FROM "AppUsers" WHERE "googleId" = $1', [profile.id]);
let currentUser = rows[0];
console.log(currentUser);
if (currentUser) {
console.log('in db ' + currentUser.id);
console.log(currentUser);
return currentUser;
} else {
const { rows } = await client.query('INSERT INTO "AppUsers" ("googleId") VALUES ($1) RETURNING *', [profile.id]);
let newUser = rows[0];
console.log('not in db ' + newUser.id);
console.log(newUser);
return newUser;
}
} catch (error) {
alert(error);
} finally {
client.release();
}
}
passport.use(
new GoogleStrategy({
// options for google strategy
clientID: keys.google.clientID,
clientSecret: keys.google.clientSecret,
callbackURL: '/auth/google/redirect'
}, (accessToken, refreshToken, profile, done) => {
// check if user already exists in our own db
return done(null, checkGoogle(profile));
})
);
Output:
The error screen
Please tell me if you need more information about.
You need to wait for the checkGoogle function to return data by using async/await.
passport.use(
new GoogleStrategy({
// options for google strategy
clientID: keys.google.clientID,
clientSecret: keys.google.clientSecret,
callbackURL: '/auth/google/redirect'
}, async (accessToken, refreshToken, profile, done) => {
const user = await checkGoogle(profile);
// check if user already exists in our own db
return done(null, user);
})
);

Resources