one session per user using Nodejs-Restify-Passport - node.js

How to allow one active session per user using node-passport-restify? ie; not allowing a user to be active in multiple session using other tabs or browsers at a time.
Here is the code on which application runs.
const
restify = require('restify'),
restifyPlugins = require('restify').plugins,
passport = require('passport'),
BearerStrategy = require('passport-azure-ad').BearerStrategy,
config = require('./config'),
authenticatedUserTokens = [],
serverPort = process.env.PORT || config.serverPort;
const authenticationStrategy = new BearerStrategy(config.credentials, (token, done) => {
let currentUser = null;
let userToken = authenticatedUserTokens.find((user) => {
currentUser = user;
user.sub === token.sub;
});
if (!userToken) {
authenticatedUserTokens.push(token);
}
return done(null, currentUser, token);
});
passport.use(authenticationStrategy);
const server = restify.createServer({
name: 'My App'
});
server.use(restifyPlugins.acceptParser(server.acceptable));
server.use(restifyPlugins.queryParser());
server.use(restifyPlugins.fullResponse());
server.use(restifyPlugins.bodyParser({
maxBodySize: 0,
multiples: true
}));
server.use(restifyPlugins.authorizationParser());
server.use(passport.initialize());
server.use(passport.session());
server.get('/api/test', passport.authenticate('oauth-bearer', {
session: false
}), (req, res, next) => {
res.send({"message":"Success"});
return next();
});
server.listen(serverPort)
config.js
module.exports.serverPort = serverPort;
module.exports.credentials = {
identityMetadata: config.creds.identityMetadata,
clientID: config.creds.clientID
};
I tried with
var passportOneSessionPerUser=require('passport-one-session-per-user')
passport.use(new passportOneSessionPerUser())
but, it was not giving expected result.

Related

passport-apple Node.js login error - Failed to obtain access token

