From f3f0ad15f28fab602e759fd19b2e6f6d36e54133 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 22 Jul 2020 21:33:17 -0700 Subject: [PATCH] Changed JWT implementation. --- lib/crypto.js | 28 +++++++++++++++++++--------- lib/session.js | 4 ++-- package.json | 1 + pages/api/auth.js | 4 ++-- pages/api/verify.js | 12 ++++++++++++ yarn.lock | 12 ++++++++++++ 6 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 pages/api/verify.js diff --git a/lib/crypto.js b/lib/crypto.js index 4d8449df..0d61da09 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -1,9 +1,10 @@ import crypto from 'crypto'; import { v5 } from 'uuid'; -import jwt from 'jsonwebtoken'; import bcrypt from 'bcrypt'; +import { JWT, JWE, JWK } from 'jose'; const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/; +const KEY = JWK.asKey(Buffer.from(secret())); export function sha256(...args) { return crypto.createHash('sha256').update(args.join('')).digest('hex'); @@ -25,14 +26,23 @@ export function isValidHash(s) { return UUID_REGEX.test(s); } -export async function createToken(payload, options) { - return jwt.sign(payload, secret(), options); -} - -export async function parseToken(token, options) { - return jwt.verify(token, secret(), options); -} - export function checkPassword(password, hash) { return bcrypt.compare(password, hash); } + +export async function createToken(payload) { + return JWT.sign(payload, KEY); +} + +export async function verifyToken(token) { + return JWT.verify(token, KEY); +} + +export async function createSecureToken(payload) { + return JWE.encrypt(await createToken(payload), KEY); +} + +export async function verifySecureToken(token) { + const result = await JWE.decrypt(token, KEY); + return verifyToken(result.toString()); +} diff --git a/lib/session.js b/lib/session.js index 8ecc6cec..fb73b1b8 100644 --- a/lib/session.js +++ b/lib/session.js @@ -1,6 +1,6 @@ import { getWebsite, getSession, createSession } from 'lib/db'; import { getCountry, getDevice, getIpAddress } from 'lib/utils'; -import { uuid, parseToken, isValidHash } from 'lib/crypto'; +import { uuid, isValidHash, verifyToken } from 'lib/crypto'; export default async req => { const { payload } = req.body; @@ -11,7 +11,7 @@ export default async req => { } try { - return await parseToken(session); + return await verifyToken(session); } catch { const ip = getIpAddress(req); const { userAgent, browser, os } = getDevice(req); diff --git a/package.json b/package.json index 85f5e05b..b34773da 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dotenv": "^8.2.0", "geolite2-redist": "^1.0.7", "is-localhost-ip": "^1.4.0", + "jose": "^1.27.2", "jsonwebtoken": "^8.5.1", "maxmind": "^4.1.3", "next": "9.3.5", diff --git a/pages/api/auth.js b/pages/api/auth.js index bfc33f33..089bc2af 100644 --- a/pages/api/auth.js +++ b/pages/api/auth.js @@ -1,5 +1,5 @@ import { serialize } from 'cookie'; -import { checkPassword, createToken, secret } from 'lib/crypto'; +import { checkPassword, createSecureToken } from 'lib/crypto'; import { getAccount } from 'lib/db'; export default async (req, res) => { @@ -9,7 +9,7 @@ export default async (req, res) => { if (account && (await checkPassword(password, account.password))) { const { user_id, username, is_admin } = account; - const token = await createToken({ user_id, username, is_admin }); + const token = await createSecureToken({ user_id, username, is_admin }); const expires = new Date(Date.now() + 31536000000); const cookie = serialize('umami.auth', token, { expires, httpOnly: true }); diff --git a/pages/api/verify.js b/pages/api/verify.js new file mode 100644 index 00000000..19961705 --- /dev/null +++ b/pages/api/verify.js @@ -0,0 +1,12 @@ +import { verifySecureToken } from 'lib/crypto'; + +export default async (req, res) => { + const { token } = req.body; + + try { + const payload = await verifySecureToken(token); + res.status(200).send(payload); + } catch { + res.status(400).send(''); + } +}; diff --git a/yarn.lock b/yarn.lock index 02583387..b34e126c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1175,6 +1175,11 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@panva/asn1.js@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" + integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== + "@prisma/cli@2.2.2": version "2.2.2" resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.2.2.tgz#9689483442277523b5724abdafde69f15b8279f0" @@ -4740,6 +4745,13 @@ jest-worker@^26.0.0: merge-stream "^2.0.0" supports-color "^7.0.0" +jose@^1.27.2: + version "1.27.2" + resolved "https://registry.yarnpkg.com/jose/-/jose-1.27.2.tgz#060c6cddf17e90eaa6316b926860b12ab76580fc" + integrity sha512-zLIwnMa8dh5A2jFo56KvhiXCaW0hFjdNvG0I5GScL8Wro+/r/SnyIYTbnX3fYztPNSfgQp56sDMHUuS9c3e6bw== + dependencies: + "@panva/asn1.js" "^1.0.0" + js-levenshtein@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"