Skip to content

Commit

Permalink
refactor: Make env variables config more robust
Browse files Browse the repository at this point in the history
I really like having config variables in a JavaScript or a TypeScript
file, but Vercel is setup to expect .env files.
  • Loading branch information
maxpatiiuk committed Apr 14, 2024
1 parent f99fda1 commit 46b0040
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 58 deletions.
4 changes: 4 additions & 0 deletions backend/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Restrict access to the API to certain origins only. Replace kgbd..
# with the ID of your extension. Or, you can allow access to the API from all
# origins by setting ACCESS_CONTROL_ALLOW_ORIGIN=*
ACCESS_CONTROL_ALLOW_ORIGIN=chrome-extension://kgbbebdcmdgkbopcffmpgkgcmcoomhmh
5 changes: 5 additions & 0 deletions backend/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# The URL at which the /api/auth endpoint from the backend is hosted
AUTH_URL=https://calendar-plus.patii.uk/api/auth

# Replace kgbb... with the ID of the extension
GOOGLE_CLIENT_REDIRECT_URL=https://kgbbebdcmdgkbopcffmpgkgcmcoomhmh.chromiumapp.org/
4 changes: 4 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Fill this in with the values received after creating a Google OAuth2
# credentials
GOOGLE_CLIENT_ID=aaaaaaaaaaaa-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=AAAAAA-BBBBBBBBBBBBBBBBBBBBBBBBBBBB
5 changes: 5 additions & 0 deletions backend/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# The URL at which the /api/auth endpoint from the backend is hosted
AUTH_URL=http://localhost:3000/api/auth

