Switch to authentication using bearer token.

This commit is contained in:
Mike Cao 2022-01-23 00:32:17 -08:00
parent 698d4d2687
commit d8e831db50
8 changed files with 33 additions and 35 deletions

View File

@ -13,6 +13,8 @@ import Icon from 'components/common/Icon';
import Logo from 'assets/logo.svg'; import Logo from 'assets/logo.svg';
import styles from './LoginForm.module.css'; import styles from './LoginForm.module.css';
import usePost from 'hooks/usePost'; import usePost from 'hooks/usePost';
import { setItem } from 'lib/web';
import { AUTH_TOKEN } from '../../lib/constants';
const validate = ({ username, password }) => { const validate = ({ username, password }) => {
const errors = {}; const errors = {};
@ -39,6 +41,8 @@ export default function LoginForm() {
}); });
if (ok) { if (ok) {
setItem(AUTH_TOKEN, data.token);
return router.push('/'); return router.push('/');
} else { } else {
setMessage( setMessage(

View File

@ -7,6 +7,8 @@ import Icon from 'components/common/Icon';
import User from 'assets/user.svg'; import User from 'assets/user.svg';
import Chevron from 'assets/chevron-down.svg'; import Chevron from 'assets/chevron-down.svg';
import styles from './UserButton.module.css'; import styles from './UserButton.module.css';
import { removeItem } from 'lib/web';
import { AUTH_TOKEN } from 'lib/constants';
export default function UserButton() { export default function UserButton() {
const user = useSelector(state => state.user); const user = useSelector(state => state.user);
@ -30,7 +32,8 @@ export default function UserButton() {
function handleSelect(value) { function handleSelect(value) {
if (value === 'logout') { if (value === 'logout') {
router.push('/logout'); removeItem(AUTH_TOKEN);
router.push('/login');
} else if (value === 'profile') { } else if (value === 'profile') {
router.push('/settings/profile'); router.push('/settings/profile');
} }

View File

@ -11,13 +11,14 @@ export default function useFetch(url, options = {}, update = []) {
const [loading, setLoadiing] = useState(false); const [loading, setLoadiing] = useState(false);
const [count, setCount] = useState(0); const [count, setCount] = useState(0);
const { basePath } = useRouter(); 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) { async function loadData(params) {
try { try {
setLoadiing(true); setLoadiing(true);
setError(null); setError(null);
const time = performance.now(); const time = performance.now();
const { data, status, ok } = await get(`${basePath}${url}`, params, headers); const { data, status, ok } = await get(`${basePath}${url}`, params, headers);
dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() })); dispatch(updateQuery({ url, time: performance.now() - time, completed: Date.now() }));

View File

@ -1,12 +1,15 @@
import { parse } from 'cookie';
import { parseSecureToken, parseToken } from './crypto'; import { parseSecureToken, parseToken } from './crypto';
import { AUTH_COOKIE_NAME, TOKEN_HEADER } from './constants'; import { TOKEN_HEADER } from './constants';
import { getWebsiteById } from './queries'; import { getWebsiteById } from './queries';
export async function getAuthToken(req) { 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) { export async function isValidToken(token, validation) {

View File

@ -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 LOCALE_CONFIG = 'umami.locale';
export const TIMEZONE_CONFIG = 'umami.timezone'; export const TIMEZONE_CONFIG = 'umami.timezone';
export const DATE_RANGE_CONFIG = 'umami.date-range'; export const DATE_RANGE_CONFIG = 'umami.date-range';
@ -80,7 +80,8 @@ export const POSTGRESQL_DATE_FORMATS = {
year: 'YYYY-01-01', 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 DESKTOP_SCREEN_WIDTH = 1920;
export const LAPTOP_SCREEN_WIDTH = 1024; export const LAPTOP_SCREEN_WIDTH = 1024;

View File

@ -1,13 +1,17 @@
import { makeUrl } from './url'; import { makeUrl } from './url';
import { AUTH_TOKEN } from './constants';
export const apiRequest = (method, url, body, headers) => export const apiRequest = (method, url, body, headers) => {
fetch(url, { const authToken = getItem(AUTH_TOKEN);
return fetch(url, {
method, method,
cache: 'no-cache', cache: 'no-cache',
credentials: 'same-origin', credentials: 'same-origin',
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
...headers, ...headers,
}, },
body, 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 })); return res.text().then(data => ({ ok: res.ok, status: res.status, res: res, data }));
}); });
};
export const get = (url, params, headers) => export const get = (url, params, headers) =>
apiRequest('get', makeUrl(url, params), undefined, headers); apiRequest('get', makeUrl(url, params), undefined, headers);
@ -64,3 +69,9 @@ export const getItem = (key, session) =>
typeof window !== 'undefined' typeof window !== 'undefined'
? JSON.parse((session ? sessionStorage : localStorage).getItem(key)) ? JSON.parse((session ? sessionStorage : localStorage).getItem(key))
: null; : null;
export const removeItem = (key, session) => {
if (typeof window !== 'undefined') {
(session ? sessionStorage : localStorage).removeItem(key);
}
};

View File

@ -1,7 +1,5 @@
import { serialize } from 'cookie';
import { checkPassword, createSecureToken } from 'lib/crypto'; import { checkPassword, createSecureToken } from 'lib/crypto';
import { getAccountByUsername } from 'lib/queries'; import { getAccountByUsername } from 'lib/queries';
import { AUTH_COOKIE_NAME } from 'lib/constants';
import { ok, unauthorized, badRequest } from 'lib/response'; import { ok, unauthorized, badRequest } from 'lib/response';
export default async (req, res) => { export default async (req, res) => {
@ -16,14 +14,6 @@ export default async (req, res) => {
if (account && (await checkPassword(password, account.password))) { if (account && (await checkPassword(password, account.password))) {
const { user_id, username, is_admin } = account; const { user_id, username, is_admin } = account;
const token = await createSecureToken({ user_id, username, is_admin }); 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 }); return ok(res, { token });
} }

View File

@ -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);
};