I am a junior engineer working in a start-up in Seoul, Korea.
In the current project, I am trying to use the passport module to develop apple login.
I have already finished developing google social login, but I faced some problems while trying to do the same with apple.
The problem is that I get an error that states : "Failed to obtain access token".
I got really confused that you have to use the POST method in order to get the profile info from apple.
Can someone please help me?? Thanks in advance!
IT would be wonderful if I could success. Thanks again
The main problem I am expecting is that
passport.authenticate('apple') calls the function which handles passport module for google that I have already developed.
I send the redirect url to Frontend, in order to open the browser in my application.
ROUTER.get('/apple/login', async function (req, res) {
const result = { status: 'N' };
const config = {
client_id: APPLE_AUTH.CLIENT_ID, // This is the service ID we created.
redirect_uri: APPLE_AUTH.REDIRECT_URI, // As registered along with our service ID
response_type: 'code id_token',
// state: 'origin:web', // Any string of your choice that you may use for some logic. It's optional and you may omit it.
scope: 'name email', // To tell apple we want the user name and emails fields in the response it sends us.
response_mode: 'form_post',
m: 11,
v: '1.5.4',
};
const queryString = Object.entries(config)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
const redirectUrl = `https://appleid.apple.com/auth/authorize?${queryString}`;
result['redirectUrl'] = redirectUrl;
result['status'] = 'Y';
res.json(result);
});
I get the url, and open the browser
async openAppleSignIn() {
const result = await this.authService.postAppleLogin();
// await this.authService.postAppleLogin();
if (result.data.status === 'Y') {
const { redirectUrl } = result.data;
console.log(redirectUrl);
if (this.deviceInfo.platform !== 'web') {
// this.browserService.openBrowser(redirectUrl);
window.open(redirectUrl, '_self');
console.log('enter');
} else {
const timer = setInterval(async () => {
if (this.newWindowForLogin.closed) {
clearInterval(timer);
}
}, 500);
const isChrome = this.commonService.checkBrowserIsChrome();
if (!this.newWindowForLogin || isChrome) {
// this.newWindowForLogin = window.open();
window.open(redirectUrl);
}
// this.newWindowForLogin.location.href = redirectUrl;
}
} else {
}
Apple strategy
const passport = require('passport');
// var AppleStrategy = require('passport-google-oauth20').Strategy;
var AppleStrategy = require('passport-apple').Strategy;
const jwt = require('jsonwebtoken');
// var fs = require('fs');
const APPLE_AUTH = require('../../../config/secret_key').get('APPLE_AUTH');
const UserModelService = require('../../api/user/model/user_model_service');
// const applePrivateKey = fs.readFileSync('config/AuthKey_Y8BG5JY7P3.p8');
// console.log(applePrivateKey);
module.exports = () => {
passport.use(
'apple',
new AppleStrategy(
{
clientID: APPLE_AUTH.CLIENT_ID,
teamID: APPLE_AUTH.TEAM_ID,
callbackURL: APPLE_AUTH.LOGIN_CALLBACK_URL,
keyID: APPLE_AUTH.KEY_ID,
privateKeyLocation: 'config/AuthKey_Y8BG5JY7P3.p8',
privateKeyString: APPLE_AUTH.privateKey,
passReqToCallback: true,
},
async (accessToken, refreshToken, idToken, profile, done) => {
try {
// console.log(profile._json.email);
// const socialLoginEmail = req.user.emails[0].value;
// console.log(email);
//화면에서 백으로
// console.log(profile._json.email);
// const user = await UserModelService.getUser(profile._json.email);
// console.log(user);
console.log('jwt', jwt.decode(idToken));
console.log('strategy', req);
done(null, idToken);
} catch (error) {
console.error(error);
// done(error);
}
},
),
);
};
index.js
const passport = require('passport');
const apple = require('./apple_auth');
const google = require('./google_auth');
module.exports = () => {
apple();
google();
};
5.Then, I intended to get the results in callback
ROUTER.post(
'/apple/callback',
passport.authenticate('apple', { failureRedirect: '/', session: false }),
async (req, res) => {
try {
console.log(res);
console.log(req);
} catch (err) {}
},
);
I customized the passport-apple usage into this way, not following the instructions in the passport docs, because the way they listed in the official document did not work for my code.
Thanks again, and I hope to find an answer in stack overflow!!

Cannot set headers after they are sent to the client - Node.js

Basically, I get an error saying "Cannot set headers after they are sent to the client". i tried redoing everything.
this is the form code:
const loader = document.querySelector('.loader');
// selecionar inputs
const submitBtn = document.querySelector('.submit-btn');
const name = document.querySelector('#name');
const email = document.querySelector('#email');
const password = document.querySelector('#password');
const number = document.querySelector('#number');
const tac = document.querySelector('#terms-and-cond');
const notification = document.querySelector('#notifications');
submitBtn.addEventListener('click', () => {
if(name.value.length < 3){
showAlert('name must be 3 letters long');
} else if (!email.value.length){
showAlert('enter your email');
} else if (password.value.length < 8){
showAlert('password should be 8 letters long');
} else if (!number.value.length){
showAlert('enter your phone number');
} else if (!Number(number.value) || number.value.length < 10){
showAlert('invalid number, please enter valid one');
} else if (!tac.checked){
showAlert('You must agree to our terms and conditions');
} else{
// para dar submit
loader.style.display = 'block';
sendData('signup', {
name: name.value,
email: email.value,
password: password.value,
number: number.value,
tac: tac.checked,
notification: notification.checked,
seller: false
})
}
})
// mandar a função dos dados
const sendData = (path, data) => {
fetch(path, {
method: 'post',
headers: new Headers({'Content-Type': 'application/json'}),
body: JSON.stringify(data)
}).then((res) => res.json())
.then(response => {
processData(response);
})
}
const processData = (data) => {
loader.style.display = null;
if(data.alert){
showAlert(data.alert);
}
}
const showAlert = (msg) => {
let alertBox = document.querySelector('.alert-box');
let alertMsg = document.querySelector('.alert-msg');
alertMsg.innerHTML = msg;
alertBox.classList.add('show');
setTimeout(() => {
alertBox.classList.remove('show');
}, 3000);
}
again, and I still get the same error...
and this is the server code:
// importar os packages
const express = require('express')
const admin = require('firebase-admin');
const bcrypt = require('bcrypt');
const path = require('path');
// setup da firebase - admin
let serviceAccount = require("./haza---pap-firebase-adminsdk-lrx0l-0da425a226.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
let db = admin.firestore();
// declarar um caminho estatico
let staticPath = path.join(__dirname, "public");
// inicializar o express.js
const app = express();
//middlewares
app.use(express.static(staticPath));
app.use(express.json());
// routes
// home route
app.get("/", (req, res) => {
res.sendFile(path.join(staticPath, "index.html"));
})
//route do signup
app.get('/signup', (req, res) =>{
res.sendFile(path.join(staticPath, "signup.html"));
})
app.post('/signup', (req, res) => {
let {name, email, password, number, tac, notification } = req.body;
// validações do form
if (name.length < 3){
return res.json({'alert': 'name must be 3 letters long'});
} else if (!email.length){
return res.json({'alert': 'enter your email'});
} else if (password.length < 8){
return res.json({'alert' :'password should be 8 letters long'});
} else if (!number.length){
return res.json({'alert' : 'enter your phone number'});
} else if (!Number(number) || number.length < 10){
return res.json({'alert' :'invalid number, please enter valid one'});
} else if (!tac){
return res.json({'alert' : 'You must agree to our terms and conditions'});
} else {
// guardar utilizadores na base de dados
db.collection('users').doc(email).get()
.then(user => {
if(user.exists){
return res.json({'alert': 'email already exists'});
} else{
// encriptar a password antes de guarda-la.
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, (err, hash) => {
req.body.password = hash;
db.collection('users').doc(email).set(req.body)
.then(data => {
res.json({
name: req.body.name,
email: req.body.email,
seller: req.body.seller,
})
})
})
})
}
})
}
res.json('data recieved');
})
//route do 404
app.get('/404', (req, res) => {
res.sendFile(path.join(staticPath, "404.html"));
})
app.use((req, res) => {
res.redirect('/404');
})
app.listen(3000, () => {
console.log('listening on port 3000.......');
})
You are setting some data in response obj after send is being processed by node js.
Try using: res.end() after your all res.send.. events
Ref: https://www.geeksforgeeks.org/express-js-res-end-function/#:~:text=end()%20Function,-Last%20Updated%20%3A%2005&text=The%20res.,Node%20core%2C%20specifically%20the%20response.
You are sending response twice in /signup route.
Remove res.json('data recieved'); and it should work fine
That Error happens when you res. and then res. again to the client. Responding to the client several times is erroneous in node.
Check your route req,res cycles. Remember middleware .use() execute sequentially, and routes execute usually in between so...good luck

