From f4e0da481e0f23d782f76cba2e73b344b254584d Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 28 Aug 2022 20:20:54 -0700 Subject: [PATCH] Use next-basics package. --- .eslintrc.json | 27 +++++- components/common/FilterLink.js | 8 +- components/common/UpdateNotice.js | 2 +- components/forms/LoginForm.js | 2 +- components/layout/Header.js | 2 +- components/metrics/FilterTags.js | 2 +- components/metrics/QueryParametersTable.js | 6 +- components/pages/DashboardEdit.js | 2 - components/pages/TestConsole.js | 2 - components/pages/WebsiteList.js | 2 - components/settings/UserButton.js | 2 +- hooks/useApi.js | 2 +- hooks/useCountryNames.js | 2 +- hooks/useDateRange.js | 2 +- hooks/useFetch.js | 8 +- hooks/useLanguageNames.js | 2 +- hooks/useLocale.js | 2 +- hooks/usePageQuery.js | 5 +- hooks/useTheme.js | 2 +- hooks/useTimezone.js | 2 +- lib/auth.js | 7 +- lib/clickhouse.js | 29 +++--- lib/crypto.js | 65 +------------ lib/db.js | 1 + lib/filters.js | 10 +- lib/kafka.js | 13 ++- lib/middleware.js | 16 +-- lib/prisma.js | 5 +- lib/redis.js | 19 ++-- lib/response.js | 43 --------- lib/security.js | 8 ++ lib/session.js | 10 +- lib/url.js | 35 ------- lib/web.js | 78 --------------- package.json | 5 +- pages/api/account/[id].js | 2 +- pages/api/account/index.js | 3 +- pages/api/account/password.js | 10 +- pages/api/accounts/index.js | 2 +- pages/api/auth/login.js | 8 +- pages/api/auth/verify.js | 2 +- pages/api/collect.js | 8 +- pages/api/config.js | 2 +- pages/api/heartbeat.js | 2 +- pages/api/realtime/init.js | 6 +- pages/api/realtime/update.js | 6 +- pages/api/share/[id].js | 6 +- pages/api/website/[id]/active.js | 2 +- pages/api/website/[id]/events.js | 2 +- pages/api/website/[id]/index.js | 2 +- pages/api/website/[id]/metrics.js | 2 +- pages/api/website/[id]/pageviews.js | 2 +- pages/api/website/[id]/reset.js | 2 +- pages/api/website/[id]/stats.js | 2 +- pages/api/website/index.js | 4 +- pages/api/websites/index.js | 2 +- pages/logout.js | 2 +- scripts/change-password.js | 8 +- store/app.js | 2 +- store/dashboard.js | 2 +- store/version.js | 2 +- yarn.lock | 107 ++++++++++++++++++--- 62 files changed, 255 insertions(+), 373 deletions(-) delete mode 100644 lib/response.js create mode 100644 lib/security.js delete mode 100644 lib/url.js delete mode 100644 lib/web.js diff --git a/.eslintrc.json b/.eslintrc.json index 215d6491..6ea8791e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,12 @@ "es2020": true, "node": true }, - "extends": ["eslint:recommended", "plugin:prettier/recommended", "next"], + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended", + "plugin:import/recommended", + "next" + ], "parserOptions": { "ecmaFeatures": { "jsx": true @@ -12,7 +17,27 @@ "ecmaVersion": 11, "sourceType": "module" }, + "settings": { + "import/resolver": { + "alias": { + "map": [ + ["assets", "./assets"], + ["components", "./components"], + ["db", "./db"], + ["hooks", "./hooks"], + ["lang", "./lang"], + ["lib", "./lib"], + ["public", "./public"], + ["queries", "./queries"], + ["store", "./store"], + ["styles", "./styles"] + ], + "extensions": [".ts", ".js", ".jsx", ".json"] + } + } + }, "rules": { + "no-console": "error", "react/display-name": "off", "react/react-in-jsx-scope": "off", "react/prop-types": "off", diff --git a/components/common/FilterLink.js b/components/common/FilterLink.js index 459a8ae1..f16258f1 100644 --- a/components/common/FilterLink.js +++ b/components/common/FilterLink.js @@ -1,10 +1,10 @@ import React from 'react'; -import Link from 'next/link'; import classNames from 'classnames'; +import Link from 'next/link'; +import { safeDecodeURI } from 'next-basics'; import usePageQuery from 'hooks/usePageQuery'; -import { safeDecodeURI } from 'lib/url'; -import Icon from './Icon'; import External from 'assets/arrow-up-right-from-square.svg'; +import Icon from './Icon'; import styles from './FilterLink.module.css'; export default function FilterLink({ id, value, label, externalUrl }) { @@ -25,7 +25,7 @@ export default function FilterLink({ id, value, label, externalUrl }) { {externalUrl && ( - + } className={styles.icon} /> )} diff --git a/components/common/UpdateNotice.js b/components/common/UpdateNotice.js index 8bba1694..7419abe8 100644 --- a/components/common/UpdateNotice.js +++ b/components/common/UpdateNotice.js @@ -1,8 +1,8 @@ import { useState, useEffect, useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; +import { setItem } from 'next-basics'; import ButtonLayout from 'components/layout/ButtonLayout'; import useStore, { checkVersion } from 'store/version'; -import { setItem } from 'lib/web'; import { REPO_URL, VERSION_CHECK } from 'lib/constants'; import Button from './Button'; import styles from './UpdateNotice.module.css'; diff --git a/components/forms/LoginForm.js b/components/forms/LoginForm.js index 3c131558..8c8aa09e 100644 --- a/components/forms/LoginForm.js +++ b/components/forms/LoginForm.js @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { Formik, Form, Field } from 'formik'; +import { setItem } from 'next-basics'; import { useRouter } from 'next/router'; import Button from 'components/common/Button'; import FormLayout, { @@ -11,7 +12,6 @@ import FormLayout, { } from 'components/layout/FormLayout'; import Icon from 'components/common/Icon'; import useApi from 'hooks/useApi'; -import { setItem } from 'lib/web'; import { AUTH_TOKEN } from 'lib/constants'; import { setUser } from 'store/app'; import Logo from 'assets/logo.svg'; diff --git a/components/layout/Header.js b/components/layout/Header.js index c6942ef5..fe2043e1 100644 --- a/components/layout/Header.js +++ b/components/layout/Header.js @@ -9,7 +9,7 @@ import HamburgerButton from 'components/common/HamburgerButton'; import UpdateNotice from 'components/common/UpdateNotice'; import UserButton from 'components/settings/UserButton'; import { HOMEPAGE_URL } from 'lib/constants'; -import useConfig from '/hooks/useConfig'; +import useConfig from 'hooks/useConfig'; import useUser from 'hooks/useUser'; import Logo from 'assets/logo.svg'; import styles from './Header.module.css'; diff --git a/components/metrics/FilterTags.js b/components/metrics/FilterTags.js index 5b8ed63c..be65540e 100644 --- a/components/metrics/FilterTags.js +++ b/components/metrics/FilterTags.js @@ -1,8 +1,8 @@ import React from 'react'; import classNames from 'classnames'; +import { safeDecodeURI } from 'next-basics'; import Button from 'components/common/Button'; import Times from 'assets/times.svg'; -import { safeDecodeURI } from 'lib/url'; import styles from './FilterTags.module.css'; export default function FilterTags({ params, onClick }) { diff --git a/components/metrics/QueryParametersTable.js b/components/metrics/QueryParametersTable.js index 2d92ad92..6743e6c6 100644 --- a/components/metrics/QueryParametersTable.js +++ b/components/metrics/QueryParametersTable.js @@ -1,10 +1,10 @@ import { useState } from 'react'; import { useIntl, defineMessages } from 'react-intl'; -import MetricsTable from './MetricsTable'; +import { safeDecodeURI } from 'next-basics'; import Tag from 'components/common/Tag'; +import FilterButtons from 'components/common/FilterButtons'; import { paramFilter } from 'lib/filters'; -import { safeDecodeURI } from 'lib/url'; -import FilterButtons from '../common/FilterButtons'; +import MetricsTable from './MetricsTable'; const FILTER_COMBINED = 0; const FILTER_RAW = 1; diff --git a/components/pages/DashboardEdit.js b/components/pages/DashboardEdit.js index d06f32f9..d231a8fb 100644 --- a/components/pages/DashboardEdit.js +++ b/components/pages/DashboardEdit.js @@ -23,8 +23,6 @@ export default function DashboardEdit({ websites }) { const ordered = useMemo(() => sortArrayByMap(websites, order, 'website_id'), [websites, order]); - console.log({ order, ordered }); - function handleWebsiteDrag({ destination, source }) { if (!destination || destination.index === source.index) return; diff --git a/components/pages/TestConsole.js b/components/pages/TestConsole.js index e4fa5a52..efeb0264 100644 --- a/components/pages/TestConsole.js +++ b/components/pages/TestConsole.js @@ -28,8 +28,6 @@ export default function TestConsole() { const website = data.find(({ website_id }) => website_id === +websiteId); const selectedValue = options.find(({ value }) => value === website?.website_id)?.value; - console.log({ websiteId, data, options, website }); - function handleSelect(value) { router.push(`/console/${value}`); } diff --git a/components/pages/WebsiteList.js b/components/pages/WebsiteList.js index 4de8d05d..2c19c167 100644 --- a/components/pages/WebsiteList.js +++ b/components/pages/WebsiteList.js @@ -24,8 +24,6 @@ export default function WebsiteList({ websites, showCharts, limit }) { const { websiteOrder } = useDashboard(); const { formatMessage } = useIntl(); - console.log({ websiteOrder }); - const ordered = useMemo( () => sortArrayByMap(websites, websiteOrder, 'website_id'), [websites, websiteOrder], diff --git a/components/settings/UserButton.js b/components/settings/UserButton.js index cf11eee6..8e0ee350 100644 --- a/components/settings/UserButton.js +++ b/components/settings/UserButton.js @@ -1,11 +1,11 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import { useRouter } from 'next/router'; +import { removeItem } from 'next-basics'; import MenuButton from 'components/common/MenuButton'; import Icon from 'components/common/Icon'; import User from 'assets/user.svg'; import styles from './UserButton.module.css'; -import { removeItem } from 'lib/web'; import { AUTH_TOKEN } from 'lib/constants'; import useUser from 'hooks/useUser'; diff --git a/hooks/useApi.js b/hooks/useApi.js index 63e88a46..662a1da9 100644 --- a/hooks/useApi.js +++ b/hooks/useApi.js @@ -1,6 +1,6 @@ import { useCallback } from 'react'; import { useRouter } from 'next/router'; -import { get, post, put, del, getItem } from 'lib/web'; +import { get, post, put, del, getItem } from 'next-basics'; import { AUTH_TOKEN, SHARE_TOKEN_HEADER } from 'lib/constants'; import useStore from 'store/app'; diff --git a/hooks/useCountryNames.js b/hooks/useCountryNames.js index 6dce6cb7..7c15779f 100644 --- a/hooks/useCountryNames.js +++ b/hooks/useCountryNames.js @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; -import { get } from 'lib/web'; +import { get } from 'next-basics'; import enUS from 'public/intl/country/en-US.json'; const countryNames = { diff --git a/hooks/useDateRange.js b/hooks/useDateRange.js index 81fc6846..be42f7d3 100644 --- a/hooks/useDateRange.js +++ b/hooks/useDateRange.js @@ -1,7 +1,7 @@ import { useCallback, useMemo } from 'react'; import { parseISO } from 'date-fns'; import { getDateRange } from 'lib/date'; -import { getItem, setItem } from 'lib/web'; +import { getItem, setItem } from 'next-basics'; import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE } from 'lib/constants'; import useForceUpdate from './useForceUpdate'; import useLocale from './useLocale'; diff --git a/hooks/useFetch.js b/hooks/useFetch.js index 5b824d1b..bf50b917 100644 --- a/hooks/useFetch.js +++ b/hooks/useFetch.js @@ -8,7 +8,7 @@ export default function useFetch(url, options = {}, update = []) { const [loading, setLoading] = useState(false); const [count, setCount] = useState(0); const { get } = useApi(); - const { params = {}, headers = {}, disabled, delay = 0, interval, onDataLoad } = options; + const { params = {}, headers = {}, disabled = false, delay = 0, interval, onDataLoad } = options; async function loadData(params) { try { @@ -29,7 +29,9 @@ export default function useFetch(url, options = {}, update = []) { onDataLoad?.(data); } catch (e) { + // eslint-disable-next-line no-console console.error(e); + setError(e); } finally { setLoading(false); @@ -44,7 +46,7 @@ export default function useFetch(url, options = {}, update = []) { clearTimeout(id); }; } - }, [url, !!disabled, count, ...update]); + }, [url, disabled, count, ...update]); useEffect(() => { if (interval && !disabled) { @@ -54,7 +56,7 @@ export default function useFetch(url, options = {}, update = []) { clearInterval(id); }; } - }, [interval, !!disabled]); + }, [interval, disabled]); return { ...response, error, loading }; } diff --git a/hooks/useLanguageNames.js b/hooks/useLanguageNames.js index 2fc313f2..86a358ac 100644 --- a/hooks/useLanguageNames.js +++ b/hooks/useLanguageNames.js @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; -import { get } from 'lib/web'; +import { get } from 'next-basics'; import enUS from 'public/intl/language/en-US.json'; const languageNames = { diff --git a/hooks/useLocale.js b/hooks/useLocale.js index 339ff4ba..06c3bc6b 100644 --- a/hooks/useLocale.js +++ b/hooks/useLocale.js @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { useRouter } from 'next/router'; -import { get, setItem } from 'lib/web'; +import { get, setItem } from 'next-basics'; import { LOCALE_CONFIG } from 'lib/constants'; import { getDateLocale, getTextDirection } from 'lib/lang'; import useStore, { setLocale } from 'store/app'; diff --git a/hooks/usePageQuery.js b/hooks/usePageQuery.js index 7cce756d..1884f12e 100644 --- a/hooks/usePageQuery.js +++ b/hooks/usePageQuery.js @@ -1,6 +1,9 @@ import { useMemo } from 'react'; import { useRouter } from 'next/router'; -import { getQueryString } from 'lib/url'; + +function getQueryString(params) { + return new URLSearchParams({ ...params }).toString(); +} export default function usePageQuery() { const router = useRouter(); diff --git a/hooks/useTheme.js b/hooks/useTheme.js index 47ac31dd..5c21bf1c 100644 --- a/hooks/useTheme.js +++ b/hooks/useTheme.js @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import useStore, { setTheme } from 'store/app'; -import { getItem, setItem } from 'lib/web'; +import { getItem, setItem } from 'next-basics'; import { THEME_CONFIG } from 'lib/constants'; const selector = state => state.theme; diff --git a/hooks/useTimezone.js b/hooks/useTimezone.js index 5032a6de..8eb5d5f8 100644 --- a/hooks/useTimezone.js +++ b/hooks/useTimezone.js @@ -1,6 +1,6 @@ import { useState, useCallback } from 'react'; import { getTimezone } from 'lib/date'; -import { getItem, setItem } from 'lib/web'; +import { getItem, setItem } from 'next-basics'; import { TIMEZONE_CONFIG } from 'lib/constants'; export default function useTimezone() { diff --git a/lib/auth.js b/lib/auth.js index 7f570031..816a73ff 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,12 +1,13 @@ -import { parseSecureToken, parseToken } from './crypto'; +import { parseSecureToken, parseToken } from 'next-basics'; import { SHARE_TOKEN_HEADER } from './constants'; import { getWebsiteById } from 'queries'; +import { secret } from './crypto'; export async function getAuthToken(req) { try { const token = req.headers.authorization; - return parseSecureToken(token.split(' ')[1]); + return parseSecureToken(token.split(' ')[1], secret()); } catch { return null; } @@ -14,7 +15,7 @@ export async function getAuthToken(req) { export async function isValidToken(token, validation) { try { - const result = await parseToken(token); + const result = parseToken(token, secret()); if (typeof validation === 'object') { return !Object.keys(validation).find(key => result[key] !== validation[key]); diff --git a/lib/clickhouse.js b/lib/clickhouse.js index 1548b77b..5f4cd63b 100644 --- a/lib/clickhouse.js +++ b/lib/clickhouse.js @@ -12,13 +12,9 @@ export const CLICKHOUSE_DATE_FORMATS = { year: '%Y-01-01', }; -const log = debug('clickhouse'); +const log = debug('umami:clickhouse'); function getClient() { - if (!process.env.CLICKHOUSE_URL) { - return null; - } - const { hostname, port, @@ -149,13 +145,13 @@ function parseFilters(table, column, filters = {}, params = [], sessionKey = 'se }; } -function replaceQuery(string, params = []) { - let formattedString = string; +function formatQuery(str, params = []) { + let formattedString = str; - params.forEach((a, i) => { - let replace = a; + params.forEach((param, i) => { + let replace = param; - if (typeof a === 'string' || a instanceof String) { + if (typeof param === 'string' || param instanceof String) { replace = `'${replace}'`; } @@ -165,11 +161,11 @@ function replaceQuery(string, params = []) { return formattedString; } -async function rawQuery(query, params = [], debug = false) { - let formattedQuery = replaceQuery(query, params); +async function rawQuery(query, params = []) { + let formattedQuery = formatQuery(query, params); - if (debug || process.env.LOG_QUERY) { - console.log(formattedQuery); + if (process.env.LOG_QUERY) { + log(formattedQuery); } return clickhouse.query(formattedQuery).toPromise(); @@ -188,7 +184,7 @@ async function findFirst(data) { } // Initialization -const clickhouse = global[CLICKHOUSE] || getClient(); +const clickhouse = process.env.CLICKHOUSE_URL && (global[CLICKHOUSE] || getClient()); export default { client: clickhouse, @@ -199,8 +195,7 @@ export default { getBetweenDates, getFilterQuery, parseFilters, - replaceQuery, - rawQuery, findUnique, findFirst, + rawQuery, }; diff --git a/lib/crypto.js b/lib/crypto.js index e59b0193..1e53aa9d 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -1,24 +1,15 @@ -import crypto from 'crypto'; -import { v4, v5, validate } from 'uuid'; -import bcrypt from 'bcryptjs'; -import { JWT, JWE, JWK } from 'jose'; +import { v4, v5 } from 'uuid'; import { startOfMonth } from 'date-fns'; - -const SALT_ROUNDS = 10; -const KEY = JWK.asKey(Buffer.from(secret())); -const ROTATING_SALT = hash(startOfMonth(new Date()).toUTCString()); -const CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - -export function hash(...args) { - return crypto.createHash('sha512').update(args.join('')).digest('hex'); -} +import { hash } from 'next-basics'; export function secret() { return hash(process.env.HASH_SALT || process.env.DATABASE_URL); } export function salt() { - return v5(hash(secret(), ROTATING_SALT), v5.DNS); + const ROTATING_SALT = hash(startOfMonth(new Date()).toUTCString()); + + return hash([secret(), ROTATING_SALT]); } export function uuid(...args) { @@ -26,49 +17,3 @@ export function uuid(...args) { return v5(args.join(''), salt()); } - -export function isValidUuid(s) { - return validate(s); -} - -export function getRandomChars(n) { - let s = ''; - for (let i = 0; i < n; i++) { - s += CHARS[Math.floor(Math.random() * CHARS.length)]; - } - return s; -} - -export function hashPassword(password) { - return bcrypt.hashSync(password, SALT_ROUNDS); -} - -export function checkPassword(password, hash) { - return bcrypt.compareSync(password, hash); -} - -export async function createToken(payload) { - return JWT.sign(payload, KEY); -} - -export async function parseToken(token) { - try { - return JWT.verify(token, KEY); - } catch { - return null; - } -} - -export async function createSecureToken(payload) { - return JWE.encrypt(await createToken(payload), KEY); -} - -export async function parseSecureToken(token) { - try { - const result = await JWE.decrypt(token, KEY); - - return parseToken(result.toString()); - } catch { - return null; - } -} diff --git a/lib/db.js b/lib/db.js index 9db00c92..58e769b7 100644 --- a/lib/db.js +++ b/lib/db.js @@ -4,6 +4,7 @@ export const MYSQL = 'mysql'; export const CLICKHOUSE = 'clickhouse'; export const KAFKA = 'kafka'; export const KAFKA_PRODUCER = 'kafka-producer'; +export const REDIS = 'redis'; // Fixes issue with converting bigint values BigInt.prototype.toJSON = function () { diff --git a/lib/filters.js b/lib/filters.js index 27b88e20..087b92bf 100644 --- a/lib/filters.js +++ b/lib/filters.js @@ -1,5 +1,3 @@ -import { removeWWW } from './url'; - export const urlFilter = data => { const isValidUrl = url => { return url !== '' && url !== null && !url.startsWith('#'); @@ -49,7 +47,7 @@ export const refFilter = data => { try { const url = new URL(x); - id = removeWWW(url.hostname) || url.href; + id = url.hostname.replace('www', '') || url.href; } catch { id = ''; } @@ -94,11 +92,7 @@ export const paramFilter = data => { return obj; }, {}); - const d = Object.keys(map).flatMap(key => + return Object.keys(map).flatMap(key => Object.keys(map[key]).map(n => ({ x: `${key}=${n}`, p: key, v: n, y: map[key][n] })), ); - - console.log({ map, d }); - - return d; }; diff --git a/lib/kafka.js b/lib/kafka.js index 01b69603..02c865e0 100644 --- a/lib/kafka.js +++ b/lib/kafka.js @@ -3,13 +3,9 @@ import dateFormat from 'dateformat'; import debug from 'debug'; import { KAFKA, KAFKA_PRODUCER } from 'lib/db'; -const log = debug('kafka'); +const log = debug('umami:kafka'); function getClient() { - if (!process.env.KAFKA_URL || !process.env.KAFKA_BROKER) { - return null; - } - const { username, password } = new URL(process.env.KAFKA_URL); const brokers = process.env.KAFKA_BROKER.split(','); @@ -73,8 +69,11 @@ let kafka; let producer; (async () => { - kafka = global[KAFKA] || getClient(); - producer = global[KAFKA_PRODUCER] || (await getProducer()); + kafka = process.env.KAFKA_URL && process.env.KAFKA_BROKER && (global[KAFKA] || getClient()); + + if (kafka) { + producer = global[KAFKA_PRODUCER] || (await getProducer()); + } })(); export default { diff --git a/lib/middleware.js b/lib/middleware.js index 0c6fd081..f7289335 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -1,19 +1,7 @@ +import { createMiddleware, unauthorized, badRequest, serverError } from 'next-basics'; import cors from 'cors'; import { getSession } from './session'; import { getAuthToken } from './auth'; -import { unauthorized, badRequest, serverError } from './response'; - -export function createMiddleware(middleware) { - return (req, res) => - new Promise((resolve, reject) => { - middleware(req, res, result => { - if (result instanceof Error) { - return reject(result); - } - return resolve(result); - }); - }); -} export const useCors = createMiddleware(cors()); @@ -23,7 +11,9 @@ export const useSession = createMiddleware(async (req, res, next) => { try { session = await getSession(req); } catch (e) { + // eslint-disable-next-line no-console console.error(e); + return serverError(res, e.message); } diff --git a/lib/prisma.js b/lib/prisma.js index af8759ba..af2408d5 100644 --- a/lib/prisma.js +++ b/lib/prisma.js @@ -2,9 +2,8 @@ import { PrismaClient } from '@prisma/client'; import chalk from 'chalk'; import moment from 'moment-timezone'; import debug from 'debug'; -import { PRISMA, MYSQL, POSTGRESQL } from 'lib/db'; +import { PRISMA, MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db'; import { FILTER_IGNORED } from 'lib/constants'; -import { getDatabaseType } from 'lib/db'; const MYSQL_DATE_FORMATS = { minute: '%Y-%m-%d %H:%i:00', @@ -22,7 +21,7 @@ const POSTGRESQL_DATE_FORMATS = { year: 'YYYY-01-01', }; -const log = debug('prisma'); +const log = debug('umami:prisma'); const PRISMA_OPTIONS = { log: [ diff --git a/lib/redis.js b/lib/redis.js index e307729b..01fef5a0 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -1,10 +1,11 @@ import { createClient } from 'redis'; import { startOfMonth } from 'date-fns'; -import { getSessions, getAllWebsites } from '/queries'; import debug from 'debug'; +import { getSessions, getAllWebsites } from 'queries'; +import { REDIS } from 'lib/db'; -const log = debug('db:redis'); -const REDIS = Symbol.for('redis'); +const log = debug('umami:redis'); +const INITIALIZED = 'redis:initialized'; async function getClient() { const redis = new createClient({ @@ -38,7 +39,7 @@ async function stageData() { await addRedis(sessionUuids); await addRedis(websiteIds); - await redis.set('initialized', 'initialized'); + await redis.set(INITIALIZED, 1); } async function addRedis(ids) { @@ -52,12 +53,12 @@ async function addRedis(ids) { let redis = null; (async () => { - redis = global[REDIS] || (await getClient()); + redis = process.env.REDIS_URL && (global[REDIS] || (await getClient())); - const value = await redis.get('initialized'); - - if (!value) { - await stageData(); + if (redis) { + if (!(await redis.get(INITIALIZED))) { + await stageData(); + } } })(); diff --git a/lib/response.js b/lib/response.js deleted file mode 100644 index 72525f18..00000000 --- a/lib/response.js +++ /dev/null @@ -1,43 +0,0 @@ -export function ok(res, data = {}) { - return json(res, data); -} - -export function json(res, data = {}) { - return res.status(200).json(data); -} - -export function send(res, data, type = 'text/plain') { - res.setHeader('Content-Type', type); - - return res.status(200).send(data); -} - -export function redirect(res, url) { - res.setHeader('Location', url); - - return res.status(303).end(); -} - -export function badRequest(res, msg = '400 Bad Request') { - return res.status(400).end(msg); -} - -export function unauthorized(res, msg = '401 Unauthorized') { - return res.status(401).end(msg); -} - -export function forbidden(res, msg = '403 Forbidden') { - return res.status(403).end(msg); -} - -export function notFound(res, msg = '404 Not Found') { - return res.status(404).end(msg); -} - -export function methodNotAllowed(res, msg = '405 Method Not Allowed') { - res.status(405).end(msg); -} - -export function serverError(res, msg = '500 Internal Server Error') { - res.status(500).end(msg); -} diff --git a/lib/security.js b/lib/security.js new file mode 100644 index 00000000..9a2a9ca1 --- /dev/null +++ b/lib/security.js @@ -0,0 +1,8 @@ +import { getItem } from 'next-basics'; +import { AUTH_TOKEN } from './constants'; + +export function getAuthHeader() { + const token = getItem(AUTH_TOKEN); + + return token ? { authorization: `Bearer ${token}` } : {}; +} diff --git a/lib/session.js b/lib/session.js index 769cc0dc..6b8bd97b 100644 --- a/lib/session.js +++ b/lib/session.js @@ -1,4 +1,6 @@ -import { isValidUuid, parseToken, uuid } from 'lib/crypto'; +import { parseToken } from 'next-basics'; +import { validate } from 'uuid'; +import { uuid } from 'lib/crypto'; import redis from 'lib/redis'; import { getClientInfo, getJsonBody } from 'lib/request'; import { createSession, getSessionByUuid, getWebsiteByUuid } from 'queries'; @@ -22,8 +24,8 @@ export async function getSession(req) { const { website: website_uuid, hostname, screen, language } = payload; - if (!isValidUuid(website_uuid)) { - throw new Error(`Invalid website: ${website_uuid}`); + if (!validate(website_uuid)) { + return null; } let websiteId = null; @@ -52,7 +54,6 @@ export async function getSession(req) { if (process.env.REDIS_URL) { sessionCreated = (await redis.get(`session:${session_uuid}`)) !== null; } else { - console.log('test'); session = await getSessionByUuid(session_uuid); sessionCreated = !!session; sessionId = session ? session.session_id : null; @@ -60,7 +61,6 @@ export async function getSession(req) { if (!sessionCreated) { try { - console.log('test2'); session = await createSession(websiteId, { session_uuid, hostname, diff --git a/lib/url.js b/lib/url.js deleted file mode 100644 index 644770b6..00000000 --- a/lib/url.js +++ /dev/null @@ -1,35 +0,0 @@ -export function removeTrailingSlash(url) { - return url && url.length > 1 && url.endsWith('/') ? url.slice(0, -1) : url; -} - -export function removeWWW(url) { - return url && url.length > 1 && url.startsWith('www.') ? url.slice(4) : url; -} - -export function getQueryString(params = {}) { - const map = Object.keys(params).reduce((arr, key) => { - if (params[key] !== undefined) { - return arr.concat(`${key}=${encodeURIComponent(params[key])}`); - } - return arr; - }, []); - - if (map.length) { - return `?${map.join('&')}`; - } - - return ''; -} - -export function makeUrl(url, params) { - return `${url}${getQueryString(params)}`; -} - -export function safeDecodeURI(s) { - try { - return decodeURI(s); - } catch (e) { - console.error(e); - } - return s; -} diff --git a/lib/web.js b/lib/web.js deleted file mode 100644 index 7317d322..00000000 --- a/lib/web.js +++ /dev/null @@ -1,78 +0,0 @@ -import { makeUrl } from './url'; - -export const apiRequest = (method, url, body, headers) => { - return fetch(url, { - method, - cache: 'no-cache', - credentials: 'same-origin', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...headers, - }, - body, - }).then(res => { - if (res.ok) { - return res.json().then(data => ({ ok: res.ok, status: res.status, data })); - } - - return res.text().then(data => ({ ok: res.ok, status: res.status, res: res, data })); - }); -}; - -export const get = (url, params, headers) => - apiRequest('get', makeUrl(url, params), undefined, headers); - -export const del = (url, params, headers) => - apiRequest('delete', makeUrl(url, params), undefined, headers); - -export const post = (url, params, headers) => - apiRequest('post', url, JSON.stringify(params), headers); - -export const put = (url, params, headers) => - apiRequest('put', url, JSON.stringify(params), headers); - -export const hook = (_this, method, callback) => { - const orig = _this[method]; - - return (...args) => { - callback.apply(null, args); - - return orig.apply(_this, args); - }; -}; - -export const doNotTrack = () => { - const { doNotTrack, navigator, external } = window; - - const msTrackProtection = 'msTrackingProtectionEnabled'; - const msTracking = () => { - return external && msTrackProtection in external && external[msTrackProtection](); - }; - - const dnt = doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack || msTracking(); - - return dnt == '1' || dnt === 'yes'; -}; - -export const setItem = (key, data, session) => { - if (typeof window !== 'undefined' && data) { - (session ? sessionStorage : localStorage).setItem(key, JSON.stringify(data)); - } -}; - -export const getItem = (key, session) => { - if (typeof window !== 'undefined') { - const value = (session ? sessionStorage : localStorage).getItem(key); - - if (value !== 'undefined') { - return JSON.parse(value); - } - } -}; - -export const removeItem = (key, session) => { - if (typeof window !== 'undefined') { - (session ? sessionStorage : localStorage).removeItem(key); - } -}; diff --git a/package.json b/package.json index 12153e84..7cbd5e55 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "dependencies": { "@fontsource/inter": "4.5.7", "@prisma/client": "4.2.1", - "bcryptjs": "^2.4.3", "chalk": "^4.1.1", "chart.js": "^2.9.4", "classnames": "^2.3.1", @@ -81,11 +80,11 @@ "is-docker": "^3.0.0", "is-localhost-ip": "^1.4.0", "isbot": "^3.4.5", - "jose": "2.0.5", "kafkajs": "^2.1.0", "maxmind": "^4.3.6", "moment-timezone": "^0.5.33", "next": "^12.2.5", + "next-basics": "^0.6.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "prop-types": "^15.7.2", @@ -115,6 +114,8 @@ "eslint": "^7.32.0", "eslint-config-next": "^12.2.4", "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.0.0", "extract-react-intl-messages": "^4.1.1", "husky": "^7.0.0", diff --git a/pages/api/account/[id].js b/pages/api/account/[id].js index 51279f4b..31c4b2dc 100644 --- a/pages/api/account/[id].js +++ b/pages/api/account/[id].js @@ -1,6 +1,6 @@ import { getAccountById, deleteAccount } from 'queries'; import { useAuth } from 'lib/middleware'; -import { methodNotAllowed, ok, unauthorized } from 'lib/response'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; export default async (req, res) => { await useAuth(req, res); diff --git a/pages/api/account/index.js b/pages/api/account/index.js index a7cb3795..fe9cafe1 100644 --- a/pages/api/account/index.js +++ b/pages/api/account/index.js @@ -1,7 +1,6 @@ +import { ok, unauthorized, methodNotAllowed, badRequest, hashPassword } from 'next-basics'; import { getAccountById, getAccountByUsername, updateAccount, createAccount } from 'queries'; import { useAuth } from 'lib/middleware'; -import { hashPassword } from 'lib/crypto'; -import { ok, unauthorized, methodNotAllowed, badRequest } from 'lib/response'; export default async (req, res) => { await useAuth(req, res); diff --git a/pages/api/account/password.js b/pages/api/account/password.js index 432ba34c..da3a643c 100644 --- a/pages/api/account/password.js +++ b/pages/api/account/password.js @@ -1,7 +1,13 @@ import { getAccountById, updateAccount } from 'queries'; import { useAuth } from 'lib/middleware'; -import { badRequest, methodNotAllowed, ok, unauthorized } from 'lib/response'; -import { checkPassword, hashPassword } from 'lib/crypto'; +import { + badRequest, + methodNotAllowed, + ok, + unauthorized, + checkPassword, + hashPassword, +} from 'next-basics'; export default async (req, res) => { await useAuth(req, res); diff --git a/pages/api/accounts/index.js b/pages/api/accounts/index.js index 29edf828..42b96a11 100644 --- a/pages/api/accounts/index.js +++ b/pages/api/accounts/index.js @@ -1,6 +1,6 @@ import { getAccounts } from 'queries'; import { useAuth } from 'lib/middleware'; -import { ok, unauthorized, methodNotAllowed } from 'lib/response'; +import { ok, unauthorized, methodNotAllowed } from 'next-basics'; export default async (req, res) => { await useAuth(req, res); diff --git a/pages/api/auth/login.js b/pages/api/auth/login.js index 9a4aed9e..f1d99100 100644 --- a/pages/api/auth/login.js +++ b/pages/api/auth/login.js @@ -1,6 +1,6 @@ -import { checkPassword, createSecureToken } from 'lib/crypto'; +import { ok, unauthorized, badRequest, checkPassword, createSecureToken } from 'next-basics'; import { getAccountByUsername } from 'queries/admin/account/getAccountByUsername'; -import { ok, unauthorized, badRequest } from 'lib/response'; +import { secret } from 'lib/crypto'; export default async (req, res) => { const { username, password } = req.body; @@ -11,10 +11,10 @@ export default async (req, res) => { const account = await getAccountByUsername(username); - if (account && (await checkPassword(password, account.password))) { + if (account && checkPassword(password, account.password)) { const { user_id, username, is_admin } = account; const user = { user_id, username, is_admin }; - const token = await createSecureToken(user); + const token = createSecureToken(user, secret()); return ok(res, { token, user }); } diff --git a/pages/api/auth/verify.js b/pages/api/auth/verify.js index d9a2bb01..303a38ec 100644 --- a/pages/api/auth/verify.js +++ b/pages/api/auth/verify.js @@ -1,5 +1,5 @@ import { useAuth } from 'lib/middleware'; -import { ok, unauthorized } from 'lib/response'; +import { ok, unauthorized } from 'next-basics'; export default async (req, res) => { await useAuth(req, res); diff --git a/pages/api/collect.js b/pages/api/collect.js index c17dd761..dec2f28d 100644 --- a/pages/api/collect.js +++ b/pages/api/collect.js @@ -1,12 +1,10 @@ const { Resolver } = require('dns').promises; import isbot from 'isbot'; import ipaddr from 'ipaddr.js'; +import { createToken, unauthorized, send, badRequest, forbidden } from 'next-basics'; import { savePageView, saveEvent } from 'queries'; import { useCors, useSession } from 'lib/middleware'; import { getJsonBody, getIpAddress } from 'lib/request'; -import { unauthorized, send, badRequest, forbidden } from 'lib/response'; -import { createToken } from 'lib/crypto'; -import { removeTrailingSlash } from 'lib/url'; import { uuid } from 'lib/crypto'; export default async (req, res) => { @@ -69,7 +67,7 @@ export default async (req, res) => { let { url, referrer, event_name, event_data } = payload; if (process.env.REMOVE_TRAILING_SLASH) { - url = removeTrailingSlash(url); + url = url.replace(/\/$/, ''); } const event_uuid = uuid(); @@ -89,7 +87,7 @@ export default async (req, res) => { return badRequest(res); } - const token = await createToken({ website_id, session_id, session_uuid }); + const token = createToken({ website_id, session_id, session_uuid }); return send(res, token); }; diff --git a/pages/api/config.js b/pages/api/config.js index a72eb0ba..27a04eaa 100644 --- a/pages/api/config.js +++ b/pages/api/config.js @@ -1,4 +1,4 @@ -import { ok, methodNotAllowed } from 'lib/response'; +import { ok, methodNotAllowed } from 'next-basics'; export default async (req, res) => { if (req.method === 'GET') { diff --git a/pages/api/heartbeat.js b/pages/api/heartbeat.js index a53213ad..dd1be1eb 100644 --- a/pages/api/heartbeat.js +++ b/pages/api/heartbeat.js @@ -1,4 +1,4 @@ -import { ok } from 'lib/response'; +import { ok } from 'next-basics'; export default async (req, res) => { return ok(res, 'nice'); diff --git a/pages/api/realtime/init.js b/pages/api/realtime/init.js index 69c70a82..ee8f4ee2 100644 --- a/pages/api/realtime/init.js +++ b/pages/api/realtime/init.js @@ -1,8 +1,8 @@ import { subMinutes } from 'date-fns'; +import { ok, methodNotAllowed, createToken } from 'next-basics'; import { useAuth } from 'lib/middleware'; -import { ok, methodNotAllowed } from 'lib/response'; import { getUserWebsites, getRealtimeData } from 'queries'; -import { createToken } from 'lib/crypto'; +import { secret } from 'lib/crypto'; export default async (req, res) => { await useAuth(req, res); @@ -12,7 +12,7 @@ export default async (req, res) => { const websites = await getUserWebsites(user_id); const ids = websites.map(({ website_id }) => website_id); - const token = await createToken({ websites: ids }); + const token = createToken({ websites: ids }, secret()); const data = await getRealtimeData(ids, subMinutes(new Date(), 30)); return ok(res, { diff --git a/pages/api/realtime/update.js b/pages/api/realtime/update.js index 4fa0ea3b..9b91663d 100644 --- a/pages/api/realtime/update.js +++ b/pages/api/realtime/update.js @@ -1,8 +1,8 @@ +import { ok, methodNotAllowed, badRequest, parseToken } from 'next-basics'; import { useAuth } from 'lib/middleware'; -import { ok, methodNotAllowed, badRequest } from 'lib/response'; import { getRealtimeData } from 'queries'; -import { parseToken } from 'lib/crypto'; import { SHARE_TOKEN_HEADER } from 'lib/constants'; +import { secret } from 'lib/crypto'; export default async (req, res) => { await useAuth(req, res); @@ -16,7 +16,7 @@ export default async (req, res) => { return badRequest(res); } - const { websites } = await parseToken(token); + const { websites } = parseToken(token, secret()); const data = await getRealtimeData(websites, new Date(+start_at)); diff --git a/pages/api/share/[id].js b/pages/api/share/[id].js index 698f1ba0..e6dcb4a3 100644 --- a/pages/api/share/[id].js +++ b/pages/api/share/[id].js @@ -1,6 +1,6 @@ import { getWebsiteByShareId } from 'queries'; -import { ok, notFound, methodNotAllowed } from 'lib/response'; -import { createToken } from 'lib/crypto'; +import { ok, notFound, methodNotAllowed, createToken } from 'next-basics'; +import { secret } from 'lib/crypto'; export default async (req, res) => { const { id } = req.query; @@ -10,7 +10,7 @@ export default async (req, res) => { if (website) { const websiteId = website.website_id; - const token = await createToken({ website_id: websiteId }); + const token = createToken({ website_id: websiteId }, secret()); return ok(res, { websiteId, token }); } diff --git a/pages/api/website/[id]/active.js b/pages/api/website/[id]/active.js index 4acc97f1..20550427 100644 --- a/pages/api/website/[id]/active.js +++ b/pages/api/website/[id]/active.js @@ -1,4 +1,4 @@ -import { methodNotAllowed, ok, unauthorized } from 'lib/response'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; import { getActiveVisitors } from 'queries'; diff --git a/pages/api/website/[id]/events.js b/pages/api/website/[id]/events.js index ab4ddc7d..c633a585 100644 --- a/pages/api/website/[id]/events.js +++ b/pages/api/website/[id]/events.js @@ -1,6 +1,6 @@ import moment from 'moment-timezone'; import { getEventMetrics } from 'queries'; -import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response'; +import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/[id]/index.js b/pages/api/website/[id]/index.js index 048b5ff1..aaebb3d4 100644 --- a/pages/api/website/[id]/index.js +++ b/pages/api/website/[id]/index.js @@ -1,5 +1,5 @@ +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { deleteWebsite, getWebsiteById } from 'queries'; -import { methodNotAllowed, ok, unauthorized } from 'lib/response'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/[id]/metrics.js b/pages/api/website/[id]/metrics.js index b6e701e7..378d8c73 100644 --- a/pages/api/website/[id]/metrics.js +++ b/pages/api/website/[id]/metrics.js @@ -1,5 +1,5 @@ import { getPageviewMetrics, getSessionMetrics, getWebsiteById } from 'queries'; -import { ok, methodNotAllowed, unauthorized, badRequest } from 'lib/response'; +import { ok, methodNotAllowed, unauthorized, badRequest } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; import { FILTER_IGNORED } from 'lib/constants'; diff --git a/pages/api/website/[id]/pageviews.js b/pages/api/website/[id]/pageviews.js index 3bd572d9..9a713761 100644 --- a/pages/api/website/[id]/pageviews.js +++ b/pages/api/website/[id]/pageviews.js @@ -1,6 +1,6 @@ import moment from 'moment-timezone'; import { getPageviewStats } from 'queries'; -import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response'; +import { ok, badRequest, methodNotAllowed, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/[id]/reset.js b/pages/api/website/[id]/reset.js index 10fc5cb7..2f5d05b4 100644 --- a/pages/api/website/[id]/reset.js +++ b/pages/api/website/[id]/reset.js @@ -1,5 +1,5 @@ import { resetWebsite } from 'queries'; -import { methodNotAllowed, ok, unauthorized } from 'lib/response'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; export default async (req, res) => { diff --git a/pages/api/website/[id]/stats.js b/pages/api/website/[id]/stats.js index bd32c79d..d652c048 100644 --- a/pages/api/website/[id]/stats.js +++ b/pages/api/website/[id]/stats.js @@ -1,5 +1,5 @@ import { getWebsiteStats } from 'queries'; -import { methodNotAllowed, ok, unauthorized } from 'lib/response'; +import { methodNotAllowed, ok, unauthorized } from 'next-basics'; import { allowQuery } from 'lib/auth'; import { useCors } from 'lib/middleware'; diff --git a/pages/api/website/index.js b/pages/api/website/index.js index 59d0a5f1..ee67a6b0 100644 --- a/pages/api/website/index.js +++ b/pages/api/website/index.js @@ -1,7 +1,7 @@ +import { ok, unauthorized, methodNotAllowed, getRandomChars } from 'next-basics'; import { updateWebsite, createWebsite, getWebsiteById } from 'queries'; import { useAuth } from 'lib/middleware'; -import { uuid, getRandomChars } from 'lib/crypto'; -import { ok, unauthorized, methodNotAllowed } from 'lib/response'; +import { uuid } from 'lib/crypto'; export default async (req, res) => { await useAuth(req, res); diff --git a/pages/api/websites/index.js b/pages/api/websites/index.js index b70272d8..8b03a9e9 100644 --- a/pages/api/websites/index.js +++ b/pages/api/websites/index.js @@ -1,6 +1,6 @@ import { getAllWebsites, getUserWebsites } from 'queries'; import { useAuth } from 'lib/middleware'; -import { ok, methodNotAllowed, unauthorized } from 'lib/response'; +import { ok, methodNotAllowed, unauthorized } from 'next-basics'; export default async (req, res) => { await useAuth(req, res); diff --git a/pages/logout.js b/pages/logout.js index 7692fb03..bcc99a10 100644 --- a/pages/logout.js +++ b/pages/logout.js @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { useRouter } from 'next/router'; -import { removeItem } from 'lib/web'; +import { removeItem } from 'next-basics'; import { AUTH_TOKEN } from 'lib/constants'; import { setUser } from 'store/app'; diff --git a/scripts/change-password.js b/scripts/change-password.js index 5e3de42f..a9b63c12 100644 --- a/scripts/change-password.js +++ b/scripts/change-password.js @@ -1,11 +1,11 @@ +/* eslint-disable no-console */ require('dotenv').config(); -const bcrypt = require('bcryptjs'); +const { hashPassword } = require('next-basics'); const chalk = require('chalk'); const prompts = require('prompts'); const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); -const SALT_ROUNDS = 10; const runQuery = async query => { return query.catch(e => { @@ -24,10 +24,6 @@ const updateAccountByUsername = (username, data) => { ); }; -const hashPassword = password => { - return bcrypt.hashSync(password, SALT_ROUNDS); -}; - const changePassword = async (username, newPassword) => { const password = hashPassword(newPassword); return updateAccountByUsername(username, { password }); diff --git a/store/app.js b/store/app.js index dc5f527d..f03eb57c 100644 --- a/store/app.js +++ b/store/app.js @@ -1,6 +1,6 @@ import create from 'zustand'; import { DEFAULT_LOCALE, DEFAULT_THEME, LOCALE_CONFIG, THEME_CONFIG } from 'lib/constants'; -import { getItem } from 'lib/web'; +import { getItem } from 'next-basics'; const initialState = { locale: getItem(LOCALE_CONFIG) || DEFAULT_LOCALE, diff --git a/store/dashboard.js b/store/dashboard.js index 35f4f2ff..7c512228 100644 --- a/store/dashboard.js +++ b/store/dashboard.js @@ -1,6 +1,6 @@ import create from 'zustand'; import { DASHBOARD_CONFIG, DEFAULT_WEBSITE_LIMIT } from 'lib/constants'; -import { getItem, setItem } from 'lib/web'; +import { getItem, setItem } from 'next-basics'; export const initialState = { showCharts: true, diff --git a/store/version.js b/store/version.js index ef8f3e48..cb5208fa 100644 --- a/store/version.js +++ b/store/version.js @@ -2,7 +2,7 @@ import create from 'zustand'; import produce from 'immer'; import semver from 'semver'; import { CURRENT_VERSION, VERSION_CHECK, UPDATES_URL } from 'lib/constants'; -import { getItem } from 'lib/web'; +import { getItem } from 'next-basics'; const initialState = { current: CURRENT_VERSION, diff --git a/yarn.lock b/yarn.lock index 53bf04e8..5da68afe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1440,11 +1440,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@panva/asn1.js@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz" - integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== - "@prisma/client@4.2.1": version "4.2.1" resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.2.1.tgz#b384587f6066070381ea4c90228a14697a0c271b" @@ -2373,6 +2368,11 @@ buble@^0.20.0: minimist "^1.2.5" regexpu-core "4.5.4" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" @@ -3020,6 +3020,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + electron-to-chromium@^1.4.118: version "1.4.143" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.143.tgz" @@ -3144,6 +3151,11 @@ eslint-config-prettier@^8.5.0: resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz" integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== +eslint-import-resolver-alias@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz#297062890e31e4d6651eb5eba9534e1f6e68fc97" + integrity sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w== + eslint-import-resolver-node@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" @@ -4146,13 +4158,6 @@ jest-worker@^26.2.1: merge-stream "^2.0.0" supports-color "^7.0.0" -jose@2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz" - integrity sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA== - dependencies: - "@panva/asn1.js" "^1.0.0" - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -4254,6 +4259,22 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -4272,6 +4293,23 @@ jsprim@^1.2.2: array-includes "^3.1.5" object.assign "^4.1.3" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + kafkajs@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-2.2.0.tgz#43b2d13c82395acee4500f09d6c7d503db8c77ea" @@ -4395,6 +4433,36 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" @@ -4405,6 +4473,11 @@ lodash.mergewith@^4.6.2: resolved "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz" integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz" @@ -4662,6 +4735,14 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +next-basics@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/next-basics/-/next-basics-0.6.0.tgz#bbb3b2dafa69931c3b7aad0cd456332ddcf019c7" + integrity sha512-S9deRGhQPj9tN9WSroK8UAcxFuoV38YNFO9B5qEQpt7ZUNCkAUITccW98LGlJ5WfNzkp7dnXVgmL3+yvRWlH4w== + dependencies: + bcryptjs "^2.4.3" + jsonwebtoken "^8.5.1" + next@^12.2.5: version "12.2.5" resolved "https://registry.yarnpkg.com/next/-/next-12.2.5.tgz#14fb5975e8841fad09553b8ef41fe1393602b717" @@ -5866,7 +5947,7 @@ semver-compare@^1.0.0: resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -"semver@2 || 3 || 4 || 5", semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==