diff --git a/.env-template b/.env-template new file mode 100644 index 00000000..bf58525f --- /dev/null +++ b/.env-template @@ -0,0 +1,33 @@ + +# Mandatory configuration +# +REACT_APP_SHARETRIBE_SDK_CLIENT_ID= +REACT_APP_GOOGLE_MAPS_API_KEY= +REACT_APP_STRIPE_PUBLISHABLE_KEY= + +# Defaults +# + +REACT_APP_SHARETRIBE_SDK_BASE_URL=http://localhost:8088 + +REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD +REACT_APP_CANONICAL_ROOT_URL=http://localhost:3000 +PORT=4000 + +# This is overwritten by configuration in .env.development and +# .env.test. In production deployments use env variable and set it to +# 'production' +REACT_APP_ENV=production + + +# Options. Uncomment and set to test. +# + +# REACT_APP_SHARETRIBE_USING_SSL=true +# SERVER_SHARETRIBE_TRUST_PROXY=true +# REACT_APP_PUBLIC_SENTRY_DSN=change-me +# SERVER_SENTRY_DSN=change-me +# REACT_APP_CSP=report +# BASIC_AUTH_USERNAME=sharetribe +# BASIC_AUTH_PASSWORD=secret +# REACT_APP_GOOGLE_ANALYTICS_ID=change-me \ No newline at end of file diff --git a/.env.development b/.env.development index cff1ca7b..8e746630 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1 @@ -NODE_ENV=development REACT_APP_ENV=development diff --git a/.env.test b/.env.test index d8e17a78..fceacf8e 100644 --- a/.env.test +++ b/.env.test @@ -1,3 +1,3 @@ -NODE_ENV=test REACT_APP_ENV=test REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD +REACT_APP_CANONICAL_ROOT_URL=http://localhost:3000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a687143..f25c23c5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ build .DS_Store .env npm-debug.log -/.env.development.local +yarn-error.log diff --git a/README.md b/README.md index bd69c8de..22a4dfa6 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,16 @@ Clone this repository and install dependencies: ## Development +**First, you need to configure Client Id, Google Maps API key and +Stripe publishable API key via [environment +variables](./docs/env.md).** The quickest way to do this is to copy +.env-template -> .env, and edit the file to add the three mandatory +configuration values. + To develop the application and to see changes live, start the frontend development server: yarn run dev -**You need to configure Client Id, Google Maps API key and Stripe -publishable API key via [environment variables](./docs/env.md).** - **Known issues:** * Adding/changing `import`s may not be synced properly with ESLint. You may see an error diff --git a/docs/env.md b/docs/env.md index 48578294..da6ecf69 100644 --- a/docs/env.md +++ b/docs/env.md @@ -3,27 +3,32 @@ The following configuration variables can be set to control the Starter App behaviour. Most of them have defaults that work for development environment. For production deploys most should be set. -| Variable | Must be set? | Default | Description | -| ----------------------------------------- | ------------ | ----------------------- | ------------------------------------------------------------------------------------------------------- | -| REACT_APP_GOOGLE_MAPS_API_KEY | X | - | See: [Google Maps API key](./google-maps.md) | -| REACT_APP_SHARETRIBE_SDK_CLIENT_ID | X | - | Client ID (API key). You will get this from the Sharetribe team. | -| REACT_APP_STRIPE_PUBLISHABLE_KEY | X | - | Stripe publishable API key for generating tokens with Stripe API. | -| REACT_APP_SHARETRIBE_SDK_BASE_URL | | `http://localhost:8088` | The base url to access the Marketplace API. | -| REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY | | USD | The currency used in the Marketplace as ISO 4217 currency code. For example: USD, EUR, CAD, AUD, etc. | -| REACT_APP_CANONICAL_ROOT_URL | | `http://localhost:3000` | Canonical root url of the marketplace is running. Needed for social media sharing and SEO optimization. | -| NODE_ENV | | ? | Node env. Use 'dev' for development and 'production' for production. | -| REACT_APP_ENV | | production | TODO Didn't understand NODE_ENV vs this? | -| REACT_APP_SHARETRIBE_USING_SSL | | false | Redirect HTTP to HTTPS. | -| SERVER_SHARETRIBE_TRUST_PROXY | | false | Set when running the app behind a reverse proxy. | -| REACT_APP_PUBLIC_SENTRY_DSN | | - | See: [Error logging with Sentry](./sentry.md) | -| SERVER_SENTRY_DSN | | - | See: [Error logging with Sentry](./sentry.md) | -| REACT_APP_CSP | | - | See: [Content Security Policy (CSP)](./content-security-policy.md) | -| BASIC_AUTH_USERNAME | | - | Set to enable HTTP Basic Auth | -| BASIC_AUTH_PASSWORD | | - | Set to enable HTTP Basic Auth | -| REACT_APP_GOOGLE_ANALYTICS_ID | | - | See: [Google Analytics](./analytics.md) | +| Variable | Must be set? | Default | Description | +| ----------------------------------------- | ------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------- | +| REACT_APP_GOOGLE_MAPS_API_KEY | X | - | See: [Google Maps API key](./google-maps.md) | +| REACT_APP_SHARETRIBE_SDK_CLIENT_ID | X | - | Client ID (API key). You will get this from the Sharetribe team. | +| REACT_APP_STRIPE_PUBLISHABLE_KEY | X | - | Stripe publishable API key for generating tokens with Stripe API. Use test key (prefix pk*test*) for development. | +| REACT_APP_SHARETRIBE_SDK_BASE_URL | | http://localhost:8088 | The base url to access the Sharetribe Flex Marketplace API. | +| REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY | | USD | The currency used in the Marketplace as ISO 4217 currency code. For example: USD, EUR, CAD, AUD, etc. | +| REACT_APP_CANONICAL_ROOT_URL | | http://localhost:3000 | Canonical root url of the marketplace. Needed for social media sharing and SEO optimization. | +| NODE_ENV | | ? | Node env. Use 'development' for development and 'production' for production. | +| PORT | | 4000 | Port for server to accept connections. | +| REACT_APP_ENV | | production | Env for client side. Use the same value as with NODE_ENV. | +| REACT_APP_SHARETRIBE_USING_SSL | | false | Redirect HTTP to HTTPS? | +| SERVER_SHARETRIBE_TRUST_PROXY | | false | Set when running the app behind a reverse proxy, e.g. in Heroku. | +| REACT_APP_PUBLIC_SENTRY_DSN | | - | See: [Error logging with Sentry](./sentry.md) | +| SERVER_SENTRY_DSN | | - | See: [Error logging with Sentry](./sentry.md) | +| REACT_APP_CSP | | - | See: [Content Security Policy (CSP)](./content-security-policy.md) | +| BASIC_AUTH_USERNAME | | - | Set to enable HTTP Basic Auth | +| BASIC_AUTH_PASSWORD | | - | Set to enable HTTP Basic Auth | +| REACT_APP_GOOGLE_ANALYTICS_ID | | - | See: [Google Analytics](./analytics.md) | ## Defining the env variables -When the Starter App is started locally with `yarn run dev` you can set environment variables by -using the file .env.development.local. However, the server process doesn't currently pick up values -from this file. In production it is recommended to set the environment variables on OS level. +When the Starter App is started locally with `yarn run dev` or `yarn run dev-server`, you can set +environment variables by using the file .env file. The repository contains a template file +.env-template. Just copy that as .env and edit as necessary. + +In production, it's recommended that you set the configuration via env variables and do not deploy +an .env file. The client application will only be packaged with env variables that start with +REACT_APP. This way server secrets don't end up in client bundles. diff --git a/docs/google-maps.md b/docs/google-maps.md index 8104697b..1c9814d2 100644 --- a/docs/google-maps.md +++ b/docs/google-maps.md @@ -20,8 +20,7 @@ section of the Places library documentation to enable using the Google Places AP ## Setup the application to use the API key The application uses the `REACT_APP_GOOGLE_MAPS_API_KEY` environment variable for the key value. For -local development, you can add the variable in the Gitignored `.env.development.local` file in the -project root: +local development, you can add the variable in the Gitignored `.env` file in the project root: ``` REACT_APP_GOOGLE_MAPS_API_KEY=my-key-here diff --git a/package.json b/package.json index 3dbf651d..82bcada9 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "test-ci": "sharetribe-scripts test --env=jsdom --runInBand", "eject": "sharetribe-scripts eject", "start": "node server/index.js", - "dev-server": "export REACT_APP_ENV=development REACT_APP_CANONICAL_ROOT_URL=http://localhost:4000&&yarn run build&&nodemon --watch server server/index.js", + "dev-server": "export NODE_ENV=development REACT_APP_CANONICAL_ROOT_URL=http://localhost:4000&&yarn run build&&nodemon --watch server server/index.js", "heroku-prebuild": "./heroku-copy-ssh-key-from-env.sh", "heroku-postbuild": "yarn run build", "audit": "nsp check --preprocessor yarn" diff --git a/server/csp.js b/server/csp.js index 6c079b38..8f160125 100644 --- a/server/csp.js +++ b/server/csp.js @@ -1,5 +1,4 @@ const helmet = require('helmet'); -const { config } = require('./importer'); const dev = process.env.REACT_APP_ENV === 'development'; const googleAnalyticsEnabled = !!process.env.REACT_APP_GOOGLE_ANALYTICS_ID; @@ -21,7 +20,7 @@ module.exports = (reportUri, enforceSsl, reportOnly) => { const self = "'self'"; const inline = "'unsafe-inline'"; const data = 'data:'; - const sharetribeApi = config.sdk.baseUrl; + const sharetribeApi = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL; const sharetribeAssets = 'https://assets-sharetribecom.sharetribe.com/'; const imgix = '*.imgix.net/'; const loremPixel = 'https://lorempixel.com/'; diff --git a/server/env.js b/server/env.js new file mode 100644 index 00000000..9fe75209 --- /dev/null +++ b/server/env.js @@ -0,0 +1,41 @@ +/** + Implements .env file loading that mimicks the way create-react-app + does it. We want this to get consistent configuration handling + between client and node server. +*/ + +const fs = require('fs'); + +const NODE_ENV = process.env.NODE_ENV; + +if (!NODE_ENV) { + throw new Error('The NODE_ENV environment variable is required but was not specified.'); +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +var dotenvFiles = [ + `.env.${NODE_ENV}.local`, + `.env.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `env.local`, + '.env', +].filter(Boolean); + +const configureEnv = () => { + // Load environment variables from .env* files. Suppress warnings using silent + // if this file is missing. dotenv will never modify any environment variables + // that have already been set. + // https://github.com/motdotla/dotenv + dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + console.log('Loading env from file:' + dotenvFile); + require('dotenv').config({ path: dotenvFile }); + } + }); +}; + +module.exports = { + configureEnv: configureEnv, +}; diff --git a/server/importer.js b/server/importer.js index f572bcac..83eb744f 100644 --- a/server/importer.js +++ b/server/importer.js @@ -17,5 +17,4 @@ module.exports = { matchPathname: mainJs.matchPathname, configureStore: mainJs.configureStore, routeConfiguration: mainJs.routeConfiguration, - config: mainJs.config, }; diff --git a/server/index.js b/server/index.js index 44c9da50..a0fd6b52 100644 --- a/server/index.js +++ b/server/index.js @@ -16,6 +16,9 @@ // This enables nice stacktraces from the minified production bundle require('source-map-support').install(); +// Configure process.env with .env.* files +require('./env').configureEnv(); + const express = require('express'); const helmet = require('helmet'); const compression = require('compression'); @@ -35,12 +38,11 @@ const { sitemapStructure } = require('./sitemap'); const csp = require('./csp'); const buildPath = path.resolve(__dirname, '..', 'build'); -const env = process.env.REACT_APP_ENV || 'production'; +const env = process.env.REACT_APP_ENV; const dev = process.env.REACT_APP_ENV === 'development'; -const PORT = process.env.PORT || 4000; -const CLIENT_ID = - process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID || '08ec69f6-d37e-414d-83eb-324e94afddf0'; -const BASE_URL = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL || 'http://localhost:8088'; +const PORT = process.env.PORT; +const CLIENT_ID = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID; +const BASE_URL = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL; const USING_SSL = process.env.REACT_APP_SHARETRIBE_USING_SSL === 'true'; const TRUST_PROXY = process.env.SERVER_SHARETRIBE_TRUST_PROXY || null; const CSP = process.env.REACT_APP_CSP; diff --git a/server/sitemap.js b/server/sitemap.js index 4b9825c7..fec85b3d 100644 --- a/server/sitemap.js +++ b/server/sitemap.js @@ -1,9 +1,8 @@ const path = require('path'); const moment = require('moment'); -const { config } = require('./importer'); const buildPath = path.resolve(__dirname, '..', 'build'); -const PORT = process.env.PORT || 4000; +const PORT = process.env.PORT; const USING_SSL = process.env.REACT_APP_SHARETRIBE_USING_SSL === 'true'; /** @@ -44,17 +43,17 @@ const port = rootURL => { }; /** - * Return a structure for sitemap.xml and robots.txt to be - * used by the express-sitemap library. Uses the canonical - * URL value from config for domain and port information. + * Return a structure for sitemap.xml and robots.txt to be used by the + * express-sitemap library. Uses the canonical URL value from env + * config for domain and port information. */ exports.sitemapStructure = () => { const now = moment().format('YYYY-MM-DD'); return { - url: domain(config.canonicalRootURL), + url: domain(process.env.REACT_APP_CANONICAL_ROOT_URL), http: USING_SSL ? 'https' : 'http', - port: port(config.canonicalRootURL), + port: port(process.env.REACT_APP_CANONICAL_ROOT_URL), sitemap: path.join(buildPath, 'static', 'sitemap.xml'), robots: path.join(buildPath, 'robots.txt'), sitemapSubmission: '/static/sitemap.xml', diff --git a/src/config.js b/src/config.js index c678c8da..0d0972af 100644 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,6 @@ import * as custom from './marketplace-custom-config.js'; -const env = process.env.REACT_APP_ENV || 'production'; +const env = process.env.REACT_APP_ENV; const dev = process.env.REACT_APP_ENV === 'development'; // If you want to change the language, remember to also change the @@ -33,9 +33,9 @@ const bookingUnitType = 'line-item/night'; // react-scripts) require using the REACT_APP_ prefix to avoid // exposing server secrets to the client side. const sdkClientId = process.env.REACT_APP_SHARETRIBE_SDK_CLIENT_ID; -const sdkBaseUrl = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL || 'http://localhost:8088'; +const sdkBaseUrl = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL; -const currency = process.env.REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY || 'USD'; +const currency = process.env.REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY; // Sentry DSN (Data Source Name), a client key for authenticating calls to Sentry const sentryDsn = process.env.REACT_APP_PUBLIC_SENTRY_DSN; @@ -219,7 +219,7 @@ const postalCode = '00100'; const streetAddress = 'Bulevardi 14'; // Canonical root url is needed in social media sharing and SEO optimization purposes. -const canonicalRootURL = process.env.REACT_APP_CANONICAL_ROOT_URL || 'http://localhost:3000'; +const canonicalRootURL = process.env.REACT_APP_CANONICAL_ROOT_URL; // Site title is needed in meta tags (bots and social media sharing reads those) const siteTitle = 'Saunatime';