Domain validation. Filter domain from referrers.

This commit is contained in:
Mike Cao 2020-08-28 21:34:20 -07:00
parent a9765c7ea7
commit 5a4cde854a
13 changed files with 51 additions and 23 deletions

View File

@ -65,9 +65,9 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
const tableProps = { const tableProps = {
...dataProps, ...dataProps,
websiteDomain: data?.domain,
limit: 10, limit: 10,
onExpand: handleExpand, onExpand: handleExpand,
websiteDomain: data?.domain,
}; };
const DetailsComponent = expand?.component; const DetailsComponent = expand?.component;

View File

@ -8,7 +8,8 @@ import FormLayout, {
FormMessage, FormMessage,
FormRow, FormRow,
} from 'components/layout/FormLayout'; } from 'components/layout/FormLayout';
import Checkbox from '../common/Checkbox'; import Checkbox from 'components/common/Checkbox';
import { DOMAIN_REGEX } from 'lib/constants';
const initialValues = { const initialValues = {
name: '', name: '',
@ -24,6 +25,8 @@ const validate = ({ name, domain }) => {
} }
if (!domain) { if (!domain) {
errors.domain = 'Required'; errors.domain = 'Required';
} else if (!DOMAIN_REGEX.test(domain)) {
errors.domain = 'Invalid domain';
} }
return errors; return errors;

View File

@ -14,6 +14,7 @@ export default function MetricsTable({
title, title,
metric, metric,
websiteId, websiteId,
websiteDomain,
startDate, startDate,
endDate, endDate,
type, type,
@ -47,6 +48,7 @@ export default function MetricsTable({
type, type,
start_at: +startDate, start_at: +startDate,
end_at: +endDate, end_at: +endDate,
domain: websiteDomain,
}); });
setData(data); setData(data);

View File

@ -30,6 +30,7 @@ export default function Referrers({
metric="Views" metric="Views"
headerComponent={limit ? null : <FilterButtons selected={filter} onClick={setFilter} />} headerComponent={limit ? null : <FilterButtons selected={filter} onClick={setFilter} />}
websiteId={websiteId} websiteId={websiteId}
websiteDomain={websiteDomain}
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
limit={limit} limit={limit}

View File

@ -1,5 +1,7 @@
export const AUTH_COOKIE_NAME = 'umami.auth'; export const AUTH_COOKIE_NAME = 'umami.auth';
export const DOMAIN_REGEX = /(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/;
export const DESKTOP_SCREEN_WIDTH = 1920; export const DESKTOP_SCREEN_WIDTH = 1920;
export const LAPTOP_SCREEN_WIDTH = 1024; export const LAPTOP_SCREEN_WIDTH = 1024;
export const MOBILE_SCREEN_WIDTH = 479; export const MOBILE_SCREEN_WIDTH = 479;

View File

@ -98,12 +98,6 @@ export function getDateArray(data, startDate, endDate, unit) {
function findData(t) { function findData(t) {
const x = data.find(e => { const x = data.find(e => {
console.log(
new Date(e.t),
getLocalTime(new Date(e.t)),
getLocalTime(new Date(e.t)).getTime(),
normalize(new Date(t)).getTime(),
);
return getLocalTime(new Date(e.t)).getTime() === normalize(new Date(t)).getTime(); return getLocalTime(new Date(e.t)).getTime() === normalize(new Date(t)).getTime();
}); });

View File

@ -1,8 +1,8 @@
import firstBy from 'thenby'; import firstBy from 'thenby';
import { BROWSERS, ISO_COUNTRIES, DEVICES } from './constants'; import { BROWSERS, ISO_COUNTRIES, DEVICES } from './constants';
import { removeTrailingSlash } from './format'; import { removeTrailingSlash, getDomainName } from './url';
export const urlFilter = (data, { domain, raw }) => { export const urlFilter = (data, { raw }) => {
const isValidUrl = url => { const isValidUrl = url => {
return url !== '' && !url.startsWith('#'); return url !== '' && !url.startsWith('#');
}; };
@ -30,7 +30,7 @@ export const urlFilter = (data, { domain, raw }) => {
return obj; return obj;
} }
const url = cleanUrl(x.startsWith('/') ? `http://${domain}${x}` : x); const url = cleanUrl(`http://x${x}`);
if (url) { if (url) {
if (!obj[url]) { if (!obj[url]) {
@ -49,7 +49,8 @@ export const urlFilter = (data, { domain, raw }) => {
}; };
export const refFilter = (data, { domain, domainOnly, raw }) => { export const refFilter = (data, { domain, domainOnly, raw }) => {
const regex = new RegExp(domain.startsWith('http') ? domain : `http[s]?://${domain}`); const domainName = getDomainName(domain);
const regex = new RegExp(`http[s]?://${domainName}`);
const isValidRef = ref => { const isValidRef = ref => {
return ref !== '' && !ref.startsWith('/') && !ref.startsWith('#'); return ref !== '' && !ref.startsWith('/') && !ref.startsWith('#');
@ -63,7 +64,7 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
try { try {
const { hostname, origin, pathname, searchParams, protocol } = new URL(url); const { hostname, origin, pathname, searchParams, protocol } = new URL(url);
if (hostname === domain || regex.test(url)) { if (hostname === domainName) {
return null; return null;
} }

View File

@ -62,7 +62,3 @@ export function formatLongNumber(value) {
return formatNumber(n); return formatNumber(n);
} }
export function removeTrailingSlash(url) {
return url.length > 1 && url.endsWith('/') ? url.slice(0, -1) : url;
}

View File

@ -347,9 +347,11 @@ export function getPageviews(
return Promise.resolve([]); return Promise.resolve([]);
} }
export function getRankings(website_id, start_at, end_at, type, table) { export function getRankings(website_id, start_at, end_at, type, table, domain) {
const db = getDatabase(); const db = getDatabase();
const filter = domain ? `and ${type} not like '%${domain}%'` : '';
if (db === POSTGRESQL) { if (db === POSTGRESQL) {
return prisma.$queryRaw( return prisma.$queryRaw(
` `
@ -357,6 +359,7 @@ export function getRankings(website_id, start_at, end_at, type, table) {
from ${table} from ${table}
where website_id=$1 where website_id=$1
and created_at between $2 and $3 and created_at between $2 and $3
${filter}
group by 1 group by 1
order by 2 desc order by 2 desc
`, `,
@ -373,6 +376,7 @@ export function getRankings(website_id, start_at, end_at, type, table) {
from ${table} from ${table}
where website_id=? where website_id=?
and created_at between ? and ? and created_at between ? and ?
${filter}
group by 1 group by 1
order by 2 desc order by 2 desc
`, `,

11
lib/url.js Normal file
View File

@ -0,0 +1,11 @@
export function removeTrailingSlash(url) {
return url.length > 1 && url.endsWith('/') ? url.slice(0, -1) : url;
}
export function getDomainName(str) {
try {
return new URL(str).hostname;
} catch {
return str;
}
}

View File

@ -1,6 +1,6 @@
{ {
"name": "umami", "name": "umami",
"version": "0.17.0", "version": "0.18.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ", "description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao <mike@mikecao.com>", "author": "Mike Cao <mike@mikecao.com>",
"license": "MIT", "license": "MIT",
@ -25,11 +25,13 @@
}, },
"lint-staged": { "lint-staged": {
"**/*.js": [ "**/*.js": [
"prettier --write" "prettier --write",
"eslint"
], ],
"**/*.css": [ "**/*.css": [
"stylelint --fix", "stylelint --fix",
"prettier --write" "prettier --write",
"eslint"
] ]
}, },
"husky": { "husky": {

View File

@ -1,5 +1,6 @@
import { getRankings } from 'lib/queries'; import { getRankings } from 'lib/queries';
import { ok, badRequest } from 'lib/response'; import { ok, badRequest } from 'lib/response';
import { DOMAIN_REGEX } from '../../../../lib/constants';
const sessionColumns = ['browser', 'os', 'device', 'country']; const sessionColumns = ['browser', 'os', 'device', 'country'];
const pageviewColumns = ['url', 'referrer']; const pageviewColumns = ['url', 'referrer'];
@ -24,12 +25,18 @@ function getColumn(type) {
} }
export default async (req, res) => { export default async (req, res) => {
const { id, type, start_at, end_at } = req.query; const { id, type, start_at, end_at, domain } = req.query;
const websiteId = +id; const websiteId = +id;
const startDate = new Date(+start_at); const startDate = new Date(+start_at);
const endDate = new Date(+end_at); const endDate = new Date(+end_at);
if (type !== 'event' && !sessionColumns.includes(type) && !pageviewColumns.includes(type)) { if (
type !== 'event' &&
!sessionColumns.includes(type) &&
!pageviewColumns.includes(type) &&
domain &&
DOMAIN_REGEX.test(domain)
) {
return badRequest(res); return badRequest(res);
} }
@ -39,6 +46,7 @@ export default async (req, res) => {
endDate, endDate,
getColumn(type), getColumn(type),
getTable(type), getTable(type),
domain,
); );
return ok(res, rankings); return ok(res, rankings);

View File

@ -73,6 +73,10 @@ create index pageview_session_id_idx on pageview(session_id);
create index pageview_website_id_created_at_idx on pageview(website_id, created_at); create index pageview_website_id_created_at_idx on pageview(website_id, created_at);
create index pageview_website_id_session_id_created_at_idx on pageview(website_id, session_id, created_at); create index pageview_website_id_session_id_created_at_idx on pageview(website_id, session_id, created_at);
-- test
create index pageview_created_at_session_id_website_id_idx on pageview(created_at, session_id, website_id);
create index pageview_created_at_website_id_session_id_idx on pageview(created_at, website_id, session_id);
create index event_created_at_idx on event(created_at); create index event_created_at_idx on event(created_at);
create index event_website_id_idx on event(website_id); create index event_website_id_idx on event(website_id);
create index event_session_id_idx on event(session_id); create index event_session_id_idx on event(session_id);