From 67732b9b5a9d39298ba82d947c24d7db4752ee14 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Tue, 8 Nov 2022 22:58:52 -0800 Subject: [PATCH] Refactor authentication and token handling. --- lib/auth.js | 6 +----- lib/middleware.js | 11 +++++----- lib/redis.js | 12 +++++++---- pages/api/auth/login.js | 44 +++++++++++++++++++++++++--------------- pages/api/auth/logout.js | 2 +- pages/api/auth/verify.js | 20 +++--------------- pages/sso.js | 25 +++-------------------- 7 files changed, 50 insertions(+), 70 deletions(-) diff --git a/lib/auth.js b/lib/auth.js index bd1b2c57..ab0d5fe4 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,4 +1,4 @@ -import { getRandomChars, parseSecureToken, parseToken } from 'next-basics'; +import { parseSecureToken, parseToken } from 'next-basics'; import { getUser, getWebsite } from 'queries'; import debug from 'debug'; import { SHARE_TOKEN_HEADER, TYPE_USER, TYPE_WEBSITE } from 'lib/constants'; @@ -6,10 +6,6 @@ import { secret } from 'lib/crypto'; const log = debug('umami:auth'); -export function generateAuthToken() { - return `auth:${getRandomChars(32)}`; -} - export function getAuthToken(req) { const token = req.headers.authorization; diff --git a/lib/middleware.js b/lib/middleware.js index a79027fd..58581d27 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -1,9 +1,9 @@ -import { createMiddleware, unauthorized, badRequest } from 'next-basics'; +import { createMiddleware, unauthorized, badRequest, parseSecureToken } from 'next-basics'; import debug from 'debug'; import cors from 'cors'; import { findSession } from 'lib/session'; -import { parseAuthToken, parseShareToken, getAuthToken } from 'lib/auth'; -import redis from 'lib/redis'; +import { parseShareToken, getAuthToken } from 'lib/auth'; +import { secret } from './crypto'; const log = debug('umami:middleware'); @@ -22,7 +22,8 @@ export const useSession = createMiddleware(async (req, res, next) => { }); export const useAuth = createMiddleware(async (req, res, next) => { - const token = redis.enabled ? await redis.get(getAuthToken(req)) : await parseAuthToken(req); + const token = getAuthToken(req); + const payload = parseSecureToken(token, secret()); const shareToken = await parseShareToken(req); if (!token && !shareToken) { @@ -30,6 +31,6 @@ export const useAuth = createMiddleware(async (req, res, next) => { return unauthorized(res); } - req.auth = { ...token, shareToken }; + req.auth = { ...payload, shareToken }; next(); }); diff --git a/lib/redis.js b/lib/redis.js index 03ae6f8b..b2ea2279 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -9,7 +9,7 @@ let redis; const enabled = Boolean(process.env.REDIS_URL); function getClient() { - if (!process.env.REDIS_URL) { + if (!enabled) { return null; } @@ -32,7 +32,11 @@ function getClient() { async function get(key) { await connect(); - return JSON.parse(await redis.get(key)); + try { + return JSON.parse(await redis.get(key)); + } catch { + return null; + } } async function set(key, value) { @@ -48,8 +52,8 @@ async function del(key) { } async function connect() { - if (!redis) { - redis = process.env.REDIS_URL && (global[REDIS] || getClient()); + if (!redis && enabled) { + redis = global[REDIS] || getClient(); } return redis; diff --git a/pages/api/auth/login.js b/pages/api/auth/login.js index bfdb0b03..a54e8013 100644 --- a/pages/api/auth/login.js +++ b/pages/api/auth/login.js @@ -1,32 +1,44 @@ -import { ok, unauthorized, badRequest, checkPassword, createSecureToken } from 'next-basics'; +import { + ok, + unauthorized, + badRequest, + checkPassword, + createSecureToken, + methodNotAllowed, + getRandomChars, +} from 'next-basics'; import { getUser } from 'queries'; import { secret } from 'lib/crypto'; import redis from 'lib/redis'; -import { generateAuthToken } from 'lib/auth'; export default async (req, res) => { - const { username, password } = req.body; + if (req.method === 'POST') { + const { username, password } = req.body; - if (!username || !password) { - return badRequest(res); - } + if (!username || !password) { + return badRequest(res); + } - const user = await getUser({ username }); + const user = await getUser({ username }); - if (user && checkPassword(password, user.password)) { - if (redis.enabled) { - const token = generateAuthToken(); + if (user && checkPassword(password, user.password)) { + if (redis.enabled) { + const key = `auth:${getRandomChars(32)}`; - await redis.set(token, user); + await redis.set(key, user); + + const token = createSecureToken(key, secret()); + + return ok(res, { token, user }); + } + + const token = createSecureToken(user.id, secret()); return ok(res, { token, user }); } - const { id: userId, username, isAdmin } = user; - const token = createSecureToken({ userId, username, isAdmin }, secret()); - - return ok(res, { token, user }); + return unauthorized(res, 'Incorrect username and/or password.'); } - return unauthorized(res); + return methodNotAllowed(res); }; diff --git a/pages/api/auth/logout.js b/pages/api/auth/logout.js index 88ea81ec..37f117be 100644 --- a/pages/api/auth/logout.js +++ b/pages/api/auth/logout.js @@ -8,7 +8,7 @@ export default async (req, res) => { if (req.method === 'POST') { if (redis.enabled) { - await redis.del(`auth:${getAuthToken(req)}`); + await redis.del(getAuthToken(req)); } return ok(res); diff --git a/pages/api/auth/verify.js b/pages/api/auth/verify.js index e7f7c0c6..670480b1 100644 --- a/pages/api/auth/verify.js +++ b/pages/api/auth/verify.js @@ -1,22 +1,8 @@ import { useAuth } from 'lib/middleware'; -import { ok, unauthorized } from 'next-basics'; -import redis from 'lib/redis'; -import { secret } from 'lib/crypto'; -import { getAuthToken } from 'lib/auth'; +import { ok } from 'next-basics'; export default async (req, res) => { - if (redis.enabled) { - const token = await getAuthToken(req, secret()); - const user = await redis.get(token); + await useAuth(req, res); - return ok(res, user); - } else { - await useAuth(req, res); - - if (req.auth) { - return ok(res, req.auth); - } - } - - return unauthorized(res); + return ok(res, req.auth); }; diff --git a/pages/sso.js b/pages/sso.js index c2c337ab..1ede73fd 100644 --- a/pages/sso.js +++ b/pages/sso.js @@ -1,38 +1,19 @@ import { useEffect } from 'react'; -import debug from 'debug'; import { useRouter } from 'next/router'; import { setItem } from 'next-basics'; import { AUTH_TOKEN } from 'lib/constants'; -import useApi from 'hooks/useApi'; -import { setUser } from 'store/app'; - -const log = debug('umami:sso'); export default function SingleSignOnPage() { const router = useRouter(); - const { get } = useApi(); const { token, url } = router.query; useEffect(() => { - async function verify() { + if (url && token) { setItem(AUTH_TOKEN, token); - const { ok, data } = await get('/auth/verify'); - - if (ok) { - log(data); - setUser(data); - - if (url) { - await router.push(url); - } - } + router.push(url); } - - if (token) { - verify(); - } - }, [token]); + }, [url, token]); return null; }