Commit 6bc9c5f8 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge branch 'main' into lighthouse-report-fixes

parents d6a7d173 4a32e7de
...@@ -5,6 +5,7 @@ SENTRY_ORG=block-scout ...@@ -5,6 +5,7 @@ SENTRY_ORG=block-scout
SENTRY_PROJECT=new-ui SENTRY_PROJECT=new-ui
SENTRY_AUTH_TOKEN=xxx SENTRY_AUTH_TOKEN=xxx
SENTRY_IGNORE_API_RESOLUTION_ERROR=1 SENTRY_IGNORE_API_RESOLUTION_ERROR=1
SENTRY_CSP_REPORT_URI=xxx
NEXT_PUBLIC_BLOCKSCOUT_VERSION=xxx NEXT_PUBLIC_BLOCKSCOUT_VERSION=xxx
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
......
const getCspPolicy = require('../../lib/csp/getCspPolicy');
async function headers() {
return [
{
source: '/:path*',
headers: [
// security headers from here - https://nextjs.org/docs/advanced-features/security-headers
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Content-Security-Policy-Report-Only',
value: getCspPolicy(),
},
],
},
];
}
module.exports = headers;
import * as Sentry from '@sentry/nextjs';
import type { NextApiRequest } from 'next'; import type { NextApiRequest } from 'next';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
...@@ -7,8 +8,7 @@ export default function getUrlWithNetwork(_req: NextApiRequest, path: string) { ...@@ -7,8 +8,7 @@ export default function getUrlWithNetwork(_req: NextApiRequest, path: string) {
const networkSubType = _req.cookies[cookies.NAMES.NETWORK_SUB_TYPE]; const networkSubType = _req.cookies[cookies.NAMES.NETWORK_SUB_TYPE];
if (!networkType || !networkSubType) { if (!networkType || !networkSubType) {
// eslint-disable-next-line no-console Sentry.captureException(new Error('Incorrect network'), { extra: { networkType, networkSubType } });
console.error(`Incorrect network: NETWORK_TYPE=${ networkType } NETWORK_SUB_TYPE=${ networkSubType }`);
} }
return `/${ networkType }/${ networkSubType }/${ path }`; return `/${ networkType }/${ networkSubType }/${ path }`;
......
import * as Sentry from '@sentry/nextjs';
export interface ErrorType<T> { export interface ErrorType<T> {
error?: T; error?: T;
status: Response['status']; status: Response['status'];
...@@ -13,10 +15,15 @@ export default function clientFetch<Success, Error>(path: string, init?: Request ...@@ -13,10 +15,15 @@ export default function clientFetch<Success, Error>(path: string, init?: Request
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
}), }),
() => Promise.reject({ () => {
status: response.status, const error = {
statusText: response.statusText, status: response.status,
}), statusText: response.statusText,
};
Sentry.captureException(new Error('Client fetch failed'), { extra: error, tags: { source: 'fetch' } });
return Promise.reject(error);
},
); );
} else { } else {
......
const parseNetworkConfig = require('../networks/parseNetworkConfig');
const KEY_WORDS = {
BLOB: 'blob:',
DATA: 'data:',
NONE: '\'none\'',
REPORT_SAMPLE: `'report-sample'`,
SELF: '\'self\'',
STRICT_DYNAMIC: `'strict-dynamic'`,
UNSAFE_INLINE: '\'unsafe-inline\'',
UNSAFE_EVAL: '\'unsafe-eval\'',
};
const MAIN_DOMAINS = [ '*.blockscout.com', 'blockscout.com' ];
const isDev = process.env.NODE_ENV === 'development';
function getNetworksExternalAssets() {
const icons = parseNetworkConfig()
.filter(({ icon }) => typeof icon === 'string')
.map(({ icon }) => new URL(icon));
return icons;
}
function makePolicyMap() {
const networkExternalAssets = getNetworksExternalAssets();
return {
'default-src': [
KEY_WORDS.NONE,
],
'connect-src': [
KEY_WORDS.SELF,
// webpack hmr in safari doesn't recognize localhost as 'self' for some reason
isDev ? 'ws://localhost:3000/_next/webpack-hmr' : '',
// client error monitoring
'sentry.io', '*.sentry.io',
],
'script-src': [
KEY_WORDS.SELF,
// next.js generates and rebuilds source maps in dev using eval()
// https://github.com/vercel/next.js/issues/14221#issuecomment-657258278
isDev ? KEY_WORDS.UNSAFE_EVAL : '',
...MAIN_DOMAINS,
// hash of ColorModeScript
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
],
'style-src': [
KEY_WORDS.SELF,
...MAIN_DOMAINS,
// google fonts
'fonts.googleapis.com',
// yes, it is unsafe as it stands, but
// - we cannot use hashes because all styles are generated dynamically
// - we cannot use nonces since we are not following along SSR path
// - and still there is very small damage that can be cause by CSS-based XSS-attacks
// so we hope we are fine here till the first major incident :)
KEY_WORDS.UNSAFE_INLINE,
],
'img-src': [
KEY_WORDS.SELF,
KEY_WORDS.DATA,
...MAIN_DOMAINS,
// github avatars
'avatars.githubusercontent.com',
// network assets
...networkExternalAssets.map((url) => url.host),
],
'font-src': [
KEY_WORDS.DATA,
// google fonts
'*.gstatic.com',
'fonts.googleapis.com',
],
'object-src': [
KEY_WORDS.NONE,
],
'base-uri': [
KEY_WORDS.NONE,
],
'report-uri': [
process.env.SENTRY_CSP_REPORT_URI,
],
};
}
function getCspPolicy() {
const policyMap = makePolicyMap();
const policyString = Object.entries(policyMap)
.map(([ key, value ]) => {
if (!value || value.length === 0) {
return;
}
return [ key, value.join(' ') ].join(' ');
})
.filter(Boolean)
.join(';');
return policyString;
}
module.exports = getCspPolicy;
...@@ -10,6 +10,8 @@ import poaSokolIcon from 'icons/networks/poa-sokol.svg'; ...@@ -10,6 +10,8 @@ import poaSokolIcon from 'icons/networks/poa-sokol.svg';
import poaIcon from 'icons/networks/poa.svg'; import poaIcon from 'icons/networks/poa.svg';
import rskIcon from 'icons/networks/rsk.svg'; import rskIcon from 'icons/networks/rsk.svg';
import parseNetworkConfig from './parseNetworkConfig';
// will change later when we agree how to host network icons // will change later when we agree how to host network icons
const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = { const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
'xdai/mainnet': gnosisIcon, 'xdai/mainnet': gnosisIcon,
...@@ -24,15 +26,13 @@ const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGEleme ...@@ -24,15 +26,13 @@ const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGEleme
'artis/sigma1': artisIcon, 'artis/sigma1': artisIcon,
}; };
export const NETWORKS: Array<Network> = (() => { const NETWORKS: Array<Network> = (() => {
try { const networksFromConfig: Array<Network> = parseNetworkConfig();
const networksFromConfig: Array<Network> = JSON.parse(process.env.NEXT_PUBLIC_SUPPORTED_NETWORKS || '[]'); return networksFromConfig.map((network) => ({ ...network, icon: network.icon || ICONS[`${ network.type }/${ network.subType }`] }));
return networksFromConfig.map((network) => ({ ...network, icon: network.icon || ICONS[`${ network.type }/${ network.subType }`] }));
} catch (error) {
return [];
}
})(); })();
export default NETWORKS;
// for easy env creation // for easy env creation
// const FOR_CONFIG = [ // const FOR_CONFIG = [
// { // {
...@@ -105,13 +105,3 @@ export const NETWORKS: Array<Network> = (() => { ...@@ -105,13 +105,3 @@ export const NETWORKS: Array<Network> = (() => {
// group: 'other', // group: 'other',
// }, // },
// ]; // ];
export const ACCOUNT_ROUTES = [ '/watchlist', '/tag_address', '/tag_transaction', '/public_tags_request', '/api_key', '/custom_abi' ];
export function isAccountRoute(route: string) {
return ACCOUNT_ROUTES.includes(route);
}
export function getAvailablePaths() {
return NETWORKS.map(({ type, subType }) => ({ params: { network_type: type, network_sub_type: subType } }));
}
import NETWORKS from './availableNetworks';
export default function getAvailablePaths() {
return NETWORKS.map(({ type, subType }) => ({ params: { network_type: type, network_sub_type: subType } }));
}
export const ACCOUNT_ROUTES = [ '/watchlist', '/tag_address', '/tag_transaction', '/public_tags_request', '/api_key', '/custom_abi' ];
export default function isAccountRoute(route: string) {
return ACCOUNT_ROUTES.includes(route);
}
// should be CommonJS module since it used for next.config.js
function parseNetworkConfig() {
try {
return JSON.parse(process.env.NEXT_PUBLIC_SUPPORTED_NETWORKS || '[]');
} catch (error) {
return [];
}
}
module.exports = parseNetworkConfig;
...@@ -2,6 +2,8 @@ const { withSentryConfig } = require('@sentry/nextjs'); ...@@ -2,6 +2,8 @@ const { withSentryConfig } = require('@sentry/nextjs');
const withReactSvg = require('next-react-svg'); const withReactSvg = require('next-react-svg');
const path = require('path'); const path = require('path');
const headers = require('./configs/nextjs/headers');
const moduleExports = { const moduleExports = {
include: path.resolve(__dirname, 'icons'), include: path.resolve(__dirname, 'icons'),
reactStrictMode: true, reactStrictMode: true,
...@@ -24,6 +26,7 @@ const moduleExports = { ...@@ -24,6 +26,7 @@ const moduleExports = {
}, },
]; ];
}, },
headers,
output: 'standalone', output: 'standalone',
}; };
...@@ -37,6 +40,9 @@ const sentryWebpackPluginOptions = { ...@@ -37,6 +40,9 @@ const sentryWebpackPluginOptions = {
silent: true, // Suppresses all logs silent: true, // Suppresses all logs
// For all available options, see: // For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options. // https://github.com/getsentry/sentry-webpack-plugin#options.
deploy: {
env: process.env.VERCEL_ENV || process.env.NODE_ENV,
},
}; };
module.exports = withReactSvg(withSentryConfig(moduleExports, sentryWebpackPluginOptions)); module.exports = withReactSvg(withSentryConfig(moduleExports, sentryWebpackPluginOptions));
...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import { getAvailablePaths } from 'lib/networks'; import getAvailablePaths from 'lib/networks/getAvailablePaths';
import ApiKeys from 'ui/pages/ApiKeys'; import ApiKeys from 'ui/pages/ApiKeys';
const ApiKeysPage: NextPage = () => { const ApiKeysPage: NextPage = () => {
......
...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import { getAvailablePaths } from 'lib/networks'; import getAvailablePaths from 'lib/networks/getAvailablePaths';
import CustomAbi from 'ui/pages/CustomAbi'; import CustomAbi from 'ui/pages/CustomAbi';
const CustomAbiPage: NextPage = () => { const CustomAbiPage: NextPage = () => {
......
...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import { getAvailablePaths } from 'lib/networks'; import getAvailablePaths from 'lib/networks/getAvailablePaths';
import PublicTags from 'ui/pages/PublicTags'; import PublicTags from 'ui/pages/PublicTags';
const PublicTagsPage: NextPage = () => { const PublicTagsPage: NextPage = () => {
......
...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import { getAvailablePaths } from 'lib/networks'; import getAvailablePaths from 'lib/networks/getAvailablePaths';
import PrivateTags from 'ui/pages/PrivateTags'; import PrivateTags from 'ui/pages/PrivateTags';
const AddressTagsPage: NextPage = () => { const AddressTagsPage: NextPage = () => {
......
...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import { getAvailablePaths } from 'lib/networks'; import getAvailablePaths from 'lib/networks/getAvailablePaths';
import PrivateTags from 'ui/pages/PrivateTags'; import PrivateTags from 'ui/pages/PrivateTags';
const TransactionTagsPage: NextPage = () => { const TransactionTagsPage: NextPage = () => {
......
...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import { getAvailablePaths } from 'lib/networks'; import getAvailablePaths from 'lib/networks/getAvailablePaths';
import WatchList from 'ui/pages/Watchlist'; import WatchList from 'ui/pages/Watchlist';
const WatchListPage: NextPage = () => { const WatchListPage: NextPage = () => {
......
...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next'; ...@@ -2,7 +2,7 @@ import type { NextPage, GetStaticPaths } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import { getAvailablePaths } from 'lib/networks'; import getAvailablePaths from 'lib/networks/getAvailablePaths';
import MyProfile from 'ui/pages/MyProfile'; import MyProfile from 'ui/pages/MyProfile';
const MyProfilePage: NextPage = () => { const MyProfilePage: NextPage = () => {
......
...@@ -3,7 +3,7 @@ import type { NextPage, GetStaticPaths } from 'next'; ...@@ -3,7 +3,7 @@ import type { NextPage, GetStaticPaths } from 'next';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { getAvailablePaths } from 'lib/networks'; import getAvailablePaths from 'lib/networks/getAvailablePaths';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
const Home: NextPage = () => { const Home: NextPage = () => {
......
...@@ -13,10 +13,6 @@ class MyDocument extends Document { ...@@ -13,10 +13,6 @@ class MyDocument extends Document {
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<link
href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,500;0,600;1,400&display=swap"
rel="stylesheet"
/>
</Head> </Head>
<body> <body>
<ColorModeScript initialColorMode={ theme.config.initialColorMode }/> <ColorModeScript initialColorMode={ theme.config.initialColorMode }/>
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
const ENV = process.env.NEXT_PUBLIC_VERCEL_ENV || process.env.NODE_ENV;
Sentry.init({ Sentry.init({
environment: ENV,
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
......
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
const ENV = process.env.VERCEL_ENV || process.env.NODE_ENV;
Sentry.init({ Sentry.init({
environment: ENV,
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
......
...@@ -4,7 +4,7 @@ import React from 'react'; ...@@ -4,7 +4,7 @@ import React from 'react';
import type { NetworkGroup } from 'types/networks'; import type { NetworkGroup } from 'types/networks';
import { NETWORKS } from 'lib/networks'; import NETWORKS from 'lib/networks/availableNetworks';
import NetworkMenuLink from './NetworkMenuLink'; import NetworkMenuLink from './NetworkMenuLink';
......
...@@ -5,7 +5,7 @@ import React from 'react'; ...@@ -5,7 +5,7 @@ import React from 'react';
import type { NetworkGroup } from 'types/networks'; import type { NetworkGroup } from 'types/networks';
import { NETWORKS } from 'lib/networks'; import NETWORKS from 'lib/networks/availableNetworks';
import NetworkMenuLink from './NetworkMenuLink'; import NetworkMenuLink from './NetworkMenuLink';
......
...@@ -6,7 +6,7 @@ import type { Network } from 'types/networks'; ...@@ -6,7 +6,7 @@ import type { Network } from 'types/networks';
import checkIcon from 'icons/check.svg'; import checkIcon from 'icons/check.svg';
import placeholderIcon from 'icons/networks/placeholder.svg'; import placeholderIcon from 'icons/networks/placeholder.svg';
import { isAccountRoute } from 'lib/networks'; import isAccountRoute from 'lib/networks/isAccountRoute';
import useColors from './useColors'; import useColors from './useColors';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment