From 678d55779dfbc2978767c4dcfc899fa31c001535 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Mon, 25 Jul 2022 15:55:58 +0100 Subject: [PATCH] Testing cookies --- package-lock.json | 37 ++++++ package.json | 1 + server/admin/default-account.js | 2 +- server/auth/login.js | 9 +- server/auth/logout.js | 4 +- server/auth/token.js | 9 +- server/server.js | 11 +- server/utilities/token-auth.js | 8 +- server/utilities/token-destroy.js | 4 +- ...-generate.js => token-generate-refresh.js} | 2 +- server/utilities/token-refresh.js | 16 +-- tools/react/token-provider.jsx | 124 ------------------ 12 files changed, 79 insertions(+), 148 deletions(-) rename server/utilities/{token-generate.js => token-generate-refresh.js} (93%) delete mode 100644 tools/react/token-provider.jsx diff --git a/package-lock.json b/package-lock.json index bdf197a..df0e6c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^8.6.0", "express": "^4.17.1", @@ -1700,6 +1701,26 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -6682,6 +6703,22 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", diff --git a/package.json b/package.json index c03ccbd..5101299 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "homepage": "https://github.com/krgamestudios/auth-server#readme", "dependencies": { "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^8.6.0", "express": "^4.17.1", diff --git a/server/admin/default-account.js b/server/admin/default-account.js index b9398eb..3da6b6d 100644 --- a/server/admin/default-account.js +++ b/server/admin/default-account.js @@ -25,7 +25,7 @@ module.exports = async () => { }); if (adminRecord == null) { - const webAddress = process.env.WEB_ADDRESS == 'localhost' ? 'example.com' : process.env.WEB_ADDRESS; //can't log in as "localhost" + const webAddress = process.env.WEB_ADDRESS == 'localhost:3000' ? 'example.com' : process.env.WEB_ADDRESS; //can't log in as "localhost" await accounts.create({ email: `${process.env.ADMIN_DEFAULT_USERNAME}@${webAddress}`, username: `${process.env.ADMIN_DEFAULT_USERNAME}`, diff --git a/server/auth/login.js b/server/auth/login.js index 0f9c27c..fb2cef0 100644 --- a/server/auth/login.js +++ b/server/auth/login.js @@ -3,7 +3,7 @@ const utils = require('util'); const bcrypt = require('bcryptjs'); const { accounts } = require('../database/models'); -const tokenGenerate = require('../utilities/token-generate'); +const tokenGenerateRefresh = require('../utilities/token-generate-refresh'); //utilities const validateEmail = require('../utilities/validate-email'); @@ -49,10 +49,13 @@ const route = async (req, res) => { } //generate the JWTs - const tokens = tokenGenerate(account.index, account.email, account.username, account.type, account.admin, account.mod); + const { accessToken, refreshToken } = tokenGenerateRefresh(account.index, account.email, account.username, account.type, account.admin, account.mod); + + //set the cookie + res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'none', maxAge: 60 * 60 * 24 * 30 }); //30 days //finally - res.status(200).json(tokens); + res.status(200).send(accessToken); return null; }; diff --git a/server/auth/logout.js b/server/auth/logout.js index ea6772d..9b0ac67 100644 --- a/server/auth/logout.js +++ b/server/auth/logout.js @@ -2,7 +2,9 @@ const tokenDestroy = require('../utilities/token-destroy'); //auth/logout const route = (req, res) => { - tokenDestroy(req.body.token); + //stored in the cookie + console.log(req.cookies.refreshToken) + tokenDestroy(req.cookies.refreshToken); return res.status(200).end(); }; diff --git a/server/auth/token.js b/server/auth/token.js index 8d9a2f4..ad6df3a 100644 --- a/server/auth/token.js +++ b/server/auth/token.js @@ -4,13 +4,16 @@ const tokenRefresh = require('../utilities/token-refresh'); //auth/token module.exports = async (req, res) => { - const refreshToken = req.body.token; + console.log(req.cookies); - return tokenRefresh(refreshToken, (err, token) => { + return tokenRefresh(req.cookies.refreshToken || '', (err, accessToken, refreshToken) => { if (err) { return res.status(err).end(); } - return res.status(200).send(token); + //set the cookie + res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'none', maxAge: 60 * 60 * 24 * 30 }); //30 days + + return res.status(200).send(accessToken); }); }; \ No newline at end of file diff --git a/server/server.js b/server/server.js index 109ebcf..8a5cf58 100644 --- a/server/server.js +++ b/server/server.js @@ -6,10 +6,19 @@ const express = require('express'); const app = express(); const server = require('http').Server(app); const cors = require('cors'); +const cookieParser = require('cookie-parser'); //config app.use(express.json()); -app.use(cors()); + +app.use(cors({ + credentials: true, + // origin: `${process.env.WEB_PROTOCOL}://${process.env.WEB_ADDRESS}`, + origin: true, + methods: ['HEAD', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'OPTIONS'] +})); + +app.use(cookieParser()); //database connection const database = require('./database'); diff --git a/server/utilities/token-auth.js b/server/utilities/token-auth.js index 9b9a2bd..79c3643 100644 --- a/server/utilities/token-auth.js +++ b/server/utilities/token-auth.js @@ -3,13 +3,13 @@ const jwt = require('jsonwebtoken'); //middleware to authenticate the JWT token module.exports = (req, res, next) => { const authHeader = req.headers['authorization']; - const token = authHeader?.split(' ')[1]; //'Bearer token' + const accessToken = authHeader?.split(' ')[1]; //'Bearer token' - if (!token) { - return res.status(401).send('No token found'); + if (!accessToken) { + return res.status(401).send('No access token found'); } - return jwt.verify(token, process.env.SECRET_ACCESS, (err, user) => { + return jwt.verify(accessToken, process.env.SECRET_ACCESS, (err, user) => { if (err) { return res.status(403).send(err); } diff --git a/server/utilities/token-destroy.js b/server/utilities/token-destroy.js index a73988a..8383b77 100644 --- a/server/utilities/token-destroy.js +++ b/server/utilities/token-destroy.js @@ -1,9 +1,9 @@ const { tokens } = require('../database/models'); -module.exports = (token) => { +module.exports = (refreshToken) => { tokens.destroy({ where: { - token: token || '' + token: refreshToken || '' } }); } \ No newline at end of file diff --git a/server/utilities/token-generate.js b/server/utilities/token-generate-refresh.js similarity index 93% rename from server/utilities/token-generate.js rename to server/utilities/token-generate-refresh.js index 49acbca..85cf6cc 100644 --- a/server/utilities/token-generate.js +++ b/server/utilities/token-generate-refresh.js @@ -13,7 +13,7 @@ module.exports = (index, email, username, type, admin, mod) => { }; //these are strings - const accessToken = jwt.sign(content, process.env.SECRET_ACCESS, { expiresIn: '10m', issuer: 'auth' }); + const accessToken = jwt.sign(content, process.env.SECRET_ACCESS, { expiresIn: '1s', issuer: 'auth' }); const refreshToken = jwt.sign(content, process.env.SECRET_REFRESH, { expiresIn: '30d', issuer: 'auth' }); tokens.create({ token: refreshToken, email: email }); diff --git a/server/utilities/token-refresh.js b/server/utilities/token-refresh.js index 61ea3c5..86d2fef 100644 --- a/server/utilities/token-refresh.js +++ b/server/utilities/token-refresh.js @@ -1,17 +1,17 @@ const jwt = require('jsonwebtoken'); const { tokens } = require('../database/models'); -const generate = require('./token-generate'); +const generate = require('./token-generate-refresh'); const destroy = require('./token-destroy'); -module.exports = async (token, callback) => { - if (!token) { +module.exports = async (oldRefreshToken, callback) => { + if (!oldRefreshToken) { return callback(401); } const tokenRecord = await tokens.findOne({ where: { - token: token || '' + token: oldRefreshToken || '' } }); @@ -19,15 +19,15 @@ module.exports = async (token, callback) => { return callback(403); } - jwt.verify(token, process.env.SECRET_REFRESH, (err, user) => { + jwt.verify(oldRefreshToken, process.env.SECRET_REFRESH, (err, user) => { if (err) { return callback(403); } - const result = generate(user.index, user.email, user.username, user.type, user.admin, user.mod); + const { accessToken, refreshToken } = generate(user.index, user.email, user.username, user.type, user.admin, user.mod); - destroy(token); + destroy(oldRefreshToken); - return callback(null, result); + return callback(null, accessToken, refreshToken); }); }; \ No newline at end of file diff --git a/tools/react/token-provider.jsx b/tools/react/token-provider.jsx deleted file mode 100644 index 7f8cb00..0000000 --- a/tools/react/token-provider.jsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { useState, useEffect, createContext } from 'react'; -import decode from 'jwt-decode'; - -export const TokenContext = createContext(); - -//DOCS: tokenFetch() and tokenCallback() are actually closures here - -const TokenProvider = props => { - //state to be used - const [accessToken, setAccessToken] = useState(''); - const [refreshToken, setRefreshToken] = useState(''); - - //make the access and refresh tokens persist between reloads - useEffect(() => { - setAccessToken(localStorage.getItem("accessToken") || ''); - setRefreshToken(localStorage.getItem("refreshToken") || ''); - }, []); - - //update the stored copies - useEffect(() => { - localStorage.setItem("accessToken", accessToken); - localStorage.setItem("refreshToken", refreshToken); - }, [accessToken, refreshToken]); - - //wrap the default fetch function - const tokenFetch = async (url, options) => { - //use this? - let bearer = accessToken; - - //if expired (10 minutes, normally) - const expired = new Date(decode(accessToken).exp * 1000) < Date.now(); - - if (expired) { - //BUGFIX: if logging out, just skip over the refresh token - if (url === `${process.env.AUTH_URI}/auth/logout`) { - return fetch(url, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${bearer}` - }, - body: JSON.stringify({ - token: refreshToken - }) - }); - } - - //ping the auth server for a new token - const response = await fetch(`${process.env.AUTH_URI}/auth/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - token: refreshToken - }) - }); - - //any errors, throw them - if (!response.ok) { - throw `${response.status}: ${await response.text()}`; - } - - //save the new auth stuff (setting bearer as well) - const newAuth = await response.json(); - - setAccessToken(newAuth.accessToken); - setRefreshToken(newAuth.refreshToken); - bearer = newAuth.accessToken; - } - - //finally, delegate to fetch - return fetch(url, { - ...(options || {}), - headers: { - ...(options || { headers: {} }).headers, - 'Authorization': `Bearer ${bearer}` - } - }); - }; - - //access the refreshed token via callback - const tokenCallback = async (cb) => { - //if expired (10 minutes, normally) - const expired = new Date(decode(accessToken).exp * 1000) < Date.now(); - - if (expired) { - //ping the auth server for a new token - const response = await fetch(`${process.env.AUTH_URI}/auth/token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - token: refreshToken - }) - }); - - //any errors, throw them - if (!response.ok) { - throw `${response.status}: ${await response.text()}`; - } - - //save the new auth stuff (setting bearer as well) - const newAuth = await response.json(); - - setAccessToken(newAuth.accessToken); - setRefreshToken(newAuth.refreshToken); - - //finally - return cb(newAuth.accessToken); - } else { - return cb(accessToken); - } - }; - - return ( - decode(accessToken) }}> - {props.children} - - ) -}; - -export default TokenProvider;