Skip to content

Commit d69b809

Browse files
authored
ENVs integrity check (blockscout#1039)
* group account envs in docs * remove NEXT_PUBLIC_LOGOUT_RETURN_URL env * simple ENVs checker * add types for all envs * group values in config * global envs types * text tweaks * fixes * [skip ci] fix docker build
1 parent b65d96a commit d69b809

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2096
-109
lines changed

.dockerignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
Dockerfile
22
.dockerignore
33
node_modules
4+
/**/node_modules
45
node_modules_linux
56
npm-debug.log
67
README.md
78
.next
8-
.git
9+
.git
10+
*.tsbuildinfo
11+
.eslintcache
12+
/test-results/
13+
/playwright-report/

.env.template

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ NEXT_PUBLIC_OTHER_LINKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_OTHER_LINKS__
3636
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_CONFIG_URL__
3737
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM__
3838
NEXT_PUBLIC_LOGOUT_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_URL__
39-
NEXT_PUBLIC_LOGOUT_RETURN_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_RETURN_URL__
4039
NEXT_PUBLIC_HOMEPAGE_CHARTS=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_CHARTS__
4140
NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR__
4241
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND__

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ module.exports = {
289289
},
290290
},
291291
{
292-
files: [ 'configs/**/*.js', 'configs/**/*.ts', '*.config.ts', 'playwright/**/*.ts' ],
292+
files: [ 'configs/**/*.js', 'configs/**/*.ts', '*.config.ts', 'playwright/**/*.ts', 'deploy/tools/**/*.ts' ],
293293
rules: {
294294
// for configs allow to consume env variables from process.env directly
295295
'no-restricted-properties': [ 0 ],

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# dependencies
44
/node_modules
55
/node_modules_linux
6+
/**/node_modules
67
/.pnp
78
.pnp.js
89

@@ -48,3 +49,8 @@ yarn-error.log*
4849
/playwright/envs.js
4950

5051
**.dec**
52+
53+
# tools: envs-validator
54+
/deploy/tools/envs-validator/index.js
55+
/deploy/tools/envs-validator/envs.ts
56+
/deploy/tools/envs-validator/schema.ts

Dockerfile

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,49 @@
1-
# Install dependencies only when needed
1+
# *****************************
2+
# *** STAGE 1: Dependencies ***
3+
# *****************************
24
FROM node:18-alpine AS deps
35
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
46
RUN apk add --no-cache libc6-compat
5-
WORKDIR /app
67

7-
# Install dependencies based on the preferred package manager
8+
# Install dependencies for App
9+
WORKDIR /app
810
COPY package.json yarn.lock ./
911
RUN apk add git
1012
RUN yarn --frozen-lockfile
1113

12-
# Rebuild the source code only when needed
14+
# Install dependencies for ENVs checker
15+
WORKDIR /envs-validator
16+
COPY ./deploy/tools/envs-validator/package.json ./deploy/tools/envs-validator/yarn.lock ./
17+
RUN yarn --frozen-lockfile
18+
19+
# *****************************
20+
# ****** STAGE 2: Build *******
21+
# *****************************
1322
FROM node:18-alpine AS builder
23+
24+
# Build app for production
1425
WORKDIR /app
1526
COPY --from=deps /app/node_modules ./node_modules
1627
COPY . .
1728
COPY .env.template .env.production
29+
RUN rm -rf ./deploy/tools/envs-validator
1830

1931
# Next.js collects completely anonymous telemetry data about general usage.
2032
# Learn more here: https://nextjs.org/telemetry
2133
# Uncomment the following line in case you want to disable telemetry during the build.
2234
# ENV NEXT_TELEMETRY_DISABLED 1
35+
RUN yarn build
2336

24-
ARG SENTRY_DSN
25-
ARG NEXT_PUBLIC_SENTRY_DSN
26-
ARG SENTRY_CSP_REPORT_URI
27-
ARG SENTRY_AUTH_TOKEN
28-
37+
# Build ENVs checker
38+
WORKDIR /envs-validator
39+
COPY --from=deps /envs-validator/node_modules ./node_modules
40+
COPY ./deploy/tools/envs-validator .
41+
COPY ./types/envs.ts .
2942
RUN yarn build
3043

44+
# *****************************
45+
# ******* STAGE 3: Run ********
46+
# *****************************
3147
# Production image, copy all the files and run next
3248
FROM node:18-alpine AS runner
3349
WORKDIR /app
@@ -50,6 +66,7 @@ RUN adduser --system --uid 1001 nextjs
5066
COPY --from=builder /app/next.config.js ./
5167
COPY --from=builder /app/public ./public
5268
COPY --from=builder /app/package.json ./package.json
69+
COPY --from=builder /envs-validator/index.js ./envs-validator.js
5370

5471
# Copy scripts and ENV templates file
5572
COPY ./deploy/scripts/entrypoint.sh .
@@ -61,7 +78,6 @@ COPY .env.template .
6178
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
6279
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
6380

64-
# Execute script for replace build ENV with run ones
6581
RUN apk add --no-cache --upgrade bash
6682
RUN ["chmod", "+x", "./entrypoint.sh"]
6783
RUN ["chmod", "+x", "./replace_envs.sh"]

configs/app/config.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { ChainIndicatorId } from 'ui/home/indicators/types';
88

99
import stripTrailingSlash from 'lib/stripTrailingSlash';
1010

11-
const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"');
11+
const getEnvValue = <T extends string>(env: T | undefined): T | undefined => env?.replaceAll('\'', '"') as T;
1212
const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
1313
try {
1414
return JSON.parse(env || 'null') as DataType | null;
@@ -70,8 +70,9 @@ const logoutUrl = (() => {
7070
try {
7171
const envUrl = getEnvValue(process.env.NEXT_PUBLIC_LOGOUT_URL);
7272
const auth0ClientId = getEnvValue(process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID);
73-
const returnUrl = getEnvValue(process.env.NEXT_PUBLIC_LOGOUT_RETURN_URL);
74-
if (!envUrl || !auth0ClientId || !returnUrl) {
73+
const returnUrl = authUrl + '/auth/logout';
74+
75+
if (!envUrl || !auth0ClientId) {
7576
throw Error();
7677
}
7778

@@ -113,20 +114,30 @@ const config = Object.freeze({
113114
rpcUrl: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_RPC_URL),
114115
isTestnet: getEnvValue(process.env.NEXT_PUBLIC_IS_TESTNET) === 'true',
115116
},
116-
otherLinks: parseEnvJson<Array<NavItemExternal>>(getEnvValue(process.env.NEXT_PUBLIC_OTHER_LINKS)) || [],
117-
featuredNetworks: getEnvValue(process.env.NEXT_PUBLIC_FEATURED_NETWORKS),
118-
footerLinks: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_LINKS),
119-
frontendVersion: getEnvValue(process.env.NEXT_PUBLIC_GIT_TAG),
120-
frontendCommit: getEnvValue(process.env.NEXT_PUBLIC_GIT_COMMIT_SHA),
121-
isAccountSupported: getEnvValue(process.env.NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED) === 'true',
122-
marketplaceConfigUrl: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_CONFIG_URL),
123-
marketplaceSubmitForm: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM),
124-
protocol: appSchema,
125-
host: appHost,
126-
port: appPort,
127-
baseUrl,
128-
authUrl,
129-
logoutUrl,
117+
navigation: {
118+
otherLinks: parseEnvJson<Array<NavItemExternal>>(getEnvValue(process.env.NEXT_PUBLIC_OTHER_LINKS)) || [],
119+
featuredNetworks: getEnvValue(process.env.NEXT_PUBLIC_FEATURED_NETWORKS),
120+
},
121+
footer: {
122+
links: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_LINKS),
123+
frontendVersion: getEnvValue(process.env.NEXT_PUBLIC_GIT_TAG),
124+
frontendCommit: getEnvValue(process.env.NEXT_PUBLIC_GIT_COMMIT_SHA),
125+
},
126+
marketplace: {
127+
configUrl: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_CONFIG_URL),
128+
submitForm: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM),
129+
},
130+
account: {
131+
isEnabled: getEnvValue(process.env.NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED) === 'true',
132+
authUrl,
133+
logoutUrl,
134+
},
135+
app: {
136+
protocol: appSchema,
137+
host: appHost,
138+
port: appPort,
139+
baseUrl,
140+
},
130141
ad: {
131142
adBannerProvider: getAdBannerProvider(),
132143
adTextProvider: getAdTextProvider(),

configs/envs/.env.poa_core

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ NEXT_PUBLIC_NETWORK_CURRENCY_NAME=POA
2727
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=POA
2828
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
2929
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0x029a799563238d0e75e20be2f4bda0ea68d00172
30-
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
30+
#NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
3131
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
3232
NEXT_PUBLIC_NETWORK_RPC_URL=https://core.poa.network
33-
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
33+
#NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
3434

3535
# api config
3636
NEXT_PUBLIC_API_HOST=blockscout.com

deploy/scripts/entrypoint.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
#!/bin/bash
22

3+
# Check run-time ENVs values integrity
4+
node "$(dirname "$0")/envs-validator.js" "$input"
5+
if [ $? != 0 ]; then
6+
echo ENV integrity check failed. 1>&2 && exit 1
7+
fi
8+
9+
# Execute script for replace build-time ENVs placeholders with their values at runtime
310
./replace_envs.sh
411

512
echo "starting Nextjs"

deploy/tools/envs-validator/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* eslint-disable no-console */
2+
import type { ZodError } from 'zod-validation-error';
3+
import { fromZodError } from 'zod-validation-error';
4+
5+
import { nextPublicEnvsSchema } from './schema';
6+
7+
try {
8+
const appEnvs = Object.entries(process.env)
9+
.filter(([ key ]) => key.startsWith('NEXT_PUBLIC_'))
10+
.reduce((result, [ key, value ]) => {
11+
result[key] = value || '';
12+
return result;
13+
}, {} as Record<string, string>);
14+
15+
console.log(`⏳ Validating environment variables schema...`);
16+
nextPublicEnvsSchema.parse(appEnvs);
17+
console.log('👍 All good!\n');
18+
} catch (error) {
19+
const validationError = fromZodError(
20+
error as ZodError,
21+
{
22+
prefix: '',
23+
prefixSeparator: '\n ',
24+
issueSeparator: ';\n ',
25+
},
26+
);
27+
console.log(validationError);
28+
console.log('🚨 ENV set is invalid\n');
29+
process.exit(1);
30+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "envs-validator",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"scripts": {
7+
"build": "yarn ts-to-zod ./envs.ts ./schema.ts && yarn webpack-cli -c ./webpack.config.js",
8+
"validate": "node ./index.js",
9+
"dev": "cp ../../../types/envs.ts ./ && yarn build && yarn dotenv -e ../../../configs/envs/.env.poa_core yarn validate"
10+
},
11+
"dependencies": {
12+
"ts-loader": "^9.4.4",
13+
"ts-to-zod": "^3.1.3",
14+
"webpack": "^5.88.2",
15+
"webpack-cli": "^5.1.4",
16+
"zod": "^3.21.4",
17+
"zod-validation-error": "^1.3.1"
18+
},
19+
"devDependencies": {
20+
"dotenv-cli": "^7.2.1"
21+
}
22+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es6",
4+
"skipLibCheck": true,
5+
"strict": true,
6+
"esModuleInterop": true,
7+
"module": "CommonJS",
8+
"moduleResolution": "node",
9+
"isolatedModules": true,
10+
"incremental": true,
11+
"baseUrl": "."
12+
},
13+
"include": ["./schema.ts"],
14+
"exclude": ["node_modules"]
15+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const path = require('path');
2+
module.exports = {
3+
mode: 'production',
4+
entry: path.resolve(__dirname) + '/index.ts',
5+
module: {
6+
rules: [
7+
{
8+
test: /\.tsx?$/,
9+
use: 'ts-loader',
10+
exclude: /node_modules/,
11+
},
12+
],
13+
},
14+
resolve: {
15+
extensions: [ '.tsx', '.ts', '.js' ],
16+
},
17+
output: {
18+
filename: 'index.js',
19+
path: path.resolve(__dirname),
20+
},
21+
};

0 commit comments

Comments
 (0)