How can I set the expired time in node oAuth2 Server

I am currently working on a little project with oAuth2 with node js.
Node js with express and node-oauth2-server as a rest full service to login etc...
Everything is working just fine, they can register, verify their email address and login (forgott password etc. is not finished yet)
But I can not set the expire value of the token.
My favourite implementation would be a login with or without permanent login (in the UI this common little switch beneath the login form).
Also I would like to store the client information with the accessToken, something like Browser, Location etc.
So that a user can request where he is currently logged in (like you can do in facebook).
Most of my oAuth2 code comes from this tutorial:
https://blog.cloudboost.io/how-to-make-an-oauth-2-server-with-node-js-a6db02dc2ce7
My main problem is, that I don't know where to handle the data. In my register (etc.) endpoints everything runs through my own middleware. But with the node-oauth2-server I have no middleware.
Thanks!
Chris
Here is my server.js:
if(process.env.NODE_ENV === undefined)
process.env.NODE_ENV = "dev"
/* REQUIRE */
const oAuth2Server = require('node-oauth2-server');
const express = require('express');
const bodyParser = require('body-parser');
const util = require('util');
const dbCon = require('./subsystem/mySql')
const oAuthModel = require('./endpoints/auth/authModel')(dbCon);
/* CONST */
let port = 3000;
if(process.env.NODE_ENV !== 'production')
port = 3000;
else
port = 80;
const debug = true;
const app = express();
/* INIT */
app.oauth = oAuth2Server({
model: oAuthModel,
grants: ['password'],
debug: debug
})
/* ROUTER */
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(app.oauth.errorHandler());
const authRoutes = require('./router/auth')(express.Router(), app, dbCon)
app.use('/auth', authRoutes);
app.all('*', (req, res) => {
res.status(404).send({message: "This service was not found"});
});
/* Start Server */
app.listen(port, () => {
console.log(`listening on port ${port} in ${process.env.NODE_ENV} mode`)
})
Here is my authModel:
let dbCon;
module.exports = injectedDbCon => {
dbCon = injectedDbCon;
return {
getClient: getClient,
saveAccessToken: saveAccessToken,
getUser: getUser,
grantTypeAllowed: grantTypeAllowed,
getAccessToken: getAccessToken
}
}
const userDB = require('../user/userDB')(dbCon);
const authDB = require('./authDB');
function getClient(clientID, clientSecret, callback){
const client = {
clientID,
clientSecret,
grants: null,
redirectUris: null
}
callback(false, client);
}
function grantTypeAllowed(clientID, grantType, callback) {
console.log('grantTypeAllowed called and clientID is: ', clientID, ' and grantType is: ', grantType);
callback(false, true);
}
function getUser(email, password, callback){
console.log('getUser() called and email is: ', email, ' and password is: ', password, ' and callback is: ', callback, ' and is userDBHelper null is: ', userDB);
//try and get the user using the user's credentials
userDB.getUserFromCrentials(email, password)
.then(data => {callback(false,data[0][0])})
.catch(error => {callback(error,null)})
}
/* saves the accessToken along with the userID retrieved the specified user */
function saveAccessToken(accessToken, clientID, expires, user, callback){
console.log('saveAccessToken() called and accessToken is: ', accessToken,
' and clientID is: ',clientID, ' and user is: ', user, ' and accessTokensDBhelper is: ', authDB)
//save the accessToken along with the user.id
authDB.saveAccessToken(accessToken, user.id)
.then(data => {callback(null)})
.catch(error => {callback(error)})
}
function getAccessToken(bearerToken, callback) {
//try and get the userID from the db using the bearerToken
authDB.getUserIDFromBearerToken(bearerToken)
.then(data => {
const accessToken = {
user: {
id: data,
},
expires: null
}
callback(true,accessToken)
})
.catch(error => {callback(false,error)})
}
Here is my authDB:
const dbCon = require('../../subsystem/mySql')
const saveAccessToken = (accessToken, userID) => {
return new Promise((resolve,reject) => {
//execute the query to get the user
dbCon.query(`INSERT INTO access_tokens (access_token, user_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE access_token = ?;`,[accessToken,userID,accessToken])
.then(data => {resolve(true)})
.catch(error => {reject(error)})
})
}
const getUserIDFromBearerToken = bearerToken => {
return new Promise((resolve,reject) => {
//create query to get the userID from the row which has the bearerToken
const getUserIDQuery = `SELECT * FROM access_tokens WHERE access_token = ?;`
//execute the query to get the userID
dbCon.query(getUserIDQuery,[bearerToken])
.then(data => {
if(data.results != null && data.results.length == 1)
resolve(data.results[0].user_id)
else
reject(false)
})
.catch(error => {reject(error)})
})
}
module.exports.saveAccessToken = saveAccessToken
module.exports.getUserIDFromBearerToken = getUserIDFromBearerToken
you can pass accessTokenLifetime (in seconds) as option to the oAuth2Server constructor.
/* INIT */
app.oauth = oAuth2Server({
model: oAuthModel,
grants: ['password'],
debug: debug,
accessTokenLifetime: 4 * 60 * 60
})
As descripted in the docs (https://oauth2-server.readthedocs.io/en/latest/api/oauth2-server.html#new-oauth2server-options) you can pass any option for the authenticate, authorize and token methods to the oAuth2Server constructors options.
The accessTokenLifetime option is an option of the token method (https://oauth2-server.readthedocs.io/en/latest/api/oauth2-server.html#token-request-response-options-callback).

App refused to connect (NodeJs & Express)

I've created an app in the Partners panel, and I followed this documentation (using Nodejs and Express).
I can get the JSON format for the products' object without any problem. However, when I add to the scopes variable "read_price_rules" I get this error message: "express-example-app refused to connect."
Is this issue caused by the app's permissions?
My app can: Read products, variants, and collections.
Here is the index.js file:
const dotenv = require('dotenv').config();
const express = require('express');
const app = express();
const crypto = require('crypto');
const cookie = require('cookie');
const nonce = require('nonce')();
const querystring = require('querystring');
const request = require('request-promise');
const apiKey = process.env.SHOPIFY_API_KEY;
const apiSecret = process.env.SHOPIFY_API_SECRET;
const scopes = 'read_products,read_price_rules';
const forwardingAddress = "https://53b16008.ngrok.io";
app.listen(3000, () => {
console.log('Example app listening on port 3000!');
});
app.get('/shopify', (req, res) => {
const shop = req.query.shop;
if (shop) {
const state = nonce();
const redirectUri = forwardingAddress + '/shopify/callback';
const installUrl = 'https://' + shop + '/admin/oauth/authorize?client_id=' + apiKey + '&scope=' + scopes + '&state=' + state + '&redirect_uri=' + redirectUri;
res.cookie('state', state);
res.redirect(installUrl);
}
else { return res.status(400).send('Missing shop parameter. Please add ?shop=your-development-shop.myshopify.com to your request'); }
});
app.get('/shopify/callback', (req, res) => {
const { shop, hmac, code, state } = req.query;
const stateCookie = cookie.parse(req.headers.cookie).state;
if (state !== stateCookie) { return res.status(403).send('Request origin cannot be verified'); }
if (shop && hmac && code) {
// DONE: Validate request is from Shopify
const map = Object.assign({}, req.query);
delete map['signature'];
delete map['hmac'];
const message = querystring.stringify(map);
const providedHmac = Buffer.from(hmac, 'utf-8');
const generatedHash = Buffer.from(crypto.createHmac('sha256', apiSecret).update(message).digest('hex'), 'utf-8');
let hashEquals = false;
try { hashEquals = crypto.timingSafeEqual(generatedHash, providedHmac) }
catch (e) { hashEquals = false; };
if (!hashEquals) { return res.status(400).send('HMAC validation failed'); }
// DONE: Exchange temporary code for a permanent access token
const accessTokenRequestUrl = 'https://' + shop + '/admin/oauth/access_token';
const accessTokenPayload = {
client_id: apiKey,
client_secret: apiSecret,
code,
};
request.post(accessTokenRequestUrl, { json: accessTokenPayload })
.then((accessTokenResponse) => {
const accessToken = accessTokenResponse.access_token;
// DONE: Use access token to make API call to 'shop' endpoint
const shopRequestUrl = 'https://' + shop + '/admin/api/2019-04/discount_codes/lookup.json?code=20OFF';
const shopRequestHeaders = { 'X-Shopify-Access-Token': accessToken, };
request.get(shopRequestUrl, { headers: shopRequestHeaders })
.then((shopResponse) => {
res.status(200).end(shopResponse);
})
.catch((error) => {
res.status(error.statusCode).send(error.error.error_description);
});
})
.catch((error) => {
res.status(error.statusCode).send(error.error.error_description);
});
} else {
res.status(400).send('Required parameters missing');
}
});
I Just had to reinstall the app after adding an extra scope in the index.js file.

Firebase function error <admin.auth is not a function at ..>

I'm fairly new to Firebase and Node.js. I have created this function in my Cloud Functions to login users with a custom token:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
var serviceAccount = require("./service-account.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: functions.config().firebase.databaseURL
});
const cors = require('cors')({origin: true});
exports.login = functions.https.onRequest((req, res) => {
cors(req, res, () => {
//doing some validation..
//get password from db and match it with input password
var userRef = admin.firestore().collection('users')
userRef.where('username', '==', username).get()
.then(snapshot => {
if(snapshot.size > 1){
res.status(200).send("Invalid account!");
return;
}
snapshot.forEach(doc => {
var userPass = doc.data().password;
//if password matches, generate token, save it in db and send it
if(userPass && password == userPass){
var uid = doc.data().uid;
var admin = Boolean(doc.data().admin);
var server = Boolean(doc.data().server);
var additionalClaims = {
admin: admin,
server: server
};
admin.auth().createCustomToken(uid, additionalClaims)
.then(function(customToken) {
res.status(200).send("token:" + customToken);
})
.catch(function(error) {
res.status(200).send("Token creation failed!");
});
//save token in db..
}else{
res.status(200).send("Invalid credentials!");
}
});
})
.catch(err => {
res.status(200).send("User authentication failed!");
});
});
});
I used the token generation method in the documentation, but whenever I try to login a user it throws the error:
TypeError: admin.auth is not a function
at snapshot.forEach.doc (/user_code/index.js:128:27)
at QuerySnapshot.forEach (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/reference.js:1012:16)
at userRef.where.get.then.snapshot (/user_code/index.js:110:13)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
What could it be that I'm doing wrong?
This declaration of admin:
var admin = Boolean(doc.data().admin);
is hiding this one:
const admin = require('firebase-admin');
Use a different name, such as:
var docAdmin = Boolean(doc.data().admin);

Resources