From 38d373cfdef8ad069c3a1b0fea26c03790dcb508 Mon Sep 17 00:00:00 2001 From: tom goriunov Date: Mon, 2 Oct 2023 11:13:34 -0300 Subject: [PATCH] custom validator for ENVs with URL value (#1238) Fixes #1236 --- configs/envs/.env.eth | 2 +- configs/envs/.env.eth_goerli | 2 +- configs/envs/.env.localhost | 2 +- configs/envs/.env.main | 2 +- configs/envs/.env.poa_core | 2 +- deploy/tools/envs-validator/schema.ts | 88 ++++++++++++++++++--------- 6 files changed, 63 insertions(+), 35 deletions(-) diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 475da9a39b..8486ce846d 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -35,7 +35,7 @@ NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_HAS_BEACON_CHAIN=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -# NEXT_PUBLIC_AUTH_URL=http://localhost:3000 +NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com diff --git a/configs/envs/.env.eth_goerli b/configs/envs/.env.eth_goerli index b6f1d9ab6c..9d71ce856b 100644 --- a/configs/envs/.env.eth_goerli +++ b/configs/envs/.env.eth_goerli @@ -37,7 +37,7 @@ NEXT_PUBLIC_APP_INSTANCE=local NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -# NEXT_PUBLIC_AUTH_URL=http://localhost:3000 +NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C diff --git a/configs/envs/.env.localhost b/configs/envs/.env.localhost index fc047b74fb..ff2e0d843b 100644 --- a/configs/envs/.env.localhost +++ b/configs/envs/.env.localhost @@ -36,5 +36,5 @@ NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.a NEXT_PUBLIC_APP_INSTANCE=local NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -# NEXT_PUBLIC_AUTH_URL=http://localhost:3000 +NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout diff --git a/configs/envs/.env.main b/configs/envs/.env.main index 64be1a8666..3700da7ada 100644 --- a/configs/envs/.env.main +++ b/configs/envs/.env.main @@ -38,7 +38,7 @@ NEXT_PUBLIC_APP_INSTANCE=local NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -# NEXT_PUBLIC_AUTH_URL=http://localhost:3000 +NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C diff --git a/configs/envs/.env.poa_core b/configs/envs/.env.poa_core index a74a582fc5..f46ee88ca5 100644 --- a/configs/envs/.env.poa_core +++ b/configs/envs/.env.poa_core @@ -35,7 +35,7 @@ NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.a NEXT_PUBLIC_APP_INSTANCE=local NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -# NEXT_PUBLIC_AUTH_URL=http://localhost:3000 +NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation NEXT_PUBLIC_NETWORK_RPC_URL=https://core.poa.network diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 0a7ee5b10f..30afaf0cb0 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -1,3 +1,11 @@ +declare module 'yup' { + interface StringSchema { + // Yup's URL validator is not perfect so we made our own + // https://github.com/jquense/yup/pull/1859 + url(): never; + } +} + import * as yup from 'yup'; import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; @@ -19,22 +27,42 @@ import * as regexp from '../../../lib/regexp'; const protocols = [ 'http', 'https' ]; +const urlTest: yup.TestConfig = { + name: 'url', + test: (value: unknown) => { + if (!value) { + return true; + } + + try { + if (typeof value === 'string') { + new URL(value); + return true; + } + } catch (error) {} + + return false; + }, + message: '${path} is not a valid URL', + exclusive: true, +}; + const marketplaceAppSchema: yup.ObjectSchema = yup .object({ id: yup.string().required(), external: yup.boolean(), title: yup.string().required(), - logo: yup.string().url().required(), - logoDarkMode: yup.string().url(), + logo: yup.string().test(urlTest).required(), + logoDarkMode: yup.string().test(urlTest), shortDescription: yup.string().required(), categories: yup.array().of(yup.string().required()).required(), - url: yup.string().url().required(), + url: yup.string().test(urlTest).required(), author: yup.string().required(), description: yup.string().required(), - site: yup.string().url(), - twitter: yup.string().url(), - telegram: yup.string().url(), - github: yup.string().url(), + site: yup.string().test(urlTest), + twitter: yup.string().test(urlTest), + telegram: yup.string().test(urlTest), + github: yup.string().test(urlTest), }); const marketplaceSchema = yup @@ -48,7 +76,7 @@ const marketplaceSchema = yup .string() .when('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', { is: (value: Array) => value.length > 0, - then: (schema) => schema.url().required(), + then: (schema) => schema.test(urlTest).required(), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_CONFIG_URL'), }), }); @@ -77,14 +105,14 @@ const rollupSchema = yup .string() .when('NEXT_PUBLIC_IS_L2_NETWORK', { is: (value: boolean) => value, - then: (schema) => schema.url().required(), + then: (schema) => schema.test(urlTest).required(), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_L1_BASE_URL cannot not be used if NEXT_PUBLIC_IS_L2_NETWORK is not set to "true"'), }), NEXT_PUBLIC_L2_WITHDRAWAL_URL: yup .string() .when('NEXT_PUBLIC_IS_L2_NETWORK', { is: (value: string) => value, - then: (schema) => schema.url().required(), + then: (schema) => schema.test(urlTest).required(), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_IS_L2_NETWORK is not set to "true"'), }), }); @@ -115,12 +143,12 @@ const adsBannerSchema = yup const sentrySchema = yup .object() .shape({ - NEXT_PUBLIC_SENTRY_DSN: yup.string().url(), + NEXT_PUBLIC_SENTRY_DSN: yup.string().test(urlTest), SENTRY_CSP_REPORT_URI: yup .string() .when('NEXT_PUBLIC_SENTRY_DSN', { is: (value: string) => Boolean(value), - then: (schema) => schema.url(), + then: (schema) => schema.test(urlTest), otherwise: (schema) => schema.max(-1, 'SENTRY_CSP_REPORT_URI cannot not be used without NEXT_PUBLIC_SENTRY_DSN'), }), NEXT_PUBLIC_APP_INSTANCE: yup @@ -154,21 +182,21 @@ const accountSchema = yup .string() .when('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', { is: (value: boolean) => value, - then: (schema) => schema.url(), + then: (schema) => schema.test(urlTest), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_AUTH_URL cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not set to "true"'), }), NEXT_PUBLIC_LOGOUT_URL: yup .string() .when('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', { is: (value: boolean) => value, - then: (schema) => schema.url().required(), + then: (schema) => schema.test(urlTest).required(), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_LOGOUT_URL cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not set to "true"'), }), NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: yup .string() .when('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', { is: (value: boolean) => value, - then: (schema) => schema.url(), + then: (schema) => schema.test(urlTest), otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ADMIN_SERVICE_API_HOST cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not set to "true"'), }), }); @@ -177,9 +205,9 @@ const featuredNetworkSchema: yup.ObjectSchema = yup .object() .shape({ title: yup.string().required(), - url: yup.string().url().required(), + url: yup.string().test(urlTest).required(), group: yup.string().oneOf(NETWORK_GROUPS).required(), - icon: yup.string().url(), + icon: yup.string().test(urlTest), isActive: yup.boolean(), invertIconInDarkMode: yup.boolean(), }); @@ -187,13 +215,13 @@ const featuredNetworkSchema: yup.ObjectSchema = yup const navItemExternalSchema: yup.ObjectSchema = yup .object({ text: yup.string().required(), - url: yup.string().url().required(), + url: yup.string().test(urlTest).required(), }); const footerLinkSchema: yup.ObjectSchema = yup .object({ text: yup.string().required(), - url: yup.string().url().required(), + url: yup.string().test(urlTest).required(), }); const footerLinkGroupSchema: yup.ObjectSchema = yup @@ -208,7 +236,7 @@ const footerLinkGroupSchema: yup.ObjectSchema = yup const networkExplorerSchema: yup.ObjectSchema = yup .object({ title: yup.string().required(), - baseUrl: yup.string().url().required(), + baseUrl: yup.string().test(urlTest).required(), paths: yup .object() .shape({ @@ -241,7 +269,7 @@ const schema = yup NEXT_PUBLIC_NETWORK_NAME: yup.string().required(), NEXT_PUBLIC_NETWORK_SHORT_NAME: yup.string(), NEXT_PUBLIC_NETWORK_ID: yup.number().positive().integer().required(), - NEXT_PUBLIC_NETWORK_RPC_URL: yup.string().url(), + NEXT_PUBLIC_NETWORK_RPC_URL: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_CURRENCY_NAME: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(), @@ -278,10 +306,10 @@ const schema = yup .transform(getEnvValue) .json() .of(navItemExternalSchema), - NEXT_PUBLIC_NETWORK_LOGO: yup.string().url(), - NEXT_PUBLIC_NETWORK_LOGO_DARK: yup.string().url(), - NEXT_PUBLIC_NETWORK_ICON: yup.string().url(), - NEXT_PUBLIC_NETWORK_ICON_DARK: yup.string().url(), + NEXT_PUBLIC_NETWORK_LOGO: yup.string().test(urlTest), + NEXT_PUBLIC_NETWORK_LOGO_DARK: yup.string().test(urlTest), + NEXT_PUBLIC_NETWORK_ICON: yup.string().test(urlTest), + NEXT_PUBLIC_NETWORK_ICON_DARK: yup.string().test(urlTest), // c. footer NEXT_PUBLIC_FOOTER_LINKS: yup @@ -307,10 +335,10 @@ const schema = yup NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(), // 5. Features configuration - NEXT_PUBLIC_API_SPEC_URL: yup.string().url(), - NEXT_PUBLIC_STATS_API_HOST: yup.string().url(), - NEXT_PUBLIC_VISUALIZE_API_HOST: yup.string().url(), - NEXT_PUBLIC_CONTRACT_INFO_API_HOST: yup.string().url(), + NEXT_PUBLIC_API_SPEC_URL: yup.string().test(urlTest), + NEXT_PUBLIC_STATS_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_VISUALIZE_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_CONTRACT_INFO_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup.string().matches(regexp.HEX_REGEXP), NEXT_PUBLIC_WEB3_WALLETS: yup .mixed() @@ -328,7 +356,7 @@ const schema = yup NEXT_PUBLIC_AD_TEXT_PROVIDER: yup.string().oneOf(SUPPORTED_AD_TEXT_PROVIDERS), NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE: yup.boolean(), NEXT_PUBLIC_OG_DESCRIPTION: yup.string(), - NEXT_PUBLIC_OG_IMAGE_URL: yup.string().url(), + NEXT_PUBLIC_OG_IMAGE_URL: yup.string().test(urlTest), // 6. External services envs NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(),