I've got some issues with authentication (using cookies and session) and my electron-app
The use case:
User logs in
Session created and cookie is stored. (by app-bl module)
I read about electron-session and electron-cookies (https://electronjs.org/docs/all?query=coo#class-cookies) but nothing works.
Application structure:
electron-app
---express-app
------app-bl
------react-client
Electron version: 3.0.13
I used this to use express within electron:
https://github.com/frankhale/electron-with-express
It seems like electrons main process doesn't know about cookies created by the rendered process.
electron/main.js:
const electron = require('electron');
const { app, BrowserWindow, session } = electron
let mainWindow;
function createWindow() {
const screenElectron = electron.screen;
mainWindow = new BrowserWindow({
show: false,
autoHideMenuBar: true,
icon: `${__dirname}/assets/icon.ico`
});
mainWindow.webContents.openDevTools();
mainWindow.loadURL(`file://${__dirname}/index.html`);
mainWindow.on("close", () => {
mainWindow.webContents.send("stop-server");
});
mainWindow.once('ready-to-show', () => {
mainWindow.show()
})
mainWindow.on("closed", () => {
mainWindow = null;
});
}
express-app/index.js:
const ev = require('express-validation');
const Path = require('path')
const Express = require('express')
const BodyParser = require('body-parser')
const CookieParser = require('cookie-parser');
const Session = require('express-session');
const App = require('./app/index.js')
// Init server
const express = Express()
const router = Express.Router()
const port = parseInt(process.argv[2]) || process.env.PORT || 5001
const ip = "0.0.0.0"
express.use(BodyParser.urlencoded({ extended: true }))
express.use(BodyParser.json())
express.use(CookieParser())
express.use(Session({
key: 'sessionId',
secret: 'key',
resave: false,
saveUninitialized: false,
cookie: {
expires: 600000
}
}))
// Init Application
const app = App({ express, router })
// Static content
express.use(Express.static(Path.join(__dirname, './client/dist')))
express.use('/*', Express.static(Path.join(__dirname, './client/dist/index.html')))
// Error handler
express.use(function (err, req, res, next) {
console.log(err)
if (err instanceof ev.ValidationError) {
return res.status(err.status).json({
status: err.status,
message: err.statusText
});
}
return res.status(err.status).json({
status: err.status,
message: err.message
});
});
(async () => {
try {
await app.init()
const server = await app.start(ip, port);
console.log("Server started http://%s:%s", ip, port)
} catch (e) {
console.log(e);
process.exit(1);
}
})()
And this is how I'm creating the session after successful login in app-bl module:
async function loginHandler(req, res, next) {
const username = req.body.username
const password = req.body.password
try {
const user = await authService.login(username, password)
req.session.userId = user.id;
res.json({ user })
} catch (error) {
error.status = 500
next(error)
}
}
I managed to create cookies inside main process and I can see then using console.log, but nothing is showing inside devTools, I tried this code:
const mainSession = mainWindow.webContents.session
const cookie = {
url: 'http://localhost:8000',
name: 'sessionId',
domain: 'localhost',
expirationDate: 99999999999999
}
mainSession.cookies.set(cookie, (error) => {
if (error) {
console.error(error)
}
})
mainSession.cookies.get({}, (error, cookies) => {
console.log(cookies)
})
I have the feeling I'm missing something here.
This package works: https://github.com/heap/electron-cookies
It seems to use Local Storage to emulate cookies. So you can read/write cookies using normal JavaScript syntax, and hopefully your application won't know the difference.
Related
When i tried to use it with react app i can not reach the value. I use sequelize orm and mysql database. I want to send the session information to the react side when the user logs in
app.js file:
const express = require('express')
const dotenv = require("dotenv")
const app = express()
const body_parser = require("body-parser");
const cookieParser = require("cookie-parser")
const session = require('express-session')
const cors = require("cors")
dotenv.config()
app.use(cors())
const SequelizeStore = require("connect-session-sequelize")(session.Store);
const adminRoute = require("./routes/admin")
const locals = require("./middlewares/locals")
app.use(cookieParser())
app.use(express.urlencoded({ extended: false }))
app.use(body_parser.json())
const sequelize = require("./data/db")
app.use(session({
secret: "hello world",
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24
},
store: new SequelizeStore({
db: sequelize
})
}))
app.use(locals);
app.use(adminRoute)
const dummy = require("./data/dummy-data")
async function x() {
// await sequelize.sync({ force: true })
// await dummy()
}
x()
app.listen(process.env.PORT, () => {
console.log(`Server running on Port: ${process.env.PORT}`)
})
locals.js file:
module.exports = (req, res, next) => {
res.locals.auth = req.session.auth
console.log("auth : ", res.locals.auth)
next();
}
admin.js routes:
router.get("/api/session", async (req, res) => {
res.json({ isAuth: res.locals.isAuth });
});
router.post("/login", async (req, res) => {
const { email, password } = req.body
console.log(req.body)
try {
const user = await Users.findOne({
where: {
email: email
}
})
if (!user) {
return res.status(404)
}
const match = await bcrypt.compare(password, user.password)
console.log("match:", match)
if (match) {
req.session.isAuth = 1
return res.status(200).send({ message: "login successful" })
}
else {
req.session.isAuth = 0
return res.status(404)
}
}
catch (err) {
console.log(err)
}
})
React Login.js:
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const login = async (e) => {
e.preventDefault()
const response = await axios.post("http://localhost:5000/login", { email: email, password: password })
console.log(response)
if(response.status === 200){
const response2 = await axios.get("http://localhost:5000/api/session")
console.log(response2)
}
}
Login.js first response = data: {message: 'login successful'}
Login.js second response = data: {}
If i try to react http://localhost:5000/login by hand on the browser auth sets as true. But when i refresh react side it sets undefined again.
I'm developing a register/login website which includes all features to make it work in an efficient and secure way using reactJS, NodeJS and Mysql.
Everything was working fine until I used express-session. In fact, when a user logs in, he will be redirected to a home page (obviously a session will be created) but when the user refreshes the page, It is expected to stay on the home page but the behavior I got is losing the session, thus being redirected to login page.
I looked for a fix and I already tried enabling credentials with Axios in the frontEnd and Cors in the backEnd but the problem is persisting.
This is my code:
server.js
const express = require('express');
const app = express();
const mysql = require('mysql2');
const cors = require('cors');
const validator = require('validator');
const {body, validationResult} = require('express-validator');
const session = require('express-session');
const cookieParser = require('cookie-parser');
app.use(express.json());
app.use(cors({
origin: ['http://localhost:3000'],
methods: ['GET', 'POST'],
credentials: true,
}
));
app.use(express.urlencoded({extended: true}));
app.use(cookieParser());
app.use(session({
name: 'session',
secret: 'crud',
resave: false,
saveUninitialized: false,
cookie: {
expires: 60 * 30,
sameSite: 'strict',
}
}
app.post('/login', (req, res) => {
const mail = validator.escape(req.body.mail);
const pass = validator.escape(req.body.pass);
const sqlSelect = 'SELECT * FROM login WHERE mail = ? AND pass = ?';
db.query(sqlSelect, [mail, pass], (err, result) => {
if (err) {
console.log(err);
}
if (result.length > 0) {
req.session.user = result;
req.session.loggedIn = true;
console.log(req.session.user);
res.send({message: 'success', session: req.session});
}
else {
res.send({message: 'Wrong combination Email/Password !'});
}
})
});
app.get('/login', (req, res) => {
console.log(req.session.user);
if (req.session.user){
res.send({
session: req.session,
message: 'logged'
});
}
else {
res.send({
message: 'Not logged'
});
}
});
app.js (login page)
Axios.defaults.withCredentials = true;
const onSubmit = () => {
Axios.post('http://localhost:9001/login', {
mail,
pass,
}).then((response) => {
console.log(response.data.message);
if (response.data.message === 'success') {
history.push('/home');
}
else {
setMessage(response.data.message);
}
});
};
home.js
export default function Home() {
const [user, setUser] = useState('');
const history = useHistory();
useEffect(() => {
Axios.get('http://localhost:9001/login', {withCredentials: true}).then((response) => {
console.log(response.data.message);
if (response.data.message === 'logged'){
setUser(response.data.session.user[0].mail);
}
else {
history.push('/');
}
})
//eslint-disable-next-line
}, []);
return (
<div>
<p>{user}</p>
</div>
)
}
I hope someone is able to suggest some fix to this. I know I can use localStorage but I want to use the session instead.
I am making a shopify app and have created a Koa + NodeJS backend and NextJS running in frontend, in the same port, and also I have created a custom server.js file When running locally in my PC, its running all right, all the routes work as expected, and I am able to fetch data from Frontend (React) from the routes defined in my backend (Koa).
Now the problem is that, when I deploy my app to vercel it doesn't seem to be recognizing my Koa routes, and is throwing a 404 error in the console for all the requests I make to the backend routes. This is my first time working with NextJS, so I have really very little idea on what is wrong here, so I would like some support on this please.
Also when I deploy it, the shopify auth also doesn't seem to be working anymore like it does when its running in local development.
My Code:
server.js:
require('isomorphic-fetch');
const dotenv = require('dotenv');
dotenv.config();
const Koa = require('koa');
const next = require('next');
const { default: createShopifyAuth } = require('#shopify/koa-shopify-auth');
const { verifyRequest } = require('#shopify/koa-shopify-auth');
const session = require('koa-session');
const { default: graphQLProxy } = require('#shopify/koa-shopify-graphql-proxy');
const { ApiVersion } = require('#shopify/koa-shopify-graphql-proxy');
const Router = require('koa-router');
const { receiveWebhook, registerWebhook } = require('#shopify/koa-shopify-webhooks');
const getSubscriptionUrl = require('./server/getSubscriptionUrl');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const {
SHOPIFY_API_SECRET_KEY,
SHOPIFY_API_KEY,
HOST,
} = process.env;
app.prepare().then(() => {
const server = new Koa();
const router = new Router();
server.use(session({ sameSite: 'none', secure: true }, server));
server.keys = [SHOPIFY_API_SECRET_KEY];
server.use(
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET_KEY,
scopes: ['read_products', 'write_products'],
async afterAuth(ctx) {
const { shop, accessToken } = ctx.session;
ctx.cookies.set("shopOrigin", shop, {
httpOnly: false,
secure: true,
sameSite: 'none'
});
const registration = await registerWebhook({
address: `${HOST}/webhooks/products/create`,
topic: 'PRODUCTS_CREATE',
accessToken,
shop,
apiVersion: ApiVersion.October19
});
if (registration.success) {
console.log('Successfully registered webhook!');
} else {
console.log('Failed to register webhook', registration.result);
}
await getSubscriptionUrl(ctx, accessToken, shop);
}
})
);
router
.get('/api', ctx => {
ctx.res.statusCode = 200;
ctx.body = "API Route"
})
const webhook = receiveWebhook({ secret: SHOPIFY_API_SECRET_KEY });
router.post('/webhooks/products/create', webhook, (ctx) => {
console.log('received webhook: ', ctx.state.webhook);
});
server.use(graphQLProxy({ version: ApiVersion.April19 }));
router.get('*', verifyRequest(), async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
});
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
I found out that Vercel isn't supporting custom servers anymore, if I right. from this https://github.com/vercel/next.js/issues/9397#issuecomment-556215227
So, I used Heroku to deploy my app instead, and now it is working all right.
When scaffolding a Nuxt app using create-nuxt-app, there's an option to add both a backend server and a testing framework.
I've chosen Express as by backend framework, and Jest as my testing framework, which allows me to runs tests with Jest perfectly. However, there's no server-side test example available in which you can test API end points.
I've create an api endpoint /api/threads and tried testing it with something like this:
const request = require('supertest')
const app = require('../app')
describe('GET /api/threads', () => {
it('should return 200', async () => {
await request(app)
.get(`/api/threads`)
.expect(200)
})
})
But am returned with the error: VueRenderer is not a constructor
I also made sure to export app.js, which currently looks like:
require('dotenv-safe').load()
const path = require('path')
const express = require('express')
const consola = require('consola')
const bodyParser = require('body-parser')
const bcrypt = require('bcryptjs')
const session = require('express-session')
const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const SequelizeStore = require('connect-session-sequelize')(session.Store)
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)
const { Nuxt, Builder } = require('nuxt')
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
const sequelize = require('./sequelize')
const models = require('./models')
const router = require('./router')
const controllers = require('./controllers')
const app = express()
config.dev = !(
process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test'
)
function addSessions() {
const sessionStorage = new SequelizeStore({
db: sequelize
})
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: sessionStorage
})
)
app.use(passport.initialize())
app.use(passport.session())
sessionStorage.sync()
}
function addLocalStrategy() {
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password'
},
async (email, password, done) => {
try {
let user = await models.User.findOne({ where: { email } })
if (!user)
return done(null, false, {
message: 'Incorrect email.'
})
user = user.toJSON()
const passValid = await bcrypt.compare(
password,
user.password.toString('utf8')
)
if (passValid === false)
return done(null, false, {
message: 'Incorrect password.'
})
if (user && user.subscriptionId) {
const subscription = await stripe.subscriptions.retrieve(
user.subscriptionId
)
user.subscriptionStatus = subscription.status
}
return done(null, user)
} catch (err) {
console.log(err)
return done(err)
}
}
)
)
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser(async (id, done) => {
try {
const user = await models.User.findById(id)
done(null, user)
} catch (err) {
throw new Error(err)
}
})
}
async function start() {
try {
const nuxt = new Nuxt(config)
const { host, port } = nuxt.options.server
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
} else {
await nuxt.ready()
}
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
addSessions()
addLocalStrategy()
app.use('/', router)
app.use('/private', controllers.User.authenticate)
app.use('/private', express.static(path.join(__dirname, 'private')))
// Give nuxt middleware to express
app.use(nuxt.render)
app.listen(port, host)
consola.ready({
message: `Server listening on http://${host}:${port}`,
badge: true
})
} catch (err) {
throw new Error(err)
}
}
start()
module.export = app
Essentially it's the scaffolded server/app.js, but with code for sessions and authentication.
Any ideas on how to successfully receive a 200 response when hitting a backend API endpoint with a Nuxt / Express combo?
Once you create an instance of Nuxt which you store inside the nuxt variable. you can try printing it out to see whats there. The nuxt.server.app is the object you can feed to supertest in order to test API endpoints.
const { resolve } = require('path')
const { Nuxt, Builder } = require('nuxt')
const request = require('supertest')
// We keep the nuxt and server instance
// So we can close them at the end of the test
let nuxt = null
// Init Nuxt.js and create a server listening on localhost:4000
beforeAll(async () => {
const config = {
dev: process.env.NODE_ENV === 'production',
rootDir: resolve(__dirname, '../'),
mode: 'universal',
}
nuxt = new Nuxt(config)
await new Builder(nuxt).build()
await nuxt.server.listen(3000, 'localhost')
}, 30000)
// Example of testing only generated html
describe('GET /', () => {
test('Route / exits and render HTML', async () => {
const { html } = await nuxt.renderRoute('/', {})
expect(html).toContain('welcome')
})
})
describe('GET /', () => {
test('returns status code 200', async () => {
const response = await request(nuxt.server.app).get('/')
expect(response.statusCode).toBe(200)
})
})
describe('GET /test', () => {
test('returns status code 404', async () => {
const response = await request(nuxt.server.app).get('/test')
expect(response.statusCode).toBe(404)
})
})
// Close server and ask nuxt to stop listening to file changes
afterAll(() => {
nuxt.close()
})
I try to deploy a web app. But i got a problem using a chat in my web site. It's work perfectly fine in local but not online. I got a response 401 "Unauthorized" when i try to access to my chat. I use socket.io
Here is the code :
Index.js
const express = require('express');
const bodyparser = require('body-parser');
const security = require('./middleware/security');
const userRouter = require('./routes/user');
const AnnonceRouter = require('./routes/annonce');
const securityRouter = require('./routes/security');
const commentRouter = require('./routes/comment');
const mailRouter = require('./routes/mail')
const path = require('path');
const isDev = process.env.NODE_ENV !== 'production';
const PORT = process.env.PORT || 5000;
const app = express();
const cors = require('cors');
var chat = require('https').createServer(app)
var io = module.exports.io = require('socket.io').listen(chat)
const SocketManager = require('./SocketManager')
io.on('connection', SocketManager)
if (process.env.NODE_ENV === 'production') {
app.use(express.static('../client/build')); // serve the static react app
app.use(cors());
app.use(bodyparser.json());
app.use(security.verifyToken);
app.use('/', securityRouter);
app.use('/annonce', AnnonceRouter);
app.use('/user', userRouter);
app.use('/comment', commentRouter);
app.use('/mail', mailRouter);
app.get(/^\/(?!api).*/, (req, res) => { // don't serve api routes to react app
res.sendFile(path.join(__dirname, '../client/build/index.html'));
})
console.log('Serving React App...');
};
app.listen(PORT, function () {
console.error(`Node ${isDev ? 'dev server' : 'cluster worker '+process.pid}: listening on port ${PORT}`);
});
a part of my layout.js
import React, { Component } from 'react';
import io from 'socket.io-client'
import { USER_CONNECTED, LOGOUT } from '../Events'
import LoginForm from './LoginForm'
import ChatContainer from './chats/ChatContainer'
const socketUrl = "https://teachonline.herokuapp.com"
export default class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
socket:null,
user:null
};
}
componentWillMount() {
this.initSocket()
}
/*
* Connect to and initializes the socket.
*/
initSocket = ()=>{
const socket = io(socketUrl)
socket.on('connect', ()=>{
console.log("Chat Connected");
})
this.setState({socket})
}
When i try to access to my chat here is the logs in Heroku
2019-08-26T22:25:04.828537+00:00 app[web.1]: TypeError: Cannot read property 'replace' of undefined
2019-08-26T22:25:04.828550+00:00 app[web.1]: at verifyToken (/app/server/middleware/security.js:13:29)
Here is my security.js
const verifyJWTToken = require('../libs/auth').verifyToken;
const access_routes = ["/login_check", "/user", "/mail/send", "/landing-page", "/security/login", "/chat","/socket.io"]
const verifyToken = (req, res, next) => {
if(access_routes.indexOf(req.path) > -1) {
next();
} else {
const auth = req.get('Authorization');
if(!auth || !auth.startsWith('Bearer ')) {
res.sendStatus(401);
}
verifyJWTToken(auth.replace("Bearer ", ""))
.then((decodedToken) => {
req.user = decodedToken;
next();
})
.catch((error) => res.status(400).send({
error: "JWT TOKEN invalid",
details: error
}));
}
}
module.exports = {
verifyToken
}
if it's needed the auth.js
const jwt = require('jsonwebtoken');
const JWT_SECRET = "MaBelleJonquille";
const createToken = function (user = {}) {
return jwt.sign({
payload: {
userName: user.user_name
}
}, JWT_SECRET, {
expiresIn: "7d",
algorithm: "HS256"
});
};
const verifyToken = function (token) {
return new Promise((resolve, reject) => jwt.verify(token, JWT_SECRET, (err, decodedToken) => {
if(err || !decodedToken) {
reject(err);
}
resolve(decodedToken);
}));
};
//fonction pour hasher le password rentré
module.exports = {
createToken,
verifyToken
}
The example of a request
let myHeaders = new Headers();
myHeaders.append("Content-type", "application/json");
myHeaders.append("Authorization", "Bearer "+localStorage.getItem('tokenJWT'));
fetch (URL + localStorage.getItem('user_name'),
{
method:'GET',
mode: "cors",
headers : myHeaders
})
.then(response => response.json())
.then(data => {
data.user_skill.map(x => {
this.skill.push({label: x, value: x});
});
})
.catch(error => (error));
I tried severals things found in the internet but none of them worked for me, so if you have any idea of what am i doing wrong, i'm listening.
Thanks for reading me