diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 83e386a..df89dfe 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -506,22 +506,39 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY || true if [ "$LEAK_COUNT" -gt 0 ]; then - echo "### Leak Details" >> $GITHUB_STEP_SUMMARY || true + echo "### Leak Summary Table" >> $GITHUB_STEP_SUMMARY || true echo "" >> $GITHUB_STEP_SUMMARY || true - echo "| File | Line | Rule | Description | Commit |" >> $GITHUB_STEP_SUMMARY || true - echo "|------|------|------|-------------|--------|" >> $GITHUB_STEP_SUMMARY || true + echo "| File | Line | Rule | Entropy | Commit | Author | Date |" >> $GITHUB_STEP_SUMMARY || true + echo "|------|------|------|---------|--------|--------|------|" >> $GITHUB_STEP_SUMMARY || true - # Extract and display leak details - jq -r '.[] | "| \(.File) | \(.Line) | \(.RuleID) | \(.Description // "N/A") | \(.Commit // "N/A") |"' gitleaks-report.json >> $GITHUB_STEP_SUMMARY || true + # Extract and display leak details in table format + jq -r '.[] | "| \(.File) | \(.Line) | \(.RuleID) | \(.Entropy // "N/A") | `\(.Commit[0:8])` | \(.Author // "N/A") | \(.Date // "N/A") |"' gitleaks-report.json >> $GITHUB_STEP_SUMMARY || true echo "" >> $GITHUB_STEP_SUMMARY || true + echo "### Detailed Findings" >> $GITHUB_STEP_SUMMARY || true + echo "" >> $GITHUB_STEP_SUMMARY || true + + # Display each finding with full details + jq -r '.[] | + "#### Finding: \(.RuleID) in \(.File):\(.Line)\n" + + "- **File:** `\(.File)`\n" + + "- **Line:** \(.Line)\n" + + "- **Rule ID:** \(.RuleID)\n" + + "- **Description:** \(.Description // "N/A")\n" + + "- **Entropy:** \(.Entropy // "N/A")\n" + + "- **Commit:** `\(.Commit)`\n" + + "- **Author:** \(.Author // "N/A") (\(.Email // "N/A"))\n" + + "- **Date:** \(.Date // "N/A")\n" + + "- **Fingerprint:** `\(.Fingerprint // "N/A")`\n"' gitleaks-report.json >> $GITHUB_STEP_SUMMARY || true + + echo "" >> $GITHUB_STEP_SUMMARY || true echo "### Full Report (JSON)" >> $GITHUB_STEP_SUMMARY || true echo '```json' >> $GITHUB_STEP_SUMMARY || true cat gitleaks-report.json >> $GITHUB_STEP_SUMMARY || true echo '```' >> $GITHUB_STEP_SUMMARY || true echo "" >> $GITHUB_STEP_SUMMARY || true - echo "⚠️ **Action Required:** Review and remove the secrets found above." >> $GITHUB_STEP_SUMMARY || true + echo "⚠️ **Action Required:** Review and remove the secrets found above. Secrets should be removed from the codebase and rotated if they were ever exposed." >> $GITHUB_STEP_SUMMARY || true else echo "✅ No secrets detected!" >> $GITHUB_STEP_SUMMARY || true fi @@ -595,22 +612,36 @@ jobs: steps: - name: Generate workflow summary run: | - echo "## 🔍 CI Workflow Summary" >> $GITHUB_STEP_SUMMARY || true - echo "" >> $GITHUB_STEP_SUMMARY || true - echo "### Job Results" >> $GITHUB_STEP_SUMMARY || true - echo "" >> $GITHUB_STEP_SUMMARY || true - echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY || true - echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY || true - echo "| 📝 Lint & Type Check | ${{ needs.lint-and-type-check.result }} |" >> $GITHUB_STEP_SUMMARY || true - echo "| 🐍 Python Lint | ${{ needs.python-lint.result }} |" >> $GITHUB_STEP_SUMMARY || true - echo "| 🧪 Backend Tests | ${{ needs.test-backend.result }} |" >> $GITHUB_STEP_SUMMARY || true - echo "| 🏗️ Build | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY || true - echo "| 🔐 Secret Scanning | ${{ needs.secret-scanning.result }} |" >> $GITHUB_STEP_SUMMARY || true - echo "| 📦 Dependency Scan | ${{ needs.dependency-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true - echo "| 🔍 SAST Scan | ${{ needs.sast-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true - echo "" >> $GITHUB_STEP_SUMMARY || true - echo "### 📊 Summary" >> $GITHUB_STEP_SUMMARY || true - echo "" >> $GITHUB_STEP_SUMMARY || true - echo "All security and validation checks have completed." >> $GITHUB_STEP_SUMMARY || true + { + echo "## 🔍 CI Workflow Summary" + echo "" + echo "This table gives a **plain-English overview** of what ran in this pipeline and whether it passed." + echo "" + echo "### Job Results" + echo "" + echo "| Job | What it does | Status |" + echo "|-----|--------------|--------|" + echo "| 📝 Lint & Type Check | Runs ESLint on the admin UI and TypeScript type-checks the viewer UI | ${{ needs.lint-and-type-check.result }} |" + echo "| 🐍 Python Lint | Runs Python style and syntax checks over the backend | ${{ needs.python-lint.result }} |" + echo "| 🧪 Backend Tests | Runs \`pytest tests/ -v\` against the FastAPI backend (with coverage) | ${{ needs.test-backend.result }} |" + echo "| 🏗️ Build | Builds the admin frontend (Vite) and viewer frontend (Next.js) | ${{ needs.build.result }} |" + echo "| 🔐 Secret Scanning | Uses Gitleaks to look for committed secrets | ${{ needs.secret-scanning.result }} |" + echo "| 📦 Dependency Scan | Uses Trivy to scan dependencies for HIGH/CRITICAL vulns | ${{ needs.dependency-scan.result }} |" + echo "| 🔍 SAST Scan | Uses Semgrep to look for insecure code patterns | ${{ needs.sast-scan.result }} |" + echo "" + echo "Legend for the **Status** column:" + echo "- \`success\`: job finished and all checks/tests passed." + echo "- \`failure\`: job ran but one or more checks/tests failed (see that job's log)." + echo "- \`cancelled\`: job was stopped before finishing." + echo "- \`skipped\`: job did not run, usually because CI was skipped for this commit." + echo "" + echo "### 📊 How to read the backend test results" + echo "" + echo "- The **Backend Tests** row tells you if the test run as a whole passed or failed." + echo "- To see which specific tests failed or how they ran:" + echo " 1. Open the **test-backend** job in this workflow run." + echo " 2. Look at the **Run backend tests** step to see the \`pytest -v\` output." + echo " 3. For local debugging, run \`pytest tests/ -v\` in your dev environment." + } >> \"${GITHUB_STEP_SUMMARY:-/dev/stdout}\" continue-on-error: true diff --git a/backend/api/auth.py b/backend/api/auth.py index 62c2715..0b219df 100644 --- a/backend/api/auth.py +++ b/backend/api/auth.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import uuid from datetime import datetime, timedelta from typing import Annotated @@ -87,7 +88,7 @@ def create_access_token(data: dict, expires_delta: timedelta) -> str: """Create JWT access token.""" to_encode = data.copy() expire = datetime.utcnow() + expires_delta - to_encode.update({"exp": expire}) + to_encode.update({"exp": expire, "jti": str(uuid.uuid4())}) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) @@ -95,7 +96,7 @@ def create_refresh_token(data: dict) -> str: """Create JWT refresh token.""" to_encode = data.copy() expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS) - to_encode.update({"exp": expire, "type": "refresh"}) + to_encode.update({"exp": expire, "type": "refresh", "jti": str(uuid.uuid4())}) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) diff --git a/tests/test_api_auth.py b/tests/test_api_auth.py index b9fd8f6..42adcd3 100644 --- a/tests/test_api_auth.py +++ b/tests/test_api_auth.py @@ -126,7 +126,7 @@ class TestTokenRefresh: """Test token refresh endpoint.""" def test_refresh_token_success( - self, test_client: TestClient, auth_token: str + self, test_client: TestClient ): """Verify successful token refresh.""" # Get refresh token from login @@ -134,7 +134,10 @@ class TestTokenRefresh: "/api/v1/auth/login", json={"username": "testadmin", "password": "testpass"} ) - refresh_token = login_response.json()["refresh_token"] + assert login_response.status_code == 200 + login_data = login_response.json() + initial_access_token = login_data["access_token"] + refresh_token = login_data["refresh_token"] # Use refresh token to get new access token response = test_client.post( @@ -146,7 +149,7 @@ class TestTokenRefresh: data = response.json() assert "access_token" in data assert "refresh_token" in data - assert data["access_token"] != auth_token # Should be different token + assert data["access_token"] != initial_access_token # Should be different token def test_refresh_token_with_invalid_token(self, test_client: TestClient): """Verify 401 with invalid refresh token."""