From e735a1c50d2f4ba6cc2c70c653c78ad856f09669 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 5 May 2022 19:04:28 -0700 Subject: [PATCH] Updated share token logic. Closes #1113. --- components/metrics/ActiveUsers.js | 6 +---- components/metrics/EventsChart.js | 5 +--- components/metrics/MetricsBar.js | 4 --- components/metrics/MetricsTable.js | 5 +--- components/metrics/WebsiteChart.js | 4 --- components/pages/RealtimeDashboard.js | 4 +-- components/pages/WebsiteDetails.js | 8 ++---- hooks/useApi.js | 39 +++++++++++++++++++++------ hooks/useShareToken.js | 1 + lib/auth.js | 4 +-- lib/constants.js | 2 +- pages/api/realtime/update.js | 4 +-- 12 files changed, 44 insertions(+), 42 deletions(-) diff --git a/components/metrics/ActiveUsers.js b/components/metrics/ActiveUsers.js index 7718b587..653fc783 100644 --- a/components/metrics/ActiveUsers.js +++ b/components/metrics/ActiveUsers.js @@ -3,20 +3,16 @@ import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import useFetch from 'hooks/useFetch'; import Dot from 'components/common/Dot'; -import { TOKEN_HEADER } from 'lib/constants'; -import useShareToken from 'hooks/useShareToken'; import styles from './ActiveUsers.module.css'; export default function ActiveUsers({ websiteId, className, value, interval = 60000 }) { - const shareToken = useShareToken(); const url = websiteId ? `/website/${websiteId}/active` : null; const { data } = useFetch(url, { interval, - headers: { [TOKEN_HEADER]: shareToken?.token }, }); const count = useMemo(() => { if (websiteId) { - return data?.[0]?.x || 0 + return data?.[0]?.x || 0; } return value !== undefined ? value : 0; diff --git a/components/metrics/EventsChart.js b/components/metrics/EventsChart.js index c5d6a898..ef9a142f 100644 --- a/components/metrics/EventsChart.js +++ b/components/metrics/EventsChart.js @@ -6,8 +6,7 @@ import useFetch from 'hooks/useFetch'; import useDateRange from 'hooks/useDateRange'; import useTimezone from 'hooks/useTimezone'; import usePageQuery from 'hooks/usePageQuery'; -import useShareToken from 'hooks/useShareToken'; -import { EVENT_COLORS, TOKEN_HEADER } from 'lib/constants'; +import { EVENT_COLORS } from 'lib/constants'; export default function EventsChart({ websiteId, className, token }) { const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId); @@ -15,7 +14,6 @@ export default function EventsChart({ websiteId, className, token }) { const { query: { url, eventType }, } = usePageQuery(); - const shareToken = useShareToken(); const { data, loading } = useFetch( `/website/${websiteId}/events`, @@ -29,7 +27,6 @@ export default function EventsChart({ websiteId, className, token }) { event_type: eventType, token, }, - headers: { [TOKEN_HEADER]: shareToken?.token }, }, [modified, eventType], ); diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js index 290cde81..21928b61 100644 --- a/components/metrics/MetricsBar.js +++ b/components/metrics/MetricsBar.js @@ -6,14 +6,11 @@ import ErrorMessage from 'components/common/ErrorMessage'; import useFetch from 'hooks/useFetch'; import useDateRange from 'hooks/useDateRange'; import usePageQuery from 'hooks/usePageQuery'; -import useShareToken from 'hooks/useShareToken'; import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format'; -import { TOKEN_HEADER } from 'lib/constants'; import MetricCard from './MetricCard'; import styles from './MetricsBar.module.css'; export default function MetricsBar({ websiteId, className }) { - const shareToken = useShareToken(); const [dateRange] = useDateRange(websiteId); const { startDate, endDate, modified } = dateRange; const [format, setFormat] = useState(true); @@ -34,7 +31,6 @@ export default function MetricsBar({ websiteId, className }) { device, country, }, - headers: { [TOKEN_HEADER]: shareToken?.token }, }, [modified, url, referrer, os, browser, device, country], ); diff --git a/components/metrics/MetricsTable.js b/components/metrics/MetricsTable.js index 48e004cb..29121487 100644 --- a/components/metrics/MetricsTable.js +++ b/components/metrics/MetricsTable.js @@ -9,10 +9,9 @@ import Arrow from 'assets/arrow-right.svg'; import { percentFilter } from 'lib/filters'; import useDateRange from 'hooks/useDateRange'; import usePageQuery from 'hooks/usePageQuery'; -import useShareToken from 'hooks/useShareToken'; import ErrorMessage from 'components/common/ErrorMessage'; import DataTable from './DataTable'; -import { DEFAULT_ANIMATION_DURATION, TOKEN_HEADER } from 'lib/constants'; +import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import styles from './MetricsTable.module.css'; export default function MetricsTable({ @@ -25,7 +24,6 @@ export default function MetricsTable({ onDataLoad, ...props }) { - const shareToken = useShareToken(); const [{ startDate, endDate, modified }] = useDateRange(websiteId); const { resolve, @@ -49,7 +47,6 @@ export default function MetricsTable({ }, onDataLoad, delay: DEFAULT_ANIMATION_DURATION, - headers: { [TOKEN_HEADER]: shareToken?.token }, }, [modified, url, referrer, os, browser, device, country], ); diff --git a/components/metrics/WebsiteChart.js b/components/metrics/WebsiteChart.js index 03cc7b58..fd74538f 100644 --- a/components/metrics/WebsiteChart.js +++ b/components/metrics/WebsiteChart.js @@ -12,9 +12,7 @@ import useDateRange from 'hooks/useDateRange'; import useTimezone from 'hooks/useTimezone'; import usePageQuery from 'hooks/usePageQuery'; import { getDateArray, getDateLength, getDateRangeValues } from 'lib/date'; -import useShareToken from 'hooks/useShareToken'; import useApi from 'hooks/useApi'; -import { TOKEN_HEADER } from 'lib/constants'; import styles from './WebsiteChart.module.css'; export default function WebsiteChart({ @@ -26,7 +24,6 @@ export default function WebsiteChart({ showChart = true, onDataLoad = () => {}, }) { - const shareToken = useShareToken(); const [dateRange, setDateRange] = useDateRange(websiteId); const { startDate, endDate, unit, value, modified } = dateRange; const [timezone] = useTimezone(); @@ -53,7 +50,6 @@ export default function WebsiteChart({ country, }, onDataLoad, - headers: { [TOKEN_HEADER]: shareToken?.token }, }, [modified, url, referrer, os, browser, device, country], ); diff --git a/components/pages/RealtimeDashboard.js b/components/pages/RealtimeDashboard.js index cd6d2a80..9091b6cd 100644 --- a/components/pages/RealtimeDashboard.js +++ b/components/pages/RealtimeDashboard.js @@ -14,7 +14,7 @@ import useFetch from 'hooks/useFetch'; import useLocale from 'hooks/useLocale'; import useCountryNames from 'hooks/useCountryNames'; import { percentFilter } from 'lib/filters'; -import { TOKEN_HEADER, REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; +import { SHARE_TOKEN_HEADER, REALTIME_RANGE, REALTIME_INTERVAL } from 'lib/constants'; import styles from './RealtimeDashboard.module.css'; function mergeData(state, data, time) { @@ -38,7 +38,7 @@ export default function RealtimeDashboard() { params: { start_at: data?.timestamp }, disabled: !init?.websites?.length || !data, interval: REALTIME_INTERVAL, - headers: { [TOKEN_HEADER]: init?.token }, + headers: { [SHARE_TOKEN_HEADER]: init?.token }, }); const renderCountryName = useCallback( diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js index 3b76cfcb..df31b4d9 100644 --- a/components/pages/WebsiteDetails.js +++ b/components/pages/WebsiteDetails.js @@ -20,8 +20,7 @@ import EventsTable from 'components/metrics/EventsTable'; import EventsChart from 'components/metrics/EventsChart'; import useFetch from 'hooks/useFetch'; import usePageQuery from 'hooks/usePageQuery'; -import useShareToken from 'hooks/useShareToken'; -import { DEFAULT_ANIMATION_DURATION, TOKEN_HEADER } from 'lib/constants'; +import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; import styles from './WebsiteDetails.module.css'; const views = { @@ -36,10 +35,7 @@ const views = { }; export default function WebsiteDetails({ websiteId }) { - const shareToken = useShareToken(); - const { data } = useFetch(`/website/${websiteId}`, { - headers: { [TOKEN_HEADER]: shareToken?.token }, - }); + const { data } = useFetch(`/website/${websiteId}`); const [chartLoaded, setChartLoaded] = useState(false); const [countryData, setCountryData] = useState(); const [eventsData, setEventsData] = useState(); diff --git a/hooks/useApi.js b/hooks/useApi.js index 35904d7e..b2e74998 100644 --- a/hooks/useApi.js +++ b/hooks/useApi.js @@ -1,13 +1,18 @@ import { useCallback } from 'react'; import { useRouter } from 'next/router'; import { get, post, put, del, getItem } from 'lib/web'; -import { AUTH_TOKEN } from 'lib/constants'; +import { AUTH_TOKEN, SHARE_TOKEN_HEADER } from 'lib/constants'; +import useStore from 'store/app'; -function includeAuthToken(headers = {}) { - const authToken = getItem(AUTH_TOKEN); +const selector = state => state.shareToken; +function parseHeaders(headers = {}, { authToken, shareToken }) { if (authToken) { - headers.Authorization = `Bearer ${authToken}`; + headers.authorization = `Bearer ${authToken}`; + } + + if (shareToken) { + headers[SHARE_TOKEN_HEADER] = shareToken.token; } return headers; @@ -15,32 +20,50 @@ function includeAuthToken(headers = {}) { export default function useApi() { const { basePath } = useRouter(); + const authToken = getItem(AUTH_TOKEN); + const shareToken = useStore(selector); return { get: useCallback( async (url, params, headers) => { - return get(`${basePath}/api${url}`, params, includeAuthToken(headers)); + return get( + `${basePath}/api${url}`, + params, + parseHeaders(headers, { authToken, shareToken }), + ); }, [get], ), post: useCallback( async (url, params, headers) => { - return post(`${basePath}/api${url}`, params, includeAuthToken(headers)); + return post( + `${basePath}/api${url}`, + params, + parseHeaders(headers, { authToken, shareToken }), + ); }, [post], ), put: useCallback( async (url, params, headers) => { - return put(`${basePath}/api${url}`, params, includeAuthToken(headers)); + return put( + `${basePath}/api${url}`, + params, + parseHeaders(headers, { authToken, shareToken }), + ); }, [put], ), del: useCallback( async (url, params, headers) => { - return del(`${basePath}/api${url}`, params, includeAuthToken(headers)); + return del( + `${basePath}/api${url}`, + params, + parseHeaders(headers, { authToken, shareToken }), + ); }, [del], ), diff --git a/hooks/useShareToken.js b/hooks/useShareToken.js index 4084cce2..87b27404 100644 --- a/hooks/useShareToken.js +++ b/hooks/useShareToken.js @@ -6,6 +6,7 @@ const selector = state => state.shareToken; export default function useShareToken(shareId) { const shareToken = useStore(selector); + console.log({ shareToken }); const { get } = useApi(); async function loadToken(id) { diff --git a/lib/auth.js b/lib/auth.js index 9c533e62..e22e5f2c 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,5 +1,5 @@ import { parseSecureToken, parseToken } from './crypto'; -import { TOKEN_HEADER } from './constants'; +import { SHARE_TOKEN_HEADER } from './constants'; import { getWebsiteById } from './queries'; export async function getAuthToken(req) { @@ -30,7 +30,7 @@ export async function isValidToken(token, validation) { export async function allowQuery(req, skipToken) { const { id } = req.query; - const token = req.headers[TOKEN_HEADER]; + const token = req.headers[SHARE_TOKEN_HEADER]; const websiteId = +id; const website = await getWebsiteById(websiteId); diff --git a/lib/constants.js b/lib/constants.js index cdeebf72..13af1f32 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -5,7 +5,7 @@ export const DATE_RANGE_CONFIG = 'umami.date-range'; export const THEME_CONFIG = 'umami.theme'; export const DASHBOARD_CONFIG = 'umami.dashboard'; export const VERSION_CHECK = 'umami.version-check'; -export const TOKEN_HEADER = 'x-umami-token'; +export const SHARE_TOKEN_HEADER = 'x-umami-share-token'; export const HOMEPAGE_URL = 'https://umami.is'; export const VERSION_URL = 'https://github.com/mikecao/umami/releases'; diff --git a/pages/api/realtime/update.js b/pages/api/realtime/update.js index 730219ae..a43d7b66 100644 --- a/pages/api/realtime/update.js +++ b/pages/api/realtime/update.js @@ -2,7 +2,7 @@ import { useAuth } from 'lib/middleware'; import { ok, methodNotAllowed, badRequest } from 'lib/response'; import { getRealtimeData } from 'lib/queries'; import { parseToken } from 'lib/crypto'; -import { TOKEN_HEADER } from 'lib/constants'; +import { SHARE_TOKEN_HEADER } from 'lib/constants'; export default async (req, res) => { await useAuth(req, res); @@ -10,7 +10,7 @@ export default async (req, res) => { if (req.method === 'GET') { const { start_at } = req.query; - const token = req.headers[TOKEN_HEADER]; + const token = req.headers[SHARE_TOKEN_HEADER]; if (!token) { return badRequest(res);