Allow embedding of share page.

This commit is contained in:
Mike Cao 2023-11-11 20:45:09 -08:00
parent 9cb6046844
commit eda2c07ea3
10 changed files with 58 additions and 39 deletions

View file

@ -3,29 +3,32 @@ require('dotenv').config();
const path = require('path'); const path = require('path');
const pkg = require('./package.json'); const pkg = require('./package.json');
const contentSecurityPolicy = ` const contentSecurityPolicy = [
default-src 'self'; `default-src 'self'`,
img-src *; `img-src *`,
script-src 'self' 'unsafe-eval' 'unsafe-inline'; `script-src 'self' 'unsafe-eval' 'unsafe-inline'`,
style-src 'self' 'unsafe-inline'; `style-src 'self' 'unsafe-inline'`,
connect-src 'self' api.umami.is; `connect-src 'self' api.umami.is`,
frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS}; ];
`;
const headers = [ const headers = [
{ {
key: 'X-DNS-Prefetch-Control', key: 'X-DNS-Prefetch-Control',
value: 'on', value: 'on',
}, },
{ !process.env.ALLOWED_FRAME_URLS && {
key: 'X-Frame-Options', key: 'X-Frame-Options',
value: 'SAMEORIGIN', value: 'SAMEORIGIN',
}, },
{ ].filter(n => n);
key: 'Content-Security-Policy',
value: contentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(), const cspHeader = (values = []) => ({
}, key: 'Content-Security-Policy',
]; value: [...contentSecurityPolicy, ...values]
.join(';')
.replace(/\s{2,}/g, ' ')
.trim(),
});
if (process.env.FORCE_SSL) { if (process.env.FORCE_SSL) {
headers.push({ headers.push({
@ -81,14 +84,13 @@ const config = {
reactStrictMode: false, reactStrictMode: false,
env: { env: {
basePath: basePath || '', basePath: basePath || '',
cloudMode: !!process.env.CLOUD_MODE, cloudMode: process.env.CLOUD_MODE || '',
cloudUrl: process.env.CLOUD_URL, cloudUrl: process.env.CLOUD_URL || '',
configUrl: '/config', configUrl: '/config',
currentVersion: pkg.version, currentVersion: pkg.version,
defaultLocale: process.env.DEFAULT_LOCALE, defaultLocale: process.env.DEFAULT_LOCALE || '',
disableLogin: process.env.DISABLE_LOGIN, disableLogin: process.env.DISABLE_LOGIN || '',
disableUI: process.env.DISABLE_UI, disableUI: process.env.DISABLE_UI || '',
isProduction: process.env.NODE_ENV === 'production',
}, },
basePath, basePath,
output: 'standalone', output: 'standalone',
@ -125,7 +127,14 @@ const config = {
return [ return [
{ {
source: '/:path*', source: '/:path*',
headers, headers: [
...headers,
cspHeader([`frame-ancestors 'self' ${process.env.ALLOWED_FRAME_URLS || ''}`]),
],
},
{
source: '/share/:path*',
headers: [...headers, cspHeader()],
}, },
]; ];
}, },

View file

@ -4,7 +4,7 @@ import { usePathname } from 'next/navigation';
import UpdateNotice from 'components/common/UpdateNotice'; import UpdateNotice from 'components/common/UpdateNotice';
import { useRequireLogin, useConfig } from 'components/hooks'; import { useRequireLogin, useConfig } from 'components/hooks';
export function Shell({ children }) { export function App({ children }) {
const { user } = useRequireLogin(); const { user } = useRequireLogin();
const config = useConfig(); const config = useConfig();
const pathname = usePathname(); const pathname = usePathname();
@ -24,4 +24,4 @@ export function Shell({ children }) {
); );
} }
export default Shell; export default App;

View file

@ -1,7 +1,7 @@
import Dashboard from 'app/(main)/dashboard/Dashboard'; import Dashboard from 'app/(main)/dashboard/Dashboard';
import { Metadata } from 'next'; import { Metadata } from 'next';
export default function DashboardPage() { export default function () {
return <Dashboard />; return <Dashboard />;
} }

View file

@ -1,11 +1,11 @@
import Shell from './Shell'; import App from './App';
import NavBar from './NavBar'; import NavBar from './NavBar';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import styles from './layout.module.css'; import styles from './layout.module.css';
export default function AppLayout({ children }) { export default function ({ children }) {
return ( return (
<Shell> <App>
<main className={styles.layout}> <main className={styles.layout}>
<nav className={styles.nav}> <nav className={styles.nav}>
<NavBar /> <NavBar />
@ -14,6 +14,6 @@ export default function AppLayout({ children }) {
<Page>{children}</Page> <Page>{children}</Page>
</section> </section>
</main> </main>
</Shell> </App>
); );
} }

View file

@ -5,9 +5,9 @@ import useFilterQuery from 'components/hooks/useFilterQuery';
import DataTable from 'components/common/DataTable'; import DataTable from 'components/common/DataTable';
import useCache from 'store/cache'; import useCache from 'store/cache';
export default function ReportsDataTable({ websiteId }) { export default function ReportsDataTable({ websiteId }: { websiteId?: string }) {
const { get } = useApi(); const { get } = useApi();
const modified = useCache(state => state?.reports); const modified = useCache(state => (state as any)?.reports);
const queryResult = useFilterQuery(['reports', { websiteId, modified }], params => const queryResult = useFilterQuery(['reports', { websiteId, modified }], params =>
get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params), get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params),
); );

View file

@ -1,7 +1,7 @@
import ReportsHeader from './ReportsHeader'; import ReportsHeader from './ReportsHeader';
import ReportsDataTable from './ReportsDataTable'; import ReportsDataTable from './ReportsDataTable';
export default function ReportsPage() { export default function () {
return ( return (
<> <>
<ReportsHeader /> <ReportsHeader />

View file

@ -8,16 +8,16 @@ import 'styles/locale.css';
import 'styles/index.css'; import 'styles/index.css';
import 'styles/variables.css'; import 'styles/variables.css';
export default function RootLayout({ children }) { export default function ({ children }) {
return ( return (
<html lang="en" data-scroll="0"> <html lang="en" data-scroll="0">
<head> <head>
<link rel="icon" href={`/favicon.ico`} /> <link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href={`/apple-touch-icon.png`} /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href={`/favicon-32x32.png`} /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href={`/favicon-16x16.png`} /> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href={`/site.webmanifest`} /> <link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href={`/safari-pinned-tab.svg`} color="#5bbad5" /> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" /> <meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)" /> <meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#2f2f2f" media="(prefers-color-scheme: dark)" /> <meta name="theme-color" content="#2f2f2f" media="(prefers-color-scheme: dark)" />

View file

@ -1,5 +1,10 @@
import Logout from './Logout'; import Logout from './Logout';
import { Metadata } from 'next';
export default function () { export default function () {
return <Logout />; return <Logout />;
} }
export const metadata: Metadata = {
title: 'Logout | umami',
};

View file

@ -1,5 +1,10 @@
import Share from './Share'; import Share from './Share';
import { Metadata } from 'next';
export default function ({ params: { id } }) { export default function ({ params: { id } }) {
return <Share shareId={id[0]} />; return <Share shareId={id[0]} />;
} }
export const metadata: Metadata = {
title: 'umami',
};

View file

@ -74,7 +74,7 @@ export default async (req: NextApiRequestCollect, res: NextApiResponse) => {
await useCors(req, res); await useCors(req, res);
if (req.method === 'POST') { if (req.method === 'POST') {
if (isbot(req.headers['user-agent']) && !process.env.DISABLE_BOT_CHECK) { if (!process.env.DISABLE_BOT_CHECK && isbot(req.headers['user-agent'])) {
return ok(res); return ok(res);
} }