From d8e831db50ab05c31101977c905cbfa435e77e5d Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 23 Jan 2022 00:32:17 -0800 Subject: [PATCH] Switch to authentication using bearer token. --- components/forms/LoginForm.js | 4 ++++ components/settings/UserButton.js | 5 ++++- hooks/useFetch.js | 3 ++- lib/auth.js | 11 +++++++---- lib/constants.js | 5 +++-- lib/web.js | 15 +++++++++++++-- pages/api/auth/login.js | 10 ---------- pages/api/auth/logout.js | 15 --------------- 8 files changed, 33 insertions(+), 35 deletions(-) delete mode 100644 pages/api/auth/logout.js diff --git a/components/forms/LoginForm.js b/components/forms/LoginForm.js index 8ac3a556..68c2775c 100644 --- a/components/forms/LoginForm.js +++ b/components/forms/LoginForm.js @@ -13,6 +13,8 @@ import Icon from 'components/common/Icon'; import Logo from 'assets/logo.svg'; import styles from './LoginForm.module.css'; import usePost from 'hooks/usePost'; +import { setItem } from 'lib/web'; +import { AUTH_TOKEN } from '../../lib/constants'; const validate = ({ username, password }) => { const errors = {}; @@ -39,6 +41,8 @@ export default function LoginForm() { }); if (ok) { + setItem(AUTH_TOKEN, data.token); + return router.push('/'); } else { setMessage( diff --git a/components/settings/UserButton.js b/components/settings/UserButton.js index c7604cb5..566c819c 100644 --- a/components/settings/UserButton.js +++ b/components/settings/UserButton.js @@ -7,6 +7,8 @@ import Icon from 'components/common/Icon'; import User from 'assets/user.svg'; import Chevron from 'assets/chevron-down.svg'; import styles from './UserButton.module.css'; +import { removeItem } from 'lib/web'; +import { AUTH_TOKEN } from 'lib/constants'; export default function UserButton() { const user = useSelector(state => state.user); @@ -30,7 +32,8 @@ export default function UserButton() { function handleSelect(value) { if (value === 'logout') { - router.push('/logout'); + removeItem(AUTH_TOKEN); + router.push('/login'); } else if (value === 'profile') { router.push('/settings/profile'); } diff --git a/hooks/useFetch.js b/hooks/useFetch.js index 8098ccc6..a8c6db35 100644 --- a/hooks/useFetch.js +++ b/hooks/useFetch.js @@ -11,13 +11,14 @@ export default function useFetch(url, options = {}, update = []) { const [loading, setLoadiing] = useState(false); const [count, setCount] = useState(0); const { basePath } = useRouter(); - const { params = {}, disabled, headers, delay = 0, interval, onDataLoad } = options; + const { params = {}, headers = {}, disabled, delay = 0, interval, onDataLoad } = options; async function loadData(params) { try { setLoadiing(true); setError(null); const time = performance.now(); + const { data, status, ok } = await get(`${basePath}${url}`, params, headers); dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() })); diff --git a/lib/auth.js b/lib/auth.js index acfbe422..9c533e62 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,12 +1,15 @@ -import { parse } from 'cookie'; import { parseSecureToken, parseToken } from './crypto'; -import { AUTH_COOKIE_NAME, TOKEN_HEADER } from './constants'; +import { TOKEN_HEADER } from './constants'; import { getWebsiteById } from './queries'; export async function getAuthToken(req) { - const token = parse(req.headers.cookie || '')[AUTH_COOKIE_NAME]; + try { + const token = req.headers.authorization; - return parseSecureToken(token); + return parseSecureToken(token.split(' ')[1]); + } catch { + return null; + } } export async function isValidToken(token, validation) { diff --git a/lib/constants.js b/lib/constants.js index c75dbfaa..86a2da9f 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -1,4 +1,4 @@ -export const AUTH_COOKIE_NAME = 'umami.auth'; +export const AUTH_TOKEN = 'umami.auth'; export const LOCALE_CONFIG = 'umami.locale'; export const TIMEZONE_CONFIG = 'umami.timezone'; export const DATE_RANGE_CONFIG = 'umami.date-range'; @@ -80,7 +80,8 @@ export const POSTGRESQL_DATE_FORMATS = { year: 'YYYY-01-01', }; -export const DOMAIN_REGEX = /^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63})$/; +export const DOMAIN_REGEX = + /^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63})$/; export const DESKTOP_SCREEN_WIDTH = 1920; export const LAPTOP_SCREEN_WIDTH = 1024; diff --git a/lib/web.js b/lib/web.js index 3ab035eb..000a870e 100644 --- a/lib/web.js +++ b/lib/web.js @@ -1,13 +1,17 @@ import { makeUrl } from './url'; +import { AUTH_TOKEN } from './constants'; -export const apiRequest = (method, url, body, headers) => - fetch(url, { +export const apiRequest = (method, url, body, headers) => { + const authToken = getItem(AUTH_TOKEN); + + return fetch(url, { method, cache: 'no-cache', credentials: 'same-origin', headers: { Accept: 'application/json', 'Content-Type': 'application/json', + ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}), ...headers, }, body, @@ -18,6 +22,7 @@ export const apiRequest = (method, url, body, headers) => return res.text().then(data => ({ ok: res.ok, status: res.status, res: res, data })); }); +}; export const get = (url, params, headers) => apiRequest('get', makeUrl(url, params), undefined, headers); @@ -64,3 +69,9 @@ export const getItem = (key, session) => typeof window !== 'undefined' ? JSON.parse((session ? sessionStorage : localStorage).getItem(key)) : null; + +export const removeItem = (key, session) => { + if (typeof window !== 'undefined') { + (session ? sessionStorage : localStorage).removeItem(key); + } +}; diff --git a/pages/api/auth/login.js b/pages/api/auth/login.js index be2400a0..fb50fe6a 100644 --- a/pages/api/auth/login.js +++ b/pages/api/auth/login.js @@ -1,7 +1,5 @@ -import { serialize } from 'cookie'; import { checkPassword, createSecureToken } from 'lib/crypto'; import { getAccountByUsername } from 'lib/queries'; -import { AUTH_COOKIE_NAME } from 'lib/constants'; import { ok, unauthorized, badRequest } from 'lib/response'; export default async (req, res) => { @@ -16,14 +14,6 @@ export default async (req, res) => { if (account && (await checkPassword(password, account.password))) { const { user_id, username, is_admin } = account; const token = await createSecureToken({ user_id, username, is_admin }); - const cookie = serialize(AUTH_COOKIE_NAME, token, { - path: '/', - httpOnly: true, - sameSite: true, - maxAge: 60 * 60 * 24 * 365, - }); - - res.setHeader('Set-Cookie', [cookie]); return ok(res, { token }); } diff --git a/pages/api/auth/logout.js b/pages/api/auth/logout.js deleted file mode 100644 index 8d551dea..00000000 --- a/pages/api/auth/logout.js +++ /dev/null @@ -1,15 +0,0 @@ -import { serialize } from 'cookie'; -import { AUTH_COOKIE_NAME } from 'lib/constants'; -import { ok } from 'lib/response'; - -export default async (req, res) => { - const cookie = serialize(AUTH_COOKIE_NAME, '', { - path: '/', - httpOnly: true, - maxAge: 0, - }); - - res.setHeader('Set-Cookie', [cookie]); - - return ok(res); -};