diff --git a/.env.dev.dist b/.env.dev.dist index c2701011..efed0788 100644 --- a/.env.dev.dist +++ b/.env.dev.dist @@ -53,3 +53,6 @@ FLOWER_BASIC_AUTH=flower:flower # Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) LOG_LEVEL=INFO + +# Comma-separated list of extra allowed CORS origins (e.g. https://app.example.com) +CORS_ORIGINS= diff --git a/.env.dist b/.env.dist index 4ea55476..4e3c21fb 100644 --- a/.env.dist +++ b/.env.dist @@ -47,4 +47,7 @@ GARAGE_METRICS_TOKEN= FLOWER_BASIC_AUTH=flower:flower -LOG_LEVEL=WARNING \ No newline at end of file +LOG_LEVEL=WARNING + +# Comma-separated list of extra allowed CORS origins (e.g. https://app.misp-workbench.com) +CORS_ORIGINS= \ No newline at end of file diff --git a/.env.test b/.env.test index 6d420a2b..974cb232 100644 --- a/.env.test +++ b/.env.test @@ -48,4 +48,7 @@ GARAGE_METRICS_TOKEN=garage-test-metrics-token FLOWER_BASIC_AUTH=flower:flower -LOG_LEVEL=DEBUG \ No newline at end of file +LOG_LEVEL=DEBUG + +# Comma-separated list of extra allowed CORS origins (e.g. https://app.example.com) +CORS_ORIGINS= \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index bfcecdec..8ac308a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,6 +54,13 @@ cd api && poetry run pre-commit run --all-files ### Frontend +Copy `frontend/.env.dist` to `frontend/.env` and set `VITE_API_URL` to the API base URL before building: + +```bash +cp frontend/.env.dist frontend/.env +# Edit frontend/.env and set VITE_API_URL=https://api.your-domain.com +``` + ```bash cd frontend npm install diff --git a/README.md b/README.md index 821aa860..f3b45d2b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Then start the stack: ```bash cp .env.dist .env -# edit .env: set OAUTH2_SECRET_KEY, OAUTH2_REFRESH_SECRET_KEY, GARAGE_ADMIN_TOKEN, S3_ACCESS_KEY, S3_SECRET_KEY, etc. +# edit .env: set CORS_ORIGINS, OAUTH2_SECRET_KEY, OAUTH2_REFRESH_SECRET_KEY, GARAGE_ADMIN_TOKEN, S3_ACCESS_KEY, S3_SECRET_KEY, etc. docker compose --env-file=".env" up --build ... ... diff --git a/api/app/auth/auth.py b/api/app/auth/auth.py index 121dea99..d321b193 100644 --- a/api/app/auth/auth.py +++ b/api/app/auth/auth.py @@ -227,10 +227,7 @@ def get_scopes_for_user(user: user_schemas.User) -> list[str]: def get_random_password(): return "".join( random.choice( - string.ascii_lowercase - + string.ascii_uppercase - + string.digits - + string.punctuation + string.ascii_lowercase + string.ascii_uppercase + string.digits ) - for _ in range(12) + for _ in range(16) ) diff --git a/api/app/main.py b/api/app/main.py index 51c92784..7a426b02 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -55,6 +55,9 @@ "http://localhost:3001", "http://localhost:6274", ] +extra_origins = os.environ.get("CORS_ORIGINS", "") +if extra_origins: + origins += [o.strip() for o in extra_origins.split(",") if o.strip()] app.add_middleware( CORSMiddleware, allow_origins=origins, diff --git a/api/app/tests/test_auth.py b/api/app/tests/test_auth.py index 8aa87e0f..2e2c80e7 100644 --- a/api/app/tests/test_auth.py +++ b/api/app/tests/test_auth.py @@ -135,10 +135,10 @@ def test_returns_empty_for_no_scopes(self): class TestGetRandomPassword: - def test_returns_string_of_length_12(self): + def test_returns_string_of_length_16(self): pwd = get_random_password() assert isinstance(pwd, str) - assert len(pwd) == 12 + assert len(pwd) == 16 def test_passwords_differ_across_calls(self): passwords = {get_random_password() for _ in range(10)} diff --git a/api/entrypoint.sh b/api/entrypoint.sh index 69a66526..13d94d97 100755 --- a/api/entrypoint.sh +++ b/api/entrypoint.sh @@ -10,9 +10,10 @@ if [ "$STORAGE_ENGINE" = "s3" ]; then fi # create admin org and user (skipped if they already exist) -ADMIN_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c 24) +ADMIN_PASSWORD=$(openssl rand -hex 16) poetry run python -m app.cli create-organisation ADMIN -poetry run python -m app.cli create-user admin@admin.local "$ADMIN_PASSWORD" 1 1 +CREATE_USER_OUTPUT=$(poetry run python -m app.cli create-user admin@admin.local "$ADMIN_PASSWORD" 1 1) +echo "$CREATE_USER_OUTPUT" cat <<'EOF' @@ -25,8 +26,11 @@ cat <<'EOF' Server is ready! EOF -echo " Admin credentials: admin@admin.local / $ADMIN_PASSWORD" -echo +if echo "$CREATE_USER_OUTPUT" | grep -q "Created user"; then + echo " Admin credentials: admin@admin.local / $ADMIN_PASSWORD" + echo " Save this password — it will not be shown again." + echo +fi # OpenSearch credentials (prod security plugin is enabled) export OPENSEARCH_USERNAME="${OPENSEARCH_USERNAME:-admin}" @@ -36,4 +40,4 @@ export OPENSEARCH_PASSWORD="${OPENSEARCH_PASSWORD:-${OPENSEARCH_INITIAL_ADMIN_PA poetry run python -m app.opensearch_setup # start API -poetry run uvicorn app.main:app --host 0.0.0.0 --port 80 +poetry run uvicorn app.main:app --host 0.0.0.0 --port 80 --proxy-headers --forwarded-allow-ips='*' diff --git a/docker-compose.yml b/docker-compose.yml index 960c1c09..85660f7d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,6 +38,7 @@ x-environment: &default-env MAIL_PORT: ${MAIL_PORT} MAIL_SERVER: ${MAIL_SERVER} LOG_LEVEL: ${LOG_LEVEL:-INFO} + CORS_ORIGINS: ${CORS_ORIGINS:-} services: postgres: diff --git a/docs/getting-started.md b/docs/getting-started.md index 02b49ef2..5165e4d6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -66,6 +66,10 @@ Open http://localhost:3000/login and log in with your credentials. ```bash cp .env.dist .env # Edit .env and set all required secrets (see Configuration reference) + +cp frontend/.env.dist frontend/.env +# Edit frontend/.env and set VITE_API_URL to the API public URL +# e.g. VITE_API_URL=https://api.your-domain.com ``` ### 2. Configure storage (Garage/S3) diff --git a/frontend/README.md b/frontend/README.md index 9791945e..1a1af5b7 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -53,11 +53,13 @@ See [Vite Configuration Reference](https://vitejs.dev/config/). ## Project Setup -Copy .env file: +Copy .env file and configure the API URL: ```sh cp .env.dist .env ``` +Edit `.env` and set `VITE_API_URL` to the API base URL (e.g. `https://api.your-domain.com` for production, `http://localhost:8080` for development). + ```sh npm install ``` diff --git a/frontend/src/stores/notifications.store.js b/frontend/src/stores/notifications.store.js index 6c2f9978..08cd8266 100644 --- a/frontend/src/stores/notifications.store.js +++ b/frontend/src/stores/notifications.store.js @@ -20,7 +20,7 @@ export const useNotificationsStore = defineStore("notifications", { async get(params = { page: 1, size: 10, read: false }) { this.status = { loading: true }; fetchWrapper - .get(baseUrl + "/?" + new URLSearchParams(params).toString()) + .get(baseUrl + "?" + new URLSearchParams(params).toString()) .then( (response) => ( (this.notifications = response), @@ -32,7 +32,7 @@ export const useNotificationsStore = defineStore("notifications", { }, async getUnreadTotal() { fetchWrapper - .get(`${baseUrl}/?read=false&size=1&page=1`) + .get(`${baseUrl}?read=false&size=1&page=1`) .then((response) => (this.unreadNotifications = response.total)) .catch((error) => (this.status = { error })); },