- Updated README.md with Telegram features section in 'Latest Features'
- Added Telegram environment variables to Environment Variables table
- Updated FEATURE_PLAN-telegram.md: marked Phases 1-5 as completed
- Updated status table with completion dates (Phase 1-4: done, Phase 5: docs complete)
OpenAPI Documentation:
- Added swagger tags to reorder route (Management Portal)
- Added swagger tags to consent routes (Consent Management)
- Regenerated openapi.json with correct tags (no more 'default' category)
Environment Configuration:
- Updated .env.backend.example with Telegram variables and session secret
- Created docker/dev/.env.example with Telegram configuration template
- Created docker/prod/.env.example with production environment template
- Moved secrets from docker-compose.yml to .env files (gitignored)
- Changed docker/dev/docker-compose.yml to use placeholders: ${TELEGRAM_BOT_TOKEN}
Security Enhancements:
- Disabled test message on server start by default (TELEGRAM_SEND_TEST_ON_START=false)
- Extended pre-commit hook to detect hardcoded Telegram secrets
- Hook prevents commit if TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID are hardcoded
- All secrets must use environment variable placeholders
Phase 5 fully completed and documented.
118 lines
3.6 KiB
Bash
Executable File
118 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
TARGET_FILE="$ROOT_DIR/docker/prod/docker-compose.yml"
|
|
ANCHOR_LINE=" - ADMIN_SESSION_DIR=/usr/src/app/src/data/sessions"
|
|
EXPECTED_LINE=" - ADMIN_SESSION_COOKIE_SECURE=true"
|
|
SECRET_ANCHOR_LINE=' - NODE_ENV=production'
|
|
SECRET_EXPECTED_LINE=' - ADMIN_SESSION_SECRET=${ADMIN_SESSION_SECRET}'
|
|
SECRET_VALUE='${ADMIN_SESSION_SECRET}'
|
|
|
|
if [[ ! -f "$TARGET_FILE" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
export TARGET_FILE
|
|
export ANCHOR_LINE
|
|
export EXPECTED_LINE
|
|
export SECRET_ANCHOR_LINE
|
|
export SECRET_EXPECTED_LINE
|
|
export SECRET_VALUE
|
|
|
|
result=$(python3 <<'PY'
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
|
|
path = pathlib.Path(os.environ['TARGET_FILE'])
|
|
anchor = os.environ['ANCHOR_LINE']
|
|
expected = os.environ['EXPECTED_LINE']
|
|
secret_anchor = os.environ['SECRET_ANCHOR_LINE']
|
|
secret_expected = os.environ['SECRET_EXPECTED_LINE']
|
|
secret_value = os.environ['SECRET_VALUE']
|
|
|
|
text = path.read_text()
|
|
new_text = text
|
|
changed = False
|
|
|
|
cookie_pattern = re.compile(r'(\-\s*ADMIN_SESSION_COOKIE_SECURE\s*=\s*)([^\n\r]+)')
|
|
secret_pattern = re.compile(r'(\-\s*ADMIN_SESSION_SECRET\s*=\s*)([^\n\r]+)')
|
|
telegram_token_pattern = re.compile(r'(\-\s*TELEGRAM_BOT_TOKEN\s*=\s*)([^\n\r${}]+)')
|
|
telegram_chat_pattern = re.compile(r'(\-\s*TELEGRAM_CHAT_ID\s*=\s*)(-?\d{10,})')
|
|
|
|
def ensure_entry(text, *, pattern, value, anchor_line, expected_line, label):
|
|
match = pattern.search(text)
|
|
if match:
|
|
desired = f"{match.group(1)}{value}"
|
|
if match.group(0) == desired:
|
|
return text, False
|
|
return pattern.sub(lambda m: f"{m.group(1)}{value}", text, count=1), True
|
|
if anchor_line not in text:
|
|
print(f"ERROR: Anchor line not found for {label}", file=sys.stderr)
|
|
sys.exit(2)
|
|
return text.replace(anchor_line, anchor_line + '\n' + expected_line, 1), True
|
|
|
|
new_text, cookie_changed = ensure_entry(
|
|
new_text,
|
|
pattern=cookie_pattern,
|
|
value='true',
|
|
anchor_line=anchor,
|
|
expected_line=expected,
|
|
label='ADMIN_SESSION_COOKIE_SECURE'
|
|
)
|
|
changed = changed or cookie_changed
|
|
|
|
if expected not in new_text:
|
|
print('ERROR: Failed to ensure ADMIN_SESSION_COOKIE_SECURE=true in docker-compose.yml', file=sys.stderr)
|
|
sys.exit(3)
|
|
|
|
new_text, secret_changed = ensure_entry(
|
|
new_text,
|
|
pattern=secret_pattern,
|
|
value=secret_value,
|
|
anchor_line=secret_anchor,
|
|
expected_line=secret_expected,
|
|
label='ADMIN_SESSION_SECRET'
|
|
)
|
|
changed = changed or secret_changed
|
|
|
|
if secret_expected not in new_text:
|
|
print('ERROR: Failed to ensure ADMIN_SESSION_SECRET uses environment variable in docker-compose.yml', file=sys.stderr)
|
|
sys.exit(4)
|
|
|
|
telegram_token_match = telegram_token_pattern.search(new_text)
|
|
if telegram_token_match and telegram_token_match.group(2).strip() not in ['${TELEGRAM_BOT_TOKEN}', '']:
|
|
print(f'ERROR: TELEGRAM_BOT_TOKEN contains hardcoded secret: {telegram_token_match.group(2)[:20]}...', file=sys.stderr)
|
|
print(' Use ${TELEGRAM_BOT_TOKEN} placeholder instead!', file=sys.stderr)
|
|
sys.exit(5)
|
|
|
|
telegram_chat_match = telegram_chat_pattern.search(new_text)
|
|
if telegram_chat_match:
|
|
print(f'ERROR: TELEGRAM_CHAT_ID contains hardcoded value: {telegram_chat_match.group(2)}', file=sys.stderr)
|
|
print(' Use ${TELEGRAM_CHAT_ID} placeholder instead!', file=sys.stderr)
|
|
sys.exit(6)
|
|
|
|
if changed:
|
|
path.write_text(new_text)
|
|
print('UPDATED')
|
|
else:
|
|
print('UNCHANGED')
|
|
PY
|
|
)
|
|
status=$?
|
|
|
|
if [[ $status -ne 0 ]]; then
|
|
echo "$result"
|
|
echo "[pre-commit] Failed to normalize ADMIN_SESSION_COOKIE_SECURE" >&2
|
|
exit $status
|
|
fi
|
|
|
|
if [[ $result == "UPDATED" ]]; then
|
|
echo "[pre-commit] Normalized ADMIN_SESSION_COOKIE_SECURE in docker/prod/docker-compose.yml"
|
|
git -C "$ROOT_DIR" add "$TARGET_FILE"
|
|
fi
|
|
|
|
exit 0
|