mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-26 12:29:05 +01:00
Use token authentication for API requests.
This commit is contained in:
parent
bff8806b61
commit
96bd7e5b47
@ -30,16 +30,19 @@ const views = {
|
||||
event: EventsTable,
|
||||
};
|
||||
|
||||
export default function WebsiteDetails({ websiteId, shareId }) {
|
||||
export default function WebsiteDetails({ websiteId, token }) {
|
||||
const router = useRouter();
|
||||
const { data } = useFetch(`/api/website/${websiteId}`, { share_id: shareId });
|
||||
const { data } = useFetch(`/api/website/${websiteId}`, { token });
|
||||
const [chartLoaded, setChartLoaded] = useState(false);
|
||||
const [countryData, setCountryData] = useState();
|
||||
const [eventsData, setEventsData] = useState();
|
||||
const {
|
||||
query: { id, view },
|
||||
basePath,
|
||||
asPath,
|
||||
} = router;
|
||||
const path = `/website/${id.join('/')}`;
|
||||
|
||||
const path = `${basePath}/${asPath.split('/')[1]}/${id.join('/')}`;
|
||||
|
||||
const BackButton = () => (
|
||||
<Button
|
||||
@ -91,6 +94,7 @@ export default function WebsiteDetails({ websiteId, shareId }) {
|
||||
|
||||
const tableProps = {
|
||||
websiteId,
|
||||
token,
|
||||
websiteDomain: data?.domain,
|
||||
limit: 10,
|
||||
onExpand: handleExpand,
|
||||
@ -118,6 +122,7 @@ export default function WebsiteDetails({ websiteId, shareId }) {
|
||||
<div className={classNames(styles.chart, 'col')}>
|
||||
<WebsiteChart
|
||||
websiteId={websiteId}
|
||||
token={token}
|
||||
title={data.name}
|
||||
onDataLoad={handleDataLoad}
|
||||
showLink={false}
|
||||
@ -162,13 +167,18 @@ export default function WebsiteDetails({ websiteId, shareId }) {
|
||||
<EventsTable {...tableProps} onDataLoad={setEventsData} />
|
||||
</div>
|
||||
<div className="col-12 col-md-12 col-lg-8 pt-5 pb-5">
|
||||
<EventsChart websiteId={websiteId} />
|
||||
<EventsChart websiteId={websiteId} token={token} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{view && (
|
||||
<MenuLayout className={styles.view} menuClassName={styles.menu} menu={menuOptions}>
|
||||
<MenuLayout
|
||||
className={styles.view}
|
||||
menuClassName={styles.menu}
|
||||
contentClassName={styles.content}
|
||||
menu={menuOptions}
|
||||
>
|
||||
<DetailsComponent {...tableProps} limit={false} />
|
||||
</MenuLayout>
|
||||
)}
|
||||
|
@ -10,6 +10,10 @@
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.backButton {
|
||||
align-self: flex-start;
|
||||
margin-bottom: 16px;
|
||||
|
@ -5,7 +5,7 @@ import { setDateRange } from 'redux/actions/websites';
|
||||
import Button from './Button';
|
||||
import Refresh from 'assets/redo.svg';
|
||||
import Dots from 'assets/ellipsis-h.svg';
|
||||
import { useDateRange } from 'hooks/useDateRange';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import { getDateRange } from '../../lib/date';
|
||||
|
||||
export default function RefreshButton({ websiteId }) {
|
||||
|
@ -4,8 +4,8 @@ import useFetch from 'hooks/useFetch';
|
||||
import styles from './ActiveUsers.module.css';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function ActiveUsers({ websiteId, className }) {
|
||||
const { data } = useFetch(`/api/website/${websiteId}/active`, {}, { interval: 60000 });
|
||||
export default function ActiveUsers({ websiteId, token, className }) {
|
||||
const { data } = useFetch(`/api/website/${websiteId}/active`, { token }, { interval: 60000 });
|
||||
const count = useMemo(() => {
|
||||
return data?.[0]?.x || 0;
|
||||
}, [data]);
|
||||
|
@ -3,13 +3,14 @@ import { FormattedMessage } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import { browserFilter } from 'lib/filters';
|
||||
|
||||
export default function BrowsersTable({ websiteId, limit, onExpand }) {
|
||||
export default function BrowsersTable({ websiteId, token, limit, onExpand }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title={<FormattedMessage id="metrics.browsers" defaultMessage="Browsers" />}
|
||||
type="browser"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
token={token}
|
||||
limit={limit}
|
||||
dataFilter={browserFilter}
|
||||
onExpand={onExpand}
|
||||
|
@ -3,13 +3,20 @@ import MetricsTable from './MetricsTable';
|
||||
import { countryFilter, percentFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function CountriesTable({ websiteId, limit, onDataLoad = () => {}, onExpand }) {
|
||||
export default function CountriesTable({
|
||||
websiteId,
|
||||
token,
|
||||
limit,
|
||||
onDataLoad = () => {},
|
||||
onExpand,
|
||||
}) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title={<FormattedMessage id="metrics.countries" defaultMessage="Countries" />}
|
||||
type="country"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
token={token}
|
||||
limit={limit}
|
||||
dataFilter={countryFilter}
|
||||
onDataLoad={data => onDataLoad(percentFilter(data))}
|
||||
|
@ -4,13 +4,14 @@ import { deviceFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { getDeviceMessage } from 'components/messages';
|
||||
|
||||
export default function DevicesTable({ websiteId, limit, onExpand }) {
|
||||
export default function DevicesTable({ websiteId, token, limit, onExpand }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title={<FormattedMessage id="metrics.devices" defaultMessage="Devices" />}
|
||||
type="device"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
token={token}
|
||||
limit={limit}
|
||||
dataFilter={deviceFilter}
|
||||
renderLabel={({ x }) => getDeviceMessage(x)}
|
||||
|
@ -3,7 +3,7 @@ import tinycolor from 'tinycolor2';
|
||||
import BarChart from './BarChart';
|
||||
import { getTimezone, getDateArray, getDateLength } from 'lib/date';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useDateRange } from 'hooks/useDateRange';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
|
||||
const COLORS = [
|
||||
'#2680eb',
|
||||
@ -16,7 +16,7 @@ const COLORS = [
|
||||
'#85d044',
|
||||
];
|
||||
|
||||
export default function EventsChart({ websiteId }) {
|
||||
export default function EventsChart({ websiteId, token }) {
|
||||
const dateRange = useDateRange(websiteId);
|
||||
const { startDate, endDate, unit, modified } = dateRange;
|
||||
const { data } = useFetch(
|
||||
@ -26,6 +26,7 @@ export default function EventsChart({ websiteId }) {
|
||||
end_at: +endDate,
|
||||
unit,
|
||||
tz: getTimezone(),
|
||||
token,
|
||||
},
|
||||
{ update: [modified] },
|
||||
);
|
||||
|
@ -3,13 +3,14 @@ import { FormattedMessage } from 'react-intl';
|
||||
import MetricsTable from './MetricsTable';
|
||||
import styles from './EventsTable.module.css';
|
||||
|
||||
export default function EventsTable({ websiteId, limit, onExpand, onDataLoad }) {
|
||||
export default function EventsTable({ websiteId, token, limit, onExpand, onDataLoad }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title={<FormattedMessage id="metrics.events" defaultMessage="Events" />}
|
||||
type="event"
|
||||
metric={<FormattedMessage id="metrics.actions" defaultMessage="Actions" />}
|
||||
websiteId={websiteId}
|
||||
token={token}
|
||||
limit={limit}
|
||||
renderLabel={({ x }) => <Label value={x} />}
|
||||
onExpand={onExpand}
|
||||
|
@ -3,12 +3,12 @@ import { FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import Loading from 'components/common/Loading';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useDateRange } from 'hooks/useDateRange';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format';
|
||||
import MetricCard from './MetricCard';
|
||||
import styles from './MetricsBar.module.css';
|
||||
|
||||
export default function MetricsBar({ websiteId, className }) {
|
||||
export default function MetricsBar({ websiteId, token, className }) {
|
||||
const dateRange = useDateRange(websiteId);
|
||||
const { startDate, endDate, modified } = dateRange;
|
||||
const { data } = useFetch(
|
||||
@ -16,6 +16,7 @@ export default function MetricsBar({ websiteId, className }) {
|
||||
{
|
||||
start_at: +startDate,
|
||||
end_at: +endDate,
|
||||
token,
|
||||
},
|
||||
{
|
||||
update: [modified],
|
||||
|
@ -10,12 +10,13 @@ import useFetch from 'hooks/useFetch';
|
||||
import Arrow from 'assets/arrow-right.svg';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { formatNumber, formatLongNumber } from 'lib/format';
|
||||
import { useDateRange } from 'hooks/useDateRange';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import styles from './MetricsTable.module.css';
|
||||
|
||||
export default function MetricsTable({
|
||||
websiteId,
|
||||
websiteDomain,
|
||||
token,
|
||||
title,
|
||||
metric,
|
||||
type,
|
||||
@ -37,6 +38,7 @@ export default function MetricsTable({
|
||||
start_at: +startDate,
|
||||
end_at: +endDate,
|
||||
domain: websiteDomain,
|
||||
token,
|
||||
},
|
||||
{ onDataLoad, delay: 300, update: [modified] },
|
||||
);
|
||||
|
@ -3,13 +3,14 @@ import MetricsTable from './MetricsTable';
|
||||
import { osFilter } from 'lib/filters';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default function OSTable({ websiteId, limit, onExpand }) {
|
||||
export default function OSTable({ websiteId, token, limit, onExpand }) {
|
||||
return (
|
||||
<MetricsTable
|
||||
title={<FormattedMessage id="metrics.operating-systems" defaultMessage="Operating system" />}
|
||||
type="os"
|
||||
metric={<FormattedMessage id="metrics.visitors" defaultMessage="Visitors" />}
|
||||
websiteId={websiteId}
|
||||
token={token}
|
||||
limit={limit}
|
||||
dataFilter={osFilter}
|
||||
onExpand={onExpand}
|
||||
|
@ -5,7 +5,7 @@ import { urlFilter } from 'lib/filters';
|
||||
import { FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
|
||||
import MetricsTable from './MetricsTable';
|
||||
|
||||
export default function PagesTable({ websiteId, websiteDomain, limit, onExpand }) {
|
||||
export default function PagesTable({ websiteId, token, websiteDomain, limit, onExpand }) {
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
|
||||
const buttons = [
|
||||
@ -25,6 +25,7 @@ export default function PagesTable({ websiteId, websiteDomain, limit, onExpand }
|
||||
limit ? null : <FilterButtons buttons={buttons} selected={filter} onClick={setFilter} />
|
||||
}
|
||||
websiteId={websiteId}
|
||||
token={token}
|
||||
limit={limit}
|
||||
dataFilter={urlFilter}
|
||||
filterOptions={{ domain: websiteDomain, raw: filter === FILTER_RAW }}
|
||||
|
@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import ButtonGroup from 'components/common/ButtonGroup';
|
||||
import { getDateRange } from 'lib/date';
|
||||
import styles from './QuickButtons.module.css';
|
||||
|
||||
const options = [
|
||||
{ label: '24h', value: '24hour' },
|
||||
{ label: '7d', value: '7day' },
|
||||
{ label: '30d', value: '30day' },
|
||||
];
|
||||
|
||||
export default function QuickButtons({ value, onChange }) {
|
||||
const selectedItem = options.find(item => item.value === value)?.value;
|
||||
|
||||
function handleClick(selected) {
|
||||
if (selected !== value) {
|
||||
onChange(getDateRange(selected));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
size="xsmall"
|
||||
className={styles.buttons}
|
||||
items={options}
|
||||
selectedItem={selectedItem}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.buttons button + button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.buttons .button {
|
||||
font-size: var(--font-size-xsmall);
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.active {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.buttons button:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 576px) {
|
||||
.buttons {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -5,7 +5,13 @@ import { refFilter } from 'lib/filters';
|
||||
import ButtonGroup from 'components/common/ButtonGroup';
|
||||
import { FILTER_DOMAIN_ONLY, FILTER_COMBINED, FILTER_RAW } from 'lib/constants';
|
||||
|
||||
export default function ReferrersTable({ websiteId, websiteDomain, limit, onExpand = () => {} }) {
|
||||
export default function ReferrersTable({
|
||||
websiteId,
|
||||
websiteDomain,
|
||||
token,
|
||||
limit,
|
||||
onExpand = () => {},
|
||||
}) {
|
||||
const [filter, setFilter] = useState(FILTER_COMBINED);
|
||||
|
||||
const buttons = [
|
||||
@ -40,6 +46,7 @@ export default function ReferrersTable({ websiteId, websiteDomain, limit, onExpa
|
||||
}
|
||||
websiteId={websiteId}
|
||||
websiteDomain={websiteDomain}
|
||||
token={token}
|
||||
limit={limit}
|
||||
dataFilter={refFilter}
|
||||
filterOptions={{
|
||||
|
@ -3,17 +3,18 @@ import { useDispatch } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import PageviewsChart from './PageviewsChart';
|
||||
import MetricsBar from './MetricsBar';
|
||||
import WebsiteHeader from './WebsiteHeader';
|
||||
import DateFilter from 'components/common/DateFilter';
|
||||
import StickyHeader from 'components/helpers/StickyHeader';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import { getDateArray, getDateLength, getTimezone } from 'lib/date';
|
||||
import { setDateRange } from 'redux/actions/websites';
|
||||
import styles from './WebsiteChart.module.css';
|
||||
import WebsiteHeader from './WebsiteHeader';
|
||||
import { useDateRange } from '../../hooks/useDateRange';
|
||||
|
||||
export default function WebsiteChart({
|
||||
websiteId,
|
||||
token,
|
||||
title,
|
||||
stickyHeader = false,
|
||||
showLink = false,
|
||||
@ -30,6 +31,7 @@ export default function WebsiteChart({
|
||||
end_at: +endDate,
|
||||
unit,
|
||||
tz: getTimezone(),
|
||||
token,
|
||||
},
|
||||
{ onDataLoad, update: [modified] },
|
||||
);
|
||||
@ -50,7 +52,7 @@ export default function WebsiteChart({
|
||||
|
||||
return (
|
||||
<>
|
||||
<WebsiteHeader websiteId={websiteId} title={title} showLink={showLink} />
|
||||
<WebsiteHeader websiteId={websiteId} token={token} title={title} showLink={showLink} />
|
||||
<div className={classNames(styles.header, 'row')}>
|
||||
<StickyHeader
|
||||
className={classNames(styles.metrics, 'col row')}
|
||||
@ -58,7 +60,7 @@ export default function WebsiteChart({
|
||||
enabled={stickyHeader}
|
||||
>
|
||||
<div className="col-12 col-lg-9">
|
||||
<MetricsBar websiteId={websiteId} />
|
||||
<MetricsBar websiteId={websiteId} token={token} />
|
||||
</div>
|
||||
<div className={classNames(styles.filter, 'col-12 col-lg-3')}>
|
||||
<DateFilter
|
||||
|
@ -2,18 +2,18 @@ import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Link from 'components/common/Link';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import RefreshButton from 'components/common/RefreshButton';
|
||||
import ButtonLayout from 'components/layout/ButtonLayout';
|
||||
import Icon from 'components/common/Icon';
|
||||
import ActiveUsers from './ActiveUsers';
|
||||
import Arrow from 'assets/arrow-right.svg';
|
||||
import styles from './WebsiteHeader.module.css';
|
||||
import RefreshButton from '../common/RefreshButton';
|
||||
import ButtonLayout from '../layout/ButtonLayout';
|
||||
import Icon from '../common/Icon';
|
||||
|
||||
export default function WebsiteHeader({ websiteId, title, showLink = false }) {
|
||||
export default function WebsiteHeader({ websiteId, token, title, showLink = false }) {
|
||||
return (
|
||||
<PageHeader>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<ActiveUsers className={styles.active} websiteId={websiteId} />
|
||||
<ActiveUsers className={styles.active} websiteId={websiteId} token={token} />
|
||||
<ButtonLayout>
|
||||
<RefreshButton websiteId={websiteId} />
|
||||
{showLink && (
|
||||
|
@ -10,7 +10,7 @@ import DateFilter from 'components/common/DateFilter';
|
||||
import Dots from 'assets/ellipsis-h.svg';
|
||||
import { getTimezone } from 'lib/date';
|
||||
import { setItem } from 'lib/web';
|
||||
import { useDateRange } from 'hooks/useDateRange';
|
||||
import useDateRange from 'hooks/useDateRange';
|
||||
import { setDateRange } from 'redux/actions/websites';
|
||||
import styles from './ProfileSettings.module.css';
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { parseISO } from 'date-fns';
|
||||
import { getDateRange } from 'lib/date';
|
||||
import { getItem } from 'lib/web';
|
||||
|
||||
export function useDateRange(websiteId, defaultDateRange = '7day') {
|
||||
export default function useDateRange(websiteId, defaultDateRange = '24hour') {
|
||||
const globalDefault = getItem('umami.date-range');
|
||||
|
||||
if (globalDefault) {
|
||||
|
44
lib/auth.js
44
lib/auth.js
@ -1,9 +1,49 @@
|
||||
import { parse } from 'cookie';
|
||||
import { parseSecureToken } from './crypto';
|
||||
import { parseSecureToken, parseToken } from './crypto';
|
||||
import { AUTH_COOKIE_NAME } from './constants';
|
||||
import { getWebsiteById } from './queries';
|
||||
|
||||
export async function verifyAuthToken(req) {
|
||||
export async function getAuthToken(req) {
|
||||
const token = parse(req.headers.cookie || '')[AUTH_COOKIE_NAME];
|
||||
|
||||
return parseSecureToken(token);
|
||||
}
|
||||
|
||||
export async function isValidToken(token, validation) {
|
||||
try {
|
||||
const result = await parseToken(token);
|
||||
|
||||
if (typeof validation === 'object') {
|
||||
return !Object.keys(validation).find(key => result[key] !== validation[key]);
|
||||
} else if (typeof validation === 'function') {
|
||||
return validation(result);
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function allowQuery(req, skipToken) {
|
||||
const { id, token } = req.query;
|
||||
const websiteId = +id;
|
||||
|
||||
const website = await getWebsiteById(websiteId);
|
||||
|
||||
if (website) {
|
||||
if (token && !skipToken) {
|
||||
return isValidToken(token, { website_id: websiteId });
|
||||
}
|
||||
|
||||
const authToken = await getAuthToken(req);
|
||||
|
||||
if (authToken) {
|
||||
const { user_id, is_admin } = authToken;
|
||||
|
||||
return is_admin || website.user_id === user_id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ export function uuid(...args) {
|
||||
return v5(args.join(''), salt());
|
||||
}
|
||||
|
||||
export function isValidId(s) {
|
||||
export function isValidUuid(s) {
|
||||
return validate(s);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import cors from 'cors';
|
||||
import { verifySession } from './session';
|
||||
import { verifyAuthToken } from './auth';
|
||||
import { getSession } from './session';
|
||||
import { getAuthToken } from './auth';
|
||||
import { unauthorized, badRequest, serverError } from './response';
|
||||
|
||||
export function use(middleware) {
|
||||
@ -21,7 +21,7 @@ export const useSession = use(async (req, res, next) => {
|
||||
let session;
|
||||
|
||||
try {
|
||||
session = await verifySession(req);
|
||||
session = await getSession(req);
|
||||
} catch (e) {
|
||||
return serverError(res, e.message);
|
||||
}
|
||||
@ -35,13 +35,7 @@ export const useSession = use(async (req, res, next) => {
|
||||
});
|
||||
|
||||
export const useAuth = use(async (req, res, next) => {
|
||||
let token;
|
||||
|
||||
try {
|
||||
token = await verifyAuthToken(req);
|
||||
} catch (e) {
|
||||
return serverError(res, e.message);
|
||||
}
|
||||
const token = await getAuthToken(req);
|
||||
|
||||
if (!token) {
|
||||
return unauthorized(res);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { getWebsiteByUuid, getSessionByUuid, createSession } from 'lib/queries';
|
||||
import { getClientInfo } from 'lib/request';
|
||||
import { uuid, isValidId } from 'lib/crypto';
|
||||
import { uuid, isValidUuid } from 'lib/crypto';
|
||||
|
||||
export async function verifySession(req) {
|
||||
export async function getSession(req) {
|
||||
const { payload } = req.body;
|
||||
|
||||
if (!payload) {
|
||||
@ -11,7 +11,7 @@ export async function verifySession(req) {
|
||||
|
||||
const { website: website_uuid, hostname, screen, language } = payload;
|
||||
|
||||
if (!isValidId(website_uuid)) {
|
||||
if (!isValidUuid(website_uuid)) {
|
||||
throw new Error(`Invalid website: ${website_uuid}`);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { getWebsiteByShareId } from 'lib/queries';
|
||||
import { ok, notFound, methodNotAllowed } from 'lib/response';
|
||||
import { createToken } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.query;
|
||||
@ -8,7 +9,10 @@ export default async (req, res) => {
|
||||
const website = await getWebsiteByShareId(id);
|
||||
|
||||
if (website) {
|
||||
return ok(res, website);
|
||||
const websiteId = website.website_id;
|
||||
const token = await createToken({ website_id: websiteId });
|
||||
|
||||
return ok(res, { websiteId, token });
|
||||
}
|
||||
|
||||
return notFound(res);
|
||||
|
@ -1,12 +1,18 @@
|
||||
import { getActiveVisitors } from 'lib/queries';
|
||||
import { methodNotAllowed, ok } from 'lib/response';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
export default async (req, res) => {
|
||||
if (req.method === 'GET') {
|
||||
const { id } = req.query;
|
||||
const website_id = +id;
|
||||
if (!(await allowQuery(req))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const result = await getActiveVisitors(website_id);
|
||||
const { id } = req.query;
|
||||
|
||||
const websiteId = +id;
|
||||
|
||||
const result = await getActiveVisitors(websiteId);
|
||||
|
||||
return ok(res, result);
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
import moment from 'moment-timezone';
|
||||
import { getEvents } from 'lib/queries';
|
||||
import { ok, badRequest, methodNotAllowed } from 'lib/response';
|
||||
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
const unitTypes = ['year', 'month', 'hour', 'day'];
|
||||
|
||||
export default async (req, res) => {
|
||||
if (req.method === 'GET') {
|
||||
if (!(await allowQuery(req))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const { id, start_at, end_at, unit, tz } = req.query;
|
||||
|
||||
if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
|
||||
|
@ -1,30 +1,30 @@
|
||||
import { deleteWebsite, getWebsiteById } from 'lib/queries';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
const { id } = req.query;
|
||||
|
||||
const { user_id, is_admin } = req.auth;
|
||||
const { id, share_id } = req.query;
|
||||
const websiteId = +id;
|
||||
|
||||
const website = await getWebsiteById(websiteId);
|
||||
|
||||
if (req.method === 'GET') {
|
||||
if (is_admin || website.user_id === user_id || (share_id && website.share_id === share_id)) {
|
||||
return ok(res, website);
|
||||
if (!(await allowQuery(req))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
return unauthorized(res);
|
||||
|
||||
const website = await getWebsiteById(websiteId);
|
||||
|
||||
return ok(res, website);
|
||||
}
|
||||
|
||||
if (req.method === 'DELETE') {
|
||||
if (is_admin || website.user_id === user_id) {
|
||||
await deleteWebsite(websiteId);
|
||||
|
||||
return ok(res);
|
||||
if (!(await allowQuery(req, true))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
return unauthorized(res);
|
||||
|
||||
await deleteWebsite(websiteId);
|
||||
|
||||
return ok(res);
|
||||
}
|
||||
|
||||
return methodNotAllowed(res);
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { getMetrics } from 'lib/queries';
|
||||
import { methodNotAllowed, ok } from 'lib/response';
|
||||
import { methodNotAllowed, ok, unauthorized } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
export default async (req, res) => {
|
||||
if (req.method === 'GET') {
|
||||
if (!(await allowQuery(req))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const { id, start_at, end_at } = req.query;
|
||||
|
||||
const websiteId = +id;
|
||||
const startDate = new Date(+start_at);
|
||||
const endDate = new Date(+end_at);
|
||||
|
@ -1,21 +1,26 @@
|
||||
import moment from 'moment-timezone';
|
||||
import { getPageviews } from 'lib/queries';
|
||||
import { ok, badRequest, methodNotAllowed } from 'lib/response';
|
||||
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
const unitTypes = ['year', 'month', 'hour', 'day'];
|
||||
|
||||
export default async (req, res) => {
|
||||
if (req.method === 'GET') {
|
||||
const { id, start_at, end_at, unit, tz } = req.query;
|
||||
|
||||
if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
|
||||
return badRequest(res);
|
||||
if (!(await allowQuery(req))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const { id, start_at, end_at, unit, tz } = req.query;
|
||||
|
||||
const websiteId = +id;
|
||||
const startDate = new Date(+start_at);
|
||||
const endDate = new Date(+end_at);
|
||||
|
||||
if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
const [pageviews, uniques] = await Promise.all([
|
||||
getPageviews(websiteId, startDate, endDate, tz, unit, '*'),
|
||||
getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id'),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { getRankings } from 'lib/queries';
|
||||
import { ok, badRequest, methodNotAllowed } from 'lib/response';
|
||||
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
|
||||
import { DOMAIN_REGEX } from 'lib/constants';
|
||||
import { allowQuery } from 'lib/auth';
|
||||
|
||||
const sessionColumns = ['browser', 'os', 'device', 'country'];
|
||||
const pageviewColumns = ['url', 'referrer'];
|
||||
@ -26,7 +27,12 @@ function getColumn(type) {
|
||||
|
||||
export default async (req, res) => {
|
||||
if (req.method === 'GET') {
|
||||
if (!(await allowQuery(req))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const { id, type, start_at, end_at, domain } = req.query;
|
||||
|
||||
const websiteId = +id;
|
||||
const startDate = new Date(+start_at);
|
||||
const endDate = new Date(+end_at);
|
||||
|
@ -15,21 +15,21 @@ export default async (req, res) => {
|
||||
if (website_id) {
|
||||
const website = await getWebsiteById(website_id);
|
||||
|
||||
if (website.user_id === user_id || is_admin) {
|
||||
let { share_id } = website;
|
||||
|
||||
if (enable_share_url) {
|
||||
share_id = share_id ? share_id : getRandomChars(8);
|
||||
} else {
|
||||
share_id = null;
|
||||
}
|
||||
|
||||
await updateWebsite(website_id, { name, domain, share_id });
|
||||
|
||||
return ok(res);
|
||||
if (website.user_id !== user_id && !is_admin) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
return unauthorized(res);
|
||||
let { share_id } = website;
|
||||
|
||||
if (enable_share_url) {
|
||||
share_id = share_id ? share_id : getRandomChars(8);
|
||||
} else {
|
||||
share_id = null;
|
||||
}
|
||||
|
||||
await updateWebsite(website_id, { name, domain, share_id });
|
||||
|
||||
return ok(res);
|
||||
} else {
|
||||
const website_uuid = uuid();
|
||||
const share_id = enable_share_url ? getRandomChars(8) : null;
|
||||
|
@ -7,13 +7,14 @@ export default async (req, res) => {
|
||||
|
||||
const { user_id: current_user_id, is_admin } = req.auth;
|
||||
const { user_id } = req.query;
|
||||
const userId = +user_id;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
if (user_id && !is_admin) {
|
||||
if (userId !== current_user_id && !is_admin) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const websites = await getUserWebsites(+user_id || current_user_id);
|
||||
const websites = await getUserWebsites(userId || current_user_id);
|
||||
|
||||
return ok(res, websites);
|
||||
}
|
||||
|
@ -14,9 +14,11 @@ export default function SharePage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { websiteId, token } = data;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<WebsiteDetails websiteId={data.website_id} shareId={shareId} />
|
||||
<WebsiteDetails websiteId={websiteId} token={token} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user