mirror of
https://github.com/kremalicious/umami.git
synced 2024-11-22 01:46:58 +01:00
Auth and session middleware.
This commit is contained in:
parent
590a70c2ff
commit
d81ee3932d
36
components/Chart.js
vendored
36
components/Chart.js
vendored
@ -1,7 +1,7 @@
|
||||
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import ChartJS from 'chart.js';
|
||||
import { format, subDays, subHours, startOfHour } from 'date-fns';
|
||||
import { get } from 'lib/web';
|
||||
import { getTimezone, getLocalTime } from 'lib/date';
|
||||
|
||||
export default function Chart({ websiteId, startDate, endDate }) {
|
||||
const [data, setData] = useState();
|
||||
@ -9,21 +9,7 @@ export default function Chart({ websiteId, startDate, endDate }) {
|
||||
const chart = useRef();
|
||||
const metrics = useMemo(() => {
|
||||
if (data) {
|
||||
const points = {};
|
||||
const now = startOfHour(new Date());
|
||||
|
||||
for (let i = 0; i <= 168; i++) {
|
||||
const d = new Date(subHours(now, 168 - i));
|
||||
const key = format(d, 'yyyy-MM-dd-HH');
|
||||
points[key] = { t: startOfHour(d).toISOString(), y: 0 };
|
||||
}
|
||||
|
||||
data.pageviews.forEach(e => {
|
||||
const key = format(new Date(e.created_at), 'yyyy-MM-dd-HH');
|
||||
points[key].y += 1;
|
||||
});
|
||||
|
||||
return points;
|
||||
return data.pageviews.map(({ t, y }) => ({ t: getLocalTime(t), y }));
|
||||
}
|
||||
}, [data]);
|
||||
console.log(metrics);
|
||||
@ -31,8 +17,9 @@ export default function Chart({ websiteId, startDate, endDate }) {
|
||||
async function loadData() {
|
||||
setData(
|
||||
await get(`/api/website/${websiteId}/pageviews`, {
|
||||
start_at: startDate,
|
||||
end_at: endDate,
|
||||
start_at: +startDate,
|
||||
end_at: +endDate,
|
||||
tz: getTimezone(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -47,6 +34,9 @@ export default function Chart({ websiteId, startDate, endDate }) {
|
||||
label: 'page views',
|
||||
data: Object.values(metrics),
|
||||
lineTension: 0,
|
||||
backgroundColor: 'rgb(38, 128, 235, 0.1)',
|
||||
borderColor: 'rgb(13, 102, 208, 0.2)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -65,19 +55,13 @@ export default function Chart({ websiteId, startDate, endDate }) {
|
||||
{
|
||||
type: 'time',
|
||||
distribution: 'series',
|
||||
offset: true,
|
||||
time: {
|
||||
unit: 'hour',
|
||||
displayFormats: {
|
||||
hour: 'ddd M/DD',
|
||||
day: 'ddd M/DD',
|
||||
},
|
||||
tooltipFormat: 'ddd M/DD hA',
|
||||
},
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
minRotation: 0,
|
||||
maxRotation: 0,
|
||||
maxTicksLimit: 7,
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
|
8
lib/auth.js
Normal file
8
lib/auth.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { parse } from 'cookie';
|
||||
import { verifySecureToken } from './crypto';
|
||||
|
||||
export default async req => {
|
||||
const token = parse(req.headers.cookie)['umami.auth'];
|
||||
|
||||
return verifySecureToken(token);
|
||||
};
|
11
lib/date.js
Normal file
11
lib/date.js
Normal file
@ -0,0 +1,11 @@
|
||||
import moment from 'moment-timezone';
|
||||
import { addMinutes } from 'date-fns';
|
||||
|
||||
export function getTimezone() {
|
||||
const tz = moment.tz.guess();
|
||||
return moment.tz.zone(tz).abbr(new Date().getTimezoneOffset());
|
||||
}
|
||||
|
||||
export function getLocalTime(t) {
|
||||
return addMinutes(new Date(t), new Date().getTimezoneOffset());
|
||||
}
|
36
lib/db.js
36
lib/db.js
@ -32,6 +32,16 @@ export async function getWebsite(website_uuid) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getWebsites(user_id) {
|
||||
return runQuery(
|
||||
prisma.website.findMany({
|
||||
where: {
|
||||
user_id,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function createSession(website_id, data) {
|
||||
return runQuery(
|
||||
prisma.session.create({
|
||||
@ -126,3 +136,29 @@ export async function getPageviews(website_id, start_at, end_at) {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function getPageviewData(
|
||||
website_id,
|
||||
start_at,
|
||||
end_at,
|
||||
timezone = 'utc',
|
||||
unit = 'day',
|
||||
count = '*',
|
||||
) {
|
||||
return runQuery(
|
||||
prisma.queryRaw(
|
||||
`
|
||||
select date_trunc('${unit}', created_at at time zone '${timezone}') t,
|
||||
count(${count}) y
|
||||
from pageview
|
||||
where website_id=$1
|
||||
and created_at between $2 and $3
|
||||
group by 1
|
||||
order by 1
|
||||
`,
|
||||
website_id,
|
||||
start_at,
|
||||
end_at,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import cors from 'cors';
|
||||
import session from './session';
|
||||
import auth from './auth';
|
||||
|
||||
export function use(middleware) {
|
||||
return (req, res) =>
|
||||
@ -13,3 +15,21 @@ export function use(middleware) {
|
||||
}
|
||||
|
||||
export const useCors = use(cors());
|
||||
|
||||
export const useSession = use(async (req, res, next) => {
|
||||
try {
|
||||
req.session = await session(req);
|
||||
} catch {
|
||||
return res.status(400).end();
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
export const useAuth = use(async (req, res, next) => {
|
||||
try {
|
||||
req.auth = await auth(req);
|
||||
} catch {
|
||||
return res.status(401).end();
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { getWebsite, getSession, createSession } from 'lib/db';
|
||||
import { getCountry, getDevice, getIpAddress } from 'lib/utils';
|
||||
import { getCountry, getDevice, getIpAddress } from 'lib/request';
|
||||
import { uuid, isValidId, verifyToken } from 'lib/crypto';
|
||||
|
||||
export default async req => {
|
||||
@ -46,6 +46,8 @@ export default async req => {
|
||||
session_id,
|
||||
session_uuid,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Invalid website: ${website_uuid}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,8 +53,8 @@
|
||||
"is-localhost-ip": "^1.4.0",
|
||||
"jose": "^1.27.2",
|
||||
"maxmind": "^4.1.3",
|
||||
"moment-timezone": "^0.5.31",
|
||||
"next": "9.3.5",
|
||||
"next-cookies": "^2.0.3",
|
||||
"node-fetch": "^2.6.0",
|
||||
"promise-polyfill": "^8.1.3",
|
||||
"react": "16.13.1",
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { savePageView, saveEvent } from 'lib/db';
|
||||
import { useCors } from 'lib/middleware';
|
||||
import checkSession from 'lib/session';
|
||||
import { useCors, useSession } from 'lib/middleware';
|
||||
import { createToken } from 'lib/crypto';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useCors(req, res);
|
||||
await useSession(req, res);
|
||||
|
||||
const session = await checkSession(req);
|
||||
|
||||
const { session } = req;
|
||||
const token = await createToken(session);
|
||||
const { website_id, session_id } = session;
|
||||
const { type, payload } = req.body;
|
||||
|
12
pages/api/website.js
Normal file
12
pages/api/website.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { getWebsites } from 'lib/db';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
||||
const { user_id } = req.auth;
|
||||
|
||||
const websites = await getWebsites(user_id);
|
||||
|
||||
res.status(200).json({ websites });
|
||||
};
|
@ -1,10 +1,15 @@
|
||||
import { getPageviews } from 'lib/db';
|
||||
import { getPageviewData } from 'lib/db';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
|
||||
export default async (req, res) => {
|
||||
console.log(req.query);
|
||||
const { id, start_at, end_at } = req.query;
|
||||
await useAuth(req, res);
|
||||
|
||||
const pageviews = await getPageviews(+id, new Date(+start_at), new Date(+end_at));
|
||||
const { id, start_at, end_at, tz } = req.query;
|
||||
|
||||
res.status(200).json({ pageviews });
|
||||
const [pageviews, uniques] = await Promise.all([
|
||||
getPageviewData(+id, new Date(+start_at), new Date(+end_at), tz, 'day', '*'),
|
||||
getPageviewData(+id, new Date(+start_at), new Date(+end_at), tz, 'day', 'distinct session_id'),
|
||||
]);
|
||||
|
||||
res.status(200).json({ pageviews, uniques });
|
||||
};
|
||||
|
13
pages/api/website/[id]/summary.js
Normal file
13
pages/api/website/[id]/summary.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { getPageviews } from 'lib/db';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
|
||||
export default async (req, res) => {
|
||||
await useAuth(req, res);
|
||||
|
||||
console.log(req.query);
|
||||
const { id, start_at, end_at } = req.query;
|
||||
|
||||
const pageviews = await getPageviews(+id, new Date(+start_at), new Date(+end_at));
|
||||
|
||||
res.status(200).json({ pageviews });
|
||||
};
|
@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import cookies from 'next-cookies';
|
||||
import { parse } from 'cookie';
|
||||
import Layout from 'components/Layout';
|
||||
import Chart from 'components/Chart';
|
||||
import { verifySecureToken } from 'lib/crypto';
|
||||
import { subDays, endOfDay } from 'date-fns';
|
||||
|
||||
export default function HomePage({ username }) {
|
||||
return (
|
||||
@ -14,8 +15,8 @@ export default function HomePage({ username }) {
|
||||
<div>
|
||||
<Chart
|
||||
websiteId={3}
|
||||
startDate={Date.now() - 1000 * 60 * 60 * 24 * 7}
|
||||
endDate={Date.now()}
|
||||
startDate={subDays(endOfDay(new Date()), 6)}
|
||||
endDate={endOfDay(new Date())}
|
||||
/>
|
||||
</div>
|
||||
<Link href="/logout">
|
||||
@ -25,8 +26,8 @@ export default function HomePage({ username }) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const token = cookies(context)['umami.auth'];
|
||||
export async function getServerSideProps({ req, res }) {
|
||||
const token = parse(req.headers.cookie)['umami.auth'];
|
||||
|
||||
try {
|
||||
const payload = await verifySecureToken(token);
|
||||
@ -37,8 +38,6 @@ export async function getServerSideProps(context) {
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
const { res } = context;
|
||||
|
||||
res.statusCode = 303;
|
||||
res.setHeader('Location', '/login');
|
||||
res.end();
|
||||
|
38
yarn.lock
38
yarn.lock
@ -1374,11 +1374,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
|
||||
|
||||
"@types/cookie@^0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803"
|
||||
integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
@ -1414,11 +1409,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
|
||||
|
||||
"@types/object-assign@^4.0.30":
|
||||
version "4.0.30"
|
||||
resolved "https://registry.yarnpkg.com/@types/object-assign/-/object-assign-4.0.30.tgz#8949371d5a99f4381ee0f1df0a9b7a187e07e652"
|
||||
integrity sha1-iUk3HVqZ9Dge4PHfCpt6GH4H5lI=
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
@ -2662,7 +2652,7 @@ convert-source-map@^0.3.3:
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190"
|
||||
integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA=
|
||||
|
||||
cookie@^0.4.0, cookie@^0.4.1:
|
||||
cookie@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
|
||||
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
|
||||
@ -5511,7 +5501,14 @@ mkdirp@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
moment@^2.10.2:
|
||||
moment-timezone@^0.5.31:
|
||||
version "0.5.31"
|
||||
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05"
|
||||
integrity sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==
|
||||
dependencies:
|
||||
moment ">= 2.9.0"
|
||||
|
||||
"moment@>= 2.9.0", moment@^2.10.2:
|
||||
version "2.27.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d"
|
||||
integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==
|
||||
@ -5596,13 +5593,6 @@ neo-async@^2.5.0, neo-async@^2.6.1:
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
||||
next-cookies@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/next-cookies/-/next-cookies-2.0.3.tgz#5a3eabcb6afa9b4d4ade69dfaaad749d16cd4a9a"
|
||||
integrity sha512-YVCQzwZx+sz+KqLO4y9niHH9jjz6jajlEQbAKfsYVT6DOfngb/0k5l6vFK4rmpExVug96pGag8OBsdSRL9FZhQ==
|
||||
dependencies:
|
||||
universal-cookie "^4.0.2"
|
||||
|
||||
next-tick@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
|
||||
@ -8719,16 +8709,6 @@ unist-util-visit@^2.0.0:
|
||||
unist-util-is "^4.0.0"
|
||||
unist-util-visit-parents "^3.0.0"
|
||||
|
||||
universal-cookie@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-4.0.3.tgz#c2fa59127260e6ad21ef3e0cdd66ad453cbc41f6"
|
||||
integrity sha512-YbEHRs7bYOBTIWedTR9koVEe2mXrq+xdjTJZcoKJK/pQaE6ni28ak2AKXFpevb+X6w3iU5SXzWDiJkmpDRb9qw==
|
||||
dependencies:
|
||||
"@types/cookie" "^0.3.3"
|
||||
"@types/object-assign" "^4.0.30"
|
||||
cookie "^0.4.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
unquote@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
|
||||
|
Loading…
Reference in New Issue
Block a user