Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Before running any services, create the local development environment from the *
./create_dev_envs.sh
```

A `default.json` file containing environment variables will be provided to contributors and should be placed in `middlewareNode/config`.
Copy `middlewareNode/.env.example` to `middlewareNode/.env` and fill in your local values. A committed `middlewareNode/config/default.js` already provides safe, non-secret defaults, nothing needs to be handed to you separately.


## Running Each Service
Expand All @@ -98,10 +98,13 @@ npm start

The server typically runs on port 8000. You should see `"MongoDB Connected..."` when it starts successfully.

The following credentials can be used for testing mentor and student accounts:
To create local mentor and student demo accounts, run:

* **Mentor:** mentor / 123123123
* **Student:** student / 123123123
```bash
npm run seed:dev
```

This generates a brand-new random password for both accounts every time it runs and prints them to your terminal. Passwords are never committed or written to a file, and re-running the command rotates both passwords. This only works outside of a production environment.

### Main React Application (Frontend)

Expand Down
59 changes: 59 additions & 0 deletions chessServer/src/tests/validateEnvironment.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const validateEnvironment = require("../validateEnvironment");

const ORIGINAL_ENV = process.env;

describe("validateEnvironment", () => {
let exitSpy;

beforeEach(() => {
process.env = { ...ORIGINAL_ENV };
exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {
throw new Error("process.exit");
});
jest.spyOn(console, "error").mockImplementation(() => {});
jest.spyOn(console, "log").mockImplementation(() => {});
});

afterEach(() => {
process.env = ORIGINAL_ENV;
jest.restoreAllMocks();
});

const clearRequired = () => {
for (const name of ["MIDDLEWARE_URL", "CORS_ORIGIN", "ALLOWED_ORIGINS"]) {
delete process.env[name];
}
};

it.each([undefined, "", "development", "test"])(
"skips validation when NODE_ENV=%p",
(nodeEnv) => {
clearRequired();
if (nodeEnv === undefined) delete process.env.NODE_ENV;
else process.env.NODE_ENV = nodeEnv;

expect(() => validateEnvironment()).not.toThrow();
expect(exitSpy).not.toHaveBeenCalled();
}
);

it.each(["production", "staging", "qa", "prod"])(
"exits when required vars are missing and NODE_ENV=%p",
(nodeEnv) => {
clearRequired();
process.env.NODE_ENV = nodeEnv;

expect(() => validateEnvironment()).toThrow("process.exit");
expect(exitSpy).toHaveBeenCalledWith(1);
}
);

it("passes in a non-local environment when everything is set", () => {
process.env.NODE_ENV = "staging";
process.env.MIDDLEWARE_URL = "https://example.com/middleware";
process.env.CORS_ORIGIN = "https://example.com";

expect(() => validateEnvironment()).not.toThrow();
expect(exitSpy).not.toHaveBeenCalled();
});
});
16 changes: 13 additions & 3 deletions chessServer/src/validateEnvironment.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@ function hasCorsConfiguration() {
);
}

// Local-only environments skip validation. Anything else (production, but
// also staging, qa, or a typo like "prod") is treated as a real deployment
// and must be fully configured, instead of silently booting with dev
// fallbacks such as middlewareNode's generated dev INDEX_KEY.
const LOCAL_ENVIRONMENTS = new Set(["", "development", "test"]);

function isLocalEnvironment() {
return LOCAL_ENVIRONMENTS.has((process.env.NODE_ENV || "").trim());
}

function validateEnvironment() {
if (process.env.NODE_ENV !== "production") {
if (isLocalEnvironment()) {
return;
}

Expand All @@ -25,12 +35,12 @@ function validateEnvironment() {

if (missing.length > 0) {
console.error(
`[chessServer] Missing required production environment variables: ${missing.join(", ")}`
`[chessServer] Missing required environment variables for NODE_ENV=${process.env.NODE_ENV}: ${missing.join(", ")}`
);
process.exit(1);
}

console.log("[chessServer] Required production environment variables validated");
console.log("[chessServer] Required environment variables validated");
}

module.exports = validateEnvironment;
2 changes: 1 addition & 1 deletion deploy/prod/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ services:
- "8080"

middleware:
image: middlewarenode
image: middlewarenode:${TAG:-latest}
container_name: middleware
environment:
- NODE_ENV=production
Expand Down
43 changes: 38 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@

services:
mongo:
image: mongo:6
container_name: dev-mongo
ports:
- "27017:27017"
volumes:
- dev-mongo-data:/data/db

frontend:
build: ./react-ystemandchess
build:
context: ./react-ystemandchess
args:
REACT_APP_MIDDLEWARE_URL: http://localhost:8000
REACT_APP_STOCKFISH_SERVER_URL: http://localhost:8080
REACT_APP_CHESS_SERVER_URL: http://localhost:3001
REACT_APP_CHESS_CLIENT_URL: http://localhost:3002
REACT_APP_AGORA_APP_ID: ""
container_name: frontend
ports:
- "3000:3000"
Expand All @@ -12,6 +26,11 @@ services:
container_name: chess-server
ports:
- "3001:3001"
environment:
- NODE_ENV=development
- PORT=3001
- MIDDLEWARE_URL=http://middleware:8000
- CORS_ORIGIN=http://localhost,http://localhost:3000,http://localhost:3002
restart: unless-stopped

chess-client:
Expand All @@ -27,14 +46,28 @@ services:
ports:
- "8000:8000"
environment:
- NODE_ENV=production
volumes:
- ./middlewareNode/config/production.json:/app/config/production.json:ro
- NODE_ENV=development
- PORT=8000
- MONGO_URI=mongodb://mongo:27017/ystem_dev
- INDEX_KEY=dev-index-key-replace-in-prod
- CORS_ORIGIN=http://localhost,http://localhost:3000
- SESSION_SECRET=dev-secret-replace-in-prod
- ANALYTICS_RATE_LIMIT_MAX=100
- LEADERBOARD_RATE_LIMIT_MAX=60
depends_on:
- mongo
restart: unless-stopped

stockfish-server:
build: ./stockfishServer
container_name: stockfish-server
ports:
- "8080:8080"
environment:
- NODE_ENV=development
- PORT=8080
- CORS_ORIGIN=http://localhost,http://localhost:3000
restart: unless-stopped

volumes:
dev-mongo-data:
1 change: 0 additions & 1 deletion middlewareNode/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,4 @@ PVP_WEIGHT_LOSS=0
ADMIN_USERNAME=

# Present in config mapping but not currently consumed by middleware source
JWT_SECRET=
EMAIL_PASS=
1 change: 0 additions & 1 deletion middlewareNode/config/custom-environment-variables.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"mongoURI": "MONGO_URI",
"jwtSecret": "JWT_SECRET",
"indexKey": "INDEX_KEY",

"corsOptions": { "origin": "CORS_ORIGIN" },
Expand Down
6 changes: 3 additions & 3 deletions middlewareNode/config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ const path = require("path");
// must have a value" in dev/test when INDEX_KEY isn't set — without checking
// a fixed signing key into git history. custom-environment-variables.json
// already maps INDEX_KEY onto this key, so any real deployment overrides it;
// validateEnvironment.js still requires INDEX_KEY in production, and runs
// before anything require()s this file, so this never touches disk in prod.
// validateEnvironment.js requires INDEX_KEY in every non-local environment
// (anything but NODE_ENV unset/development/test), and runs before anything
// require()s this file, so this never touches disk in a real deployment.
//
// Persisted (gitignored) rather than regenerated per boot, so dev JWTs and
// password-reset links survive a nodemon restart instead of invalidating on
Expand Down Expand Up @@ -44,7 +45,6 @@ if (!process.env.INDEX_KEY) {

module.exports = {
mongoURI: "",
jwtSecret: "",
indexKey: devIndexKey,

corsOptions: {
Expand Down
3 changes: 2 additions & 1 deletion middlewareNode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
},
"scripts": {
"start": "nodemon src/server.js",
"test": "jest --testPathPatterns=tests/"
"test": "jest --testPathPatterns=tests/",
"seed:dev": "node src/scripts/seedDevAccounts.js"
},
"author": "",
"license": "ISC",
Expand Down
Loading
Loading