# Replace kgbb... with the ID of the extension
GOOGLE_CLIENT_REDIRECT_URL=https://kgbbebdcmdgkbopcffmpgkgcmcoomhmh.chromiumapp.org/
2 changes: 1 addition & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/config.js
/.env*.local
.next
.swc
/node_modules
26 changes: 13 additions & 13 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,21 @@ This backend is used to generate a more-persistent token.
2. Create
[Google OAuth2 client](https://github.com/googleapis/google-api-nodejs-client?tab=readme-ov-file#oauth2-client)

- Create 2 clients - one for development, another for production
- Create 2 Client ides - one for development, another for production
- Set type to "Web application"
- Set authorized redirect URI to `https://EXTENSION_ID.chromiumapp.org/`
(replace `EXTENSION_ID` with the extension ID)
- For development set:
- Authorized redirect URIs: `https://calendar-plus.patii.uk/api/route`
- For production set:
- Authorized JavaScript origins: set to domain on which `backend` will be
hosted (`https://calendar-plus.patii.uk`)
- Set authorized redirect URI to
`https://kgbbebdcmdgkbopcffmpgkgcmcoomhmh.chromiumapp.org/` (replace
`kgbb...` with the extension ID)

3. Copy [./example.config.js](./example.config.js) into `./config.js` and fill
it in according to instructions in that file and the credentials you received
in the previous file. Fill it out with development credentials in
development, and production credentials when the app is deployed to Vercel.
3. (optional) Edit the configuration in [./.env](./.env),
[./.env.development](./.env.development) and
[./.env.production](./.env.production)

4. Install dependencies:
4. Copy [./.env.example](./.env.example) into `./.env.development.local` and
`./.env.production.local` and fill it in according to the instructions in
that file and the credentials you received in the previous step.

5. Install dependencies:

```sh
npm install
Expand All @@ -48,3 +47,4 @@ This backend is used to generate a more-persistent token.

1. Create new vercel.com project from this repository
2. Change the "Root Directory" setting to current directory (/packages/backend)
3. Set environment variables as per `./.env.production.local`
14 changes: 7 additions & 7 deletions backend/app/api/auth/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
*/

import { NextRequest } from 'next/server';
import {
googleClientId,
googleClientSecret,
accessControlAllowOrigin,
} from '../../../config.js';

export const runtime = 'edge';

const googleClientId = process.env.GOOGLE_CLIENT_ID;
if (googleClientId === undefined)
throw new Error('GOOGLE_CLIENT_ID is not defined');
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
if (googleClientSecret === undefined)
throw new Error('GOOGLE_CLIENT_SECRET is not defined');
const accessControlAllowOrigin = process.env.ACCESS_CONTROL_ALLOW_ORIGIN;
if (accessControlAllowOrigin === undefined)
throw new Error('ACCESS_CONTROL_ALLOW_ORIGIN is not defined');

export async function POST(request: NextRequest): Promise<Response> {
const origin = request.headers.get('origin');
Expand Down Expand Up @@ -46,8 +46,8 @@ export async function POST(request: NextRequest): Promise<Response> {
const response = await fetch(
formatUrl('https://oauth2.googleapis.com/token', {
...payload,
client_id: googleClientId,
client_secret: googleClientSecret,
client_id: googleClientId!,
client_secret: googleClientSecret!,
}),
{
method: 'POST',
Expand Down
16 changes: 0 additions & 16 deletions backend/example.config.js

This file was deleted.

17 changes: 16 additions & 1 deletion src/src/components/Background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ const requestHandlers: {
const response = await fetch(resolveUrl, { method: 'POST' });

const responseText = await response.text();
// Example response:
// {
// "access_token": "REDACTED",
// "expires_in": 3599,
// "scope": "https://www.googleapis.com/auth/calendar.readonly",
// "token_type": "Bearer"
// }

if (response.status === 200) {
try {
const data = JSON.parse(responseText);
Expand All @@ -132,8 +140,15 @@ const requestHandlers: {
});

const response = await fetch(authUrl, { method: 'POST' });

const responseText = await response.text();
// Example response:
// {
// "access_token": "REDACTED",
// "expires_in": 3599,
// "scope": "https://www.googleapis.com/auth/calendar.readonly",
// "token_type": "Bearer"
// }

if (response.status === 200) {
try {
const data = JSON.parse(responseText);
Expand Down
61 changes: 41 additions & 20 deletions src/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,49 @@
* WebPack config for development and production
*/

import path from 'path';
import path from 'node:path';
import webpack from 'webpack';
import {
developmentAuthUrl,
productionAuthUrl,
googleClientId,
} from '../backend/config.js';
import { fileURLToPath } from 'url';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';

function parseEnv(name) {
/*
* Next.js expects env files to be in it's root. Webpack needs to reuse some
* of those variables, so we must piggyback on Next.js's env files.
*/
const envFileLocation = `../auth-backend/${name}`;
const envFile = fs.readFileSync(envFileLocation, 'utf8');
return Object.fromEntries(
envFile
.split('\n')
.filter((line) => line.includes('=') && !line.startsWith('#'))
.map((line) => line.split('='))
.map(([name, value]) => [name.trim(), value.trim()]),
);
}

const productionGoogleClientId = parseEnv(
'.env.production.local',
).GOOGLE_CLIENT_ID;
if (productionGoogleClientId === undefined)
throw new Error('Production GOOGLE_CLIENT_ID is not defined');
const developmentGoogleClientId = parseEnv(
'.env.development.local',
).GOOGLE_CLIENT_ID;
if (developmentGoogleClientId === undefined)
throw new Error('Development GOOGLE_CLIENT_ID is not defined');
const productionAuthUrl = parseEnv('.env.production').AUTH_URL;
if (productionAuthUrl === undefined)
throw new Error('Production AUTH_URL is not defined');
const developmentAuthUrl = parseEnv('.env.development').AUTH_URL;
if (developmentAuthUrl === undefined)
throw new Error('Development AUTH_URL is not defined');

const outputPath = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'dist',
);

function ensureDefined(value, error) {
if (value === undefined) throw new Error(error);
return value;
}

export default (_env, argv) =>
/** @type { import('webpack').Configuration } */ ({
module: {
Expand Down Expand Up @@ -70,7 +94,7 @@ export default (_env, argv) =>
// Set appropriate process.env.NODE_ENV
mode: argv.mode,
/*
* User recommended source map type in production
* Use recommended source map type in production
* Can't use the recommended "eval-source-map" in development due to
* https://stackoverflow.com/questions/48047150/chrome-extension-compiled-by-webpack-throws-unsafe-eval-error
*/
Expand All @@ -86,15 +110,12 @@ export default (_env, argv) =>
plugins: [
new webpack.DefinePlugin({
'process.env.AUTH_URL': JSON.stringify(
ensureDefined(
argv.mode === 'development'
? developmentAuthUrl
: productionAuthUrl,
'AUTH_URL is not defined',
),
argv.mode === 'development' ? developmentAuthUrl : productionAuthUrl,
),
'process.env.GOOGLE_CLIENT_ID': JSON.stringify(
ensureDefined(googleClientId, 'GOOGLE_CLIENT_ID is not defined'),
argv.mode === 'development'
? developmentGoogleClientId
: productionGoogleClientId,
),
}),
argv.mode === 'development'
Expand Down

0 comments on commit 46b0040

Please sign in to comment.