Merge branch 'dev' into feat/um-23-v2-schema-init

pull/1628/head
Brian Cao 2022-10-31 23:48:12 -07:00
commit 689b732829
11 changed files with 1107 additions and 892 deletions

View File

@ -2,7 +2,7 @@ import { useEffect } from 'react';
import useStore, { setConfig } from 'store/app'; import useStore, { setConfig } from 'store/app';
import useApi from 'hooks/useApi'; import useApi from 'hooks/useApi';
let fetched = false; let loading = false;
export default function useConfig() { export default function useConfig() {
const { config } = useStore(); const { config } = useStore();
@ -10,12 +10,13 @@ export default function useConfig() {
async function loadConfig() { async function loadConfig() {
const { data } = await get('/config'); const { data } = await get('/config');
loading = false;
setConfig(data); setConfig(data);
} }
useEffect(() => { useEffect(() => {
if (!config && !fetched) { if (!config && !loading) {
fetched = true; loading = true;
loadConfig(); loadConfig();
} }
}, []); }, []);

View File

@ -1,6 +1,8 @@
{ {
"label.accounts": "Konta", "label.accounts": "Konta",
"label.add-account": "Dodaj konto", "label.add-account": "Dodaj konto",
"label.add-column": "Dodaj kolumnę",
"label.add-filter": "Dodaj filtr",
"label.add-website": "Dodaj witrynę", "label.add-website": "Dodaj witrynę",
"label.administrator": "Administrator", "label.administrator": "Administrator",
"label.all": "Wszystkie", "label.all": "Wszystkie",
@ -25,6 +27,8 @@
"label.edit-account": "Edytuj konto", "label.edit-account": "Edytuj konto",
"label.edit-website": "Edytuj witrynę", "label.edit-website": "Edytuj witrynę",
"label.enable-share-url": "Włącz udostępnianie adresu URL", "label.enable-share-url": "Włącz udostępnianie adresu URL",
"label.event-data": "Dane zdarzenia",
"label.field-name": "Nazwa pola",
"label.invalid": "Nieprawidłowy", "label.invalid": "Nieprawidłowy",
"label.invalid-domain": "Nieprawidłowa witryna", "label.invalid-domain": "Nieprawidłowa witryna",
"label.language": "Język", "label.language": "Język",
@ -36,7 +40,7 @@
"label.more": "Więcej", "label.more": "Więcej",
"label.name": "Nazwa", "label.name": "Nazwa",
"label.new-password": "Nowe hasło", "label.new-password": "Nowe hasło",
"label.none": "None", "label.none": "Brak",
"label.owner": "Właściciel", "label.owner": "Właściciel",
"label.password": "Hasło", "label.password": "Hasło",
"label.passwords-dont-match": "Hasła się nie zgadzają", "label.passwords-dont-match": "Hasła się nie zgadzają",
@ -48,6 +52,7 @@
"label.reset": "Zresetuj", "label.reset": "Zresetuj",
"label.reset-website": "Zresetuj statystyki", "label.reset-website": "Zresetuj statystyki",
"label.save": "Zapisz", "label.save": "Zapisz",
"label.search": "Szukaj",
"label.settings": "Ustawienia", "label.settings": "Ustawienia",
"label.share-url": "Udostępnij adres URL", "label.share-url": "Udostępnij adres URL",
"label.single-day": "W tym dniu", "label.single-day": "W tym dniu",
@ -58,16 +63,19 @@
"label.timezone": "Strefa czasowa", "label.timezone": "Strefa czasowa",
"label.today": "Dzisiaj", "label.today": "Dzisiaj",
"label.tracking-code": "Kod śledzenia", "label.tracking-code": "Kod śledzenia",
"label.type": "Typ",
"label.unknown": "Nieznany", "label.unknown": "Nieznany",
"label.username": "Nazwa użytkownika", "label.username": "Nazwa użytkownika",
"label.value": "Wartość",
"label.view-details": "Pokaż szczegóły", "label.view-details": "Pokaż szczegóły",
"label.websites": "Witryny", "label.websites": "Witryny",
"label.yesterday": "Wczoraj",
"message.active-users": "{x} aktualnie {x, plural, one {odwiedzający} other {odwiedzających}}", "message.active-users": "{x} aktualnie {x, plural, one {odwiedzający} other {odwiedzających}}",
"message.confirm-delete": "Czy na pewno chcesz usunąć {target}?", "message.confirm-delete": "Czy na pewno chcesz usunąć {target}?",
"message.confirm-reset": "Czy na pewno chcesz zresetować statystyki {target}?", "message.confirm-reset": "Czy na pewno chcesz zresetować statystyki {target}?",
"message.copied": "Skopiowano!", "message.copied": "Skopiowano!",
"message.delete-warning": "Wszystkie powiązane dane również zostaną usunięte.", "message.delete-warning": "Wszystkie powiązane dane również zostaną usunięte.",
"message.edit-dashboard": "Edit dashboard", "message.edit-dashboard": "Edytuj panel",
"message.failure": "Coś poszło nie tak.", "message.failure": "Coś poszło nie tak.",
"message.get-share-url": "Uzyskaj adres URL udostępniania", "message.get-share-url": "Uzyskaj adres URL udostępniania",
"message.get-tracking-code": "Pobierz kod śledzenia", "message.get-tracking-code": "Pobierz kod śledzenia",
@ -103,7 +111,7 @@
"metrics.operating-systems": "System operacyjny", "metrics.operating-systems": "System operacyjny",
"metrics.page-views": "Wyświetlenia strony", "metrics.page-views": "Wyświetlenia strony",
"metrics.pages": "Strony", "metrics.pages": "Strony",
"metrics.query-parameters": "Query parameters", "metrics.query-parameters": "Parametry query",
"metrics.referrers": "Źródła odsyłające", "metrics.referrers": "Źródła odsyłające",
"metrics.screens": "Ekrany", "metrics.screens": "Ekrany",
"metrics.unique-visitors": "Unikalni odwiedzający", "metrics.unique-visitors": "Unikalni odwiedzający",

View File

@ -1,22 +1,26 @@
import { parseSecureToken, parseToken } from 'next-basics'; import { parseSecureToken, parseToken } from 'next-basics';
import { getUser, getWebsite } from 'queries'; import { getUser, getWebsite } from 'queries';
import debug from 'debug';
import { SHARE_TOKEN_HEADER, TYPE_ACCOUNT, TYPE_WEBSITE } from 'lib/constants'; import { SHARE_TOKEN_HEADER, TYPE_ACCOUNT, TYPE_WEBSITE } from 'lib/constants';
import { secret } from 'lib/crypto'; import { secret } from 'lib/crypto';
export function getAuthToken(req) { const log = debug('umami:auth');
export function parseAuthToken(req) {
try { try {
const token = req.headers.authorization; const token = req.headers.authorization;
return parseSecureToken(token.split(' ')[1], secret()); return parseSecureToken(token.split(' ')[1], secret());
} catch { } catch (e) {
log(e);
return null; return null;
} }
} }
export function getShareToken(req) { export function parseShareToken(req) {
try { try {
return parseToken(req.headers[SHARE_TOKEN_HEADER], secret()); return parseToken(req.headers[SHARE_TOKEN_HEADER], secret());
} catch { } catch (e) {
log(e);
return null; return null;
} }
} }
@ -29,6 +33,7 @@ export function isValidToken(token, validation) {
return validation(token); return validation(token);
} }
} catch (e) { } catch (e) {
log(e);
return false; return false;
} }

View File

@ -9,11 +9,11 @@ export function secret() {
export function salt() { export function salt() {
const ROTATING_SALT = hash(startOfMonth(new Date()).toUTCString()); const ROTATING_SALT = hash(startOfMonth(new Date()).toUTCString());
return hash([secret(), ROTATING_SALT]); return hash(secret(), ROTATING_SALT);
} }
export function uuid(...args) { export function uuid(...args) {
if (!args.length) return v4(); if (!args.length) return v4();
return v5(hash([...args, salt()]), v5.DNS); return v5(hash(...args, salt()), v5.DNS);
} }

View File

@ -1,7 +1,7 @@
import { createMiddleware, unauthorized, badRequest, serverError } from 'next-basics'; import { createMiddleware, unauthorized, badRequest, serverError } from 'next-basics';
import cors from 'cors'; import cors from 'cors';
import { getSession } from './session'; import { getSession } from './session';
import { getAuthToken, getShareToken } from './auth'; import { parseAuthToken, parseShareToken } from './auth';
export const useCors = createMiddleware(cors()); export const useCors = createMiddleware(cors());
@ -26,8 +26,8 @@ export const useSession = createMiddleware(async (req, res, next) => {
}); });
export const useAuth = createMiddleware(async (req, res, next) => { export const useAuth = createMiddleware(async (req, res, next) => {
const token = await getAuthToken(req); const token = await parseAuthToken(req);
const shareToken = await getShareToken(req); const shareToken = await parseShareToken(req);
if (!token && !shareToken) { if (!token && !shareToken) {
return unauthorized(res); return unauthorized(res);

View File

@ -1,6 +1,6 @@
{ {
"name": "umami", "name": "umami",
"version": "1.39.2", "version": "2.0.0-beta.1",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.", "description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Mike Cao <mike@mikecao.com>", "author": "Mike Cao <mike@mikecao.com>",
"license": "MIT", "license": "MIT",
@ -84,7 +84,7 @@
"maxmind": "^4.3.6", "maxmind": "^4.3.6",
"moment-timezone": "^0.5.35", "moment-timezone": "^0.5.35",
"next": "^12.3.1", "next": "^12.3.1",
"next-basics": "^0.18.0", "next-basics": "^0.20.0",
"node-fetch": "^3.2.8", "node-fetch": "^3.2.8",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",

View File

@ -2,34 +2,27 @@ import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';
import useLocale from 'hooks/useLocale'; import useLocale from 'hooks/useLocale';
import useConfig from 'hooks/useConfig';
import 'styles/variables.css'; import 'styles/variables.css';
import 'styles/bootstrap-grid.css'; import 'styles/bootstrap-grid.css';
import 'styles/index.css'; import 'styles/index.css';
import '@fontsource/inter/400.css'; import '@fontsource/inter/400.css';
import '@fontsource/inter/600.css'; import '@fontsource/inter/600.css';
const Intl = ({ children }) => {
const { locale, messages } = useLocale();
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
return (
<IntlProvider locale={locale} messages={messages[locale]} textComponent={Wrapper}>
{children}
</IntlProvider>
);
};
export default function App({ Component, pageProps }) { export default function App({ Component, pageProps }) {
const { locale, messages } = useLocale();
const { basePath } = useRouter(); const { basePath } = useRouter();
const { dir } = useLocale(); const { dir } = useLocale();
useConfig();
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
if (process.env.uiDisabled) { if (process.env.uiDisabled) {
return null; return null;
} }
return ( return (
<Intl> <IntlProvider locale={locale} messages={messages[locale]} textComponent={Wrapper}>
<Head> <Head>
<link rel="icon" href={`${basePath}/favicon.ico`} /> <link rel="icon" href={`${basePath}/favicon.ico`} />
<link rel="apple-touch-icon" sizes="180x180" href={`${basePath}/apple-touch-icon.png`} /> <link rel="apple-touch-icon" sizes="180x180" href={`${basePath}/apple-touch-icon.png`} />
@ -45,6 +38,6 @@ export default function App({ Component, pageProps }) {
<div className="container" dir={dir}> <div className="container" dir={dir}>
<Component {...pageProps} /> <Component {...pageProps} />
</div> </div>
</Intl> </IntlProvider>
); );
} }

View File

@ -53,6 +53,10 @@ export default async (req, res) => {
} }
if (req.method === 'DELETE') { if (req.method === 'DELETE') {
if (id === userId) {
return badRequest(res, 'You cannot delete your own user.');
}
if (!isAdmin) { if (!isAdmin) {
return unauthorized(res); return unauthorized(res);
} }

38
pages/sso.js Normal file
View File

@ -0,0 +1,38 @@
import { useEffect } from 'react';
import debug from 'debug';
import { useRouter } from 'next/router';
import { setItem } from 'next-basics';
import { AUTH_TOKEN } from 'lib/constants';
import useApi from 'hooks/useApi';
import { setUser } from 'store/app';
const log = debug('umami:sso');
export default function SingleSignOnPage() {
const router = useRouter();
const { get } = useApi();
const { token, url } = router.query;
useEffect(() => {
async function verify() {
setItem(AUTH_TOKEN, token);
const { ok, data } = await get('/auth/verify');
if (ok) {
log(data);
setUser(data);
if (url) {
await router.push(url);
}
}
}
if (token) {
verify();
}
}, [token]);
return null;
}

View File

@ -11,6 +11,18 @@
"value": "Dodaj konto" "value": "Dodaj konto"
} }
], ],
"label.add-column": [
{
"type": 0,
"value": "Dodaj kolumnę"
}
],
"label.add-filter": [
{
"type": 0,
"value": "Dodaj filtr"
}
],
"label.add-website": [ "label.add-website": [
{ {
"type": 0, "type": 0,
@ -155,6 +167,18 @@
"value": "Włącz udostępnianie adresu URL" "value": "Włącz udostępnianie adresu URL"
} }
], ],
"label.event-data": [
{
"type": 0,
"value": "Dane zdarzenia"
}
],
"label.field-name": [
{
"type": 0,
"value": "Nazwa pola"
}
],
"label.invalid": [ "label.invalid": [
{ {
"type": 0, "type": 0,
@ -244,7 +268,7 @@
"label.none": [ "label.none": [
{ {
"type": 0, "type": 0,
"value": "None" "value": "Brak"
} }
], ],
"label.owner": [ "label.owner": [
@ -313,6 +337,12 @@
"value": "Zapisz" "value": "Zapisz"
} }
], ],
"label.search": [
{
"type": 0,
"value": "Szukaj"
}
],
"label.settings": [ "label.settings": [
{ {
"type": 0, "type": 0,
@ -373,6 +403,12 @@
"value": "Kod śledzenia" "value": "Kod śledzenia"
} }
], ],
"label.type": [
{
"type": 0,
"value": "Typ"
}
],
"label.unknown": [ "label.unknown": [
{ {
"type": 0, "type": 0,
@ -385,6 +421,12 @@
"value": "Nazwa użytkownika" "value": "Nazwa użytkownika"
} }
], ],
"label.value": [
{
"type": 0,
"value": "Wartość"
}
],
"label.view-details": [ "label.view-details": [
{ {
"type": 0, "type": 0,
@ -397,6 +439,12 @@
"value": "Witryny" "value": "Witryny"
} }
], ],
"label.yesterday": [
{
"type": 0,
"value": "Wczoraj"
}
],
"message.active-users": [ "message.active-users": [
{ {
"type": 1, "type": 1,
@ -474,7 +522,7 @@
"message.edit-dashboard": [ "message.edit-dashboard": [
{ {
"type": 0, "type": 0,
"value": "Edit dashboard" "value": "Edytuj panel"
} }
], ],
"message.failure": [ "message.failure": [
@ -770,7 +818,7 @@
"metrics.query-parameters": [ "metrics.query-parameters": [
{ {
"type": 0, "type": 0,
"value": "Query parameters" "value": "Parametry query"
} }
], ],
"metrics.referrers": [ "metrics.referrers": [

1832
yarn.lock

File diff suppressed because it is too large Load Diff