Skip to content

Commit

Permalink
Support more modes for running the dev server (GSI-1233) (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cito authored Jan 8, 2025
1 parent 36d7b26 commit 4b925b1
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 162 deletions.
18 changes: 12 additions & 6 deletions .devcontainer/create_cert
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@

# create self-signed certificate for testing with the browser

BASE_URL=${BASE_URL:-https://data.staging.ghga.dev}
HOST=${BASE_URL#https://}
CERTFILE=cert.pem
KEYFILE=key.pem

cd /workspace/.devcontainer

if ! test -f "$CERTFILE" || ! test -f "$KEYFILE"; then
URL=${data_portal_base_url}
echo "URL=$URL"
HOST=$(echo "$URL" | awk -F[/:] '{print $4}')
if [ -z "$HOST" ] || [ "$HOST" == "localhost" ] || [ "$HOST" == "127.0.0.1" ]; then
HOST="data.staging.ghga.dev"
fi
echo "Creating self-signed certificate for $HOST"
openssl req -x509 -newkey rsa:4096 -nodes \
-out "ca-$CERTFILE" -keyout "ca-$KEYFILE" \
-subj "/CN=$HOST" -days 356
if ! test -f "ca-$CERTFILE" || ! test -f "ca-$KEYFILE"; then
openssl req -x509 -newkey rsa:4096 -nodes \
-out "ca-$CERTFILE" -keyout "ca-$KEYFILE" \
-subj "/CN=$HOST" -days 356
fi
openssl req -newkey rsa:4096 -nodes \
-out "req-$CERTFILE" -keyout "$KEYFILE" \
-subj "/CN=$HOST"
Expand All @@ -27,6 +33,6 @@ extendedKeyUsage=serverAuth" > "ca.ext"
-in "req-$CERTFILE" -out "$CERTFILE" \
-CAcreateserial -days 356 \
-extfile ca.ext
rm -f "req-$CERTFILE" "ca-$KEYFILE" ca.ext "ca-${CERTFILE%.pem}.srl"
rm -f "req-$CERTFILE" ca.ext "ca-${CERTFILE%.pem}.srl"
echo "Add ca-$CERTFILE to your browser's trusted certificates"
fi
12 changes: 1 addition & 11 deletions .devcontainer/dev_launcher
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,5 @@

cd /workspace

# allow passing modes as arguments
if [ "$1" = "staging" ]; then
export data_portal_base_url="https://data.staging.ghga.dev"
if [[ "$2" = *:* ]]; then
export data_portal_basic_auth="$2"
fi
elif [ "$1" = "msw" ]; then
export data_portal_base_url="http://127.0.0.1:8080"
fi

# start the development server
./run.js --dev
./run.js --dev "$@"
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,42 @@ dev_launcher

Once the server is running, open your browser and navigate to `http://localhost:8080/`. The application will automatically reload whenever you modify any of the source files.

By default, this will not use a proxy configuration and the API will be provided via the mock service worker.
By default, this will not use a proxy configuration; the API will be provided via the mock service worker, and the authentication will be faked as well.

If you want to test the application against the backend provided by the staging deployment, then run:

```bash
dev_launcher staging
dev_launcher --with-backend
```

In this case, a proxy configuration will be used that proxies all API endpoints to the staging environment, while the application itself is still served by the development server. You can change the name of the staging backend via the environment variable `data_portal_base_url`.
In this case, a proxy configuration will be used that proxies all API endpoints to the staging environment, while the application itself is still served by the development server. You can change the name of the staging backend via the environment variable `data_portal_base_url`; by default it will be `data.staging.ghga.dev`.

If you change the hosts file on your host computer so that localhost points to `data.staging.ghga.dev`, then this setup also allows testing authentication using the real OIDC provider. You need to point your browser to `https://data.staging.ghga.dev` in this case. The development server will serve the application via SSL in this setup, using the certificate created in `.devcontainer/cert.pem`. You can add the corresponding CA certificate `.devcontainer/ca-cert.pem` to the trusted certificates of your development computer or web browser to avoid the warnings when loading the page.
If the staging backend requires an additional Basic authentication, you can set it in the environment variable `data_portal_basic_auth`.

If the staging backend requires an additional Basic authentication, you can set it in the environment variable `data_portal_basic_auth` or pass it on the command line like this:
If you want to test authentication using the real OIDC provider, then run:

```bash
dev_launcher staging username:password
dev_launcher --with-oidc
```

In order to make the OIDC and basic authentication work, you also need to add a `.devcontainer/local.env` file like this, with the proper credentials:
The development server will serve the application via SSL in this mode, using the certificate created in `.devcontainer/cert.pem`. You should add the corresponding CA certificate `.devcontainer/ca-cert.pem` to the trusted certificates of your development computer or web browser to avoid the warnings when loading the page.

In this mode, the `data_portal_oidc_client_id` and the other OIDC settings must be set properly as required by the OIDC provider.

You will also need to changed the hosts file on your host computer so that localhost points to the staging backend. If you use the default staging backend, then you can browse the application at `https://data.staging.ghga.dev`.

To test against the real backend and with the real OIDC provider, you can start the development server like this:

```bash
dev_launcher --with-backend --with-oidc
```

It is recommended to put the necessary settings, particularly the credentials that should be kept secret, in the `local.env` file inside the `.devcontainer` directory. It should look something like this:

```env
data_portal_base_url=https://data.staging.ghga.dev
data_portal_basic_auth=USERNAME:PASSWORD
data_portal_oidc_client_id=OIDC_DEV_CLIENT_ID
data_portal_oidc_client_id=THE_OIDC_CLIENT_ID
```

## Code scaffolding
Expand Down
6 changes: 5 additions & 1 deletion proxy.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const useProxy = !target.startsWith('http://127.');
const config = {};

if (useProxy) {
console.log('Configuring proxy server...');
console.log(`\nRunning proxy server with target: ${target}`);
config['/api'] = {
target,
changeOrigin: true,
Expand All @@ -54,4 +54,8 @@ if (useProxy) {
};
}

if (baseUrl && !baseUrl.startsWith('http://127.')) {
console.log(`\n\x1b[33mPlease point your browser to: ${baseUrl}\x1b[0m\n`);
}

export default config;
111 changes: 85 additions & 26 deletions run.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
#!/usr/bin/env node

/**
* Run the development or production server for the data portal.
*
* Syntax: run.js [--dev [--with-backend] [--with-oidc]]
*/

import { spawnSync } from 'child_process';
import fs from 'fs';
import yaml from 'js-yaml';
import path from 'path';
import { fileURLToPath } from 'url';

const NAME = 'data-portal';
const DEV_MODE = process.argv.slice(2).includes('--dev');
const DEFAULT_BACKEND = 'https://data.staging.ghga.dev';

const args = process.argv.slice(1);
const DEV = args.includes('--dev');
const WITH_BACKEND = args.includes('--with-backend');
const WITH_OIDC = args.includes('--with-oidc');

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

Expand All @@ -24,7 +36,7 @@ function readSettings() {
const defaultSettings = yaml.load(fs.readFileSync(defaultSettingsPath, 'utf8'));

// Read the (optional) development or production specific configuration file
const specificSettingsPath = DEV_MODE ? `.devcontainer/${NAME}.yaml` : `${NAME}.yaml`;
const specificSettingsPath = DEV ? `.devcontainer/${NAME}.yaml` : `${NAME}.yaml`;

let specificSettings = {};
try {
Expand Down Expand Up @@ -83,26 +95,22 @@ function getBrowserDir(distDir) {
* @throws {Error} If the output directory does not exist.
*/
function writeSettings(settings) {
const outputDir = DEV_MODE ? 'public' : getBrowserDir('dist');
const outputDir = DEV ? 'public' : getBrowserDir('dist');

// Ensure the output directory exists
if (!fs.existsSync(outputDir)) {
throw new Error(`Output directory not found: ${outputDir}`);
}

const configPath = path.join(outputDir, 'config.js');
const configScript = `window.config = ${JSON.stringify(settings)}`;
const configScript = `window.config = ${JSON.stringify(settings)};`;
fs.writeFileSync(configPath, configScript, 'utf8');
}

/**
* Get the name and IP address of the specified URL.
* Find the IP address for a given hostname
*/
function getNameAndAddress(url) {
const { hostname } = new URL(url);
if (!hostname) {
console.error(`Invalid URL: ${url}`);
}
function getIpAddress(hostname) {
const result = spawnSync('dig', ['+short', hostname, '@8.8.8.8'], {
encoding: 'utf8',
});
Expand All @@ -111,7 +119,7 @@ function getNameAndAddress(url) {
console.error(`Cannot resolve ${hostname}`);
process.exit(1);
}
return [hostname, address];
return address;
}

/**
Expand All @@ -136,21 +144,37 @@ function addHostEntry(name, ip) {
function runDevServer(host, port, ssl, sslCert, sslKey, logLevel, baseUrl, basicAuth) {
console.log('Running the development server...');

if (baseUrl === `http://${host}:${port}`) {
const { hostname } = new URL(baseUrl);
if (!hostname) {
console.error(`Invalid URL: ${baseUrl}`);
return;
}

if (WITH_BACKEND || WITH_OIDC) {
console.log(`Using ${hostname} as backend for API calls via proxy.`);
const ipAddress = getIpAddress(hostname);
addHostEntry(hostname, ipAddress);
} else {
console.log('Using the mock service worker for API calls.');
basicAuth = null;
} else if (baseUrl.startsWith('https://')) {
console.log(`Using ${baseUrl} as backend for API calls via proxy.`);
const [baseName, baseIp] = getNameAndAddress(baseUrl);
addHostEntry(baseName, baseIp);
console.log(`Your host computer should resolve ${baseName} to ${host}.`);
port = 443;
ssl = true;
}
if (WITH_OIDC) {
console.log('Using OIDC for authentication.');
if (port != 443 || !ssl) {
port = 443;
ssl = true;
console.log('The server must use HTTPS for OIDC.');
}
} else {
console.error(`Invalid base URL: ${baseUrl}`);
process.exit(1);
console.log('Using the mock service worker for authentication.');
}

if (host != hostname) {
console.log(`Your host computer should resolve ${hostname} to ${host}.`);
}

console.log('Please point your browser to:', baseUrl);

// export settings used in the proxy config
process.env.data_portal_base_url = baseUrl;
if (basicAuth) {
Expand Down Expand Up @@ -227,10 +251,7 @@ function main() {

const settings = readSettings();

console.log('Runtime settings:');
console.table(settings);

const {
let {
host,
port,
ssl,
Expand All @@ -241,6 +262,39 @@ function main() {
basic_auth: basicAuth,
} = settings;

let msg = 'Running';
let adapted = false;
if (DEV) {
msg += ' in development mode';
if (WITH_BACKEND || WITH_OIDC) {
if (!baseUrl || baseUrl.startsWith('http://127.')) {
settings.base_url = baseUrl = DEFAULT_BACKEND;
adapted = true;
}
}
if (WITH_BACKEND) {
msg += ` with ${baseUrl.split('://')[1] || baseUrl} as backend`;
} else {
msg += ' with mock API';
}
if (WITH_OIDC) {
msg += ' and authentication via OIDC';
if (settings.port !== 443 || !settings.ssl) {
settings.port = port = 443;
settings.ssl = ssl = true;
adapted = true;
}
} else {
msg += ' and mock authentication';
}
} else {
msg += ' in production mode';
}
console.log(msg);
console.log(`Runtime settings${adapted ? ' (adapted)' : ''}:`);

console.table(settings);

if (!host || !port) {
console.error('Host and port must be specified');
process.exit(1);
Expand All @@ -258,9 +312,14 @@ function main() {
delete settings.log_level;
delete settings.basic_auth;

if (DEV) {
settings.mock_api = !WITH_BACKEND;
settings.mock_oidc = !WITH_OIDC;
}

writeSettings(settings);

(DEV_MODE ? runDevServer : runProdServer)(
(DEV ? runDevServer : runProdServer)(
host,
port,
ssl,
Expand Down
2 changes: 2 additions & 0 deletions src/app/shared/services/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ interface Config {
oidc_token_url: string;
oidc_userinfo_url: string;
oidc_use_discovery: boolean;
mock_api: boolean;
mock_oidc: boolean;
}

declare global {
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { appConfig } from './app/app.config';
* Start the application
*/
async function startApp() {
if (isDevMode()) {
if (isDevMode() && (window.config.mock_api || window.config.mock_oidc)) {
const { worker } = await import('./mocks/setup');
await worker.start({ waitUntilReady: true });
}
Expand Down
Loading

0 comments on commit 4b925b1

Please sign in to comment.