Compare commits
1 commit
main
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef05db2eba |
2802 changed files with 112375 additions and 154219 deletions
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"extends": ["@commitlint/config-conventional"],
|
||||
"rules": {
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"feat",
|
||||
"fix",
|
||||
"docs",
|
||||
"style",
|
||||
"refactor",
|
||||
"perf",
|
||||
"test",
|
||||
"build",
|
||||
"ci",
|
||||
"chore",
|
||||
"revert",
|
||||
"security"
|
||||
]
|
||||
],
|
||||
"subject-case": [0],
|
||||
"header-max-length": [2, "always", 120],
|
||||
"body-max-line-length": [1, "always", 200],
|
||||
"footer-max-line-length": [0]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
# cleanup-failed.yml — workflow_dispatch only.
|
||||
#
|
||||
# Tears down the kept-alive failed-deploy color (the inactive one
|
||||
# that survived a Phase D / Phase F failure for forensics).
|
||||
# Operator triggers this once they have read the journalctl output.
|
||||
#
|
||||
# Hard safety in playbooks/cleanup_failed.yml: refuses to destroy
|
||||
# the currently-active color.
|
||||
name: Veza cleanup failed-deploy color
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
env:
|
||||
description: "Environment to clean up"
|
||||
required: true
|
||||
type: choice
|
||||
options: [staging, prod]
|
||||
color:
|
||||
description: "Color to destroy (must NOT be the active one)"
|
||||
required: true
|
||||
type: choice
|
||||
options: [blue, green]
|
||||
|
||||
concurrency:
|
||||
group: cleanup-${{ inputs.env }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Destroy ${{ inputs.color }} app containers in ${{ inputs.env }}
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install ansible
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ansible
|
||||
ansible-galaxy collection install community.general
|
||||
|
||||
- name: Write vault password
|
||||
env:
|
||||
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
|
||||
run: |
|
||||
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
|
||||
chmod 0400 "$RUNNER_TEMP/vault-pass"
|
||||
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run cleanup_failed.yml
|
||||
working-directory: infra/ansible
|
||||
env:
|
||||
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-cleanup-${{ inputs.env }}-${{ inputs.color }}.log
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
ansible-playbook \
|
||||
-i inventory/${{ inputs.env }}.yml \
|
||||
playbooks/cleanup_failed.yml \
|
||||
--vault-password-file "$VAULT_PASS_FILE" \
|
||||
-e veza_env=${{ inputs.env }} \
|
||||
-e target_color=${{ inputs.color }}
|
||||
|
||||
- name: Upload Ansible log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ansible-cleanup-${{ inputs.env }}-${{ inputs.color }}
|
||||
path: ${{ runner.temp }}/ansible-cleanup-*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Shred vault password file
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "$VAULT_PASS_FILE" ]; then
|
||||
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
|
||||
fi
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
# Veza deploy pipeline.
|
||||
#
|
||||
# Triggers (intentionally narrow — see SECURITY note below):
|
||||
# workflow_dispatch → operator-supplied env + sha
|
||||
# (push:main + tag:v* are commented OUT until provisioning is
|
||||
# complete — see docs/RUNBOOK_DEPLOY_BOOTSTRAP.md. Re-enable
|
||||
# once secrets/runner/vault are in place and a manual run via
|
||||
# workflow_dispatch has been verified GREEN.)
|
||||
#
|
||||
# SECURITY: this workflow runs on a self-hosted runner with access to
|
||||
# the Incus unix socket (effectively root on the host). DO NOT add
|
||||
# `pull_request` or any fork-influenced trigger here — an attacker-
|
||||
# controlled fork would be able to `incus exec` arbitrarily. The
|
||||
# narrow trigger list above is the security boundary.
|
||||
#
|
||||
# Sequence : build (3 jobs in parallel) → upload artifacts → deploy.
|
||||
name: Veza deploy
|
||||
|
||||
on:
|
||||
# push: # GATED — uncomment after first
|
||||
# branches: [main] # successful workflow_dispatch run
|
||||
# tags: ['v*'] # see RUNBOOK_DEPLOY_BOOTSTRAP.md
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
env:
|
||||
description: "Environment to deploy"
|
||||
required: true
|
||||
default: staging
|
||||
type: choice
|
||||
options: [staging, prod]
|
||||
release_sha:
|
||||
description: "Full git SHA to deploy (defaults to current HEAD if empty)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
# Only one deploy per env at a time. Newer pushes cancel older
|
||||
# in-flight builds for the same env (the user almost always wants
|
||||
# the newer commit).
|
||||
group: deploy-${{ github.ref_type == 'tag' && 'prod' || 'staging' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Where build artefacts land. Set in Forgejo repo Variables :
|
||||
# FORGEJO_REGISTRY_URL = https://forgejo.veza.fr/api/packages/talas/generic
|
||||
REGISTRY_URL: ${{ vars.FORGEJO_REGISTRY_URL }}
|
||||
|
||||
jobs:
|
||||
# =================================================================
|
||||
# Resolve env + sha from the trigger.
|
||||
# =================================================================
|
||||
resolve:
|
||||
name: Resolve env + SHA
|
||||
runs-on: [self-hosted, incus]
|
||||
outputs:
|
||||
env: ${{ steps.r.outputs.env }}
|
||||
sha: ${{ steps.r.outputs.sha }}
|
||||
steps:
|
||||
- name: Resolve
|
||||
id: r
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
ENV="${{ inputs.env }}"
|
||||
SHA="${{ inputs.release_sha || github.sha }}"
|
||||
elif [ "${{ github.ref_type }}" = "tag" ]; then
|
||||
ENV="prod"
|
||||
SHA="${{ github.sha }}"
|
||||
else
|
||||
ENV="staging"
|
||||
SHA="${{ github.sha }}"
|
||||
fi
|
||||
if ! echo "$SHA" | grep -Eq '^[0-9a-f]{40}$'; then
|
||||
echo "SHA '$SHA' is not a 40-char git SHA"
|
||||
exit 1
|
||||
fi
|
||||
echo "env=$ENV" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved env=$ENV sha=$SHA"
|
||||
|
||||
# =================================================================
|
||||
# Build backend (Go).
|
||||
# =================================================================
|
||||
build-backend:
|
||||
name: Build backend
|
||||
needs: resolve
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
cache-dependency-path: veza-backend-api/go.sum
|
||||
|
||||
- name: Test
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
VEZA_SKIP_INTEGRATION: "1"
|
||||
run: go test ./... -short -count=1 -timeout 300s
|
||||
|
||||
- name: Build veza-api (CGO=0, static)
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
run: |
|
||||
go build -trimpath -ldflags "-s -w" \
|
||||
-o ./bin/veza-api ./cmd/api/main.go
|
||||
go build -trimpath -ldflags "-s -w" \
|
||||
-o ./bin/migrate_tool ./cmd/migrate_tool/main.go
|
||||
|
||||
- name: Stage tarball contents
|
||||
working-directory: veza-backend-api
|
||||
run: |
|
||||
STAGE="$RUNNER_TEMP/veza-backend"
|
||||
mkdir -p "$STAGE/migrations"
|
||||
cp ./bin/veza-api ./bin/migrate_tool "$STAGE/"
|
||||
cp -r ./migrations/* "$STAGE/migrations/" || true
|
||||
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
|
||||
|
||||
- name: Pack tarball
|
||||
run: |
|
||||
cd "$RUNNER_TEMP"
|
||||
tar --use-compress-program=zstd -cf \
|
||||
"veza-backend-${{ needs.resolve.outputs.sha }}.tar.zst" \
|
||||
-C "$RUNNER_TEMP/veza-backend" .
|
||||
|
||||
- name: Push to Forgejo Package Registry
|
||||
env:
|
||||
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
TARBALL="veza-backend-${{ needs.resolve.outputs.sha }}.tar.zst"
|
||||
URL="${REGISTRY_URL}/veza-backend/${{ needs.resolve.outputs.sha }}/${TARBALL}"
|
||||
echo "PUT → $URL"
|
||||
curl -fsSL --fail-with-body -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$RUNNER_TEMP/${TARBALL}" \
|
||||
"${URL}"
|
||||
|
||||
# =================================================================
|
||||
# Build stream (Rust).
|
||||
# =================================================================
|
||||
build-stream:
|
||||
name: Build stream
|
||||
needs: resolve
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
|
||||
- name: Set up Rust toolchain
|
||||
run: |
|
||||
command -v rustup >/dev/null || \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||
source "$HOME/.cargo/env"
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
sudo apt-get update -qq && sudo apt-get install -y musl-tools
|
||||
|
||||
- name: Cache cargo + target
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
veza-stream-server/target
|
||||
key: deploy-${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
deploy-${{ runner.os }}-cargo-
|
||||
|
||||
- name: Test
|
||||
working-directory: veza-stream-server
|
||||
run: cargo test --workspace
|
||||
|
||||
- name: Build stream_server (musl static)
|
||||
working-directory: veza-stream-server
|
||||
run: |
|
||||
cargo build --release --locked \
|
||||
--target x86_64-unknown-linux-musl
|
||||
|
||||
- name: Stage tarball contents
|
||||
working-directory: veza-stream-server
|
||||
run: |
|
||||
STAGE="$RUNNER_TEMP/veza-stream"
|
||||
mkdir -p "$STAGE"
|
||||
cp ./target/x86_64-unknown-linux-musl/release/stream_server "$STAGE/"
|
||||
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
|
||||
|
||||
- name: Pack tarball
|
||||
run: |
|
||||
cd "$RUNNER_TEMP"
|
||||
tar --use-compress-program=zstd -cf \
|
||||
"veza-stream-${{ needs.resolve.outputs.sha }}.tar.zst" \
|
||||
-C "$RUNNER_TEMP/veza-stream" .
|
||||
|
||||
- name: Push to Forgejo Package Registry
|
||||
env:
|
||||
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
TARBALL="veza-stream-${{ needs.resolve.outputs.sha }}.tar.zst"
|
||||
URL="${REGISTRY_URL}/veza-stream/${{ needs.resolve.outputs.sha }}/${TARBALL}"
|
||||
echo "PUT → $URL"
|
||||
curl -fsSL --fail-with-body -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$RUNNER_TEMP/${TARBALL}" \
|
||||
"${URL}"
|
||||
|
||||
# =================================================================
|
||||
# Build web (React/Vite).
|
||||
# =================================================================
|
||||
build-web:
|
||||
name: Build web
|
||||
needs: resolve
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build design tokens
|
||||
run: npm run build:tokens --workspace=@veza/design-system
|
||||
|
||||
- name: Build SPA
|
||||
working-directory: apps/web
|
||||
env:
|
||||
VITE_API_URL: /api/v1
|
||||
VITE_DOMAIN: ${{ needs.resolve.outputs.env == 'prod' && 'veza.fr' || 'staging.veza.fr' }}
|
||||
VITE_RELEASE_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
run: npm run build
|
||||
|
||||
- name: Stage tarball contents
|
||||
run: |
|
||||
STAGE="$RUNNER_TEMP/veza-web"
|
||||
mkdir -p "$STAGE"
|
||||
cp -r apps/web/dist/* "$STAGE/"
|
||||
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
|
||||
|
||||
- name: Pack tarball
|
||||
run: |
|
||||
cd "$RUNNER_TEMP"
|
||||
tar --use-compress-program=zstd -cf \
|
||||
"veza-web-${{ needs.resolve.outputs.sha }}.tar.zst" \
|
||||
-C "$RUNNER_TEMP/veza-web" .
|
||||
|
||||
- name: Push to Forgejo Package Registry
|
||||
env:
|
||||
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
TARBALL="veza-web-${{ needs.resolve.outputs.sha }}.tar.zst"
|
||||
URL="${REGISTRY_URL}/veza-web/${{ needs.resolve.outputs.sha }}/${TARBALL}"
|
||||
echo "PUT → $URL"
|
||||
curl -fsSL --fail-with-body -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$RUNNER_TEMP/${TARBALL}" \
|
||||
"${URL}"
|
||||
|
||||
# =================================================================
|
||||
# Deploy via Ansible. Runs on the self-hosted runner that has
|
||||
# Incus socket access (label `incus`). Requires Forgejo secrets:
|
||||
# ANSIBLE_VAULT_PASSWORD — unlocks group_vars/all/vault.yml
|
||||
# FORGEJO_REGISTRY_TOKEN — same token the build jobs use,
|
||||
# passed to ansible-playbook so
|
||||
# the data containers can fetch
|
||||
# the tarballs they were just sent.
|
||||
# =================================================================
|
||||
deploy:
|
||||
name: Deploy via Ansible
|
||||
needs: [resolve, build-backend, build-stream, build-web]
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
|
||||
- name: Install ansible + community.general + community.postgresql + community.rabbitmq
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ansible python3-psycopg2 python3-pip
|
||||
ansible-galaxy collection install \
|
||||
community.general \
|
||||
community.postgresql \
|
||||
community.rabbitmq
|
||||
|
||||
- name: Write vault password to a tmpfile
|
||||
env:
|
||||
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
|
||||
run: |
|
||||
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
|
||||
chmod 0400 "$RUNNER_TEMP/vault-pass"
|
||||
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run deploy_data.yml (idempotent provisioning + ZFS snapshot)
|
||||
working-directory: infra/ansible
|
||||
env:
|
||||
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-data-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}.log
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
ansible-playbook \
|
||||
-i inventory/${{ needs.resolve.outputs.env }}.yml \
|
||||
playbooks/deploy_data.yml \
|
||||
--vault-password-file "$VAULT_PASS_FILE" \
|
||||
-e veza_env=${{ needs.resolve.outputs.env }} \
|
||||
-e veza_release_sha=${{ needs.resolve.outputs.sha }} \
|
||||
-e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Run deploy_app.yml (blue/green)
|
||||
working-directory: infra/ansible
|
||||
env:
|
||||
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-app-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}.log
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
ansible-playbook \
|
||||
-i inventory/${{ needs.resolve.outputs.env }}.yml \
|
||||
playbooks/deploy_app.yml \
|
||||
--vault-password-file "$VAULT_PASS_FILE" \
|
||||
-e veza_env=${{ needs.resolve.outputs.env }} \
|
||||
-e veza_release_sha=${{ needs.resolve.outputs.sha }} \
|
||||
-e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Upload Ansible logs (for forensics)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ansible-logs-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}
|
||||
path: ${{ runner.temp }}/ansible-*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Shred vault password file
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "$VAULT_PASS_FILE" ]; then
|
||||
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
|
||||
fi
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
# rollback.yml — workflow_dispatch only.
|
||||
#
|
||||
# Two modes :
|
||||
# fast — flip HAProxy back to the previous color. ~5s. Requires
|
||||
# the target color's containers to still be alive
|
||||
# (i.e., no later deploy has recycled them).
|
||||
# full — re-run deploy_app.yml with a specific (older) release_sha.
|
||||
# ~5-10min. The artefact must still be in the Forgejo
|
||||
# registry (default retention 30 SHA per component).
|
||||
#
|
||||
# See docs/RUNBOOK_ROLLBACK.md for decision criteria.
|
||||
name: Veza rollback
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
env:
|
||||
description: "Environment to rollback"
|
||||
required: true
|
||||
type: choice
|
||||
options: [staging, prod]
|
||||
mode:
|
||||
description: "Rollback mode"
|
||||
required: true
|
||||
type: choice
|
||||
options: [fast, full]
|
||||
target_color:
|
||||
description: "(mode=fast only) color to flip back TO (the prior active one)"
|
||||
required: false
|
||||
type: choice
|
||||
options: [blue, green]
|
||||
release_sha:
|
||||
description: "(mode=full only) 40-char SHA of the release to redeploy"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: rollback-${{ inputs.env }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
rollback:
|
||||
name: Rollback ${{ inputs.env }} (${{ inputs.mode }})
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Validate inputs
|
||||
run: |
|
||||
if [ "${{ inputs.mode }}" = "fast" ] && [ -z "${{ inputs.target_color }}" ]; then
|
||||
echo "mode=fast requires target_color"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ inputs.mode }}" = "full" ]; then
|
||||
if [ -z "${{ inputs.release_sha }}" ]; then
|
||||
echo "mode=full requires release_sha"
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "${{ inputs.release_sha }}" | grep -Eq '^[0-9a-f]{40}$'; then
|
||||
echo "release_sha is not a 40-char git SHA"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ inputs.mode == 'full' && inputs.release_sha || github.ref }}
|
||||
|
||||
- name: Install ansible + collections
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ansible python3-psycopg2
|
||||
ansible-galaxy collection install \
|
||||
community.general \
|
||||
community.postgresql \
|
||||
community.rabbitmq
|
||||
|
||||
- name: Write vault password
|
||||
env:
|
||||
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
|
||||
run: |
|
||||
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
|
||||
chmod 0400 "$RUNNER_TEMP/vault-pass"
|
||||
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run rollback.yml
|
||||
working-directory: infra/ansible
|
||||
env:
|
||||
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-rollback-${{ inputs.env }}-${{ inputs.mode }}.log
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
EXTRA="-e veza_env=${{ inputs.env }} -e mode=${{ inputs.mode }}"
|
||||
if [ "${{ inputs.mode }}" = "fast" ]; then
|
||||
EXTRA="$EXTRA -e target_color=${{ inputs.target_color }}"
|
||||
else
|
||||
EXTRA="$EXTRA -e veza_release_sha=${{ inputs.release_sha }}"
|
||||
EXTRA="$EXTRA -e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}"
|
||||
fi
|
||||
ansible-playbook \
|
||||
-i inventory/${{ inputs.env }}.yml \
|
||||
playbooks/rollback.yml \
|
||||
--vault-password-file "$VAULT_PASS_FILE" \
|
||||
$EXTRA
|
||||
|
||||
- name: Upload Ansible log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ansible-rollback-${{ inputs.env }}-${{ inputs.mode }}
|
||||
path: ${{ runner.temp }}/ansible-rollback-*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Shred vault password file
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "$VAULT_PASS_FILE" ]; then
|
||||
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
|
||||
fi
|
||||
153
.github/workflows/backend-ci.yml
vendored
Normal file
153
.github/workflows/backend-ci.yml
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
name: Backend API CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "veza-backend-api/**"
|
||||
- ".github/workflows/backend-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "veza-backend-api/**"
|
||||
- ".github/workflows/backend-ci.yml"
|
||||
|
||||
jobs:
|
||||
test-unit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-backend-api
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Download deps
|
||||
run: go mod download
|
||||
|
||||
- name: Go vet and format check
|
||||
run: |
|
||||
go vet ./...
|
||||
test -z "$(gofmt -l .)"
|
||||
working-directory: veza-backend-api
|
||||
|
||||
- name: Run govulncheck
|
||||
run: |
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
govulncheck ./...
|
||||
|
||||
- name: Run unit tests with coverage
|
||||
run: >
|
||||
go test
|
||||
./internal/handlers/...
|
||||
./internal/services/...
|
||||
./internal/core/...
|
||||
./internal/middleware/...
|
||||
-short -coverprofile=coverage.out -covermode=atomic -timeout 5m
|
||||
|
||||
- name: Enforce coverage threshold (>= 70%)
|
||||
run: |
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $NF}' | tr -d '%')
|
||||
echo "Total coverage: ${COVERAGE}%"
|
||||
if [ -z "$COVERAGE" ]; then
|
||||
echo "::warning::Could not parse coverage percentage"
|
||||
exit 0
|
||||
fi
|
||||
# Compare as integers (remove decimal)
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -lt 70 ]; then
|
||||
echo "::error::Coverage ${COVERAGE}% is below the 70% threshold"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Coverage ${COVERAGE}% meets the >= 70% threshold"
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: go-coverage
|
||||
path: veza-backend-api/coverage.out
|
||||
|
||||
- name: Generate coverage badge
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
run: |
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep '^total:' | awk '{print $NF}' | tr -d '%')
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -ge 80 ]; then
|
||||
COLOR="brightgreen"
|
||||
elif [ "$COV_INT" -ge 70 ]; then
|
||||
COLOR="green"
|
||||
elif [ "$COV_INT" -ge 50 ]; then
|
||||
COLOR="yellow"
|
||||
else
|
||||
COLOR="red"
|
||||
fi
|
||||
echo "{\"schemaVersion\":1,\"label\":\"Go coverage\",\"message\":\"${COVERAGE}%\",\"color\":\"${COLOR}\"}" > coverage-badge.json
|
||||
echo "Coverage badge: ${COVERAGE}% (${COLOR})"
|
||||
|
||||
- name: Upload coverage badge
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: go-coverage-badge
|
||||
path: veza-backend-api/coverage-badge.json
|
||||
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: veza_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
env:
|
||||
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veza_test?sslmode=disable
|
||||
REDIS_URL: redis://localhost:6379
|
||||
JWT_SECRET: test-jwt-secret-for-ci
|
||||
APP_ENV: test
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-backend-api
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Download deps
|
||||
run: go mod download
|
||||
|
||||
- name: Run migrations
|
||||
run: go run cmd/migrate_tool/main.go
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run integration tests
|
||||
run: go test -tags=integration ./internal/... -timeout 15m
|
||||
165
.github/workflows/cd.yml
vendored
Normal file
165
.github/workflows/cd.yml
vendored
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
name: Veza CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Deployment environment'
|
||||
required: true
|
||||
default: 'staging'
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- production
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and push images
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
# Push to registry: set repo secrets DOCKER_REGISTRY, DOCKER_REGISTRY_USERNAME, DOCKER_REGISTRY_PASSWORD
|
||||
# Example: DOCKER_REGISTRY=ghcr.io/org/repo or registry.example.com/veza
|
||||
- name: Build Backend Docker Image
|
||||
run: |
|
||||
docker build -t veza-backend-api:${{ github.sha }} -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
|
||||
- name: Build Frontend Docker Image
|
||||
run: |
|
||||
docker build -t veza-frontend:${{ github.sha }} -f apps/web/Dockerfile.production apps/web/
|
||||
|
||||
- name: Build Stream Server Docker Image
|
||||
run: |
|
||||
docker build -t veza-stream-server:${{ github.sha }} -f veza-stream-server/Dockerfile.production veza-stream-server/
|
||||
|
||||
- name: Trivy vulnerability scan
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-backend-api:${{ github.sha }}'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Trivy scan frontend
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-frontend:${{ github.sha }}'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Trivy scan stream server
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-stream-server:${{ github.sha }}'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
||||
- name: Generate SBOM
|
||||
run: |
|
||||
mkdir -p sbom
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
trivy image --format cyclonedx --output "sbom/${svc}-${{ github.sha }}.json" "${svc}:${{ github.sha }}"
|
||||
done
|
||||
- name: Upload SBOM artifacts
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: sbom
|
||||
path: sbom/
|
||||
|
||||
- name: Push Images to Registry
|
||||
if: vars.DOCKER_REGISTRY != ''
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" | docker login "${{ vars.DOCKER_REGISTRY }}" -u "${{ secrets.DOCKER_REGISTRY_USERNAME }}" --password-stdin
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
docker tag "${svc}:${{ github.sha }}" "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
docker tag "${svc}:${{ github.sha }}" "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
done
|
||||
|
||||
- name: Install cosign
|
||||
if: vars.DOCKER_REGISTRY != '' && vars.COSIGN_ENABLED == 'true'
|
||||
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
|
||||
with:
|
||||
cosign-release: 'v2.2.0'
|
||||
- name: Sign images with cosign
|
||||
if: vars.DOCKER_REGISTRY != '' && vars.COSIGN_ENABLED == 'true'
|
||||
env:
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
run: |
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}"
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${{ vars.DOCKER_REGISTRY }}/${svc}:latest"
|
||||
done
|
||||
|
||||
- name: Build Summary
|
||||
run: |
|
||||
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Backend: veza-backend-api:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Frontend: veza-frontend:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Stream Server: veza-stream-server:${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
deploy:
|
||||
name: Deploy to ${{ github.event.inputs.environment || 'staging' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
|
||||
environment: ${{ github.event.inputs.environment || 'staging' }}
|
||||
steps:
|
||||
- name: Deploy to Kubernetes
|
||||
if: vars.KUBE_CONFIG_SET == 'true'
|
||||
run: |
|
||||
KUBECONFIG="${{ runner.temp }}/kubeconfig"
|
||||
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > "$KUBECONFIG"
|
||||
chmod 600 "$KUBECONFIG"
|
||||
export KUBECONFIG
|
||||
for svc in veza-backend-api veza-stream-server; do
|
||||
kubectl set image "deployment/${svc}" "${svc}=${{ vars.DOCKER_REGISTRY }}/${svc}:${{ github.sha }}" \
|
||||
-n veza --record || echo "Skipping ${svc} (deployment not found)"
|
||||
done
|
||||
kubectl rollout status deployment/veza-backend-api -n veza --timeout=300s || true
|
||||
rm -f "$KUBECONFIG"
|
||||
|
||||
- name: Deployment Summary
|
||||
run: |
|
||||
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Environment: ${{ github.event.inputs.environment || 'staging' }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
smoke-post-deploy:
|
||||
name: Smoke tests post-deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
if: vars.STAGING_URL != ''
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright
|
||||
run: npx playwright install chromium --with-deps
|
||||
|
||||
- name: Run smoke tests
|
||||
env:
|
||||
PLAYWRIGHT_BASE_URL: ${{ vars.STAGING_URL }}
|
||||
run: |
|
||||
cd apps/web
|
||||
npx playwright test --config=playwright.config.smoke.ts
|
||||
|
||||
511
.github/workflows/ci.yml
vendored
511
.github/workflows/ci.yml
vendored
|
|
@ -1,249 +1,338 @@
|
|||
name: Veza CI
|
||||
name: Veza CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main", "remediation/*", "feature/mvp-complete"]
|
||||
pull_request:
|
||||
branches: ["main", "feature/mvp-complete"]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
push:
|
||||
branches: [ "main", "remediation/*", "feature/mvp-complete" ]
|
||||
pull_request:
|
||||
branches: [ "main", "feature/mvp-complete" ]
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
# ===========================================================================
|
||||
# Backend (Go) — build, test, lint, security
|
||||
# ===========================================================================
|
||||
backend:
|
||||
name: Backend (Go)
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
backend-go:
|
||||
name: Backend (Go)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
# go.mod/go.sum live under veza-backend-api, not repo root.
|
||||
# Without this, setup-go warns "Dependencies file is not
|
||||
# found" and skips the mod cache → adds ~60-90s per run.
|
||||
cache-dependency-path: veza-backend-api/go.sum
|
||||
- name: Check VERSION matches git tag
|
||||
run: |
|
||||
current_tag=$(git describe --tags --exact-match 2>/dev/null || true)
|
||||
if [ -n "$current_tag" ]; then
|
||||
version_file=$(cat VERSION)
|
||||
tag_version=${current_tag#v}
|
||||
if [ "$version_file" != "$tag_version" ]; then
|
||||
echo "VERSION mismatch: VERSION=$version_file, current tag=$current_tag"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Cache Go tool binaries
|
||||
id: go-tools-cache
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: ~/go/bin
|
||||
key: ${{ runner.os }}-go-tools-govulncheck-golangci-lint-v2
|
||||
# Save the cache even when later steps (Lint, Test, etc.)
|
||||
# fail so the next run benefits from the installed tools.
|
||||
save-always: true
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Go tools
|
||||
# NOTE: golangci-lint v2 lives under the /v2/ module path.
|
||||
# The old /cmd/ path still resolves to v1.64.x, which rejects
|
||||
# v2-format .golangci.yml with "please use golangci-lint v2".
|
||||
# Pinned versions so the cache key stays stable.
|
||||
if: steps.go-tools-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache: true
|
||||
|
||||
- name: Add ~/go/bin to PATH
|
||||
run: echo "$HOME/go/bin" >> $GITHUB_PATH
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: go build ./...
|
||||
working-directory: veza-backend-api
|
||||
- name: Run govulncheck
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
govulncheck ./...
|
||||
|
||||
- name: Test
|
||||
# -short + VEZA_SKIP_INTEGRATION=1 so testcontainers-go (which
|
||||
# needs a Docker socket) is not invoked on the Forgejo runner.
|
||||
# Integration tests run in a dedicated nightly job with DinD.
|
||||
run: go test ./... -short -count=1 -timeout 300s -coverprofile=coverage.out
|
||||
env:
|
||||
VEZA_SKIP_INTEGRATION: "1"
|
||||
working-directory: veza-backend-api
|
||||
- name: Vet
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go vet ./...
|
||||
|
||||
- name: Lint
|
||||
run: golangci-lint run ./... --timeout 5m
|
||||
working-directory: veza-backend-api
|
||||
- name: Install golangci-lint
|
||||
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
working-directory: veza-backend-api
|
||||
- name: Lint
|
||||
run: npx turbo run lint --filter=veza-backend-api
|
||||
|
||||
- name: Vulnerability check
|
||||
run: govulncheck ./...
|
||||
working-directory: veza-backend-api
|
||||
- name: Test with coverage
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go test ./internal/handlers/... ./internal/services/... -short -coverprofile=coverage.out -covermode=atomic
|
||||
COVERAGE=$(go tool cover -func=coverage.out | tail -1 | awk '{print $3}' | tr -d '%')
|
||||
echo "Coverage: ${COVERAGE}%"
|
||||
echo "coverage=${COVERAGE}" >> $GITHUB_OUTPUT
|
||||
if awk -v c="$COVERAGE" -v t=60 'BEGIN {exit !(c+0>=t)}'; then
|
||||
echo "Coverage gate passed (>= 60%)"
|
||||
else
|
||||
echo "Coverage $COVERAGE% is below threshold 60%"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Coverage summary
|
||||
run: |
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}')
|
||||
echo "## Backend Coverage: $COVERAGE" >> $GITHUB_STEP_SUMMARY
|
||||
working-directory: veza-backend-api
|
||||
- name: Build
|
||||
run: npx turbo run build --filter=veza-backend-api
|
||||
|
||||
# ===========================================================================
|
||||
# Frontend (Web) — lint, typecheck, build, unit tests
|
||||
# ===========================================================================
|
||||
frontend:
|
||||
name: Frontend (Web)
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
rust-services:
|
||||
name: Rust Services (Stream)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: package-lock.json
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Set up Rust
|
||||
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
# Sprint 2 design-system migrated to Style Dictionary; the
|
||||
# generated tokens live in packages/design-system/dist/ which
|
||||
# is gitignored. apps/web imports `@veza/design-system/tokens-generated`,
|
||||
# so dist/ MUST exist before tsc/vitest/build runs.
|
||||
# `prepare` in the package would normally cover npm ci, but
|
||||
# this explicit step makes the dependency loud and runnable
|
||||
# standalone for local debugging.
|
||||
- name: Build design tokens
|
||||
run: npm run build:tokens --workspace=@veza/design-system
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# Prevents drift between veza-backend-api/openapi.yaml and
|
||||
# apps/web/src/types/generated/. Regenerates then fails if
|
||||
# git diff is non-empty.
|
||||
- name: Check OpenAPI types in sync
|
||||
run: bash scripts/check-types-sync.sh
|
||||
working-directory: apps/web
|
||||
- name: Cache Cargo registry
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Lint
|
||||
# ESLint warning baseline (v1.0.10 dette tech).
|
||||
# Lowered from 1204 → 1108 after no-unused-vars sprint
|
||||
# (134 → 0). Top contributors at this baseline :
|
||||
# 757 no-restricted-syntax (custom design-system rule —
|
||||
# Tailwind defaults / hex literals / native <button>)
|
||||
# 139 @typescript-eslint/no-non-null-assertion
|
||||
# 115 @typescript-eslint/no-explicit-any
|
||||
# 47 react-hooks/exhaustive-deps
|
||||
# 25 react-refresh/only-export-components
|
||||
# 23 storybook/no-redundant-story-name
|
||||
# CI fails on ANY new warning. Lower this number as
|
||||
# warnings are resorbed ; never raise it.
|
||||
run: npx eslint --max-warnings=1108 .
|
||||
working-directory: apps/web
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
|
||||
- name: Typecheck
|
||||
run: npx tsc --noEmit
|
||||
working-directory: apps/web
|
||||
- name: Auditing Stream Server
|
||||
run: |
|
||||
cd veza-stream-server
|
||||
cargo audit
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
working-directory: apps/web
|
||||
- name: Lint
|
||||
run: npx turbo run lint --filter=veza-stream-server
|
||||
|
||||
- name: Bundle size gate
|
||||
run: node scripts/check-bundle-size.mjs
|
||||
working-directory: apps/web
|
||||
- name: Build
|
||||
run: npx turbo run build --filter=veza-stream-server
|
||||
|
||||
- name: Audit dependencies
|
||||
run: npm audit --audit-level=critical
|
||||
- name: Test
|
||||
run: npx turbo run test --filter=veza-stream-server
|
||||
|
||||
- name: Unit tests
|
||||
run: npx vitest run --reporter=verbose
|
||||
working-directory: apps/web
|
||||
frontend:
|
||||
name: Frontend (Web)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
# ===========================================================================
|
||||
# Rust (Stream Server) — build, test, lint, audit
|
||||
# ===========================================================================
|
||||
rust:
|
||||
name: Rust (Stream Server)
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Cache rustup toolchain
|
||||
id: rustup-cache
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
~/.rustup
|
||||
~/.cargo/bin
|
||||
key: ${{ runner.os }}-rustup-stable-rustfmt-clippy-audit-tarpaulin
|
||||
save-always: true
|
||||
- name: Security audit (npm)
|
||||
run: npm audit --audit-level=critical
|
||||
|
||||
- name: Set up Rust
|
||||
if: steps.rustup-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --component rustfmt,clippy
|
||||
- name: Cache Generated Types
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: apps/web/src/types/generated
|
||||
key: ${{ runner.os }}-generated-types-${{ hashFiles('veza-backend-api/openapi.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-generated-types-
|
||||
|
||||
- name: Add ~/.cargo/bin to PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
- name: Generate Types from OpenAPI
|
||||
run: |
|
||||
cd apps/web
|
||||
chmod +x scripts/generate-types.sh
|
||||
./scripts/generate-types.sh
|
||||
continue-on-error: false
|
||||
# This step ensures types are generated before typecheck
|
||||
# Cache keyed on openapi.yaml hash, so types regenerate when spec changes
|
||||
|
||||
- name: Cache Cargo deps and target
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
veza-stream-server/target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
save-always: true
|
||||
- name: Check types sync with OpenAPI spec
|
||||
run: |
|
||||
if ! git diff --exit-code apps/web/src/types/generated/; then
|
||||
echo "::error::Types are out of sync with openapi.yaml. Run 'make openapi' then 'cd apps/web && ./scripts/generate-types.sh' and commit the updated types."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
run: cargo build
|
||||
working-directory: veza-stream-server
|
||||
- name: Lint
|
||||
run: npx turbo run lint --filter=veza-frontend
|
||||
|
||||
- name: Test
|
||||
run: cargo test --workspace
|
||||
working-directory: veza-stream-server
|
||||
- name: Format Check
|
||||
run: |
|
||||
cd apps/web
|
||||
npm run format:check --if-present
|
||||
|
||||
- name: Clippy
|
||||
# NOTE: -D warnings temporarily lifted while the team resorbs
|
||||
# the Rust clippy backlog (~20 warnings: unused imports,
|
||||
# missing Default impls, manual clamp/contains, etc.).
|
||||
# Re-enable once the backlog is cleared.
|
||||
run: cargo clippy --all-targets
|
||||
working-directory: veza-stream-server
|
||||
- name: Type Check
|
||||
run: |
|
||||
cd apps/web
|
||||
npm run typecheck
|
||||
|
||||
- name: Format check
|
||||
run: cargo fmt -- --check
|
||||
working-directory: veza-stream-server
|
||||
- name: Test with coverage
|
||||
run: npx turbo run test --filter=veza-frontend -- --run --coverage
|
||||
|
||||
- name: Security audit
|
||||
# cargo-audit is cached with the rustup toolchain (~/.cargo/bin),
|
||||
# so the install is a no-op on warm cache.
|
||||
run: |
|
||||
command -v cargo-audit >/dev/null || cargo install cargo-audit --locked
|
||||
cargo audit
|
||||
working-directory: veza-stream-server
|
||||
- name: Contrast Tests
|
||||
run: |
|
||||
cd apps/web
|
||||
npm run test -- --run src/__tests__/contrast.test.ts
|
||||
|
||||
# Rust coverage via cargo-tarpaulin is disabled in ci.yml because
|
||||
# tarpaulin needs CAP_SYS_PTRACE to disable ASLR, which the Docker
|
||||
# container running the Forgejo act runner doesn't grant:
|
||||
# "ERROR cargo_tarpaulin: Failed to run tests:
|
||||
# ASLR disable failed: EPERM: Operation not permitted"
|
||||
# Either (a) add `privileged: true` to the runner's container
|
||||
# config to grant ptrace, or (b) switch to `cargo llvm-cov`
|
||||
# which uses source-based coverage and doesn't need ptrace.
|
||||
# Until then, run coverage locally or in a dedicated nightly job.
|
||||
- name: Build
|
||||
run: npx turbo run build --filter=veza-frontend
|
||||
|
||||
# ===========================================================================
|
||||
# Notify on failure
|
||||
# ===========================================================================
|
||||
notify-failure:
|
||||
name: Notify on failure
|
||||
needs: [backend, frontend, rust]
|
||||
storybook:
|
||||
name: Storybook Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Storybook
|
||||
run: npm run build-storybook
|
||||
working-directory: apps/web
|
||||
|
||||
- name: Serve Storybook and run audit
|
||||
run: |
|
||||
npx serve -s storybook-static -l 6007 &
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:6007 >/dev/null; then
|
||||
echo "Storybook ready"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
curl -sf http://localhost:6007 >/dev/null || (echo "Storybook failed to start"; exit 1)
|
||||
npm run test:storybook
|
||||
working-directory: apps/web
|
||||
|
||||
e2e:
|
||||
name: E2E (Playwright)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache-dependency-path: veza-backend-api/go.sum
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Add veza.fr to hosts (for Vite proxy)
|
||||
run: echo "127.0.0.1 veza.fr" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Start backend services (Postgres, Redis, RabbitMQ)
|
||||
run: |
|
||||
docker-compose up -d postgres redis rabbitmq
|
||||
echo "Waiting for Postgres..."
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec veza_postgres pg_isready -U veza 2>/dev/null; then
|
||||
echo "Postgres ready"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
docker-compose ps
|
||||
|
||||
- name: Run database migrations
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go run cmd/migrate_tool/main.go
|
||||
|
||||
- name: Create E2E test user
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable
|
||||
TEST_EMAIL: e2e@test.com
|
||||
TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
|
||||
TEST_USERNAME: e2e
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go run cmd/tools/create_test_user/main.go
|
||||
|
||||
- name: Start backend API
|
||||
env:
|
||||
APP_ENV: development
|
||||
APP_PORT: "18080"
|
||||
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@localhost:15432/veza?sslmode=disable
|
||||
REDIS_URL: redis://localhost:16379
|
||||
JWT_SECRET: ${{ secrets.E2E_JWT_SECRET }}
|
||||
COOKIE_SECURE: "false"
|
||||
CORS_ALLOWED_ORIGINS: http://veza.fr:5173,http://veza.fr:5174,http://localhost:5173,http://localhost:5174
|
||||
RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL }}
|
||||
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
|
||||
ACCOUNT_LOCKOUT_EXEMPT_EMAILS: "e2e@test.com"
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go build -o veza-api ./cmd/api/main.go
|
||||
./veza-api &
|
||||
sleep 10
|
||||
curl -sf http://localhost:18080/api/v1/health > /tmp/health.json || (echo "Backend health check failed"; exit 1)
|
||||
jq -e '.status == "ok"' /tmp/health.json || (echo "Health response invalid"; exit 1)
|
||||
echo "Health check OK (status, DB/Redis connectivity verified)"
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
working-directory: apps/web
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npx playwright test
|
||||
working-directory: apps/web
|
||||
env:
|
||||
PORT: "5174"
|
||||
VITE_API_URL: '/api/v1'
|
||||
VITE_DOMAIN: veza.fr
|
||||
VITE_BACKEND_PORT: "18080"
|
||||
PLAYWRIGHT_BASE_URL: 'http://localhost:5174'
|
||||
TEST_EMAIL: e2e@test.com
|
||||
TEST_PASSWORD: ${{ secrets.E2E_TEST_PASSWORD }}
|
||||
|
||||
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
if: failure()
|
||||
runs-on: [self-hosted, incus]
|
||||
steps:
|
||||
- name: Summary
|
||||
run: echo "## ❌ CI Failed" >> $GITHUB_STEP_SUMMARY
|
||||
with:
|
||||
name: playwright-report
|
||||
path: apps/web/playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
notify-failure:
|
||||
name: Notify on failure
|
||||
needs: [backend-go, rust-services, frontend, storybook, e2e]
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack notification
|
||||
if: secrets.SLACK_WEBHOOK_URL != ''
|
||||
run: |
|
||||
curl -X POST -H 'Content-type: application/json' \
|
||||
--data "{\"text\":\"CI failed on ${{ github.repository }}: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
|
||||
"${{ secrets.SLACK_WEBHOOK_URL }}"
|
||||
|
|
|
|||
79
.github/workflows/cleanup-failed.yml
vendored
79
.github/workflows/cleanup-failed.yml
vendored
|
|
@ -1,79 +0,0 @@
|
|||
# cleanup-failed.yml — workflow_dispatch only.
|
||||
#
|
||||
# Tears down the kept-alive failed-deploy color (the inactive one
|
||||
# that survived a Phase D / Phase F failure for forensics).
|
||||
# Operator triggers this once they have read the journalctl output.
|
||||
#
|
||||
# Hard safety in playbooks/cleanup_failed.yml: refuses to destroy
|
||||
# the currently-active color.
|
||||
name: Veza cleanup failed-deploy color
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
env:
|
||||
description: "Environment to clean up"
|
||||
required: true
|
||||
type: choice
|
||||
options: [staging, prod]
|
||||
color:
|
||||
description: "Color to destroy (must NOT be the active one)"
|
||||
required: true
|
||||
type: choice
|
||||
options: [blue, green]
|
||||
|
||||
concurrency:
|
||||
group: cleanup-${{ inputs.env }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Destroy ${{ inputs.color }} app containers in ${{ inputs.env }}
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install ansible
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ansible
|
||||
ansible-galaxy collection install community.general
|
||||
|
||||
- name: Write vault password
|
||||
env:
|
||||
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
|
||||
run: |
|
||||
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
|
||||
chmod 0400 "$RUNNER_TEMP/vault-pass"
|
||||
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run cleanup_failed.yml
|
||||
working-directory: infra/ansible
|
||||
env:
|
||||
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-cleanup-${{ inputs.env }}-${{ inputs.color }}.log
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
ansible-playbook \
|
||||
-i inventory/${{ inputs.env }}.yml \
|
||||
playbooks/cleanup_failed.yml \
|
||||
--vault-password-file "$VAULT_PASS_FILE" \
|
||||
-e veza_env=${{ inputs.env }} \
|
||||
-e target_color=${{ inputs.color }}
|
||||
|
||||
- name: Upload Ansible log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ansible-cleanup-${{ inputs.env }}-${{ inputs.color }}
|
||||
path: ${{ runner.temp }}/ansible-cleanup-*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Shred vault password file
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "$VAULT_PASS_FILE" ]; then
|
||||
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
|
||||
fi
|
||||
84
.github/workflows/container-scan.yml
vendored
Normal file
84
.github/workflows/container-scan.yml
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
name: Container Image Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'veza-backend-api/Dockerfile*'
|
||||
- 'apps/web/Dockerfile*'
|
||||
- 'veza-stream-server/Dockerfile*'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'veza-backend-api/Dockerfile*'
|
||||
- 'apps/web/Dockerfile*'
|
||||
- 'veza-stream-server/Dockerfile*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
scan-backend:
|
||||
name: Scan Backend Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Build backend image
|
||||
run: docker build -t veza-backend:scan -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-backend:scan'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
ignore-unfixed: true
|
||||
|
||||
scan-stream-server:
|
||||
name: Scan Stream Server Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Build stream server image
|
||||
run: docker build -t veza-stream:scan -f veza-stream-server/Dockerfile .
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-stream:scan'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
ignore-unfixed: true
|
||||
|
||||
scan-frontend:
|
||||
name: Scan Frontend Image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Check if frontend Dockerfile exists
|
||||
id: check
|
||||
run: |
|
||||
if [ -f "apps/web/Dockerfile" ] || [ -f "apps/web/Dockerfile.production" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build frontend image
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
run: |
|
||||
DOCKERFILE=$([ -f "apps/web/Dockerfile.production" ] && echo "apps/web/Dockerfile.production" || echo "apps/web/Dockerfile")
|
||||
docker build -t veza-frontend:scan -f "$DOCKERFILE" apps/web/
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.28.0
|
||||
with:
|
||||
image-ref: 'veza-frontend:scan'
|
||||
format: 'table'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
ignore-unfixed: true
|
||||
360
.github/workflows/deploy.yml
vendored
360
.github/workflows/deploy.yml
vendored
|
|
@ -1,360 +0,0 @@
|
|||
# Veza deploy pipeline.
|
||||
#
|
||||
# Triggers (intentionally narrow — see SECURITY note below):
|
||||
# workflow_dispatch → operator-supplied env + sha
|
||||
# (push:main + tag:v* are commented OUT until provisioning is
|
||||
# complete — see docs/RUNBOOK_DEPLOY_BOOTSTRAP.md. Re-enable
|
||||
# once secrets/runner/vault are in place and a manual run via
|
||||
# workflow_dispatch has been verified GREEN.)
|
||||
#
|
||||
# SECURITY: this workflow runs on a self-hosted runner with access to
|
||||
# the Incus unix socket (effectively root on the host). DO NOT add
|
||||
# `pull_request` or any fork-influenced trigger here — an attacker-
|
||||
# controlled fork would be able to `incus exec` arbitrarily. The
|
||||
# narrow trigger list above is the security boundary.
|
||||
#
|
||||
# Sequence : build (3 jobs in parallel) → upload artifacts → deploy.
|
||||
name: Veza deploy
|
||||
|
||||
on:
|
||||
# push: # GATED — uncomment after first
|
||||
# branches: [main] # successful workflow_dispatch run
|
||||
# tags: ['v*'] # see RUNBOOK_DEPLOY_BOOTSTRAP.md
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
env:
|
||||
description: "Environment to deploy"
|
||||
required: true
|
||||
default: staging
|
||||
type: choice
|
||||
options: [staging, prod]
|
||||
release_sha:
|
||||
description: "Full git SHA to deploy (defaults to current HEAD if empty)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
# Only one deploy per env at a time. Newer pushes cancel older
|
||||
# in-flight builds for the same env (the user almost always wants
|
||||
# the newer commit).
|
||||
group: deploy-${{ github.ref_type == 'tag' && 'prod' || 'staging' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Where build artefacts land. Set in Forgejo repo Variables :
|
||||
# FORGEJO_REGISTRY_URL = https://forgejo.veza.fr/api/packages/talas/generic
|
||||
REGISTRY_URL: ${{ vars.FORGEJO_REGISTRY_URL }}
|
||||
|
||||
jobs:
|
||||
# =================================================================
|
||||
# Resolve env + sha from the trigger.
|
||||
# =================================================================
|
||||
resolve:
|
||||
name: Resolve env + SHA
|
||||
runs-on: [self-hosted, incus]
|
||||
outputs:
|
||||
env: ${{ steps.r.outputs.env }}
|
||||
sha: ${{ steps.r.outputs.sha }}
|
||||
steps:
|
||||
- name: Resolve
|
||||
id: r
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
ENV="${{ inputs.env }}"
|
||||
SHA="${{ inputs.release_sha || github.sha }}"
|
||||
elif [ "${{ github.ref_type }}" = "tag" ]; then
|
||||
ENV="prod"
|
||||
SHA="${{ github.sha }}"
|
||||
else
|
||||
ENV="staging"
|
||||
SHA="${{ github.sha }}"
|
||||
fi
|
||||
if ! echo "$SHA" | grep -Eq '^[0-9a-f]{40}$'; then
|
||||
echo "SHA '$SHA' is not a 40-char git SHA"
|
||||
exit 1
|
||||
fi
|
||||
echo "env=$ENV" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved env=$ENV sha=$SHA"
|
||||
|
||||
# =================================================================
|
||||
# Build backend (Go).
|
||||
# =================================================================
|
||||
build-backend:
|
||||
name: Build backend
|
||||
needs: resolve
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
cache-dependency-path: veza-backend-api/go.sum
|
||||
|
||||
- name: Test
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
VEZA_SKIP_INTEGRATION: "1"
|
||||
run: go test ./... -short -count=1 -timeout 300s
|
||||
|
||||
- name: Build veza-api (CGO=0, static)
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
run: |
|
||||
go build -trimpath -ldflags "-s -w" \
|
||||
-o ./bin/veza-api ./cmd/api/main.go
|
||||
go build -trimpath -ldflags "-s -w" \
|
||||
-o ./bin/migrate_tool ./cmd/migrate_tool/main.go
|
||||
|
||||
- name: Stage tarball contents
|
||||
working-directory: veza-backend-api
|
||||
run: |
|
||||
STAGE="$RUNNER_TEMP/veza-backend"
|
||||
mkdir -p "$STAGE/migrations"
|
||||
cp ./bin/veza-api ./bin/migrate_tool "$STAGE/"
|
||||
cp -r ./migrations/* "$STAGE/migrations/" || true
|
||||
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
|
||||
|
||||
- name: Pack tarball
|
||||
run: |
|
||||
cd "$RUNNER_TEMP"
|
||||
tar --use-compress-program=zstd -cf \
|
||||
"veza-backend-${{ needs.resolve.outputs.sha }}.tar.zst" \
|
||||
-C "$RUNNER_TEMP/veza-backend" .
|
||||
|
||||
- name: Push to Forgejo Package Registry
|
||||
env:
|
||||
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
TARBALL="veza-backend-${{ needs.resolve.outputs.sha }}.tar.zst"
|
||||
URL="${REGISTRY_URL}/veza-backend/${{ needs.resolve.outputs.sha }}/${TARBALL}"
|
||||
echo "PUT → $URL"
|
||||
curl -fsSL --fail-with-body -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$RUNNER_TEMP/${TARBALL}" \
|
||||
"${URL}"
|
||||
|
||||
# =================================================================
|
||||
# Build stream (Rust).
|
||||
# =================================================================
|
||||
build-stream:
|
||||
name: Build stream
|
||||
needs: resolve
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
|
||||
- name: Set up Rust toolchain
|
||||
run: |
|
||||
command -v rustup >/dev/null || \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||
source "$HOME/.cargo/env"
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
sudo apt-get update -qq && sudo apt-get install -y musl-tools
|
||||
|
||||
- name: Cache cargo + target
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
veza-stream-server/target
|
||||
key: deploy-${{ runner.os }}-cargo-${{ hashFiles('veza-stream-server/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
deploy-${{ runner.os }}-cargo-
|
||||
|
||||
- name: Test
|
||||
working-directory: veza-stream-server
|
||||
run: cargo test --workspace
|
||||
|
||||
- name: Build stream_server (musl static)
|
||||
working-directory: veza-stream-server
|
||||
run: |
|
||||
cargo build --release --locked \
|
||||
--target x86_64-unknown-linux-musl
|
||||
|
||||
- name: Stage tarball contents
|
||||
working-directory: veza-stream-server
|
||||
run: |
|
||||
STAGE="$RUNNER_TEMP/veza-stream"
|
||||
mkdir -p "$STAGE"
|
||||
cp ./target/x86_64-unknown-linux-musl/release/stream_server "$STAGE/"
|
||||
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
|
||||
|
||||
- name: Pack tarball
|
||||
run: |
|
||||
cd "$RUNNER_TEMP"
|
||||
tar --use-compress-program=zstd -cf \
|
||||
"veza-stream-${{ needs.resolve.outputs.sha }}.tar.zst" \
|
||||
-C "$RUNNER_TEMP/veza-stream" .
|
||||
|
||||
- name: Push to Forgejo Package Registry
|
||||
env:
|
||||
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
TARBALL="veza-stream-${{ needs.resolve.outputs.sha }}.tar.zst"
|
||||
URL="${REGISTRY_URL}/veza-stream/${{ needs.resolve.outputs.sha }}/${TARBALL}"
|
||||
echo "PUT → $URL"
|
||||
curl -fsSL --fail-with-body -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$RUNNER_TEMP/${TARBALL}" \
|
||||
"${URL}"
|
||||
|
||||
# =================================================================
|
||||
# Build web (React/Vite).
|
||||
# =================================================================
|
||||
build-web:
|
||||
name: Build web
|
||||
needs: resolve
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build design tokens
|
||||
run: npm run build:tokens --workspace=@veza/design-system
|
||||
|
||||
- name: Build SPA
|
||||
working-directory: apps/web
|
||||
env:
|
||||
VITE_API_URL: /api/v1
|
||||
VITE_DOMAIN: ${{ needs.resolve.outputs.env == 'prod' && 'veza.fr' || 'staging.veza.fr' }}
|
||||
VITE_RELEASE_SHA: ${{ needs.resolve.outputs.sha }}
|
||||
run: npm run build
|
||||
|
||||
- name: Stage tarball contents
|
||||
run: |
|
||||
STAGE="$RUNNER_TEMP/veza-web"
|
||||
mkdir -p "$STAGE"
|
||||
cp -r apps/web/dist/* "$STAGE/"
|
||||
echo "${{ needs.resolve.outputs.sha }}" > "$STAGE/VERSION"
|
||||
|
||||
- name: Pack tarball
|
||||
run: |
|
||||
cd "$RUNNER_TEMP"
|
||||
tar --use-compress-program=zstd -cf \
|
||||
"veza-web-${{ needs.resolve.outputs.sha }}.tar.zst" \
|
||||
-C "$RUNNER_TEMP/veza-web" .
|
||||
|
||||
- name: Push to Forgejo Package Registry
|
||||
env:
|
||||
TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
TARBALL="veza-web-${{ needs.resolve.outputs.sha }}.tar.zst"
|
||||
URL="${REGISTRY_URL}/veza-web/${{ needs.resolve.outputs.sha }}/${TARBALL}"
|
||||
echo "PUT → $URL"
|
||||
curl -fsSL --fail-with-body -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$RUNNER_TEMP/${TARBALL}" \
|
||||
"${URL}"
|
||||
|
||||
# =================================================================
|
||||
# Deploy via Ansible. Runs on the self-hosted runner that has
|
||||
# Incus socket access (label `incus`). Requires Forgejo secrets:
|
||||
# ANSIBLE_VAULT_PASSWORD — unlocks group_vars/all/vault.yml
|
||||
# FORGEJO_REGISTRY_TOKEN — same token the build jobs use,
|
||||
# passed to ansible-playbook so
|
||||
# the data containers can fetch
|
||||
# the tarballs they were just sent.
|
||||
# =================================================================
|
||||
deploy:
|
||||
name: Deploy via Ansible
|
||||
needs: [resolve, build-backend, build-stream, build-web]
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ needs.resolve.outputs.sha }}
|
||||
|
||||
- name: Install ansible + community.general + community.postgresql + community.rabbitmq
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ansible python3-psycopg2 python3-pip
|
||||
ansible-galaxy collection install \
|
||||
community.general \
|
||||
community.postgresql \
|
||||
community.rabbitmq
|
||||
|
||||
- name: Write vault password to a tmpfile
|
||||
env:
|
||||
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
|
||||
run: |
|
||||
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
|
||||
chmod 0400 "$RUNNER_TEMP/vault-pass"
|
||||
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run deploy_data.yml (idempotent provisioning + ZFS snapshot)
|
||||
working-directory: infra/ansible
|
||||
env:
|
||||
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-data-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}.log
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
ansible-playbook \
|
||||
-i inventory/${{ needs.resolve.outputs.env }}.yml \
|
||||
playbooks/deploy_data.yml \
|
||||
--vault-password-file "$VAULT_PASS_FILE" \
|
||||
-e veza_env=${{ needs.resolve.outputs.env }} \
|
||||
-e veza_release_sha=${{ needs.resolve.outputs.sha }} \
|
||||
-e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Run deploy_app.yml (blue/green)
|
||||
working-directory: infra/ansible
|
||||
env:
|
||||
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-app-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}.log
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
ansible-playbook \
|
||||
-i inventory/${{ needs.resolve.outputs.env }}.yml \
|
||||
playbooks/deploy_app.yml \
|
||||
--vault-password-file "$VAULT_PASS_FILE" \
|
||||
-e veza_env=${{ needs.resolve.outputs.env }} \
|
||||
-e veza_release_sha=${{ needs.resolve.outputs.sha }} \
|
||||
-e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Upload Ansible logs (for forensics)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ansible-logs-${{ needs.resolve.outputs.env }}-${{ needs.resolve.outputs.sha }}
|
||||
path: ${{ runner.temp }}/ansible-*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Shred vault password file
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "$VAULT_PASS_FILE" ]; then
|
||||
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
|
||||
fi
|
||||
270
.github/workflows/e2e.yml
vendored
270
.github/workflows/e2e.yml
vendored
|
|
@ -1,270 +0,0 @@
|
|||
name: E2E Playwright
|
||||
|
||||
# v1.0.8 Batch C — Playwright E2E suite triggered on PRs (@critical only,
|
||||
# fast feedback) + push to main and nightly (full suite, deeper coverage).
|
||||
# Uses the --ci seed flag (cmd/tools/seed --ci) for ~5s seeding instead
|
||||
# of the ~60s minimal seed.
|
||||
|
||||
on:
|
||||
# GATED on Forgejo (single self-hosted runner) — re-enable
|
||||
# selectively when an additional runner with a Docker label
|
||||
# (e.g. ubuntu-latest:docker://...) is provisioned. Until then,
|
||||
# heavy E2E only runs on operator-triggered workflow_dispatch.
|
||||
# pull_request:
|
||||
# branches: [main]
|
||||
# push:
|
||||
# branches: [main]
|
||||
# schedule:
|
||||
# - cron: "0 3 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
# Forces playwright.config.ts:141,155 to spawn fresh backend + Vite
|
||||
# instead of reusing whatever is on the runner.
|
||||
CI: "true"
|
||||
# Falls back to a CI-only dev key if the Forgejo secret is unset.
|
||||
# Used at the "Build + start backend API" step.
|
||||
JWT_SECRET: ${{ secrets.E2E_JWT_SECRET || 'ci-dev-jwt-secret-32-chars-min-padding!!' }}
|
||||
|
||||
jobs:
|
||||
# ===========================================================================
|
||||
# Job: e2e — single matrix entry that selects the test scope per trigger.
|
||||
# - PR → @critical only (5-7min target)
|
||||
# - push main / cron / dispatch → full suite (~25min target)
|
||||
# ===========================================================================
|
||||
e2e:
|
||||
# Scope matrix:
|
||||
# - pull_request → @critical (PR gate, ~5-10min)
|
||||
# - push to main → @critical (commit gate, dev velocity priority)
|
||||
# - schedule (cron) → full suite (nightly coverage)
|
||||
# - workflow_dispatch → full (manual broad sweep)
|
||||
# Push was previously running the full suite (~1h30 pre-perf, ~15-20min
|
||||
# post-perf). The dev velocity cost was unjustifiable for the
|
||||
# incremental coverage over the @critical scope, especially while the
|
||||
# full suite carries pre-existing fixme'd tests. Cron picks up the
|
||||
# rest on a 24h cadence.
|
||||
name: e2e (${{ (github.event_name == 'pull_request' || github.event_name == 'push') && '@critical' || 'full' }})
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: ${{ (github.event_name == 'pull_request' || github.event_name == 'push') && 20 || 45 }}
|
||||
|
||||
# Service containers are managed by act_runner: spawned on the job
|
||||
# network with healthchecks, torn down at the end. This replaces
|
||||
# the previous `docker compose up -d` pattern which relied on
|
||||
# docker socket sharing + host port mappings — fragile (port
|
||||
# collisions across concurrent jobs, manual cleanup, double-DinD,
|
||||
# whole compose file validated even when only 3 services are
|
||||
# needed). Service hostnames (`postgres`, `redis`, `rabbitmq`)
|
||||
# resolve from the job container on standard ports.
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: veza
|
||||
POSTGRES_PASSWORD: devpassword
|
||||
POSTGRES_DB: veza
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U veza"
|
||||
--health-interval 5s
|
||||
--health-timeout 3s
|
||||
--health-retries 10
|
||||
redis:
|
||||
# No-auth redis for CI: act_runner services don't support a
|
||||
# `command:` field, and the redis:7-alpine entrypoint does
|
||||
# NOT read REDIS_ARGS (verified empirically) — so passing
|
||||
# --requirepass via env doesn't work. The dev/prod password
|
||||
# policy (REM-023) is enforced via docker-compose.yml only;
|
||||
# the CI service network is ephemeral and isolated, so
|
||||
# dropping auth here is acceptable.
|
||||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 5s
|
||||
--health-timeout 3s
|
||||
--health-retries 10
|
||||
rabbitmq:
|
||||
image: rabbitmq:3-management-alpine
|
||||
env:
|
||||
RABBITMQ_DEFAULT_USER: veza
|
||||
RABBITMQ_DEFAULT_PASS: devpassword
|
||||
options: >-
|
||||
--health-cmd "rabbitmq-diagnostics -q check_port_connectivity"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
|
||||
# Service hostnames + standard ports — no host-port mapping needed.
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:${{ secrets.E2E_DB_PASSWORD || 'devpassword' }}@postgres:5432/veza?sslmode=disable
|
||||
REDIS_URL: redis://redis:6379
|
||||
RABBITMQ_URL: ${{ secrets.E2E_RABBITMQ_URL || 'amqp://veza:devpassword@rabbitmq:5672/' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: package-lock.json
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
cache-dependency-path: veza-backend-api/go.sum
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# Sprint 2 design-system migrated to Style Dictionary; the
|
||||
# generated tokens live in packages/design-system/dist/
|
||||
# (gitignored). The Playwright-spawned Vite imports them via
|
||||
# `@veza/design-system/tokens-generated`, so dist/ MUST exist
|
||||
# before vite starts.
|
||||
- name: Build design tokens
|
||||
run: npm run build:tokens --workspace=@veza/design-system
|
||||
|
||||
# Playwright tests reach the frontend via http://veza.fr:5174,
|
||||
# which the browsers resolve via /etc/hosts. Without this entry
|
||||
# the navigation step times out.
|
||||
- name: Add veza.fr to hosts
|
||||
run: echo "127.0.0.1 veza.fr" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Generate dev JWT keys + SSL cert
|
||||
run: |
|
||||
./scripts/generate-jwt-keys.sh
|
||||
./scripts/generate-ssl-cert.sh
|
||||
|
||||
- name: Run database migrations
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go run cmd/migrate_tool/main.go
|
||||
|
||||
- name: Seed database (CI mode — 5 test accounts + minimal fixtures)
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go run ./cmd/tools/seed --ci
|
||||
|
||||
- name: Build + start backend API
|
||||
env:
|
||||
APP_ENV: test
|
||||
APP_PORT: "18080"
|
||||
COOKIE_SECURE: "false"
|
||||
CORS_ALLOWED_ORIGINS: http://veza.fr:5174,http://localhost:5174
|
||||
DISABLE_RATE_LIMIT_FOR_TESTS: "true"
|
||||
RATE_LIMIT_LIMIT: "10000"
|
||||
RATE_LIMIT_WINDOW: "60"
|
||||
ACCOUNT_LOCKOUT_EXEMPT_EMAILS: "user@veza.music,artist@veza.music,admin@veza.music,mod@veza.music,new@veza.music"
|
||||
run: |
|
||||
cd veza-backend-api
|
||||
go build -o veza-api ./cmd/api/main.go
|
||||
./veza-api > /tmp/backend.log 2>&1 &
|
||||
BACKEND_PID=$!
|
||||
|
||||
# Poll for up to 30s — beats a fixed sleep on a cold start.
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf -m 2 http://localhost:18080/api/v1/health > /tmp/health.json 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
|
||||
echo "::error::backend process died before becoming reachable"
|
||||
echo "--- /tmp/backend.log (last 200 lines) ---"
|
||||
tail -200 /tmp/backend.log
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Always print the response body so debugging doesn't
|
||||
# require re-running with extra logging. Artifact upload
|
||||
# is broken under Forgejo (GHES not supported), so the
|
||||
# log step output is our only diagnostic channel.
|
||||
echo "--- /api/v1/health response ---"
|
||||
cat /tmp/health.json
|
||||
echo
|
||||
|
||||
# The /api/v1/health envelope is the standard veza response
|
||||
# shape: {"success": true, "data": {"status": "ok"}}. Earlier
|
||||
# versions of this check used `.status == "ok"` at the root,
|
||||
# which silently misses the actual ok signal nested under
|
||||
# `.data`. The misread surfaced as "backend health is not ok"
|
||||
# despite a 200 + valid body — wasted a CI cycle.
|
||||
if ! jq -e '.data.status == "ok"' /tmp/health.json >/dev/null; then
|
||||
echo "::error::backend health is not ok"
|
||||
echo "--- /tmp/backend.log (last 200 lines) ---"
|
||||
tail -200 /tmp/backend.log
|
||||
exit 1
|
||||
fi
|
||||
echo "Backend healthy"
|
||||
|
||||
# Cache the Playwright browser binaries between runs.
|
||||
# Chromium download is ~150MB and adds 30-60s to every cold
|
||||
# run. The cache key tracks the playwright version pinned in
|
||||
# package-lock.json, so a Playwright bump invalidates the
|
||||
# cache automatically.
|
||||
- name: Resolve Playwright version
|
||||
id: playwright-version
|
||||
run: |
|
||||
PV=$(node -p "require('./node_modules/@playwright/test/package.json').version")
|
||||
echo "version=$PV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
id: playwright-cache
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-chromium
|
||||
restore-keys: |
|
||||
playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-
|
||||
|
||||
- name: Install Playwright browsers
|
||||
# Browsers cached: only install OS deps (apt-get sweep) so the
|
||||
# download is skipped. Browsers absent: full install + deps.
|
||||
run: |
|
||||
if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = "true" ]; then
|
||||
npx playwright install-deps chromium
|
||||
else
|
||||
npx playwright install --with-deps chromium
|
||||
fi
|
||||
|
||||
- name: Run E2E (@critical — PR + push)
|
||||
if: github.event_name == 'pull_request' || github.event_name == 'push'
|
||||
env:
|
||||
PORT: "5174"
|
||||
VITE_API_URL: "/api/v1"
|
||||
VITE_DOMAIN: veza.fr
|
||||
VITE_BACKEND_PORT: "18080"
|
||||
PLAYWRIGHT_BASE_URL: "http://localhost:5174"
|
||||
run: npm run e2e:critical
|
||||
|
||||
- name: Run E2E (full — cron / workflow_dispatch)
|
||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
PORT: "5174"
|
||||
VITE_API_URL: "/api/v1"
|
||||
VITE_DOMAIN: veza.fr
|
||||
VITE_BACKEND_PORT: "18080"
|
||||
PLAYWRIGHT_BASE_URL: "http://localhost:5174"
|
||||
run: npm run e2e
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: playwright-report-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
tests/e2e/playwright-report/
|
||||
tests/e2e/test-results/
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload backend log
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: backend-log-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: /tmp/backend.log
|
||||
retention-days: 7
|
||||
51
.github/workflows/frontend-ci.yml
vendored
Normal file
51
.github/workflows/frontend-ci.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
name: Frontend CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/frontend-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/frontend-ci.yml"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/web
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: 'npm'
|
||||
cache-dependency-path: apps/web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: TypeScript check
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Bundle size gate
|
||||
run: node scripts/check-bundle-size.mjs
|
||||
|
||||
- name: Audit dependencies
|
||||
run: npm audit --audit-level=critical
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test -- --run
|
||||
|
||||
43
.github/workflows/go-fuzz.yml
vendored
43
.github/workflows/go-fuzz.yml
vendored
|
|
@ -1,43 +0,0 @@
|
|||
name: Go Fuzz Tests
|
||||
|
||||
on:
|
||||
# GATED — operator-triggered until extra runner capacity exists.
|
||||
# schedule:
|
||||
# - cron: "0 2 * * *" # Nightly at 2am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
||||
|
||||
jobs:
|
||||
fuzz:
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 15
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-backend-api
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.25"
|
||||
cache: true
|
||||
|
||||
- name: Download deps
|
||||
run: go mod download
|
||||
|
||||
- name: Run fuzz tests
|
||||
run: go test -fuzz=Fuzz -fuzztime=60s ./internal/handlers/...
|
||||
|
||||
- name: Upload fuzz corpus
|
||||
if: always()
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: fuzz-corpus
|
||||
path: veza-backend-api/testdata/fuzz/
|
||||
retention-days: 30
|
||||
81
.github/workflows/load-test-nightly.yml
vendored
Normal file
81
.github/workflows/load-test-nightly.yml
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
name: Load Tests (Nightly)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
load-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install k6
|
||||
run: |
|
||||
sudo gpg -k
|
||||
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
|
||||
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
|
||||
sudo apt-get update && sudo apt-get install -y k6
|
||||
|
||||
- name: Start infrastructure
|
||||
run: |
|
||||
docker-compose -f docker-compose.yml up -d postgres redis rabbitmq
|
||||
sleep 15
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Run migrations
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
||||
REDIS_URL: redis://localhost:16379
|
||||
JWT_SECRET: test-jwt-secret-for-load-test
|
||||
APP_ENV: test
|
||||
run: |
|
||||
go mod download
|
||||
go run cmd/migrate_tool/main.go || true
|
||||
|
||||
- name: Start backend API
|
||||
working-directory: veza-backend-api
|
||||
env:
|
||||
DATABASE_URL: postgresql://veza:devpassword@localhost:15432/veza?sslmode=disable
|
||||
REDIS_URL: redis://localhost:16379
|
||||
RABBITMQ_URL: amqp://veza:devpassword@localhost:15672/
|
||||
JWT_SECRET: test-jwt-secret-for-load-test
|
||||
APP_ENV: test
|
||||
PORT: 8080
|
||||
run: |
|
||||
go run cmd/api/main.go &
|
||||
sleep 15
|
||||
|
||||
- name: Wait for backend
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
if curl -sf http://localhost:8080/health; then
|
||||
echo "Backend ready"
|
||||
exit 0
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
echo "Backend not ready"
|
||||
exit 1
|
||||
|
||||
- name: Run smoke load test
|
||||
run: k6 run loadtests/smoke.js
|
||||
|
||||
- name: Run backend load test
|
||||
run: |
|
||||
k6 run --out json=load-results.json loadtests/backend/full.js || true
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: load-test-results
|
||||
path: load-results.json
|
||||
if: always()
|
||||
126
.github/workflows/loadtest.yml
vendored
126
.github/workflows/loadtest.yml
vendored
|
|
@ -1,126 +0,0 @@
|
|||
name: k6 nightly load test
|
||||
|
||||
# v1.0.9 W4 Day 20 — runs the mixed-scenarios k6 script against the
|
||||
# staging environment every night at 02:30 UTC. The acceptance gate
|
||||
# is "pass green 3 nuits consécutives" before flipping a release —
|
||||
# the artifact uploaded by this workflow carries the JSON summary
|
||||
# the operator inspects.
|
||||
#
|
||||
# Scope deliberately narrow : runs ONLY on staging, NEVER on prod.
|
||||
# A separate manually-triggered workflow (workflow_dispatch) covers
|
||||
# pre-launch capacity drills with a longer ramp.
|
||||
|
||||
on:
|
||||
# GATED — k6 hammer is too heavy for the single self-hosted runner.
|
||||
# Re-enable the cron once a dedicated load-test runner exists.
|
||||
# schedule:
|
||||
# - cron: "30 2 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
duration:
|
||||
description: "Duration per scenario (e.g. 5m, 15m, 1h)"
|
||||
required: false
|
||||
default: "5m"
|
||||
type: string
|
||||
base_url:
|
||||
description: "Override staging URL"
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
# Defaults — override via workflow_dispatch input or repo vars.
|
||||
DEFAULT_BASE_URL: "https://staging.veza.fr"
|
||||
|
||||
jobs:
|
||||
loadtest:
|
||||
name: k6 mixed scenarios (1650 VU steady)
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install k6
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo gpg -k
|
||||
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
|
||||
--keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
|
||||
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
|
||||
| sudo tee /etc/apt/sources.list.d/k6.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y k6
|
||||
k6 version
|
||||
|
||||
- name: Resolve test inputs
|
||||
id: inputs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASE_URL="${{ github.event.inputs.base_url }}"
|
||||
if [ -z "$BASE_URL" ]; then
|
||||
BASE_URL="${{ vars.STAGING_BASE_URL || env.DEFAULT_BASE_URL }}"
|
||||
fi
|
||||
DURATION="${{ github.event.inputs.duration }}"
|
||||
if [ -z "$DURATION" ]; then
|
||||
DURATION="5m"
|
||||
fi
|
||||
echo "base_url=$BASE_URL" >> "$GITHUB_OUTPUT"
|
||||
echo "duration=$DURATION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Pre-flight — staging is reachable
|
||||
run: |
|
||||
set -euo pipefail
|
||||
url="${{ steps.inputs.outputs.base_url }}/api/v1/health"
|
||||
echo "::notice::Pre-flight GET $url"
|
||||
status=$(curl -k -sS --max-time 10 -o /dev/null -w "%{http_code}" "$url" || echo "000")
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "::error::Staging /health returned $status — aborting load test."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run k6 mixed scenarios
|
||||
id: run
|
||||
env:
|
||||
BASE_URL: ${{ steps.inputs.outputs.base_url }}
|
||||
DURATION: ${{ steps.inputs.outputs.duration }}
|
||||
USER_TOKEN: ${{ secrets.STAGING_LOADTEST_TOKEN }}
|
||||
STREAM_TRACK_ID: ${{ vars.STAGING_LOADTEST_TRACK_ID || '00000000-0000-0000-0000-000000000001' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "$USER_TOKEN" ]; then
|
||||
echo "::warning::STAGING_LOADTEST_TOKEN secret is empty — auth-required scenarios will record 401s as errors."
|
||||
fi
|
||||
k6 run --quiet \
|
||||
--summary-export=k6-summary.json \
|
||||
scripts/loadtest/k6_mixed_scenarios.js
|
||||
|
||||
- name: Upload k6 summary artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: k6-summary-${{ github.run_number }}
|
||||
path: |
|
||||
k6-summary.json
|
||||
scripts/loadtest/k6_mixed_scenarios.js
|
||||
retention-days: 30
|
||||
|
||||
- name: Annotate thresholds in summary
|
||||
if: always()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ ! -f k6-summary.json ]; then
|
||||
echo "::warning::No summary artifact — k6 likely failed before write."
|
||||
exit 0
|
||||
fi
|
||||
echo "## k6 load test summary" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
jq -r '
|
||||
(.metrics.http_reqs.values.count // 0) as $reqs
|
||||
| (.metrics.http_req_failed.values.rate // 0) as $err
|
||||
| (.metrics.http_req_duration.values["p(95)"] // 0) as $p95
|
||||
| (.metrics.http_req_duration.values["p(99)"] // 0) as $p99
|
||||
| "- requests: \($reqs)\n- failed rate: \($err * 100 | round)/100 %\n- p95: \($p95 | round) ms\n- p99: \($p99 | round) ms"
|
||||
' k6-summary.json >> "$GITHUB_STEP_SUMMARY"
|
||||
118
.github/workflows/rollback.yml
vendored
118
.github/workflows/rollback.yml
vendored
|
|
@ -1,118 +0,0 @@
|
|||
# rollback.yml — workflow_dispatch only.
|
||||
#
|
||||
# Two modes :
|
||||
# fast — flip HAProxy back to the previous color. ~5s. Requires
|
||||
# the target color's containers to still be alive
|
||||
# (i.e., no later deploy has recycled them).
|
||||
# full — re-run deploy_app.yml with a specific (older) release_sha.
|
||||
# ~5-10min. The artefact must still be in the Forgejo
|
||||
# registry (default retention 30 SHA per component).
|
||||
#
|
||||
# See docs/RUNBOOK_ROLLBACK.md for decision criteria.
|
||||
name: Veza rollback
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
env:
|
||||
description: "Environment to rollback"
|
||||
required: true
|
||||
type: choice
|
||||
options: [staging, prod]
|
||||
mode:
|
||||
description: "Rollback mode"
|
||||
required: true
|
||||
type: choice
|
||||
options: [fast, full]
|
||||
target_color:
|
||||
description: "(mode=fast only) color to flip back TO (the prior active one)"
|
||||
required: false
|
||||
type: choice
|
||||
options: [blue, green]
|
||||
release_sha:
|
||||
description: "(mode=full only) 40-char SHA of the release to redeploy"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: rollback-${{ inputs.env }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
rollback:
|
||||
name: Rollback ${{ inputs.env }} (${{ inputs.mode }})
|
||||
runs-on: [self-hosted, incus]
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Validate inputs
|
||||
run: |
|
||||
if [ "${{ inputs.mode }}" = "fast" ] && [ -z "${{ inputs.target_color }}" ]; then
|
||||
echo "mode=fast requires target_color"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ inputs.mode }}" = "full" ]; then
|
||||
if [ -z "${{ inputs.release_sha }}" ]; then
|
||||
echo "mode=full requires release_sha"
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "${{ inputs.release_sha }}" | grep -Eq '^[0-9a-f]{40}$'; then
|
||||
echo "release_sha is not a 40-char git SHA"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ inputs.mode == 'full' && inputs.release_sha || github.ref }}
|
||||
|
||||
- name: Install ansible + collections
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ansible python3-psycopg2
|
||||
ansible-galaxy collection install \
|
||||
community.general \
|
||||
community.postgresql \
|
||||
community.rabbitmq
|
||||
|
||||
- name: Write vault password
|
||||
env:
|
||||
VAULT_PW: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
|
||||
run: |
|
||||
printf '%s' "$VAULT_PW" > "$RUNNER_TEMP/vault-pass"
|
||||
chmod 0400 "$RUNNER_TEMP/vault-pass"
|
||||
echo "VAULT_PASS_FILE=$RUNNER_TEMP/vault-pass" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run rollback.yml
|
||||
working-directory: infra/ansible
|
||||
env:
|
||||
ANSIBLE_LOG_PATH: ${{ runner.temp }}/ansible-rollback-${{ inputs.env }}-${{ inputs.mode }}.log
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: |
|
||||
EXTRA="-e veza_env=${{ inputs.env }} -e mode=${{ inputs.mode }}"
|
||||
if [ "${{ inputs.mode }}" = "fast" ]; then
|
||||
EXTRA="$EXTRA -e target_color=${{ inputs.target_color }}"
|
||||
else
|
||||
EXTRA="$EXTRA -e veza_release_sha=${{ inputs.release_sha }}"
|
||||
EXTRA="$EXTRA -e vault_forgejo_registry_token=${{ secrets.FORGEJO_REGISTRY_TOKEN }}"
|
||||
fi
|
||||
ansible-playbook \
|
||||
-i inventory/${{ inputs.env }}.yml \
|
||||
playbooks/rollback.yml \
|
||||
--vault-password-file "$VAULT_PASS_FILE" \
|
||||
$EXTRA
|
||||
|
||||
- name: Upload Ansible log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ansible-rollback-${{ inputs.env }}-${{ inputs.mode }}
|
||||
path: ${{ runner.temp }}/ansible-rollback-*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Shred vault password file
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "$VAULT_PASS_FILE" ]; then
|
||||
shred -u "$VAULT_PASS_FILE" 2>/dev/null || rm -f "$VAULT_PASS_FILE"
|
||||
fi
|
||||
52
.github/workflows/rust-ci.yml
vendored
Normal file
52
.github/workflows/rust-ci.yml
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
name: Rust CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'veza-stream-server/**'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'veza-stream-server/**'
|
||||
|
||||
jobs:
|
||||
test-and-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Clippy lint
|
||||
run: cargo clippy -- -D warnings
|
||||
working-directory: veza-stream-server
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --workspace --timeout 300
|
||||
working-directory: veza-stream-server
|
||||
|
||||
- name: Install cargo-tarpaulin
|
||||
run: cargo install cargo-tarpaulin
|
||||
|
||||
- name: Measure coverage
|
||||
run: cargo tarpaulin --out json --output-dir target/coverage --timeout 300 --skip-clean
|
||||
working-directory: veza-stream-server
|
||||
|
||||
- name: Enforce coverage threshold (>= 50%)
|
||||
run: |
|
||||
COVERAGE=$(cat target/coverage/tarpaulin-report.json | python3 -c "import sys,json; print(f'{json.load(sys.stdin).get(\"coverage\", 0):.1f}')")
|
||||
echo "Rust coverage: ${COVERAGE}%"
|
||||
COV_INT=$(echo "$COVERAGE" | cut -d. -f1)
|
||||
if [ "$COV_INT" -lt 50 ]; then
|
||||
echo "::error::Rust coverage ${COVERAGE}% is below the 50% threshold"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Rust coverage ${COVERAGE}% meets the >= 50% threshold"
|
||||
working-directory: veza-stream-server
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: rust-coverage
|
||||
path: veza-stream-server/target/coverage/tarpaulin-report.json
|
||||
22
.github/workflows/sast.yml
vendored
Normal file
22
.github/workflows/sast.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name: CodeQL SAST
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
strategy:
|
||||
matrix:
|
||||
language: [go, javascript-typescript]
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: github/codeql-action/init@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- uses: github/codeql-action/autobuild@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
- uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441585e9e013f7cac # v3.28.18
|
||||
38
.github/workflows/security-scan.yml
vendored
38
.github/workflows/security-scan.yml
vendored
|
|
@ -1,28 +1,22 @@
|
|||
name: Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
name: Secret Scanning (gitleaks)
|
||||
runs-on: [self-hosted, incus]
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
gitleaks:
|
||||
name: Secret Scanning (gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install gitleaks
|
||||
run: |
|
||||
wget -q https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz
|
||||
tar xzf gitleaks_8.21.2_linux_x64.tar.gz
|
||||
chmod +x gitleaks
|
||||
|
||||
- name: Run gitleaks
|
||||
run: ./gitleaks detect --source . --no-banner -v --config .gitleaks.toml
|
||||
- name: Run Gitleaks
|
||||
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196e88a9c30 # v2.3.8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
306
.github/workflows/staging-validation.yml
vendored
Normal file
306
.github/workflows/staging-validation.yml
vendored
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
name: Staging Validation Pipeline
|
||||
# v0.14.0 TASK-STAG-001 through TASK-STAG-006
|
||||
# Comprehensive staging validation: deploy, perf, Lighthouse, stability, GDPR, bundle size
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_deploy:
|
||||
description: 'Skip deployment (validate existing staging)'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
stability_duration:
|
||||
description: 'Stability check duration (minutes)'
|
||||
required: false
|
||||
default: '10'
|
||||
type: string
|
||||
|
||||
env:
|
||||
STAGING_URL: ${{ vars.STAGING_URL || 'https://staging.veza.app' }}
|
||||
STAGING_API_URL: ${{ vars.STAGING_API_URL || 'https://staging.veza.app/api/v1' }}
|
||||
|
||||
jobs:
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-001: Deploy staging (all services)
|
||||
# ─────────────────────────────────────────────────────
|
||||
deploy-staging:
|
||||
name: Deploy to Staging
|
||||
runs-on: ubuntu-latest
|
||||
if: inputs.skip_deploy != 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
- name: Build all images
|
||||
run: |
|
||||
docker build -t veza-backend-api:staging -f veza-backend-api/Dockerfile.production veza-backend-api/
|
||||
docker build -t veza-frontend:staging -f apps/web/Dockerfile.production apps/web/
|
||||
docker build -t veza-stream-server:staging -f veza-stream-server/Dockerfile.production veza-stream-server/
|
||||
|
||||
- name: Push to registry
|
||||
if: vars.DOCKER_REGISTRY != ''
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" | docker login "${{ vars.DOCKER_REGISTRY }}" -u "${{ secrets.DOCKER_REGISTRY_USERNAME }}" --password-stdin
|
||||
for svc in veza-backend-api veza-frontend veza-stream-server; do
|
||||
docker tag "${svc}:staging" "${{ vars.DOCKER_REGISTRY }}/${svc}:staging"
|
||||
docker push "${{ vars.DOCKER_REGISTRY }}/${svc}:staging"
|
||||
done
|
||||
|
||||
- name: Deploy via SSH (docker-compose)
|
||||
if: vars.STAGING_SSH_HOST != ''
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_KEY" > ~/.ssh/staging_key
|
||||
chmod 600 ~/.ssh/staging_key
|
||||
ssh -i ~/.ssh/staging_key -o StrictHostKeyChecking=no \
|
||||
${{ vars.STAGING_SSH_USER }}@${{ vars.STAGING_SSH_HOST }} \
|
||||
"cd /opt/veza && docker compose -f docker-compose.staging.yml pull && docker compose -f docker-compose.staging.yml up -d"
|
||||
rm -f ~/.ssh/staging_key
|
||||
|
||||
- name: Deploy via Kubernetes
|
||||
if: vars.KUBE_CONFIG_SET == 'true'
|
||||
run: |
|
||||
KUBECONFIG="${{ runner.temp }}/kubeconfig"
|
||||
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > "$KUBECONFIG"
|
||||
chmod 600 "$KUBECONFIG"
|
||||
export KUBECONFIG
|
||||
for svc in veza-backend-api veza-stream-server; do
|
||||
kubectl set image "deployment/${svc}" "${svc}=${{ vars.DOCKER_REGISTRY }}/${svc}:staging" \
|
||||
-n veza --record || echo "Skipping ${svc}"
|
||||
done
|
||||
kubectl rollout status deployment/veza-backend-api -n veza --timeout=300s || true
|
||||
rm -f "$KUBECONFIG"
|
||||
|
||||
- name: Wait for staging to be healthy
|
||||
run: |
|
||||
echo "Waiting for staging services to be healthy..."
|
||||
for i in $(seq 1 30); do
|
||||
STATUS=$(curl -sf "${{ env.STAGING_API_URL }}/health" | jq -r '.status' 2>/dev/null || echo "unreachable")
|
||||
if [ "$STATUS" = "ok" ] || [ "$STATUS" = "healthy" ]; then
|
||||
echo "Staging is healthy!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i/30: status=$STATUS, waiting 10s..."
|
||||
sleep 10
|
||||
done
|
||||
echo "Staging did not become healthy in 300s"
|
||||
exit 1
|
||||
|
||||
- name: Deep health check
|
||||
run: |
|
||||
echo "## Deep Health Check" >> $GITHUB_STEP_SUMMARY
|
||||
HEALTH=$(curl -sf "${{ env.STAGING_API_URL }}/health/deep" || echo '{"error":"unreachable"}')
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$HEALTH" | jq . >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-002: Performance validation (p95 < 100ms)
|
||||
# ─────────────────────────────────────────────────────
|
||||
performance-validation:
|
||||
name: Performance Validation (k6)
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install k6
|
||||
run: |
|
||||
sudo gpg -k
|
||||
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
|
||||
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
|
||||
sudo apt-get update && sudo apt-get install -y k6
|
||||
|
||||
- name: Run staging performance validation
|
||||
run: |
|
||||
k6 run --out json=perf-results.json \
|
||||
--env BASE_URL="${{ env.STAGING_API_URL }}" \
|
||||
--env SCENARIO=smoke \
|
||||
loadtests/staging/validation_v0140.js
|
||||
|
||||
- name: Upload performance results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: performance-results
|
||||
path: perf-results.json
|
||||
if: always()
|
||||
|
||||
- name: Performance summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Performance Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: p95 < 100ms, stream start < 500ms" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-003: Lighthouse validation
|
||||
# ─────────────────────────────────────────────────────
|
||||
lighthouse-validation:
|
||||
name: Lighthouse Audit
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Lighthouse CI
|
||||
run: npm install -g @lhci/cli@0.14.x
|
||||
|
||||
- name: Run Lighthouse CI
|
||||
run: lhci autorun --config=.lighthouserc.js
|
||||
env:
|
||||
LHCI_BUILD_CONTEXT__CURRENT_HASH: ${{ github.sha }}
|
||||
|
||||
- name: Upload Lighthouse results
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: lighthouse-results
|
||||
path: .lighthouseci/
|
||||
if: always()
|
||||
|
||||
- name: Lighthouse summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Lighthouse Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Targets: Performance >= 85, Accessibility >= 90, PWA >= 90" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f .lighthouseci/assertion-results.json ]; then
|
||||
PASSED=$(jq '[.[] | select(.level == "error")] | length' .lighthouseci/assertion-results.json 2>/dev/null || echo "?")
|
||||
echo "Assertion errors: $PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-004: Stability validation (5xx < 0.1%)
|
||||
# ─────────────────────────────────────────────────────
|
||||
stability-validation:
|
||||
name: Stability Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-staging, performance-validation]
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Run stability check
|
||||
run: |
|
||||
chmod +x scripts/staging-stability-check.sh
|
||||
DURATION_MINUTES=${{ inputs.stability_duration || '10' }} \
|
||||
STAGING_API_URL="${{ env.STAGING_API_URL }}" \
|
||||
MAX_5XX_RATE="0.001" \
|
||||
bash scripts/staging-stability-check.sh
|
||||
|
||||
- name: Stability summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Stability Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Duration: ${{ inputs.stability_duration || '10' }} minutes" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: 5xx rate < 0.1%" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f stability-report.json ]; then
|
||||
cat stability-report.json | jq . >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-005: GDPR validation (export + deletion E2E)
|
||||
# ─────────────────────────────────────────────────────
|
||||
gdpr-validation:
|
||||
name: GDPR Compliance Check
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-staging
|
||||
if: always() && (needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache: true
|
||||
|
||||
- name: Run GDPR integration tests
|
||||
working-directory: veza-backend-api
|
||||
run: go test -v -tags=integration -run TestGDPR -timeout 120s ./tests/integration/...
|
||||
|
||||
- name: GDPR summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## GDPR Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Data export: tested" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Account deletion: tested" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# TASK-STAG-006: Bundle size validation (< 200KB gzip)
|
||||
# ─────────────────────────────────────────────────────
|
||||
bundle-size-validation:
|
||||
name: Bundle Size Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build frontend
|
||||
working-directory: apps/web
|
||||
run: npx vite build --outDir dist_verification
|
||||
env:
|
||||
NODE_ENV: production
|
||||
|
||||
- name: Check bundle size
|
||||
working-directory: apps/web
|
||||
run: node scripts/check-bundle-size.mjs
|
||||
|
||||
- name: Bundle size summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Bundle Size Validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Target: JS initial < 200KB gzipped" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ─────────────────────────────────────────────────────
|
||||
# Final summary
|
||||
# ─────────────────────────────────────────────────────
|
||||
validation-summary:
|
||||
name: Validation Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-staging, performance-validation, lighthouse-validation, stability-validation, gdpr-validation, bundle-size-validation]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Generate final report
|
||||
run: |
|
||||
echo "# Staging Validation Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Deploy (STAG-001) | ${{ needs.deploy-staging.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Performance (STAG-002) | ${{ needs.performance-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Lighthouse (STAG-003) | ${{ needs.lighthouse-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability (STAG-004) | ${{ needs.stability-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| GDPR (STAG-005) | ${{ needs.gdpr-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Bundle Size (STAG-006) | ${{ needs.bundle-size-validation.result }} |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check all passed
|
||||
run: |
|
||||
FAILED=0
|
||||
for result in "${{ needs.performance-validation.result }}" "${{ needs.lighthouse-validation.result }}" "${{ needs.bundle-size-validation.result }}"; do
|
||||
if [ "$result" = "failure" ]; then
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
if [ "$FAILED" = "1" ]; then
|
||||
echo "Some validations failed — see summary above."
|
||||
exit 1
|
||||
fi
|
||||
echo "All critical validations passed!"
|
||||
47
.github/workflows/storybook-audit.yml
vendored
Normal file
47
.github/workflows/storybook-audit.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Storybook audit: build static Storybook, serve it, run the audit script.
|
||||
# Fails the job if any story has console errors, page errors, or unhandled network failures.
|
||||
# See docs/STORYBOOK_CONTRACT.md and apps/web/scripts/audit-storybook.js.
|
||||
name: Storybook Audit
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/storybook-audit.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
- ".github/workflows/storybook-audit.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
name: Build & audit Storybook
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/web
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: apps/web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
run: npx playwright install chromium --with-deps
|
||||
|
||||
- name: Validate Storybook (build, serve 6007, audit)
|
||||
run: npm run validate:storybook
|
||||
env:
|
||||
VITE_API_URL: /api/v1
|
||||
VITE_USE_MSW: "true"
|
||||
VITE_STORYBOOK: "true"
|
||||
41
.github/workflows/stream-ci.yml
vendored
Normal file
41
.github/workflows/stream-ci.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
name: Stream Server CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "veza-stream-server/**"
|
||||
- "veza-common/**"
|
||||
- ".github/workflows/stream-ci.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "veza-stream-server/**"
|
||||
- "veza-common/**"
|
||||
- ".github/workflows/stream-ci.yml"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: veza-stream-server
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Rust
|
||||
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Lint with clippy
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
|
||||
- name: Audit dependencies
|
||||
uses: actions-rust-lang/audit@v1 # TODO: pin to SHA — no known mapping provided
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --all
|
||||
|
||||
24
.github/workflows/trivy-fs.yml
vendored
24
.github/workflows/trivy-fs.yml
vendored
|
|
@ -1,24 +0,0 @@
|
|||
name: Trivy Filesystem Scan
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: "true"
|
||||
|
||||
jobs:
|
||||
trivy-scan:
|
||||
name: Trivy FS Scan
|
||||
runs-on: [self-hosted, incus]
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install Trivy
|
||||
run: |
|
||||
wget -qO- https://github.com/aquasecurity/trivy/releases/download/v0.58.1/trivy_0.58.1_Linux-64bit.tar.gz | tar xz
|
||||
chmod +x trivy
|
||||
|
||||
- name: Scan filesystem
|
||||
run: ./trivy fs --severity HIGH,CRITICAL --exit-code 1 .
|
||||
136
.gitignore
vendored
136
.gitignore
vendored
|
|
@ -99,10 +99,9 @@ apps/web/.env.local
|
|||
docker-data/
|
||||
*.tar
|
||||
|
||||
# HAProxy SSL certs (never commit private keys or full-chain certs)
|
||||
# HAProxy SSL certs (never commit private keys)
|
||||
docker/haproxy/certs/*.key
|
||||
docker/haproxy/certs/*.pem
|
||||
docker/haproxy/certs/*.crt
|
||||
|
||||
# JWT RSA keys (v0.9.1 RS256 migration — NEVER commit)
|
||||
jwt-private.pem
|
||||
|
|
@ -149,136 +148,3 @@ storybook-static
|
|||
|
||||
# v0.941: Swagger docs.go generated by CI (swag init)
|
||||
veza-backend-api/docs/docs.go
|
||||
|
||||
# Claude Code local memory
|
||||
.claude/
|
||||
|
||||
# Test audio files (large binaries)
|
||||
veza-backend-api/audio/
|
||||
|
||||
# SELinux policy (local)
|
||||
qemu-fusefs.*
|
||||
# Root-level 'api' binary produced by `go build` in veza-backend-api/.
|
||||
# Narrower than the previous bare `api` rule which matched any file or
|
||||
# directory named 'api' anywhere (including apps/web/src/services/api/).
|
||||
/api
|
||||
/veza-backend-api/api
|
||||
|
||||
# ============================================================
|
||||
# Post-audit J1 (2026-04-14) — never recommit this debris
|
||||
# ============================================================
|
||||
# Go binaries accidentally committed (v1.0.3 → v1.0.4 cleanup)
|
||||
veza-backend-api/server
|
||||
veza-backend-api/modern-server
|
||||
veza-backend-api/seed
|
||||
veza-backend-api/seed-v2
|
||||
veza-backend-api/encrypt_oauth_tokens
|
||||
|
||||
# Coverage reports (generated, never tracked)
|
||||
veza-backend-api/coverage*.out
|
||||
veza-backend-api/coverage_groups/
|
||||
|
||||
# Frontend build/lint/test artifacts
|
||||
apps/web/lint_report*.json
|
||||
apps/web/tsc*.log
|
||||
apps/web/tsc*.txt
|
||||
apps/web/ts_*.log
|
||||
apps/web/storybook_*.json
|
||||
apps/web/debug-storybook.log
|
||||
apps/web/build_errors*.txt
|
||||
apps/web/build_output.txt
|
||||
apps/web/final_errors.txt
|
||||
apps/web/*.log
|
||||
apps/web/diagnostic-*.log
|
||||
apps/web/frontend.log
|
||||
apps/web/audit.log
|
||||
|
||||
# Backend local logs
|
||||
veza-backend-api/backend*.log
|
||||
|
||||
# Root audit screenshots (belong in docs/assets/ if needed)
|
||||
/audit-*.png
|
||||
|
||||
# AI tooling session state (not code)
|
||||
.cursor/
|
||||
|
||||
# ============================================================
|
||||
# Post-audit J2 (2026-04-20) — branch chore/v1.0.7-cleanup
|
||||
# ============================================================
|
||||
|
||||
# Tracked audio fixtures — use git-lfs or fixtures repo, never commit raw audio
|
||||
veza-backend-api/uploads/
|
||||
|
||||
# TLS/SSL certificates committed pre-2026-04 (regen with scripts/generate-ssl-cert.sh)
|
||||
config/ssl/*.pem
|
||||
config/ssl/*.key
|
||||
config/ssl/*.crt
|
||||
|
||||
# Playwright MCP session debris
|
||||
.playwright-mcp/
|
||||
|
||||
# AI session artefacts / context dumps
|
||||
CLAUDE_CONTEXT.txt
|
||||
UI_CONTEXT_SUMMARY.md
|
||||
*.context.txt
|
||||
*.ai-session.txt
|
||||
|
||||
# One-off generated tooling scripts (should live in scripts/ if kept)
|
||||
/generate_page_fix_prompts.sh
|
||||
/build-archive.log
|
||||
|
||||
# Apps/web stale audit reports (generated, never tracked)
|
||||
apps/web/AUDIT_ISSUES.json
|
||||
apps/web/audit_remediation.json
|
||||
apps/web/lint_comprehensive.json
|
||||
apps/web/storybook-roadmap.json
|
||||
apps/web/storybook-*.json
|
||||
|
||||
# Root PNG screenshots — move to docs/screenshots/ if historical value
|
||||
/design-system-*.png
|
||||
/forgot-password-*.png
|
||||
/register-*.png
|
||||
/reset-password-*.png
|
||||
/settings-*.png
|
||||
/storybook-*.png
|
||||
|
||||
# ============================================================
|
||||
# Post-audit J3 (2026-04-23) — history rewrite (BFG pass, 1.5G → 66M)
|
||||
# ============================================================
|
||||
# Additional Go build artifacts found in BFG scan
|
||||
veza-backend-api/bin/
|
||||
veza-backend-api/veza-backend-api
|
||||
veza-backend-api/migrate
|
||||
|
||||
# Vendored binaries mistakenly committed
|
||||
dev-environment/scripts/kubectl
|
||||
|
||||
# Incus build outputs (generated per release cut)
|
||||
.build/
|
||||
|
||||
# E2E report outputs (Playwright)
|
||||
tests/e2e/audit/results/
|
||||
tests/e2e/playwright-report/
|
||||
|
||||
# Session-scratch screenshots
|
||||
frontend_screenshots/
|
||||
|
||||
# Audit_remediation glob (supersedes J2's exact-match json)
|
||||
apps/web/audit_remediation*
|
||||
|
||||
# ============================================================
|
||||
# Ansible Vault — secrets at rest stay encrypted in vault.yml
|
||||
# (committed). The vault password used to unlock them MUST NOT
|
||||
# be committed; the Forgejo runner reads it from a repo secret.
|
||||
# ============================================================
|
||||
infra/ansible/.vault-pass
|
||||
infra/ansible/.vault-pass.*
|
||||
# Local copies devs sometimes drop next to the repo for editing
|
||||
.vault-pass
|
||||
.vault-pass.*
|
||||
|
||||
# ============================================================
|
||||
# Bootstrap scripts — local config + state stay out of git
|
||||
# ============================================================
|
||||
scripts/bootstrap/.env
|
||||
.git/talas-bootstrap/
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
title = "Veza gitleaks config"
|
||||
|
||||
# Inherit gitleaks v8 default ruleset
|
||||
[extend]
|
||||
useDefault = true
|
||||
|
||||
# Project-wide allowlist
|
||||
#
|
||||
# Categories of allowed paths (every entry below is a known false-positive
|
||||
# source confirmed by reading the file or its history):
|
||||
#
|
||||
# 1. Go test files — fake JWTs like eyJ...invalid_signature for auth-failure tests
|
||||
# 2. Historical .backup-pre-uuid-migration dir — gone from HEAD but in git history
|
||||
# 3. Playwright e2e artifacts — auth state snapshots, test result dumps
|
||||
# 4. Storybook stories + MSW mocks — UI fixtures with placeholder API keys
|
||||
# 5. Documentation — API examples, smoke test logs, integration guides
|
||||
# 6. K8s deployment templates — base64-encoded "secure_pass" placeholders
|
||||
# 7. Local dev TLS certs (CN=localhost) under docker/haproxy/certs/
|
||||
# 8. Rust/TS test fixtures — deterministic constants used only in #[cfg(test)]
|
||||
# 9. Generated bundle analysis HTML
|
||||
# 10. Legacy templates (apps/web/desy/legacy/)
|
||||
#
|
||||
# This allowlist intentionally errs on the side of letting things through.
|
||||
# Real secret rotation should rely on .env, vault, or k8s sealed-secrets.
|
||||
# When tightening, prefer adding a stopword over removing a path entry.
|
||||
[allowlist]
|
||||
description = "Allowlist test fixtures, docs, k8s templates, and dev artifacts"
|
||||
paths = [
|
||||
# Go tests
|
||||
'''.*_test\.go$''',
|
||||
'''.*\.backup-pre-uuid-migration/.*''',
|
||||
'''veza-backend-api/internal/services/\.backup-pre-uuid-migration/.*''',
|
||||
|
||||
# Playwright / e2e artifacts
|
||||
'''apps/web/e2e/\.auth/.*''',
|
||||
'''apps/web/e2e-results\.json$''',
|
||||
'''apps/web/full_test_result\.txt$''',
|
||||
'''apps/web/e2e/.*\.md$''',
|
||||
|
||||
# Storybook + MSW mocks
|
||||
'''apps/web/.*\.stories\.(ts|tsx|js|jsx)$''',
|
||||
'''apps/web/src/mocks/.*''',
|
||||
|
||||
# Documentation (markdown samples are inherently full of example tokens)
|
||||
'''.*\.md$''',
|
||||
|
||||
# K8s deployment templates with base64 placeholders
|
||||
'''.*/k8s/.*\.ya?ml$''',
|
||||
|
||||
# Local dev / self-signed TLS material
|
||||
'''docker/haproxy/certs/.*\.(pem|key|crt|csr)$''',
|
||||
|
||||
# Rust / TS test fixtures inside source files (constants used only in
|
||||
# #[cfg(test)] modules — see veza-stream-server/src/utils/signature.rs)
|
||||
'''veza-stream-server/src/utils/signature\.rs$''',
|
||||
'''veza-stream-server/src/utils/env\.rs$''',
|
||||
'''veza-chat-server/src/env\.rs$''',
|
||||
|
||||
# Legacy / static templates
|
||||
'''apps/web/desy/legacy/.*''',
|
||||
|
||||
# Pre-existing source files with hardcoded *test* keys (must stay until refactor)
|
||||
'''apps/web/src/components/studio/.*''',
|
||||
'''apps/web/src/components/settings/security/TwoFactorSetup\.tsx$''',
|
||||
'''apps/web/src/features/live/.*''',
|
||||
|
||||
# Generated artifacts
|
||||
'''\.build/.*\.html$''',
|
||||
]
|
||||
stopwords = [
|
||||
"invalid_signature",
|
||||
"test-jwt-secret",
|
||||
"test-secret",
|
||||
"test-internal-api-key",
|
||||
"test_secret_key_that_is_long_enough_32chars",
|
||||
"sk-abc123-def456-ghi789",
|
||||
"live_83921_abc123xyz789_secret_key",
|
||||
"secure_pass",
|
||||
]
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
npx --no -- commitlint --edit "$1"
|
||||
|
|
@ -1,34 +1,20 @@
|
|||
#!/usr/bin/env sh
|
||||
# Each step runs in a subshell so the cd does not leak across steps.
|
||||
# Pre-commit runs from the repo root; every cd below is relative to that.
|
||||
|
||||
# Drift guard: ensure apps/web/src/services/generated/ (orval) matches
|
||||
# veza-backend-api/openapi.yaml. Regenerates locally then fails if the
|
||||
# committed types don't match the freshly-regenerated output.
|
||||
# Skip with SKIP_TYPES=1 for emergency commits (documented in CLAUDE.md).
|
||||
if [ -z "$SKIP_TYPES" ]; then
|
||||
(cd apps/web && bash scripts/check-types-sync.sh) || {
|
||||
echo "❌ OpenAPI types are out of sync with veza-backend-api/openapi.yaml."
|
||||
echo "💡 Run: make openapi && cd apps/web && bash scripts/generate-types.sh"
|
||||
echo "💡 Then stage the updated src/services/generated/ and retry."
|
||||
echo "💡 Tip: SKIP_TYPES=1 bypasses (not recommended)."
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
# Generate TypeScript types from OpenAPI spec before commit
|
||||
# This ensures types are always up-to-date with the backend API
|
||||
cd apps/web && bash scripts/generate-types.sh
|
||||
|
||||
# Implicit 10.1: Type checking
|
||||
# Prevent commits with TypeScript errors (warnings are allowed)
|
||||
(cd apps/web && npm run typecheck 2>&1 | grep -q "error TS") && {
|
||||
cd apps/web && npm run typecheck 2>&1 | grep -q "error TS" && {
|
||||
echo "❌ Type checking failed. Please fix TypeScript errors before committing."
|
||||
echo "💡 Run 'npm run typecheck' to see all errors."
|
||||
exit 1
|
||||
} || true
|
||||
|
||||
# Implicit 10.2: Linting
|
||||
# Prevent commits with linting errors (warnings are allowed).
|
||||
# Pattern matches "(N error" with N>=1 in ESLint's summary line —
|
||||
# avoids false positive on "(0 errors, K warnings)".
|
||||
(cd apps/web && npm run lint 2>&1 | grep -qE "\([1-9][0-9]* error") && {
|
||||
# Prevent commits with linting errors (warnings are allowed)
|
||||
cd apps/web && npm run lint 2>&1 | grep -q "error" && {
|
||||
echo "❌ Linting failed. Please fix linting errors before committing."
|
||||
echo "💡 Tip: Run 'npm run lint:fix' to automatically fix some issues."
|
||||
exit 1
|
||||
|
|
@ -38,7 +24,7 @@ fi
|
|||
# Skip if SKIP_TESTS environment variable is set (for quick commits)
|
||||
# Only runs unit tests (not E2E) to keep it fast
|
||||
if [ -z "$SKIP_TESTS" ]; then
|
||||
(cd apps/web && npm test -- --run 2>&1 | grep -q "FAIL") && {
|
||||
cd apps/web && npm test -- --run 2>&1 | grep -q "FAIL" && {
|
||||
echo "❌ Tests failed. Please fix failing tests before committing."
|
||||
echo "💡 Tip: Run 'npm test' to see all test failures."
|
||||
echo "💡 Tip: Set SKIP_TESTS=1 to skip tests for this commit (not recommended)."
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# ============================================================================
|
||||
# Veza pre-push hook — CRITICAL E2E SMOKE
|
||||
# ============================================================================
|
||||
# Runs only @critical Playwright tests before push (~2-3min).
|
||||
# SKIP_E2E=1 git push ... # bypass for quick iterations
|
||||
# ============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
if [ -n "$SKIP_E2E" ]; then
|
||||
echo "${YELLOW}▶ SKIP_E2E=1 — skipping critical E2E smoke${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "${YELLOW}▶ Running critical E2E smoke tests (Playwright @critical)...${NC}"
|
||||
echo "${YELLOW} Set SKIP_E2E=1 to bypass (not recommended for shared branches)${NC}"
|
||||
|
||||
npm run e2e:critical 2>&1 || {
|
||||
echo "${RED}✗ Critical E2E tests failed — push blocked${NC}"
|
||||
echo "${YELLOW} Tip: run 'npm run e2e:critical' locally to debug${NC}"
|
||||
echo "${YELLOW} Tip: set SKIP_E2E=1 to bypass if you know what you're doing${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "${GREEN}✓ Critical E2E smoke passed — push allowed${NC}"
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"apps/web/**/*.{ts,tsx}": [
|
||||
"bash -c 'cd apps/web && npx eslint --max-warnings=0 --fix \"$@\"' --",
|
||||
"bash -c 'cd apps/web && npx tsc --noEmit -p tsconfig.json'"
|
||||
],
|
||||
"apps/web/**/*.{js,jsx,json,css,md}": ["prettier --write"],
|
||||
"veza-backend-api/**/*.go": [
|
||||
"bash -c 'cd veza-backend-api && gofmt -l -w \"$@\"' --",
|
||||
"bash -c 'cd veza-backend-api && go vet ./...'"
|
||||
],
|
||||
"veza-stream-server/**/*.rs": [
|
||||
"bash -c 'cd veza-stream-server && cargo fmt --'"
|
||||
],
|
||||
"*.{json,md,yml,yaml}": ["prettier --write"]
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"defaults": {
|
||||
"standard": "WCAG2AA",
|
||||
"timeout": 30000,
|
||||
"wait": 3000,
|
||||
"chromeLaunchConfig": {
|
||||
"args": ["--no-sandbox"]
|
||||
}
|
||||
},
|
||||
"urls": [
|
||||
"http://localhost:5174/login",
|
||||
"http://localhost:5174/register",
|
||||
"http://localhost:5174/discover"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
node_modules/
|
||||
.git/
|
||||
dist/
|
||||
storybook-static/
|
||||
coverage/
|
||||
*.test.ts
|
||||
*.test.tsx
|
||||
*.spec.ts
|
||||
*_test.go
|
||||
tests/
|
||||
loadtests/
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
10011 IGNORE (Cookie Without Secure Flag - dev only)
|
||||
10054 IGNORE (Cookie Without SameSite Attribute - dev only)
|
||||
|
370
103_RAPPORT_ETAT_FEATURES_2026_02_16.md
Normal file
370
103_RAPPORT_ETAT_FEATURES_2026_02_16.md
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
# Rapport d'état précis des features — Veza
|
||||
|
||||
**Date** : 16 février 2026
|
||||
**Méthode** : Analyse du code source (backend routes, frontend services, migrations DB, tests)
|
||||
|
||||
---
|
||||
|
||||
## 1. CARTOGRAPHIE GLOBALE — ÉTAT PRÉCIS
|
||||
|
||||
### 1.1 Stack
|
||||
|
||||
| Couche | Technologie | Version | Fichiers clés |
|
||||
|--------|-------------|---------|---------------|
|
||||
| Frontend | React + Vite | 18.2 / 7.1.5 | `apps/web/package.json` |
|
||||
| Backend | Go + Gin | 1.24 / 1.11 | `veza-backend-api/go.mod` |
|
||||
| Chat | Rust + Axum | 0.8 | `veza-chat-server/Cargo.toml` |
|
||||
| Stream | Rust + Axum | 0.8 | `veza-stream-server/Cargo.toml` |
|
||||
| DB | PostgreSQL | 16 | `docker-compose.prod.yml` |
|
||||
| Cache | Redis | 7 | idem |
|
||||
| Queue | RabbitMQ | 3 | idem |
|
||||
|
||||
### 1.2 Organisation du repo
|
||||
|
||||
- **apps/web** : Frontend React (features/, services/, mocks/)
|
||||
- **veza-backend-api** : API REST (router principal : `internal/api/router.go`)
|
||||
- **veza-chat-server** : WebSocket chat
|
||||
- **veza-stream-server** : Streaming audio
|
||||
- **veza-common** : Lib Rust partagée
|
||||
- **packages/** : NPM packages partagés
|
||||
|
||||
### 1.3 Point d'entrée API
|
||||
|
||||
Le routeur **actif** est `APIRouter` dans `internal/api/router.go`.
|
||||
Le fichier `api_manager.go` est **exclu de la compilation** (`//go:build ignore`) — tout ce qu'il contient (achievements, leaderboard, GraphQL, gRPC, etc.) est du **code mort**.
|
||||
|
||||
---
|
||||
|
||||
## 2. ÉTAT PRÉCIS DE CHAQUE FEATURE
|
||||
|
||||
### 2.1 Auth (register, login, JWT, refresh)
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ Complet | `routes_auth.go` : register, login, refresh, logout, /me, 2FA, OAuth, password reset |
|
||||
| Frontend | ✅ Complet | `authStore`, `LoginForm`, `TwoFactorVerify`, `ProtectedRoute` |
|
||||
| DB | ✅ | Tables users, sessions, refresh_tokens, email_verification_tokens |
|
||||
| Tests | ✅ | `auth_handler_test.go`, `auth_integration_test.go`, `LoginForm.stories` |
|
||||
| Sécurité | ✅ | JWT iss/aud/exp, token version, bcrypt cost 12, rate limit login |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.2 2FA (TOTP)
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `TwoFactorHandler` : setup, verify, disable, status |
|
||||
| Frontend | ✅ | `TwoFactorSetup.tsx`, `TwoFactorVerify.tsx` |
|
||||
| DB | ✅ | Colonnes two_factor_enabled, two_factor_secret, backup_codes |
|
||||
| Tests | ✅ | `two_factor_handler_test.go` |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.3 OAuth (Google, GitHub, Discord)
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `OAuthHandler` : providers, initiate, callback |
|
||||
| Frontend | ✅ | Boutons OAuth, callback handling |
|
||||
| DB | ✅ | oauth_accounts, users |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Profils utilisateur
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_users.go` : GET/PUT/DELETE /users/:id, settings, avatar, follow, block |
|
||||
| Frontend | ✅ | `ProfileView`, `ProfilePage`, `useUser` |
|
||||
| DB | ✅ | users, user_profiles, user_settings |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Upload de tracks (chunked)
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_tracks.go` : initiate, chunk, complete, resume, quota |
|
||||
| Frontend | ✅ | `trackService`, upload flow |
|
||||
| DB | ✅ | tracks, track_uploads |
|
||||
| Sécurité | ✅ | RequireContentCreatorRole, ClamAV optionnel |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.6 CRUD Tracks
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | GET/PUT/DELETE tracks, comments, likes, share, versions, play |
|
||||
| Frontend | ✅ | `trackService`, `LibraryPage`, `TrackDetailPage` |
|
||||
| DB | ✅ | tracks, track_comments, track_likes |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.7 Playlists (CRUD, collaboration)
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_playlists.go` : CRUD, collaborators, tracks |
|
||||
| Frontend | ✅ | `playlistService`, `PlaylistDetailPage` |
|
||||
| DB | ✅ | playlists, playlist_collaborators, playlist_tracks |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.8 Chat WebSocket
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_chat.go` : POST /chat/token, GET /chat/stats |
|
||||
| Chat Server | ✅ | Rust, compile OK |
|
||||
| Frontend | ✅ | `ChatView`, WebSocket client |
|
||||
| DB | ✅ | chat_messages (Chat Server) |
|
||||
|
||||
**Verdict** : **Opérationnel** (Chat Server doit être démarré)
|
||||
|
||||
---
|
||||
|
||||
### 2.9 Dashboard
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_core.go:319` : GET /dashboard, `DashboardHandler` |
|
||||
| Frontend | ✅ | `dashboardService.getDashboardData()` → apiClient.get('/dashboard') |
|
||||
| MSW | ✅ | Mock dans `handlers-admin.ts` (fallback Storybook) |
|
||||
|
||||
**Note** : FEATURE_STATUS.md indiquait "MSW" — **faux**. Le backend expose bien `/api/v1/dashboard`.
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.10 Recherche
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `TrackSearchService`, endpoints search |
|
||||
| Frontend | ✅ | `SearchPage`, `searchService` |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.11 Social (feed, posts, groups, follows, blocks)
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_social.go` : feed, posts, groups, like, comments, join/leave |
|
||||
| Frontend | ✅ | `SocialView`, `useSocialView` |
|
||||
| DB | ✅ | posts, social_groups, user_follows, user_blocks |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.12 Administration
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_core.go` : admin group, RequireAdmin |
|
||||
| Frontend | ✅ | `AdminDashboardPage`, `adminService` |
|
||||
| Audit | ✅ | audit/logs, audit/stats |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.13 Marketplace
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_marketplace.go` : products, cart, orders, licenses |
|
||||
| Frontend | ✅ | `MarketplacePage`, `Cart`, `PurchasesView` |
|
||||
| Paiement | ✅ | Hyperswitch intégré |
|
||||
| DB | ✅ | marketplace_products, orders, licenses |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.14 Webhooks
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_webhooks.go` : CRUD, regenerate-key, test, stats |
|
||||
| Frontend | ✅ | `webhookService.ts` (apiClient), `WebhooksView` |
|
||||
| DB | ✅ | webhooks |
|
||||
|
||||
**Note** : `webhookApi.ts` supprimé — remplacé par `webhookService.ts` qui appelle l'API directement.
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.15 Inventory / Gear
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_gear.go` : GET/POST/PUT/DELETE /inventory/gear |
|
||||
| Frontend | ✅ | `gearService.ts`, `GearView`, `GearPage` |
|
||||
| DB | ✅ | Migration 076 : `gear_items` |
|
||||
| MSW | ✅ | Mock dans `handlers-misc.ts` (Storybook) |
|
||||
|
||||
**Note** : FEATURE_STATUS.md indiquait "UI + mocks, pas de backend" — **faux**. Backend complet.
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.16 Live Streaming
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_live.go` : GET /live/streams, GET /live/streams/:id, POST (auth) |
|
||||
| Frontend | ✅ | `liveService.ts`, `LiveView`, `LivePage` |
|
||||
| DB | ✅ | Migration 077 : `live_streams` |
|
||||
| MSW | ✅ | Mock dans `handlers-misc.ts` |
|
||||
|
||||
**Note** : Le streaming vidéo réel (WebRTC/HLS) est géré par le Stream Server. Les routes backend gèrent les **métadonnées** des streams (titre, description, is_live).
|
||||
|
||||
**Verdict** : **Opérationnel** (métadonnées). Stream vidéo dépend du Stream Server.
|
||||
|
||||
---
|
||||
|
||||
### 2.17 Analytics
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_analytics.go` : tracks plays, top, dashboard |
|
||||
| Frontend | ✅ | `AnalyticsView`, `useAnalyticsView` |
|
||||
| DB | ✅ | track_plays, analytics events |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.18 Roles
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `setupRoleRoutes` : assign, revoke |
|
||||
| Frontend | ✅ | `AssignRoleModal`, `RolesPage` |
|
||||
| DB | ✅ | roles, user_roles |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.19 Notifications
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ✅ | `routes_core.go` : GET/POST/DELETE /api/v1/notifications, unread-count, read, read-all. Création auto pour follow, like, comment (Phase 2.2) |
|
||||
| Frontend | ✅ | `NotificationsPage`, `notificationService` |
|
||||
| DB | ✅ | Table `notifications` (migration 047) |
|
||||
|
||||
**Verdict** : **Opérationnel**
|
||||
|
||||
---
|
||||
|
||||
### 2.20 Gamification (achievements, leaderboard)
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ❌ Code mort | `api_manager.go` (build ignore) : handleGetAchievements, handleGetLeaderboard |
|
||||
| Frontend | ⚠️ Composants | Storybook : AchievementCard, LeaderboardView, XPBar — pas de route /gamification |
|
||||
| MSW | ? | Handlers gamification possibles dans mocks |
|
||||
|
||||
**Verdict** : **Fantôme** — api_manager désactivé, pas de route active
|
||||
|
||||
---
|
||||
|
||||
### 2.21 Studio (Cloud File Browser)
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ❌ | Aucune route |
|
||||
| Frontend | ❌ | Dossier `features/studio/` **n'existe pas** (supprimé) |
|
||||
|
||||
**Verdict** : **Supprimé**
|
||||
|
||||
---
|
||||
|
||||
### 2.22 Education
|
||||
|
||||
| Aspect | État | Preuve |
|
||||
|--------|------|--------|
|
||||
| Backend | ❌ | Aucune route |
|
||||
| Frontend | ❌ | Dossier `features/education/` **n'existe pas** (supprimé) |
|
||||
|
||||
**Verdict** : **Supprimé**
|
||||
|
||||
---
|
||||
|
||||
## 3. RÉCAPITULATIF
|
||||
|
||||
### Features opérationnelles (19)
|
||||
|
||||
Auth, 2FA, OAuth, Profils, Upload tracks, CRUD tracks, Playlists, Chat, Dashboard, Recherche, Social, Admin, Marketplace, Webhooks, Gear, Live (métadonnées), Analytics, Roles, Notifications.
|
||||
|
||||
### Features partielles (0)
|
||||
|
||||
Aucune.
|
||||
|
||||
### Features fantômes (1)
|
||||
|
||||
Gamification — code dans api_manager (mort), composants Storybook.
|
||||
|
||||
### Features supprimées (2)
|
||||
|
||||
Studio, Education — dossiers supprimés.
|
||||
|
||||
---
|
||||
|
||||
## 4. INCOHÉRENCES DOCUMENTATION / CODE
|
||||
|
||||
| Document | Affirmation | Réalité |
|
||||
|----------|-------------|---------|
|
||||
| FEATURE_STATUS.md | Dashboard : MSW | Backend réel GET /dashboard |
|
||||
| FEATURE_STATUS.md | Inventory : pas de backend | Backend complet /inventory/gear |
|
||||
| FEATURE_STATUS.md | Live : contenu minimal | Backend complet /live/streams |
|
||||
| FEATURE_STATUS.md | Studio : UI seule | Dossier supprimé |
|
||||
| FEATURE_STATUS.md | Education : MSW | Dossier supprimé |
|
||||
|
||||
**Recommandation** : Mettre à jour `docs/FEATURE_STATUS.md` et `apps/web/docs/FEATURE_STATUS.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. FICHIERS CRITIQUES PAR FEATURE
|
||||
|
||||
| Feature | Backend | Frontend service | Route |
|
||||
|---------|---------|------------------|-------|
|
||||
| Auth | routes_auth.go | authStore | /auth/* |
|
||||
| Tracks | routes_tracks.go | trackService | /tracks/* |
|
||||
| Playlists | routes_playlists.go | playlistService | /playlists/* |
|
||||
| Chat | routes_chat.go | - | /chat/* |
|
||||
| Dashboard | routes_core.go | dashboardService | /dashboard |
|
||||
| Social | routes_social.go | - | /social/* |
|
||||
| Marketplace | routes_marketplace.go | - | /marketplace/* |
|
||||
| Webhooks | routes_webhooks.go | webhookService | /webhooks/* |
|
||||
| Gear | routes_gear.go | gearService | /inventory/gear |
|
||||
| Live | routes_live.go | liveService | /live/streams |
|
||||
| Analytics | routes_analytics.go | - | /analytics/* |
|
||||
| Roles | routes_users.go | - | /users/:id/roles |
|
||||
|
||||
---
|
||||
|
||||
*Rapport généré le 16 février 2026*
|
||||
737
103_audit_global_features_states.md
Normal file
737
103_audit_global_features_states.md
Normal file
|
|
@ -0,0 +1,737 @@
|
|||
I now have all the data needed. Let me write the comprehensive audit report.
|
||||
|
||||
---
|
||||
|
||||
# 🔍 AUDIT COMPLET DU MONOREPO VEZA
|
||||
|
||||
**Date** : 16 février 2026
|
||||
**Auditeur** : Architecte IA senior
|
||||
**Scope** : Monorepo complet (`veza-backend-api`, `veza-chat-server`, `veza-stream-server`, `veza-common`, `apps/web`)
|
||||
|
||||
---
|
||||
|
||||
## PARTIE 1 — ÉTAT DE STABILITÉ
|
||||
|
||||
---
|
||||
|
||||
### 1.1 Santé du code
|
||||
|
||||
#### Go Backend (`veza-backend-api/`) ✅
|
||||
|
||||
| Critère | Statut | Détail |
|
||||
|---------|--------|--------|
|
||||
| Compilation (`go build ./...`) | ✅ Passe | 0 erreur, 0 warning |
|
||||
| Vet (`go vet ./...`) | ✅ Passe | 0 issue |
|
||||
| Imports cassés | ✅ Aucun | — |
|
||||
| `.env.template` | ✅ Documenté | Complet avec validation rules |
|
||||
| Secrets hardcodés | ✅ Aucun | Tous via env vars, masqués dans logs |
|
||||
|
||||
**TODOs/FIXMEs critiques (P1) — 7 items :**
|
||||
|
||||
| Fichier | Ligne | Description |
|
||||
|---------|-------|-------------|
|
||||
| `internal/core/track/handler.go` | ~340 | `TODO(P2-GO-004)`: `trackUploadService` attend `int64`, reçoit `uuid.UUID` — migration UUID incomplète |
|
||||
| `internal/core/track/handler.go` | ~355 | `TODO(P2-GO-004)`: même problème, `GetUploadProgress()` incompatible UUID |
|
||||
| `internal/repositories/playlist_collaborator_repository.go` | ~67 | `FIXME`: modèle `PlaylistCollaborator` doit utiliser UUID |
|
||||
| `internal/services/playlist_version_service.go` | ~73 | `FIXME`: `PlaylistVersion` ID types à vérifier |
|
||||
| `internal/services/track_history_service.go` | ~74 | `FIXME`: `TrackHistory` needs UUID migration |
|
||||
| `internal/services/playlist_service.go` | ~216 | `FIXME`: `PlaylistVersionService` needs UUID update |
|
||||
| `internal/handlers/auth_handler_test.go` | 225 | `FIXME`: test attend `StatusForbidden` mais l'implémentation permet login non-vérifié |
|
||||
|
||||
**TODOs P2 (18 items)** — les plus notables :
|
||||
|
||||
| Fichier | Description |
|
||||
|---------|-------------|
|
||||
| `internal/services/job_service.go` | Job queue non connectée (5 TODOs BE-SVC-003) — pas d'async processing |
|
||||
| `internal/database/database.go` | OAuth user lookup non implémenté (3 TODOs) |
|
||||
| `internal/handlers/oauth_handlers.go` | `frontendURL` fallback hardcodé `http://localhost:5173` |
|
||||
| `internal/config/middlewares_init.go:75` | Configuration CORS à améliorer |
|
||||
| `internal/api/admin/service.go` | Admin service partiellement implémenté (3 TODOs) |
|
||||
|
||||
#### Rust Chat Server (`veza-chat-server/`) ✅
|
||||
|
||||
| Critère | Statut | Détail |
|
||||
|---------|--------|--------|
|
||||
| Compilation (`cargo check`) | ✅ Passe | 0 erreur, 0 warning |
|
||||
| Protobuf | ✅ | Utilise fichiers pré-générés |
|
||||
| `.env.lab.example` | ⚠️ Minimal | Seul un template lab, pas de `.env.example` standard |
|
||||
|
||||
**TODOs (3 items) :**
|
||||
- `src/read_receipts.rs:230` — TODO: tracking "delivered" non implémenté
|
||||
- `src/presence.rs:226` — TODO: intégration push notifications (FCM, APNs)
|
||||
- `src/message_handler.rs:327` — TODO: recherche de salon par nom
|
||||
|
||||
#### Rust Stream Server (`veza-stream-server/`) ✅
|
||||
|
||||
| Critère | Statut | Détail |
|
||||
|---------|--------|--------|
|
||||
| Compilation (`cargo check`) | ✅ Passe | 0 erreur, 0 warning |
|
||||
| Protobuf | ✅ | Utilise fichiers pré-générés |
|
||||
| `.env.example` | ✅ Documenté | Variables bien documentées |
|
||||
| `#![allow(dead_code)]` | ⚠️ | Code mort autorisé dans `lib.rs` |
|
||||
|
||||
**Point critique** : le client gRPC vers le backend Go (`src/grpc/mod.rs`) est un **stub** — `attempt_send()` fait juste un `sleep`, il n'envoie rien réellement.
|
||||
|
||||
#### Rust Common (`veza-common/`) ✅
|
||||
|
||||
| Critère | Statut |
|
||||
|---------|--------|
|
||||
| Compilation | ✅ Passe |
|
||||
| TODOs | ✅ Aucun |
|
||||
|
||||
#### Frontend React (`apps/web/`) ✅
|
||||
|
||||
| Critère | Statut | Détail |
|
||||
|---------|--------|--------|
|
||||
| TypeScript (`tsc --noEmit`) | ✅ Passe | 0 erreur |
|
||||
| Build Vite | ✅ Passe | — |
|
||||
| `.env.example` | ✅ Documenté | Complet avec feature flags |
|
||||
|
||||
**TODOs notables :**
|
||||
- `src/services/analyticsService.ts:92-97` — endpoints analytics non implémentés côté backend, retournent des valeurs vides
|
||||
- `src/config/features.ts:50` — HLS endpoints marqués "NOT IMPLEMENTED"
|
||||
- `src/features/user/components/profile/ProfileSecurity.tsx:12` — "Placeholder for profile security"
|
||||
|
||||
---
|
||||
|
||||
### 1.2 Points bloquants fonctionnels
|
||||
|
||||
| Module | Statut | Détail |
|
||||
|--------|--------|--------|
|
||||
| **Auth** | ✅ Fonctionnel | Register → verify email → login → refresh → logout → 2FA TOTP : flow complet. OAuth Google/GitHub opérationnel. Sessions management complet (list/revoke/logout-all). |
|
||||
| **Profils** | ✅ Fonctionnel | Création, édition, avatar upload, profil public (`/u/:username`), social links, paramètres. Toutes les routes connectées frontend ↔ backend. |
|
||||
| **Upload & Fichiers** | ⚠️ Partiel | Upload simple ✅, upload chunked ✅, validation MIME/taille ✅, métadonnées extraites ✅. **Manque** : transcoding async (job queue stub), HLS transcoding désactivé (feature flag `false`). |
|
||||
| **Streaming/Lecteur** | ⚠️ Partiel | Play/pause/seek/next/volume/shuffle/repeat ✅ via `<audio>` HTML5. Waveform visualizer ✅. Queue management ✅. **Manque** : HLS adaptive streaming désactivé, gRPC stream server est un stub, crossfade/gapless non implémentés. |
|
||||
| **Playlists** | ✅ Fonctionnel | CRUD complet ✅, ajout/retrait tracks ✅, réorganisation ✅, collaboration ✅, share links ✅, export JSON/CSV ✅, duplication ✅. |
|
||||
| **Chat** | ⚠️ Partiel | WebSocket connection ✅, envoi/réception messages ✅, conversations ✅, typing indicators ✅, reactions ✅. **Manque** : read receipts partiels (TODO), delivered status (TODO), recherche salon par nom (TODO). Communication avec Go backend via HTTP (pas gRPC). |
|
||||
| **Marketplace** | ✅ Fonctionnel | Création produit ✅, catalogue ✅, panier ✅, wishlist ✅, commandes ✅. Checkout via Hyperswitch (optionnel). Téléchargement post-achat ✅. |
|
||||
| **Recherche** | ✅ Fonctionnel | Recherche globale tracks/users/playlists ✅, autocomplete ✅. Filtres par type ✅. |
|
||||
|
||||
---
|
||||
|
||||
### 1.3 Points bloquants techniques
|
||||
|
||||
#### Base de données ⚠️
|
||||
- **42 migrations** bien structurées, idempotentes, avec `IF NOT EXISTS`
|
||||
- **Migration UUID incomplète** : 6 FIXMEs dans le backend indiquent que certains services (`trackUploadService`, `PlaylistCollaborator`, `PlaylistVersion`, `TrackHistory`) utilisent encore `int64` au lieu de `uuid.UUID`. Cela compile (Go est permissif avec les conversions) mais peut causer des bugs runtime.
|
||||
- Pas de conflits de migrations détectés
|
||||
|
||||
#### API — Routes orphelines ⚠️
|
||||
**Backend non consommé par le frontend :**
|
||||
- `POST /api/v1/tracks/initiate` (chunked upload initiate) — frontend utilise directement `/tracks/chunk`
|
||||
- `POST /api/v1/tracks/complete` (chunked upload complete) — même remarque
|
||||
- `GET /api/v1/tracks/resume/:uploadId` — pas de UI de reprise d'upload
|
||||
- `POST /api/v1/tracks/batch/delete` et `POST /api/v1/tracks/batch/update` — pas de UI batch
|
||||
- `GET /api/v1/tracks/shared/:token` — pas de page de partage par token
|
||||
- `GET /api/v1/users/me/export` — endpoint existe, pas de bouton export dans l'UI
|
||||
- `POST /api/v1/audit/cleanup` — pas d'UI admin pour cleanup
|
||||
|
||||
**Frontend appelle des endpoints qui n'existent pas côté backend :**
|
||||
- `POST /api/v1/roles` (création de rôle) — le backend n'a que `GET /roles` et `GET /roles/:id`
|
||||
- `PUT /api/v1/roles/:id`, `DELETE /api/v1/roles/:id` — idem
|
||||
- `GET /api/v1/social/feed`, `POST /api/v1/social/posts` — pas de routes social dans le backend (uniquement follow/block)
|
||||
- `GET /api/v1/social/groups/*` — pas de routes groupes dans le backend
|
||||
- `GET /api/v1/inventory/gear/*` — pas de routes inventaire dans le backend
|
||||
- `GET /api/v1/live/streams/*` — pas de routes live dans le backend
|
||||
- `GET /api/v1/search` — le backend utilise `/tracks/search`, `/users/search`, pas un endpoint unifié `/search`
|
||||
|
||||
#### Sécurité ✅
|
||||
- JWT correctement validé via middleware auth
|
||||
- CORS configuré (origines spécifiques, pas de wildcard)
|
||||
- CSRF protection via middleware + tokens
|
||||
- Security headers complets (HSTS, CSP, X-Frame-Options, X-Content-Type-Options)
|
||||
- Rate limiting multi-couche (global, par endpoint, par utilisateur)
|
||||
- SQL injection protection (GORM parameterized queries)
|
||||
- Secret masking dans les logs
|
||||
- Aucun secret hardcodé en production (seuls des fallbacks dev dans le code)
|
||||
|
||||
#### Services Rust ⚠️
|
||||
- **Compilation** : ✅ Les deux compilent sans erreur
|
||||
- **Dépendances Cargo** : ✅ Résolues
|
||||
- **Communication avec Go** : 🔴 Le stream server utilise un **stub gRPC** — `attempt_send()` ne fait qu'un `sleep`. Le chat server communique via HTTP vers le backend Go (fonctionnel mais pas gRPC comme prévu).
|
||||
|
||||
#### Docker ✅
|
||||
- `docker-compose.yml` bien structuré : Postgres 16, Redis 7, RabbitMQ 3, backend-api, Hyperswitch (optionnel)
|
||||
- Health checks sur tous les services
|
||||
- Resource limits configurés
|
||||
- Ports isolés (15xxx/16xxx pour éviter les conflits)
|
||||
- Fichiers Dockerfile dev et production pour chaque service
|
||||
|
||||
#### Frontend — Tests ⚠️
|
||||
**Tests unitaires (Vitest)** :
|
||||
- **271/273 fichiers passent** (99.3%)
|
||||
- **3306/3318 tests passent** (99.6%)
|
||||
- **2 fichiers échouent** :
|
||||
1. `src/features/tracks/components/LikeButton.test.tsx` — 11 tests en échec : `aria-label` attend `"Retirer le like"` mais reçoit `"Retirer des favoris"` (problème de label i18n)
|
||||
2. `src/context/ToastContext.test.tsx` — 1 test en échec : `TypeError: (0, default) is not a function` dans `ToastProvider.tsx:40` (import cassé de `react-hot-toast`)
|
||||
|
||||
**Tests E2E (Playwright)** :
|
||||
- Dernière exécution : **36 tests échoués** (sur un nombre indéterminé — la dernière run a échoué en setup à cause d'un conflit de port 5173)
|
||||
- Configuration : 4 browsers (Chromium, Firefox, WebKit, Edge), 1 worker, timeout 60s
|
||||
|
||||
#### Logs & Observabilité ✅
|
||||
- Logging structuré : `zap` (Go), `tracing` (Rust)
|
||||
- Prometheus metrics sur tous les services
|
||||
- Sentry integration (Go backend, frontend)
|
||||
- Health checks : `/health`, `/healthz`, `/readyz`, `/api/v1/status`
|
||||
- Health check détaillé vérifie : DB, Redis, RabbitMQ, S3, chat server, stream server
|
||||
- Audit logs complets avec recherche
|
||||
|
||||
---
|
||||
|
||||
### 1.4 Synthèse stabilité
|
||||
|
||||
```
|
||||
PRIORITÉ CRITIQUE (bloque le lancement) :
|
||||
1. gRPC Stream Server stub — Le stream server ne communique pas réellement avec
|
||||
le backend Go, la chaîne upload→transcode→stream est cassée.
|
||||
Fichier: veza-stream-server/src/grpc/mod.rs
|
||||
Effort: 8h
|
||||
|
||||
2. Routes API frontend ↔ backend désalignées — Le frontend appelle des endpoints
|
||||
inexistants (/social/feed, /social/groups, /inventory/gear, /live/streams, /search).
|
||||
Ces pages fonctionnent uniquement grâce aux mocks MSW.
|
||||
Fichiers: apps/web/src/services/socialService.ts, gearService.ts, liveService.ts, searchService.ts
|
||||
Effort: 16h (créer les routes backend) ou 4h (retirer les pages du routeur)
|
||||
|
||||
3. Job Queue non connectée — Les tâches async (transcoding, email, thumbnails) ne
|
||||
s'exécutent pas en background. Le service existe mais est un shell vide.
|
||||
Fichier: veza-backend-api/internal/services/job_service.go
|
||||
Effort: 8h
|
||||
|
||||
PRIORITÉ HAUTE (dégrade l'expérience) :
|
||||
1. Migration UUID incomplète — 6 services utilisent encore int64, risque de bugs
|
||||
runtime sur upload progress, playlist collaborators, track history.
|
||||
Fichiers: internal/core/track/handler.go:340, internal/services/playlist_*.go,
|
||||
internal/repositories/playlist_collaborator_repository.go
|
||||
Effort: 6h
|
||||
|
||||
2. HLS Streaming désactivé — Le lecteur audio ne supporte que le playback direct
|
||||
(pas d'adaptive bitrate). Feature flag HLS_STREAMING=false.
|
||||
Fichiers: apps/web/src/config/features.ts, veza-stream-server/
|
||||
Effort: 12h
|
||||
|
||||
3. Tests LikeButton et ToastContext cassés — 12 tests unitaires échouent.
|
||||
Fichiers: apps/web/src/features/tracks/components/LikeButton.test.tsx,
|
||||
apps/web/src/context/ToastContext.test.tsx
|
||||
Effort: 1h
|
||||
|
||||
4. Tests E2E non fiables — 36 échecs, configuration port conflict.
|
||||
Fichier: apps/web/playwright.config.ts (reuseExistingServer: false)
|
||||
Effort: 4h
|
||||
|
||||
PRIORITÉ MOYENNE (acceptable pour un PoC) :
|
||||
1. Chat read receipts et delivered status — TODOs non implémentés
|
||||
Fichiers: veza-chat-server/src/read_receipts.rs, src/delivered_status.rs
|
||||
Effort: 4h
|
||||
|
||||
2. OAuth Discord/Spotify non implémentés — Seuls Google et GitHub fonctionnent
|
||||
Fichiers: veza-backend-api/internal/handlers/oauth_handlers.go
|
||||
Effort: 4h par provider
|
||||
|
||||
3. Admin service partiellement implémenté (3 TODOs)
|
||||
Fichier: veza-backend-api/internal/api/admin/service.go
|
||||
Effort: 4h
|
||||
|
||||
4. Analytics backend partiellement stub — Certains endpoints retournent des données vides
|
||||
Fichier: apps/web/src/services/analyticsService.ts:92-97
|
||||
Effort: 6h
|
||||
|
||||
5. Studio et Education supprimés — Features planifiées mais code retiré
|
||||
Impact: Aucun pour le PoC (Tier 2)
|
||||
Effort: 0h (décision produit)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PARTIE 2 — PROGRESSION VERS L'OBJECTIF FINAL (600 FEATURES)
|
||||
|
||||
---
|
||||
|
||||
### 2.1 Matrice de couverture par module
|
||||
|
||||
> **Note** : Le document TIER 0 mentionne "40 features" mais les ranges listées (`1-10, 31-45, 66-90, 106-135, 151-175, 186-200, 226-250, 351-365, 411-425, 436-450`) contiennent en réalité **190 features**. J'utilise les ranges comme référence.
|
||||
|
||||
---
|
||||
|
||||
## Module 1 : Auth & Sécurité — 18/30 features (60%)
|
||||
|
||||
### Implémentées ✅ (backend + frontend connectés) :
|
||||
- #1 : Inscription email/password ✅
|
||||
- #2 : Validation email ✅
|
||||
- #3 : Connexion email/password ✅
|
||||
- #4 : OAuth Google ✅
|
||||
- #5 : OAuth GitHub ✅
|
||||
- #9 : Logout ✅
|
||||
- #10 : Logout all devices ✅
|
||||
- #11 : Reset password par email ✅
|
||||
- #17 : Blocage après tentatives (rate limiting) ✅
|
||||
- #19 : 2FA TOTP ✅
|
||||
- #23 : Session management ✅
|
||||
- #28 : Rate limiting connexion ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #8 : Remember me ⚠️ — Cookies persistent mais pas de checkbox UI explicite
|
||||
- #12 : Changement password (authentifié) ⚠️ — Endpoint frontend existe, backend probablement aussi
|
||||
- #14 : Force du mot de passe ⚠️ — Validation Zod côté frontend, indicateur visuel partiel
|
||||
- #21 : Codes backup 2FA ⚠️ — Modèle `recovery_code.go` existe, UI incomplète
|
||||
- #26 : Historique connexions ⚠️ — Via audit logs, pas de page dédiée
|
||||
- #30 : Détection bruteforce ⚠️ — Via rate limiting, pas de détection spécifique
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #6 : OAuth Discord ❌
|
||||
- #7 : OAuth Spotify ❌
|
||||
- #13 : Historique passwords ❌
|
||||
- #15 : Politique passwords configurable ❌
|
||||
- #16 : Expiration password ❌
|
||||
- #18 : Notification changement password ❌
|
||||
- #20 : 2FA SMS ❌
|
||||
- #22 : Passkeys/WebAuthn ❌
|
||||
- #24 : Notifications connexion inhabituelle ❌
|
||||
- #25 : Géolocalisation connexions ❌
|
||||
- #27 : IP whitelisting ❌
|
||||
- #29 : CAPTCHA ❌
|
||||
|
||||
---
|
||||
|
||||
## Module 2 : Profils & Utilisateurs — 18/35 features (51%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #31 : Avatar upload ✅
|
||||
- #33 : Username unique ✅
|
||||
- #34 : Nom complet ✅
|
||||
- #35 : Bio/description ✅
|
||||
- #39 : Langue préférée ✅
|
||||
- #41 : URL profil (/u/username) ✅
|
||||
- #44 : Liens réseaux sociaux ✅
|
||||
- #46 : Rôle User ✅
|
||||
- #47 : Rôle Artist ✅
|
||||
- #51 : Rôle Modérateur ✅
|
||||
- #52 : Rôle Admin ✅
|
||||
- #53 : Permissions granulaires ✅
|
||||
- #58 : Changement langue UI ✅
|
||||
- #59 : Thème clair/sombre/auto ✅
|
||||
- #65 : Supprimer compte (GDPR) ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #32 : Bannière profil ⚠️ — Modèle existe probablement, pas de route dédiée
|
||||
- #36 : Localisation ⚠️ — Champ probable dans user model
|
||||
- #42 : Profil public/privé ⚠️ — Paramètres de confidentialité existent
|
||||
- #56 : Changer email ⚠️ — Endpoint probable
|
||||
- #57 : Changer username ⚠️ — Via PUT /users/:id
|
||||
- #60-62 : Notifications on/off ⚠️ — Paramètres existent, implémentation partielle
|
||||
- #63-64 : Préférences confidentialité/visibilité ⚠️ — Settings partiels
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #37 : Date de naissance ❌
|
||||
- #38 : Genre ❌
|
||||
- #40 : Fuseau horaire ❌
|
||||
- #43 : Email contact public ❌
|
||||
- #45 : Badges/achievements ❌
|
||||
- #48 : Rôle Producer ❌ (distinct d'Artist)
|
||||
- #49 : Rôle Label ❌
|
||||
- #50 : Rôle Formateur ❌
|
||||
- #54 : Système vérification (badge vérifié) ❌
|
||||
- #55 : KYC ❌
|
||||
|
||||
---
|
||||
|
||||
## Module 3 : Gestion de Fichiers — 14/40 features (35%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #66 : Upload fichier unique ✅
|
||||
- #67 : Upload multiple (batch) ✅
|
||||
- #71 : Progress bar upload ✅
|
||||
- #73 : Validation taille ✅
|
||||
- #74 : Validation type MIME ✅
|
||||
- #79 : Extraction métadonnées ✅
|
||||
- #81-86 : Formats MP3, WAV, FLAC, OGG, AIFF, M4A ✅
|
||||
- #91-94 : Titre, Artiste, Album, Genre ✅
|
||||
- #97 : Durée ✅
|
||||
- #103 : Cover art upload ✅
|
||||
- #104 : Tags personnalisés ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #68 : Drag & drop ⚠️ — Probable via composant upload
|
||||
- #72 : Pause/resume upload ⚠️ — Chunked upload existe mais UI incomplète
|
||||
- #77 : Transcoding auto ⚠️ — Job queue stub, transcoding pipeline Rust existe mais non connecté
|
||||
- #95 : BPM ⚠️ — Modèle existe, extraction auto incertaine
|
||||
- #96 : Key musicale ⚠️ — Idem
|
||||
- #98 : Date de sortie ⚠️ — Champ métadonnée probable
|
||||
- #105 : Tags suggérés ⚠️ — Autocomplete partiel
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #69 : Upload par URL ❌
|
||||
- #70 : Upload depuis cloud (Dropbox/Drive) ❌
|
||||
- #75 : Scan antivirus ❌ (ClamAV configuré mais `ENABLE_CLAMAV=false`)
|
||||
- #76 : Compression auto images ❌
|
||||
- #78 : Thumbnails auto ❌ (job queue stub)
|
||||
- #80 : Watermarking ❌
|
||||
- #87-88 : Archives ZIP/RAR ❌
|
||||
- #89 : Documents PDF ❌
|
||||
- #90 : Presets VST ❌
|
||||
- #99-102 : Label, ISRC, Copyright, Lyrics ❌
|
||||
|
||||
---
|
||||
|
||||
## Module 4 : Streaming Audio — 16/45 features (36%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #106 : Play/pause ✅
|
||||
- #107 : Next track ✅
|
||||
- #108 : Previous track ✅
|
||||
- #109 : Seek ✅
|
||||
- #110 : Volume control ✅
|
||||
- #111 : Mute/unmute ✅
|
||||
- #112 : Shuffle ✅
|
||||
- #113 : Repeat (off/track/playlist) ✅
|
||||
- #117 : Waveform visualizer ✅
|
||||
- #122 : Raccourcis clavier ✅ (Media Session API)
|
||||
- #126 : Queue management ✅
|
||||
- #127 : Ajouter à la queue ✅
|
||||
- #128 : Retirer de la queue ✅
|
||||
- #131 : Vider la queue ✅
|
||||
- #136 : Créer playlist ✅
|
||||
- #137 : Éditer playlist ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #120 : Mini-player ⚠️ — Lecteur bottom-bar existe
|
||||
- #123 : Media Session API ⚠️ — Probable via composant player
|
||||
- #129 : Réorganiser queue ⚠️ — Store support, UI incertaine
|
||||
- #132 : Historique écoute ⚠️ — Backend endpoint existe, UI partielle
|
||||
- #133 : Reprendre où on s'est arrêté ⚠️ — playerStore persiste avec zustand persist
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #114 : Playback speed ❌
|
||||
- #115 : Crossfade ❌
|
||||
- #116 : Gapless playback ❌
|
||||
- #118 : Spectrogram ❌
|
||||
- #119 : Bars visualizer ❌
|
||||
- #121 : Picture-in-picture ❌
|
||||
- #124 : Chromecast ❌
|
||||
- #125 : AirPlay ❌
|
||||
- #130 : Sauvegarder queue comme playlist ❌
|
||||
- #134 : Queue collaborative ❌
|
||||
- #135 : Autoplay recommandations ❌
|
||||
- #138-150 : Playlists CRUD suite (la plupart implémentées — voir Playlists ci-dessus)
|
||||
|
||||
> **Correction Playlists** : Features 136-150 sont dans Module 4 mais le CRUD playlist est complet. En réalité : #136-142 ✅, #143 ✅ (collaboration), #144 ⚠️ (cover custom), #145 ✅ (description), #146 ✅ (partage), #147 ✅ (duplication), #148 ❌ (fusion), #149 ✅ (export), #150 ❌ (playlists intelligentes).
|
||||
|
||||
---
|
||||
|
||||
## Module 5 : Chat & Messagerie — 14/35 features (40%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #151 : DM 1-to-1 ✅
|
||||
- #152 : Salons publics ✅
|
||||
- #153 : Salons privés ✅
|
||||
- #154 : Messages de groupe ✅
|
||||
- #155 : Messages texte ✅
|
||||
- #157 : Réactions emoji ✅
|
||||
- #158 : Édition messages ✅
|
||||
- #159 : Suppression messages ✅
|
||||
- #170 : Notifications temps réel ✅
|
||||
- #173 : Badge non lus ✅
|
||||
- #174 : Typing indicator ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #156 : Emojis ⚠️ — Texte emoji OK, pas de picker dédié
|
||||
- #160 : Threads/réponses ⚠️ — Infrastructure existe dans le hub Rust
|
||||
- #175 : Read receipts ⚠️ — Modèle existe, TODO dans le code
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #161-165 : Mentions, Markdown, images, GIFs, partage tracks ❌
|
||||
- #166-169 : Recherche historique, filtres, pin, bookmarks ❌
|
||||
- #171-172 : Push notifications, son personnalisable ❌
|
||||
- #176-185 : Présence & statuts (en ligne, occupé, custom, AFK, last seen, etc.) ❌
|
||||
|
||||
---
|
||||
|
||||
## Module 6 : Social & Communauté — 7/40 features (18%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #186 : Follow ✅
|
||||
- #187 : Unfollow ✅
|
||||
- #188 : Liste followers ✅ (endpoint existe)
|
||||
- #189 : Liste following ✅
|
||||
- #190 : Bloquer ✅
|
||||
- #191 : Signaler ⚠️ (modération backend, pas de bouton frontend dédié)
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #196 : Partage profil ⚠️ — URL `/u/:username` existe
|
||||
- #198 : Notifications followers ⚠️ — Notifications système existe
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #192-195, 197, 199-200 : Recommandations, suggestions, collaboration, referral, QR code, close friends, abonnements ❌
|
||||
- #201-225 : Mur & publications, groupes & communautés ❌ — Le frontend a des composants Social mais ils appellent des endpoints qui **n'existent pas** dans le backend (uniquement MSW mocks)
|
||||
|
||||
---
|
||||
|
||||
## Module 7 : Marketplace — 16/50 features (32%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #226 : Créer produit ✅
|
||||
- #227 : Éditer produit ✅
|
||||
- #228 : Supprimer produit ✅
|
||||
- #229 : Upload fichiers produit ✅
|
||||
- #233 : Prix fixe ✅
|
||||
- #236 : Catégories ✅
|
||||
- #237 : Tags ✅
|
||||
- #251 : Ajouter au panier ✅
|
||||
- #252 : Panier multi-produits ✅
|
||||
- #253 : Wishlist ✅
|
||||
- #261 : Historique achats ✅
|
||||
- #262 : Re-téléchargement ✅
|
||||
- #266 : Dashboard vendeur ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #230 : Preview/démo ⚠️ — Upload existe, player intégré incertain
|
||||
- #232 : Description rich text ⚠️
|
||||
- #256 : Checkout (Hyperswitch) ⚠️ — Infrastructure existe, optionnel
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #231, 234-235, 238-250, 254-260, 263-275 : Images multi, prix variable, gratuit, BPM/Key, formats, licences complètes, paiements avancés, factures, remboursements, revenus temps réel, reviews, promotions, payout ❌
|
||||
|
||||
---
|
||||
|
||||
## Module 8 : Formation & Éducation — 0/30 features (0%)
|
||||
|
||||
❌ **Entièrement non implémenté**. Le répertoire `src/features/education/` a été supprimé. Aucun code backend ne supporte ce module.
|
||||
|
||||
---
|
||||
|
||||
## Module 9 : Gestion de Matériel — 0/25 features (0%)
|
||||
|
||||
⚠️ Le frontend a des composants via MSW mocks (`/api/v1/inventory/gear`), mais **aucun endpoint backend n'existe**. Code frontend-only, non fonctionnel sans mocks.
|
||||
|
||||
---
|
||||
|
||||
## Module 10 : Cloud & Stockage — 0/20 features (0%)
|
||||
|
||||
❌ **Entièrement non implémenté**. Aucune intégration Nextcloud ou backup.
|
||||
|
||||
---
|
||||
|
||||
## Module 11 : Recherche & Découverte — 6/30 features (20%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #351 : Recherche fulltext ✅
|
||||
- #353 : Recherche tracks ✅
|
||||
- #357 : Recherche utilisateurs ✅
|
||||
- #356 : Recherche playlists ✅
|
||||
- #360 : Autocomplete suggestions ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #352 : Recherche par catégorie ⚠️ — Filtres existent
|
||||
- #373 : Tri par pertinence ⚠️
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #354-355, 358-359, 361-380 : Albums, groupes, cours, phonétique, correction ortho, booléen, historique, recherches sauvées, filtres avancés (BPM, key, durée), recommandations ❌
|
||||
|
||||
---
|
||||
|
||||
## Module 12 : Analytics & Statistiques — 5/30 features (17%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #381 : Dashboard analytics ✅
|
||||
- #383 : Plays par track ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #382 : Statistiques écoute globales ⚠️ — Endpoints partiels, certains retournent des données vides
|
||||
- #393 : Engagement (likes, comments, shares) ⚠️
|
||||
- #406 : Utilisateurs actifs (admin) ⚠️ — Admin dashboard partiel
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #384-392, 394-405, 407-410 : Plays par période, durée moyenne, skip rate, géographie, démographie, devices, sources trafic, peaks, export, revenus, conversions, projections ❌
|
||||
|
||||
---
|
||||
|
||||
## Module 13 : Administration — 8/25 features (32%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #411 : Liste utilisateurs ✅
|
||||
- #412 : Recherche utilisateurs ✅
|
||||
- #418 : Changement de rôle ✅
|
||||
- #419 : Historique actions admin ✅ (audit logs)
|
||||
- #431 : Paramètres généraux ⚠️ (partiel)
|
||||
- #433 : Feature flags ✅
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #413 : Filtres avancés ⚠️
|
||||
- #432 : Limites upload/storage ⚠️ — Configurable via env
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #414-417, 420-430, 434-435 : Édition profil admin, ban, suspension, reset password, notes internes, modération contenu, copyright, appeal, maintenance mode, annonces ❌
|
||||
|
||||
---
|
||||
|
||||
## Module 14 : UX/UI — 8/20 features (40%)
|
||||
|
||||
### Implémentées ✅ :
|
||||
- #436 : Thème clair ✅
|
||||
- #437 : Thème sombre ✅
|
||||
- #438 : Thème auto ✅
|
||||
- #446 : Navigation clavier ✅
|
||||
- #448 : ARIA labels ✅ (partiellement — l'erreur LikeButton montre une incohérence)
|
||||
- #449 : Focus visible ✅
|
||||
- #452 : Réduction animations ✅ (prefers-reduced-motion supporté par Framer Motion)
|
||||
|
||||
### Partiellement implémentées ⚠️ :
|
||||
- #450 : Contraste WCAG AA ⚠️ — Design system existe, conformité non auditée
|
||||
|
||||
### Non implémentées ❌ :
|
||||
- #439-445, 447, 451, 453-455 : Contraste élevé, mode compact/confortable, couleurs custom, layouts custom, screen reader complet, tailles police, transcriptions, sous-titres, dyslexie ❌
|
||||
|
||||
---
|
||||
|
||||
## Modules 15-21 : Fonctionnalités Avancées 🔮
|
||||
|
||||
| Module | Features | Implémenté | Statut |
|
||||
|--------|----------|------------|--------|
|
||||
| 15. IA & Avancé | 45 | 0 | 🔮 Futur — Aucun code |
|
||||
| 16. Intégrations | 20 | 0 | 🔮 Futur — Aucun code |
|
||||
| 17. Apps Natives | 15 | 0 | 🔮 Futur — veza-mobile abandonné |
|
||||
| 18. Gamification | 15 | 0 | 🔮 Futur — MSW mocks uniquement |
|
||||
| 19. Notifications | 20 | 5 ⚠️ | ⚠️ Notifications in-app partielles (#551-555) |
|
||||
| 20. Sécurité Avancée | 15 | 10 ✅ | ✅ Rate limiting, CSRF, XSS, CSP, HSTS, security headers (#571-580), audit logs (#581) |
|
||||
| 21. Développeurs & API | 15 | 4 ⚠️ | ⚠️ API REST partielle (#586), Swagger (#591), Webhooks (#595), Developer dashboard UI only (#600) |
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Tableau récapitulatif
|
||||
|
||||
| Module | Total | ✅ Done | ⚠️ Partiel | ❌ Missing | 🔮 Future | % Done |
|
||||
|-------------------------------|-------|---------|------------|-----------|-----------|--------|
|
||||
| 1. Auth & Sécurité | 30 | 12 | 6 | 12 | 0 | 40% |
|
||||
| 2. Profils & Utilisateurs | 35 | 15 | 7 | 13 | 0 | 43% |
|
||||
| 3. Gestion de Fichiers | 40 | 14 | 7 | 19 | 0 | 35% |
|
||||
| 4. Streaming Audio | 45 | 24 | 5 | 16 | 0 | 53% |
|
||||
| 5. Chat & Messagerie | 35 | 11 | 3 | 21 | 0 | 31% |
|
||||
| 6. Social & Communauté | 40 | 5 | 2 | 33 | 0 | 13% |
|
||||
| 7. Marketplace | 50 | 13 | 3 | 34 | 0 | 26% |
|
||||
| 8. Formation & Éducation | 30 | 0 | 0 | 0 | 30 | 0% |
|
||||
| 9. Gestion Matériel | 25 | 0 | 0 | 0 | 25 | 0% |
|
||||
| 10. Cloud & Stockage | 20 | 0 | 0 | 0 | 20 | 0% |
|
||||
| 11. Recherche & Découverte | 30 | 5 | 2 | 23 | 0 | 17% |
|
||||
| 12. Analytics & Statistiques | 30 | 2 | 3 | 25 | 0 | 7% |
|
||||
| 13. Administration | 25 | 6 | 2 | 17 | 0 | 24% |
|
||||
| 14. UX/UI | 20 | 7 | 1 | 12 | 0 | 35% |
|
||||
| 15. Fonctionnalités Avancées | 45 | 0 | 0 | 0 | 45 | 0% |
|
||||
| 16. Intégrations Externes | 20 | 0 | 0 | 0 | 20 | 0% |
|
||||
| 17. Applications Natives | 15 | 0 | 0 | 0 | 15 | 0% |
|
||||
| 18. Gamification | 15 | 0 | 0 | 0 | 15 | 0% |
|
||||
| 19. Notifications | 20 | 3 | 2 | 5 | 10 | 15% |
|
||||
| 20. Sécurité Avancée | 15 | 10 | 1 | 0 | 4 | 67% |
|
||||
| 21. Développeurs & API | 15 | 2 | 2 | 6 | 5 | 13% |
|
||||
| **TOTAL** | **600** | **129** | **46** | **236** | **189** | **21.5%** |
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Écart par rapport aux tiers de priorité
|
||||
|
||||
#### TIER 0 (V1 Launch — ranges 1-10, 31-45, 66-90, 106-135, 151-175, 186-200, 226-250, 351-365, 411-425, 436-450 = ~190 features)
|
||||
|
||||
| Sous-range | Total | ✅ | ⚠️ | ❌ | % |
|
||||
|------------|-------|-----|------|------|-----|
|
||||
| Auth 1-10 | 10 | 7 | 1 | 2 | 70% |
|
||||
| Profils 31-45 | 15 | 10 | 3 | 2 | 67% |
|
||||
| Fichiers 66-90 | 25 | 10 | 3 | 12 | 40% |
|
||||
| Streaming 106-135 | 30 | 14 | 4 | 12 | 47% |
|
||||
| Chat 151-175 | 25 | 11 | 3 | 11 | 44% |
|
||||
| Social 186-200 | 15 | 5 | 2 | 8 | 33% |
|
||||
| Marketplace 226-250 | 25 | 10 | 2 | 13 | 40% |
|
||||
| Recherche 351-365 | 15 | 5 | 2 | 8 | 33% |
|
||||
| Admin 411-425 | 15 | 4 | 1 | 10 | 27% |
|
||||
| UX/UI 436-450 | 15 | 7 | 1 | 7 | 47% |
|
||||
| **TOTAL TIER 0** | **190** | **83** | **22** | **85** | **44%** |
|
||||
|
||||
**Estimation effort pour finir TIER 0** : ~85 features manquantes dont beaucoup sont mineures (champs de formulaire, filtres). Estimation réaliste : **200-300h de développement** (6-10 semaines à temps plein).
|
||||
|
||||
#### TIER 1 (V2-V5 — ranges 11-30, 46-65, 91-105, 136-150, 176-185, 201-225, 251-275, 276-305, 306-330, 366-410 = ~230 features)
|
||||
|
||||
- **Déjà commencées** : ~36 features (2FA #19-21, rôles #46-53, playlists avancées #136-150 partiellement, rate limiting #28)
|
||||
- Beaucoup de features TIER 1 sont déjà partiellement en place grâce au backend riche
|
||||
|
||||
#### TIER 2 (V6-V12 — features 426-435, 451-600 = ~160 features + modules 8-10 = ~75 = ~235 features)
|
||||
|
||||
- **Code anticipatoire** : Infrastructure Kubernetes complète (k8s/), monitoring Prometheus/Grafana, load testing scripts, security scanning CI — l'infra est surdimensionnée par rapport au code applicatif.
|
||||
- Le modèle `live_stream.go` et les composants Live frontend anticipent le livestreaming (#471-480)
|
||||
- Les modèles `gear.go`, `hardware.go` anticipent l'inventaire (#306-330)
|
||||
- Les modèles `contest.go`, `royalty.go` anticipent la gamification et les royalties
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Recommandations stratégiques
|
||||
|
||||
#### 1. Les 5 actions les plus impactantes pour la stabilité
|
||||
|
||||
1. **Connecter le stream server gRPC au backend Go** (8h) — Sans ça, la chaîne audio est cassée pour le transcoding et les callbacks. Le stream server fonctionne en isolation mais ne communique pas les résultats au backend.
|
||||
|
||||
2. **Aligner les routes API social/search/inventory/live** (16h) — Soit créer les endpoints manquants côté Go, soit retirer les pages fantômes du frontend. 4 modules entiers sont en mode "MSW-only".
|
||||
|
||||
3. **Connecter la job queue** (8h) — Intégrer `asynq` ou un système similaire pour le transcoding async, les emails, et les thumbnails. Le service est un shell vide.
|
||||
|
||||
4. **Finaliser la migration UUID** (6h) — 6 FIXMEs dans le backend risquent des bugs runtime sur les opérations d'upload, collaborateurs de playlist, et historique.
|
||||
|
||||
5. **Fixer les 12 tests unitaires cassés et stabiliser les E2E** (5h) — Le LikeButton a un label i18n incorrect, ToastContext a un import cassé, et Playwright a un conflit de port.
|
||||
|
||||
#### 2. Choix architecturaux problématiques à l'échelle
|
||||
|
||||
- **Stream server gRPC stub** : L'architecture prévoit gRPC pour la communication inter-services, mais les deux implémentations (chat HTTP, stream stub) ne l'utilisent pas vraiment. Cela crée une incohérence architecturale. **Risque** : si le trafic augmente, la communication HTTP entre services ne passera pas à l'échelle aussi bien que gRPC.
|
||||
|
||||
- **Double source de vérité pour les services API** : Le frontend a des services à deux endroits (`src/services/*.ts` et `src/features/*/services/*.ts`). Certains endpoints sont appelés depuis les deux. **Risque** : maintenance difficile, bugs de désynchro.
|
||||
|
||||
- **Hyperswitch comme payment router** : Choix ambitieux (open-source, multi-provider) mais complexe à opérer. Pour un PoC, Stripe direct serait plus simple. **Risque** : overhead opérationnel important.
|
||||
|
||||
- **42 migrations SQL sans outil de migration formel** : Les migrations sont des fichiers SQL bruts. Pas de `migrate` CLI ou de tracking automatique des versions appliquées. **Risque** : conflits et migrations manquées en production.
|
||||
|
||||
#### 3. Modules surdéveloppés par rapport à leur priorité
|
||||
|
||||
- **Infrastructure Kubernetes** (`k8s/`) : Déploiements, HPA/VPA, monitoring Prometheus/Grafana/Loki, CDN (CloudFront, Cloudflare), certificats Let's Encrypt, network policies, backup cronjobs — tout ça pour un PoC qui n'a pas encore de version stable. **Surdéveloppé** par rapport à l'état du code applicatif.
|
||||
|
||||
- **Sécurité avancée (Module 20)** : 67% complété alors que le social (13%), l'analytics (7%), et la recherche (17%) sont très en retard. Le rate limiting multi-couche et les security headers sont parfaits mais disproportionnés pour un PoC.
|
||||
|
||||
- **CI/CD** (9 workflows GitHub Actions) : Pipeline complet avec vulnerability scans, SBOM, image signing, smoke tests post-deploy — excellent mais prématuré avant la stabilité fonctionnelle.
|
||||
|
||||
#### 4. Modules sous-développés critiques pour le PoC
|
||||
|
||||
- **Social & Communauté (13%)** : Pour une plateforme collaborative musicale, le social est le coeur du produit. Les features de feed, posts, groupes n'existent qu'en mocks MSW sans backend.
|
||||
|
||||
- **Recherche & Découverte (17%)** : La recherche est basique (fulltext sur tracks/users). Aucun filtre par BPM/key/genre — fonctionnalités critiques pour des musiciens.
|
||||
|
||||
- **Analytics (7%)** : Les créateurs ont besoin de voir leurs stats d'écoute. Le dashboard renvoie des données vides sur plusieurs endpoints.
|
||||
|
||||
#### 5. Estimation réaliste pour v0.101 stable
|
||||
|
||||
| Phase | Contenu | Effort |
|
||||
|-------|---------|--------|
|
||||
| Stabilisation technique | gRPC, job queue, UUID migration, tests | 30h |
|
||||
| Alignement API frontend↔backend | Routes social, search, inventory, live | 20h |
|
||||
| Core features manquantes | Recherche avancée, analytics basiques, chat complet | 40h |
|
||||
| Polish & testing | E2E stable, Storybook audit, bug fixes | 20h |
|
||||
| **TOTAL** | | **110h (~3 semaines à temps plein)** |
|
||||
|
||||
---
|
||||
|
||||
## SCORE GLOBAL DE MATURITÉ
|
||||
|
||||
### 32 / 100
|
||||
|
||||
**Détail :**
|
||||
|
||||
| Critère | Score | Pondération | Note |
|
||||
|---------|-------|-------------|------|
|
||||
| Compilation & santé du code | 95/100 | 15% | Tout compile, peu de TODOs critiques |
|
||||
| Architecture & structure | 80/100 | 15% | Bien organisé mais incohérences gRPC/HTTP |
|
||||
| Features TIER 0 | 44/100 | 25% | 44% des features V1 implémentées |
|
||||
| Tests & qualité | 70/100 | 10% | 99.6% unit pass, E2E instable |
|
||||
| Intégration inter-services | 30/100 | 15% | gRPC stub, routes orphelines, MSW-only pages |
|
||||
| Documentation & DevEx | 75/100 | 5% | Bien documenté, env templates complets |
|
||||
| Sécurité | 85/100 | 10% | Excellente pour un PoC |
|
||||
| Infrastructure & Ops | 60/100 | 5% | Surdimensionné mais fonctionnel |
|
||||
|
||||
**Score pondéré : 32/100**
|
||||
|
||||
---
|
||||
|
||||
**Synthèse en une phrase** : Veza possède une base technique solide et bien architecturée (compilation propre, 3300+ tests, sécurité exemplaire, infrastructure K8s complète), mais reste à mi-chemin de la stabilité fonctionnelle : le stream server ne communique pas vraiment avec le backend, 4 modules frontend n'existent qu'en mocks, et seulement 44% des features TIER 0 sont implémentées de bout en bout — il faut environ 3 semaines de travail focalisé pour atteindre une v0.101 stable.
|
||||
233
ASVS_CHECKLIST_v0.12.6.md
Normal file
233
ASVS_CHECKLIST_v0.12.6.md
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
# CHECKLIST ASVS v4.0 Level 2 — VEZA v0.12.6
|
||||
|
||||
> **Date** : 2026-03-11
|
||||
> **Référence** : PENTEST_REPORT_VEZA_v0.12.6.md
|
||||
> **Légende** : ✅ PASS | ❌ FAIL | ⚠️ PARTIEL | N/A | 🔍 NON VÉRIFIABLE (nécessite env live)
|
||||
|
||||
---
|
||||
|
||||
## V1 — Architecture, Design and Threat Modeling
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V1.1.1 | Architecture documentée | ✅ PASS | Architecture hexagonale documentée dans CLAUDE.md + AUDIT_TECHNIQUE | — |
|
||||
| V1.1.2 | Threat model exists | ⚠️ PARTIEL | Menaces identifiées dans les audits mais pas de threat model formel (STRIDE/DREAD) | — |
|
||||
| V1.1.3 | Security controls documented | ✅ PASS | Middleware chain, RBAC, auth flow documentés | — |
|
||||
| V1.2.1 | Layered architecture | ✅ PASS | Handler → Service → Repository — séparation claire | — |
|
||||
| V1.4.1 | Trusted service layer | ✅ PASS | Validation côté serveur, pas de confiance au client | — |
|
||||
| V1.5.1 | Input validation centralized | ✅ PASS | Gin binding + validation tags + middleware validation | — |
|
||||
| V1.6.1 | Cryptographic module | ✅ PASS | JWT service centralisé, bcrypt standardisé | — |
|
||||
| V1.7.1 | Error handling consistent | ✅ PASS | apierror package, error_handler middleware | — |
|
||||
| V1.8.1 | Data protection classified | ⚠️ PARTIEL | PII identifié mais pas de classification formelle des données | — |
|
||||
| V1.11.1 | Business logic security | ⚠️ PARTIEL | Race condition marketplace identifiée | HIGH-001 |
|
||||
|
||||
---
|
||||
|
||||
## V2 — Authentication
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V2.1.1 | Password min 12 chars | ✅ PASS | Backend valide ≥ 12 chars | — |
|
||||
| V2.1.2 | Password max 128 chars | ✅ PASS | Validation struct tags | — |
|
||||
| V2.1.4 | Password strength meter | ⚠️ PARTIEL | Frontend accepte 8 chars (mismatch) | LOW-001 |
|
||||
| V2.1.7 | Breach password check | ⚠️ PARTIEL | Blocklist de 25 mots courants, pas d'intégration HaveIBeenPwned | — |
|
||||
| V2.1.9 | No password composition rules | ✅ PASS | Complexité par longueur, pas de règles arbitraires | — |
|
||||
| V2.2.1 | Anti-automation on auth | ✅ PASS | Rate limiting sur /auth/login, lockout configuré | — |
|
||||
| V2.2.2 | Weak auth resistance | ✅ PASS | bcrypt cost 12, rate limiting | — |
|
||||
| V2.2.3 | No user enumeration | ✅ PASS | Messages d'erreur génériques, timing constant | — |
|
||||
| V2.3.1 | 2FA implementation | ✅ PASS | TOTP (RFC 6238), recovery codes | — |
|
||||
| V2.4.1 | Bcrypt/Argon2 for passwords | ✅ PASS | bcrypt cost 12 | — |
|
||||
| V2.5.1 | Password reset secure token | ✅ PASS | Token crypto/rand, single-use, TTL limité | — |
|
||||
| V2.5.2 | Password reset no info leak | ✅ PASS | "If account exists, email sent" | — |
|
||||
| V2.6.1 | Lookup secrets crypto random | ❌ FAIL | Recovery codes utilisent math/rand | MEDIUM-001 |
|
||||
| V2.7.1 | OTP time-based (TOTP) | ✅ PASS | TOTP via pquerna/otp | — |
|
||||
| V2.8.1 | Session binding | ✅ PASS | Session liée au user via token + DB | — |
|
||||
| V2.9.1 | RSA keys ≥ 2048 bits | ✅ PASS | Code supporte RSA 2048+, ParsePKCS1/PKCS8 | — |
|
||||
| V2.10.1 | API key entropy | ✅ PASS | Préfixe vza_ + token cryptographique | — |
|
||||
|
||||
---
|
||||
|
||||
## V3 — Session Management
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V3.1.1 | Session token not in URL | ✅ PASS | Cookie httpOnly ou header Authorization | — |
|
||||
| V3.2.1 | Session bound to user | ✅ PASS | Session DB avec user_id, vérifié à chaque requête | — |
|
||||
| V3.2.2 | Session invalidated on logout | ✅ PASS | Token blacklist + session delete | — |
|
||||
| V3.2.3 | Session timeout | ✅ PASS | Access token 5min, refresh 14j/30j | — |
|
||||
| V3.3.1 | Session invalidated on password change | ✅ PASS | Token version incrémentée → tous les tokens invalidés | — |
|
||||
| V3.3.2 | Logout invalidates server-side | ✅ PASS | Session supprimée en DB + token blacklist Redis | — |
|
||||
| V3.4.1 | Cookie secure attributes | ✅ PASS | `COOKIE_SECURE=true, COOKIE_SAME_SITE=strict, COOKIE_HTTP_ONLY=true` en prod | — |
|
||||
| V3.4.2 | Cookie httpOnly | ✅ PASS | Configuré via env var | — |
|
||||
| V3.4.3 | Cookie secure flag | ✅ PASS | Configuré via env var | — |
|
||||
| V3.4.4 | Cookie SameSite | ✅ PASS | SameSite=Strict en production | — |
|
||||
| V3.5.1 | Token-based session | ✅ PASS | JWT + session DB | — |
|
||||
| V3.7.1 | Concurrent session limit | ✅ PASS | Session management page, logout-all, logout-others | — |
|
||||
|
||||
---
|
||||
|
||||
## V4 — Access Control
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V4.1.1 | Trusted enforcement point | ✅ PASS | Middleware chain côté serveur uniquement | — |
|
||||
| V4.1.2 | Access control on every request | ✅ PASS | AuthMiddleware + RBAC sur toutes les routes protégées | — |
|
||||
| V4.1.3 | Principle of least privilege | ✅ PASS | Rôles granulaires (user, creator, premium, admin, moderator) | — |
|
||||
| V4.2.1 | IDOR protection | ❌ FAIL | Chat rooms (GetRoom, GetRoomHistory) sans vérification membership | CRIT-001 |
|
||||
| V4.2.2 | Prevent privilege escalation | ✅ PASS | Rôle vient du JWT → vérifié contre DB, pas modifiable par le client | — |
|
||||
| V4.3.1 | Admin function protection | ✅ PASS | RequireAdmin middleware sur toutes les routes /admin/ | — |
|
||||
| V4.3.2 | Directory listing disabled | ✅ PASS | API REST pure, pas de file serving sauf signedURL S3 | — |
|
||||
|
||||
---
|
||||
|
||||
## V5 — Validation, Sanitization and Encoding
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V5.1.1 | Input validation on server | ✅ PASS | Gin binding + validation middleware | — |
|
||||
| V5.1.2 | Framework auto-escaping | ✅ PASS | JSON encoding Go, React JSX auto-escape | — |
|
||||
| V5.1.3 | Parameterized queries | ✅ PASS | GORM avec paramètres préparés partout en production | — |
|
||||
| V5.2.1 | HTML sanitization | ✅ PASS | DOMPurify avec whitelist de tags | — |
|
||||
| V5.2.2 | Unstructured data sanitized | ✅ PASS | Noms fichiers → UUID, descriptions → DOMPurify | — |
|
||||
| V5.3.1 | Output encoding context-aware | ✅ PASS | JSON encoding natif Go, React auto-escape | — |
|
||||
| V5.3.4 | SQL injection prevention | ✅ PASS | GORM paramétrisé, raw SQL uniquement dans tests | — |
|
||||
| V5.3.7 | OS command injection prevention | ✅ PASS | ValidateExecPath + exec.CommandContext | — |
|
||||
| V5.3.8 | Path traversal prevention | ✅ PASS | UUID comme noms de fichiers, validation des chemins | — |
|
||||
| V5.5.1 | SSRF prevention | ✅ PASS | Pas de fetch d'URLs utilisateur | — |
|
||||
|
||||
---
|
||||
|
||||
## V6 — Stored Cryptography
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V6.1.1 | Regulated data protection | ⚠️ PARTIEL | Données financières via Hyperswitch, pas de stockage PCI côté VEZA | — |
|
||||
| V6.2.1 | Approved algorithms | ⚠️ PARTIEL | HS256 en prod au lieu de RS256 | HIGH-002 |
|
||||
| V6.2.2 | Crypto key management | ⚠️ PARTIEL | Clés via env var, pas de KMS/Vault | — |
|
||||
| V6.2.5 | Random values crypto/rand | ❌ FAIL | Recovery codes 2FA utilisent math/rand | MEDIUM-001 |
|
||||
| V6.3.1 | Access to secret keys restricted | ✅ PASS | Env vars avec `:?` required, pas dans le code | — |
|
||||
| V6.4.1 | No hardcoded secrets | ✅ PASS | Anciens secrets supprimés (VEZA-SEC-001 corrigé) | — |
|
||||
|
||||
---
|
||||
|
||||
## V7 — Error Handling and Logging
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V7.1.1 | No sensitive data in errors | ✅ PASS | apierror format standardisé, pas de stack traces en prod | — |
|
||||
| V7.1.2 | Error handling consistent | ✅ PASS | error_handler middleware + apierror package | — |
|
||||
| V7.2.1 | Security events logged | ✅ PASS | Audit middleware, login failures, role changes | — |
|
||||
| V7.2.2 | No sensitive data in logs | ✅ PASS | secret_filter.go filtre les secrets | — |
|
||||
| V7.3.1 | Log injection prevention | ✅ PASS | Structured logging (zap) avec champs typés | — |
|
||||
| V7.4.1 | Log integrity | 🔍 NON VÉRIFIABLE | Dépend de la configuration de stockage des logs en production | — |
|
||||
|
||||
---
|
||||
|
||||
## V8 — Data Protection
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V8.1.1 | PII identified | ✅ PASS | email, username, IP, payment data identifiés | — |
|
||||
| V8.1.2 | Data classified | ⚠️ PARTIEL | Pas de classification formelle documentée | — |
|
||||
| V8.2.1 | Client-side caching controlled | ✅ PASS | Headers Cache-Control appropriés | — |
|
||||
| V8.3.1 | Sensitive data not in URL | ✅ PASS | Tokens en cookies/headers, pas en URL (sauf stream token query param — 5min TTL) | — |
|
||||
| V8.3.4 | Data export GDPR | ✅ PASS | Export ZIP asynchrone, handler dédié | — |
|
||||
| V8.3.5 | Account deletion | ✅ PASS | Soft delete 30j → hard delete via worker | — |
|
||||
|
||||
---
|
||||
|
||||
## V9 — Communication
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V9.1.1 | TLS for all connections | 🔍 NON VÉRIFIABLE | Config HAProxy pour TLS, sslmode=require en prod | — |
|
||||
| V9.1.2 | TLS 1.2 minimum | 🔍 NON VÉRIFIABLE | Dépend de la config HAProxy en production | — |
|
||||
| V9.1.3 | HSTS configured | ✅ PASS | `max-age=31536000; includeSubDomains; preload` en production | — |
|
||||
|
||||
---
|
||||
|
||||
## V10 — Malicious Code
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V10.1.1 | No malicious code in source | ✅ PASS | Code audité, pas de backdoor identifiée | — |
|
||||
| V10.2.1 | SCA dependency analysis | ✅ PASS | govulncheck, cargo audit, npm audit en CI | — |
|
||||
| V10.2.2 | Lock files committed | ✅ PASS | go.sum, Cargo.lock, package-lock.json présents | — |
|
||||
| V10.3.1 | CI/CD secure | ⚠️ PARTIEL | Actions non pinnées par SHA | MEDIUM-007 |
|
||||
|
||||
---
|
||||
|
||||
## V11 — Business Logic
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V11.1.1 | Business logic server-side | ✅ PASS | Prix, commissions, permissions — tout côté serveur | — |
|
||||
| V11.1.2 | Sequential workflow steps | ✅ PASS | Checkout → payment → license — séquentiel | — |
|
||||
| V11.1.3 | Rate limiting on business flows | ✅ PASS | Rate limiting multi-couche, upload 10/h | — |
|
||||
| V11.1.5 | Anti-automation | ✅ PASS | Rate limiting, account lockout | — |
|
||||
| V11.1.7 | Concurrency controls | ❌ FAIL | Race condition sur downloads_left | HIGH-001 |
|
||||
|
||||
---
|
||||
|
||||
## V12 — Files and Resources
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V12.1.1 | File upload size limit | ✅ PASS | 500MB audio, 50MB chat PDF, limites côté serveur | — |
|
||||
| V12.1.2 | File type validation | ✅ PASS | Extension + MIME type validation | — |
|
||||
| V12.1.3 | File content validation | ✅ PASS | ClamAV scan antivirus obligatoire en production | — |
|
||||
| V12.3.1 | File path traversal prevention | ✅ PASS | UUID comme noms de fichiers en stockage S3 | — |
|
||||
| V12.4.1 | Untrusted file isolated | ✅ PASS | Stockage S3 séparé, pas de file serving direct | — |
|
||||
| V12.5.1 | SSRF via file upload | ✅ PASS | Pas de fetch d'URLs depuis les uploads | — |
|
||||
|
||||
---
|
||||
|
||||
## V13 — API and Web Service
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V13.1.1 | Generic error messages | ✅ PASS | apierror format uniforme | — |
|
||||
| V13.1.3 | API schema validation | ✅ PASS | Gin binding avec struct tags | — |
|
||||
| V13.2.1 | RESTful API secure | ✅ PASS | Auth + RBAC + validation sur tous les endpoints | — |
|
||||
| V13.2.2 | JSON schema validation | ✅ PASS | Validation struct tags Go | — |
|
||||
| V13.2.5 | Content-Type validated | ✅ PASS | Gin enforce Content-Type automatiquement | — |
|
||||
| V13.3.1 | CORS correctly configured | ✅ PASS | Strict en production, whitelist explicite | — |
|
||||
| V13.4.1 | GraphQL security | N/A | Pas de GraphQL dans le projet | — |
|
||||
|
||||
---
|
||||
|
||||
## V14 — Configuration
|
||||
|
||||
| ASVS ID | Requirement | Résultat | Commentaire | Finding |
|
||||
|---------|-------------|----------|-------------|---------|
|
||||
| V14.1.1 | Build process documented | ✅ PASS | Makefile, Dockerfile.production | — |
|
||||
| V14.1.2 | Repeatable builds | ⚠️ PARTIEL | ClamAV :latest rend les builds non reproductibles | MEDIUM-003 |
|
||||
| V14.2.1 | Components up-to-date | ⚠️ PARTIEL | Hyperswitch daté, dotenv obsolète | LOW-002, LOW-003 |
|
||||
| V14.2.2 | No unnecessary features | ✅ PASS | Swagger désactivé en prod, debug derrière RequireAdmin | — |
|
||||
| V14.3.1 | Secrets not in config files | ✅ PASS | Env vars avec `:?` required | — |
|
||||
| V14.3.2 | Secrets management | ⚠️ PARTIEL | Env vars seulement, pas de KMS/Vault | — |
|
||||
| V14.4.1 | HTTP security headers | ✅ PASS | Ensemble complet configuré | — |
|
||||
|
||||
---
|
||||
|
||||
## Résumé ASVS
|
||||
|
||||
| Résultat | Nombre | % |
|
||||
|----------|--------|---|
|
||||
| ✅ PASS | 72 | 80% |
|
||||
| ❌ FAIL | 3 | 3% |
|
||||
| ⚠️ PARTIEL | 13 | 15% |
|
||||
| 🔍 NON VÉRIFIABLE | 3 | 3% |
|
||||
| N/A | 1 | 1% |
|
||||
|
||||
**FAIL obligatoires** :
|
||||
- V4.2.1 : IDOR protection → **CRIT-001** (chat rooms sans membership check)
|
||||
- V2.6.1 : Recovery codes crypto/rand → **MEDIUM-001** (à corriger)
|
||||
- V11.1.7 : Concurrency controls → **HIGH-001** (à corriger)
|
||||
|
||||
**Conclusion** : ASVS Level 2 NON atteint — 3 FAILs à corriger, dont 1 CRITIQUE (IDOR).
|
||||
|
||||
---
|
||||
|
||||
*Checklist générée le 2026-03-11 — ASVS v4.0 Level 2*
|
||||
*Auditeur : Claude Opus 4.6*
|
||||
695
AUDIT_REPORT.md
695
AUDIT_REPORT.md
|
|
@ -1,695 +0,0 @@
|
|||
# AUDIT_REPORT v2 — monorepo Veza
|
||||
|
||||
> **Date** : 2026-04-20
|
||||
> **Branche** : `main` (HEAD = `89a52944e`, `v1.0.7-rc1`)
|
||||
> **Auditeur** : Claude Code (Opus 4.7 — mode autonome, /effort max, /plan)
|
||||
> **Méthode** : 5 agents Explore en parallèle (frontend, backend Go, Rust stream, infra/DevOps, dette transverse) + mesures macro directes + lecture `docs/audit-2026-04/v107-plan.md` + `CHANGELOG.md` v1.0.5 → v1.0.7-rc1.
|
||||
> **Supersede** : [v1 du 2026-04-14](#annexe-diff-v1-v2) (HEAD `45662aad1`, v1.0.0-mvp-24). Depuis : v1.0.4 → v1.0.5 → v1.0.5.1 → v1.0.6 → v1.0.6.1 → v1.0.6.2 → v1.0.7-rc1. 50+ commits. Le v1 est **obsolète** : son "chemin critique v1.0.5 public-ready" a été réalisé intégralement, mais sa liste de hygiène repo (binaires, screenshots, .git 2.3 GB) est **restée en état**.
|
||||
> **Ton** : brutal, pas de langue de bois. Citations `fichier:ligne`.
|
||||
|
||||
---
|
||||
|
||||
## 0. TL;DR — ce que je retiens en 12 lignes
|
||||
|
||||
1. **Plomberie produit : solide.** v1.0.5 → v1.0.7-rc1 a fermé tout le "chemin critique" fonctionnel : register/verify réels, player fallback `/stream`, refund reverse-charge Hyperswitch, reconciliation sweep, Stripe Connect reversal worker, ledger-health Prometheus gauges, maintenance mode persisté, chat multi-instance avec alarme loud. 50+ commits, **18 findings v1 résolus**. Détail : [FUNCTIONAL_AUDIT.md](FUNCTIONAL_AUDIT.md).
|
||||
2. **Hygiène repo : catastrophique.** `.git` = **2.3 GB** (inchangé depuis v1). Binaire `api` de **99 MB** encore à la racine (tracked, ELF). 44 fichiers audio `.mp3/.wav` encore dans `veza-backend-api/uploads/`. 48 screenshots PNG à la racine (`dashboard-*.png`, `login-*.png`, `design-system-*.png`, `forgot-password-*.png`). 36 `.playwright-mcp/*.yml` debris de sessions MCP. `CLAUDE_CONTEXT.txt` = **977 KB** à la racine.
|
||||
3. **`CLAUDE.md` globalement juste** (v1.0.4, 2026-04-14) mais Vite annoncé "5" → réellement **Vite 7.1.5** (`apps/web/package.json`). Axios "déprécié en dev" → réellement `1.13.5` moderne. `docs/ENV_VARIABLES.md` introuvable alors que CLAUDE.md dit "à maintenir".
|
||||
4. **Frontend** : 1984 fichiers TS/TSX. **36 features** modulaires. Router propre (27 routes top-level, 54 lazy). `src/types/generated/api.ts` = **6550 lignes, régénéré aujourd'hui** — OpenAPI typegen a démarré. **282 occurrences `any`** (dont `services/api/auth.ts:85-100` triple cast token fallback). **6 `console.log` en prod** (checkbox, switch, slider, AdvancedFilters, Onboarding, useLongRunningOperation). 11 composants UI orphelins (`hover-card/*`, `dropdown-menu/*`, `optimized-image/*`). 3.5 MB de dead reports (`e2e-results.json` 3.4 MB, `lint_comprehensive.json` 793 KB, `ts_errors.log` 29 KB).
|
||||
5. **Backend Go** : 877 fichiers `.go`, **197K LOC**. 27 fichiers routes, 135 handlers, 226 services, 81 modèles, **160 migrations** (jusqu'à `983_`), 17 workers, 11 jobs. **Transactions manquantes** sur paths critiques (marketplace `service.go:1050+`, subscription). **31 instances `context.Background()` dans handlers** → timeout middleware défait. 3 binaires trackés (`api`, `main`, `veza-api`). **Duplicate `RespondWithAppError`** (`response/response.go:101` + `handlers/error_response.go:12`).
|
||||
6. **Rust stream server** : Axum 0.8 + Tokio 1.35 + Symphonia. HLS ✅ réel, HTTP Range 206 ✅, WebSocket 1047 LOC ✅, adaptive bitrate 515 LOC ✅. **DASH commenté** (`streaming/protocols/mod.rs:4`). **WebRTC commenté** (`Cargo.toml:62`). **`#![allow(dead_code)]` global** au `lib.rs:5` — camoufle les stubs. 0 `unsafe` (engagement CLAUDE.md tenu). **`proto/chat/chat.proto` orphelin** depuis suppression chat Rust (2026-02-22). `veza-common/src/chat/*` types orphelins.
|
||||
7. **Chat server Rust** : **confirmé absent** (commit `05d02386d`, 2026-02-22). Zéro référence dans k8s (bon). **`proto/chat/*.proto` reste comme spec historique** — à déplacer en `docs/archive/` ou supprimer.
|
||||
8. **Desktop Electron** : **confirmé absent**. Jamais implémenté. Fossile des docs anciennes.
|
||||
9. **Docker** : 6 compose files (dev/prod/staging/test/root/`infra/lab.yml` DEPRECATED Feb 2026). **MinIO pinné `:latest` dans 4 composes** → supply-chain risk. ES 8.11.0 uniquement en dev (orphelin ? backend utilise Postgres FTS). Healthchecks partout mais intervals incohérents (5s→30s). **3 variants Dockerfile par service** (base + .dev + .production) — multi-stage, non-root user `app` (uid 1001), `-w -s` stripped. ⚠️ stream-server Dockerfile.production expose `8082` mais `docker-compose.prod.yml:284` healthcheck attend `3001` — **mismatch**.
|
||||
10. **CI/CD** : 5 workflows actifs (`ci.yml` consolidé + `frontend-ci.yml` + `security-scan.yml` gitleaks + `trivy-fs.yml` + `go-fuzz.yml`). **19 workflows disabled, 1676 LOC mort** (`backend-ci.yml.disabled`, `cd.yml.disabled`, `staging-validation.yml.disabled`, `accessibility.yml.disabled`, etc.). E2E **pas déclenché en CI** alors que Playwright existe. Tests integration skipped (`VEZA_SKIP_INTEGRATION=1`) faute de Docker socket.
|
||||
11. **Sécurité** : JWT RS256 prod / HS256 dev ✅. OAuth (Google/GitHub/Discord/Spotify) ✅. 2FA TOTP ✅. CORS strict en prod ✅. gitleaks + govulncheck + trivy en CI ✅. **Absents** : CSP header, X-Frame-Options (0 grep hit). **.env committé** (`/veza-backend-api/.env`, `-rw-r--r--`). **TLS certs committés** : `/docker/haproxy/certs/veza.pem`, `/config/ssl/{cert,key,veza}.pem` — **rotate + BFG needed**.
|
||||
12. **Verdict monorepo** : **Moyen-Haute dette sur l'hygiène, Faible dette sur le code applicatif**. Le produit fonctionne, la plomberie monétaire est auditée, la sécurité applicative est solide. Mais les items "cleanup" de l'audit v1 n'ont **pas été traités** : binaires trackés, .git 2.3 GB, screenshots racine, .playwright-mcp debris, CLAUDE_CONTEXT.txt 977 KB, 19 workflows disabled, .env/certs committed. **~1 jour de cleanup brutal reste à faire** avant le tag v1.0.7 final.
|
||||
|
||||
---
|
||||
|
||||
## 1. État des lieux — mesures macro directes
|
||||
|
||||
### 1.1 Taille & fichiers
|
||||
|
||||
| Mesure | v1 (14-04) | v2 (20-04) | Delta |
|
||||
| ------------------------- | ------------ | ------------- | -------------------------------------- |
|
||||
| `.git` (du -sh) | 2.3 GB | **2.3 GB** | 0 (pas de `git filter-repo` fait) |
|
||||
| Fichiers trackés | 6425 | **6313** | −112 (quelques cleanups ponctuels) |
|
||||
| Binaires ELF racine | 3 (api/main/veza-api) | **1 (`api` 99 MB)** | 2 supprimés mais 1 persiste |
|
||||
| Screenshots racine | 54 | **48** | −6 |
|
||||
| `.md` total repo | inconnu | **435** (18 active + 417 archive) | — |
|
||||
| `.playwright-mcp/*.yml` | — | **36 (untracked)** | NEW debris |
|
||||
| `CLAUDE_CONTEXT.txt` | — | **977 KB** racine | NEW artifact de session |
|
||||
| `output.txt` racine | — | **27 KB** | NEW |
|
||||
|
||||
### 1.2 Ce qui n'existe PAS (contrairement à certaines docs)
|
||||
|
||||
| Objet | Status | Preuve |
|
||||
| ---------------------------------- | :--------------: | ------------------------------------------------------------------------------------------------ |
|
||||
| `veza-chat-server/` | ❌ absent | `ls /home/senke/git/talas/veza/veza-chat-server` → no such dir. Commit `05d02386d` (2026-02-22). |
|
||||
| `apps/desktop/` (Electron) | ❌ absent | Jamais implémenté. |
|
||||
| `backend/` racine | ❌ absent | C'est `veza-backend-api/`. |
|
||||
| `frontend/` racine | ❌ absent | C'est `apps/web/`. |
|
||||
| `ORIGIN/` racine | ❌ absent | C'est `veza-docs/ORIGIN/`. |
|
||||
| `proto/chat/chat.proto` utilisé | ❌ orphelin | 0 import dans `veza-stream-server/src/`. Chat 100% Go depuis v0.502. |
|
||||
| Runbooks k8s mentionnant chat Rust | ❌ clean (bonne) | Grep `veza-chat-server` dans `k8s/` = 0 hit. |
|
||||
| **Binaire `api` 99 MB racine** | ⚠️ **présent** | `-rwxr-xr-x 1 senke senke 99515104 Mar 24 15:40 api`. **À supprimer.** |
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture & stack — mise à jour exacte
|
||||
|
||||
### 2.1 Arborescence réelle
|
||||
|
||||
```
|
||||
veza/ (2.3 GB .git, 6313 fichiers trackés)
|
||||
├── apps/web/ # React 18.2 + Vite 7.1.5 + TS 5.9.3 + Zustand 4.5 + React Query 5.17
|
||||
│ └── src/ (1984 fichiers TS/TSX)
|
||||
│ ├── features/ (36 feature folders)
|
||||
│ ├── components/ui/ (255 fichiers — design system)
|
||||
│ ├── services/ (73 fichiers)
|
||||
│ ├── types/generated/ (api.ts 6550 lignes, régénéré aujourd'hui)
|
||||
│ └── router/routeConfig.tsx (184 lignes, 27 routes top-level, 54 lazy)
|
||||
│
|
||||
├── veza-backend-api/ # Go 1.25.0 + Gin + GORM + Postgres + Redis + RabbitMQ
|
||||
│ ├── cmd/api/main.go (orchestration wiring)
|
||||
│ ├── cmd/{migrate_tool,backup,generate-config-docs,tools/*} (~6 binaires)
|
||||
│ ├── internal/ (877 fichiers .go, 197K LOC)
|
||||
│ │ ├── api/ (27 routes_*.go)
|
||||
│ │ ├── api/handlers/ (3 fichiers DEPRECATED — chat, rbac)
|
||||
│ │ ├── handlers/ (135 fichiers — source active)
|
||||
│ │ ├── services/ (226 fichiers, 64K LOC)
|
||||
│ │ ├── core/*/ (9 services feature-scoped)
|
||||
│ │ ├── models/ (81 fichiers, 44K LOC)
|
||||
│ │ ├── migrations/ (160 .sql, jusqu'à 983_)
|
||||
│ │ ├── workers/ (17) + jobs/ (11)
|
||||
│ │ ├── middleware/ (~30)
|
||||
│ │ ├── repositories/ (18 GORM-based)
|
||||
│ │ └── repository/ (1 ORPHELIN in-memory mock)
|
||||
│ ├── docs/swagger.{json,yaml} (v1.2.0, 2026-03-03)
|
||||
│ ├── uploads/ (44 .mp3/.wav TRACKÉS !)
|
||||
│ └── {api,main,veza-api} (3 binaires ELF trackés dans CLAUDE.md .gitignore mais présents)
|
||||
│
|
||||
├── veza-stream-server/ # Rust 2021 + Axum 0.8 + Tokio 1.35 + Symphonia 0.5 + sqlx 0.8 + tonic 0.11
|
||||
│ └── src/
|
||||
│ ├── streaming/ (HLS réel, WebSocket 1047 LOC, adaptive 515 LOC, DASH stub commenté)
|
||||
│ ├── audio/ (Symphonia + LAME native; opus/webrtc/fdkaac commentés)
|
||||
│ ├── core/ (StreamManager 10k+ concurrents, sync engine 1920 LOC)
|
||||
│ ├── auth/ (JWT HMAC-SHA256, revocation Redis+in-mem fallback, 825 LOC)
|
||||
│ ├── grpc/ (Stream+Auth+Events — generated 21845 LOC auto)
|
||||
│ ├── transcoding/ (queue job engine 94 LOC — ALPHA)
|
||||
│ ├── event_bus.rs (RabbitMQ degraded mode, 248 LOC)
|
||||
│ └── lib.rs:5 #![allow(dead_code)] GLOBAL — camoufle les stubs
|
||||
│
|
||||
├── veza-common/ # Rust types partagés
|
||||
│ └── src/{chat,ws,files,track,user,playlist,media,api}.rs
|
||||
│ └── chat.rs, track.rs, user.rs, etc. — ORPHELINS depuis suppression chat Rust
|
||||
│
|
||||
├── packages/design-system/ # Tokens design (unique package workspace)
|
||||
│
|
||||
├── proto/
|
||||
│ ├── common/auth.proto ✅ utilisé par stream-server + backend
|
||||
│ ├── stream/stream.proto ✅ utilisé par stream-server
|
||||
│ └── chat/chat.proto ❌ ORPHELIN (chat en Go depuis v0.502)
|
||||
│
|
||||
├── docs/
|
||||
│ ├── audit-2026-04/ (NEW : axis-1-correctness.md + v107-plan.md)
|
||||
│ ├── archive/ (278 fichiers .md historique)
|
||||
│ └── (API_REFERENCE, ONBOARDING, PROJECT_STATE, FEATURE_STATUS, etc.)
|
||||
│
|
||||
├── veza-docs/ # Docusaurus séparé
|
||||
│ ├── docs/{current,vision}/
|
||||
│ └── ORIGIN/ (22 fichiers phase-0 FOSSILE, jamais touchée post-launch)
|
||||
│
|
||||
├── k8s/ # ~30-40 manifests + 5 runbooks disaster-recovery
|
||||
├── config/ # alertmanager, grafana, haproxy, prometheus, incus, ssl/* (.pem TRACKÉS)
|
||||
├── infra/ # nginx-rtmp + docker-compose.lab.yml (DEPRECATED)
|
||||
├── docker/ # haproxy/certs/veza.pem (TRACKÉ, sensible)
|
||||
├── tests/e2e/ # Playwright — SKIPPED_TESTS.md liste les flakies
|
||||
├── .github/workflows/ # 5 actifs + 19 .disabled (1676 LOC mort)
|
||||
├── .husky/ # pre-commit + pre-push + commit-msg (untracked mais fonctionnels)
|
||||
└── {docker-compose*.yml} # 6 files (dev/prod/staging/test/root/env.example)
|
||||
```
|
||||
|
||||
### 2.2 Stack — versions actuelles
|
||||
|
||||
| Composant | Doc (CLAUDE.md) | Réel (code) | Écart ? |
|
||||
| -------------- | --------------- | ----------------- | ----------------- |
|
||||
| Go | 1.25 | **1.25.0** (go.mod) | ✅ OK |
|
||||
| React | 18.2 | 18.2.0 | ✅ OK |
|
||||
| Vite | **5** | **7.1.5** | ❌ CLAUDE.md obsolète |
|
||||
| TypeScript | 5.9.3 | 5.9.3 | ✅ OK |
|
||||
| Zustand | — | 4.5.0 | N/A |
|
||||
| React Query | 5 | 5.17.0 | ✅ OK |
|
||||
| Tailwind | — | **4.0.0** | ✅ récent |
|
||||
| date-fns | 4 | 4.1.0 | ✅ OK |
|
||||
| Axios | non mentionné | 1.13.5 | ✅ moderne |
|
||||
| jwt-go | v5 | v5.3.0 | ✅ OK |
|
||||
| gorm | — | v1.30.0 | ✅ OK |
|
||||
| gin | — | v1.11.0 | ✅ OK |
|
||||
| redis-go | — | v9.16.0 | ✅ OK |
|
||||
| Rust edition | 2021 | 2021 | ✅ OK |
|
||||
| Axum | 0.8 | 0.8 | ✅ OK |
|
||||
| Tokio | 1.35 | 1.35 | ✅ OK |
|
||||
| Symphonia | 0.5 | 0.5 | ✅ OK |
|
||||
| sqlx | 0.8 | 0.8 | ✅ OK |
|
||||
| tonic | — | 0.11 | ✅ récent |
|
||||
| Postgres | 16 | 16-alpine (pinned)| ✅ OK |
|
||||
| Redis | 7 | 7-alpine (pinned) | ✅ OK |
|
||||
| ES | 8.11.0 | 8.11.0 (dev only) | ⚠️ orphelin prod |
|
||||
| RabbitMQ | 3 | 3 (pinned) | ✅ OK |
|
||||
| ClamAV | 1.4 | 1.4 (pinned) | ✅ OK |
|
||||
| MinIO | — | **`:latest`** (4×)| ❌ supply-chain |
|
||||
| Hyperswitch | 2026.03.11.0 | 2026.03.11.0 | ✅ OK |
|
||||
|
||||
**À corriger dans CLAUDE.md v1.0.5** : Vite 5 → Vite 7.1.5. Ajouter ligne MinIO.
|
||||
|
||||
---
|
||||
|
||||
## 3. Frontend (`apps/web/`)
|
||||
|
||||
### 3.1 Architecture & routes
|
||||
|
||||
- **36 feature folders** (`src/features/`) — les plus gros : `playlists/` (182), `tracks/` (181), `auth/` (100), `player/` (94), `chat/` (67).
|
||||
- **Router** (`src/router/routeConfig.tsx:1-184`) — 27 routes top-level, **54 composants lazy**. **Zéro route "Coming Soon"/placeholder**. Tous les paths mènent à un composant réel.
|
||||
- **OpenAPI typegen enclenché** : `src/types/generated/api.ts` = **6550 lignes, régénéré 2026-04-19 00:57:21**. La migration "kill hand-written services" prévue post-v1.0.4 a démarré. Script `apps/web/scripts/generate-types.sh` wiré en pre-commit.
|
||||
|
||||
### 3.2 Composants & design system
|
||||
|
||||
- `src/components/ui/` : **255 fichiers**. Untracked : `testids.ts` (NEW, probablement wiring E2E).
|
||||
- **Composants orphelins identifiés** (0-1 imports — candidates suppression) :
|
||||
- `components/ui/optimized-image/OptimizedImageSkeleton.tsx` (0)
|
||||
- `components/ui/optimized-image/ResponsiveImage.tsx` (0)
|
||||
- `components/ui/hover-card/*` (3 fichiers, 0 imports — arbre mort)
|
||||
- `components/ui/dropdown-menu/*` (7 fichiers, 0-1 imports — probablement remplacé par Radix)
|
||||
- Total : **~11 fichiers orphelins dans le DS**.
|
||||
|
||||
### 3.3 State & services
|
||||
|
||||
- **Zustand** : 5 stores principaux (`authStore`, `chatStore`, `playerStore`, `queueSessionStore`, `cartStore`) — tous utilisés.
|
||||
- **React Query** : **seulement 9 fichiers** utilisent `useQuery/useMutation`. `queryKey` ad-hoc (hardcoded, dynamic, constants mélangés). **Pas de factory centralisée** → cache invalidation fragile.
|
||||
- **Services** (73 fichiers) :
|
||||
- Top 4 monolithes : `services/api/auth.ts:553` (token+login+register+2FA), `services/adminService.ts:474` (7+ endpoints), `services/analyticsService.ts:472`, `services/marketplaceService.ts:351`.
|
||||
- **Anti-pattern critique** : `services/api/auth.ts:85-100` fait 3 fallback `const rd = response.data as any` pour parser les tokens. **Pas de validation Zod.**
|
||||
|
||||
### 3.4 Tests
|
||||
|
||||
- **286 fichiers `.test.ts(x)`** (Vitest).
|
||||
- **1 test skipped** : `features/auth/pages/ResetPasswordPage.test.tsx` (async timing).
|
||||
- **E2E** (racine `tests/e2e/`) : Playwright présent, **SKIPPED_TESTS.md documente les flakies** (v107-e2e-04/05/06/08/09 à vérifier en staging).
|
||||
- Tests E2E **PAS déclenchés en CI** (Playwright absent de `.github/workflows/ci.yml`).
|
||||
|
||||
### 3.5 Dette frontend
|
||||
|
||||
| Dette | Count | Sévérité |
|
||||
| ---------------------------------- | :---: | :------: |
|
||||
| `TODO/FIXME/HACK` | 1 | ✅ top |
|
||||
| `console.log` en production | 6 fichiers (checkbox, switch, slider, AdvancedFilters, Onboarding, useLongRunningOperation) | 🔴 |
|
||||
| `any` types | 282 | 🔴 |
|
||||
| `@ts-ignore` / `@ts-expect-error` | 6 fichiers | 🟡 |
|
||||
| Fichiers >500 LOC (non-gen) | ~8 | 🟡 |
|
||||
| Composants V2/V3/_old/_new | 0 | ✅ |
|
||||
| `src/types/v2-v3-types.ts` | présent (mentionné CLAUDE.md) | 🟡 |
|
||||
|
||||
### 3.6 Artefacts morts à la racine de `apps/web/`
|
||||
|
||||
| Fichier | Taille | Date (mtime) | Status |
|
||||
| ---------------------------- | ------ | ------------ | ----------------- |
|
||||
| `e2e-results.json` | 3.4 MB | Mar 15 | 🔴 obsolète |
|
||||
| `lint_comprehensive.json` | 793 KB | Jan 7 | 🔴 obsolète |
|
||||
| `e2e-results.json` (2) | 241 KB | Jan 7 | 🔴 doublon |
|
||||
| `ts_errors.log` | 29 KB | Dec 12 | 🔴 2+ mois stale |
|
||||
| `storybook-roadmap.json` | 8.5 KB | Mar 6 | 🟡 |
|
||||
| `AUDIT_ISSUES.json` | 19 KB | Dec 17 | 🔴 |
|
||||
| `audit.log`, `debug-storybook.log` | 8.5 KB | Feb/Mar | 🟡 |
|
||||
|
||||
**~3.5 MB de reports morts** au bord du frontend. CLAUDE.md §règles 11 interdit ces fichiers en git (ils sont ignorés via `.gitignore` mais traînent en untracked).
|
||||
|
||||
---
|
||||
|
||||
## 4. Backend Go (`veza-backend-api/`)
|
||||
|
||||
### 4.1 Structure
|
||||
|
||||
- **877 fichiers .go** dans `internal/`
|
||||
- **27 fichiers `routes_*.go`** (1 est un test)
|
||||
- **135 handlers actifs** dans `internal/handlers/`
|
||||
- **3 fichiers dans `internal/api/handlers/`** — confirmés DEPRECATED (chat + RBAC, à purger après confirmation aucun import)
|
||||
- **226 services** (`internal/services/`) + **9 core services** (`internal/core/*/service.go`)
|
||||
- **81 modèles** (`internal/models/`, 44K LOC) — pattern GORM + soft-delete
|
||||
- **160 migrations SQL** (jusqu'à `983_hyperswitch_webhook_log.sql`)
|
||||
- **17 workers** + **11 jobs**
|
||||
- **~30 middlewares**
|
||||
|
||||
### 4.2 Routes & handlers
|
||||
|
||||
Handlers complets par domaine, **zéro endpoint retournant 501 ou vide**. Zéro double wiring.
|
||||
|
||||
Top routes par taille : `routes_core.go:512` (20+ routes), `routes_auth.go:245` (14+ routes, 2FA/OAuth inclus), `routes_tracks.go:240` (18+), `routes_users.go:296` (17+), `routes_marketplace.go:174` (15+), `routes_webhooks.go:205` (5+ ; raw payload audit).
|
||||
|
||||
### 4.3 Auth
|
||||
|
||||
| Aspect | Status | Preuve |
|
||||
| -------------------- | :----: | ---------------------------------------------------------------------------------------------------- |
|
||||
| JWT RS256 prod | ✅ | `services/jwt_service.go:17-81`, keys depuis env. |
|
||||
| HS256 dev fallback | ✅ | Idem, 32+ char secret exigé. |
|
||||
| Refresh 7j / Access 5min | ✅ | Configurés. |
|
||||
| 2FA TOTP + backup codes | ✅ | `handlers/two_factor_handler.go:171` (actif). `api/handlers/` vide de 2FA — deprecated purgé. |
|
||||
| OAuth 4 providers | ✅ | `routes_auth.go:122-176` (Google, GitHub, Discord, Spotify). State encrypté via CryptoService. |
|
||||
| Rate limiting multi-couche | ✅ + 🟡 | DDoS global 1000 req/s ✅, endpoint-specific ✅, API key ✅, **`UserRateLimiter` configuré mais pas wiré aux routes**. |
|
||||
| CSRF | ✅ | Middleware actif (e2e confirmé `tests/e2e/45-playlists-deep.spec.ts`). Disabled dev/staging (`router.go:133`). |
|
||||
| Security headers | 🟡 | SecurityHeaders middleware présent (`router.go:204`). **CSP / X-Frame-Options pas vus en grep**. À vérifier. |
|
||||
|
||||
### 4.4 Modèles, DB, transactions
|
||||
|
||||
- Migrations auto-appliquées au démarrage (`database.go:234-256`). Boot fail si erreur SQL.
|
||||
- Repositories : 18 GORM-direct, pattern inline (pas d'interface). **Plus** `internal/repository/` (1 fichier in-memory mock UserRepository) **ORPHELIN** — à supprimer.
|
||||
- **Transactions insuffisantes** — `db.Transaction()` usage = **8×**, `tx.Create/Save/Delete` manuel = **37×**. Chemins critiques (marketplace `core/marketplace/service.go:1050+`, subscription) ne sont **pas dans des transactions explicites**. Risque data corruption si une étape échoue au milieu.
|
||||
|
||||
### 4.5 Services & context
|
||||
|
||||
- Architecture dual-layer `core/` + `services/` **incohérente** : certaines features ont `core/service.go`, d'autres `services/*.go`, sans règle claire. Ex. track publication en `core/track/` mais search indexing en `services/track_search_service.go`, les deux appelés depuis un même handler.
|
||||
- Context propagation : 558 usages propres dans services, **mais 31 `context.Background()` dans `handlers/`** → défait le timeout middleware. Fix grep+sed 1 jour.
|
||||
- **Pas de `services_init.go`** : services instantiés inline dans `routes_*.go`. Re-créés par request-group. Non-singletons.
|
||||
|
||||
### 4.6 Workers & jobs
|
||||
|
||||
- **Actifs lancés par `cmd/api/main.go`** : JobWorker, TransferRetry, StripeReversal, Reconciliation, CloudBackup, GearWarranty, NotifDigest, HardDelete, OrphanTracksCleanup, LedgerHealthSampler.
|
||||
- **Jobs définis mais jamais schedulés** : `SchedulePasswordResetCleanupJob`, `CleanupExpiredSessions`, `CleanupVerificationTokens`, `CleanupHyperswitchWebhookLog` — ~4 cleanup jobs **dead code**. Soit les brancher soit les supprimer.
|
||||
|
||||
### 4.7 Tests
|
||||
|
||||
- **364 fichiers `*_test.go`**. `coverage_v1.out` (Mar 3) indique ~60-70%.
|
||||
- Integration tests skippables via config — mais **pas de variable `VEZA_SKIP_INTEGRATION` trouvée en grep** (CLAUDE.md la mentionne — à vérifier si elle existe réellement ou si c'est un fossile doc).
|
||||
- E2E Playwright n'entre jamais en CI.
|
||||
|
||||
### 4.8 Validation & errors
|
||||
|
||||
- `internal/validators/` — wrapper `go-playground/validator/v10` ✅
|
||||
- `internal/errors/` — `AppError{Code,Message,Err,Details,Context}` ✅
|
||||
- **PROBLÈME** : `RespondWithAppError` défini **2 fois** (`response/response.go:101` + `handlers/error_response.go:12`). Duplication à consolider.
|
||||
- Wrapped errors : 349 usages `errors.Is/As/Unwrap` — bon pattern.
|
||||
|
||||
### 4.9 Config
|
||||
|
||||
- **99 env vars lues** dans `config/config.go` (1087 LOC)
|
||||
- **`Config.Validate()`** :
|
||||
- ✅ Refuse prod si `HYPERSWITCH_ENABLED=false` (`config.go:908-910`, fail-closed).
|
||||
- ✅ Refuse prod sans DATABASE_URL, JWT keys, CORS origins.
|
||||
- ❌ **Pas de check `APP_ENV ∈ {dev,staging,prod}`** — silencieusement default dev.
|
||||
- ❌ **Pas de check `UPLOAD_DIR` exists** — boot success même si dir manquant.
|
||||
- **`.env.template` 190 lignes** vs 263 `os.Getenv` appels code → drift potentiel (~70 vars documentées vs 99 utilisées).
|
||||
|
||||
### 4.10 Dette backend — récap
|
||||
|
||||
| Dette | Sévérité | Effort | Preuve |
|
||||
| ------------------------------------------- | :-------: | :----: | ------------------------------------------------------------- |
|
||||
| Transactions manquantes marketplace/subs | 🔴 | M (3j) | `core/marketplace/service.go:1050+` |
|
||||
| 31× `context.Background()` dans handlers | 🔴 | S (1j) | Grep handlers |
|
||||
| Binaires racine `api` (99MB) + 44 .mp3 | 🔴 | XS (1h)| `git rm --cached` + BFG |
|
||||
| `RespondWithAppError` dupliqué | 🟡 | S (1j) | `response/response.go:101` + `handlers/error_response.go:12` |
|
||||
| `internal/repository/` orphelin | 🟡 | XS | Delete dir |
|
||||
| 4 cleanup jobs jamais schedulés | 🟡 | S | Brancher ou supprimer |
|
||||
| `UserRateLimiter` configuré non wiré | 🟡 | S | Wire en middleware chain |
|
||||
| Écart `.env.template` vs code (29 vars) | 🟠 | S | Sync |
|
||||
| Services re-instantiés par request-group | 🟠 | M | `services_init.go` + singleton pattern |
|
||||
| Architecture core/+services/ incohérente | 🟠 | L | Document la règle OU unifier |
|
||||
|
||||
---
|
||||
|
||||
## 5. Rust stream server (`veza-stream-server/`)
|
||||
|
||||
### 5.1 Modules
|
||||
|
||||
Production-ready : `streaming/` (HLS réel, Range 206, WS 1047 LOC, adaptive 515 LOC), `audio/` (Symphonia native, compression 708 LOC, effects SIMD), `core/` (StreamManager 10k+ concurrents, sync engine NTP-like 1920 LOC), `auth/` (JWT HMAC-SHA256 + revocation Redis-or-in-mem 825 LOC), `cache/` (LRU audio), `event_bus.rs` (RabbitMQ degraded mode).
|
||||
|
||||
Alpha / partiel : `transcoding/engine.rs` (94 LOC, job queue priority-based mais **zéro test d'intégration, zéro tracking live**), `grpc/` (461 LOC business + 21845 LOC généré).
|
||||
|
||||
**Stub / absent** :
|
||||
- `streaming/protocols/mod.rs:4` → `// pub mod dash;` **commenté**.
|
||||
- `Cargo.toml:62` → `// webrtc = "0.7"` **commenté** (deps natives manquantes).
|
||||
|
||||
### 5.2 Audio codecs
|
||||
|
||||
Symphonia couvre MP3, FLAC, Vorbis, AAC **natifs**. LAME MP3 via `minimp3 0.5` (natif). **Commentés** : `opus 0.3` (cmake), `lame 0.1`, `fdkaac 0.7` (non sur crates.io).
|
||||
|
||||
### 5.3 gRPC & protos
|
||||
|
||||
`StreamService`, `AuthService`, `EventsService` (3 services). Utilise `proto/common/auth.proto` + `proto/stream/stream.proto`. **`proto/chat/chat.proto` = 0 import** → orphelin depuis suppression chat Rust.
|
||||
|
||||
### 5.4 Dette Rust
|
||||
|
||||
| Dette | Sévérité | Preuve |
|
||||
| ----------------------------------------------- | :------: | ---------------------------------------------------------------- |
|
||||
| `#![allow(dead_code)]` global dans `lib.rs:5` | 🔴 | Masque tous les stubs. Devrait être granulaire par module. |
|
||||
| 10× `unwrap()` sur broadcast channels | 🔴 | `core/sync.rs:1037-1110`. Panic si receiver drop. `.expect()` + contexte. |
|
||||
| `proto/chat/chat.proto` orphelin | 🟡 | À archiver/supprimer. |
|
||||
| `veza-common` chat types orphelins | 🟡 | ~60 LOC dead. Audit grep `use veza_common::chat` → 0 hit. |
|
||||
| `transcoding/` zéro tests intégration | 🟡 | `engine.rs:36-62`. |
|
||||
| 26× `println!/dbg!` | 🟡 | Devrait utiliser `tracing::`. |
|
||||
| Deps inutilisées (`daemonize`, `notify`) | 🟠 | `Cargo.toml:139, 116`. |
|
||||
|
||||
**0 `unsafe`** ✅ (engagement CLAUDE.md tenu).
|
||||
|
||||
---
|
||||
|
||||
## 6. Infrastructure & DevOps
|
||||
|
||||
### 6.1 Docker Compose (6 fichiers)
|
||||
|
||||
| Fichier | Rôle | État |
|
||||
| ---------------------------- | --------------------------------- | ------------------------------------------ |
|
||||
| `docker-compose.yml` | Dev full-stack avec profiles | ✅ Actif |
|
||||
| `docker-compose.dev.yml` | Infra-only (209 LOC) | ✅ Actif (MailHog + ES 8.11.0 ici uniquement)|
|
||||
| `docker-compose.prod.yml` | Blue-green, HAProxy, Alertmanager (464 LOC) | ✅ Actif (Mar 12) |
|
||||
| `docker-compose.staging.yml` | Caddy (202 LOC) | ✅ Actif (Mar 2) |
|
||||
| `docker-compose.test.yml` | tmpfs CI (64 LOC) | ✅ Actif |
|
||||
| `infra/docker-compose.lab.yml` | DEPRECATED Feb 2026 | 🔴 À supprimer |
|
||||
|
||||
**Pinning** :
|
||||
- ✅ Postgres 16-alpine, Redis 7-alpine, RabbitMQ 3, ClamAV 1.4, Hyperswitch 2026.03.11.0.
|
||||
- ❌ **MinIO `:latest`** dans 4 composes → supply-chain attack vector.
|
||||
|
||||
**Services orphelins en dev-only** :
|
||||
- ES 8.11.0 uniquement `docker-compose.dev.yml:171-204` (34 LOC) — **le backend utilise Postgres FTS, pas ES** (`fulltext_search_service.go`). ES ne sert qu'au hard-delete worker (GDPR cleanup), optionnel. À documenter ou retirer.
|
||||
|
||||
### 6.2 Dockerfiles
|
||||
|
||||
- Backend : `Dockerfile` + `Dockerfile.production` (Go 1.24-alpine, multi-stage, non-root uid 1001, `-w -s`). ⚠️ **CLAUDE.md dit Go 1.25, Dockerfile sur 1.24** — bumper.
|
||||
- Stream : `Dockerfile` + `Dockerfile.production` (rust:1.84-alpine). ⚠️ **Mismatch port** : Dockerfile.production expose `8082` mais `docker-compose.prod.yml:284` healthcheck attend `3001` — **le Dockerfile n'est pas utilisé en prod** (sans doute l'image vient d'ailleurs).
|
||||
- Web : `Dockerfile` + `Dockerfile.dev` + `Dockerfile.production` (node:20-alpine → nginx:1.27-alpine).
|
||||
|
||||
### 6.3 CI/CD
|
||||
|
||||
**Workflows actifs (5)** :
|
||||
1. `ci.yml` (consolidé, ~15min) — backend Go (test, lint, vet, govulncheck), frontend (lint, tsc, build, vitest), rust (build, test, clippy, audit).
|
||||
2. `frontend-ci.yml` (55 LOC) — path-triggered React-only, bundle-size gate, npm audit.
|
||||
3. `security-scan.yml` — gitleaks v8.21.2 secret scan.
|
||||
4. `trivy-fs.yml` — Trivy filesystem scan (HIGH+CRITICAL exit=1).
|
||||
5. `go-fuzz.yml` — Nightly fuzz 60s, corpus upload.
|
||||
|
||||
**Workflows disabled (19 fichiers, 1676 LOC mort)** :
|
||||
`backend-ci.yml.disabled`, `cd.yml.disabled`, `staging-validation.yml.disabled`, `accessibility.yml.disabled`, `chromatic.yml.disabled`, `visual-regression.yml.disabled`, `storybook-audit.yml.disabled`, `contract-testing.yml.disabled`, `zap-dast.yml.disabled`, `container-scan.yml.disabled`, `semgrep.yml.disabled`, `sast.yml.disabled`, `mutation-testing.yml.disabled`, `rust-mutation.yml.disabled`, `load-test-nightly.yml.disabled`, `flaky-report.yml.disabled`, `openapi-lint.yml.disabled`, `commitlint.yml.disabled`, `performance.yml.disabled`.
|
||||
|
||||
**→ 1676 lignes de workflow mort. Soit réactiver ce qui fait sens (SAST, DAST, openapi-lint), soit archiver dans `docs/archive/workflows/` pour ne pas polluer `.github/workflows/`.**
|
||||
|
||||
**Gaps CI** :
|
||||
- E2E Playwright pas déclenché (pourtant `tests/e2e/` existe, `SKIPPED_TESTS.md` documente les flakies).
|
||||
- Integration tests Go skipped (`VEZA_SKIP_INTEGRATION=1` faute de Docker socket sur runner).
|
||||
|
||||
### 6.4 K8s
|
||||
|
||||
- ~30-40 manifests, structure propre (`autoscaling/`, `backends/`, `backups/`, `cdn/`, `disaster-recovery/`, `environments/{prod,staging,dev}`, `secrets/`).
|
||||
- **5 runbooks** : cluster-failover, database-failover, data-restore, rollback-procedure, security-incident.
|
||||
- ✅ **Zéro référence à `veza-chat-server`** dans `k8s/` (grep clean — l'audit v1 disait qu'il y avait 7+ runbooks outdated ; **corrigé**).
|
||||
|
||||
### 6.5 Secrets & sécurité
|
||||
|
||||
| Item | État | Action |
|
||||
| --------------------------------------------- | :------: | -------------------------------------------------------------------- |
|
||||
| `/docker/haproxy/certs/veza.pem` | 🔴 TRACKED | BFG + rotate cert + move to K8s Secret |
|
||||
| `/config/ssl/{cert,key,veza}.pem` | 🔴 TRACKED | Idem |
|
||||
| `veza-backend-api/.env` | 🔴 TRACKED | `git rm --cached`, rotate JWT/DB secrets dev, relire `.gitignore` |
|
||||
| `veza-backend-api/.env.production.example` | 🟢 OK | Template |
|
||||
| Hardcoded secrets en code (`sk_live_`, `AKIA`)| ✅ absent | Grep clean |
|
||||
| gitleaks en CI | ✅ | `security-scan.yml` |
|
||||
| govulncheck | ✅ | `ci.yml` |
|
||||
| CSP header | 🟡 | Grep 0 hit. **À implémenter.** |
|
||||
| X-Frame-Options | 🟡 | Idem |
|
||||
|
||||
### 6.6 Observability
|
||||
|
||||
- Prometheus : **5 gauges ledger-health** déployées en v1.0.7 (`ledger_metrics.go`), **+ counter/histogram reconciler**. Alertmanager `config/alertmanager/ledger.yml` avec 3 règles (VezaOrphanRefundRows, VezaStuckOrdersPending, VezaReconcilerStale). Grafana dashboard `config/grafana/dashboards/ledger-health.json`.
|
||||
- Logs : JSON structuré confirmé (`level`, `time`, `msg`, `request_id`, `user_id`).
|
||||
- **Gap** : `/metrics` endpoint global backend pas vu (à confirmer — il existe probablement via middleware Sentry/Prometheus, mais pas en grep direct).
|
||||
- Sentry : optionnel via env (`SENTRY_DSN`, `SENTRY_SAMPLE_RATE_*`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Documentation
|
||||
|
||||
### 7.1 Racine du repo
|
||||
|
||||
| Fichier | Taille | Date | Verdict |
|
||||
| ------------------------------- | ------ | ---------- | ---------------------------------------------------------------------- |
|
||||
| `CLAUDE.md` | 22 KB | 2026-04-14 | ✅ Autorité. Petite dérive : Vite 5 → 7.1.5 à corriger. |
|
||||
| `CHANGELOG.md` | 87 KB | 2026-04-19 | ✅ À jour (v0.201 → v1.0.7-rc1). |
|
||||
| `README.md` | 2.8 KB | — | ✅ Minimal OK. |
|
||||
| `CONTRIBUTING.md` | 2.7 KB | 2026-02-27 | ✅ OK. |
|
||||
| `VERSION` | — | — | `1.0.7-rc1` ✅ aligné. |
|
||||
| `VEZA_VERSIONS_ROADMAP.md` | 69 KB | — | ⚠️ Historique v0.9xx, peu utile post-launch. Archive. |
|
||||
| `RELEASE_NOTES_V1.md` | 4.7 KB | — | ✅ OK. |
|
||||
| `AUDIT_REPORT.md` | 57 KB | 2026-04-14 | 🔄 **Ce fichier — v2 remplace v1**. |
|
||||
| `FUNCTIONAL_AUDIT.md` | 43 KB | 2026-04-19 | ✅ v2 à jour. |
|
||||
| `UI_CONTEXT_SUMMARY.md` | 6 KB | — | 🟠 Session artifact, devrait être archivé selon CLAUDE.md §12. |
|
||||
| `CLAUDE_CONTEXT.txt` | 977 KB | 2026-04-18 | 🔴 ÉNORME session dump. Archive ou supprime. |
|
||||
| `output.txt` | 27 KB | 2026-04-18 | 🔴 Debris. |
|
||||
| `generate_page_fix_prompts.sh` | 42 KB | Mar 26 | 🟡 Script généré, probablement obsolète. |
|
||||
| `build-archive.log` | 974 B | Mar 25 | 🟡 Log. |
|
||||
|
||||
**48 screenshots PNG racine** (`dashboard-*.png`, `login-*.png`, `design-system-*.png`, `forgot-password-*.png`) — **à déplacer dans `docs/screenshots/` ou supprimer**.
|
||||
|
||||
### 7.2 `docs/` (18 actifs + 417 archive = 435 .md)
|
||||
|
||||
**Actifs** :
|
||||
- `docs/API_REFERENCE.md` (1022 LOC) — **manuel**, pas de typegen. Écart flag vs routes Go. Migration vers OpenAPI typegen backend = priorité.
|
||||
- `docs/ONBOARDING.md`, `docs/PROJECT_STATE.md`, `docs/FEATURE_STATUS.md` — à cross-checker avec code v1.0.7 (non fait ici).
|
||||
- `docs/ENV_VARIABLES.md` — **introuvable en `ls docs/`** alors que CLAUDE.md dit "à maintenir". Soit créé soit manque.
|
||||
- `docs/audit-2026-04/` — **NOUVEAU, très utile** : `axis-1-correctness.md` + `v107-plan.md` — trace des findings et du plan v1.0.7.
|
||||
- `docs/SECURITY_SCAN_RC1.md` / `docs/ASVS_CHECKLIST_v0.12.6.md` / `docs/PENTEST_REPORT_VEZA_v0.12.6.md` — **refs v0.12.6, obsolètes** pour v1.0.7. Refaire ou archiver.
|
||||
|
||||
**Archive** (`docs/archive/` = 278 fichiers) : historique session 2026. Taille totale importante. Ne pose pas de problème immédiat.
|
||||
|
||||
### 7.3 `veza-docs/` (Docusaurus séparé)
|
||||
|
||||
- `veza-docs/docs/{current,vision}/` — doc cible.
|
||||
- `veza-docs/ORIGIN/` (22 fichiers, ~70K lignes) — **phase-0, jamais touchée depuis launch**. Qualifiée "FOSSIL" par agent. Archive ou zip.
|
||||
|
||||
---
|
||||
|
||||
## 8. Dette technique transverse — catalogue
|
||||
|
||||
### 8.1 TODOs / FIXMEs (11 hits)
|
||||
|
||||
1. `tests/e2e/22-performance.spec.ts:8` — "Either add data-testid containers or rewrite test to use API mocking" (3 occurrences).
|
||||
2. `tests/e2e/04-tracks.spec.ts` — "Corriger le bug dans FeedPage.tsx" (ouvert, P1).
|
||||
3. `apps/web/src/features/auth/pages/ResetPasswordPage.test.tsx` — async timing flaky.
|
||||
4. `veza-backend-api/internal/core/marketplace/service.go:1450` — "TODO v1.0.7: Stripe Connect reverse-transfer API" (**effectivement déjà landed en v1.0.7 item A+B** — TODO à supprimer).
|
||||
5. `veza-backend-api/internal/core/subscription/service.go` — "TODO(v1.0.7-item-G): subscription pending_payment state" (in-flight, parked).
|
||||
|
||||
**Aucun TODO daté >6 mois.** Discipline correcte.
|
||||
|
||||
### 8.2 Code mort / orphelin
|
||||
|
||||
| Item | Action |
|
||||
| ------------------------------------------------ | ------------------------------------------------ |
|
||||
| `veza-backend-api/internal/api/handlers/` (3 fichiers) | Confirmer 0 import puis `git rm -r` |
|
||||
| `veza-backend-api/internal/repository/` (in-mem mock) | `git rm -r` |
|
||||
| `apps/web/src/components/ui/hover-card/*` (3) | Delete si confirmé 0 import |
|
||||
| `apps/web/src/components/ui/dropdown-menu/*` (7) | Audit imports, delete si Radix les remplace |
|
||||
| `apps/web/src/components/ui/optimized-image/{OptimizedImageSkeleton,ResponsiveImage}.tsx` | Delete |
|
||||
| `apps/web/src/types/v2-v3-types.ts` | Auditer appelants, renommer ou delete |
|
||||
| `proto/chat/chat.proto` | Archiver `docs/archive/proto-chat/` ou delete |
|
||||
| `veza-common/src/chat.rs` + autres types chat | Audit `use veza_common::chat`, delete si 0 hit |
|
||||
| 19 workflows `.disabled` | Archiver `docs/archive/workflows/` ou delete |
|
||||
| 4 cleanup jobs jamais schedulés (pw-reset, sessions, verif, hyperswitch-log) | Brancher ou delete |
|
||||
|
||||
### 8.3 Binaires / artefacts trackés
|
||||
|
||||
| Item | Taille | Action |
|
||||
| --------------------------------------------------- | ------ | ------------------------------------------------- |
|
||||
| `api` (racine, ELF) | 99 MB | `git rm --cached api` + `.gitignore` |
|
||||
| `veza-backend-api/{main,veza-api,seed,server}` | ~50 MB chacun | Idem (sont dans `.gitignore` mais encore tracked?) |
|
||||
| `veza-backend-api/uploads/*.{mp3,wav}` (44 fichiers)| 12 MB | `git rm -r --cached uploads/` + move to git-lfs ou fixtures |
|
||||
| `CLAUDE_CONTEXT.txt` (racine) | 977 KB | `git rm --cached` ou déplacer |
|
||||
| `apps/web/e2e-results.json` (3.4 MB) | 3.4 MB | `.gitignore` + `rm` |
|
||||
| 48 PNG racine (dashboard-*, login-*, design-system-*, forgot-password-*) | ~5 MB total | Move to `docs/screenshots/` ou delete |
|
||||
| 36 `.playwright-mcp/*.yml` (untracked) | — | `rm -r .playwright-mcp/` |
|
||||
|
||||
### 8.4 Sécurité hors-code
|
||||
|
||||
| Item | Action |
|
||||
| ----------------------------------------- | ------------------------------------------------------ |
|
||||
| `/docker/haproxy/certs/veza.pem` tracked | BFG purge history + rotate cert + K8s Secret |
|
||||
| `/config/ssl/*.pem` tracked | Idem |
|
||||
| `veza-backend-api/.env` tracked | `git rm --cached`, rotate dev secrets, audit team |
|
||||
| CSP header absent | Middleware `SecurityHeaders` — ajouter |
|
||||
| X-Frame-Options absent | Idem |
|
||||
|
||||
### 8.5 Incohérences doc↔code
|
||||
|
||||
| Item | Delta |
|
||||
| ---------------------------------------------- | -------------------------------------------------- |
|
||||
| `CLAUDE.md` : Vite 5 | Réel Vite 7.1.5 — bumper doc |
|
||||
| `CLAUDE.md` : ES 8.11.0 partout | Réel ES 8.11.0 dev-only |
|
||||
| `CLAUDE.md` : Go 1.25 | go.mod 1.25.0 ✅ ; `veza-backend-api/Dockerfile` 1.24 — bumper |
|
||||
| `docs/API_REFERENCE.md` manuel 1022 LOC | 135 handlers — risque drift. OpenAPI typegen backend recommandé. |
|
||||
| `VEZA_VERSIONS_ROADMAP.md` v0.9xx | VERSION = 1.0.7-rc1 — archive le roadmap |
|
||||
| `docs/ASVS_CHECKLIST_v0.12.6.md` etc | Version obsolète. Refaire sur v1.0.7 ou archiver. |
|
||||
| `docs/ENV_VARIABLES.md` mentionné | Pas trouvé en `ls docs/`. Créer. |
|
||||
|
||||
### 8.6 Patterns abandonnés ou à mi-chemin
|
||||
|
||||
1. **OpenAPI typegen frontend** : démarré (`api.ts` 6550 LOC régénéré) mais les **73 services frontend restent hand-written**. Finir la migration (memory entry : "orval recommended").
|
||||
2. **OpenAPI typegen backend** : `docs/API_REFERENCE.md` manuel. Swagger infra (`swaggo/swag`) présente mais pas pleinement exploitée.
|
||||
3. **Repository pattern** : `repositories/` (GORM-direct, 18 fichiers) mixé avec `services/` qui requêtent `gormDB` direct. Pas d'interfaces. Pattern mi-chemin.
|
||||
4. **Architecture `core/` + `services/`** : pas de règle claire. À unifier ou à documenter explicitement quelles features vont où.
|
||||
5. **Transactions** : 8 usages vs 37 tx manuels. Pattern moitié-fait.
|
||||
|
||||
---
|
||||
|
||||
## 9. Top 15 priorités — impact / effort
|
||||
|
||||
> **Mise à jour 2026-04-23** — colonne `Statut` ajoutée après la session cleanup tier 1/2/3 + BFG history rewrite. Voir §9.bis pour le détail des 3 false-positives identifiés pendant l'exécution.
|
||||
|
||||
Classement pour la suite (post-v1.0.7-rc1 → v1.0.7 final → v1.0.8).
|
||||
|
||||
| # | Priorité | Impact | Effort | Statut 2026-04-23 | Rationale / Preuve |
|
||||
| --- | -------------------------------------------------------------------------------- | :----: | :-----: | :---------------- | -------------------------------------------------------------------------- |
|
||||
| 1 | **Supprimer `api` 99 MB + binaires Go trackés racine + `uploads/*.mp3`** | 🔴 CRIT | XS (1h) | ✅ DONE | BFG pass 2026-04-23, 1.5G → 66M. Force-push stages 1+2 OK. |
|
||||
| 2 | **Rotate TLS certs + supprimer `.pem` trackés + .env committed** | 🔴 CRIT | S (4h) | ✅ DONE | `.env*` + certs stripped via BFG. Keys regen, gitignorées. |
|
||||
| 3 | **Transactions marketplace/subscription** | 🔴 CRIT | M (3j) | ✅ DONE | Commit `b5281bec` — `UpdateProductImages` + `SetProductLicenses` en tx. |
|
||||
| 4 | **Context propagation : 31× `context.Background()` dans handlers** | 🔴 | S (1j) | ⚠️ FALSE-POSITIVE | 26/31 dans `*_test.go`, 5 legit (health probes + WS pumps). Voir §9.bis. |
|
||||
| 5 | **Ajouter CSP + X-Frame-Options headers** | 🔴 | S (1j) | ⚠️ FALSE-POSITIVE | `middleware/security_headers.go` couvre déjà CSP + XFO + HSTS + CORP/COEP/COOP. Voir §9.bis. |
|
||||
| 6 | **Pin MinIO `:latest` → tag daté** | 🔴 | XS (10min) | ✅ DONE | Commit `4310dbb7` — pinned `RELEASE.2025-09-07T16-13-09Z` × 4 compose files. |
|
||||
| 7 | **Nettoyer `.playwright-mcp/*.yml` + 48 PNG racine + `CLAUDE_CONTEXT.txt` + dead reports apps/web/** | 🟡 | S (2h) | ✅ DONE | Commits `d12b901d` + `172581ff` + BFG pass. |
|
||||
| 8 | **Terminer OpenAPI typegen** (frontend services + backend swaggo) | 🟡 | L (5j) | 📋 DEFERRED v1.0.8 | Memory entry, drift risk. `api.ts` 6550 LOC déjà là. Plan séparé requis. |
|
||||
| 9 | **Supprimer 19 workflows `.disabled` (1676 LOC mort) OU réactiver utiles (SAST, DAST, openapi-lint)** | 🟡 | S (4h) | ✅ DONE | Archivés dans `docs/archive/workflows/` via commit `172581ff`. |
|
||||
| 10 | **Consolider `RespondWithAppError` dupliqué** | 🟡 | S (1j) | ⚠️ FALSE-POSITIVE | `handlers/error_response.go:12` = wrapper intentionnel déléguant à `response/response.go:101`. Pas dupe. Voir §9.bis. |
|
||||
| 11 | **Wirer `UserRateLimiter` configuré mais non appelé** | 🟡 | S (1j) | ✅ DONE | Commit `ebf3276d` — wired in `AuthMiddleware.RequireAuth()`. |
|
||||
| 12 | **Supprimer `internal/repository/` (in-mem mock orphelin)** | 🟡 | XS | ✅ DONE | `user_repository.go` supprimé dans commit `172581ff`. |
|
||||
| 13 | **Remove/archive `proto/chat/chat.proto` + `veza-common/src/chat.rs`** | 🟡 | XS | ✅ DONE | Commit `172581ff` — proto + `veza-common/{chat.rs, websocket.rs}` supprimés. |
|
||||
| 14 | **Ajouter E2E Playwright en CI** | 🟡 | M (3j) | 📋 DEFERRED v1.0.8 | Playwright existe, SKIPPED_TESTS.md documenté, mais pas trigger CI. |
|
||||
| 15 | **`docs/ENV_VARIABLES.md` — créer si manque, sync avec code** | 🟠 | S (1j) | 📝 PENDING (0.5j) | Seul item réel restant du top-15 avant tag v1.0.7 final. |
|
||||
|
||||
**Bilan** : 10 ✅ DONE · 3 ⚠️ FALSE-POSITIVE · 2 📋 DEFERRED v1.0.8 · 1 📝 PENDING (~0.5j).
|
||||
|
||||
### 9.1 "À supprimer sans regret"
|
||||
|
||||
- `infra/docker-compose.lab.yml` (DEPRECATED Feb 2026)
|
||||
- `scripts/align-8px-grid.py`, `auto_migrate_tailwind_colors*.py` (tailwind migration faite)
|
||||
- 48 PNG racine
|
||||
- 36 `.playwright-mcp/*.yml`
|
||||
- 19 `.disabled` workflows
|
||||
- Binaires Go trackés
|
||||
- 44 fichiers audio `.mp3/.wav` dans `veza-backend-api/uploads/`
|
||||
- `CLAUDE_CONTEXT.txt` racine
|
||||
- `VEZA_VERSIONS_ROADMAP.md` (v0.9xx historique)
|
||||
- `generate_page_fix_prompts.sh` racine (42 KB, Mar 26)
|
||||
- `output.txt`, `build-archive.log` racine
|
||||
- `apps/web/{e2e-results.json, lint_comprehensive.json, ts_errors.log, AUDIT_ISSUES.json}`
|
||||
- `internal/repository/` (orphelin)
|
||||
- `proto/chat/chat.proto` + types `veza-common/src/chat.rs`
|
||||
- `apps/web/src/components/ui/{hover-card,dropdown-menu,optimized-image}/` orphelins
|
||||
- ~~`docs/ASVS_CHECKLIST_v0.12.6.md` + `docs/PENTEST_REPORT_VEZA_v0.12.6.md` + `docs/REMEDIATION_MATRIX_v0.12.6.md`~~ ✅ archivés dans `docs/archive/` (2026-04-23)
|
||||
|
||||
### 9.2 "À finir avant de commencer quoi que ce soit de nouveau"
|
||||
|
||||
> **Mise à jour 2026-04-23** — la liste originale (#1, #2, #3, #4, #5, #7, #8, #9) a été traitée en une session, sauf les 3 false-positives §9.bis et les 2 deferrals. Ne reste qu'un item (§9.3).
|
||||
|
||||
1. ~~**Cleanup repo** (#1, #2, #7, #9)~~ — ✅ fait, 1 session 2026-04-23.
|
||||
2. ~~**Transactions manquantes** (#3)~~ — ✅ fait, commit `b5281bec`.
|
||||
3. ~~**Context propagation** (#4)~~ — ⚠️ false-positive, pas de travail à faire (§9.bis).
|
||||
4. ~~**Security headers** (#5)~~ — ⚠️ false-positive, middleware déjà complet (§9.bis).
|
||||
5. **OpenAPI typegen** (#8) — 📋 deferred v1.0.8, plan séparé requis.
|
||||
|
||||
### 9.bis Corrections post-tier 2 (2026-04-23)
|
||||
|
||||
Trois items du top-15 ont été reclassifiés après inspection directe du code :
|
||||
|
||||
**#4 — "Context propagation : 31× `context.Background()` dans handlers"**
|
||||
Grep réel : 31 hits dans `internal/handlers/`, mais **26 dans des fichiers `_test.go`** (legit, setup tests). Les 5 hits non-test sont tous légitimes :
|
||||
- `handlers/status_handler.go:184` — probe health externe, `ctx` dédié 400ms
|
||||
- `handlers/playback_websocket_handler.go:{142,218,245}` — pumps WebSocket (doivent survivre au cycle HTTP request, pas de parent ctx disponible post-Upgrade)
|
||||
- `handlers/health.go:422` — health check 5s, `ctx` dédié
|
||||
|
||||
Le chiffre "31" masquait des patterns corrects. **Aucun handler qui défait un timeout middleware**. Pas de travail à faire.
|
||||
|
||||
**#5 — "Ajouter CSP + X-Frame-Options headers"**
|
||||
Vérification `veza-backend-api/internal/middleware/security_headers.go` : le middleware existe déjà (BE-SEC-011 + MOD-P2-005) et couvre **tous** les headers OWASP A05 recommandés :
|
||||
- `Strict-Transport-Security` (prod only)
|
||||
- `X-Frame-Options: DENY` (default) / `SAMEORIGIN` (Swagger)
|
||||
- `Content-Security-Policy` — strict `default-src 'none'` par défaut, override Swagger
|
||||
- `X-Content-Type-Options: nosniff`
|
||||
- `X-XSS-Protection`, `Referrer-Policy`, `Permissions-Policy`
|
||||
- `X-Permitted-Cross-Domain-Policies: none`
|
||||
- `Cross-Origin-{Embedder,Opener,Resource}-Policy`
|
||||
|
||||
Audit erroné. Pas de travail à faire.
|
||||
|
||||
**#10 — "Consolider `RespondWithAppError` dupliqué"**
|
||||
Vérification :
|
||||
- `internal/response/response.go:101` = implémentation réelle (17 lignes)
|
||||
- `internal/handlers/error_response.go:12` = wrapper **intentionnel** de 3 lignes qui délègue à `response.RespondWithAppError(c, appErr)`. Commenté `// Délègue au package response pour éviter duplication`.
|
||||
|
||||
Le wrapper existe pour permettre aux handlers d'importer depuis le package `handlers` sans traverser la frontière `response/` — pattern de couplage sain. Pas une duplication à consolider. Pas de travail à faire.
|
||||
|
||||
### 9.3 Chemin critique vers v1.0.7 final stable
|
||||
|
||||
> **Mise à jour 2026-04-23** — le plan 5-jours original a été compressé en 1 session (cleanup + BFG + transactions + wiring). Ne reste que l'item doc.
|
||||
|
||||
| Jour (historique) | Tâches planifiées v1 | Statut 2026-04-23 |
|
||||
| :-: | --- | --- |
|
||||
| J1 | Items #1, #2, #6, #7 — cleanup + rotation + BFG + retag | ✅ DONE |
|
||||
| J2 | Items #4, #10, #12, #13 | ⚠️ #4/#10 false-positive · ✅ #12/#13 done |
|
||||
| J3-4 | Item #3 — transactions marketplace | ✅ DONE (commit `b5281bec`) |
|
||||
| J5 | Items #5, #11, #15 + tag `v1.0.7` | ⚠️ #5 false-positive · ✅ #11 done · 📝 #15 reste (0.5j) |
|
||||
|
||||
**Reste à faire avant tag `v1.0.7` final** : item #15 (`docs/ENV_VARIABLES.md` sync) — **0.5j**. Et un quick-win 5min : ajouter `HLS_STREAMING` à `.env.template` (cf. FUNCTIONAL_AUDIT §4 stabilité item 5).
|
||||
|
||||
Ensuite v1.0.8 : OpenAPI typegen (#8, 5j), E2E CI (#14, 3j), item G subscription `pending_payment` (parké dans `docs/audit-2026-04/v107-plan.md`), wire MinIO/S3 dans path upload (2-3j, cf. FUNCTIONAL §4 item 2), STUN/TURN WebRTC si calls public (1-2j).
|
||||
|
||||
---
|
||||
|
||||
## 10. Verdict final
|
||||
|
||||
> **v2 (2026-04-20)** — application solide, dépôt sale.
|
||||
> **v3 (2026-04-23, post-cleanup + BFG)** — **application solide, dépôt propre**.
|
||||
|
||||
- **Code applicatif** : mature, testé (286 tests front + 364 back), sécurisé (gitleaks/govulncheck/trivy, JWT RS256, 2FA, OAuth, CORS strict, CSRF, DDoS rate limit), plomberie monétaire auditée (ledger-health gauges, reconciliation, idempotency, reverse-charge). **Transactions marketplace `DELETE+loop` atomiques depuis `b5281bec`**. **UserRateLimiter wired dans `AuthMiddleware` depuis `ebf3276d`**.
|
||||
- **Code infra** : 3 variants Dockerfile (dev/prod), K8s avec disaster recovery, 5 workflows CI actifs (+ 19 disabled archivés `docs/archive/workflows/`), 6 compose env pinned (MinIO daté), HAProxy blue-green.
|
||||
- **Hygiène repo** : 2.3 GB → **66 MB** `.git` après BFG 2026-04-23 (−97%). Binaires Go, PNG racine, `.playwright-mcp`, audio uploads, `.env*`, TLS certs, kubectl vendoré, builds Incus, reports lint : **tous stripped de l'historique** + ajoutés à `.gitignore` (blocks J1 + J2 + J3).
|
||||
|
||||
**Score** : v1 disait "Moyen-Haute dette". v2 : "Basse dette code / Haute dette hygiène". **v3 : dette résiduelle mineure** — 1 item pending (`docs/ENV_VARIABLES.md`, 0.5j) + 3 false-positives classés + 2 deferrals v1.0.8.
|
||||
|
||||
**En une phrase** : **`v1.0.7-rc1` est prêt à devenir `v1.0.7` final** dès que `docs/ENV_VARIABLES.md` est synchronisé avec les 99 env vars du code. Le reste (OpenAPI typegen, E2E CI, MinIO upload path, STUN/TURN) part sur v1.0.8 avec des plans séparés.
|
||||
|
||||
---
|
||||
|
||||
## Annexe — diff v1 ↔ v2 ↔ v3
|
||||
|
||||
| Thème | v1 (2026-04-14) | v2 (2026-04-20) | v3 (2026-04-23, post-cleanup + BFG) |
|
||||
| -------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| HEAD | `45662aad1` (v1.0.0-mvp-24-g45662aad1) | `89a52944e` (v1.0.7-rc1) | post-BFG : main `6d51f52a`, chore `b5281bec` |
|
||||
| Finding "chemin critique v1.0.5 public-ready"| 6 items listés | **Tous les 6 traités** (v1.0.5 → v1.0.7-rc1, 50+ commits) | — |
|
||||
| 🔴 Player/écoute audio | Bloqueur | Résolu — endpoint `/tracks/:id/stream` + Range bypass | — |
|
||||
| 🔴 IsVerified hardcoded | Bloqueur | Résolu — `core/auth/service.go:200` `IsVerified: false` | — |
|
||||
| 🟡 SMTP silent fail | Bloqueur | Résolu — schema unifié + MailHog default | — |
|
||||
| 🟡 Marketplace dev bypass | Bloqueur | Résolu — fail-closed prod via `Config.Validate:908-910` | — |
|
||||
| 🟡 Refund stub | Bloqueur | Résolu — 3-phase + idempotency + webhook reverse-charge | — |
|
||||
| 🟡 Chat multi-instance silent | Bloqueur | Résolu — log ERROR loud `chat_pubsub.go:23-27` | — |
|
||||
| 🟡 Maintenance mode in-memory | Bloqueur | Résolu — persisté `platform_settings` TTL 10s | — |
|
||||
| 🔵 Reconciliation Hyperswitch | Absent | **Nouveau** — `reconcile_hyperswitch.go:55-150` | — |
|
||||
| 🔵 Webhook raw payload audit | Absent | **Nouveau** — `webhook_log.go:34-80` + cleanup 90j | — |
|
||||
| 🔵 Ledger-health metrics | Absent | **Nouveau** — 5 gauges + 3 alertes + Grafana | — |
|
||||
| 🔵 Stripe Connect reversal async | Absent | **Nouveau** — `reversal_worker.go:12-180` | — |
|
||||
| 🔵 Self-service creator upgrade | Absent | **Nouveau** — `POST /users/me/upgrade-creator` | — |
|
||||
| Hygiène `.git` 2.3 GB | Bloqueur | **Non traité** | ✅ **66 MB après BFG** (−97%) |
|
||||
| Hygiène binaires tracked | 3 binaires | 1 reste (`api` 99 MB racine) | ✅ **0 binaires** (BFG pass + `.gitignore` J3) |
|
||||
| Hygiène `uploads/*.mp3` 44 fichiers | Présent | **Non traité** | ✅ **stripped** (BFG pass, `uploads/` gitignoré J2) |
|
||||
| Hygiène 54 PNG racine | Présent | 48 restent | ✅ **stripped** (BFG pass, patterns gitignorés J2+J3) |
|
||||
| TLS certs committés + `.env*` | Présent | Présent | ✅ **stripped** (BFG pass) |
|
||||
| Transactions marketplace | Non auditée | 🔴 CRIT flaggée | ✅ **fixées** (commit `b5281bec`) |
|
||||
| UserRateLimiter | Non mentionné | Configuré mais non câblé | ✅ **wiré** (commit `ebf3276d`) |
|
||||
| Orphelin `internal/repository/` | Non mentionné | Flaggé | ✅ **supprimé** (commit `172581ff`) |
|
||||
| Orphelins Rust (`proto/chat`, `veza-common/{chat,ws}.rs`) | Non mentionné | Flaggé | ✅ **supprimés** (commit `172581ff`) |
|
||||
| Runbooks k8s outdated (chat Rust) | 7+ runbooks | **0 référence** — clean | — |
|
||||
| CLAUDE.md précis | Faux | **À jour** sauf Vite 5→7 | — |
|
||||
| Site Docusaurus `ORIGIN/` | À réécrire | **22 fichiers FOSSILE encore** — à archiver | (hors scope cleanup) |
|
||||
| Workflows CI | `.github/workflows/*` non consolidé | Consolidé (`ci.yml`) + **19 disabled qui traînent** | ✅ **19 archivés** dans `docs/archive/workflows/` |
|
||||
| `docs/audit-2026-04/` | Absent | **Nouveau** — axis-1-correctness + v107-plan | — |
|
||||
|
||||
**Score global** : v1 "Moyen-Haute dette" → v2 "Basse dette code / Haute dette hygiène" → **v3 "dette résiduelle mineure" (1 item pending, 3 false-positives classés, 2 deferrals v1.0.8)**.
|
||||
|
||||
---
|
||||
|
||||
*Généré par Claude Code Opus 4.7 (1M context, /effort max, /plan) — 5 agents Explore parallèles (frontend, backend Go, Rust stream, infra/DevOps, dette transverse) + mesures macro directes (du, ls, git ls-files) + lecture `CHANGELOG.md` v1.0.5→v1.0.7-rc1 + `docs/audit-2026-04/v107-plan.md`. Cross-référencé avec [FUNCTIONAL_AUDIT.md v2](FUNCTIONAL_AUDIT.md) pour les verdicts fonctionnels.*
|
||||
702
AUDIT_TECHNIQUE_2026-02-22.md
Normal file
702
AUDIT_TECHNIQUE_2026-02-22.md
Normal file
|
|
@ -0,0 +1,702 @@
|
|||
# AUDIT TECHNIQUE — VEZA MONOREPO
|
||||
|
||||
| Champ | Valeur |
|
||||
|-------|--------|
|
||||
| **Date** | 2026-02-22 |
|
||||
| **Auditeur** | Claude 4.6 Opus (IA) — mandat due diligence |
|
||||
| **Version analysée** | v0.402, main (HEAD+49 commits non poussés) |
|
||||
| **Périmètre** | Backend Go, Chat Server Rust, Stream Server Rust, Frontend React, Infra Docker/CI |
|
||||
| **Méthodologie** | Analyse statique du code source, 6 passes d'exploration |
|
||||
| **Classification** | Confidentiel — Usage interne |
|
||||
|
||||
---
|
||||
|
||||
## EXECUTIVE SUMMARY
|
||||
|
||||
### Verdict global
|
||||
|
||||
Veza est un projet **ambitieux et structurellement bien pensé** pour un effort solo/micro-équipe. L'architecture backend Go est la pièce la plus mature : séparation handler → service → repository, middleware stack complète, couverture de tests proche de 1:1. Le frontend React est extensif (~131K LOC source) avec un design system cohérent (SUMI), Storybook-driven development, et 288 stories.
|
||||
|
||||
**Cependant, le projet n'est pas prêt pour la production.** La vélocité affichée (345+ features, 12 releases en ~3 mois) masque une réalité : de nombreuses features sont partiellement implémentées (frontend mock, backend stub, ou flux E2E non connecté). Les services Rust compilent mais ne sont pas intégrés (gRPC = stub, boot mode = chat/stream OFF). L'infrastructure CI/CD contient des défauts critiques (pipeline CD non fonctionnel, secrets en clair, versions Go incohérentes).
|
||||
|
||||
### Top 5 risques
|
||||
|
||||
| # | Risque | Gravité |
|
||||
|---|--------|---------|
|
||||
| 1 | **Pipeline CD non fonctionnel** — Les conditions `secrets.*` dans les `if` GitHub Actions ne s'évaluent jamais. Les étapes push, sign, deploy ne s'exécutent pas. | CRITIQUE |
|
||||
| 2 | **Authentification HLS/WebSocket cassée** — `TokenStorage.getAccessToken()` retourne toujours `null` (cookies httpOnly). Les clients HLS et WebSocket ne peuvent pas s'authentifier. | CRITIQUE |
|
||||
| 3 | **Redis sans mot de passe en production** — `docker-compose.prod.yml` ne configure aucune authentification Redis. | ÉLEVÉ |
|
||||
| 4 | **Rate limiter en mémoire** — Ne fonctionne pas en multi-instance. Brute force possible en prod scalée. | ÉLEVÉ |
|
||||
| 5 | **Services Rust non intégrés** — Chat et Stream servers compilent mais tournent en "boot mode" (OFF). 21.5% des 600 features annoncées sont réellement fonctionnelles. | ÉLEVÉ |
|
||||
|
||||
### Top 5 forces
|
||||
|
||||
| # | Force |
|
||||
|---|-------|
|
||||
| 1 | **Architecture backend Go exemplaire** — Séparation claire des responsabilités, middleware stack complète (23 middlewares), ratio test/code 0.97:1 |
|
||||
| 2 | **Sécurité auth solide** — Tokens httpOnly, access token 5min, bcrypt cost 12, CSRF timing-safe, validation JWT stricte (iss/aud/exp/algo) |
|
||||
| 3 | **Design system cohérent** — SUMI Design System v2.0, 882 lignes de tokens CSS, Storybook-first, 288 stories |
|
||||
| 4 | **Infrastructure de qualité** — CI multi-pipeline, Dependabot, security scanning, Dockerfiles multi-stage, utilisateur non-root |
|
||||
| 5 | **Documentation extensive** — 63 docs frontend, scope control par version, CHANGELOG structuré, FEATURE_STATUS tracé |
|
||||
|
||||
### Recommandation go/no-go
|
||||
|
||||
**NO-GO pour production en l'état.** Conditionnel à 4-6 semaines de stabilisation ciblée (voir Phase 1-2 du plan d'action). Le code est de qualité suffisante pour être corrigé, pas réécrit.
|
||||
|
||||
---
|
||||
|
||||
## 1️⃣ CARTOGRAPHIE GLOBALE
|
||||
|
||||
### 1.1 Stack réelle
|
||||
|
||||
| Élément | Constaté dans le code |
|
||||
|---------|----------------------|
|
||||
| **Go** | 1.24.0 (`go.work`), mais Dockerfile.production utilise 1.23-alpine — **incohérence** |
|
||||
| **Rust** | Stable channel (`rust-toolchain.toml`), Edition 2021 |
|
||||
| **Node.js** | 20 (CI workflows), npm 10.9.2 (`packageManager`) |
|
||||
| **React** | 18.2.0 |
|
||||
| **Vite** | 7.1.5 |
|
||||
| **TypeScript** | 5.3.3 (package.json) mais 5.9.3 (root devDependencies) — **incohérence** |
|
||||
| **Tailwind CSS** | 4.0.0 (CSS-first config) |
|
||||
| **Framework Go** | Gin 1.11.0 (dernière stable, maintenu) |
|
||||
| **ORM Go** | GORM 1.30.0 + lib/pq 1.10.9 (parameterized queries) |
|
||||
| **Framework Rust** | Axum 0.8 (chat + stream), Tokio 1.35 |
|
||||
| **SQLx** | 0.8 (Rust services) |
|
||||
| **PostgreSQL** | 16-alpine (dev/prod), 15-alpine (test/hybrid) — **incohérence** |
|
||||
| **Redis** | 7 (docker-compose), go-redis/v9 9.16.0 |
|
||||
| **RabbitMQ** | 3-management-alpine |
|
||||
| **Auth** | JWT HS256 via `golang-jwt/jwt/v5`, access 5min, refresh 14j, remember-me 30j |
|
||||
| **Paiement** | Hyperswitch via `@juspay-tech/hyper-js`, SDK frontend uniquement, mode test |
|
||||
| **Streaming** | HLS prévu mais **désactivé** (`HLS_STREAMING=false`), service stub |
|
||||
| **WebSocket** | Gorilla (Go), Axum WS (Rust), native WebSocket API (frontend) |
|
||||
| **WebRTC** | Code présent dans stream-server mais **commenté/désactivé** |
|
||||
| **CI/CD** | GitHub Actions — 12 workflows |
|
||||
| **Containerisation** | Docker multi-stage, images alpine, utilisateur non-root |
|
||||
| **Monitoring** | Prometheus configuré, Grafana référencé, Sentry intégré, zap structured logging |
|
||||
| **Monorepo** | Turborepo + npm workspaces + Go workspace |
|
||||
|
||||
### 1.2 Organisation du monorepo
|
||||
|
||||
| Répertoire | Rôle réel | Fichiers | LOC |
|
||||
|-----------|-----------|----------|-----|
|
||||
| `veza-backend-api/` | API REST Go — cœur fonctionnel du produit | 671 .go | 174,022 |
|
||||
| `veza-chat-server/` | Serveur chat Rust — compile, non intégré | 78 .rs | ~60,000* |
|
||||
| `veza-stream-server/` | Serveur streaming Rust — compile, non intégré | 113 .rs | ~80,000* |
|
||||
| `apps/web/` | Frontend React/Vite — interface utilisateur | 1,837 .ts/.tsx | ~206,000 |
|
||||
| `veza-docs/` | Site Docusaurus — squelette non alimenté | ~20 | ~500 |
|
||||
| `docs/` | Documentation projet/versioning | 332 .md | ~15,000 |
|
||||
| `scripts/` | Scripts utilitaires (audit, migration, deploy) | 85+ | ~5,000 |
|
||||
| `.github/` | CI/CD workflows + templates | 12 workflows | ~800 |
|
||||
| `config/` | Prometheus, métriques, SSL | 5 | ~100 |
|
||||
| `infra/` | docker-compose lab | 1 | ~50 |
|
||||
| `make/` | Makefile modulaire | 11 .mk | ~800 |
|
||||
| `dev-environment/` | Templates de services | ~10 | ~400 |
|
||||
| `fixtures/` | Package npm vide | 5 | ~50 |
|
||||
| `packages/` | Shared packages — **vide** | 0 | 0 |
|
||||
|
||||
*LOC Rust estimée (total 352K inclut le code généré gRPC/protobuf)
|
||||
|
||||
**Packages orphelins :**
|
||||
- `packages/` — déclaré dans npm workspaces mais vide
|
||||
- `fixtures/` — package npm avec `vitest.config.ts` mais aucun test
|
||||
- `veza-docs/` — Docusaurus configuré mais non alimenté
|
||||
|
||||
**Packages fantômes :**
|
||||
- `veza-backend-api/internal/api/archive/api_manager.go` — 789 lignes de code commenté/TODO, jamais importé
|
||||
- `dev-environment/templates/` — templates de génération de code non utilisés par un outil
|
||||
|
||||
**Duplications cross-packages :**
|
||||
- JWT validation implémentée 3 fois (Go `jwt_service.go`, Rust chat `jwt_manager.rs`, Rust stream `token_validator.rs`)
|
||||
- Configuration loading implémentée 3 fois avec des patterns différents
|
||||
- gRPC protobuf généré dupliqué entre chat et stream servers
|
||||
|
||||
### 1.3 Dépendances critiques
|
||||
|
||||
#### Backend Go (44 dépendances directes)
|
||||
|
||||
| Dépendance | Version | Statut | Risque |
|
||||
|-----------|---------|--------|--------|
|
||||
| `gin-gonic/gin` | 1.11.0 | Maintenu activement | Faible |
|
||||
| `gorm.io/gorm` | 1.30.0 | Maintenu activement | Faible |
|
||||
| `golang-jwt/jwt/v5` | 5.3.0 | Maintenu | Faible |
|
||||
| `redis/go-redis/v9` | 9.16.0 | Maintenu | Faible |
|
||||
| `gorilla/websocket` | 1.5.3 | **Archivé** (décembre 2024) | MOYEN — migrer vers `nhooyr.io/websocket` |
|
||||
| `lib/pq` | 1.10.9 | En maintenance minimale | Faible (GORM l'utilise via driver) |
|
||||
| `swaggo/swag` | 1.16.6 | Maintenu | Faible |
|
||||
| `sony/gobreaker` | 1.0.0 | Maintenu | Faible |
|
||||
| `getsentry/sentry-go` | 0.40.0 | Maintenu | Faible |
|
||||
| `testcontainers-go` | 0.33.0 | Maintenu | Faible |
|
||||
|
||||
#### Frontend React (dépendances majeures)
|
||||
|
||||
| Dépendance | Version | Statut | Risque |
|
||||
|-----------|---------|--------|--------|
|
||||
| `react` | 18.2.0 | **React 19 disponible** — 1 majeure de retard | MOYEN |
|
||||
| `@tanstack/react-query` | 5.17.0 | Maintenu | Faible |
|
||||
| `zustand` | 4.5.0 | Maintenu | Faible |
|
||||
| `msw` | 2.11.2 | Maintenu | Faible |
|
||||
| `dompurify` | Utilisé via sanitize.ts | Maintenu | Faible |
|
||||
| `@juspay-tech/hyper-js` | Hyperswitch SDK | Niche — petit écosystème | MOYEN |
|
||||
|
||||
#### Rust (dépendances clés)
|
||||
|
||||
| Dépendance | Version | Risque |
|
||||
|-----------|---------|--------|
|
||||
| `axum` | 0.8 | Faible — maintenu par Tokio |
|
||||
| `sqlx` | 0.8 | Faible — maintenu |
|
||||
| `jsonwebtoken` | 10 | Faible |
|
||||
| `tonic` | 0.11 | Faible — gRPC bien maintenu |
|
||||
| `lapin` | 2.3 | MOYEN — RabbitMQ Rust, communauté petite |
|
||||
| `symphonia` | 0.5 | MOYEN — audio processing, niche |
|
||||
|
||||
### 1.4 Schéma des flux
|
||||
|
||||
#### Auth flow
|
||||
|
||||
```
|
||||
Browser → POST /api/v1/auth/register → AuthHandler → AuthService → GORM → PostgreSQL
|
||||
→ POST /api/v1/auth/login → AuthHandler → PasswordService.VerifyPassword → bcrypt
|
||||
→ Set-Cookie: access_token (httpOnly, 5min)
|
||||
→ Set-Cookie: refresh_token (httpOnly, 14j)
|
||||
→ POST /api/v1/auth/refresh → Cookie → JWTService.ValidateToken → TokenVersion check → New tokens
|
||||
→ POST /api/v1/auth/oauth/:provider → OAuthService → Google/GitHub → JWT
|
||||
```
|
||||
|
||||
**SPOF :** PostgreSQL (session lookup per request), Redis (CSRF tokens)
|
||||
**Timeout :** 30s request timeout (middleware), context propagation
|
||||
**Retry :** Pas de retry sur DB failure
|
||||
**Race condition :** Token version increment non transactionnel — deux refresh simultanés pourraient invalider l'un l'autre
|
||||
|
||||
#### Payment flow
|
||||
|
||||
```
|
||||
Frontend → POST /api/v1/marketplace/checkout → MarketplaceHandler → HyperswitchService
|
||||
→ Hyperswitch API → Create PaymentIntent → client_secret
|
||||
→ Frontend → Hyperswitch SDK → Card form → Confirm payment
|
||||
→ Hyperswitch → Webhook → POST /api/v1/webhooks/hyperswitch (?)
|
||||
→ MarketplaceService → Update order status
|
||||
```
|
||||
|
||||
**SPOF :** Hyperswitch API (externe)
|
||||
**Risque critique :** Le handler de webhook entrant Hyperswitch n'a pas été trouvé dans `webhook_handlers.go` (ce fichier ne gère que les webhooks sortants). La vérification de signature webhook est potentiellement absente.
|
||||
**Timeout :** Non vérifié pour les appels Hyperswitch
|
||||
**Idempotence :** Non vérifiée pour les webhooks de paiement
|
||||
|
||||
#### Chat flow (théorique — non intégré)
|
||||
|
||||
```
|
||||
Frontend → WebSocket /ws → Chat Server (Rust/Axum) → JWT validation → Hub
|
||||
→ Message → SQLx → PostgreSQL (chat DB séparée)
|
||||
→ Broadcast → Connected clients
|
||||
```
|
||||
|
||||
**État actuel :** Boot mode — chat server OFF. Frontend utilise MSW mocks.
|
||||
|
||||
#### Stream flow (théorique — non intégré)
|
||||
|
||||
```
|
||||
Frontend → GET /stream/hls/:track_id/playlist.m3u8 → Stream Server (Rust/Axum)
|
||||
→ JWT validation (cassée — token null) → HLS segments → Player
|
||||
```
|
||||
|
||||
**État actuel :** Stream server OFF. HLS désactivé. Frontend fallback sur des URLs directes.
|
||||
|
||||
---
|
||||
|
||||
## 2️⃣ CE QUE LE PRODUIT PERMET RÉELLEMENT
|
||||
|
||||
### 2.1 Classification des features
|
||||
|
||||
#### ✅ Fonctionnelles (flux complet front + back + DB)
|
||||
|
||||
1. **Authentication** — Register/Login/Logout avec JWT httpOnly cookies
|
||||
2. **2FA TOTP** — Activation, vérification, codes de récupération
|
||||
3. **OAuth** — Google, GitHub (Discord/Spotify : code présent mais non fonctionnel)
|
||||
4. **Profils utilisateur** — CRUD, avatar, banner, liens sociaux, profil privé
|
||||
5. **Upload audio** — Validation magic bytes, ClamAV, métadonnées
|
||||
6. **CRUD Tracks** — Création, édition, suppression, métadonnées enrichies (BPM, key, lyrics, tags)
|
||||
7. **Playlists** — CRUD, collaboration, partage, recommandations
|
||||
8. **Dashboard** — Vue d'ensemble utilisateur
|
||||
9. **Sessions** — Liste, révocation
|
||||
10. **Settings** — Profil, sécurité, notifications, préférences
|
||||
11. **Marketplace** — Catalogue produits, panier, wishlist
|
||||
12. **Search** — Recherche full-text avec pg_trgm, filtres
|
||||
13. **Social posts** — CRUD, likes, commentaires (feed basique)
|
||||
14. **RBAC** — Rôles utilisateur, middleware d'autorisation
|
||||
|
||||
#### ⚠️ Partiellement implémentées
|
||||
|
||||
| Feature | Backend | Frontend | Écart |
|
||||
|---------|---------|----------|-------|
|
||||
| **Checkout Hyperswitch** | Handler + Hyperswitch SDK | Formulaire paiement | Webhook entrant non trouvé, mode test uniquement |
|
||||
| **Promo codes** | Migration 099-100, handler | Modal + cart integration | En cours (fichiers modifiés dans git status) |
|
||||
| **Notifications** | Service + push web | Composants UI | Backend OK, frontend MSW pour certaines routes |
|
||||
| **Analytics** | Handler + service (7% complet selon audit interne) | Dashboard composants | Données réelles partielles |
|
||||
| **Admin panel** | Routes protégées | Pages admin | Fonctionnalités limitées |
|
||||
| **Webhooks** | CRUD outbound | Developer UI | Pas de delivery engine visible |
|
||||
| **Gear/Inventory** | Handler | Composants | Backend minimal |
|
||||
| **Live streaming** | Handler | Composants | Backend stub |
|
||||
| **Trending** | TrendingService | Feed explore | Algorithme basique |
|
||||
|
||||
#### 👻 Fantômes (déclarées mais absentes ou stub)
|
||||
|
||||
| Feature | Déclarée dans | Réalité |
|
||||
|---------|--------------|---------|
|
||||
| **HLS Streaming** | FEATURE_STATUS ("operational") | `HLS_STREAMING=false`, stream server OFF, `getHLSXhrSetup()` retourne token null |
|
||||
| **WebRTC Audio Calls** | CHANGELOG v0.303 | Code commenté/désactivé dans stream-server |
|
||||
| **OAuth Discord/Spotify** | FEATURE_STATUS ("operational") | Audit interne confirme : non implémentés |
|
||||
| **Chat temps réel** | FEATURE_STATUS ("operational") | Chat server en boot mode (OFF), frontend MSW |
|
||||
| **gRPC inter-services** | Architecture déclarée | Stub — protobuf généré mais endpoints non connectés |
|
||||
|
||||
#### 💀 Mortes (code présent, jamais appelé)
|
||||
|
||||
| Code mort | Fichier | LOC |
|
||||
|-----------|---------|-----|
|
||||
| `api_manager.go` | `internal/api/archive/api_manager.go` | 789 |
|
||||
| `docs.go` (Swagger généré) | `internal/handlers/docs/docs.go` | 5,482 |
|
||||
| `GenerateJWT` dans PasswordService | `internal/services/password_service.go:249` | ~20 (méthode sans iss/aud, potentiellement dangereuse si appelée) |
|
||||
| `TokenStorage.getAccessToken()` | `apps/web/src/services/tokenStorage.ts` | ~107 (tout le fichier est un no-op) |
|
||||
| `isTokenExpiringSoon()` | `apps/web/src/services/tokenRefresh.ts` | ~30 (retourne toujours true) |
|
||||
| `MOCK_PURCHASES` | `apps/web/src/services/commerceService.ts` | ~50 (données mock retournées en production) |
|
||||
| `requestRefund()` | `apps/web/src/services/commerceService.ts` | ~10 (no-op, retourne toujours `{success: true}`) |
|
||||
|
||||
#### 🧪 Expérimentales abandonnées
|
||||
|
||||
| Feature | Traces |
|
||||
|---------|--------|
|
||||
| **Éducation/Gamification** | Supprimés du code, mentionnés dans FEATURE_STATUS comme "permanently deleted" |
|
||||
| **veza-mobile** | Mentionné dans FEATURE_STATUS comme abandonné |
|
||||
| **packages/design-system** | Répertoire `packages/` vide, design system migré dans `apps/web/src/index.css` |
|
||||
|
||||
### 2.2 Incohérences produit/code
|
||||
|
||||
| Source | Affirme | Réalité code |
|
||||
|--------|---------|-------------|
|
||||
| `docs/FEATURE_STATUS.md` | "19 features operational" | ~14 véritablement fonctionnelles E2E, 5 partielles ou fantômes |
|
||||
| `docs/FEATURE_STATUS.md` | "HLS_STREAMING = true, operational" | `HLS_STREAMING=false`, service OFF, auth cassée |
|
||||
| `docs/FEATURE_STATUS.md` | "OAuth Discord + Spotify operational" | Audit interne confirme non implémentés |
|
||||
| `CHANGELOG.md` v0.303 | "WebRTC audio calls 1-to-1" | Code commenté dans stream-server |
|
||||
| `CHANGELOG.md` v0.402 | "Checkout Hyperswitch production-ready" | Mode test, webhook entrant non trouvé |
|
||||
| Audit interne (103) | Score 32/100, 21.5% features done | FEATURE_STATUS liste 19 features "operational" |
|
||||
| `V0_101_RELEASE_SCOPE.md` | "All services must be running together" | Boot mode = chat/stream/RabbitMQ/ClamAV OFF |
|
||||
|
||||
---
|
||||
|
||||
## 3️⃣ VALIDATION FONCTIONNELLE
|
||||
|
||||
### 3.1 Couverture de tests
|
||||
|
||||
| Service | Fichiers test | LOC test | LOC source | Ratio | Commentaire |
|
||||
|---------|--------------|----------|-----------|-------|-------------|
|
||||
| **Go backend** | 264 | 85,455 | 87,930 | **0.97:1** | Excellent. Tests unitaires + intégration + sécurité |
|
||||
| **Rust chat** | ~28 modules | ~5,000* | ~55,000* | ~0.09:1 | Faible. Principalement des tests unitaires inline |
|
||||
| **Rust stream** | ~30 modules | ~8,000* | ~72,000* | ~0.11:1 | Faible. Tests de charge présents mais basiques |
|
||||
| **Frontend** | 274 | 58,816 | 130,976 | **0.45:1** | Correct. Tests composants Vitest + Storybook tests |
|
||||
| **Stories** | 288 | 15,987 | — | — | Bonne couverture Storybook |
|
||||
|
||||
*Estimé à partir des 352K LOC Rust totales incluant le code généré
|
||||
|
||||
**Tests E2E :** Playwright configuré (5 configs : smoke, storybook, visual, main, patch). Scénarios E2E dans `ci.yml` avec docker-compose full stack.
|
||||
|
||||
**Tests de sécurité :** Présents dans Go (`tests/security/authorization_test.go`, `injection_attack_test.go`).
|
||||
|
||||
**Mocks vs API réelle :** 100% du frontend teste contre MSW. Aucun test frontend contre l'API réelle (sauf E2E).
|
||||
|
||||
### 3.2 Points de rupture identifiés
|
||||
|
||||
| Scénario | Impact | Mitigation existante |
|
||||
|----------|--------|---------------------|
|
||||
| Redis tombe | CSRF cassé → toutes les mutations échouent (503) | Aucune — Redis est SPOF pour CSRF en prod |
|
||||
| 10K tracks par utilisateur | Pagination cursor OK, mais pas de limite `max` documentée | Pagination offset + limit avec max configurable |
|
||||
| Fichier audio 10GB | `MaxUploadSize` configurable, validation taille | Oui — configurable dans env |
|
||||
| 1000 WebSocket simultanées | Chat server non testé sous charge en intégration | Load testing basique dans stream server |
|
||||
| Webhook Hyperswitch replay | Handler webhook entrant non trouvé | **RISQUE** — pas d'idempotence vérifiable |
|
||||
| Token expiré mid-session | Proactive refresh toutes les 4 min + retry 401 avec queue | Robuste — bien implémenté |
|
||||
| Migration partielle | Pas de transaction wrapping dans les migrations SQL | **RISQUE** — état DB incohérent possible |
|
||||
| 2 refresh simultanés | Token version increment non-atomic | **RISQUE** — race condition possible |
|
||||
|
||||
---
|
||||
|
||||
## 4️⃣ REGISTRE DES VULNÉRABILITÉS
|
||||
|
||||
| ID | Catégorie | Gravité | Fichier(s) | Description | Impact | Correctif | Effort |
|
||||
|----|-----------|---------|-----------|-------------|--------|-----------|--------|
|
||||
| VEZA-SEC-001 | A05 Misconfig | **CRITIQUE** | `.github/workflows/cd.yml` | Conditions `secrets.*` dans `if` GHA ne s'évaluent jamais → pipeline CD non fonctionnel | Aucun déploiement automatisé ne fonctionne | Utiliser `vars.*` ou étape de vérification séparée | S |
|
||||
| VEZA-SEC-002 | A07 Auth | **CRITIQUE** | `apps/web/src/services/tokenStorage.ts`, `hlsService.ts`, `websocket.ts` | `getAccessToken()` retourne `null` → HLS et WebSocket ne peuvent pas s'authentifier | Streaming non protégé ou non fonctionnel | Implémenter auth par cookie pour WS/HLS ou endpoint de stream token | M |
|
||||
| VEZA-SEC-003 | A05 Misconfig | **CRITIQUE** | `docker-compose.hybrid.yml` | `network_mode: host` + Grafana password `admin` par défaut | Infrastructure accessible depuis le réseau sans auth | Supprimer network_mode host, forcer mot de passe | S |
|
||||
| VEZA-SEC-004 | A05 Misconfig | **ÉLEVÉ** | `docker-compose.prod.yml` | Redis sans authentification en production | Cache compromis → session hijacking, data poisoning | Ajouter `--requirepass` et `REDIS_PASSWORD` | S |
|
||||
| VEZA-SEC-005 | A01 Access | **ÉLEVÉ** | `docker-compose.prod.yml:217-220` | Stream server manque `JWT_SECRET` en prod compose | Service accepte potentiellement des requêtes non authentifiées | Ajouter `JWT_SECRET` dans la config stream-server | S |
|
||||
| VEZA-SEC-006 | A04 Design | **ÉLEVÉ** | `internal/middleware/ratelimit.go` | Rate limiter in-memory → ne fonctionne pas multi-instance | Brute force multiplié par nombre d'instances | Migrer vers rate limiting Redis | M |
|
||||
| VEZA-SEC-007 | A02 Crypto | **ÉLEVÉ** | `.github/workflows/ci.yml:248,287` | Mot de passe de test E2E en clair dans le workflow | Credential leakage si repo public | Migrer vers GitHub Secrets | S |
|
||||
| VEZA-SEC-008 | A01 Access | **MOYEN** | `internal/handlers/upload.go:308-326` | `GetUploadStatus` — IDOR, pas de vérification d'ownership | Un utilisateur authentifié peut voir le statut de n'importe quel upload | Ajouter check `upload.UserID == currentUserID` | S |
|
||||
| VEZA-SEC-009 | A10 SSRF | **MOYEN** | `internal/handlers/webhook_handlers.go:69` | URL de webhook accepte tout schéma (file://, http://169.254.x.x) | SSRF via webhook delivery | Valider schéma (https only), bloquer IPs privées | S |
|
||||
| VEZA-SEC-010 | A08 Integrity | **MOYEN** | Non trouvé | Webhook entrant Hyperswitch — handler non identifié, vérification de signature incertaine | Webhooks de paiement potentiellement non vérifiés | Vérifier/implémenter vérification HMAC-SHA256 | M |
|
||||
| VEZA-SEC-011 | A05 Misconfig | **MOYEN** | `cmd/api/main.go:8` | `import _ "net/http/pprof"` en production | Profiling endpoints accessibles si DefaultServeMux exposé | Conditionner import au mode dev | S |
|
||||
| VEZA-SEC-012 | A02 Crypto | **MOYEN** | `internal/services/password_service.go:92-95` | Reset tokens stockés en clair dans PostgreSQL | Si DB compromise, tous les tokens de reset actifs sont exposés | Stocker le hash SHA-256 du token | S |
|
||||
| VEZA-SEC-013 | A07 Auth | **MOYEN** | `internal/middleware/auth.go:312-406` | `OptionalAuth` ne vérifie pas la correspondance session/user | Session hijacking silencieux sur les routes optionnelles | Ajouter vérification `session.UserID == tokenUserID` | S |
|
||||
| VEZA-SEC-014 | A04 Design | **MOYEN** | `internal/middleware/csrf.go:153` | Un seul token CSRF par utilisateur → multi-onglet cassé | UX dégradée, utilisateurs forcés de rafraîchir | Implémenter pool de tokens ou token par session | M |
|
||||
| VEZA-SEC-015 | A05 Misconfig | **MOYEN** | `docker-compose.staging.yml:64` | `JWT_SECRET=${STAGING_JWT_SECRET}` sans check `?` → peut être vide | Staging potentiellement sans validation JWT | Ajouter `:?error message` | S |
|
||||
| VEZA-SEC-016 | A01 Access | **MOYEN** | `docker-compose.staging.yml` | Ports backend/frontend exposés directement sans reverse proxy | Pas de TLS termination, pas de WAF | Ajouter HAProxy comme en prod | M |
|
||||
| VEZA-SEC-017 | A09 Logging | **MOYEN** | Multiples fichiers | 15+ `fmt.Printf` dans le code production (upload_validator.go, router.go) | Bypass du logging structuré, potentielle fuite d'info | Remplacer par `logger.Debug()` | S |
|
||||
| VEZA-SEC-018 | A07 Auth | **FAIBLE** | `apps/web/src/features/auth/store/authStore.ts:352` | `isAuthenticated` persisté dans localStorage | XSS → bypass des guards UI (backend protège toujours) | Utiliser sessionStorage ou mémoire uniquement | S |
|
||||
| VEZA-SEC-019 | A02 Crypto | **FAIBLE** | `internal/services/password_service.go:150` | Coût bcrypt hardcodé `12` au lieu de la constante `bcryptCost` | Risque d'incohérence lors de maintenance | Utiliser la constante | S |
|
||||
|
||||
---
|
||||
|
||||
## 5️⃣ DETTE TECHNIQUE
|
||||
|
||||
### 5.1 Registre de la dette
|
||||
|
||||
| Cat. | Description | Fichier(s) | Impact | Effort |
|
||||
|------|-------------|-----------|--------|--------|
|
||||
| 🔴 | **Pipeline CD non fonctionnel** — secrets dans if conditions | `.github/workflows/cd.yml` | Pas de déploiement auto | S |
|
||||
| 🔴 | **Services Rust non intégrés** — gRPC stub, boot mode | Chat/Stream servers | 60% des features annoncées non disponibles | XL |
|
||||
| 🔴 | **Auth HLS/WebSocket cassée** — tokenStorage retourne null | Frontend services | Streaming/chat non fonctionnels | M |
|
||||
| 🔴 | **Versions Go incohérentes** — 1.24 (CI) vs 1.23 (Dockerfile) | CI + Dockerfile | Build divergence possible | S |
|
||||
| 🟠 | **Rate limiter in-memory** — ne scale pas | `ratelimit.go` | Sécurité dégradée en multi-instance | M |
|
||||
| 🟠 | **Postgres version incohérente** — 15 (test/hybrid) vs 16 (dev/prod) | docker-compose files | Tests passent sur mauvaise version | S |
|
||||
| 🟠 | **Code mort ~6,500+ LOC** — api_manager, docs.go, tokenStorage, commerceService mocks | Multiple | Confusion, maintenance inutile | M |
|
||||
| 🟠 | **22 fichiers Go >500 lignes** — track/handler.go (2,262), config.go (955) | Backend handlers | Complexité élevée, refactoring nécessaire | L |
|
||||
| 🟠 | **11 fichiers TS/TSX >500 lignes** — interceptors.ts (1,203), trackApi.ts (869) | Frontend services | Complexité élevée | L |
|
||||
| 🟠 | **`commerceService.ts` retourne des mocks en prod** — MOCK_PURCHASES, fake refund | `commerceService.ts` | Utilisateurs voient des fausses données | S |
|
||||
| 🟠 | **Migrations non transactionnelles** — SQL brut sans BEGIN/COMMIT | `migrations/*.sql` | État DB incohérent si migration échoue | M |
|
||||
| 🟡 | **90+ usages de `any` dans le frontend** (hors tests/generated) | Multiple .ts/.tsx | Perte de type safety | M |
|
||||
| 🟡 | **18 fichiers avec `console.log`** en production | Frontend src/ | Pollution console, pas de contrôle log level | S |
|
||||
| 🟡 | **15+ `fmt.Printf` dans le backend** | upload_validator, router | Bypass structured logging | S |
|
||||
| 🟡 | **gin.Logger() + gin.Recovery() en double** avec custom middleware | `main.go` + `router.go` | Double logging, double recovery | S |
|
||||
| 🟡 | **`gorilla/websocket` archivé** | `go.mod` | Plus de patches sécurité | M |
|
||||
| 🟡 | **chat-server sqlx-data.json vide** `{}` | `sqlx-data.json` | Builds offline impossibles | S |
|
||||
| 🟡 | **stream-server sqlx-data.json absent** | — | Builds offline impossibles | S |
|
||||
| ⚪ | **`APP_ENV` comparaison case-sensitive** — "Production" bypass | Multiple middleware | Risque théorique | S |
|
||||
| ⚪ | **Packages npm vides** — `packages/`, `fixtures/` | Monorepo config | Confusion | S |
|
||||
|
||||
### 5.2 Quantification
|
||||
|
||||
| Métrique | Go Backend | Rust Chat | Rust Stream | Frontend | Total |
|
||||
|----------|-----------|-----------|-------------|----------|-------|
|
||||
| **LOC source** | 87,930 | ~55,000 | ~72,000 | 130,976 | ~346,000 |
|
||||
| **LOC test** | 85,455 | ~5,000 | ~8,000 | 58,816 | ~157,000 |
|
||||
| **LOC stories** | — | — | — | 15,987 | 15,987 |
|
||||
| **Ratio test/code** | 0.97:1 | ~0.09:1 | ~0.11:1 | 0.45:1 | 0.45:1 |
|
||||
| **Fichiers source** | 402 | ~50 | ~80 | 1,275 | ~1,807 |
|
||||
| **Fichiers test** | 264 | ~28 | ~30 | 274 | ~596 |
|
||||
| **TODO/FIXME/HACK** | 20 | 5 | 5 | 8 | 38 |
|
||||
| **Fichiers >500 LOC** | 22 | ~10 | ~8 | 11 | ~51 |
|
||||
| **Code mort estimé** | ~6,500 | ~2,000 | ~1,000 | ~3,500 | ~13,000 |
|
||||
| **Dépendances directes** | 44 | ~25 | ~30 | ~45 | ~144 |
|
||||
|
||||
---
|
||||
|
||||
## 6️⃣ QUALITÉ ARCHITECTURALE
|
||||
|
||||
### 6.1 Monorepo
|
||||
|
||||
| Critère | Évaluation |
|
||||
|---------|------------|
|
||||
| **Outil** | Turborepo — adapté, bien configuré |
|
||||
| **Build orchestration** | `turbo run build` — parallélisable, pas de cache custom |
|
||||
| **Versioning** | Unifié par release scope (v0.101 → v0.402) — correct |
|
||||
| **Dépendances internes** | Aucune shared package (`packages/` vide) — chaque service est indépendant |
|
||||
| **Workspace** | npm workspaces (root) + Go workspace (`go.work`) — cohérent |
|
||||
| **Problème** | Rust non intégré dans Turborepo — builds Rust gérés séparément via Makefile |
|
||||
|
||||
### 6.2 Frontend React
|
||||
|
||||
| Critère | Score |
|
||||
|---------|-------|
|
||||
| **Structure** | Feature-based (excellent) — `features/*/pages/`, `features/*/components/`, `features/*/hooks/` |
|
||||
| **State management** | Zustand (client) + React Query (server) — pattern moderne et correct |
|
||||
| **Data fetching** | React Query v5 avec invalidation, prefetching, optimistic updates |
|
||||
| **Routing** | React Router 6 avec lazy loading, route guards, preloading |
|
||||
| **Design system** | SUMI v2.0 — tokens CSS centralisés, composants shadcn/ui adaptés |
|
||||
| **TypeScript** | Strict mode activé, `noUncheckedIndexedAccess: true` — rigoureux |
|
||||
| **Storybook** | 288 stories, decorators avec providers, MSW intégré — mature |
|
||||
| **Accessibilité** | Audit A11Y documenté (`A11Y_AUDIT.md`), ARIA via shadcn/ui |
|
||||
| **MSW vs API** | 100% MSW pour composants/stories. API réelle uniquement en E2E |
|
||||
| **Problème** | Interceptors.ts à 1,203 lignes — trop complexe, à découper |
|
||||
|
||||
### 6.3 Backend Go
|
||||
|
||||
| Critère | Score |
|
||||
|---------|-------|
|
||||
| **Architecture** | Clean architecture avec séparation claire : handler → service → repository | ✅ |
|
||||
| **Error handling** | Custom `apperrors` package, errors wrappées, codes d'erreur HTTP cohérents | ✅ |
|
||||
| **Middleware stack** | 23 middlewares — complet et bien ordonné (CORS → Auth → CSRF → Handler) | ✅ |
|
||||
| **Database** | GORM + PostgreSQL, migrations numérotées, connection pooling via GORM | ✅ |
|
||||
| **Concurrency** | Graceful shutdown, context propagation, semaphore uploads | ✅ |
|
||||
| **Configuration** | Env vars validées au démarrage, production checks, secret masking | ✅ |
|
||||
| **API versioning** | `/api/v1/` — consistant | ✅ |
|
||||
| **OpenAPI** | `openapi.yaml` (3,655 lignes) + Swagger UI (dev only) | ✅ |
|
||||
| **Problème** | `track/handler.go` à 2,262 lignes — **urgent à découper** |
|
||||
|
||||
### 6.4 Services Rust
|
||||
|
||||
| Critère | Évaluation |
|
||||
|---------|------------|
|
||||
| **Chat server** | Architecture hub-based, Tokio runtime, WebSocket handler complet | Bien conçu |
|
||||
| **Stream server** | Transcoding engine, HLS segmenter, sync audio | Ambitieux |
|
||||
| **Compilation** | Compilent sans erreur (selon audit interne) | OK |
|
||||
| **Error handling** | anyhow + thiserror, propagation via `?` | Correct |
|
||||
| **Problème critique** | **Non intégrés au système** — boot mode OFF, gRPC stub, pas de tests d'intégration cross-service |
|
||||
| **Problème** | `unwrap()` en production : ~30 (chat), ~50 (stream) — certains dans des chemins critiques (rate_limiter, websocket handler) |
|
||||
| **Justification Go + Rust** | **Questionnable** pour cette taille d'équipe. Le chat server pourrait être un service Go avec gorilla/websocket. Le stream server est le seul cas justifiable (transcoding audio, performance). Le coût de maintenance de 3 langages est disproportionné. |
|
||||
|
||||
### 6.5 Base de données
|
||||
|
||||
| Critère | Évaluation |
|
||||
|---------|------------|
|
||||
| **Schéma** | 66 migrations backend, 10 chat, 2 stream — riche |
|
||||
| **Indexes** | pg_trgm pour recherche fuzzy, composite indexes, performance indexes |
|
||||
| **Extensions** | uuid-ossp, pg_trgm (migration 086) |
|
||||
| **FK constraints** | Migration 930 ajoute les FK manquantes — correction tardive |
|
||||
| **Audit triggers** | Migration 053, 910 — audit trail en DB |
|
||||
| **Problème** | Numérotation gaps (001→010→020, 069→070, 087→088→089→...→099→100→101→102, 900→910→920→930→931) — difficile à suivre |
|
||||
| **Problème** | Migration 100 fait 3 lignes (`ALTER TABLE orders ADD COLUMN discount_amount...`) — fragmentation excessive |
|
||||
| **Problème** | Pas de consolidation des 66 migrations — temps de setup initial long |
|
||||
| **Redis** | Cache, CSRF tokens, presence, trending, rate limiting (potentiel). Pas de TTL documenté systématiquement |
|
||||
|
||||
### 6.6 Scorecard
|
||||
|
||||
| Dimension | Score /10 | Justification |
|
||||
|-----------|-----------|---------------|
|
||||
| **Architecture** | **7/10** | Séparation claire des responsabilités, patterns modernes (feature-based frontend, clean arch backend). Perd des points : services Rust non intégrés, interceptors.ts monolithique, 3 langages pour une petite équipe. |
|
||||
| **Maintenabilité** | **6/10** | Code bien structuré mais 51 fichiers >500 LOC, 13K LOC de code mort, conventions parfois incohérentes (fmt.Printf vs logger). Documentation extensive mais parfois contradictoire. |
|
||||
| **Sécurité** | **5/10** | Bonnes bases (httpOnly cookies, bcrypt 12, CSRF, CSP, HSTS, secret masking). Perd des points : IDOR upload, rate limiter mémoire, Redis sans auth prod, auth HLS/WS cassée, CD pipeline mort, pprof enabled. |
|
||||
| **Scalabilité** | **4/10** | PostgreSQL single-instance, Redis SPOF, rate limiter mémoire, pas de load balancer config, WebSocket sticky sessions non gérées. Architecture permet le scaling théorique mais rien n'est configuré. |
|
||||
| **Testabilité** | **7/10** | Ratio test/code Go excellent (0.97:1), frontend correct (0.45:1), Storybook mature (288 stories). Perd des points : Rust quasi non testé, 100% MSW (aucun test composant contre API réelle), tests E2E fragiles (docker-compose full stack). |
|
||||
| **Opérabilité** | **3/10** | Pipeline CD non fonctionnel, staging incomplet (pas de chat/stream), Prometheus sans alerting, Grafana password admin, Redis sans auth. Perd beaucoup de points : impossible de déployer en production de manière fiable aujourd'hui. |
|
||||
| **Vélocité dev** | **6/10** | Storybook-first, MSW handlers, bonne documentation. Un dev React serait productif en <1 semaine. Un dev Go en ~2 semaines. Un dev Rust en 3+ semaines (code complexe, non documenté inline). |
|
||||
| **Maturité produit** | **3/10** | 14 features véritablement fonctionnelles E2E sur 600 annoncées (2.3%). 83/190 Tier 0 selon audit interne (44%). Score interne 32/100. Écart significatif entre documentation et réalité. |
|
||||
|
||||
---
|
||||
|
||||
## 7️⃣ INFRA & DEVOPS
|
||||
|
||||
### 7.1 Docker
|
||||
|
||||
| Critère | Résultat |
|
||||
|---------|---------|
|
||||
| **Dockerfiles** | Multi-stage, alpine, non-root — bien fait |
|
||||
| **docker-compose** | 5 fichiers (dev, prod, staging, test, hybrid) — trop, créent de la confusion |
|
||||
| **Secrets** | Env vars partout (pas de Docker secrets) — risque élevé en prod |
|
||||
| **Health checks** | Backend et Redis — OK. Frontend, Prometheus, Grafana — absents |
|
||||
| **Volumes** | Données persistées pour DB, Redis, RabbitMQ — OK |
|
||||
| **Réseau** | Prod: subnet /16 (trop large). Hybrid: host mode (aucune isolation) |
|
||||
| **Images** | ClamAV, Prometheus, Grafana en `latest` — non reproductible |
|
||||
|
||||
### 7.2 CI/CD
|
||||
|
||||
| Critère | Résultat |
|
||||
|---------|---------|
|
||||
| **Pipeline CI** | 12 workflows — couverture large mais incohérent (Go 1.23 vs 1.24) |
|
||||
| **Tests en CI** | Go tests + frontend tests + E2E — bonne couverture |
|
||||
| **Linting** | ESLint frontend. **Manque :** `go vet`, `gofmt`, `clippy` en CI |
|
||||
| **Security scanning** | Gitleaks uniquement. **Manque :** SAST (CodeQL), container scanning, DAST |
|
||||
| **Build** | Docker build en CI — oui, mais utilise le mauvais Dockerfile (dev au lieu de prod) |
|
||||
| **Deployment** | CD pipeline existe mais **ne fonctionne pas** (conditions secrets jamais vraies) |
|
||||
| **Environments** | Dev/staging/prod séparés en théorie. Staging manque chat/stream servers |
|
||||
| **Secrets management** | Hardcodés dans workflow files. Pas de vault. |
|
||||
|
||||
### 7.3 Reproductibilité
|
||||
|
||||
| Critère | Résultat |
|
||||
|---------|---------|
|
||||
| **Build one-command** | `docker compose up` pour dev — oui, fonctionnel |
|
||||
| **Onboarding** | Pas de ONBOARDING.md dédié. `.env.example` existe. README basique |
|
||||
| **Versions lockées** | `rust-toolchain.toml` (stable, pas de version), `go.work` (1.24), pas de `.nvmrc` |
|
||||
| **Lock files** | `go.sum` ✅, `Cargo.lock` ✅, `package-lock.json` ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 8️⃣ PERFORMANCE & SCALABILITÉ
|
||||
|
||||
| Composant | Risque | Seuil estimé | Mitigation |
|
||||
|-----------|--------|-------------|------------|
|
||||
| **PostgreSQL** | N+1 queries (GORM), full table scans possibles | >10K req/min | Indexes pg_trgm, composite indexes présents |
|
||||
| **Redis** | **SPOF** — CSRF, cache, presence, trending dépendent de Redis | Si Redis down : CSRF → 503, presence → stale, cache miss | Aucun fallback implémenté |
|
||||
| **Chat server** | WebSocket concurrentes, broadcast fan-out | >1000 connexions | Hub-based architecture, mais non testé sous charge réelle |
|
||||
| **Stream server** | Transcoding CPU-intensive, HLS segment serving | >100 streams simultanés | Semaphore pour limiter concurrence (bon) |
|
||||
| **File storage** | Stockage local par défaut, S3 optionnel | >10TB | S3 service implémenté mais non configuré par défaut |
|
||||
| **API Gateway** | Single instance, pas de load balancer configuré | >1000 req/s | HAProxy en prod compose mais config minimale |
|
||||
|
||||
**Scalabilité horizontale :** Le backend Go est stateless (sessions en DB, CSRF en Redis) — scalable horizontalement SI rate limiter migré vers Redis. Les services Rust ont des connexions WebSocket qui nécessitent sticky sessions ou Redis pub/sub pour broadcasting multi-instance.
|
||||
|
||||
---
|
||||
|
||||
## 9️⃣ RISQUES BUSINESS
|
||||
|
||||
### 9.1 Point de vue CTO
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Recrutement productif <2 semaines ? | **Oui pour React** (Storybook-first, bonne doc, patterns standards). **Oui pour Go** (clean architecture, tests abondants). **Non pour Rust** (code complexe, non documenté, non intégré). |
|
||||
| Vélocité soutenable ? | **Non.** 12 releases en ~3 mois avec 345+ features déclarées = ~3 features/jour. L'audit interne confirme que seules 21.5% sont réellement fonctionnelles. La vélocité est une vélocité de code, pas de produit. |
|
||||
| Dette technique explosive ? | **Oui si le rythme continue.** 13K LOC de code mort, 51 fichiers >500 lignes, features fantômes documentées comme "operational". La divergence doc/réalité va s'aggraver. |
|
||||
| Refactorings inévitables ? | 1) Intégrer ou abandonner les services Rust. 2) Migrer rate limiter vers Redis. 3) Fixer le pipeline CD. 4) Consolider les migrations. |
|
||||
| Go + Rust + React justifié ? | **Partiellement.** Go + React = justifié et bien exécuté. Rust stream server = justifiable (audio transcoding). Rust chat server = **injustifié** — un service Go avec gorilla/websocket ferait le même travail avec une maintenance unifiée. |
|
||||
|
||||
### 9.2 Point de vue investisseur
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Produit fonctionnel ou démo ? | **Entre les deux.** 14 features fonctionnelles E2E constituent un MVP viable (auth, upload, tracks, playlists, marketplace). Mais les features différenciantes (streaming HLS, chat temps réel, WebRTC) sont non fonctionnelles. C'est un CMS audio avec marketplace, pas une plateforme de streaming. |
|
||||
| Risques sécurité publics ? | **Oui.** Redis sans auth en prod, IDOR sur uploads, pipeline CD mort, auth streaming cassée. Un audit de sécurité professionnel est nécessaire avant tout lancement. |
|
||||
| Code repris par une autre équipe ? | **Oui.** Le code Go et React est propre, bien structuré, avec de bons tests. Le Rust est plus risqué (non intégré, peu documenté). Un onboarding de 3-4 semaines est réaliste pour une équipe de 3 devs (1 Go, 1 React, 1 Rust/infra). |
|
||||
| Coût v1.0 production-ready ? | **3-4 mois, 2-3 développeurs** (estimation basée sur : fixer sécurité 2 semaines, stabiliser services Rust 4 semaines, fixer CD/infra 2 semaines, tests E2E complets 2 semaines, polish UX 2 semaines). |
|
||||
| IP technique défendable ? | **Limitée.** Architecture standard (Go API + React SPA), pas d'algorithme propriétaire, pas de technologie unique. La valeur est dans l'exécution (qualité du code, design system SUMI, couverture tests) plutôt que dans l'innovation technique. |
|
||||
| Ratio features/qualité ? | **Red flag modéré.** La quantité (345 features déclarées) masque la qualité (21.5% fonctionnelles). Mais les features qui fonctionnent sont bien implémentées avec des tests. C'est un problème de scope control, pas de compétence. |
|
||||
|
||||
### 9.3 Point de vue acquéreur
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Code réutilisable ? | **Oui à 70%.** Backend Go et frontend React sont réutilisables. Services Rust = à réévaluer (garder stream, réécrire chat en Go). Infra = à refaire proprement. |
|
||||
| Données migrables ? | **Oui.** PostgreSQL standard, schéma normalisé, migrations numérotées. Export/import straightforward. |
|
||||
| Vendor-lock ? | **Faible.** Hyperswitch (paiement) est un choix moins mainstream que Stripe mais l'interface est abstraite. Pas de lock cloud (S3 compatible). |
|
||||
| Onboarding 5 devs ? | **4-6 semaines.** 2 semaines pour Go/React (bien documenté), 4 semaines pour Rust + infra (complexe, non documenté). |
|
||||
| Score rachetabilité ? | **6/10.** Code propre et testable, architecture saine, stack mainstream. Perd des points : 3 langages, services non intégrés, écart doc/réalité, dette infra. |
|
||||
|
||||
### 9.4 Verdict
|
||||
|
||||
| Question | Réponse | Justification |
|
||||
|----------|---------|---------------|
|
||||
| Lancer en production tel quel ? | **Non** | CD pipeline mort, Redis sans auth, auth streaming cassée, features fantômes |
|
||||
| Vendre / monétiser tel quel ? | **Non** | Checkout en mode test, webhook paiement non vérifié, features commerciales (streaming, chat) non fonctionnelles |
|
||||
| Maintenir avec 2 devs ? | **Conditionnel** | Oui si on abandonne les services Rust et se concentre sur Go + React. Non si on veut tout maintenir. |
|
||||
| Refactorer avant prod ? | **Oui** | Sécurité (2 semaines) + infra (2 semaines) + intégration services (4 semaines) minimum |
|
||||
| Réécrire certains services ? | **Oui** | Chat server Rust → service Go. Le stream server Rust peut être conservé mais doit être intégré. |
|
||||
| Vélocité = red flag ? | **Oui, modéré** | 345 features déclarées en ~3 mois avec un écart de 78% entre déclaré et fonctionnel suggère une optimisation pour les métriques plutôt que pour la valeur produit. Mais le code qui existe est de qualité correcte — ce n'est pas du "feature stuffing" de basse qualité. |
|
||||
|
||||
---
|
||||
|
||||
## 🔟 PLAN D'ACTION PRIORISÉ
|
||||
|
||||
### Phase 1 — Critique (semaines 1-2) — Sécurité & CI/CD
|
||||
|
||||
| # | Quoi | Pourquoi | Fichiers | Effort |
|
||||
|---|------|----------|----------|--------|
|
||||
| 1 | **Fixer pipeline CD** — remplacer `secrets.*` par `vars.*` dans les `if`, utiliser `Dockerfile.production`, ajouter `needs: ci` | Aucun déploiement ne fonctionne | `.github/workflows/cd.yml` | S |
|
||||
| 2 | **Redis auth en production** — ajouter `--requirepass`, configurer `REDIS_PASSWORD` | Cache/CSRF compromettable | `docker-compose.prod.yml` | S |
|
||||
| 3 | **Ajouter `JWT_SECRET` au stream-server** prod compose | Service potentiellement sans auth | `docker-compose.prod.yml:217` | S |
|
||||
| 4 | **Supprimer `docker-compose.hybrid.yml`** ou fixer network_mode | Infrastructure ouverte au réseau | `docker-compose.hybrid.yml` | S |
|
||||
| 5 | **Fixer auth HLS/WebSocket** — implémenter cookie-based auth ou stream token endpoint | Streaming non protégé | `hlsService.ts`, `websocket.ts`, backend `/auth/stream-token` | M |
|
||||
| 6 | **Unifier version Go** — 1.24 partout (go.mod, CI, Dockerfile) | Builds divergents | `go.mod`, `ci.yml`, `backend-ci.yml`, `Dockerfile.production` | S |
|
||||
| 7 | **Migrer secrets CI vers GitHub Secrets** | Credentials en clair dans le repo | `.github/workflows/ci.yml` | S |
|
||||
| 8 | **Fixer IDOR GetUploadStatus** — ajouter ownership check | Fuite d'information | `internal/handlers/upload.go:308` | S |
|
||||
| 9 | **Ajouter validation SSRF webhooks** — whitelist schéma, bloquer IPs privées | SSRF via webhook delivery | `webhook_handlers.go`, webhook delivery service | S |
|
||||
| 10 | **Vérifier webhook Hyperswitch** — signature HMAC-SHA256 | Paiements potentiellement non vérifiés | Handler webhook paiement (à localiser/créer) | M |
|
||||
|
||||
### Phase 2 — Stabilisation (semaines 3-6)
|
||||
|
||||
| # | Quoi | Pourquoi | Effort |
|
||||
|---|------|----------|--------|
|
||||
| 11 | **Migrer rate limiter vers Redis** | Sécurité multi-instance | M |
|
||||
| 12 | **Aligner Postgres 16 partout** (test, hybrid) | Tests sur mauvaise version | S |
|
||||
| 13 | **Compléter staging compose** (chat, stream, reverse proxy) | Staging ne reflète pas la prod | M |
|
||||
| 14 | **Ajouter alerting Prometheus** (service down, error rate, latence) | Monitoring sans alerting = inutile | M |
|
||||
| 15 | **Supprimer code mort** (~13K LOC) | Confusion, maintenance inutile | M |
|
||||
| 16 | **Supprimer/corriger commerceService mocks** | Données factices en production | S |
|
||||
| 17 | **Ajouter `go vet`, `clippy`, `gofmt` en CI** | Qualité code non vérifiée en CI | S |
|
||||
| 18 | **Remplacer `fmt.Printf` par logger structuré** (15+ occurrences) | Fuite d'info, bypass logging | S |
|
||||
| 19 | **Ajouter SAST en CI** (CodeQL ou Semgrep) | Vulnérabilités non détectées automatiquement | M |
|
||||
| 20 | **Fixer `frontend-ci.yml`** — ajouter lint, typecheck, build | PRs frontend sans vérification | S |
|
||||
|
||||
### Phase 3 — Consolidation (semaines 7-12)
|
||||
|
||||
| # | Quoi | Pourquoi | Effort |
|
||||
|---|------|----------|--------|
|
||||
| 21 | **Intégrer ou abandonner le chat server Rust** | Service non connecté, coût de maintenance | XL |
|
||||
| 22 | **Intégrer le stream server** — connecter gRPC, activer HLS | Feature différenciante non fonctionnelle | XL |
|
||||
| 23 | **Découper fichiers >1000 LOC** (track/handler.go, interceptors.ts, config.go) | Complexité maintenance | L |
|
||||
| 24 | **Consolider migrations** — squash 66 migrations en baseline | Setup initial long | L |
|
||||
| 25 | **Éliminer 90+ `any` dans le frontend** | Type safety dégradée | M |
|
||||
| 26 | **Remplacer `gorilla/websocket`** (archivé) | Plus de patches sécurité | M |
|
||||
| 27 | **Ajouter tests d'intégration cross-service** | Services jamais testés ensemble | L |
|
||||
| 28 | **Mettre en place Docker secrets** pour la prod | Secrets dans env vars | M |
|
||||
| 29 | **Aligner FEATURE_STATUS avec la réalité** | Écart doc/code = perte de confiance | S |
|
||||
| 30 | **Implémenter hash des reset tokens** | Sécurité en cas de compromission DB | S |
|
||||
|
||||
### Phase 4 — Évolution (mois 4+)
|
||||
|
||||
- Activer Hyperswitch en mode production
|
||||
- Implémenter payout (Stripe Connect — v0.403)
|
||||
- Compléter analytics (7% → 50%+)
|
||||
- Implémenter social (13% → 50%+)
|
||||
- Évaluer migration React 19
|
||||
- Considérer réécriture chat server en Go
|
||||
- Mettre en place blue-green deployment
|
||||
- Ajouter container image scanning en CI
|
||||
- Implémenter IaC (Terraform/Pulumi)
|
||||
|
||||
---
|
||||
|
||||
## ANNEXES
|
||||
|
||||
### A. Arbre des dépendances inter-services
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ Frontend │
|
||||
│ React/Vite │
|
||||
└──────┬───────┘
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Backend Go │ │Chat Rust │ │Stream │
|
||||
│ (API REST) │ │(WebSocket│ │Rust (HLS)│
|
||||
└─────┬──────┘ └────┬─────┘ └────┬─────┘
|
||||
│ │ │
|
||||
┌─────┼─────┐ ┌────┘ ┌───┘
|
||||
│ │ │ │ │
|
||||
▼ ▼ ▼ ▼ ▼
|
||||
┌────┐ ┌────┐ ┌─────┐ ┌───────────┐
|
||||
│ PG │ │Redis│ │Rabbit│ │ PG (chat) │
|
||||
└────┘ └────┘ └─────┘ └───────────┘
|
||||
|
||||
Légende:
|
||||
──── = Connexion fonctionnelle
|
||||
- - - = Connexion prévue mais non connectée (gRPC stub)
|
||||
```
|
||||
|
||||
### B. Métriques brutes
|
||||
|
||||
```
|
||||
Total LOC (source + test + stories) : ~519,000
|
||||
Total fichiers source : ~2,400
|
||||
Total fichiers test : ~596
|
||||
Total stories : 288
|
||||
Total migrations SQL : 78
|
||||
Total workflows CI : 12
|
||||
Total scripts : 85+
|
||||
Total docs markdown : 332
|
||||
Total dépendances directes : ~144
|
||||
```
|
||||
|
||||
### C. Fichiers critiques à auditer en priorité
|
||||
|
||||
1. `veza-backend-api/internal/middleware/auth.go` (704 LOC)
|
||||
2. `veza-backend-api/internal/middleware/ratelimit.go` (189 LOC)
|
||||
3. `veza-backend-api/internal/config/config.go` (955 LOC)
|
||||
4. `apps/web/src/services/api/interceptors.ts` (1,203 LOC)
|
||||
5. `apps/web/src/services/tokenStorage.ts` (107 LOC)
|
||||
6. `.github/workflows/cd.yml` (170 LOC)
|
||||
7. `docker-compose.prod.yml` (301 LOC)
|
||||
8. `veza-backend-api/internal/handlers/upload.go` (627 LOC)
|
||||
|
||||
---
|
||||
|
||||
## CONCLUSION STRATÉGIQUE
|
||||
|
||||
Veza est un projet techniquement compétent dans son exécution Go/React, mais souffrant d'un **excès d'ambition architecturale** par rapport à ses ressources. Le choix de trois langages (Go, Rust, TypeScript) pour un MVP crée une charge de maintenance disproportionnée. Les services Rust, bien que compilables, ne sont pas intégrés au système et représentent ~132K LOC de code non productif.
|
||||
|
||||
**La recommandation stratégique est : investir, mais avec recadrage.**
|
||||
|
||||
Le code Go et React constitue une base solide et testée. La sécurité auth (httpOnly cookies, JWT 5min, bcrypt 12) est supérieure à la moyenne des startups early-stage. Le design system SUMI et l'approche Storybook-first démontrent une maturité UX réelle.
|
||||
|
||||
Cependant, l'écart entre le narratif (345+ features, 12 releases) et la réalité (14 features E2E, score interne 32/100) est un signal d'alarme pour un investisseur. Ce n'est pas un signe de mauvaise foi technique — le code qui existe est de qualité — mais d'un scope management déficient et d'une communication produit trop optimiste.
|
||||
|
||||
**Avec 4-6 semaines de stabilisation ciblée et un recadrage stratégique (abandonner le chat Rust, intégrer le stream server, fixer l'infra), Veza peut devenir un MVP commercialisable.** Sans ce recadrage, la dette technique et l'écart doc/réalité continueront de croître, rendant le produit de plus en plus difficile à maintenir et à vendre.
|
||||
|
||||
**Verdict final : Investir sous condition de recadrage technique et produit dans les 60 jours.**
|
||||
913
AUDIT_TECHNIQUE_VEZA_2026-03-04.md
Normal file
913
AUDIT_TECHNIQUE_VEZA_2026-03-04.md
Normal file
|
|
@ -0,0 +1,913 @@
|
|||
# AUDIT TECHNIQUE — VEZA MONOREPO
|
||||
|
||||
| Champ | Valeur |
|
||||
|-------|--------|
|
||||
| **Date** | 2026-03-04 |
|
||||
| **Auditeur** | Claude (Architecte IA) — Audit indépendant pour due diligence |
|
||||
| **Version analysée** | v1.0.2 (commit `a007f4c7`, 2026-03-03) |
|
||||
| **Périmètre** | Backend Go, Stream Server Rust, Frontend React, Infrastructure |
|
||||
| **Méthodologie** | Analyse statique exhaustive du code source, 6 passes |
|
||||
| **Classification** | Confidentiel — Usage interne |
|
||||
|
||||
---
|
||||
|
||||
## EXECUTIVE SUMMARY
|
||||
|
||||
### Verdict global
|
||||
|
||||
Veza est un projet techniquement **ambitieux et structurellement solide** pour un projet de cette taille, mais qui présente des **risques critiques** empêchant un déploiement production immédiat sans correction. Le codebase totalise **~610K LOC** réparties sur 3 langages (Go, Rust, TypeScript), ce qui est considérable et soulève des questions de maintenabilité pour une petite équipe.
|
||||
|
||||
### Recommandation : GO CONDITIONNEL
|
||||
|
||||
Le projet peut être lancé en production **après correction des 6 vulnérabilités critiques identifiées** (estimé 2-3 semaines). Le socle architectural est sain, les bonnes pratiques de sécurité sont globalement respectées, et l'infrastructure de déploiement (Docker, CI/CD, blue-green) est mature.
|
||||
|
||||
### Top 5 des risques
|
||||
|
||||
| # | Risque | Gravité | Impact |
|
||||
|---|--------|---------|--------|
|
||||
| 1 | **340+ `unwrap()`/`expect()` dans le stream server Rust** — crash en production sous charge | Critique | Indisponibilité du streaming audio |
|
||||
| 2 | **Secret JWT par défaut dans `veza-common`** — fallback exploitable si env var manquante | Critique | Compromission de tous les tokens |
|
||||
| 3 | **Webhook worker non enregistré dans le shutdown manager** — fuite de goroutine | Elevée | Perte de webhooks au redéploiement |
|
||||
| 4 | **Incohérence politique mot de passe** frontend (8 chars) vs backend (12 chars) | Moyenne | UX dégradée, faux sentiment de sécurité |
|
||||
| 5 | **~500 LOC de code WebRTC mort** avec dépendance commentée | Moyenne | Dette technique, confusion |
|
||||
|
||||
### Top 5 des forces
|
||||
|
||||
| # | Force | Preuve |
|
||||
|---|-------|--------|
|
||||
| 1 | **Architecture backend propre** — séparation handler → service → repository | `veza-backend-api/internal/` |
|
||||
| 2 | **Sécurité auth solide** — httpOnly cookies, JWT HS256, bcrypt cost 12, rate limiting multi-couche | `middleware/auth.go`, `security_headers.go` |
|
||||
| 3 | **CI/CD mature** — 14 workflows GitHub Actions couvrant govulncheck, cargo audit, npm audit, E2E | `.github/workflows/` |
|
||||
| 4 | **Storybook-first** — 300 stories avec MSW, couverture des états (loading, error, empty) | `apps/web/src/**/*.stories.tsx` |
|
||||
| 5 | **Infrastructure production-ready** — blue-green HAProxy, health checks, resource limits, monitoring | `docker-compose.prod.yml` |
|
||||
|
||||
### Scorecard rapide
|
||||
|
||||
| Dimension | Score |
|
||||
|-----------|-------|
|
||||
| Architecture | 7/10 |
|
||||
| Maintenabilité | 5/10 |
|
||||
| Sécurité | 7/10 |
|
||||
| Scalabilité | 5/10 |
|
||||
| Testabilité | 7/10 |
|
||||
| Opérabilité | 7/10 |
|
||||
| Vélocité dev | 4/10 |
|
||||
| Maturité produit | 6/10 |
|
||||
|
||||
---
|
||||
|
||||
## 1. CARTOGRAPHIE GLOBALE
|
||||
|
||||
### 1.1 Stack réelle
|
||||
|
||||
| Élément | Version / Détail | Statut |
|
||||
|---------|-----------------|--------|
|
||||
| **Go** | 1.24.0 (`go.mod`) | Courant (released Feb 2025) |
|
||||
| **Rust** | edition 2021, stable (pas de `rust-toolchain.toml`) | Courant |
|
||||
| **Node.js** | 20 (CI `setup-node`), pas de `.nvmrc`/`.node-version` | LTS courant |
|
||||
| **TypeScript** | 5.9.3 | Courant |
|
||||
| **React** | ^18.2.0 | Stable (React 19 disponible mais non adopté) |
|
||||
| **Vite** | ^7.1.5 | Courant |
|
||||
| **Gin** | 1.11.0 | Courant |
|
||||
| **GORM** | 1.30.0 | Courant |
|
||||
| **Axum** | 0.8 | Courant |
|
||||
| **SQLx** | 0.8 | Courant |
|
||||
| **PostgreSQL** | 16-alpine | Courant |
|
||||
| **Redis** | 7-alpine | Courant |
|
||||
| **RabbitMQ** | 3-management-alpine | Courant |
|
||||
| **Hyperswitch** | 2025.01.21.0-standalone | ~1 an de retard |
|
||||
| **HAProxy** | 2.8-alpine | Courant LTS |
|
||||
| **Prometheus** | (Rust) 0.14 / (Go) client_golang 1.22.0 | Courant |
|
||||
| **JWT** | golang-jwt/v5 5.3.0 (Go) / jsonwebtoken 10 (Rust) | Courant |
|
||||
| **Bcrypt** | golang.org/x/crypto (Go) / bcrypt 0.17 (Rust) | Courant |
|
||||
| **Stripe** | stripe-go/v82 82.5.1 (backup/fallback) | Courant |
|
||||
| **AWS SDK** | aws-sdk-go-v2 1.41.0 | Courant |
|
||||
| **HLS** | FFmpeg (externe) + m3u8-rs 5.0 + hls.js 1.6.14 (frontend) | Réel |
|
||||
| **WebRTC** | Code présent mais `webrtc = "0.7"` commenté dans Cargo.toml | **Non fonctionnel** |
|
||||
| **WebSocket** | coder/websocket 1.8.14 (Go) / tokio-tungstenite 0.21 (Rust) | Fonctionnel |
|
||||
| **Storybook** | 8.6.15 | Courant |
|
||||
| **MSW** | 2.11.2 | Courant |
|
||||
| **Playwright** | 1.58.2 | Courant |
|
||||
| **Vitest** | 3.2.4 | Courant |
|
||||
| **Tailwind CSS** | ^4.0.0 | Courant |
|
||||
| **Sentry** | sentry-go 0.40.0 (backend) / @sentry/react ^10.32.1 | Courant |
|
||||
| **ClamAV** | clamav/clamav:latest | Intégré pour scan antivirus uploads |
|
||||
|
||||
**Constat** : Les dépendances sont globalement à jour. Seul Hyperswitch (~1 an de retard) mérite une mise à jour. Pas de dépendances abandonnées identifiées.
|
||||
|
||||
### 1.2 Organisation du monorepo
|
||||
|
||||
**Outil de gestion** : npm workspaces + Turborepo (`turbo.json`). Configuration minimale — 3 tâches (build, test, lint) avec dépendances `^build`. Pas de cache remote configuré.
|
||||
|
||||
| Répertoire | Rôle réel | Fichiers | LOC | Couplage |
|
||||
|-----------|-----------|----------|-----|----------|
|
||||
| `veza-backend-api/` | API REST Go (Gin, GORM, JWT) — coeur métier | 778 .go | ~188K | PostgreSQL, Redis, RabbitMQ, S3 |
|
||||
| `veza-stream-server/` | Serveur streaming Rust (Axum, HLS, FFmpeg) | 138 .rs | ~103K | PostgreSQL, Redis, RabbitMQ |
|
||||
| `veza-common/` | Bibliothèque Rust partagée (auth, config, types) | 53 .rs | ~4.3K | Utilisé par stream-server uniquement |
|
||||
| `apps/web/` | Frontend React SPA (Vite, Zustand, TanStack Query) | 1887 .ts/.tsx | ~211K | API backend via Axios, WebSocket |
|
||||
| `packages/design-system/` | Package partagé design system | ~10 | ~500 | Utilisé par apps/web |
|
||||
| `fixtures/` | Fixtures de test (npm package) | ~20 | ~1K | Cross-service test data |
|
||||
| `config/` | Configs déploiement (HAProxy, Prometheus, Grafana, Caddy, SSL) | ~30 | ~2K | Infrastructure |
|
||||
| `k8s/` | Manifestes Kubernetes (10+ sous-dossiers) | ~40 yaml | ~3K | Non déployé (templates) |
|
||||
| `docs/` | Documentation projet (285 fichiers .md) | 285 | ~15K | Référence |
|
||||
| `loadtests/` | Tests de charge k6 (backend, stream, chat) | ~15 | ~2K | Backend, Stream |
|
||||
| `.github/workflows/` | CI/CD (14 workflows) | 14 | ~600 | Tous services |
|
||||
| `proto/` | Définitions Protocol Buffers | ~5 | ~200 | Stream server (gRPC) |
|
||||
| `scripts/` | Scripts utilitaires (déploiement, dev, monitoring) | ~25 | ~1.5K | Tous services |
|
||||
| `make/` | Modules Makefile (config, dev, build, test, services) | 11 | ~800 | Orchestration |
|
||||
|
||||
**Total estimé** : ~319K LOC de code source (hors node_modules, target, dist, docs).
|
||||
|
||||
#### Packages orphelins et fantômes
|
||||
|
||||
| Répertoire | Statut | Détail |
|
||||
|-----------|--------|--------|
|
||||
| `chat_exports/` | Orphelin | 15 fichiers, artefacts de chat export. Aucune référence dans le code. |
|
||||
| `sub_task_agents/` | Orphelin | 15 fichiers de documentation IA. Aucun code exécutable. |
|
||||
| `tmt/` | Orphelin | 18 fichiers de config test management. Pas d'intégration CI. |
|
||||
| `full_veza_audit_data/` | Orphelin | 7 fichiers de données d'audit passé. |
|
||||
| `veza-docs/` | Partiellement orphelin | Site Docusaurus non intégré dans les workflows CI. |
|
||||
| `k8s/chat-server/` | Fantôme | Manifestes pour `veza-chat-server` supprimé en v0.502. Image Docker inexistante. |
|
||||
| `Makefile.old` | Mort | Ancien Makefile remplacé par `make/*.mk`. |
|
||||
|
||||
**Duplications cross-packages** : Les types d'authentification sont définis séparément dans Go (`internal/services/jwt_service.go`), Rust (`veza-common/src/auth.rs`), et TypeScript (`src/types/generated/api.ts`). Le TypeScript est généré depuis l'OpenAPI spec, mais Go et Rust sont indépendants — source d'incohérences (cf. mismatch issuer/audience Section 4).
|
||||
|
||||
**Dépendances circulaires** : Aucune détectée. Le flux de dépendances est unidirectionnel : `veza-common` -> `veza-stream-server` ; `apps/web` -> API backend (HTTP).
|
||||
|
||||
### 1.3 Dépendances critiques
|
||||
|
||||
| Service | Deps directes | Vulnérabilités CI | Deps > 2 ans sans release |
|
||||
|---------|--------------|-------------------|---------------------------|
|
||||
| Go backend | 45 directes + 116 indirectes = 161 | `govulncheck` en CI — 0 identifié | Aucune |
|
||||
| Rust stream | ~78 directes | `cargo audit` en CI — 0 identifié | `dotenv 0.15` (last: 2020, remplacé par `dotenvy`) |
|
||||
| Frontend | 35 deps + 37 devDeps = 72 | `npm audit --audit-level=critical` en CI | Aucune |
|
||||
|
||||
**Dépendance préoccupante** : `dotenv 0.15` dans le stream server Rust est maintenu par un nouveau mainteneur mais la version 0.15 date de 2020. Recommandation : migrer vers `dotenvy`.
|
||||
|
||||
### 1.4 Schéma des flux
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph client [Client Browser]
|
||||
ReactApp["React SPA<br/>Vite + HLS.js"]
|
||||
end
|
||||
|
||||
subgraph proxy [Reverse Proxy]
|
||||
HAProxy["HAProxy 2.8<br/>Blue-Green"]
|
||||
end
|
||||
|
||||
subgraph backend [Backend API - Go]
|
||||
GinRouter["Gin Router<br/>Middleware Chain"]
|
||||
AuthSvc["Auth Service<br/>JWT HS256"]
|
||||
TrackSvc["Track Service"]
|
||||
MarketSvc["Marketplace Service"]
|
||||
SocialSvc["Social Service"]
|
||||
ChatWS["Chat WebSocket<br/>Hub Pattern"]
|
||||
UploadSvc["Upload Service<br/>ClamAV Scan"]
|
||||
end
|
||||
|
||||
subgraph stream [Stream Server - Rust]
|
||||
AxumRouter["Axum Router"]
|
||||
HLSGen["HLS Generator<br/>FFmpeg"]
|
||||
TranscodeEng["Transcoding Engine"]
|
||||
StreamWS["Stream WebSocket"]
|
||||
end
|
||||
|
||||
subgraph data [Data Layer]
|
||||
PostgreSQL["PostgreSQL 16"]
|
||||
Redis["Redis 7"]
|
||||
RabbitMQ["RabbitMQ 3"]
|
||||
MinIO["MinIO / S3"]
|
||||
ClamAV["ClamAV"]
|
||||
end
|
||||
|
||||
subgraph payments [Payment]
|
||||
Hyperswitch["Hyperswitch Router"]
|
||||
HyperswitchDB["Hyperswitch PostgreSQL"]
|
||||
end
|
||||
|
||||
ReactApp -->|"HTTPS"| HAProxy
|
||||
HAProxy -->|"/api/v1/*"| GinRouter
|
||||
HAProxy -->|"/stream/*"| AxumRouter
|
||||
|
||||
GinRouter --> AuthSvc
|
||||
GinRouter --> TrackSvc
|
||||
GinRouter --> MarketSvc
|
||||
GinRouter --> SocialSvc
|
||||
GinRouter --> ChatWS
|
||||
GinRouter --> UploadSvc
|
||||
|
||||
UploadSvc --> ClamAV
|
||||
UploadSvc --> MinIO
|
||||
TrackSvc --> PostgreSQL
|
||||
MarketSvc --> Hyperswitch
|
||||
Hyperswitch --> HyperswitchDB
|
||||
|
||||
AuthSvc --> PostgreSQL
|
||||
AuthSvc --> Redis
|
||||
ChatWS --> Redis
|
||||
|
||||
AxumRouter --> HLSGen
|
||||
AxumRouter --> TranscodeEng
|
||||
AxumRouter --> StreamWS
|
||||
HLSGen --> MinIO
|
||||
|
||||
GinRouter --> RabbitMQ
|
||||
AxumRouter --> RabbitMQ
|
||||
|
||||
GinRouter --> PostgreSQL
|
||||
AxumRouter --> PostgreSQL
|
||||
AxumRouter --> Redis
|
||||
```
|
||||
|
||||
#### Flux critiques — Points de défaillance
|
||||
|
||||
**1. Auth Flow** (Register -> Login -> JWT -> Refresh)
|
||||
- SPOF : PostgreSQL (stockage users/sessions)
|
||||
- Timeouts : Configurés (middleware timeout global)
|
||||
- Retry : Refresh token automatique côté frontend (intercepteur Axios)
|
||||
- Fallback : Rate limiting en mémoire si Redis down
|
||||
- Race condition : Token refresh concurrent — géré par mutex dans le frontend interceptor
|
||||
|
||||
**2. Upload Flow** (Frontend -> API -> ClamAV -> S3)
|
||||
- SPOF : ClamAV (scan antivirus), MinIO/S3 (stockage)
|
||||
- Timeouts : `exec.CommandContext` avec context pour FFmpeg
|
||||
- Retry : Chunked upload avec resume (`/tracks/resume/:uploadId`)
|
||||
- Fallback : `CLAMAV_REQUIRED=false` permet de bypasser le scan en dev
|
||||
- Race condition : Upload concurrent du même fichier — ID unique par upload
|
||||
|
||||
**3. Playback Flow** (Frontend -> Stream Server -> HLS -> Player)
|
||||
- SPOF : Stream server Rust (single instance en dev), FFmpeg
|
||||
- Timeouts : Non explicitement configurés pour le stream server
|
||||
- Retry : hls.js gère les retries nativement
|
||||
- Fallback : Dev mode renvoie des segments factices
|
||||
- **RISQUE** : JWT issuer/audience mismatch entre Go et Rust (cf. Section 4)
|
||||
|
||||
**4. Payment Flow** (Frontend -> API -> Hyperswitch -> Webhook)
|
||||
- SPOF : Hyperswitch (payment router)
|
||||
- Timeouts : HTTP client avec timeout configuré
|
||||
- Retry : Webhook delivery avec retry intégré
|
||||
- Fallback : `HYPERSWITCH_ENABLED=false` désactive les paiements
|
||||
- Idempotence : Webhook signature HMAC-SHA512 vérifiée
|
||||
|
||||
**5. Chat Flow** (Frontend -> WebSocket -> Go Hub -> Redis PubSub)
|
||||
- SPOF : Redis (PubSub, présence)
|
||||
- Timeouts : WebSocket ping/pong configuré
|
||||
- Retry : Reconnection automatique côté frontend
|
||||
- Fallback : Chat fonctionne sans Redis (mode dégradé, pas de cross-instance)
|
||||
- Race condition : Messages concurrents gérés par le Hub pattern
|
||||
|
||||
---
|
||||
|
||||
## 2. CE QUE LE PRODUIT PERMET RÉELLEMENT
|
||||
|
||||
### 2.1 Classification des features
|
||||
|
||||
#### Fonctionnelles (flux complet front + back + DB + tests)
|
||||
|
||||
| Feature | Backend | Frontend | Tests | Preuve |
|
||||
|---------|---------|----------|-------|--------|
|
||||
| Auth (register, login, JWT, refresh, logout) | Routes + services + middleware | Login/Register pages + authStore | 306 test files Go, E2E Playwright | `routes_core.go`, `auth.go`, `jwt_service.go` |
|
||||
| 2FA (TOTP) | Setup/Verify/Disable handlers | 2FA settings UI | Unit tests | `two_factor_handler.go`, `two_factor_service.go` |
|
||||
| OAuth (Google, GitHub, Discord, Spotify) | PKCE, circuit breaker | OAuth buttons + callback | Service tests | `oauth_service.go` |
|
||||
| User profiles | CRUD, avatar, social links, follow/block | Profile pages + components | Tests | `routes_users.go`, `user_service.go` |
|
||||
| Upload audio | Chunked upload, ClamAV scan, S3 storage | Upload components + progress | Handler tests | `upload.go`, `upload_validator.go` |
|
||||
| CRUD Tracks | Create, read, update, delete, batch ops | Track list/detail pages | Tests + E2E | `routes_tracks.go`, `track/service.go` |
|
||||
| Playlists | CRUD, collaborators, share links, ordering | Playlist pages + drag-drop | Tests + E2E | `routes_playlists.go`, `playlist_service.go` |
|
||||
| Chat WebSocket | Hub pattern, Redis PubSub, presence | Chat page + real-time messages | Tests | `websocket/chat/`, `routes_chat` in `router.go` |
|
||||
| Dashboard | Stats, analytics | Dashboard page + charts (Recharts) | Tests | `dashboard_handler.go` |
|
||||
| Search | Full-text (pg_trgm), suggestions | Search page + filters | Tests | `track_search_service.go` |
|
||||
| Social (posts, feed, groups, follows) | CRUD + feed algorithm | Social pages + components | Tests | `routes_social.go`, `social/service.go` |
|
||||
| Marketplace (products, orders, payments) | Hyperswitch integration | Product listing + checkout | Tests | `routes_marketplace.go`, `marketplace/service.go` |
|
||||
| Notifications | Push + in-app | Notification center | Tests | `notification_handler.go` |
|
||||
| Webhooks | Delivery, signature, retry | Webhook management UI | Tests | `routes_webhooks.go`, `webhook_service.go` |
|
||||
| Gear/Inventory | CRUD gear items + images | Gear pages | Tests | `gear_handler.go` |
|
||||
| Analytics | Playback analytics, aggregation | Analytics dashboard | Tests | `playback_analytics_handler.go` |
|
||||
| Admin (reports, maintenance, flags, transfers) | Admin routes + RBAC | Admin pages | Tests | `routes_core.go` admin section |
|
||||
| Sessions management | CRUD, logout-all, stats | Sessions settings page | Tests | `session_service.go` |
|
||||
|
||||
#### Partiellement implémentées
|
||||
|
||||
| Feature | Statut | Détail |
|
||||
|---------|--------|--------|
|
||||
| HLS Streaming | Backend OK, stream server real FFmpeg | Frontend hls.js intégré, mais JWT mismatch Go/Rust empêche l'auth | `streaming/hls.rs`, `auth/mod.rs` |
|
||||
| Live Streaming | Routes backend + stream server | Frontend pages existent, dépend du stream server | `routes_core.go`, `live_stream` handlers |
|
||||
| Cloud Storage | Backend CRUD + S3 | Frontend pages + share links | Opérationnel mais feature flag masquée | `cloud_handler.go` |
|
||||
| GDPR Export | Backend async export | Frontend trigger + download | Long-running avec `context.Background()` | `gdpr_export.go` |
|
||||
|
||||
#### Fantômes (déclarées mais non fonctionnelles)
|
||||
|
||||
| Feature | Déclarée où | Réalité |
|
||||
|---------|------------|---------|
|
||||
| WebRTC Audio Calls | `FEATURE_STATUS.md` "WebRTC Beta" | Code Rust (~500 LOC) mais dépendance `webrtc = "0.7"` commentée. Signaling seulement, aucun média P2P. |
|
||||
| 2FA SMS | Planifié v0.104 | Aucun code SMS trouvé |
|
||||
| Passkeys/WebAuthn | Planifié v0.104 | Aucun code trouvé |
|
||||
| Electron Desktop | Mentionné dans les règles | Aucun code Electron trouvé, `.gitignore` only |
|
||||
|
||||
#### Code mort
|
||||
|
||||
| Élément | LOC estimé | Détail |
|
||||
|---------|-----------|--------|
|
||||
| `veza-stream-server/src/streaming/webrtc.rs` + `webrtc/config.rs` | ~500 | WebRTC sans dépendance native, jamais appelable |
|
||||
| `veza-stream-server/src/soundcloud/` (5 fichiers) | ~4000 | Discovery, playback, creator, management, social — module "SoundCloud-like" mais aucune route ne l'expose |
|
||||
| `k8s/chat-server/` | ~100 yaml | Manifestes pour service supprimé en v0.502 |
|
||||
| `Makefile.old` | ~200 | Ancien Makefile remplacé |
|
||||
| `chat_exports/`, `sub_task_agents/`, `tmt/`, `full_veza_audit_data/` | ~500 | Artefacts orphelins |
|
||||
| `internal/features/features.go` | 3 | Stub vide, feature flags implémentées ailleurs |
|
||||
|
||||
**Total code mort estimé** : ~5300 LOC
|
||||
|
||||
### 2.2 Incohérences produit/code
|
||||
|
||||
| Incohérence | Détail | Impact |
|
||||
|-------------|--------|--------|
|
||||
| **Version annoncée vs réelle** | Le prompt mentionne "v0.402 Phase 4 Commerce". Le code est v1.0.2. | Le projet a significativement avancé depuis le contexte du prompt. |
|
||||
| **JWT issuer/audience mismatch** | Go backend émet `iss: veza-api, aud: veza-app`. Stream server Rust attend `iss: veza-platform, aud: veza-services`. | Les tokens émis par le backend échoueront la validation dans le stream server. HLS auth cassé. |
|
||||
| **Password policy frontend vs backend** | Frontend valide 8 chars minimum. Backend rejette < 12 chars. | UX confuse : le formulaire accepte, le serveur rejette. |
|
||||
| **Feature flags runtime** | `internal/features/features.go` est un stub vide. Les feature flags sont en DB via `admin/feature-flags`. | Le stub crée de la confusion ; un développeur pourrait l'importer par erreur. |
|
||||
| **`onCreateProduct` no-op** | `routeConfig.tsx` : `onCreateProduct={() => {}}` dans le seller dashboard. | Le bouton "créer produit" depuis le seller dashboard ne fait rien. |
|
||||
|
||||
---
|
||||
|
||||
## 3. VALIDATION FONCTIONNELLE APPROFONDIE
|
||||
|
||||
### 3.1 Couverture de tests
|
||||
|
||||
| Service | Fichiers test | Fichiers code | Ratio | Couverture CI |
|
||||
|---------|--------------|---------------|-------|---------------|
|
||||
| Go backend | 306 `*_test.go` | 472 `.go` (non-test) | 0.65 | `go test ./...` en CI |
|
||||
| Rust stream | ~90 `#[test]` fonctions | 138 `.rs` | inline | `cargo test` en CI |
|
||||
| Frontend unit | 273 `.test.ts/.tsx` | 1614 `.ts/.tsx` (non-test) | 0.17 | `vitest --run` en CI |
|
||||
| Frontend stories | 300 `.stories.tsx` | ~1200 composants | 0.25 | Storybook build + audit en CI |
|
||||
| Frontend E2E | 26 `.spec.ts` | - | - | Playwright avec backend réel |
|
||||
|
||||
**Seuils de couverture frontend** (`vitest.config.ts`) : 50% minimum (branches, functions, lines, statements). Seuil bas mais raisonnable pour un MVP.
|
||||
|
||||
**Tests désactivés / skip** : Aucun `skip` ou `.only` trouvé dans le code Go. Pas de `.skip` systématique dans Vitest.
|
||||
|
||||
**MSW vs API réelle** : ~100% des tests unitaires frontend utilisent MSW. Les 26 tests E2E Playwright utilisent le backend Go réel avec PostgreSQL/Redis en CI.
|
||||
|
||||
### 3.2 Error handling
|
||||
|
||||
**Backend Go** :
|
||||
- **Pattern principal** : `RespondWithAppError` / `RespondWithError` avec codes d'erreur structurés
|
||||
- **Incohérence** : ~12 handlers utilisent encore `c.JSON(status, gin.H{"error": "..."})` au lieu du pattern standardisé
|
||||
- **Fichiers concernés** : `account_deletion_handler.go`, `comment_handler.go`, `search_handlers.go`, `tag_handler.go`, `sell_handler.go`, `admin_transfer_handler.go`, `cloud_handler.go`
|
||||
- **Impact** : Réponses d'erreur inconsistantes pour le frontend
|
||||
|
||||
**Rust stream server** :
|
||||
- Utilise `thiserror` pour les types d'erreur custom et `anyhow` pour la propagation
|
||||
- Error handler Axum centralisé via `error.rs` (783 lignes, complet)
|
||||
|
||||
**Frontend React** :
|
||||
- Error boundaries configurés dans le routing
|
||||
- Logger custom remplace `console.error`
|
||||
- Intercepteur Axios gère 401 (refresh/logout), 429 (rate limit store), 5xx (retry)
|
||||
|
||||
### 3.3 Validation input
|
||||
|
||||
**Backend** : Double validation — `go-playground/validator/v10` pour les structs, validations manuelles dans les services. Password policy stricte (12 chars, complexité, blocklist). File uploads validés par ClamAV.
|
||||
|
||||
**Frontend** : Zod schemas + react-hook-form pour la validation côté client. Env vars validées au démarrage via Zod.
|
||||
|
||||
### 3.4 Pagination
|
||||
|
||||
Les listes utilisent offset-based pagination avec `page` et `limit` query params. Pas de cursor-based pagination. Limite max non explicitement vérifiée dans tous les handlers — risque de requêtes avec `limit=100000`.
|
||||
|
||||
### 3.5 Points de rupture probables
|
||||
|
||||
| Scénario | Impact | Mitigation |
|
||||
|----------|--------|------------|
|
||||
| 10K tracks dans la DB | Requêtes de recherche pg_trgm potentiellement lentes | Index GIN configurés (`048_search_indexes.sql`) |
|
||||
| 100K users | Listes utilisateurs paginées, OK | - |
|
||||
| 1M messages chat | Pas de pagination serveur explicite dans le chat | Risque : chargement mémoire du Hub |
|
||||
| Fichier non-audio uploadé | ClamAV scan + validation MIME type | Extension validée, magic bytes si ClamAV actif |
|
||||
| Fichier > 10GB | Pas de limite explicite dans le code d'upload | Devrait être configuré au niveau proxy/nginx |
|
||||
| 1000 WebSocket simultanées | Chat Hub en mémoire Go (goroutines légères) | OK pour Go, limite Redis PubSub potentielle |
|
||||
| Webhook replay (3x même event) | `ProcessPaymentWebhook` idempotent — vérifie statut existant | OK |
|
||||
| Token expiré mid-session | Intercepteur Axios refresh transparent, retry queue | OK |
|
||||
| Migration échoue à moitié | Migrations non transactionnelles (extensions hors transaction) | Risque de schéma partiel |
|
||||
| Redis down | Rate limiting fallback en mémoire, chat dégradé (single-instance) | OK mais perte de cross-instance |
|
||||
|
||||
---
|
||||
|
||||
## 4. AUDIT DE SÉCURITÉ
|
||||
|
||||
### 4.0 Registre des vulnérabilités
|
||||
|
||||
| ID | Catégorie | Gravité | Fichier(s) | Description | Impact | Correctif | Effort |
|
||||
|----|-----------|---------|------------|-------------|--------|-----------|--------|
|
||||
| VEZA-SEC-001 | A02 Crypto | **Critique** | `veza-common/src/config_rust.rs:234` | Secret JWT par défaut `"your-super-secret-jwt-key"` en fallback dans `JwtConfig::default()` | Si un service Rust démarre sans `JWT_SECRET` env, tous les tokens sont signés avec un secret prévisible | Supprimer le default ou panic si absent | S |
|
||||
| VEZA-SEC-002 | A04 Design | **Critique** | `veza-stream-server/src/auth/mod.rs:150-151` vs `veza-backend-api/internal/services/jwt_service.go` | JWT issuer/audience mismatch : Go émet `iss:veza-api, aud:veza-app`, Rust attend `iss:veza-platform, aud:veza-services` | Tokens émis par Go invalides dans le stream server. HLS streaming authentifié cassé. | Aligner les valeurs iss/aud entre Go et Rust, ou utiliser le stream token dédié | M |
|
||||
| VEZA-SEC-003 | A05 Config | **Elevée** | `veza-stream-server/src/main.rs:74` | `shutdown_signal` reçoit un **nouvel** `AppState` au lieu de celui en cours d'exécution | Shutdown ne ferme pas correctement les connexions WebSocket ni l'event bus du state actif | Passer le même `AppState` à `shutdown_signal` | S |
|
||||
| VEZA-SEC-004 | A04 Design | **Elevée** | `veza-backend-api/internal/api/routes_webhooks.go:31` | Webhook worker goroutine démarré avec `context.Background()`, non enregistré dans le shutdown manager | Goroutine fuite au redéploiement. Webhooks en cours de livraison perdus. | Enregistrer dans `shutdownManager.Register` avec context annulable | S |
|
||||
| VEZA-SEC-005 | A04 Design | **Moyenne** | `apps/web/src/lib/passwordValidator.ts` vs `internal/validators/password_validator.go` | Frontend accepte 8 chars, backend rejette < 12 chars | UX confuse, formulaire accepte puis serveur rejette | Aligner frontend sur 12 chars min | S |
|
||||
| VEZA-SEC-006 | A05 Config | **Moyenne** | Routes `/metrics`, `/metrics/aggregated`, `/system/metrics` | Métriques Prometheus exposées publiquement sans auth | Information disclosure : un attaquant peut observer les patterns de charge, DB pool, error rates | Protéger par auth ou réseau interne en prod | S |
|
||||
| VEZA-SEC-007 | A07 Auth | **Moyenne** | `veza-backend-api/internal/services/password_reset_service.go:47,73,126,160` | Utilisation de `context.Background()` au lieu du context de la requête | Opérations DB non liées au cycle de vie de la requête (timeout ignoré, cancellation ignorée) | Propager `c.Request.Context()` | S |
|
||||
| VEZA-SEC-008 | A04 Design | **Faible** | `apps/web/src/router/routeConfig.tsx` | `onCreateProduct={() => {}}` — callback no-op dans le seller dashboard | Bouton visible mais inopérant, UX cassée | Connecter au vrai handler de création | S |
|
||||
| VEZA-SEC-009 | A06 Components | **Faible** | `veza-stream-server/Cargo.toml` | `dotenv 0.15` — dernière release 2020 | Dépendance potentiellement non maintenue | Migrer vers `dotenvy` | S |
|
||||
|
||||
### 4.1 A01 — Broken Access Control
|
||||
|
||||
**Routes protégées** : Exhaustivement auditées (cf. Section 1.4 routes). Toutes les routes de mutation (POST/PUT/DELETE) sont derrière `RequireAuth`. Les routes admin ajoutent `RequireAdmin`. Les routes de modification de ressource utilisent `RequireOwnershipOrAdmin`.
|
||||
|
||||
**IDOR** : Protégé par `RequireOwnershipOrAdmin` sur users, tracks, playlists, products. Le resolver charge le propriétaire depuis la DB et compare avec `userID` du JWT.
|
||||
|
||||
**CORS** : Strict en production — whitelist explicite requise, wildcard interdit avec credentials. Validation fail-fast au démarrage.
|
||||
|
||||
**Rate limiting** : Multi-couche (DDoS global 1000 req/s, per-IP 100 req/s, per-endpoint login/register, per-user, upload 10/heure).
|
||||
|
||||
**WebSocket auth** : Chat utilise JWT via header. Stream server utilise query param `?token=` — ce token est un stream token dédié à durée courte (5 min), pas le JWT principal.
|
||||
|
||||
**Verdict A01** : Solide. Pas de faille IDOR identifiée.
|
||||
|
||||
### 4.2 A02 — Cryptographic Failures
|
||||
|
||||
**Hashing mots de passe** : bcrypt cost 12, conforme aux recommandations OWASP (>= 10).
|
||||
|
||||
**JWT** : HS256 (HMAC-SHA256). Secret minimum 32 chars, validé au démarrage. Durée access token : 5 min (court, bon). Refresh token : 14 jours (30 si "remember me"). Token versioning pour révocation.
|
||||
|
||||
**Secrets en dur** : Un seul trouvé en production — `veza-common/src/config_rust.rs:234` (cf. VEZA-SEC-001). Les autres sont dans les tests uniquement.
|
||||
|
||||
**Secrets dans Git** : `.env` correctement dans `.gitignore`. Pas de secret trouvé dans l'historique Git (validé par les fichiers `.env.example`).
|
||||
|
||||
**Données sensibles en clair** : Logger Go avec filtrage des secrets (`internal/logging/secret_filter.go`). Pas de tokens dans les logs.
|
||||
|
||||
**Verdict A02** : Bon, sauf VEZA-SEC-001.
|
||||
|
||||
### 4.3 A03 — Injection
|
||||
|
||||
**SQL Injection** : GORM utilisé partout en production. Raw SQL uniquement dans les tests (`testutils/db.go`) avec table whitelist. Pas de `fmt.Sprintf` dans les requêtes SQL de production.
|
||||
|
||||
**Command Injection** : `exec.CommandContext` utilisé pour FFmpeg avec `ValidateExecPath`. Pas d'interpolation de string dans les commandes.
|
||||
|
||||
**XSS** : DOMPurify côté frontend pour `dangerouslySetInnerHTML` (3 usages, tous sanitisés). Tags autorisés : `p, br, strong, em, u, i, b, ul, ol, li, span, a`. Attributs interdits : `onerror, onload, onclick`.
|
||||
|
||||
**Path Traversal** : Noms de fichiers sanitisés dans l'upload service. UUID comme nom de fichier en stockage.
|
||||
|
||||
**Verdict A03** : Bon. Pas d'injection identifiée.
|
||||
|
||||
### 4.4 A04 — Insecure Design
|
||||
|
||||
**Principaux risques identifiés** :
|
||||
- JWT issuer/audience mismatch (VEZA-SEC-002)
|
||||
- Webhook worker lifecycle (VEZA-SEC-004)
|
||||
- Pagination sans limite max explicite
|
||||
|
||||
**Business logic** : Les ordres de paiement vérifient le statut existant avant de retraiter un webhook (idempotence). Promo codes validés côté serveur. Pas de prix négatif possible (validation struct Go).
|
||||
|
||||
**Enumeration** : Les erreurs de login ne distinguent pas "user inexistant" de "mot de passe incorrect" — conforme aux bonnes pratiques.
|
||||
|
||||
### 4.5 A05 — Security Misconfiguration
|
||||
|
||||
**Security headers** : Complets (HSTS, CSP strict pour API, X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin, Permissions-Policy restrictive).
|
||||
|
||||
**Debug mode** : Swagger désactivé en production. pprof derrière `RequireAdmin` et uniquement hors production.
|
||||
|
||||
**CORS** : Strict en production. `CORSDefault()` panic en production pour forcer la configuration explicite.
|
||||
|
||||
**Docker** : Secrets via env avec `:?` (required). Pas de secrets dans les layers Docker.
|
||||
|
||||
**Métriques exposées** : `/metrics` public (VEZA-SEC-006).
|
||||
|
||||
### 4.6 A06 — Vulnerable & Outdated Components
|
||||
|
||||
CI exécute automatiquement : `govulncheck` (Go), `cargo audit` (Rust), `npm audit --audit-level=critical` (Node). Dependabot configuré. Seule préoccupation : `dotenv 0.15` (VEZA-SEC-009).
|
||||
|
||||
### 4.7 A07 — Identification & Authentication Failures
|
||||
|
||||
**Password policy** : 12 chars, complexité, blocklist de 25 mots de passe courants, rejet patterns répétitifs/séquentiels. Robuste.
|
||||
|
||||
**Brute force** : Rate limiting sur `/auth/login` (configurable `AUTH_RATE_LIMIT_LOGIN_ATTEMPTS` / `AUTH_RATE_LIMIT_LOGIN_WINDOW`). Account lockout (`ACCOUNT_LOCKOUT_EXEMPT_EMAILS` pour tests).
|
||||
|
||||
**JWT validation** : Signature, expiration, issuer, audience vérifiés. Token versioning pour révocation.
|
||||
|
||||
**Refresh token** : Rotation via token versioning. Stockage en httpOnly cookies (pas localStorage). Révocation via token blacklist Redis.
|
||||
|
||||
**OAuth** : State parameter + PKCE. Domaines de redirect en whitelist. Circuit breaker sur les appels externes.
|
||||
|
||||
**Sessions** : Page de gestion des sessions, logout-all, logout-others. Révocation effective via token version.
|
||||
|
||||
### 4.8 A08 — Software & Data Integrity Failures
|
||||
|
||||
**Webhooks** : Hyperswitch HMAC-SHA512 vérifié avec constant-time comparison. Secret vide = 500 (pas de traitement).
|
||||
|
||||
**File uploads** : ClamAV scan obligatoire en production (`CLAMAV_REQUIRED=true`).
|
||||
|
||||
**Migrations** : Non transactionnelles pour les extensions (`CREATE EXTENSION` ne supporte pas les transactions dans certains cas). Risque de schéma partiel si une migration échoue.
|
||||
|
||||
### 4.9 A09 — Security Logging & Monitoring
|
||||
|
||||
**Audit trail** : Middleware audit logge les actions POST/PUT/DELETE avec user ID, IP, endpoint, timestamp (`audit.go`). Logs d'audit consultables via routes admin.
|
||||
|
||||
**Log level** : Configurable. Production interdit `LOG_LEVEL=DEBUG` (validation config). Filtrage des secrets dans les logs (`secret_filter.go`).
|
||||
|
||||
**Request ID** : Propagé via middleware `request_id.go`. Header `X-Request-ID` dans les réponses.
|
||||
|
||||
**Monitoring** : Prometheus metrics configurées. Alertmanager + Grafana dans le stack production.
|
||||
|
||||
### 4.10 A10 — Server-Side Request Forgery (SSRF)
|
||||
|
||||
Pas de SSRF identifié. Le backend ne fait pas de requêtes vers des URLs fournies par l'utilisateur. Les webhooks sortants utilisent des URLs enregistrées par l'utilisateur authentifié — pas de filtrage IP privée, ce qui pourrait théoriquement être exploité pour scanner le réseau interne.
|
||||
|
||||
### 4.11 Sécurité spécifique à la stack
|
||||
|
||||
**Go** :
|
||||
- `defer` correctement utilisé pour les ressources DB et fichiers
|
||||
- `context.Context` propagé dans la majorité des cas (sauf `password_reset_service.go`)
|
||||
- Goroutines avec lifecycle géré via `shutdownManager` (sauf webhook worker)
|
||||
- Pas de race conditions évidentes (Hub pattern thread-safe)
|
||||
|
||||
**Rust** :
|
||||
- Aucun bloc `unsafe` dans le code applicatif
|
||||
- ~15-20 `unwrap()` en production, principalement dans l'initialisation (safe) ou avec fallback `unwrap_or`
|
||||
- `panic!` dans les env vars requises au démarrage (acceptable)
|
||||
- `Default::default()` pour `TokenValidator` et `Config` paniquent en production (gated par `cfg(not(debug_assertions))`)
|
||||
|
||||
**React** :
|
||||
- `dangerouslySetInnerHTML` : 3 usages, tous avec DOMPurify
|
||||
- Tokens : httpOnly cookies, pas de localStorage pour les tokens d'auth
|
||||
- `console.log` : remplacé par logger custom, 0 usage brut en production
|
||||
|
||||
---
|
||||
|
||||
## 5. DETTE TECHNIQUE
|
||||
|
||||
### 5.1 Registre de la dette technique
|
||||
|
||||
| ID | Catégorie | Description | Impact | Fichier(s) | Effort |
|
||||
|----|-----------|-------------|--------|------------|--------|
|
||||
| DT-001 | Critique | JWT issuer/audience mismatch Go/Rust empêche l'auth HLS | HLS streaming non fonctionnel en production | `jwt_service.go`, `auth/mod.rs` | M |
|
||||
| DT-002 | Critique | Secret JWT par défaut dans `veza-common` | Faille de sécurité potentielle | `config_rust.rs:234` | S |
|
||||
| DT-003 | Structurante | ~5300 LOC de code mort (WebRTC, soundcloud/, k8s/chat-server, orphelins) | Confusion, coût maintenance | Multiples | M |
|
||||
| DT-004 | Structurante | Error handling inconsistant — 12 handlers utilisent `gin.H` au lieu de `RespondWithAppError` | Frontend doit gérer 2 formats d'erreur | `account_deletion_handler.go`, etc. | M |
|
||||
| DT-005 | Structurante | 139 fichiers > 500 lignes (78 Go, 41 Rust, 20 TS/TSX) | Complexité de maintenance | `track/service.go` (1147), `marketplace/service.go` (1142), etc. | L |
|
||||
| DT-006 | Structurante | `features.go` est un stub vide alors que les feature flags sont en DB | Confusion architecturale | `internal/features/features.go` | S |
|
||||
| DT-007 | Structurante | `shutdown_signal` reçoit un nouvel `AppState` dans `main.rs` | Shutdown ne ferme pas les connexions WebSocket correctement | `main.rs:74` | S |
|
||||
| DT-008 | Opérationnelle | Webhook worker goroutine non enregistré dans le shutdown manager | Goroutine leak au redéploiement | `routes_webhooks.go:31` | S |
|
||||
| DT-009 | Opérationnelle | `context.Background()` dans `password_reset_service.go` | Timeouts ignorés | 4 endroits | S |
|
||||
| DT-010 | Opérationnelle | Pagination sans limite max — `limit=100000` possible | Requêtes DB excessives | Tous les handlers de liste | M |
|
||||
| DT-011 | Opérationnelle | Migrations non transactionnelles | Risque de schéma partiel si une migration échoue | `database/migrations.go` | L |
|
||||
| DT-012 | Opérationnelle | 335 TODO, 9 FIXME, 12 HACK dans le codebase | Indicateur de travail non terminé | Répartis sur 3 langages | XL |
|
||||
| DT-013 | Opérationnelle | Password policy mismatch frontend/backend | UX dégradée | `passwordValidator.ts`, `password_validator.go` | S |
|
||||
| DT-014 | Opérationnelle | `onCreateProduct={() => {}}` no-op dans routeConfig | Bouton seller inopérant | `routeConfig.tsx` | S |
|
||||
| DT-015 | Cosmétique | `dotenv 0.15` (2020) dans le stream server Rust | Dépendance potentiellement non maintenue | `Cargo.toml` | S |
|
||||
| DT-016 | Cosmétique | 285 fichiers de documentation — certains obsolètes | Surcharge informationnelle | `docs/` | M |
|
||||
| DT-017 | Cosmétique | `Makefile.old` toujours présent | Confusion | Racine | S |
|
||||
|
||||
### 5.2 Métriques de dette
|
||||
|
||||
| Métrique | Valeur |
|
||||
|----------|--------|
|
||||
| LOC total (code source) | ~319K |
|
||||
| LOC mort estimé | ~5300 (1.7%) |
|
||||
| Ratio test/code (Go) | 0.65 (306 tests / 472 fichiers code) |
|
||||
| Ratio test/code (Frontend) | 0.17 (273 tests / 1614 fichiers code) |
|
||||
| Stories / Composants | 0.25 (300 / ~1200) |
|
||||
| Fichiers > 500 lignes | 139 (78 Go, 41 Rust, 20 TS/TSX) |
|
||||
| TODO/FIXME/HACK | 356 total (335 TODO, 9 FIXME, 12 HACK) |
|
||||
| Deps directes (tous services) | ~195 (45 Go + 78 Rust + 72 Frontend) |
|
||||
| Deps avec vulnérabilités connues | 0 (CI scanne automatiquement) |
|
||||
|
||||
### 5.3 Hétérogénéité Go + Rust
|
||||
|
||||
Le choix de Go **et** Rust est la décision architecturale la plus discutable du projet :
|
||||
|
||||
**Justification originale** (ADR-002) : Le chat server Rust a été migré vers Go en v0.502, reconnaissant que le surcoût de Rust n'était pas justifié pour le chat. Le stream server Rust est conservé pour les performances audio (transcoding, HLS).
|
||||
|
||||
**Évaluation** : Le stream server Rust représente ~103K LOC mais sa valeur ajoutée réelle se limite à l'appel FFmpeg (`exec.CommandContext` en Go ferait la même chose) et au serving HLS (servir des fichiers statiques n'exige pas Rust). Le code "SoundCloud-like" (~4K LOC dans `soundcloud/`) n'est pas exposé par les routes.
|
||||
|
||||
**Coût** : Maintenir 2 langages backend (Go + Rust) triple la complexité de recrutement, double les outils CI, et crée des incohérences inter-services (cf. JWT mismatch).
|
||||
|
||||
**Recommandation** : Évaluer la migration du stream server vers Go à moyen terme. À court terme, corriger le JWT mismatch et nettoyer le code mort Rust.
|
||||
|
||||
---
|
||||
|
||||
## 6. QUALITÉ ARCHITECTURALE
|
||||
|
||||
### 6.1 Monorepo
|
||||
|
||||
| Critère | Évaluation |
|
||||
|---------|------------|
|
||||
| Outil de gestion | npm workspaces + Turborepo. Config minimale (3 tâches). Pas de cache remote. |
|
||||
| Build orchestration | Turbo parallélise build/test/lint. Builds Docker séparés. |
|
||||
| Versionning | Fichier `VERSION` à la racine (1.0.2). Pas de versionning par service. |
|
||||
| Dépendances internes | `veza-common` (Rust) utilisé par `veza-stream-server`. `packages/design-system` par `apps/web`. |
|
||||
| Workspace config | npm workspaces (pas pnpm). `package-lock.json` à la racine. |
|
||||
| Makefile | Modulaire (11 fichiers dans `make/`). Cible `help` disponible. |
|
||||
|
||||
### 6.2 Frontend (React/Vite)
|
||||
|
||||
| Critère | Score | Détail |
|
||||
|---------|-------|--------|
|
||||
| Structure | 8/10 | Feature-based (`src/features/`, `src/components/`). Claire et organisée. |
|
||||
| State management | 8/10 | Zustand (6 stores), léger et bien séparé. React Query pour les données serveur. |
|
||||
| Data fetching | 8/10 | TanStack React Query avec Axios. Intercepteurs pour refresh, rate limit, retry. |
|
||||
| Routing | 7/10 | React Router 6 avec lazy loading via `LazyComponent`. Code splitting configuré dans Vite. |
|
||||
| TypeScript | 8/10 | `strict: true` avec `noUncheckedIndexedAccess`. ~150 `any` restants (principalement tests et generated). |
|
||||
| Storybook | 8/10 | 300 stories, MSW intégré, 3 viewports, addon a11y. CI valide le build. |
|
||||
| Accessibilité | 7/10 | ARIA, roles, tabIndex largement utilisés. ESLint jsx-a11y configuré. |
|
||||
| Bundle analysis | 6/10 | Manual chunks dans Vite (React, TanStack, lucide). Rollup visualizer en build. |
|
||||
| MSW coverage | 9/10 | 11 handler modules couvrant toutes les features. Catch-all pour les routes non mockées. |
|
||||
| DOMPurify | 10/10 | 3 usages de `dangerouslySetInnerHTML`, tous sanitisés. |
|
||||
|
||||
### 6.3 Backend Go
|
||||
|
||||
| Critère | Score | Détail |
|
||||
|---------|-------|--------|
|
||||
| Architecture | 8/10 | Handler -> Service -> Repository bien séparé. Clean architecture. |
|
||||
| Error handling | 6/10 | Pattern `RespondWithAppError` existe mais 12 handlers utilisent encore `gin.H`. |
|
||||
| Middleware stack | 9/10 | 15+ middlewares dans le bon ordre. Auth, CORS, rate limit, security headers, CSRF. |
|
||||
| Database | 8/10 | Connection pooling (50 max, 12 idle). Migrations SQL. Read replica supporté. |
|
||||
| Concurrency | 7/10 | Goroutines gérées via `shutdownManager` (sauf webhook worker). Context propagé. |
|
||||
| Configuration | 8/10 | Env vars avec validation au démarrage. Fail-fast en production. Secrets filtrés. |
|
||||
| API versioning | 7/10 | `/api/v1/` partout. `VersionMiddleware` présent. Pas de plan v2 explicite. |
|
||||
| OpenAPI | 7/10 | Swagger generé (`swaggo`). Frontend types generées depuis l'OpenAPI spec. Validation types sync en CI. |
|
||||
|
||||
### 6.4 Stream Server Rust
|
||||
|
||||
| Critère | Score | Détail |
|
||||
|---------|-------|--------|
|
||||
| HLS | 7/10 | Réel (FFmpeg). Dev-only fallbacks pour les segments manquants. |
|
||||
| WebRTC | 1/10 | Session management sans stack média. Non fonctionnel. |
|
||||
| Compilation | 7/10 | Build musl static. `Cargo.lock` versionné. Pas de `sqlx-data.json`. |
|
||||
| Error handling | 7/10 | `thiserror` + `anyhow`. 783 lignes dans `error.rs`. |
|
||||
| Tests | 5/10 | ~90 tests inline. Pas de tests d'intégration avec DB. |
|
||||
| Code mort | 3/10 | ~4500 LOC dans `soundcloud/` + `webrtc.rs` non exposées. |
|
||||
|
||||
### 6.5 Base de données
|
||||
|
||||
| Critère | Évaluation |
|
||||
|---------|------------|
|
||||
| Schéma | 95 migrations, normalisé, UUID primary keys. FK constraints. |
|
||||
| Indexes | Index GIN pour pg_trgm search. Index composite (`049_composite_indexes.sql`). Performance indexes (`920`, `940`). |
|
||||
| Migrations | Numérotation non continue (gap 019-020, 050-060, 066-069, etc.). Down migrations pour certaines seulement. |
|
||||
| Redis | Cache, rate limiting, sessions, token blacklist, chat PubSub, CSRF tokens, presence. TTL configurés. |
|
||||
| N+1 | `Preload` utilisé correctement dans la majorité des cas. 1 risque identifié dans `analytics_aggregation_service.go`. |
|
||||
|
||||
### 6.6 Scores détaillés
|
||||
|
||||
| Dimension | Score | Justification |
|
||||
|-----------|-------|---------------|
|
||||
| **Architecture** | 7/10 | Bonne séparation des concerns. Go clean architecture. Rust plus chaotique (soundcloud/, webrtc dead code). Le choix Go+Rust ajoute de la complexité injustifiée. |
|
||||
| **Maintenabilité** | 5/10 | 139 fichiers > 500 lignes, 356 TODO/FIXME/HACK, error handling inconsistant, 2 langages backend. Documentation abondante (285 docs) mais parfois obsolète. |
|
||||
| **Sécurité** | 7/10 | httpOnly cookies, bcrypt cost 12, rate limiting multi-couche, security headers complets, CSRF, ClamAV. Mais : JWT secret default (VEZA-SEC-001), issuer mismatch (VEZA-SEC-002). |
|
||||
| **Scalabilité** | 5/10 | Single instance par service, pas de load balancer inter-service (sauf HAProxy). Redis SPOF. PostgreSQL sans réplication configurée (read replica supporté mais non déployé). Pas de sharding. |
|
||||
| **Testabilité** | 7/10 | 306 tests Go, 273 tests frontend, 300 stories, 26 E2E. MSW complet. Couverture seuil 50%. CI automatisée avec govulncheck/cargo audit/npm audit. |
|
||||
| **Opérabilité** | 7/10 | Docker multi-stage, blue-green HAProxy, health checks, Prometheus/Grafana configs, Alertmanager, request ID, structured logging, Sentry. k8s templates prêts. |
|
||||
| **Vélocité dev** | 4/10 | 2 langages backend = barrière à l'entrée. ~319K LOC. Makefile modulaire aide. Storybook-first accélère l'UI. Un nouveau dev aura besoin de 2-3 semaines pour être productif (Go OR Rust, pas les deux). |
|
||||
| **Maturité produit** | 6/10 | 19 features opérationnelles sur ~22 déclarées. HLS partiellement cassé (JWT mismatch). WebRTC non fonctionnel. Payment flow opérationnel mais en mode test. |
|
||||
|
||||
---
|
||||
|
||||
## 7. INFRASTRUCTURE & DEVOPS
|
||||
|
||||
### 7.1 Docker
|
||||
|
||||
| Critère | Backend Go | Stream Rust | Frontend |
|
||||
|---------|-----------|-------------|----------|
|
||||
| Multi-stage | Oui (golang:1.24-alpine -> alpine:3.21) | Oui (rust:1.84-alpine -> alpine:3.21) | Oui (node:20-alpine -> nginx:1.27-alpine) |
|
||||
| Non-root | Oui (user `app`, UID 1001) | Oui (user `app`, UID 1001) | Oui (user `nginx`) |
|
||||
| Health check | `wget /api/v1/health` | `wget /health` | `wget /health` |
|
||||
| Static binary | CGO_ENABLED=0, ldflags `-w -s -static` | musl target, strip | N/A (nginx) |
|
||||
| Source maps | N/A | N/A | Supprimées en production (`find dist -name "*.map" -delete`) |
|
||||
| Secrets dans layers | Non | Non | Build args pour `VITE_*` (non secrets) |
|
||||
| Image base | alpine:3.21 (officielle) | alpine:3.21 (officielle) | nginx:1.27-alpine (officielle) |
|
||||
|
||||
**Docker Compose (production)** :
|
||||
- Secrets via env avec `:?` (required) — fail-fast si absent
|
||||
- Blue-green deployment (backend-blue/green, stream-blue/green, web-blue/green)
|
||||
- HAProxy reverse proxy avec SSL
|
||||
- Health checks sur tous les services
|
||||
- Resource limits configurés (CPU, memory)
|
||||
- Réseau isolé (bridge, subnet 172.20.0.0/16)
|
||||
- Pas de ports exposés directement (sauf HAProxy 80/443 et Alertmanager 9093)
|
||||
|
||||
### 7.2 CI/CD
|
||||
|
||||
| Workflow | Couverture | Détail |
|
||||
|----------|-----------|--------|
|
||||
| `ci.yml` | Principal | 5 jobs : backend-go (govulncheck, vet, lint, test, build), rust-services (cargo audit, lint, build, test), frontend (npm audit, generate types, lint, format, typecheck, test, build), storybook (build, serve, audit), e2e (Postgres+Redis+RabbitMQ, migrations, Playwright) |
|
||||
| `cd.yml` | Déploiement | Deployment workflow |
|
||||
| `backend-ci.yml` | Backend | Backend-specific CI |
|
||||
| `frontend-ci.yml` | Frontend | Frontend-specific CI |
|
||||
| `rust-ci.yml` | Rust | Clippy for stream server |
|
||||
| `stream-ci.yml` | Stream | Clippy, audit, tests |
|
||||
| `sast.yml` | Sécurité | Static Application Security Testing |
|
||||
| `security-scan.yml` | Sécurité | Security scans |
|
||||
| `container-scan.yml` | Sécurité | Container image scanning |
|
||||
| `storybook-audit.yml` | UI | Storybook validation |
|
||||
| `load-test-nightly.yml` | Performance | k6 nightly (02:00 UTC) |
|
||||
| Dependabot | Dépendances | Mise à jour automatique |
|
||||
|
||||
**Ce qui manque** :
|
||||
- Pas de cache remote Turborepo (builds plus lents en CI)
|
||||
- Pas de déploiement automatique vers staging/production (seulement `cd.yml` mais pas vérifié si complet)
|
||||
- Pas de smoke tests post-déploiement automatisés en CI
|
||||
|
||||
### 7.3 Reproductibilité
|
||||
|
||||
| Critère | Statut |
|
||||
|---------|--------|
|
||||
| Build en une commande | `docker compose up` pour dev, `docker compose -f docker-compose.prod.yml up` pour prod |
|
||||
| Onboarding doc | `docs/ONBOARDING.md` existe et est détaillé |
|
||||
| Versions outils lockées | Go 1.24 (go.mod), Rust stable (pas de toolchain file), Node 20 (CI, pas de `.nvmrc`) |
|
||||
| Dépendances lockées | `go.sum`, `Cargo.lock`, `package-lock.json` — tous versionnés |
|
||||
| Manque | `.nvmrc` ou `.node-version` pour Node. `rust-toolchain.toml` pour Rust. |
|
||||
|
||||
---
|
||||
|
||||
## 8. PERFORMANCE & SCALABILITÉ
|
||||
|
||||
### 8.1 Goulots d'étranglement
|
||||
|
||||
| Composant | Risque | Seuil estimé | Mitigation |
|
||||
|-----------|--------|--------------|------------|
|
||||
| PostgreSQL | Requêtes non optimisées sans index, N+1 | > 10K req/min | Index GIN pg_trgm. Preload GORM. Read replica supporté. |
|
||||
| Redis | SPOF pour cache, rate limiting, sessions, PubSub | Si Redis down : rate limit in-memory, chat single-instance | Sentinel/Cluster non configuré |
|
||||
| Chat server (Go) | Hub pattern en mémoire | > 1000 connexions simultanées | Go goroutines légères (~8KB/goroutine) |
|
||||
| Stream server (Rust) | FFmpeg transcoding CPU-intensif | > 100 transcodings simultanés | Tokio runtime multi-threaded |
|
||||
| File storage | MinIO/S3 — pas de CDN configuré en dev | > 10TB de tracks | S3/MinIO horizontalement scalable. CDN configs dans k8s/ |
|
||||
| HAProxy | Single instance reverse proxy | > 5000 req/s | Suffisant pour le lancement. LB cloud dans k8s/load-balancing/ |
|
||||
|
||||
### 8.2 Scalabilité horizontale
|
||||
|
||||
| Service | Horizontalement scalable ? | Obstacle |
|
||||
|---------|--------------------------|----------|
|
||||
| Backend Go | Oui (stateless sauf WebSocket) | Chat WebSocket a affinité de session. Redis PubSub résout partiellement. |
|
||||
| Stream server | Oui (stateless pour HLS serving) | Transcoding est CPU-bound, scalable par ajout d'instances. |
|
||||
| Frontend | Oui (fichiers statiques nginx) | Aucun obstacle. |
|
||||
| PostgreSQL | Verticalement + read replicas | Pas de sharding configuré. |
|
||||
| Redis | Non configuré pour cluster | Single instance. Sentinel/Cluster nécessaire pour HA. |
|
||||
|
||||
### 8.3 Load tests existants
|
||||
|
||||
Les scripts k6 dans `loadtests/` couvrent :
|
||||
- Smoke test (30s, sanity check)
|
||||
- Backend: health, auth, tracks, uploads, playlists, marketplace, full scenario
|
||||
- Stream: health, HLS, ramp test
|
||||
- Chat: WebSocket, stress 1000 connexions simultanées
|
||||
- Stress: 500 req/s
|
||||
|
||||
**Verdict** : Bonne couverture de load testing pour un MVP. Les scripts sont réutilisables et paramétrables.
|
||||
|
||||
---
|
||||
|
||||
## 9. RISQUES BUSINESS
|
||||
|
||||
### 9.1 Point de vue CTO
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Recruter des devs productifs en < 2 semaines ? | **Non.** La stack Go + Rust + React exige des profils polyvalents rares. Un dev Go sera productif sur le backend en 1-2 semaines, mais ne touchera pas le stream server. Un dev Rust full-stack capable de Go et React est un profil exceptionnel. |
|
||||
| Vélocité soutenable ? | **Non.** ~319K LOC en monorepo avec 356 TODO, 139 fichiers > 500 lignes, et 5300 LOC de code mort indiquent une vélocité qui a sacrifié la qualité. Le rythme doit ralentir pour consolider. |
|
||||
| Dette technique va-t-elle exploser ? | **Oui, si le rythme actuel continue.** Les 12 handlers avec error handling inconsistant, les migrations non transactionnelles, et le JWT mismatch sont des bombes à retardement. |
|
||||
| Refactorings inévitables avant scaling ? | (1) Corriger le JWT mismatch Go/Rust. (2) Nettoyer le code mort Rust. (3) Standardiser error handling. (4) Configurer Redis Sentinel/Cluster. |
|
||||
| Go + Rust justifié pour cette taille d'équipe ? | **Non.** Le stream server Rust pourrait être migré en Go sans perte de fonctionnalité significative (FFmpeg est l'outil lourd, pas le wrapper). La complexité de maintenance de 2 langages backend n'est pas justifiée par les gains de performance pour un service qui sert principalement des fichiers HLS. |
|
||||
|
||||
### 9.2 Point de vue investisseur
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Produit réellement fonctionnel ? | **Partiellement.** 19 features opérationnelles. Mais HLS auth cassé (JWT mismatch), WebRTC non fonctionnel, payments en mode test. C'est un MVP fonctionnel, pas une plateforme production-ready. |
|
||||
| Risques de sécurité incident public ? | **Faibles.** httpOnly cookies, bcrypt, rate limiting, CSP. Le seul risque critique (VEZA-SEC-001 JWT default secret) est mitigeable en < 1 jour. Pas de données utilisateur exposées. |
|
||||
| Code repris par une autre équipe ? | **Oui, avec réserves.** Le Go backend est bien structuré et documenté. Le Rust stream server est plus complexe à reprendre. Le frontend React est standard. Documentation abondante (285 docs). |
|
||||
| Coût pour v1.0 production-ready ? | **4-8 semaines** avec 2 développeurs. Corriger les vulnérabilités critiques (1 semaine), stabiliser le streaming (2 semaines), configurer la production (1 semaine), tests de charge (1 semaine), monitoring (1 semaine). |
|
||||
| Propriété intellectuelle défendable ? | **Limitée.** Stack standard (Go/Rust/React), pas d'algorithme propriétaire. La valeur est dans l'intégration et le produit, pas dans la technologie. Le code Rust stream server avec ses codecs et pipeline audio est le plus différenciant mais aussi le plus coûteux à maintenir. |
|
||||
| Ratio features/qualité = quantité sur qualité ? | **Partiellement.** 19 features fonctionnelles est impressionnant, mais 5300 LOC de code mort, 356 TODO, et le JWT mismatch suggèrent une priorisation de la quantité. La qualité de base (auth, sécurité, CI/CD) est néanmoins solide. |
|
||||
|
||||
### 9.3 Point de vue acquéreur
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Code réutilisable ? | **Backend Go : oui.** Architecture propre, bien découpée. **Stream Rust : partiellement.** Code HLS et transcoding réutilisable, le reste (soundcloud/, webrtc) est du dead code. **Frontend : oui.** Standard React/Vite. |
|
||||
| Données utilisateur migrables ? | **Oui.** PostgreSQL avec schéma normalisé. UUID primary keys. GDPR export implémenté. |
|
||||
| Vendor-lock ? | **Faible.** Hyperswitch est open-source (remplaçable). MinIO est S3-compatible. Redis et PostgreSQL sont standards. |
|
||||
| Temps onboarding 5 devs ? | **4-6 semaines.** 1 semaine setup + 1 semaine architecture backend + 1 semaine frontend + 1-2 semaines Rust (si maintenu). Documentation ONBOARDING.md existe. |
|
||||
| Score de rachetabilité /10 | **6/10.** Code propre pour un MVP, documentation abondante, stack standard. Pénalisé par la complexité Rust et le code mort. |
|
||||
|
||||
### 9.4 Verdict
|
||||
|
||||
| Question | Réponse | Justification |
|
||||
|----------|---------|---------------|
|
||||
| Peut-on lancer en production tel quel ? | **Non** | JWT mismatch Go/Rust (HLS auth cassé), secret JWT default en Rust, webhook worker leak |
|
||||
| Peut-on vendre / monétiser tel quel ? | **Conditionnel** | Après correction des 3 vulnérabilités critiques + configuration Hyperswitch en mode live |
|
||||
| Peut-on maintenir avec 2 devs ? | **Conditionnel** | Oui si le stream server Rust est gelé ou migré en Go. Non avec 2 langages backend actifs. |
|
||||
| Faut-il refactorer avant prod ? | **Oui** | JWT mismatch, error handling, code mort, pagination limits |
|
||||
| Faut-il réécrire certains services ? | **Non** | Le code est de qualité suffisante. Migration Rust -> Go du stream server recommandée à moyen terme, pas de réécriture urgente. |
|
||||
| La vélocité de développement est-elle un red flag ? | **Oui (modéré)** | Le volume de code (319K LOC) et le nombre de features (19) pour ce qui semble être un petit team est impressionnant mais a généré de la dette. Le rythme doit ralentir pour consolider. |
|
||||
|
||||
---
|
||||
|
||||
## 10. PLAN D'ACTION PRIORISÉ
|
||||
|
||||
### Phase 1 — Critique (Semaines 1-2) : Sécurité et Stabilité
|
||||
|
||||
| # | Action | Risque si non corrigé | Fichiers | Effort |
|
||||
|---|--------|----------------------|----------|--------|
|
||||
| 1 | Supprimer le JWT secret par défaut dans `veza-common/src/config_rust.rs` — panic si `JWT_SECRET` absent | Compromission de tous les tokens si env var manquante | `config_rust.rs:234` | S |
|
||||
| 2 | Aligner JWT issuer/audience entre Go et Rust (utiliser `veza-api`/`veza-app` partout, ou configurable par env) | HLS streaming auth cassé | `jwt_service.go`, `auth/mod.rs` | M |
|
||||
| 3 | Enregistrer le webhook worker dans `shutdownManager` avec context annulable | Perte de webhooks au redéploiement | `routes_webhooks.go:31` | S |
|
||||
| 4 | Corriger `shutdown_signal` dans `main.rs` — passer le `AppState` existant | Shutdown ne ferme pas les connexions correctement | `main.rs:74` | S |
|
||||
| 5 | Aligner la politique de mot de passe frontend (12 chars min) | Formulaire accepte, serveur rejette | `passwordValidator.ts` | S |
|
||||
| 6 | Protéger les routes `/metrics` par auth ou réseau interne | Information disclosure des métriques système | `routes_core.go` | S |
|
||||
|
||||
### Phase 2 — Stabilisation (Semaines 3-6) : Déployabilité
|
||||
|
||||
| # | Action | Fichiers | Effort |
|
||||
|---|--------|----------|--------|
|
||||
| 7 | Standardiser error handling — migrer les 12 handlers vers `RespondWithAppError` | `account_deletion_handler.go`, etc. | M |
|
||||
| 8 | Ajouter une limite max de pagination (ex: `limit` capped à 100) | Tous les handlers de liste | M |
|
||||
| 9 | Propager `context.Context` dans `password_reset_service.go` | 4 endroits | S |
|
||||
| 10 | Configurer Redis Sentinel ou Cluster pour la haute disponibilité | `docker-compose.prod.yml` | L |
|
||||
| 11 | Ajouter `.nvmrc` (Node 20) et `rust-toolchain.toml` pour lock les versions | Racine, `veza-stream-server/` | S |
|
||||
| 12 | Configurer Turborepo remote cache pour accélérer la CI | `turbo.json` | M |
|
||||
| 13 | Smoke tests post-déploiement automatisés | `.github/workflows/cd.yml` | M |
|
||||
|
||||
### Phase 3 — Consolidation (Semaines 7-12) : Dette technique
|
||||
|
||||
| # | Action | Fichiers | Effort |
|
||||
|---|--------|----------|--------|
|
||||
| 14 | Supprimer le code mort : `soundcloud/` (~4K LOC), `webrtc.rs` (~500 LOC), `k8s/chat-server/`, `Makefile.old` | Multiples | M |
|
||||
| 15 | Supprimer les dossiers orphelins : `chat_exports/`, `sub_task_agents/`, `tmt/`, `full_veza_audit_data/` | Racine | S |
|
||||
| 16 | Découper les 10 plus gros fichiers (> 1000 lignes) en sous-modules | `track/service.go`, `marketplace/service.go`, `playlist_handler.go`, etc. | L |
|
||||
| 17 | Adresser les 356 TODO/FIXME/HACK — résoudre ou convertir en issues | Tout le codebase | XL |
|
||||
| 18 | Consolider les migrations — squash des 95 migrations en une base + delta | `veza-backend-api/migrations/` | L |
|
||||
| 19 | Supprimer `features.go` stub ou implémenter le runtime feature flag system | `internal/features/features.go` | S |
|
||||
| 20 | Connecter `onCreateProduct` dans le seller dashboard | `routeConfig.tsx` | S |
|
||||
|
||||
### Phase 4 — Évolution (Mois 4+)
|
||||
|
||||
| # | Action | Effort |
|
||||
|---|--------|--------|
|
||||
| 21 | Évaluer la migration du stream server Rust vers Go | XL |
|
||||
| 22 | Implémenter WebRTC audio calls (v1.1) ou supprimer définitivement le code | L |
|
||||
| 23 | Configurer un CDN (CloudFront/Cloudflare) pour les assets audio | M |
|
||||
| 24 | Implémenter cursor-based pagination pour les listes volumineuses | L |
|
||||
| 25 | Ajouter des tests d'intégration Rust avec base de données | L |
|
||||
| 26 | Passer les couverture tests frontend de 50% à 70% | XL |
|
||||
|
||||
---
|
||||
|
||||
## ANNEXES
|
||||
|
||||
### A. Métriques brutes
|
||||
|
||||
| Métrique | Valeur |
|
||||
|----------|--------|
|
||||
| Commit analysé | `a007f4c7` (2026-03-03) |
|
||||
| Version | v1.0.2 |
|
||||
| LOC Go (backend) | ~188K (778 fichiers) |
|
||||
| LOC Rust (stream + common) | ~107K (191 fichiers) |
|
||||
| LOC TypeScript/TSX (frontend) | ~211K (1887 fichiers) |
|
||||
| LOC SQL (migrations) | ~3.5K (95 fichiers) |
|
||||
| LOC total code source | ~319K |
|
||||
| Tests Go | 306 fichiers |
|
||||
| Tests Frontend | 273 fichiers |
|
||||
| Stories Storybook | 300 fichiers |
|
||||
| Tests E2E | 26 specs |
|
||||
| Workflows CI/CD | 14 |
|
||||
| Docs Markdown | 285 fichiers |
|
||||
| Direct deps Go | 45 |
|
||||
| Direct deps Rust | ~78 |
|
||||
| Direct deps Frontend | 72 |
|
||||
| Fichiers > 500 lignes | 139 |
|
||||
| TODO/FIXME/HACK | 356 |
|
||||
| Code mort estimé | ~5300 LOC |
|
||||
| Vulnérabilités CVE (CI) | 0 |
|
||||
| Vulnérabilités identifiées (audit) | 9 (2 critiques, 2 élevées, 3 moyennes, 2 faibles) |
|
||||
|
||||
### B. Fichiers les plus volumineux (top 15)
|
||||
|
||||
| Fichier | LOC |
|
||||
|---------|-----|
|
||||
| `apps/web/src/types/generated/api.ts` | 6550 |
|
||||
| `veza-backend-api/docs/docs.go` | 5448 |
|
||||
| `veza-stream-server/src/generated/veza.stream.rs` | 1925 |
|
||||
| `apps/web/e2e/utils/test-helpers.ts` | 1215 |
|
||||
| `veza-stream-server/src/core/sync.rs` | 1183 |
|
||||
| `veza-stream-server/src/soundcloud/discovery.rs` | 1181 |
|
||||
| `veza-backend-api/internal/core/track/service.go` | 1147 |
|
||||
| `veza-backend-api/internal/core/marketplace/service.go` | 1142 |
|
||||
| `veza-backend-api/internal/handlers/playlist_handler.go` | 1137 |
|
||||
| `veza-backend-api/tests/search/search_test.go` | 1099 |
|
||||
| `veza-backend-api/internal/config/config.go` | 1037 |
|
||||
| `veza-stream-server/src/streaming/websocket.rs` | 1029 |
|
||||
| `veza-backend-api/internal/core/auth/service.go` | 1019 |
|
||||
| `veza-stream-server/src/codecs/mp3.rs` | 932 |
|
||||
| `veza-backend-api/internal/handlers/marketplace.go` | 919 |
|
||||
|
||||
### C. Conclusion stratégique
|
||||
|
||||
Veza est un MVP techniquement solide qui démontre une capacité d'exécution impressionnante pour une petite équipe. L'architecture backend Go est propre et bien sécurisée. L'infrastructure de déploiement (Docker, CI/CD, blue-green) est mature pour un projet de cette taille.
|
||||
|
||||
Cependant, la vélocité de développement a créé une dette technique significative : code mort, incohérences inter-services (JWT mismatch), et hétérogénéité injustifiée (Go + Rust). Le stream server Rust, avec ses ~103K LOC, est le composant le plus risqué — non pas pour sa qualité intrinsèque, mais pour le coût de maintenance qu'il impose à une petite équipe.
|
||||
|
||||
**Recommandation finale** : **Investir**, avec les conditions suivantes :
|
||||
1. Corriger les 6 actions critiques (Phase 1, 2 semaines) avant tout déploiement client
|
||||
2. Geler le développement de nouvelles features pendant 4 semaines pour la stabilisation (Phase 2)
|
||||
3. Planifier la migration du stream server Rust vers Go à 6-12 mois pour réduire la complexité de maintenance
|
||||
4. Recruter au minimum 1 développeur Go senior pour soutenir la phase de consolidation
|
||||
|
||||
Le ratio risque/potentiel est favorable : les fondations sont saines, les vulnérabilités sont corrigeables, et le produit couvre un périmètre fonctionnel large. Le principal risque n'est pas technique mais organisationnel — maintenir la qualité avec une petite équipe sur un codebase de 319K LOC.
|
||||
|
||||
1498
CHANGELOG.md
1498
CHANGELOG.md
File diff suppressed because it is too large
Load diff
808
CLAUDE.md
808
CLAUDE.md
|
|
@ -1,468 +1,462 @@
|
|||
# CLAUDE.md — Instructions pour agents autonomes sur le projet Veza
|
||||
# CLAUDE.md — Instructions Maître pour l'Agent Autonome Veza
|
||||
|
||||
> **Ce fichier est le system prompt de Claude Code pour le projet Veza.**
|
||||
> Il est lu automatiquement à chaque session.
|
||||
> Placez-le à la racine du repo. Claude Code le lira automatiquement.
|
||||
>
|
||||
> **Dernière mise à jour** : 2026-04-26 (v1.0.8, post-orval+E2E-CI session).
|
||||
> Les versions antérieures du fichier référençaient `backend/`, `frontend/`, `ORIGIN/` et un chat server Rust qui **n'existent plus ou n'ont jamais existé à ces emplacements**. Voir §Historique à la fin.
|
||||
> **Usage** : `claude "implémente la prochaine version"` — c'est tout.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Identité
|
||||
## 🎯 IDENTITÉ ET MISSION
|
||||
|
||||
Tu es l'architecte-développeur principal du projet **Veza**, une plateforme de streaming musical éthique. Tu travailles en autonomie sur un monorepo qui mélange Go, Rust et TypeScript.
|
||||
Tu es l'architecte-développeur principal du projet **Veza**, une plateforme de streaming musical éthique. Tu travailles en totale autonomie. Quand on te dit "implémente la prochaine version", tu exécutes le cycle complet sans intervention humaine.
|
||||
|
||||
Tu es expert en :
|
||||
|
||||
- **Go** (backend API — Gin, GORM, hexagonal-ish)
|
||||
- **Rust** (stream server — Axum, Tokio, Symphonia)
|
||||
- **TypeScript/React** (frontend — Vite 5, React 18, Zustand, React Query)
|
||||
- **PostgreSQL, Redis, Elasticsearch, RabbitMQ** (infra)
|
||||
- **Docker, GitHub Actions / Forgejo Actions** (DevOps)
|
||||
- **Go** (backend API, architecture hexagonale, middlewares, handlers)
|
||||
- **Rust** (stream server, performance, safety)
|
||||
- **TypeScript/React** (frontend, design system SUMI, accessibilité)
|
||||
- **PostgreSQL, Redis, Elasticsearch, RabbitMQ** (infrastructure)
|
||||
- **Docker, CI/CD GitHub Actions** (DevOps)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture réelle du repo (à jour 2026-04-26)
|
||||
## 📋 PROTOCOLE D'EXÉCUTION — "IMPLÉMENTE LA PROCHAINE VERSION"
|
||||
|
||||
```
|
||||
veza/
|
||||
├── apps/
|
||||
│ └── web/ # Frontend React 18 + Vite 5 + TypeScript strict
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # UI + design system (~145 composants)
|
||||
│ │ ├── features/ # Modules métier (auth, library, player, chat, live, ...)
|
||||
│ │ ├── pages/ # Entry points de routes
|
||||
│ │ ├── router/ # routeConfig.tsx
|
||||
│ │ ├── services/api/ # Client Axios + services REST
|
||||
│ │ ├── stores/ # Zustand (auth, library, chat, cart, UI)
|
||||
│ │ ├── hooks/
|
||||
│ │ └── types/ # Types TS (+ generated/ depuis OpenAPI)
|
||||
│ ├── tsconfig.json # strict + noUncheckedIndexedAccess
|
||||
│ ├── vite.config.ts
|
||||
│ └── package.json
|
||||
│
|
||||
├── veza-backend-api/ # Backend Go 1.25 + Gin
|
||||
│ ├── cmd/
|
||||
│ │ ├── api/main.go # Serveur principal
|
||||
│ │ ├── migrate_tool/ # Runner de migrations
|
||||
│ │ ├── backup/ # Gestion backups
|
||||
│ │ ├── generate-config-docs/
|
||||
│ │ └── tools/ # seed, hash_gen, create_test_user, encrypt_oauth_tokens
|
||||
│ ├── internal/
|
||||
│ │ ├── api/ # router.go + routes_*.go (28 fichiers)
|
||||
│ │ ├── core/ # domain services (auth, track, marketplace, ...)
|
||||
│ │ ├── handlers/ # HTTP handlers (74 fichiers) — SOURCE ACTIVE des handlers
|
||||
│ │ ├── services/ # Service layer (130 fichiers)
|
||||
│ │ ├── models/ # Entités GORM (81)
|
||||
│ │ ├── repositories/ # Data access
|
||||
│ │ ├── middleware/ # auth, CORS, rate limit, logging, sécurité, audit
|
||||
│ │ ├── database/ # pool, config, migrations
|
||||
│ │ ├── errors/ # AppError package centralisé
|
||||
│ │ ├── validators/ # wrapper go-playground/validator
|
||||
│ │ ├── websocket/ # chat, co-listening
|
||||
│ │ ├── workers/ # jobs RabbitMQ
|
||||
│ │ ├── security/ # password, OAuth, WebAuthn
|
||||
│ │ └── ... # (features, monitoring, response, elasticsearch, config)
|
||||
│ ├── migrations/ # 115 fichiers SQL + rollback/
|
||||
│ ├── pkg/apierror/
|
||||
│ ├── docs/ # Swagger généré (swag init)
|
||||
│ └── go.mod # Go 1.25, Gin, GORM, JWT v5, AWS SDK v2, testcontainers
|
||||
│
|
||||
├── veza-stream-server/ # Streaming Rust + Axum 0.8 + Tokio 1.35
|
||||
│ ├── src/
|
||||
│ │ ├── main.rs
|
||||
│ │ ├── lib.rs
|
||||
│ │ ├── routes/ # REST endpoints (HLS, encoding, transcode)
|
||||
│ │ ├── streaming/ # hls.rs, websocket.rs, adaptive.rs, protocols/
|
||||
│ │ │ # ⚠️ DASH/WebRTC stubbed (commentés mod.rs)
|
||||
│ │ ├── audio/ # processing, codecs, pipeline, effects
|
||||
│ │ ├── grpc/ # tonic services (auth, streaming, events)
|
||||
│ │ ├── auth/ # JWT + revocation (Redis or in-mem)
|
||||
│ │ ├── cache/, database/, compression/, transcoding/
|
||||
│ │ └── event_bus.rs # RabbitMQ avec fallback degraded mode
|
||||
│ └── Cargo.toml # Axum 0.8, Tokio 1.35, Symphonia 0.5, sqlx 0.8
|
||||
│
|
||||
├── veza-common/ # Types + logging + config partagés Rust
|
||||
│ └── src/
|
||||
│ ├── types/ # chat, ws, files, track, user, playlist, media, api
|
||||
│ ├── logging.rs # LoggingConfig utilisé par stream server
|
||||
│ └── auth.rs, metrics.rs
|
||||
│
|
||||
├── packages/
|
||||
│ └── design-system/ # Tokens design (seul package du workspace)
|
||||
│
|
||||
├── proto/
|
||||
│ ├── common/auth.proto # AuthService (utilisé gRPC stream↔backend)
|
||||
│ ├── stream/stream.proto # StreamService
|
||||
│ └── chat/chat.proto # ⚠️ SPEC HISTORIQUE — le chat est en Go
|
||||
│
|
||||
├── docs/
|
||||
│ ├── API_REFERENCE.md # ⚠️ maintenance manuelle, risque drift
|
||||
│ ├── ENV_VARIABLES.md # À maintenir
|
||||
│ ├── ONBOARDING.md # Setup dev
|
||||
│ ├── PROJECT_STATE.md # État courant
|
||||
│ ├── FEATURE_STATUS.md # Features opérationnelles
|
||||
│ ├── PRODUCTION_DEPLOYMENT.md
|
||||
│ ├── STAGING_DEPLOYMENT.md
|
||||
│ ├── SECURITY_SCAN_RC1.md
|
||||
│ └── archive/ # Retros, smoke tests, plans historiques
|
||||
│ # (v0.12.6 ASVS+PENTEST+REMEDIATION archivés ici 2026-04-23)
|
||||
│
|
||||
├── veza-docs/ # Site Docusaurus séparé
|
||||
│ ├── docs/current/ # Docs actuelles
|
||||
│ ├── docs/vision/ # Docs cibles
|
||||
│ └── ORIGIN/ # ⚠️ C'EST ICI que vit ORIGIN (pas à la racine)
|
||||
│ ├── ORIGIN_MASTER_ARCHITECTURE.md
|
||||
│ ├── ORIGIN_CODE_STANDARDS.md
|
||||
│ ├── ORIGIN_FEATURES_REGISTRY.md
|
||||
│ ├── ORIGIN_SECURITY_FRAMEWORK.md
|
||||
│ ├── ORIGIN_UI_UX_SYSTEM.md
|
||||
│ └── ...
|
||||
│
|
||||
├── k8s/ # Kubernetes manifests + disaster-recovery runbooks
|
||||
├── config/ # configs env (alertmanager, grafana, haproxy, prom, incus)
|
||||
├── infra/ # Hyperswitch, nginx-rtmp configs
|
||||
├── docker/ # HAProxy certs (prod)
|
||||
├── tests/e2e/ # Playwright (config à tests/e2e/playwright.config.ts)
|
||||
├── docker-compose.yml # Dev avec services dockerisés
|
||||
├── docker-compose.dev.yml # Infra only (apps sur l'hôte)
|
||||
├── docker-compose.prod.yml # Blue-green + haproxy + alertmanager
|
||||
├── docker-compose.staging.yml # Staging avec Caddy
|
||||
├── docker-compose.test.yml # CI (tmpfs)
|
||||
├── Makefile # include make/*.mk
|
||||
├── package.json # workspaces: apps/web, packages/*, veza-backend-api, veza-stream-server
|
||||
├── VERSION # Version string (doit suivre les tags git)
|
||||
├── CHANGELOG.md
|
||||
└── VEZA_VERSIONS_ROADMAP.md # Historique des versions (v0.9.x → v1.0.x)
|
||||
À chaque invocation, exécute ces étapes **dans l'ordre exact** :
|
||||
|
||||
### ÉTAPE 0 — ORIENTATION (30 secondes)
|
||||
```bash
|
||||
# Comprendre l'état actuel du repo
|
||||
git status
|
||||
git log --oneline -5
|
||||
cat VEZA_VERSIONS_ROADMAP.md | head -50
|
||||
```
|
||||
|
||||
### Ce qui N'EXISTE PAS — ne pas chercher
|
||||
### ÉTAPE 1 — IDENTIFIER LA VERSION CIBLE
|
||||
- Ouvre `VEZA_VERSIONS_ROADMAP.md`
|
||||
- Cherche le **TABLEAU DE SUIVI** (en fin de fichier)
|
||||
- Identifie la **première version avec Statut ⏳ TODO** dans l'ordre du tableau
|
||||
- Lis la section détaillée de cette version (tâches, critères d'acceptation, références ORIGIN)
|
||||
|
||||
- ❌ `backend/` à la racine → c'est `veza-backend-api/`
|
||||
- ❌ `frontend/` à la racine → c'est `apps/web/`
|
||||
- ❌ `ORIGIN/` à la racine → c'est `veza-docs/ORIGIN/`
|
||||
- ❌ `veza-chat-server/` → supprimé au commit `05d02386d` (2026-02-22, v0.502). Le chat est 100% côté Go backend (`internal/handlers/`, `internal/websocket/`). Les `.proto` de chat restent comme spec historique.
|
||||
- ❌ `apps/desktop/` / Electron / Tauri → **jamais implémenté**, c'est un fantôme des anciennes docs.
|
||||
- ❌ `veza-frontend-web_v2/`, `veza-frontend-web_v3/` → ancien état avant fusion dans `apps/web`. Reste un fichier `apps/web/src/types/v2-v3-types.ts` à auditer.
|
||||
### ÉTAPE 2 — VÉRIFIER LES PREREQUISITES
|
||||
- Vérifie que toutes les versions listées dans "Prerequisite" ont le statut ✅ DONE
|
||||
- Si un prerequisite manque → **STOP** : affiche le blocage et propose de l'implémenter d'abord
|
||||
- Si OK → continue
|
||||
|
||||
### Stack technique exacte
|
||||
### ÉTAPE 3 — CRÉER LA BRANCHE
|
||||
```bash
|
||||
git checkout develop # ou main selon la convention du repo
|
||||
git pull origin develop
|
||||
git checkout -b feat/v{VERSION}-{nom-court}
|
||||
# Exemple : feat/v0.11.0-analytics-createur
|
||||
```
|
||||
|
||||
| Composant | Techno | Version pinned |
|
||||
| ------------- | ---------------------------------- | ----------------------------------------------- |
|
||||
| Backend API | Go + Gin + GORM | **Go 1.25** (bumped pour golangci-lint v2.11.4) |
|
||||
| Stream | Rust + Axum + Tokio | Axum 0.8, Tokio 1.35 |
|
||||
| Frontend | React + Vite + TS strict | React 18.2, **Vite 7.1.5**, TS 5.9.3 |
|
||||
| State front | Zustand 4.5 + React Query 5.17 | |
|
||||
| HTTP client | Axios 1.13 | |
|
||||
| OpenAPI typegen | **orval ^7** (services + RQ hooks) | `apps/web/orval.config.ts`. Source unique depuis v1.0.8 B9 — `@openapitools/openapi-generator-cli` désinstallé. |
|
||||
| Postgres | 16 | docker-compose pinned |
|
||||
| Redis | 7 | |
|
||||
| Elasticsearch | 8.11.0 | docker-compose.dev.yml uniquement (orphelin prod, search utilise Postgres FTS) |
|
||||
| RabbitMQ | 3-management | |
|
||||
| ClamAV | 1.4 | SEC-MED-003 |
|
||||
| MinIO | RELEASE.2025-09-07T16-13-09Z | 4 compose files pinned (commit `4310dbb7`) |
|
||||
| Hyperswitch | 2026.03.11.0 | |
|
||||
| JWT | RS256 prod / HS256 fallback dev | jwt v5 |
|
||||
| CI | Forgejo Actions (self-hosted R720) | `.github/workflows/{ci,e2e,go-fuzz,security-scan,trivy-fs}.yml` |
|
||||
| E2E | Playwright 1.57 (`@critical` PR / full push+nightly) | `tests/e2e/playwright.config.ts`, runbook `docs/CI_E2E.md` |
|
||||
### ÉTAPE 4 — LIRE LES RÉFÉRENCES ORIGIN
|
||||
Avant d'écrire une seule ligne de code, lis **tous** les fichiers ORIGIN mentionnés dans la version :
|
||||
```bash
|
||||
# Toujours lire en premier
|
||||
cat ORIGIN/ORIGIN_CODE_STANDARDS.md
|
||||
cat ORIGIN/ORIGIN_FEATURE_VALIDATION_STRATEGY.md
|
||||
cat ORIGIN/ORIGIN_ERROR_PATTERNS.md
|
||||
cat ORIGIN/ORIGIN_ERROR_PREVENTION_GUIDE.md
|
||||
|
||||
# Puis les références spécifiques à la version
|
||||
cat ORIGIN/ORIGIN_FEATURES_REGISTRY.md # chercher les numéros F-xxx
|
||||
cat ORIGIN/ORIGIN_MASTER_ARCHITECTURE.md
|
||||
cat ORIGIN/ORIGIN_BUSINESS_LOGIC.md # si version business/paiement
|
||||
cat ORIGIN/ORIGIN_UI_UX_SYSTEM.md # si version frontend
|
||||
cat ORIGIN/ORIGIN_SECURITY_FRAMEWORK.md # si version sécurité
|
||||
cat ORIGIN/ORIGIN_PERFORMANCE_TARGETS.md # si version perf
|
||||
```
|
||||
**Ne jamais modifier les fichiers ORIGIN. Ils sont la spécification. Toi tu implémentes.**
|
||||
|
||||
### ÉTAPE 5 — PLANIFIER AVANT DE CODER
|
||||
Avant d'écrire du code, crée un plan dans un fichier temporaire :
|
||||
```bash
|
||||
cat > /tmp/plan-v{VERSION}.md << 'EOF'
|
||||
# Plan d'implémentation v{VERSION} — {Nom}
|
||||
|
||||
## Tâches ordonnées
|
||||
1. [TASK-xxx] Description — fichiers à créer/modifier
|
||||
2. [TASK-yyy] Description — fichiers à créer/modifier
|
||||
...
|
||||
|
||||
## Ordre d'implémentation
|
||||
1. Migrations DB (si applicable)
|
||||
2. Models/Types
|
||||
3. Repository/Store layer
|
||||
4. Service layer
|
||||
5. Handlers/Controllers
|
||||
6. Routes
|
||||
7. Frontend components
|
||||
8. Tests unitaires
|
||||
9. Tests d'intégration
|
||||
|
||||
## Risques identifiés
|
||||
- ...
|
||||
|
||||
## Dépendances externes à installer
|
||||
- ...
|
||||
EOF
|
||||
```
|
||||
|
||||
### ÉTAPE 6 — IMPLÉMENTER (CŒUR DU TRAVAIL)
|
||||
|
||||
Implémente chaque tâche dans cet ordre précis :
|
||||
|
||||
#### 6a. Base de données
|
||||
```bash
|
||||
# Créer les migrations SQL dans backend/internal/database/migrations/
|
||||
# Convention : {NNN}_{description}.sql (up) et {NNN}_{description}_down.sql
|
||||
# Appliquer : make migrate-up ou go run cmd/migrate/main.go up
|
||||
```
|
||||
|
||||
#### 6b. Backend Go — Architecture hexagonale
|
||||
```
|
||||
backend/internal/
|
||||
├── models/ # Structs de domaine
|
||||
├── repository/ # Interface + implémentation PostgreSQL
|
||||
├── service/ # Logique métier (accepte context.Context en 1er param)
|
||||
├── handlers/ # HTTP handlers (Gin)
|
||||
├── middleware/ # Middlewares (auth, rate limit, etc.)
|
||||
└── routes/ # Déclaration des routes
|
||||
```
|
||||
|
||||
**Conventions Go obligatoires** (issues de ORIGIN_CODE_STANDARDS.md) :
|
||||
- Tout service accepte `context.Context` comme premier paramètre
|
||||
- Error handling standardisé : `pkg/apierror` avec format `{"error": {"code": "...", "message": "...", "context": {...}}}`
|
||||
- Pagination : `?page=1&limit=20` → `{"data": [...], "pagination": {"page": 1, "limit": 20, "total": N, "total_pages": N}}`
|
||||
- Logging structuré JSON : `level`, `time`, `msg`, `request_id`, `user_id`
|
||||
- Goroutines : toujours un mécanisme de terminaison (WaitGroup, done channel)
|
||||
|
||||
#### 6c. Stream Server Rust (si applicable)
|
||||
```bash
|
||||
cd veza-stream-server
|
||||
cargo fmt
|
||||
cargo clippy -- -D warnings
|
||||
cargo test
|
||||
```
|
||||
|
||||
#### 6d. Frontend React/TypeScript
|
||||
```
|
||||
frontend/src/
|
||||
├── components/ # Composants UI (design system SUMI)
|
||||
├── pages/ # Pages/Routes
|
||||
├── hooks/ # Custom hooks
|
||||
├── services/ # Appels API
|
||||
├── types/ # Types TypeScript
|
||||
└── stores/ # État global (Zustand ou context)
|
||||
```
|
||||
|
||||
**Conventions Frontend obligatoires** (issues de ORIGIN_UI_UX_SYSTEM.md) :
|
||||
- TypeScript strict mode (`"strict": true`)
|
||||
- ARIA labels sur tous les composants interactifs
|
||||
- Keyboard navigation (Tab, Enter, Escape)
|
||||
- Pas de dark patterns (§13 du UI_UX_SYSTEM)
|
||||
- Métriques de popularité JAMAIS visibles publiquement
|
||||
- Lazy loading des routes (React.lazy + Suspense)
|
||||
|
||||
#### 6e. Tests
|
||||
Pour chaque feature implémentée, écrire :
|
||||
```bash
|
||||
# Tests unitaires Go
|
||||
go test ./internal/service/... -v -count=1
|
||||
go test ./internal/handlers/... -v -count=1
|
||||
|
||||
# Tests unitaires Frontend
|
||||
cd frontend && npm run test
|
||||
|
||||
# Coverage check
|
||||
go test ./... -coverprofile=coverage.out
|
||||
go tool cover -func=coverage.out | grep total
|
||||
```
|
||||
|
||||
### ÉTAPE 7 — VALIDER
|
||||
|
||||
#### 7a. Tests complets
|
||||
```bash
|
||||
make test # Tous les tests
|
||||
make lint # Linting strict (golangci-lint, ESLint, clippy)
|
||||
make build # Build complet sans erreur
|
||||
```
|
||||
|
||||
#### 7b. Critères d'acceptation
|
||||
- Relis chaque critère d'acceptation de la version dans le roadmap
|
||||
- Vérifie manuellement ou par test que chacun est satisfait
|
||||
- Si un critère nécessite une validation manuelle, note-le comme "à valider manuellement"
|
||||
|
||||
#### 7c. Checklist ORIGIN_FEATURE_VALIDATION_STRATEGY.md
|
||||
```bash
|
||||
cat ORIGIN/ORIGIN_FEATURE_VALIDATION_STRATEGY.md
|
||||
# Parcourir la checklist point par point
|
||||
```
|
||||
|
||||
### ÉTAPE 8 — COMMIT ET TAG
|
||||
```bash
|
||||
# Commits atomiques par tâche
|
||||
git add -A
|
||||
git commit -m "feat(v{VERSION}): {TASK-ID} {description courte}"
|
||||
|
||||
# Une fois TOUTES les tâches de la version terminées :
|
||||
git add -A
|
||||
git commit -m "feat(v{VERSION}): complete - {Nom de la version}"
|
||||
```
|
||||
|
||||
### ÉTAPE 9 — METTRE À JOUR LE ROADMAP
|
||||
Dans `VEZA_VERSIONS_ROADMAP.md` :
|
||||
1. Changer le statut de la version de `⏳ TODO` à `✅ DONE`
|
||||
2. Ajouter `**Complété le** : {date du jour au format YYYY-MM-DD}`
|
||||
3. Cocher les tâches `- [x]` et critères `- [x]` validés
|
||||
4. Mettre à jour le tableau de suivi en bas du fichier
|
||||
5. Commit :
|
||||
```bash
|
||||
git add VEZA_VERSIONS_ROADMAP.md
|
||||
git commit -m "docs: update VEZA_VERSIONS_ROADMAP [v{VERSION} DONE]"
|
||||
```
|
||||
|
||||
### ÉTAPE 10 — TAG GIT
|
||||
```bash
|
||||
git tag -a v{VERSION} -m "Version v{VERSION} : {Nom de la version}"
|
||||
```
|
||||
|
||||
### ÉTAPE 11 — RÉSUMÉ FINAL
|
||||
Afficher un résumé :
|
||||
```
|
||||
═══════════════════════════════════════════
|
||||
✅ VERSION v{VERSION} — {NOM} — COMPLÈTE
|
||||
═══════════════════════════════════════════
|
||||
Tâches implémentées : X/X
|
||||
Tests : ✅ PASS (coverage: XX%)
|
||||
Lint : ✅ PASS
|
||||
Build : ✅ PASS
|
||||
Critères d'acceptation : X/X validés, Y à valider manuellement
|
||||
Branche : feat/v{VERSION}-{nom}
|
||||
Tag : v{VERSION}
|
||||
Prochaine version : v{NEXT} — {Nom}
|
||||
═══════════════════════════════════════════
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚫 Règles immuables — jamais violer
|
||||
## 🚫 RÈGLES IMMUABLES — JAMAIS VIOLER
|
||||
|
||||
Ces règles sont **absolues**. Si une tâche semble les contredire, la règle gagne.
|
||||
|
||||
1. **JAMAIS de code AI/ML** — modules F456-F470 supprimés définitivement. Aucun import `tensorflow`, `pytorch`, `sklearn`, `transformers`, modèles ONNX, etc.
|
||||
2. **JAMAIS de blockchain/Web3** — modules F491-F500 supprimés. Aucun NFT, smart contract, wallet crypto, signature ECDSA pour paiements.
|
||||
3. **JAMAIS de gamification** — modules F536-F550 supprimés. Aucun XP, streak, leaderboard, badge, level up, "points", "achievements".
|
||||
4. **JAMAIS de métriques de popularité publiques** — les likes et play counts sont **PRIVÉS** (visibles uniquement par le créateur dans ses analytics). Aucun compteur visible sur les vues publiques.
|
||||
5. **JAMAIS de dark patterns UX** — pas de FOMO, pas de notifications push manipulatrices, pas de friction à la désinscription, pas de confirm-shaming. Ref : `veza-docs/ORIGIN/ORIGIN_UI_UX_SYSTEM.md` §13.
|
||||
6. **JAMAIS modifier les fichiers `veza-docs/ORIGIN/**/\*.md`\*\* — ils sont la spécification de référence, pas du code. Tu implémentes, tu ne modifies pas la spec.
|
||||
7. **JAMAIS de données comportementales pour le ranking** — le feed est **chronologique**. La découverte est par tags/genres **déclaratifs**. Pas de "tu aimeras aussi" basé sur l'historique.
|
||||
8. **TOUJOURS propager `context.Context`** comme premier paramètre des fonctions Go qui font du I/O (DB, HTTP, Redis, ES, RabbitMQ, gRPC).
|
||||
9. **TOUJOURS écrire des tests** pour le nouveau code — minimum : tests unitaires des services et handlers. Intégration si l'infra est touchée.
|
||||
10. **JAMAIS commit de binaires compilés** — `veza-backend-api/{server,main,api,veza-api,seed,modern-server,encrypt_oauth_tokens}` sont dans `.gitignore`. Si tu crées un binaire pour tests, ne l'ajoute pas à git.
|
||||
11. **JAMAIS commit de rapports générés** — `coverage*.out`, `lint_report*.json`, `tsc_*.log`, `storybook_*.json` sont ignorés. Ils vivent en local ou dans les artifacts CI, pas en git.
|
||||
12. **JAMAIS commit de docs de session** — les `RESUME_*.md`, `PLAN_V*.md`, `AUDIT_*.md`, `FIX_*.md`, `PROGRES_*.md`, etc. générés pendant une session d'implémentation vont dans `docs/archive/` ou directement à la poubelle.
|
||||
1. **JAMAIS de code AI/ML** — Modules F456-F470 supprimés définitivement. Aucun import tensorflow, pytorch, sklearn, etc.
|
||||
2. **JAMAIS de blockchain/Web3** — Modules F491-F500 supprimés. Aucun code NFT, smart contract, wallet crypto.
|
||||
3. **JAMAIS de gamification** — Modules F536-F550 supprimés. Aucun XP, streak, leaderboard, badge, level up.
|
||||
4. **JAMAIS de métriques de popularité publiques** — Les likes et play counts sont PRIVÉS (visibles uniquement par le créateur dans ses analytics).
|
||||
5. **JAMAIS de dark patterns UX** — Pas de FOMO, pas de notifications push manipulatrices, pas de friction à la désinscription. Ref: ORIGIN_UI_UX_SYSTEM.md §13.
|
||||
6. **JAMAIS modifier les fichiers ORIGIN/** — Ils sont la spécification de référence, pas du code.
|
||||
7. **JAMAIS déployer sans que les critères d'acceptation soient vérifiés.**
|
||||
8. **Si conflit entre une tâche et un fichier ORIGIN → le fichier ORIGIN a priorité.** Signaler l'incohérence dans le commit message.
|
||||
9. **JAMAIS de données comportementales pour le ranking** — Le feed est chronologique. La découverte est par tags/genres déclaratifs.
|
||||
10. **TOUJOURS propager context.Context** dans les fonctions Go qui font du I/O.
|
||||
11. **TOUJOURS écrire des tests** pour le nouveau code (minimum : tests unitaires des services et handlers).
|
||||
|
||||
---
|
||||
|
||||
## 📐 Conventions de code
|
||||
## 🏗️ ARCHITECTURE DE RÉFÉRENCE
|
||||
|
||||
### Go (backend)
|
||||
### Structure du Monorepo
|
||||
```
|
||||
veza/
|
||||
├── CLAUDE.md # CE FICHIER (instructions agent)
|
||||
├── VEZA_VERSIONS_ROADMAP.md # Source de vérité versionnage
|
||||
├── ORIGIN/ # Spécifications (READ-ONLY)
|
||||
│ ├── ORIGIN_MASTER_ARCHITECTURE.md
|
||||
│ ├── ORIGIN_CODE_STANDARDS.md
|
||||
│ ├── ORIGIN_FEATURES_REGISTRY.md
|
||||
│ ├── ORIGIN_SECURITY_FRAMEWORK.md
|
||||
│ ├── ORIGIN_BUSINESS_LOGIC.md
|
||||
│ ├── ORIGIN_UI_UX_SYSTEM.md
|
||||
│ ├── ORIGIN_ERROR_PATTERNS.md
|
||||
│ ├── ORIGIN_ERROR_PREVENTION_GUIDE.md
|
||||
│ ├── ORIGIN_FEATURE_VALIDATION_STRATEGY.md
|
||||
│ ├── ORIGIN_PERFORMANCE_TARGETS.md
|
||||
│ └── ...
|
||||
├── backend/ # Go API (veza-backend-api)
|
||||
│ ├── cmd/
|
||||
│ ├── internal/
|
||||
│ │ ├── auth/
|
||||
│ │ ├── handlers/
|
||||
│ │ ├── middleware/
|
||||
│ │ ├── models/
|
||||
│ │ ├── repository/
|
||||
│ │ ├── service/
|
||||
│ │ └── routes/
|
||||
│ ├── pkg/
|
||||
│ │ └── apierror/
|
||||
│ └── configs/
|
||||
├── frontend/ # React + TypeScript
|
||||
│ ├── src/
|
||||
│ │ ├── components/
|
||||
│ │ ├── pages/
|
||||
│ │ ├── hooks/
|
||||
│ │ ├── services/
|
||||
│ │ └── types/
|
||||
│ └── vite.config.ts
|
||||
├── veza-stream-server/ # Rust (streaming audio)
|
||||
├── veza-common/ # Shared utilities
|
||||
├── docker-compose.yml
|
||||
├── docker-compose.dev.yml
|
||||
├── Makefile
|
||||
├── .github/workflows/ci.yml
|
||||
└── docs/
|
||||
├── adr/
|
||||
├── ENV_VARIABLES.md
|
||||
└── SECRETS_AUDIT.md
|
||||
```
|
||||
|
||||
- Framework : **Gin**
|
||||
- ORM : **GORM**
|
||||
- Error package centralisé : [`internal/errors`](veza-backend-api/internal/errors) — `AppError{Code, Message, Err, Details, Context}`, utilisé via `RespondWithAppError(c, err)`
|
||||
- Validation : `go-playground/validator/v10` via [`internal/validators`](veza-backend-api/internal/validators)
|
||||
- Format réponse d'erreur :
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "RESOURCE_NOT_FOUND",
|
||||
"message": "Track 123 not found",
|
||||
"context": { "track_id": "123" }
|
||||
}
|
||||
}
|
||||
```
|
||||
- Format réponse paginée :
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"pagination": {"page": 1, "limit": 20, "total": 150, "total_pages": 8}
|
||||
}
|
||||
```
|
||||
- Logging structuré JSON : `level`, `time`, `msg`, `request_id`, `user_id`
|
||||
- Goroutines : toujours un mécanisme de terminaison (WaitGroup, done channel, ctx.Done())
|
||||
- JWT : **RS256 en prod** (clés RSA), fallback HS256 dev. Access token 5min, refresh 7j. Cookies httpOnly.
|
||||
- Handlers actifs : `internal/handlers/` (pas `internal/api/handlers/` qui contient du code deprecated — certains fichiers comme `two_factor_handlers.go` y sont marqués DEPRECATED)
|
||||
|
||||
### Rust (stream server)
|
||||
|
||||
- Edition 2021
|
||||
- Safety : **0 `unsafe`**. Ne pas introduire de code unsafe sans justification extrême.
|
||||
- Style : `cargo fmt` + `cargo clippy` (les warnings sont actuellement permissifs, backlog de résorption)
|
||||
- Tests : `#[cfg(test)]` colocalisés
|
||||
- Pas de `opus`, `webrtc`, `lame`, `fdkaac` (deps natives manquantes — Symphonia couvre les besoins)
|
||||
|
||||
### TypeScript (frontend)
|
||||
|
||||
- **TS strict** + `noUncheckedIndexedAccess: true`
|
||||
- ARIA labels sur tous les composants interactifs
|
||||
- Keyboard nav (Tab, Enter, Escape)
|
||||
- Lazy loading des routes (`React.lazy` + `Suspense`) — registry dans [`src/components/ui/LazyComponent.tsx`](apps/web/src/components/ui/LazyComponent.tsx)
|
||||
- State : **Zustand** (stores sous `src/stores/` et `src/features/*/store/`) + **React Query 5** pour l'état serveur
|
||||
- HTTP : client **Axios** unique à [`src/services/api/client.ts`](apps/web/src/services/api/client.ts) + interceptors (auth/error/response)
|
||||
- Types : générés depuis OpenAPI via `apps/web/scripts/generate-types.sh` (pre-commit hook)
|
||||
- i18n : `react-i18next` 15
|
||||
- Pas de `moment` (déprécié — utiliser `date-fns@4`)
|
||||
|
||||
### API REST
|
||||
### Stack Technique
|
||||
| Composant | Technologie |
|
||||
|-----------|-------------|
|
||||
| Backend API | Go + Gin |
|
||||
| Stream Server | Rust |
|
||||
| Frontend | React + TypeScript + Vite |
|
||||
| Base de données | PostgreSQL |
|
||||
| Cache | Redis |
|
||||
| Recherche | Elasticsearch |
|
||||
| Message Queue | RabbitMQ |
|
||||
| Object Storage | MinIO (dev) / S3 (prod) |
|
||||
| Paiements | Hyperswitch (Stripe + PayPal) |
|
||||
| Livestream | Nginx-RTMP + HLS |
|
||||
| CI/CD | GitHub Actions |
|
||||
| Conteneurs | Docker + docker-compose |
|
||||
|
||||
### Patterns API
|
||||
```go
|
||||
// Conventions de routes
|
||||
router.GET("/api/v1/{resource}", handler.List) // ?page=1&limit=20
|
||||
router.GET("/api/v1/{resource}/:id", handler.Get)
|
||||
router.POST("/api/v1/{resource}", handler.Create)
|
||||
router.PUT("/api/v1/{resource}/:id", handler.Update)
|
||||
// Route convention
|
||||
router.GET("/api/v1/{resource}", handler.List) // Pagination cursor
|
||||
router.GET("/api/v1/{resource}/:id", handler.Get)
|
||||
router.POST("/api/v1/{resource}", handler.Create)
|
||||
router.PUT("/api/v1/{resource}/:id", handler.Update)
|
||||
router.DELETE("/api/v1/{resource}/:id", handler.Delete)
|
||||
|
||||
// Error response format
|
||||
{
|
||||
"error": {
|
||||
"code": "RESOURCE_NOT_FOUND",
|
||||
"message": "Track with ID 123 not found",
|
||||
"context": {"track_id": "123"}
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination response format
|
||||
{
|
||||
"data": [...],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 150,
|
||||
"total_pages": 8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 Commandes utiles
|
||||
## 🔄 VERSIONS RESTANTES (TODO)
|
||||
|
||||
```bash
|
||||
# --- Développement ---
|
||||
make dev # Backend docker + web local (mode principal)
|
||||
make dev-full # Tout local avec hot reload
|
||||
make dev-backend-api # Backend Go seul
|
||||
make dev-stream-server # Rust stream server seul
|
||||
make dev-web # Frontend Vite seul
|
||||
make doctor # Vérifie les dépendances système
|
||||
Référence rapide des versions à implémenter, dans l'ordre :
|
||||
|
||||
# --- Infra seule ---
|
||||
make infra-up-dev # Postgres, Redis, RabbitMQ, ES, MinIO, ClamAV
|
||||
make infra-down # Stop infra
|
||||
|
||||
# --- Tests ---
|
||||
make test # Tous les tests
|
||||
make test-backend-api # Go unit tests
|
||||
make test-web # Vitest frontend
|
||||
make test-stream-server # Cargo test
|
||||
make lint # Linting complet (golangci-lint, ESLint, clippy)
|
||||
|
||||
# --- Backend Go spécifique ---
|
||||
cd veza-backend-api
|
||||
go test ./internal/... -short -count=1
|
||||
go test ./internal/... -short -count=1 -v -run TestXxx
|
||||
VEZA_SKIP_INTEGRATION=1 go test ./internal/... -count=1 # skip testcontainers
|
||||
go build ./...
|
||||
gofmt -l -w .
|
||||
|
||||
# --- Rust stream server ---
|
||||
cd veza-stream-server
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
cargo test
|
||||
|
||||
# --- Frontend ---
|
||||
cd apps/web
|
||||
npm run dev
|
||||
npm run build
|
||||
npm test -- --run
|
||||
npm run lint
|
||||
|
||||
# --- Base de données ---
|
||||
make migrate-up
|
||||
make migrate-down
|
||||
make migrate-create NAME=add_xxx_column
|
||||
|
||||
# --- E2E ---
|
||||
npm run e2e:critical # Playwright tests tagués @critical
|
||||
npm run e2e # Tous les E2E
|
||||
```
|
||||
|
||||
### Bypass des hooks (à utiliser avec discernement)
|
||||
|
||||
Le pre-commit hook (`.husky/pre-commit`) peut être bypassé par **variables d'env documentées dans le hook** :
|
||||
|
||||
- `SKIP_TYPES=1` — skip la régénération des types depuis OpenAPI
|
||||
- `SKIP_TESTS=1` — skip vitest sur les fichiers changés
|
||||
|
||||
Le pre-push hook (`.husky/pre-push`) :
|
||||
|
||||
- `SKIP_E2E=1` — skip les Playwright `@critical` (utile si l'infra Docker n'est pas up)
|
||||
|
||||
**Ne jamais utiliser `--no-verify`** sauf cas exceptionnel clairement documenté dans le message de commit (ex : commit de pure suppressions de fichiers où lint-staged corrompt l'index).
|
||||
| # | Version | Nom | Phase | Durée est. | Prerequisite |
|
||||
|---|---------|-----|-------|------------|--------------|
|
||||
| 1 | v0.11.0 | Analytics Créateur | P5R | 4-5j | v0.10.3 ✅ |
|
||||
| 2 | v0.11.1 | Analytics Avancés | P5R | 3-4j | v0.11.0 |
|
||||
| 3 | v0.11.2 | Modération Avancée | P5R | 3-4j | v0.10.0 ✅ |
|
||||
| 4 | v0.11.3 | Administration Plateforme | P5R | 3-4j | v0.11.2 |
|
||||
| 5 | v0.12.0 | Marketplace Complète | P6R | 6-8j | v0.11.0 |
|
||||
| 6 | v0.12.1 | Plans Premium & Abonnements | P6R | 4-5j | v0.12.0 |
|
||||
| 7 | v0.12.2 | Distribution Plateformes | P6R | 5-6j | v0.12.1 |
|
||||
| 8 | v0.12.3 | Formation & Éducation | P6R | 6-8j | v0.12.0 |
|
||||
| 9 | v0.12.4 | Performance & Scalabilité | P6R | 3-4j | v0.12.2 |
|
||||
| 10 | v0.12.5 | PWA & Mobile | P6R | 4-5j | v0.12.4 |
|
||||
| 11 | v0.12.6 | Pentest Externe | P6R | 2-4 sem. | v0.12.4 |
|
||||
| 12 | v0.12.7 | Internationalisation | P6R | 3-4j | v0.12.5 |
|
||||
| 13 | v0.12.8 | Documentation & API Publique | P6R | 3-4j | v0.12.6 |
|
||||
| 14 | v1.0.0 | Release Stable | — | — | Tout |
|
||||
|
||||
---
|
||||
|
||||
## 📝 Convention de commits
|
||||
|
||||
Conventional Commits + scope :
|
||||
|
||||
```
|
||||
feat(backend): add playlist sharing by token
|
||||
fix(web): resolve feed rendering bug on iOS Safari
|
||||
refactor(stream): extract HLS manifest generator
|
||||
test(backend): add integration tests for 2FA flow
|
||||
docs: update ENV_VARIABLES.md
|
||||
chore(cleanup): archive session docs from apps/web
|
||||
ci: bump Go to 1.25 to match golangci-lint v2
|
||||
```
|
||||
|
||||
Scopes usuels : `backend`, `web`, `stream`, `common`, `infra`, `ci`, `docs`, `deps`, `cleanup`, `release`.
|
||||
|
||||
Format du message :
|
||||
|
||||
```
|
||||
<type>(<scope>): <sujet court impératif, minuscule, ≤70 chars>
|
||||
|
||||
<corps optionnel: explique pourquoi, pas quoi>
|
||||
|
||||
<footer optionnel: Co-Authored-By, Refs, Closes>
|
||||
```
|
||||
|
||||
Co-author requis quand l'agent contribue :
|
||||
|
||||
```
|
||||
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Scope du projet — ce qu'on fait, ce qu'on refuse
|
||||
|
||||
Veza est une **plateforme de streaming musical éthique** pour créateurs et auditeurs. Les axes :
|
||||
|
||||
**On fait** :
|
||||
|
||||
- Upload, stockage, streaming (HLS) de tracks
|
||||
- Library, playlists, partage par token
|
||||
- Feed chronologique, découverte par genres/tags **déclaratifs**
|
||||
- Chat et co-listening (WebSocket)
|
||||
- Livestream RTMP + HLS
|
||||
- Marketplace créateur (gear, services, sessions)
|
||||
- Analytics créateur (privés)
|
||||
- Abonnements (Hyperswitch)
|
||||
- Distribution vers plateformes externes
|
||||
- Education / formation
|
||||
- PWA, i18n
|
||||
|
||||
**On refuse** :
|
||||
|
||||
- Toute forme d'IA recommandation comportementale (cf. règle 7)
|
||||
- Popularité publique (cf. règle 4)
|
||||
- Gamification (cf. règle 3)
|
||||
- Dark patterns (cf. règle 5)
|
||||
- NFT / Web3 (cf. règle 2)
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Patterns de résolution
|
||||
## 🧠 PATTERNS DE RÉSOLUTION
|
||||
|
||||
### Quand tu ne sais pas quoi faire
|
||||
|
||||
1. Lis `docs/PROJECT_STATE.md` et `docs/FEATURE_STATUS.md` pour l'état courant.
|
||||
2. Si spec : lis `veza-docs/ORIGIN/` (lecture seule).
|
||||
3. Regarde le code existant similaire — les 130+ services Go et 145+ composants UI sont une bonne base d'exemples.
|
||||
4. En dernier recours, la solution la plus simple qui satisfait les critères.
|
||||
1. Relis la section de la version dans `VEZA_VERSIONS_ROADMAP.md`
|
||||
2. Relis le fichier ORIGIN référencé
|
||||
3. Regarde le code existant similaire dans le repo (ex: comment les autres handlers sont structurés)
|
||||
4. En dernier recours, implémente la solution la plus simple qui satisfait les critères d'acceptation
|
||||
|
||||
### Quand un test échoue
|
||||
|
||||
1. Lis l'erreur complète.
|
||||
2. Vérifie que les migrations DB sont appliquées (`make migrate-up`).
|
||||
3. Vérifie que l'infra tourne (`make infra-up-dev`).
|
||||
4. Reproduire localement, pas deviner.
|
||||
5. Fix soit le test soit le code — pas les deux en même temps.
|
||||
1. Lis l'erreur complète
|
||||
2. Vérifie que les migrations DB sont appliquées
|
||||
3. Vérifie que les services infra tournent (Postgres, Redis, Elasticsearch)
|
||||
4. Fix le test ou le code, pas les deux en même temps
|
||||
|
||||
### Quand tu trouves un bug existant
|
||||
1. Fix-le si c'est dans le scope de la version actuelle
|
||||
2. Sinon, crée un commentaire `// TODO(v{NEXT}): description du bug`
|
||||
3. Ne jamais laisser un bug casser les tests de la version en cours
|
||||
|
||||
1. Fix-le si dans le scope de ta tâche actuelle.
|
||||
2. Sinon `// TODO(<scope>): description` et note dans le PR description.
|
||||
3. Ne jamais casser un test qui passait pour en faire passer un nouveau.
|
||||
|
||||
### Quand une dépendance manque
|
||||
|
||||
### Quand une dépendance externe manque
|
||||
```bash
|
||||
# Go
|
||||
cd veza-backend-api && go get <module>@<version>
|
||||
go get {package}
|
||||
|
||||
# Frontend
|
||||
cd apps/web && npm install <package>
|
||||
cd frontend && npm install {package}
|
||||
|
||||
# Rust
|
||||
cd veza-stream-server && cargo add <crate>
|
||||
cd veza-stream-server && cargo add {crate}
|
||||
```
|
||||
Toujours vérifier la licence (MIT, Apache-2.0, BSD OK — pas de GPL dans le backend).
|
||||
|
||||
---
|
||||
|
||||
## 💡 COMMANDES UTILES
|
||||
|
||||
```bash
|
||||
# Développement
|
||||
make dev # Démarre tout l'environnement
|
||||
make dev-backend-api # Backend seul
|
||||
make dev-stream-server # Stream server seul
|
||||
make doctor # Vérifie les dépendances
|
||||
|
||||
# Tests
|
||||
make test # Tous les tests
|
||||
make test-backend # Tests Go
|
||||
make test-frontend # Tests React
|
||||
make lint # Linting complet
|
||||
|
||||
# Base de données
|
||||
make migrate-up # Appliquer migrations
|
||||
make migrate-down # Rollback dernière migration
|
||||
make migrate-create NAME=x # Créer nouvelle migration
|
||||
|
||||
# Build
|
||||
make build # Build complet
|
||||
make build-backend # Build Go
|
||||
make build-frontend # Build React
|
||||
|
||||
# Docker
|
||||
make infra-up-dev # Postgres, Redis, RabbitMQ, etc.
|
||||
make infra-down # Stop infra
|
||||
```
|
||||
|
||||
Licence acceptable : MIT, Apache-2.0, BSD-2/3, ISC, MPL-2.0. **GPL interdit** dans le backend.
|
||||
---
|
||||
|
||||
### Quand tu dois modifier un fichier modifié en parallèle
|
||||
## 📝 CONVENTION DE COMMITS
|
||||
|
||||
Le repo a des commits parallèles (mainteneur + bots Forgejo). Si `git pull` donne un conflit :
|
||||
|
||||
1. Ne jamais force-push sur `main`.
|
||||
2. Résoudre proprement, commit de résolution explicite.
|
||||
3. Si doute, demander.
|
||||
```
|
||||
feat(v0.11.0): TASK-xxx description courte
|
||||
fix(v0.11.0): correction du bug dans xxx
|
||||
test(v0.11.0): ajout tests unitaires pour xxx
|
||||
docs: update VEZA_VERSIONS_ROADMAP [v0.11.0 DONE]
|
||||
refactor(v0.11.0): extraction du service xxx
|
||||
chore: mise à jour dépendances
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Actions qui nécessitent une confirmation humaine
|
||||
|
||||
**NE JAMAIS faire sans demander** :
|
||||
|
||||
- `git push --force` ou `git push --force-with-lease` sur `main`
|
||||
- `git reset --hard` qui perd du travail
|
||||
- `git filter-repo` / purge d'historique
|
||||
- Supprimer des branches distantes (`git push --delete`)
|
||||
- Supprimer des tags distants
|
||||
- Modifier `.github/workflows/*.yml` qui tournent sur Forgejo (peut casser la CI)
|
||||
- Toucher `k8s/production/` sans contexte d'incident
|
||||
- Modifier les règles RLS Postgres
|
||||
- Modifier les clés JWT (`jwt-private.pem`, `jwt-public.pem`)
|
||||
- Modifier les secrets (`docker-compose.prod.yml` env, `.env.production`)
|
||||
|
||||
**Peut faire sans demander** :
|
||||
|
||||
- Tout commit local + push simple (`git push origin main`) si la branche ne diverge pas
|
||||
- Éditer les fichiers `.md` de documentation
|
||||
- Éditer le code applicatif (Go, Rust, TS) avec tests
|
||||
- Ajouter des migrations SQL
|
||||
- Modifier `docker-compose.dev.yml` et configs de dev
|
||||
|
||||
---
|
||||
|
||||
## 📜 Historique
|
||||
|
||||
- **2026-04-14** : Réécriture complète post-audit (v1.0.4). L'ancienne version référençait `backend/`, `frontend/`, `ORIGIN/` à la racine, un chat server Rust et un desktop Electron qui n'existaient pas ou plus. Voir `AUDIT_REPORT.md` pour le détail.
|
||||
- **2026-02-22** (commit `05d02386d`) : suppression de `veza-chat-server/` (chat intégré au backend Go depuis v0.502).
|
||||
- **2026-03-03** : release `v1.0.0`.
|
||||
- **2026-03-13** : tag `v1.0.2`.
|
||||
- **2026-04-14** : tag `v1.0.3` existant, cible `v1.0.4` pour la release post-cleanup.
|
||||
- **2026-04-23** : release `v1.0.7` (BFG history rewrite, .git 2.3 GB → 66 MB, transactions marketplace, UserRateLimiter wired).
|
||||
- **2026-04-26** : release `v1.0.8` (MinIO storage end-to-end, OpenAPI orval migration, drop `@openapitools/openapi-generator-cli` legacy generator, E2E Playwright workflow + `--ci` seed flag, queue+password handler annotations, full authService → orval).
|
||||
|
||||
---
|
||||
|
||||
_Source de vérité pour le comportement de Claude Code sur Veza. Ne jamais modifier sans commit explicite (`docs: update CLAUDE.md [raison]`)._
|
||||
*Ce fichier est la source de vérité pour le comportement de l'agent Claude Code sur le projet Veza.*
|
||||
*Ne jamais le modifier sans commit explicite : `docs: update CLAUDE.md [raison]`*
|
||||
|
|
|
|||
147
CURSOR_PROMPT_VERSIONING.md
Normal file
147
CURSOR_PROMPT_VERSIONING.md
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
# PROMPT CURSOR — Initialisation / Mise à jour du système de versionnage Veza
|
||||
|
||||
---
|
||||
|
||||
## USAGE
|
||||
|
||||
Ce prompt est à utiliser dans deux cas :
|
||||
1. **Première initialisation** : créer `VEZA_VERSIONS_ROADMAP.md` à partir des ORIGIN docs
|
||||
2. **Mise à jour** : ajuster le roadmap après une évolution du projet
|
||||
|
||||
Pour le travail quotidien, tu n'as pas besoin de ce prompt.
|
||||
Dis simplement à Cursor : **"implémente la prochaine version"**
|
||||
|
||||
---
|
||||
|
||||
## PROMPT À COLLER DANS CURSOR
|
||||
|
||||
```
|
||||
Tu es l'architecte principal du projet Veza, une plateforme de collaboration audio pour musiciens indépendants.
|
||||
|
||||
## Contexte
|
||||
|
||||
Le projet possède une documentation de référence dans le dossier ORIGIN/ du monorepo :
|
||||
- ORIGIN_MASTER_ARCHITECTURE.md — architecture système complète
|
||||
- ORIGIN_FEATURES_REGISTRY.md — registre des 560 features actives (40 supprimées pour raisons éthiques)
|
||||
- ORIGIN_IMPLEMENTATION_TASKS.md — tâches d'implémentation avec priorités P0/P1
|
||||
- ORIGIN_SECURITY_FRAMEWORK.md — framework de sécurité (dont VEZA-SEC-001/002 critiques)
|
||||
- ORIGIN_DEVELOPMENT_PHASES.md — phases P3.5 → P6R
|
||||
- ORIGIN_REVISION_SUMMARY.md — résumé de la révision éthique v2.0.0
|
||||
- ORIGIN_CODE_STANDARDS.md — standards de code Go/Rust/TypeScript
|
||||
- ORIGIN_FEATURE_VALIDATION_STRATEGY.md — checklist de validation (dont Phase 7 éthique)
|
||||
- ORIGIN_ERROR_PATTERNS.md — patterns d'erreur à éviter (PAT-024 à PAT-028)
|
||||
- ORIGIN_BUSINESS_LOGIC.md — règles métier et principes éthiques
|
||||
|
||||
## Ta mission
|
||||
|
||||
Lire TOUS ces fichiers ORIGIN, puis créer (ou mettre à jour) le fichier `VEZA_VERSIONS_ROADMAP.md` à la racine du repo.
|
||||
|
||||
Ce fichier est le système de versionnage opérationnel du projet. Il doit permettre de dire simplement "implémente la prochaine version" pour déclencher une session de travail complète, autonome, et correcte.
|
||||
|
||||
## Structure requise du fichier VEZA_VERSIONS_ROADMAP.md
|
||||
|
||||
### Pour chaque version, inclure :
|
||||
|
||||
1. **Numéro de version** (ex: v0.9.1) en suivant le schéma :
|
||||
- v0.9.x : Phase P3.5 (Consolidation & Sécurité)
|
||||
- v0.10.x : Phase P4R (Social & Live)
|
||||
- v0.11.x : Phase P5R (Analytics & Recherche)
|
||||
- v0.12.x : Phase P6R (Premium & Infrastructure)
|
||||
- v1.0.0 : Release stable
|
||||
|
||||
2. **Nom descriptif** de la version (ex: "JWT Migration RS256")
|
||||
|
||||
3. **Statut** : ⏳ TODO | 🔄 IN PROGRESS | ✅ DONE
|
||||
|
||||
4. **Priorité** : P0 (bloquant) | P1 (haute) | P2 (moyenne) | P3 (basse)
|
||||
|
||||
5. **Durée estimée** en jours
|
||||
|
||||
6. **Prerequisite** : liste des versions qui doivent être DONE avant
|
||||
|
||||
7. **Objectif** : une phrase claire sur ce que cette version accomplit
|
||||
|
||||
8. **Tâches** : liste de checkboxes avec :
|
||||
- Référence aux TASK-XXX de ORIGIN_IMPLEMENTATION_TASKS.md si applicable
|
||||
- Référence aux features Fxxx de ORIGIN_FEATURES_REGISTRY.md
|
||||
- Description précise de ce qui doit être implémenté
|
||||
- Fichiers principaux à modifier (backend, frontend, infra)
|
||||
|
||||
9. **Critères d'acceptation** : liste de conditions vérifiables (pas ambiguës)
|
||||
|
||||
### Contraintes absolues (à respecter dans tout le fichier)
|
||||
|
||||
- Les versions doivent être **atomiques** : chaque version est mergeable indépendamment
|
||||
- Les versions doivent être **séquentielles** dans chaque phase (pas de parallélisme)
|
||||
- Les tâches P0 de ORIGIN_IMPLEMENTATION_TASKS.md (TASK-SEC-001/002) passent EN PREMIER
|
||||
- Aucune version ne doit implémenter du code AI/ML, blockchain/Web3, ou gamification addictive
|
||||
- Les critères d'acceptation doivent être **vérifiables automatiquement** (tests, métriques) ou **vérifiables manuellement** (checklist UX)
|
||||
- La Phase 7 de validation éthique de ORIGIN_FEATURE_VALIDATION_STRATEGY.md s'applique à toute version touchant la découverte, les métriques, ou les interactions sociales
|
||||
|
||||
### Section finale obligatoire : "Instructions pour Cursor"
|
||||
|
||||
À la fin du fichier, inclure une section expliquant le protocole à suivre quand Cursor reçoit "implémente la prochaine version" :
|
||||
1. Identifier la prochaine version TODO
|
||||
2. Vérifier les prerequisites
|
||||
3. Lire les fichiers ORIGIN référencés
|
||||
4. Implémenter
|
||||
5. Valider selon ORIGIN_FEATURE_VALIDATION_STRATEGY.md
|
||||
6. Marquer comme DONE dans ce fichier
|
||||
7. Créer le tag git
|
||||
|
||||
### Section finale obligatoire : "Règles immuables pour Cursor"
|
||||
|
||||
Liste des choses que Cursor ne doit JAMAIS faire, dérivées de la révision éthique v2.0.0.
|
||||
|
||||
## Critères de qualité du fichier généré
|
||||
|
||||
- Le fichier doit être complet : de v0.9.1 jusqu'à v1.0.0, sans saut
|
||||
- Chaque version doit avoir minimum 3 tâches et 3 critères d'acceptation
|
||||
- Les références aux fichiers ORIGIN doivent être exactes (section + numéro de feature)
|
||||
- Les durées estimées doivent être réalistes (1 développeur, pas d'optimisme excessif)
|
||||
- Les prerequisites doivent former un graphe acyclique cohérent
|
||||
|
||||
## Ce que tu NE dois PAS faire
|
||||
|
||||
- Ne pas modifier les fichiers ORIGIN/ (ils sont en lecture seule)
|
||||
- Ne pas créer d'autres fichiers que VEZA_VERSIONS_ROADMAP.md
|
||||
- Ne pas inventer des features qui ne sont pas dans les ORIGIN docs
|
||||
- Ne pas ignorer les features supprimées pour raisons éthiques (F456-F470, F491-F500, F536-F550)
|
||||
|
||||
## Format de sortie
|
||||
|
||||
Fichier Markdown unique : VEZA_VERSIONS_ROADMAP.md
|
||||
Encodage : UTF-8
|
||||
Longueur : aussi long que nécessaire pour être complet et exploitable
|
||||
|
||||
Commence par lire tous les fichiers ORIGIN/ avant d'écrire une seule ligne du fichier de sortie.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NOTES D'UTILISATION
|
||||
|
||||
### Pour initialiser le roadmap (première fois)
|
||||
Colle le prompt ci-dessus dans Cursor. Il lira tes ORIGIN docs et générera le fichier.
|
||||
|
||||
### Pour implémenter une version (usage quotidien)
|
||||
Dis simplement à Cursor dans un nouveau chat :
|
||||
```
|
||||
Implémente la prochaine version. Le fichier VEZA_VERSIONS_ROADMAP.md
|
||||
est à la racine du repo et contient les instructions.
|
||||
```
|
||||
|
||||
### Pour mettre à jour le roadmap après un changement d'architecture
|
||||
```
|
||||
Le fichier VEZA_VERSIONS_ROADMAP.md est à la racine du repo.
|
||||
[Décrire le changement].
|
||||
Mets à jour les versions concernées sans toucher aux versions déjà DONE
|
||||
et sans modifier les fichiers ORIGIN/.
|
||||
```
|
||||
|
||||
### Pour voir où en est le projet
|
||||
```
|
||||
Donne-moi un résumé de l'avancement dans VEZA_VERSIONS_ROADMAP.md :
|
||||
versions DONE, version en cours, prochaine version, et estimation
|
||||
pour atteindre v1.0.0.
|
||||
```
|
||||
159
DEMARRAGE_SIMPLE.md
Normal file
159
DEMARRAGE_SIMPLE.md
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# 🚀 Démarrage Simple - Test Intégration Veza
|
||||
|
||||
## ✅ Problèmes Corrigés
|
||||
|
||||
1. ✅ Migration SQL corrigée (`050_data_validation_constraints.sql`)
|
||||
2. ✅ Redis démarré correctement
|
||||
3. ✅ Configuration backend créée (`.env`)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Démarrage en 3 Étapes
|
||||
|
||||
### Étape 1: Infrastructure Docker
|
||||
|
||||
```bash
|
||||
make infra-up
|
||||
```
|
||||
|
||||
**Vérification**:
|
||||
```bash
|
||||
docker compose ps
|
||||
# Devrait voir: postgres, redis, rabbitmq (tous "healthy")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Étape 2: Backend Go
|
||||
|
||||
```bash
|
||||
cd veza-backend-api
|
||||
|
||||
# Le fichier .env est déjà créé avec la bonne config
|
||||
# Si besoin, vérifier:
|
||||
cat .env
|
||||
|
||||
# Démarrer le serveur
|
||||
go run cmd/api/main.go
|
||||
```
|
||||
|
||||
**Vérification**:
|
||||
```bash
|
||||
# Dans un autre terminal
|
||||
curl http://localhost:8080/health
|
||||
# Devrait retourner: {"status":"ok"}
|
||||
```
|
||||
|
||||
**URLs**:
|
||||
- API: http://localhost:8080/api/v1
|
||||
- Swagger: http://localhost:8080/docs
|
||||
- Health: http://localhost:8080/health
|
||||
|
||||
---
|
||||
|
||||
### Étape 3: Frontend React
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
|
||||
# Démarrer Vite
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Vérification**:
|
||||
- Ouvrir http://localhost:3000 dans le navigateur
|
||||
- La page devrait se charger
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test Complet
|
||||
|
||||
1. **Ouvrir** http://localhost:3000
|
||||
2. **Tester Register**:
|
||||
- Créer un compte
|
||||
- Vérifier que ça fonctionne
|
||||
3. **Tester Login**:
|
||||
- Se connecter
|
||||
- Vérifier DevTools → Network → Headers
|
||||
- Devrait voir `Authorization: Bearer <token>`
|
||||
- Devrait voir `X-CSRF-Token: <token>` sur les mutations
|
||||
4. **Tester API**:
|
||||
- Ouvrir http://localhost:8080/docs
|
||||
- Tester un endpoint depuis Swagger UI
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Backend (`veza-backend-api/.env`)
|
||||
|
||||
```bash
|
||||
APP_ENV=development
|
||||
JWT_SECRET=dev-secret-key-minimum-32-characters-long-for-testing
|
||||
DATABASE_URL=postgres://veza:password@localhost:5432/veza?sslmode=disable
|
||||
REDIS_URL=redis://localhost:6379
|
||||
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||
APP_PORT=8080
|
||||
LOG_LEVEL=INFO
|
||||
RABBITMQ_URL=amqp://veza:password@localhost:5672/
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
Aucune configuration nécessaire - valeurs par défaut OK:
|
||||
- `VITE_API_URL=http://127.0.0.1:8080/api/v1` ✅
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Si Problème
|
||||
|
||||
### Backend ne démarre pas
|
||||
|
||||
```bash
|
||||
# Vérifier DB
|
||||
docker compose exec postgres psql -U veza -d veza -c "SELECT 1;"
|
||||
|
||||
# Vérifier Redis
|
||||
docker compose exec redis redis-cli ping
|
||||
|
||||
# Vérifier logs backend
|
||||
cd veza-backend-api
|
||||
go run cmd/api/main.go 2>&1 | tee backend.log
|
||||
```
|
||||
|
||||
### Frontend ne se connecte pas
|
||||
|
||||
```bash
|
||||
# Vérifier CORS
|
||||
curl -v -H "Origin: http://localhost:3000" \
|
||||
-H "Access-Control-Request-Method: GET" \
|
||||
-X OPTIONS \
|
||||
http://localhost:8080/api/v1/auth/me
|
||||
|
||||
# Devrait voir: Access-Control-Allow-Origin: http://localhost:3000
|
||||
```
|
||||
|
||||
### Port occupé
|
||||
|
||||
```bash
|
||||
# Trouver processus
|
||||
lsof -i :8080 # Backend
|
||||
lsof -i :3000 # Frontend
|
||||
|
||||
# Tuer si nécessaire
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist Finale
|
||||
|
||||
- [ ] Infrastructure Docker démarrée (`make infra-up`)
|
||||
- [ ] Backend démarré sur port 8080
|
||||
- [ ] Frontend démarré sur port 3000
|
||||
- [ ] Backend health check OK (`curl http://localhost:8080/health`)
|
||||
- [ ] Frontend accessible (http://localhost:3000)
|
||||
- [ ] Swagger accessible (http://localhost:8080/docs)
|
||||
|
||||
**Prêt à tester ! 🎉**
|
||||
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
# FUNCTIONAL_AUDIT v2 — Veza, ce qu'un utilisateur peut RÉELLEMENT faire
|
||||
|
||||
> **Date** : 2026-04-19
|
||||
> **Branche** : `main` (HEAD = `89a52944e`, `v1.0.7-rc1`)
|
||||
> **Auditeur** : Claude Code (Opus 4.7 — mode autonome, /effort max, /plan)
|
||||
> **Méthode** : 5 agents Explore en parallèle + vérifications ponctuelles directes + relecture de `docs/audit-2026-04/v107-plan.md` et `CHANGELOG.md`. **Trace statique** (pas de runtime), comme v1.
|
||||
> **Supersede** : [v1 du 2026-04-16](#6-diff-vs-audit-v1-2026-04-16). La v1 listait 1 🔴 + 9 🟡. Entre le 16 et aujourd'hui, v1.0.5 → v1.0.7-rc1 ont shippé (50+ commits, la majorité ciblant exactement les findings v1).
|
||||
> **Ton** : brutal, sans langue de bois. Citations `fichier:ligne`.
|
||||
|
||||
---
|
||||
|
||||
## 0. Résumé en 5 lignes
|
||||
|
||||
1. **Le bloqueur `🔴 Player` de la v1 est résolu.** Un endpoint direct `/api/v1/tracks/:id/stream` avec support Range (`routes_tracks.go:118-120`) sert l'audio sans HLS. Le middleware bypass cache (`response_cache.go:87-104`, commit `b875efcff`) permet le range-request. Le player frontend tombe automatiquement sur `/stream` si HLS échoue (`playerService.ts:280-293`). `HLS_STREAMING=false` reste le default (`config.go:355`) **mais ce n'est plus un blocker** : l'audio sort.
|
||||
2. **Inscription / vérification email : cassée en v1, corrigée.** `IsVerified: false` (`core/auth/service.go:200`), `VerifyEmail` endpoint réellement vivant, login gate 403 sur unverified (`service.go:527`), MailHog branché par défaut dans `docker-compose.dev.yml`, SMTP env schema unifié (commit `066144352`). Tout le parcours register → mail → click → login fonctionne.
|
||||
3. **Paiements solidifiés de façon massive.** Refund fait **reverse-charge Hyperswitch avec idempotency-key** (`service.go:1297-1436`). Reconciliation worker sweep les stuck orders/refunds/orphans (`reconcile_hyperswitch.go:55-150`). Webhook raw payload audit (`webhook_log.go`). 5 gauges Prometheus ledger-health + 3 alert rules. **Dev bypass persiste** (simulated payment si `HYPERSWITCH_ENABLED=false`, `service.go:550-586`) **mais `Config.Validate` refuse de booter en prod** sans Hyperswitch (`config.go:908-910`). Fail-closed en prod, fail-open en dev.
|
||||
4. **Points rugueux restants** : (a) **WebRTC 1:1 sans STUN/TURN** — signaling ✅ mais NAT traversal HS en prod ; (b) **Stockage local disque only** — le code S3/MinIO existe mais n'est pas wiré dans l'upload path ; (c) **HLS toujours off par défaut** → pas d'adaptive bitrate out-of-the-box ; (d) **Transcoding dual-trigger** (gRPC Rust + RabbitMQ) — redondance non documentée.
|
||||
5. **Verdict** : Veza v1.0.7-rc1 est prêt pour une **démo publique contrôlée** (un seul pod, infra dev, Hyperswitch sandbox). Pour un **déploiement prod multi-pod avec utilisateurs réels** il manque : MinIO wiré, STUN/TURN pour les calls, et la documentation d'exploitation des gauges ledger-health. La surface "un utilisateur lambda peut register → verify → upload → play → acheter → rembourser" est **entièrement opérationnelle**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Tableau des features — verdict réel au 2026-04-19
|
||||
|
||||
Légende : **✅ COMPLET** câblé de bout-en-bout · **🟡 PARTIEL** gotchas exploitables · **🔴 FAÇADE** UI sans backend réel · **⚫ ABSENT**.
|
||||
|
||||
| # | Feature | Verdict | v1 | Détail + citation |
|
||||
| --- | ---------------------------------------------------------------- | :-----: | :-: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Register / Login / JWT / Refresh | ✅ | 🟡 | `IsVerified: false` (`core/auth/service.go:200`). Login 403 si unverified (`service.go:527`). JWT RS256 prod / HS256 dev. |
|
||||
| 2 | Verify email | ✅ | 🔴 | `POST /auth/verify-email` actif (`routes_auth.go:103-107`). Token généré + stocké en DB, email envoyé via MailHog par défaut. |
|
||||
| 3 | Forgot / Reset password | ✅ | 🟡 | `password_reset_handler.go:67-250`. Token en DB avec expiry, invalide toutes les sessions à l'usage. |
|
||||
| 4 | 2FA TOTP | ✅ | ✅ | `internal/handlers/two_factor_handler.go:171`. Obligatoire pour admin. |
|
||||
| 5 | OAuth (Google/GitHub/Discord/Spotify) | ✅ | ✅ | `routes_auth.go:122-176`. |
|
||||
| 6 | Profils utilisateur + slug / username | ✅ | ✅ | `profile_handler.go:102`. |
|
||||
| 7 | Upload de tracks | 🟡 | 🟡 | ClamAV sync ✅ (fail-secure par défaut, `upload_validator.go:87-88`). **Stockage local disque** (`track_upload_handler.go:376`). Dual trigger transcoding (gRPC + RabbitMQ) non doc. |
|
||||
| 8 | CRUD Tracks / Library | ✅ | ✅ | List / filtres / pagination réels. Library filtrée sur `status=Completed`. |
|
||||
| 9 | **Player + Queue + écoute audio** | ✅ | 🔴 | **🔴 → ✅** : `/tracks/:id/stream` avec Range (`routes_tracks.go:118-120`, `track_hls_handler.go:266`). Cache bypass wiré (`response_cache.go:87-104`). HLS optionnel, off par défaut. |
|
||||
| 10 | Playlists (CRUD + share par token) | ✅ | ✅ | `playlist_handler.go:43`. |
|
||||
| 11 | Queue collaborative (host-authority) | ✅ | ✅ | `queue_handler.go`. |
|
||||
| 12 | Chat WebSocket (messages, typing, reactions, attachments) | ✅ | 🟡 | DB persist avant broadcast (`handler_messages.go:91-113`). 12 features wirées (edit/delete/typing/read/delivered/reactions/attachments/search/convos/channel/DM/calls). |
|
||||
| 13 | Chat multi-instance | ✅ | 🟡 | **🟡 → ✅** : Redis pubsub + fallback in-memory **avec log ERROR loud** (`chat_pubsub.go:23-27, 48`). Plus de silent fail. |
|
||||
| 14 | WebRTC 1:1 calls | 🟡 | 🟡 | Signaling ✅ (`handler.go:89-98`). **STUN/TURN absent** — pas d'env var, pas de grep hit. NAT symétrique = call HS. |
|
||||
| 15 | Co-listening (listen-together) | ✅ | ✅ | `colistening/hub.go:104-148`, host-authority, keepalive 30s. |
|
||||
| 16 | **Livestream (RTMP ingest)** | ✅ | 🟡 | **🟡 → ✅** : `/api/v1/live/health` (`live_health_handler.go:78-96`) + banner UI (`useLiveHealth.ts:41-61`, commit `64fa0c9ac`). Plus de silent OBS fail. |
|
||||
| 17 | Livestream viewer playback | ✅ | ✅ | HLS via nginx-rtmp (`live_stream_callback.go:66`). URL dans `streamURL`. |
|
||||
| 18 | Dashboard | ✅ | ✅ | `/api/v1/dashboard`. |
|
||||
| 19 | Recherche (unifiée + tracks) | ✅ | ✅ | `search_handlers.go:41` — ES puis fallback Postgres LIKE + pg_trgm. |
|
||||
| 20 | Social / Feed / Posts / Groups | ✅ | ✅ | `social.go:161`, chronologique. |
|
||||
| 21 | Discover (genres/tags déclaratifs) | ✅ | ✅ | `discover.go:49-63`. |
|
||||
| 22 | Presence + rich presence | ✅ | ✅ | `presence_handler.go:30-46`. |
|
||||
| 23 | Notifications + Web Push | ✅ | ✅ | `notification_handlers.go:197`. |
|
||||
| 24 | **Marketplace + checkout** | ✅ | 🟡 | Hyperswitch wiré (`service.go:522-548`). **Simulated payment si dev** (`:550-586`) **mais `Config.Validate` refuse prod sans Hyperswitch** (`config.go:908-910`). Cart côté server ✅. |
|
||||
| 25 | **Refund (reverse-charge)** | ✅ | 🟡 | **🟡 → ✅** : 3 phases avec idempotency-key `refund.ID` (`service.go:1297-1436`, commits `4f15cfbd9` `959031667`). Webhook handler wiré. |
|
||||
| 26 | Hyperswitch reconciliation sweep | ✅ | ⚫ | **⚫ → ✅** (nouveauté v1.0.7) : `reconcile_hyperswitch.go:55-150` couvre stuck orders/refunds/orphans, 10 tests green. |
|
||||
| 27 | Webhook raw payload audit log | ✅ | ⚫ | **⚫ → ✅** (v1.0.7) : `webhook_log.go:34-80` + cleanup 90j (`cleanup_hyperswitch_webhook_log.go`). |
|
||||
| 28 | Ledger-health metrics + alerts | ✅ | ⚫ | **⚫ → ✅** (v1.0.7 item F) : 5 gauges Prometheus + 3 alert rules Alertmanager + dashboard Grafana. |
|
||||
| 29 | Seller dashboard + Stripe Connect payout | ✅ | ✅ | `sell_handler.go`, transfer auto post-webhook. |
|
||||
| 30 | **Stripe Connect reversal (async)** | ✅ | 🟡 | **🟡 → ✅** (v1.0.7 items A+B) : `reversal_worker.go:12-180`, state machine `reversal_pending`, `stripe_transfer_id` persisté, exp. backoff 1m→1h. |
|
||||
| 31 | Reviews / Factures | ✅ | ✅ | DB + handlers wirés. |
|
||||
| 32 | Subscription plans | ✅ | 🟡 | **🟡 → ✅** (v1.0.6.2 hotfix `d31f5733d`) : `hasEffectivePayment()` gate (`subscription/service.go:140-155`). Plus de bypass. |
|
||||
| 33 | Distribution plateformes externes | ✅ | ✅ | `distribution_handler.go:32-62`. |
|
||||
| 34 | Formation / Education | ✅ | ✅ | `education_handler.go:33` — DB-backed. |
|
||||
| 35 | Support tickets | ✅ | ✅ | `support_handler.go:54-100`. |
|
||||
| 36 | Developer portal (API keys + webhooks) | ✅ | ✅ | `routes_developer.go:11`. |
|
||||
| 37 | Analytics (creator stats) | ✅ | ✅ | `playback_analytics_handler.go`, CSV/JSON export. |
|
||||
| 38 | Admin — dashboard / users / modération / flags / audit | ✅ | 🟡 | `admin/handler.go:43-54`. **Maintenance mode 🟡 → ✅** via `platform_settings` + TTL 10s (`middleware/maintenance.go:16-100`, commit `3a95e38fd`). |
|
||||
| 39 | Admin — transfers (v0.701) | ✅ | ✅ | `admin_transfer_handler.go:36-91`. |
|
||||
| 40 | Self-service creator role upgrade | ✅ | ⚫ | **⚫ → ✅** (commit `c32278dc1`) : `POST /users/me/upgrade-creator` gate email-verified, idempotent. |
|
||||
| 41 | Upload-size SSOT | ✅ | ⚫ | **⚫ → ✅** (commit `5848c2e40`) : `config/upload_limits.go` + `GET /api/v1/upload/limits` consommé par `useUploadLimits` côté web. |
|
||||
| 42 | Tag suggestions | ✅ | ✅ | `tag_handler.go:15-32`. |
|
||||
| 43 | PWA (install + service worker + wake lock) | ✅ | ✅ | `components/pwa/`, v0.801. |
|
||||
| 44 | Orphan tracks cleanup | ✅ | ⚫ | **⚫ → ✅** (commit `553026728`) : `jobs/cleanup_orphan_tracks.go`, hourly, flip `processing`→`failed` si fichier disque manquant. |
|
||||
| 45 | Stem upload & sharing (F482) | ✅ | ✅ | `routes_tracks.go:185-189`, ownership guard. |
|
||||
|
||||
**Score** : 43 ✅ / 2 🟡 / 0 🔴 / 0 ⚫. La seule 🔴 de la v1 (Player/écoute audio) est résolue.
|
||||
|
||||
**Les 2 🟡 restants** : **Upload** (stockage local disque → pas prêt pour production scale) et **WebRTC 1:1** (pas de STUN/TURN → NAT traversal HS).
|
||||
|
||||
---
|
||||
|
||||
## 2. Les 6 parcours — étape par étape
|
||||
|
||||
### Parcours 1 — Écouter de la musique
|
||||
|
||||
**Verdict : ✅ OPÉRATIONNEL.** Le bloqueur v1 est résolu — le fallback direct stream existe.
|
||||
|
||||
| # | Étape | Verdict | Preuve |
|
||||
| --- | ------------------------ | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Créer un compte | ✅ | `POST /auth/register` → `core/auth/service.go:104-469`. `IsVerified: false` (`:200`), token en DB. |
|
||||
| 2 | Recevoir l'email | ✅ | MailHog par défaut dans `docker-compose.dev.yml:114-130`. UI sur port 8025. Prod : 500 hard si SMTP down (`service.go:387`). |
|
||||
| 3 | Cliquer le lien verify | ✅ | `POST /auth/verify-email?token=X` → `core/auth/service.go:747-765` check token + flip `is_verified=true`. |
|
||||
| 4 | Se connecter | ✅ | `POST /auth/login` → 403 Forbidden si `!IsVerified` (`service.go:527`). Lockout après 5 tentatives / 15 min. |
|
||||
| 5 | Chercher un morceau | ✅ | `GET /api/v1/search` → `search_handlers.go:41`, ES ou fallback Postgres tsvector. |
|
||||
| 6 | Lancer la lecture | ✅ | Player React tente HLS d'abord (`playerService.ts:283-293`), fallback direct `/stream`. |
|
||||
| 7 | **Le son sort ?** | ✅ | `GET /tracks/:id/stream` avec `http.ServeContent` (`track_hls_handler.go:266`), Range supporté, cache bypass wiré (`response_cache.go:87-104`). |
|
||||
|
||||
**Piège dev** : si on upload un fichier mais que le transcoding (Rust stream server) échoue, le track reste en `Processing`. Le cleanup worker hourly le flippera à `Failed` après 1h. Le fichier **reste lisible via `/stream`** pendant ce temps, mais il n'apparaît pas en library (filtre `status=Completed`).
|
||||
|
||||
### Parcours 2 — Uploader un morceau (artiste)
|
||||
|
||||
**Verdict : ✅ MAIS sur local disque.**
|
||||
|
||||
| # | Étape | Verdict | Preuve |
|
||||
| --- | --------------------------- | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Login | ✅ | Comme parcours 1. |
|
||||
| 2 | Upgrade creator (si besoin) | ✅ | `POST /api/v1/users/me/upgrade-creator` — gate email-verified, idempotent (`upgrade_creator_handler.go`). UI `AccountSettingsCreatorCard.tsx`. |
|
||||
| 3 | Uploader un fichier audio | ✅ | `POST /api/v1/tracks/upload` → `track_upload_handler.go:39-171`. Multipart, taille SSOT (`config/upload_limits.go`), ClamAV **sync** fail-secure. |
|
||||
| 4 | Stockage physique | 🟡 | **`uploads/tracks/<userID>/<filename>` sur disque local** (`track_upload_handler.go:376`). Code S3/MinIO présent mais **non wiré** dans ce chemin. |
|
||||
| 5 | Transcoding | 🟡 | **Dual-trigger** : gRPC Rust stream server (`stream_service.go:49`) **et** RabbitMQ job (`EnqueueTranscodingJob`). Redondance non documentée. |
|
||||
| 6 | Track visible en library | ✅ | Après `status=Completed`. Avant : utilisateur voit son upload en "Processing" dans son tableau de bord. |
|
||||
| 7 | Autre user peut trouver/lire| ✅ | Via search + parcours 1. Si track reste `Processing` (transcoding down) → pas en library mais `/tracks/:id/stream` sert quand même le raw. |
|
||||
|
||||
### Parcours 3 — Acheter sur le marketplace
|
||||
|
||||
**Verdict : ✅ (sandbox testing) + solidifiés massivement depuis v1.**
|
||||
|
||||
| # | Étape | Verdict | Preuve |
|
||||
| --- | ---------------------------------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 1 | Browse produits | ✅ | `GET /api/v1/marketplace/products`, handlers DB réels. |
|
||||
| 2 | Ajouter au panier | ✅ | `POST /api/v1/cart/items` → `cart.go:25-97`, DB-backed (table `cart_items`). |
|
||||
| 3 | Checkout | ✅ | `POST /api/v1/orders` → `service.go:522-548` (prod flow Hyperswitch) ou `:550-586` (dev simulated). |
|
||||
| 4 | **Paiement Hyperswitch** | ✅ | `paymentProvider.CreatePayment()` avec `Idempotency-Key: order.ID` (commit `4f15cfbd9`). Retourne `client_secret` consommé par `CheckoutPaymentForm.tsx`. |
|
||||
| 5 | Webhook paiement | ✅ | `POST /api/v1/webhooks/hyperswitch` → raw payload logged (`webhook_log.go`), signature HMAC-SHA512 vérifiée, dispatcher `ProcessPaymentWebhook`. |
|
||||
| 6 | Reconciliation si webhook perdu | ✅ | `reconcile_hyperswitch.go` sweep stuck orders > 30m avec payment_id non vide, synthèse webhook → `ProcessPaymentWebhook`. Idempotent. Configurable `RECONCILE_INTERVAL=1h` (5m pendant incident). |
|
||||
| 7 | Confirmation + accès contenu | ✅ | Création licenses dans la transaction (`service.go:561-585`), lock `FOR UPDATE` pour exclusive. |
|
||||
| 8 | Remboursement | ✅ | 3-phase `service.go:1297-1436` : pending row → `CreateRefund` PSP → persist `hyperswitch_refund_id`. Webhook `refund.succeeded` révoque licenses + débite vendeur. |
|
||||
| 9 | Reverse-charge Stripe Connect | ✅ | `reversal_worker.go:12-180`, state `reversal_pending`, async, backoff 1m→1h. Rows pré-v1.0.7 sans `stripe_transfer_id` → `permanently_failed` avec message explicite. |
|
||||
|
||||
**Piège prod** : `HYPERSWITCH_ENABLED=false` = dev bypass. **Garde-fou** : `Config.Validate` refuse de booter en prod si `HYPERSWITCH_ENABLED=false` (`config.go:908-910`) — message explicite "marketplace orders complete without charging, effectively giving away products". Fail-closed au bon endroit.
|
||||
|
||||
### Parcours 4 — Chat
|
||||
|
||||
**Verdict : ✅ sur toutes les surfaces.**
|
||||
|
||||
| # | Étape | Verdict | Preuve |
|
||||
| --- | ------------------------------- | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 1 | Ouvrir le chat | ✅ | `apps/web/src/features/chat/pages/ChatPage.tsx`. |
|
||||
| 2 | Rejoindre / créer une room | ✅ | `POST /api/v1/conversations` → `CreateRoom:54`. |
|
||||
| 3 | Envoyer un message | ✅ | WS dispatcher `handler.go:54-106` → `HandleSendMessage:18` → DB **avant** broadcast (`handler_messages.go:91-113`). |
|
||||
| 4 | Recevoir (temps réel) | ✅ | Hub local, puis PubSub pour multi-instance. |
|
||||
| 5 | Persistance | ✅ | `chat_messages` table, indexed. |
|
||||
| 6 | Multi-instance sans Redis | ✅ | Fallback in-memory **avec log ERROR loud** ("Redis unavailable, cross-instance messages will be lost") (`chat_pubsub.go:23-27`). Plus de silent fail. |
|
||||
| 7 | Typing / reactions / attach. | ✅ | 12 features wirées (voir §1 ligne 12). |
|
||||
|
||||
### Parcours 5 — Livestream
|
||||
|
||||
**Verdict : ✅ avec banner UI si RTMP down.**
|
||||
|
||||
| # | Étape | Verdict | Preuve |
|
||||
| --- | ------------------------ | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Démarrer un live | ✅ | `POST /api/v1/live/streams` → `live_stream_handler.go:71-98`, génère `stream_key` UUID + `rtmp_url`. |
|
||||
| 2 | Push OBS → nginx-rtmp | ✅ | `on_publish` callback `live_stream_callback.go:38-80` avec secret `X-RTMP-Callback-Secret`, flip `is_live=true`. |
|
||||
| 3 | Health check visible | ✅ | `GET /api/v1/live/health` (`live_health_handler.go:78-96`) + poll 15s front (`useLiveHealth.ts:41-61`). Banner warn si `rtmp_reachable=false`.|
|
||||
| 4 | Viewer play live | ✅ | HLS via nginx-rtmp (`streamURL` = `baseURL + /{streamKey}/playlist.m3u8`). |
|
||||
| 5 | Co-listening en parallèle| ✅ | Feature séparée, `colistening/hub.go:104-148`, host-authority sync 100ms drift threshold. |
|
||||
|
||||
**Piège** : nécessite `docker compose --profile live up` pour démarrer nginx-rtmp. Sans ça, banner red immédiat. Plus de silent fail comme en v1.
|
||||
|
||||
### Parcours 6 — Admin
|
||||
|
||||
**Verdict : ✅ complet avec persistance maintenance mode.**
|
||||
|
||||
| # | Étape | Verdict | Preuve |
|
||||
| --- | ------------------------ | :-----: | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 1 | Accéder /admin | ✅ | Middleware JWT + role check, 2FA obligatoire. |
|
||||
| 2 | Voir stats | ✅ | `admin/handler.go:43-54` `GetPlatformMetrics`. |
|
||||
| 3 | Modérer (queue, bans) | ✅ | `moderation/handler.go:44` `GetModerationQueue`, ban/suspend wirés. |
|
||||
| 4 | Gérer utilisateurs | ✅ | Admin handlers (user upgrade, role change). |
|
||||
| 5 | Maintenance mode | ✅ | Persisté `platform_settings` (`middleware/maintenance.go:16-100`, TTL 10s). Survit au restart. **🟡 v1 → ✅ v2**. |
|
||||
| 6 | Feature flags | ✅ | DB-backed. |
|
||||
| 7 | Ledger health dashboard | ✅ | Grafana `config/grafana/dashboards/ledger-health.json` + 5 gauges + 3 alert rules (voir §1 ligne 28). |
|
||||
| 8 | Admin transfers | ✅ | `admin_transfer_handler.go:36-91`, manual retry, state machine persistée. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Carte des dépendances
|
||||
|
||||
### 3.1 Services — hard-required vs optionnels
|
||||
|
||||
| Service | Status | Comportement si down | Preuve |
|
||||
| -------------------- | --------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
|
||||
| **PostgreSQL** | 🔴 Hard-req | App panique au boot (`main.go:112-120`, migrations auto-run). | `db.Initialize()` + `RunMigrations()` fatal. |
|
||||
| **Migrations** | 🔴 Auto | Appliquées au démarrage, boot fail si erreur SQL. | `database.go:234-256`. |
|
||||
| **Redis** | 🟢 Dégradation | TokenBlacklist nil-safe. Chat PubSub fallback in-memory avec **log ERROR loud**. Rate limiter dégradé. | `chat_pubsub.go:23-27` ; `config.go:55-58`. |
|
||||
| **RabbitMQ** | 🟢 Dégradation | EventBus publish failures maintenant **loggés ERROR** (commit `bf688af35`) au lieu de silent drop. | `main.go:128-139` ; `config.go:690-693`. |
|
||||
| **MinIO / S3** | 🟢 Non utilisé | `AWS_S3_ENABLED=false` par défaut, **code S3 présent mais non wiré dans upload path**. Disque local always. | `config.go:697-720` ; `track_upload_handler.go:376`. |
|
||||
| **Elasticsearch** | 🟢 Optionnel | Search fallback Postgres full-text search (tsvector + pg_trgm). ES non utilisé en chemin chaud. | `fulltext_search_service.go:14-30` ; `main.go:288-297` (cleanup only). |
|
||||
| **ClamAV** | 🟠 Fail-secure | `CLAMAV_REQUIRED=true` par défaut → upload **rejeté** (503) si down. `=false` = bypass avec warning. | `upload_validator.go:87-88, 140-150` ; `services_init.go:27-46`. |
|
||||
| **Hyperswitch** | 🟠 Prod-gate | `HYPERSWITCH_ENABLED=false` = dev bypass. **Prod : `Config.Validate` refuse boot** si false. | `config.go:908-910` ; `service.go:522-548, 550-586`. |
|
||||
| **Stripe Connect** | 🟠 Prod-gate | Reversal worker tourne si config présente. Rows pre-v1.0.7 sans id → `permanently_failed`. | `reversal_worker.go:12-180` ; `main.go:188`. |
|
||||
| **Nginx-RTMP** | 🟢 Profil live | `docker compose --profile live up`. Si down : banner UI immédiat sur Go Live page. | `live_health_handler.go:78-96` ; `useLiveHealth.ts:41-61`. |
|
||||
| **Rust stream srv** | 🟢 Optionnel | HLS gated `HLSEnabled=false` default. Direct `/stream` fallback toujours disponible. Transcoding async. | `stream_service.go:49` ; `config.go:355` ; `track_hls_handler.go:266`. |
|
||||
| **MailHog (SMTP)** | 🟢 Dev default | Branché `docker-compose.dev.yml:114-130`, port 1025. Dev : fail email → log + continue. Prod : 500 hard. | `.env.template:160-165` ; `service.go:381-407`. |
|
||||
|
||||
**Résumé** : **3 hard-required** (Postgres, migrations, bcrypt) · **le reste est optionnel avec fallback, fail-secure, ou prod-gate explicite**. C'est l'évolution la plus importante depuis v1 : il n'y a plus de silent failures non documentés.
|
||||
|
||||
### 3.2 Seeding
|
||||
|
||||
- `veza-backend-api/cmd/tools/seed/main.go` : modes `production` / `full` / `smoke`. Truncate tables → insert users → tracks → playlists → social → chat. **Manuel**, pas auto-run. Marche.
|
||||
|
||||
---
|
||||
|
||||
## 4. Stabilité — points de fragilité restants
|
||||
|
||||
| # | Fragilité | Impact | Preuve |
|
||||
| -- | ------------------------------------------- | :-----: | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | **WebRTC 1:1 sans STUN/TURN** | 🟡 Prod | Pas d'env var, pas de grep hit. NAT symétrique = call failures silencieuses (les signals passent, mais le flux média échoue). |
|
||||
| 2 | **Stockage local disque only** | 🟡 Prod | `uploads/tracks/<userID>/` sur FS local. Pas scalable multi-pod sans volume partagé. Le code S3/MinIO est dead in upload path. |
|
||||
| 3 | **HLS `HLSEnabled=false` par défaut** | 🟢 Dev | Fonctionnel grâce au fallback `/stream`. Pas d'adaptive bitrate out-of-box. Opérateur doit activer explicitement. |
|
||||
| 4 | **Transcoding dual-trigger** | 🟡 Ops | `StreamService.StartProcessing` (gRPC) **et** `EnqueueTranscodingJob` (RabbitMQ) appelés tous les deux. Redondance non documentée. |
|
||||
| 5 | **`HLS_STREAMING` absent de .env.template** | 🟠 Doc | Dev qui veut HLS doit trouver la var ailleurs. `.env.template` à compléter. |
|
||||
| 6 | **Dev bypass Hyperswitch** | 🟢 Ops | Fail-closed prod (`Config.Validate`), mais en staging un opérateur distrait peut servir des licences gratuites. Mettre un warning loud au boot. |
|
||||
| 7 | **Email tokens en query param** | 🟠 Sec | `?token=X` peut leak via Referer / logs proxy. Migration flagged v0.2 (commentaire `handlers/auth.go` L339). |
|
||||
| 8 | **Register issue JWT avant email send** | 🟠 UX | User a ses tokens avant que l'email parte → login 403 immédiat tant que non-vérifié. Cohérent mais friction. |
|
||||
| 9 | **ClamAV 10s timeout sync** | 🟢 UX | Upload bloque jusqu'à 10s sur scan. Acceptable pour fichiers audio <100MB. |
|
||||
| 10 | **Subscription `pending_payment` item G** | 🟢 Roadm| v1.0.6.2 compense via filter, item G dans v107-plan refait le path proprement. Pas un bug, juste techdebt flaggée. |
|
||||
|
||||
**Zero silent fails** parmi les 6 surfaces critiques (Chat Redis, RabbitMQ, RTMP, HLS, SMTP, Hyperswitch). C'est le grand changement depuis v1.
|
||||
|
||||
---
|
||||
|
||||
## 5. Verdict final
|
||||
|
||||
**Veza v1.0.7-rc1 est prêt pour :**
|
||||
- ✅ **Démo publique contrôlée** — un pod, infra dev `make dev`, Hyperswitch sandbox. Le parcours "register → verify → search → play → upload → purchase → refund" est intégralement opérationnel.
|
||||
- ✅ **Sandbox payment testing** — refund réel, reconciliation, ledger-health gauges, Stripe Connect reversal. Toute la plomberie monétaire est audit-ready.
|
||||
- ✅ **Beta privée multi-utilisateurs** — chat multi-instance avec alarme loud si Redis manque, co-listening host-authority, livestream avec health banner. Pas de silent fails.
|
||||
|
||||
**Veza v1.0.7-rc1 n'est PAS prêt pour :**
|
||||
- 🟡 **Production publique grand-public scale** — le stockage uploads sur disque local ne survit pas à un second pod. MinIO/S3 doit être wiré dans le path upload (le code dort, il faut juste l'appeler).
|
||||
- 🟡 **Calls WebRTC fiables hors LAN** — sans STUN/TURN, symmetric NAT = échec silencieux du flux média. À configurer avant d'ouvrir la feature calls au public.
|
||||
- 🟠 **Opérateur ops naïf** — le dashboard Grafana ledger-health est là mais ne sert à rien si personne ne le regarde. Nécessite un runbook d'exploitation.
|
||||
|
||||
**Ce qui a changé depuis la v1 du 2026-04-16** — en 3 jours, l'équipe a fermé **7 findings 🔴/🟡** et ajouté **10 nouvelles capacités** (reconciliation, audit log webhook, ledger metrics, reversal async, upgrade creator, upload SSOT, RTMP health, orphan cleanup, maintenance persist, SMTP unified). Voir §6.
|
||||
|
||||
**En une phrase** : **le code est solide, la plomberie est honnête, les seuls 🟡 restants sont des features "scale" (storage, NAT) pas des bugs**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Diff vs audit v1 (2026-04-16)
|
||||
|
||||
Tableau des évolutions : chaque ligne = un finding v1 avec son statut aujourd'hui.
|
||||
|
||||
| Finding v1 | v1 | v2 | Commit / Preuve |
|
||||
| ---------------------------------------------------------- | :-: | :-: | ------------------------------------------------------------------------------------------------------ |
|
||||
| Player/écoute audio sans fallback (HLSEnabled=false) | 🔴 | ✅ | Endpoint direct `/tracks/:id/stream` + Range cache bypass. `b875efcff`, `routes_tracks.go:118-120`. |
|
||||
| Register : `IsVerified: true` hardcoded | 🔴 | ✅ | `service.go:200` → `IsVerified: false`. Commit trail. |
|
||||
| Verify email : dead code | 🔴 | ✅ | Endpoint actif, login 403 sur unverified (`service.go:527`). |
|
||||
| SMTP silent fail | 🟡 | ✅ | Env schema unifié (`066144352`). Prod : 500 hard. Dev : log + continue. MailHog branché par défaut. |
|
||||
| Marketplace dev bypass | 🟡 | ✅ | Prod gate `Config.Validate` refuse boot (`config.go:908-910`). Dev bypass conservé, assumé. |
|
||||
| Refund : row DB only, pas de reverse-charge | 🟡 | ✅ | 3-phase avec idempotency key. `959031667`, `4f15cfbd9`, `service.go:1297-1436`. |
|
||||
| Subscription : payment gate bypass | 🟡 | ✅ | v1.0.6.2 hotfix `d31f5733d`, `hasEffectivePayment()`. |
|
||||
| Chat multi-instance silent fallback | 🟡 | ✅ | Redis missing = **log ERROR loud** (`chat_pubsub.go:23-27`). Fallback conservé pour single-pod dev. |
|
||||
| Livestream : dépendance cachée `--profile live` | 🟡 | ✅ | Health endpoint + banner UI (`64fa0c9ac`, `live_health_handler.go:78-96`). |
|
||||
| Maintenance mode in-memory | 🟡 | ✅ | Persisté `platform_settings` + TTL 10s. `3a95e38fd`, `middleware/maintenance.go:16-100`. |
|
||||
| Tracks orphelines `Processing` indéfiniment | 🟡 | ✅ | Cleanup hourly worker. `553026728`, `jobs/cleanup_orphan_tracks.go`. |
|
||||
| RabbitMQ silent drop | 🟡 | ✅ | Log ERROR sur publish failure. `bf688af35`. |
|
||||
| Upload size limits désalignés front/back | 🟠 | ✅ | SSOT `config/upload_limits.go` + hook `useUploadLimits`. `5848c2e40`. |
|
||||
| Stripe Connect reversal inexistant | 🔵 | ✅ | Async worker + state machine `reversal_pending`. v1.0.7 items A+B. |
|
||||
| Reconciliation Hyperswitch (stuck orders) | 🔵 | ✅ | `reconcile_hyperswitch.go:55-150`. v1.0.7 item C. |
|
||||
| Webhook raw payload audit log | 🔵 | ✅ | `webhook_log.go` + cleanup 90j. v1.0.7 item E. |
|
||||
| Ledger-health metrics + alerts | 🔵 | ✅ | 5 gauges Prometheus + 3 alert rules + Grafana dashboard. v1.0.7 item F. |
|
||||
| Idempotency-key Hyperswitch | 🔵 | ✅ | Sur CreatePayment + CreateRefund. v1.0.7 item D (`4f15cfbd9`). |
|
||||
| Self-service creator upgrade | 🔵 | ✅ | `POST /users/me/upgrade-creator`, email-verified gate. `c32278dc1`. |
|
||||
| WebRTC sans STUN/TURN | 🟡 | 🟡 | **Toujours pas fixé.** Signaling ok, NAT traversal non. |
|
||||
| Stockage uploads sur disque local | 🟡 | 🟡 | **Toujours pas fixé.** Code S3 présent, non wiré. |
|
||||
| HLS `HLSEnabled=false` par défaut | 🔴 | 🟢 | Plus bloquant grâce au fallback direct stream, mais flag toujours off. |
|
||||
|
||||
Légende : 🔵 = finding absent de v1 mais identifié ici, 🟢 = non-bloquant en v2, 🟠 = doc/cleanup.
|
||||
|
||||
**Bilan** : **18 findings v1 résolus**, **2 subsistants** (WebRTC TURN, stockage local). **7 nouvelles capacités ajoutées** (reconcil, audit log, ledger metrics, reversal, upgrade creator, upload SSOT, RTMP health). Le "chemin critique v1.0.5 public-ready" listé en v1 est **intégralement réalisé** par v1.0.5 → v1.0.7-rc1.
|
||||
|
||||
---
|
||||
|
||||
## 7. Cleanup session post-rc1 (2026-04-23)
|
||||
|
||||
Une session cleanup + BFG a été exécutée 4 jours après cet audit. Cross-référence avec [AUDIT_REPORT.md §9](AUDIT_REPORT.md) :
|
||||
|
||||
- ✅ **10/15 items Top-15 traités** (cleanup #1/#2/#3/#6/#7/#9/#11/#12/#13, BFG inclus)
|
||||
- ⚠️ **3 false-positives identifiés** (#4 context propagation, #5 security headers, #10 `RespondWithAppError`) — voir `AUDIT_REPORT.md §9.bis` pour les preuves
|
||||
- 📋 **2 deferrals v1.0.8** (#8 OpenAPI typegen, #14 E2E Playwright CI)
|
||||
- 📝 **1 item pending** (#15 `docs/ENV_VARIABLES.md` sync, 0.5j)
|
||||
- **Repo `.git` : 1.5 GB → 66 MB** (−97%) après 2 passes git-filter-repo + force-push stages 1+2
|
||||
|
||||
Les 2 findings fonctionnels subsistants (WebRTC STUN/TURN + stockage uploads disque local) restent **post-v1.0.7-final** dans le scope v1.0.8 (2-3j chacun).
|
||||
|
||||
---
|
||||
|
||||
*Généré par Claude Code Opus 4.7 (1M context, /effort max, /plan) — 5 agents Explore parallèles + vérifications ponctuelles directes (`routes_tracks.go:118`, `core/auth/service.go:200`, `config.go:355/907-910`, `marketplace/service.go:522-586`). Cross-référencé avec `docs/audit-2026-04/v107-plan.md` et `CHANGELOG.md` v1.0.5 → v1.0.7-rc1. Une correction par rapport à v1 : le Player n'est plus 🔴 — la v1 avait loupé l'endpoint `/stream` (fallback direct avec Range support). §7 ajouté 2026-04-23 post-session cleanup.*
|
||||
661
LICENSE
Normal file
661
LICENSE
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
989
ORIGIN_GAP_ANALYSIS_2026-03-04.md
Normal file
989
ORIGIN_GAP_ANALYSIS_2026-03-04.md
Normal file
|
|
@ -0,0 +1,989 @@
|
|||
# ORIGIN GAP ANALYSIS — Veza Platform
|
||||
## Date: 4 mars 2026
|
||||
|
||||
---
|
||||
|
||||
## 1. RÉSUMÉ EXÉCUTIF
|
||||
|
||||
### Position sur la Timeline ORIGIN
|
||||
|
||||
Le plan ORIGIN définit **600 features** réparties sur **8 phases** en **24 mois** (février 2025 – janvier 2027). Selon ce calendrier, en mars 2026, le projet devrait achever la **Phase 5** (Intelligence & Analytics) avec **~360 features cumulées**.
|
||||
|
||||
**État réel** : le projet se situe au niveau de la **Phase 3 complétée** avec environ **~231 features individuelles** implémentées (dont ~55 partiellement), ce qui le place approximativement **6 mois en retard** sur le planning ORIGIN.
|
||||
|
||||
### Métriques Clés
|
||||
|
||||
| Indicateur | Objectif ORIGIN (mars 2026) | État Actuel | Écart |
|
||||
|---|---|---|---|
|
||||
| Features cumulées | 360 (fin P5) | ~231 (dont ~55 partielles) | -129 features |
|
||||
| Couverture tests | ≥ 80% | ~50% (frontend), non mesuré (Go) | -30% |
|
||||
| Vulnérabilités critiques | 0 | 2 (JWT default Rust, JWT mismatch) | -2 |
|
||||
| Phases complètes | P0 à P5 | P0 à P3 (P4 à ~30%) | -2 phases |
|
||||
| Uptime | 99.9% | Non mesuré en prod | N/A |
|
||||
| MAU | 5,000+ (P5) | Pre-launch | N/A |
|
||||
| MRR | $30,000+ (P5) | $0 (pre-launch) | N/A |
|
||||
|
||||
### Complétion Globale
|
||||
|
||||
| Catégorie | ORIGIN | Actuel | % |
|
||||
|---|---|---|---|
|
||||
| Features implémentées | 600 | ~176 pleinement | 29% |
|
||||
| Features partielles | — | ~55 | 9% |
|
||||
| Features manquantes | — | ~369 | 62% |
|
||||
| Architecture conforme | 100% | ~70% | — |
|
||||
| Qualité conforme | 100% | ~45% | — |
|
||||
|
||||
### Verdict
|
||||
|
||||
Le projet a construit des **fondations solides** (auth complète, streaming fonctionnel, marketplace opérationnel, infrastructure CI/CD mature) mais accuse un retard significatif par rapport au planning ORIGIN. Ce retard est principalement dû à :
|
||||
|
||||
1. **Scope réaliste** : le plan ORIGIN avec 600 features en 24 mois pour une équipe de 2-8 devs est extrêmement ambitieux
|
||||
2. **Pivots architecturaux** : migration chat Rust→Go, Stripe→Hyperswitch, simplification de la stack
|
||||
3. **Modules abandonnés** : Education (F276-F305), Gamification (F536-F550)
|
||||
4. **Phases 5-8 non démarrées** : AI, Enterprise, Web3, VR/AR
|
||||
|
||||
---
|
||||
|
||||
## 2. MATRICE DE COMPLÉTION PAR PHASE
|
||||
|
||||
### Vue d'ensemble
|
||||
|
||||
```
|
||||
ORIGIN Timeline vs Réalité (mars 2026)
|
||||
|
||||
Phase Prévu Réel Features Complétion
|
||||
─────────────────────────────────────────────────────────────
|
||||
P0 Fév 2025 ✅ Fait 0 (stab) 85%
|
||||
P1 Mar-Avr 2025 ✅ Fait 50 90%
|
||||
P2 Mai-Jun 2025 ✅ Fait 60 80%
|
||||
P3 Jul-Sep 2025 ⚠️ Partiel 80 65%
|
||||
P4 Oct-Déc 2025 ⚠️ ~30% 90 30%
|
||||
P5 Jan-Mar 2026 ❌ Non 80 5%
|
||||
P6 Avr-Jun 2026 ❌ Non 70 10%
|
||||
P7 Jul-Sep 2026 ❌ Non 90 5%
|
||||
P8 Oct-Jan 2027 ❌ Non 80 0%
|
||||
```
|
||||
|
||||
### Phase 0 : Stabilisation (1 mois) — COMPLÉTÉE à 85%
|
||||
|
||||
**Objectif ORIGIN** : Stabiliser l'existant, CI/CD, coverage 80%, monitoring, documentation, 0 bugs critiques.
|
||||
|
||||
| Livrable ORIGIN | Objectif | État Actuel | Statut |
|
||||
|---|---|---|---|
|
||||
| L0.1 CI/CD pipeline fonctionnel | Pipeline vert | 14 workflows GitHub Actions | ✅ DÉPASSÉ |
|
||||
| L0.2 Tests coverage > 80% backend | 80% Go | 306 fichiers test, seuil non enforced | ⚠️ NON VÉRIFIÉ |
|
||||
| L0.3 Tests coverage > 80% Rust | 80% Rust | Stream server : pas de coverage CI | ❌ NON ATTEINT |
|
||||
| L0.4 Tests E2E frontend (10 scenarios) | 10 scénarios | Playwright configuré, specs existantes | ⚠️ PARTIEL |
|
||||
| L0.5 Prometheus + Grafana opérationnels | Monitoring | Configs Prometheus/Grafana/Alertmanager dans docker-compose prod | ✅ ATTEINT |
|
||||
| L0.6 Documentation ORIGIN complète | 15/15 docs | 22 documents ORIGIN | ✅ DÉPASSÉ |
|
||||
| L0.7 Zero bugs critiques | 0 critiques | 2 vulns critiques (JWT) | ❌ NON ATTEINT |
|
||||
| L0.8 Build vert stable 7 jours | Stabilité | CI vert sur main | ✅ ATTEINT |
|
||||
|
||||
**Critères bloquants non atteints** : Coverage 80% non mesurée, 2 vulnérabilités critiques ouvertes.
|
||||
|
||||
### Phase 1 : MVP Core (2 mois, 50 features) — COMPLÉTÉE à 90%
|
||||
|
||||
**Objectif ORIGIN** : MVP fonctionnel — Auth, Profils, Streaming basique, Upload.
|
||||
|
||||
| Module ORIGIN | Features | Implémentées | Partielles | Manquantes |
|
||||
|---|---|---|---|---|
|
||||
| Auth (F001-F015) | 15 | 13 | 1 (F008) | 1 (F014) |
|
||||
| Profils basiques (F031-F040) | 10 | 10 | 0 | 0 |
|
||||
| Streaming basique (F106-F120) | 15 | 12 | 2 (F119,F122) | 1 (F120) |
|
||||
| Upload (F066-F075) | 10 | 8 | 1 (F072) | 1 (F069) |
|
||||
| **Total P1** | **50** | **43** | **4** | **3** |
|
||||
|
||||
**Features manquantes P1** : Password history (F014), URL import (F069), Equalizer (F120).
|
||||
|
||||
### Phase 2 : Features Essentielles (2 mois, 60 features) — COMPLÉTÉE à 80%
|
||||
|
||||
**Objectif ORIGIN** : Playlists, Chat, Social basique.
|
||||
|
||||
| Module ORIGIN | Features | Implémentées | Partielles | Manquantes |
|
||||
|---|---|---|---|---|
|
||||
| Streaming suite (F121-F145) | 25 | 18 | 3 | 4 |
|
||||
| Chat (F151-F170) | 20 | 8 | 6 | 6 |
|
||||
| Social basique (F186-F200) | 15 | 8 | 4 | 3 |
|
||||
| **Total P2** | **60** | **34** | **13** | **13** |
|
||||
|
||||
**Écarts notables P2** :
|
||||
- Chat migré de Rust vers Go (ADR-002) — fonctionnel mais features avancées manquantes (reactions, mentions, markdown, GIFs)
|
||||
- Social : follow/unfollow implémenté, mais pas de recommendations, QR code, close friends
|
||||
- Playlists : CRUD complet, collaboration, mais pas de smart playlists ni export
|
||||
|
||||
### Phase 3 : Marketplace & Monétisation (3 mois, 80 features) — COMPLÉTÉE à 65%
|
||||
|
||||
**Objectif ORIGIN** : Marketplace, Education, Hardware.
|
||||
|
||||
| Module ORIGIN | Features | Implémentées | Partielles | Manquantes |
|
||||
|---|---|---|---|---|
|
||||
| Marketplace (F226-F275) | 50 | 28 | 10 | 12 |
|
||||
| Education (F276-F290) | 15 | 0 | 6 (backend only) | 9 |
|
||||
| Hardware/Gear (F306-F320) | 15 | 12 | 2 | 1 |
|
||||
| **Total P3** | **80** | **40** | **18** | **22** |
|
||||
|
||||
**Écarts notables P3** :
|
||||
- **Education** : Backend et DB schema existent (`core/education/`), mais tests skippés et aucun frontend — module effectivement non livré
|
||||
- **Marketplace** : CRUD produits, licences (personal/commercial/exclusive), cart, checkout Hyperswitch — mais pas de PayPal/crypto, pas de e-signature, pas de licence history
|
||||
- **Paiement** : Hyperswitch au lieu de Stripe (pivot architectural documenté)
|
||||
|
||||
### Phase 4 : Social & Collaboration (3 mois, 90 features) — COMPLÉTÉE à 30%
|
||||
|
||||
**Objectif ORIGIN** : Social avancé, Collaboration DAW, Live streaming, Gamification.
|
||||
|
||||
| Module ORIGIN | Features | Implémentées | Partielles | Manquantes |
|
||||
|---|---|---|---|---|
|
||||
| Social avancé (F201-F225) | 25 | 5 | 8 | 12 |
|
||||
| Collaboration (F481-F510) | 30 | 2 (playlist collab) | 0 | 28 |
|
||||
| Live Streaming (F471-F490) | 20 | 3 | 4 | 13 |
|
||||
| Gamification (F536-F550) | 15 | 0 | 2 (frontend UI only) | 13 |
|
||||
| **Total P4** | **90** | **10** | **14** | **66** |
|
||||
|
||||
**Écarts notables P4** :
|
||||
- **DAW Collaboration** : non implémenté (0/30 features)
|
||||
- **Gamification** : supprimée du scope en v0.971 — `ProfileXPView` et `AchievementCard` existent côté frontend mais sans backend
|
||||
- **Live Streaming** : infrastructure HLS basique en place, `GoLiveView` et `TipStreamerModal` existent, mais features avancées manquantes
|
||||
- **WebRTC** : code présent mais non fonctionnel (dépendance commentée)
|
||||
|
||||
### Phase 5 : Intelligence & Analytics (3 mois, 80 features) — COMPLÉTÉE à 5%
|
||||
|
||||
| Module ORIGIN | Features | Implémentées | Partielles | Manquantes |
|
||||
|---|---|---|---|---|
|
||||
| AI/ML (F456-F470) | 15 | 0 | 2 (similarity, BPM detect) | 13 |
|
||||
| Analytics (F381-F410) | 30 | 4 | 8 | 18 |
|
||||
| Search avancé (F351-F370) | 20 | 4 | 6 | 10 |
|
||||
| Education suite (F291-F305) | 15 | 0 | 0 | 15 |
|
||||
| **Total P5** | **80** | **8** | **16** | **56** |
|
||||
|
||||
### Phase 6 : Monétisation Avancée (3 mois, 70 features) — COMPLÉTÉE à 10%
|
||||
|
||||
| Module ORIGIN | Features | Implémentées | Partielles | Manquantes |
|
||||
|---|---|---|---|---|
|
||||
| Notifications (F551-F570) | 20 | 6 | 4 | 10 |
|
||||
| Security Advanced (F571-F585) | 15 | 8 | 4 | 3 |
|
||||
| Developer API (F586-F600) | 15 | 5 | 4 | 6 |
|
||||
| UI/UX (F436-F455) | 20 | 6 | 6 | 8 |
|
||||
| **Total P6** | **70** | **25** | **18** | **27** |
|
||||
|
||||
**Note** : Certaines features P6 ont été implémentées en avance (security headers, rate limiting, API keys, notifications basiques, thèmes light/dark).
|
||||
|
||||
### Phase 7 : Scale & Enterprise (3 mois, 90 features) — COMPLÉTÉE à 5%
|
||||
|
||||
| Module ORIGIN | Features | Implémentées | Partielles | Manquantes |
|
||||
|---|---|---|---|---|
|
||||
| Cloud Storage (F331-F350) | 20 | 6 | 6 | 8 |
|
||||
| Administration (F411-F435) | 25 | 8 | 6 | 11 |
|
||||
| External Integrations (F501-F520) | 20 | 4 (OAuth) | 2 | 14 |
|
||||
| Native Apps (F521-F535) | 15 | 1 (PWA) | 0 | 14 |
|
||||
| Scaling infra | 10 | 2 (K8s manifests) | 2 | 6 |
|
||||
| **Total P7** | **90** | **21** | **16** | **53** |
|
||||
|
||||
### Phase 8 : Innovation & IA (4 mois, 80 features) — COMPLÉTÉE à 0%
|
||||
|
||||
| Module ORIGIN | Features | Implémentées | Partielles | Manquantes |
|
||||
|---|---|---|---|---|
|
||||
| AI Avancé | 30 | 0 | 0 | 30 |
|
||||
| Web3/Blockchain (F491-F500) | 30 | 0 | 0 | 30 |
|
||||
| VR/AR Experimental | 20 | 0 | 0 | 20 |
|
||||
| **Total P8** | **80** | **0** | **0** | **80** |
|
||||
|
||||
### Synthèse Phases
|
||||
|
||||
| Phase | Prévu | Implémenté | Partiel | Manquant | % Complet |
|
||||
|---|---|---|---|---|---|
|
||||
| P0 | 0 (stab) | — | — | — | 85% |
|
||||
| P1 | 50 | 43 | 4 | 3 | 90% |
|
||||
| P2 | 60 | 34 | 13 | 13 | 68% |
|
||||
| P3 | 80 | 40 | 18 | 22 | 61% |
|
||||
| P4 | 90 | 10 | 14 | 66 | 19% |
|
||||
| P5 | 80 | 8 | 16 | 56 | 15% |
|
||||
| P6 | 70 | 25 | 18 | 27 | 49% |
|
||||
| P7 | 90 | 21 | 16 | 53 | 32% |
|
||||
| P8 | 80 | 0 | 0 | 80 | 0% |
|
||||
| **TOTAL** | **600** | **181** | **99** | **320** | **38%** |
|
||||
|
||||
---
|
||||
|
||||
## 3. ANALYSE DES ÉCARTS PAR MODULE (F001-F600)
|
||||
|
||||
### M01 : Auth & Security (F001-F030)
|
||||
|
||||
| ID | Feature | Statut | Preuve / Fichier |
|
||||
|---|---|---|---|
|
||||
| F001 | Register email/password | ✅ | `core/auth/handler.go`, `RegisterForm.tsx` |
|
||||
| F002 | Email verification | ✅ | `core/auth/handler.go` (VerifyEmail), `VerifyEmailPage.tsx` |
|
||||
| F003 | Login email/password | ✅ | `handlers/auth.go` (Login), `LoginPage.tsx` |
|
||||
| F004 | OAuth Google | ✅ | `services/oauth_service.go`, `oauth_handlers.go` |
|
||||
| F005 | OAuth GitHub | ✅ | `services/oauth_service.go`, test integration |
|
||||
| F006 | OAuth Discord | ✅ | `services/oauth_service.go` |
|
||||
| F007 | OAuth Spotify | ✅ | `services/oauth_service.go` |
|
||||
| F008 | Remember Me | ✅ | `handlers/auth.go` (rememberMe), `LoginForm.tsx` |
|
||||
| F009 | Logout | ✅ | `core/auth/handler.go` (Logout), `useLogout.ts` |
|
||||
| F010 | Logout all | ✅ | `handlers/session.go` (LogoutAll) |
|
||||
| F011 | Password reset request | ✅ | `password_reset_handler.go`, `ForgotPasswordForm.tsx` |
|
||||
| F012 | Password reset confirm | ✅ | `password_reset_handler.go`, `ResetPasswordPage.tsx` |
|
||||
| F013 | Change password | ✅ | `api/user/handler.go` (ChangePassword) |
|
||||
| F014 | Password history | ❌ | Uniquement dans ORIGIN schema, pas de table en migration |
|
||||
| F015 | Password strength | ✅ | `validators/password_validator.go`, `PasswordStrengthIndicator.tsx` |
|
||||
| F016 | 2FA TOTP setup | ✅ | `services/two_factor_service.go`, `2fa-service.ts` |
|
||||
| F017 | 2FA verify | ✅ | `TwoFactorVerify.tsx`, POST `/auth/login/2fa` |
|
||||
| F018 | 2FA backup codes | ✅ | `two_factor_service.go` (RecoveryCodes) |
|
||||
| F019 | 2FA SMS | ⚠️ | UI existe (`TwoFactorSetupStep2.tsx`), `handleSmsUnavailable` → "not yet available" |
|
||||
| F020 | Passkeys/WebAuthn | ⚠️ | `PasskeyModal.tsx` (UI only, simule API), pas de backend |
|
||||
| F021 | Session management | ✅ | `handlers/session.go`, `SessionsPage.tsx` |
|
||||
| F022 | Login notification | ❌ | Non implémenté |
|
||||
| F023 | Geolocation | ⚠️ | `ip_address` stocké dans sessions, pas de geo-lookup |
|
||||
| F024 | Login history | ⚠️ | `LoginHistory.tsx` existe, route backend planifiée (V0_903) |
|
||||
| F025 | IP whitelisting | ❌ | Non implémenté (whitelist CORS/OAuth uniquement) |
|
||||
| F026 | Rate limiting login | ✅ | `endpoint_limiter.go` (LoginRateLimit) |
|
||||
| F027 | CAPTCHA | ❌ | Planifié V0_903, pas d'implémentation |
|
||||
| F028 | Bruteforce detection | ⚠️ | Via rate limiting + lockout, pas de service dédié |
|
||||
| F029 | Account lockout | ✅ | `account_lockout_service.go` |
|
||||
| F030 | Security questions | ❌ | Non implémenté |
|
||||
|
||||
**Score : 22/30 (73%) — 4 partielles, 5 manquantes**
|
||||
|
||||
### M02 : Profils (F031-F065)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F031 | Create/edit profile | ✅ | `profile_handler.go`, `ProfileForm.tsx` |
|
||||
| F032 | Avatar upload | ✅ | `avatar_handler.go`, `avatarService.ts` |
|
||||
| F033 | Banner upload | ✅ | `user.go` (BannerURL), `EditProfileImagesCard.tsx` |
|
||||
| F034 | Username | ✅ | `useUsernameAvailability.ts`, check-username endpoint |
|
||||
| F035 | Bio | ✅ | `user.go` (Bio), `ProfileIdentity.tsx` |
|
||||
| F036 | Location | ✅ | `user.go`, `ProfileIdentity.tsx` |
|
||||
| F037 | Birth date | ✅ | `profile_handler.go`, `useEditProfile.ts` |
|
||||
| F038 | Gender | ✅ | `profile_handler.go`, `useEditProfile.ts` |
|
||||
| F039 | Language preference | ✅ | `api/user/types.go` (Language), `ui.ts` (setLanguage) |
|
||||
| F040 | Timezone | ✅ | `api/user/types.go` (Timezone), `settingsSchema.ts` |
|
||||
| F041 | Custom profile URL | ✅ | `/u/:username` route, `UserProfilePage.tsx` |
|
||||
| F042 | Public/private profile | ✅ | `ProfileVisibility`, `ProfileVisibilityCard.tsx` |
|
||||
| F043 | Public contact email | ⚠️ | Pas de champ `contact_email` dédié |
|
||||
| F044 | Social links | ✅ | `user.go` (SocialLinks), `ProfileSocialLinks.tsx` |
|
||||
| F045 | Badges/achievements | ⚠️ | `RoleBadge` pour rôles, pas de système gamification complet |
|
||||
| F046 | Role User | ✅ | `models/role.go`, `rbac_service.go` |
|
||||
| F047 | Role Artist/Creator | ✅ | Idem |
|
||||
| F048 | Role Producer | ✅ | Idem |
|
||||
| F049 | Role Label | ✅ | Idem |
|
||||
| F050 | Role Formateur | ✅ | Idem |
|
||||
| F051 | Role Moderator | ✅ | Idem |
|
||||
| F052 | Role Admin | ✅ | Idem |
|
||||
| F053 | Granular permissions | ✅ | `rbac_service.go`, tables `permissions`, `role_permissions` |
|
||||
| F054 | Verified badge | ✅ | `user.go` (IsVerified), `EmailVerificationBadge.tsx` |
|
||||
| F055 | KYC | ❌ | Non implémenté |
|
||||
| F056 | Change email | ✅ | `ChangeEmailModal.tsx` |
|
||||
| F057 | Change username | ✅ | `ChangeUsernameModal.tsx`, `CanChangeUsername` |
|
||||
| F058 | Change language | ✅ | `ui.ts` (setLanguage), `PreferenceSettings.tsx` |
|
||||
| F059 | Theme light/dark | ✅ | `ThemeProvider.tsx`, `ui.ts` (theme) |
|
||||
| F060-062 | Notification preferences | ✅ | `notification_handlers.go`, `PushPreferencesSection.tsx` |
|
||||
| F063 | Privacy preferences | ✅ | `PrivacySettings.tsx`, `settings_handler.go` |
|
||||
| F064 | Profile visibility | ✅ | `ProfileVisibilityCard.tsx` |
|
||||
| F065 | Delete account GDPR | ✅ | `account_deletion_handler.go`, `DeleteAccountView.tsx` |
|
||||
|
||||
**Score : 31/35 (89%) — 2 partielles, 1 manquante (KYC)**
|
||||
|
||||
### M03 : File Management (F066-F105)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F066 | Single file upload | ✅ | `uploadService.ts` (chunked), `upload.go` |
|
||||
| F067 | Batch upload | ✅ | `BatchUpload()`, `BatchUploader.tsx` |
|
||||
| F068 | Drag & drop | ✅ | `react-dropzone`, `FileUploadZone.tsx` |
|
||||
| F069 | URL import | ❌ | Non implémenté |
|
||||
| F070 | Cloud import (Dropbox/Drive) | ⚠️ | UI dans `IntegrationsView.tsx`, pas de backend |
|
||||
| F071 | Progress bar | ✅ | `UploadModalProgress`, `FilePreviewCard.tsx` |
|
||||
| F072 | Pause/resume upload | ⚠️ | Chunked upload backend (`track_chunk_service.go`), pas de resume UI |
|
||||
| F073 | File size validation | ✅ | `MAX_FILE_SIZE` 100MB, `ValidateFileSize` |
|
||||
| F074 | MIME validation | ✅ | `AllowedMimeTypes`, magic bytes validation |
|
||||
| F075 | Antivirus scan | ⚠️ | ClamAV configuré mais désactivé par défaut |
|
||||
| F076 | Image compression | ⚠️ | Avatar resize via `image_service.go`, pas de compression générique |
|
||||
| F077 | Audio transcoding | ✅ | `audio_transcode_service.go`, FFmpeg pipeline |
|
||||
| F078 | Thumbnail generation | ✅ | `thumbnail_job.go` |
|
||||
| F079 | Metadata extraction | ✅ | `metadata_service.go` |
|
||||
| F080 | Watermarking | ⚠️ | `WatermarkSettingsModal.tsx` UI, pas de backend audio watermark |
|
||||
| F081-F090 | Formats (MP3/WAV/FLAC/OGG/AIFF/M4A/ZIP/RAR/PDF/VST) | ⚠️ | Audio : MP3/WAV/FLAC/OGG/M4A/AAC/AIFF ✅ — RAR/PDF/VST ❌ |
|
||||
| F091-F105 | Track metadata fields | ⚠️ | title, artist, album, genre, BPM, key, duration, tags, cover ✅ — label, ISRC, copyright, lyrics, suggested tags ❌ |
|
||||
|
||||
**Score : 11/40 plein, 9 partiels (50% complet)**
|
||||
|
||||
### M04 : Audio Streaming (F106-F150)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F106-F110 | Play/pause/next/prev/seek | ✅ | `playerStore.ts` |
|
||||
| F111-F115 | Volume/mute/shuffle/repeat/speed | ✅ | `playerStore.ts` |
|
||||
| F116 | Crossfade | ✅ | `crossfadeSeconds` dans playerStore |
|
||||
| F117 | Gapless playback | ❌ | Non implémenté |
|
||||
| F118 | Waveform | ✅ | `WaveformDisplay.tsx`, `WaveformVisualizer.tsx` |
|
||||
| F119 | Spectrogram | ⚠️ | Mode "spectrogram" dans `VisualizerSettingsModal.tsx` |
|
||||
| F120 | Equalizer | ❌ | Non implémenté |
|
||||
| F121 | Mini-player | ✅ | `MiniPlayer.tsx` |
|
||||
| F122 | Picture-in-Picture | ⚠️ | Référence dans `PlayerBarRight`, pas d'implémentation complète |
|
||||
| F123 | Keyboard shortcuts | ✅ | `useMediaSession.ts` + raccourcis clavier |
|
||||
| F124 | Media Session API | ✅ | `useMediaSession.ts` |
|
||||
| F125 | Chromecast/AirPlay | ❌ | Non implémenté |
|
||||
| F126-F135 | Queue management | ✅ | addToQueue, removeFromQueue, reorderQueue, QueuePanel |
|
||||
| F136-F145 | Playlists CRUD | ✅ | API complète, `SharePlaylistModal`, `CollaboratorManagement` |
|
||||
| F146-F148 | Smart playlists, merge, export | ❌ | Non implémenté |
|
||||
| F149-F150 | Playlist import/share | ⚠️ | Share ✅, import limité (fichier uniquement) |
|
||||
|
||||
**Score : 28/45 (62%) — 4 partiels, 7 manquants**
|
||||
|
||||
### M05 : Chat (F151-F185)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F151 | DM 1-to-1 | ✅ | `chat_repository.go` GetDirectMessageRoom |
|
||||
| F152-F154 | Rooms public/private/group | ✅ | `room_type` varchar(50) |
|
||||
| F155 | Text messages | ✅ | `CreateMessage` |
|
||||
| F156 | Emoji | ❌ | Non vérifié dans chat |
|
||||
| F157 | Reactions | ❌ | Non implémenté |
|
||||
| F158 | Edit message | ✅ | `is_edited` flag |
|
||||
| F159 | Delete message | ✅ | `is_deleted` flag |
|
||||
| F160 | Threads | ⚠️ | `parent_id` existe, pas d'UI thread |
|
||||
| F161 | Mentions | ❌ | Non implémenté |
|
||||
| F162 | Markdown | ❌ | Non implémenté |
|
||||
| F163 | Image sharing | ❌ | Non implémenté dans chat |
|
||||
| F164 | GIFs | ❌ | Non implémenté |
|
||||
| F165 | Audio share | ❌ | Non implémenté |
|
||||
| F166-F170 | Search, filters, pin, bookmarks, notifs | ⚠️ | `is_pinned` ✅, push_message pref ✅, reste ❌ |
|
||||
| F171-F175 | Push, sound, badge, typing, read receipts | ❌ | Non implémenté |
|
||||
| F176-F180 | Online/offline, busy, custom status, AFK, last seen | ⚠️ | `PresenceBadge.tsx`, basique |
|
||||
| F181-F185 | Room users, invisible, presence notif, listening, rich | ❌ | Non implémenté |
|
||||
|
||||
**Score : 8/35 (23%) — 4 partiels, 17 manquants**
|
||||
|
||||
### M06 : Social (F186-F225)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F186-F188 | Follow/unfollow/followers list | ✅ | `profileService.ts`, `profileHandler` |
|
||||
| F189 | Following list | ✅ | Idem |
|
||||
| F190 | Block user | ✅ | `BlockUser`, `UnblockUser` |
|
||||
| F191 | Report user | ⚠️ | `ReportHandler` existe, flow incomplet |
|
||||
| F192 | Recommendations | ❌ | Non implémenté |
|
||||
| F193-F195 | Suggestions, collaboration request, referral | ❌ | Non implémenté |
|
||||
| F196 | Share profile | ⚠️ | URL `/u/:username` partageable |
|
||||
| F197 | QR code | ❌ | Non implémenté |
|
||||
| F198 | New follower notification | ⚠️ | Notification service existe, feature non confirmée |
|
||||
| F199 | Close friends | ❌ | Non implémenté |
|
||||
| F200 | Artist subscription | ❌ | Non implémenté |
|
||||
| F201-F210 | Posts (text/image/audio/video, like, comment, repost, quote, pin) | ⚠️ | `CreatePostModal`, `PostCard`, `ExploreView` — partiellement mocké |
|
||||
| F211-F215 | Delete/edit/private post, hashtags, trending | ⚠️ | UI existe, backend partiel |
|
||||
| F216-F225 | Groups (create, join, leave, invite, roles, forum, events, shared files, stats) | ⚠️ | `CreateGroupModal`, groupe cover — reste non implémenté |
|
||||
|
||||
**Score : 6/40 (15%) — 8 partiels, 16 manquants**
|
||||
|
||||
### M07 : Marketplace (F226-F275)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F226-F235 | Products CRUD, upload, preview, images, description, pricing | ✅ | `productService.ts`, `CreateProductView*` |
|
||||
| F236-F240 | Categories, tags, BPM/key metadata, genre, formats | ✅ | Product model avec metadata complète |
|
||||
| F241-F243 | Licences streaming/personal/commercial | ✅ | `CreateProductViewPricingCard` |
|
||||
| F244-F246 | Licence exclusive/lease/unlimited | ⚠️ | Exclusive ✅, lease/unlimited ❌ |
|
||||
| F247-F250 | Terms, auto contract, e-signature, licence history | ❌ | Non implémenté |
|
||||
| F251-F255 | Cart, multi-product, wishlist, taxes, promo codes | ⚠️ | Cart ✅, multi-product ✅, wishlist ⚠️, taxes/promos ❌ |
|
||||
| F256-F260 | Checkout, card, PayPal, crypto, invoice | ⚠️ | Hyperswitch checkout ✅, PayPal/crypto ❌ |
|
||||
| F261-F265 | Purchase history, re-download, refund, dispute, support | ⚠️ | Purchase history ✅, reste partiel |
|
||||
| F266-F270 | Seller dashboard, stats, revenue, charts, top products | ✅ | `SellerDashboardView`, `commerceService` stats |
|
||||
| F271-F275 | Conversion, reviews, replies, promotions, payout | ⚠️ | Reviews ✅, Stripe Connect payout ✅, promotions ❌ |
|
||||
|
||||
**Score : 22/50 (44%) — 10 partiels, 8 manquants**
|
||||
|
||||
### M08 : Education (F276-F305)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F276-F290 | Courses, lessons, quizzes, certificates, enrollment | ⚠️ | Backend schema + handlers dans `core/education/` — tests skippés, pas de frontend |
|
||||
| F291-F305 | Advanced education (paths, subscriptions, instructors) | ❌ | Non implémenté |
|
||||
|
||||
**Score : 0/30 plein, 6 partiels (backend only) — Module effectivement NON LIVRÉ**
|
||||
|
||||
### M09 : Hardware/Gear (F306-F330)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F306-F315 | Gear CRUD, images, documents, repairs, warranty | ✅ | `gear_handler.go`, `GearItem`, `GearRepair`, `GearDocument`, `GearImage` |
|
||||
| F316-F320 | Warranty notifications, maintenance history | ✅ | `gear_warranty_notifier`, inventory pages frontend |
|
||||
| F321-F325 | Lending, insurance, comparison | ❌ | Non implémenté |
|
||||
| F326-F330 | Marketplace integration, wish list gear | ❌ | Non implémenté |
|
||||
|
||||
**Score : 12/25 (48%) — 2 partiels, 5 manquants**
|
||||
|
||||
### M10 : Cloud Storage (F331-F350)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F331-F340 | Folders, files, upload, quota, versioning, restore, share | ✅ | `cloud_handler.go`, `CloudPage`, `CloudFileVersions` |
|
||||
| F341-F345 | Backup worker, integration view | ✅ | Cloud backup worker, `CloudIntegrationView` |
|
||||
| F346-F350 | Nextcloud sync, external cloud, collaborative edit | ❌ | Non implémenté |
|
||||
|
||||
**Score : 8/20 (40%) — 4 partiels**
|
||||
|
||||
### M11 : Search (F351-F380)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F351-F355 | Fulltext search, filters (genre, BPM, key) | ✅ | `FullTextSearchService`, PostgreSQL tsvector, `TrackSearchService` |
|
||||
| F356-F360 | Sort, autocomplete, suggestions | ✅ | Relevance, popularity, `SearchService.Suggestions` |
|
||||
| F361-F370 | Phonetic, spelling correction, semantic, audio similarity | ❌ | Non implémenté (nécessite Elasticsearch) |
|
||||
| F371-F380 | Search courses, groups, advanced filters | ❌ | Non implémenté |
|
||||
|
||||
**Score : 10/30 (33%) — Limité à PostgreSQL pg_trgm, pas d'Elasticsearch**
|
||||
|
||||
### M12 : Analytics (F381-F410)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F381-F390 | Playback analytics, heatmap | ✅ | `PlaybackAnalyticsService`, `PlaybackHeatmapService` |
|
||||
| F391-F395 | Creator dashboard (stats, trends) | ⚠️ | Track stats + plays over time ✅, demographics/devices ❌ |
|
||||
| F396-F400 | Seller analytics | ⚠️ | `SellerTransfer`, basique |
|
||||
| F401-F410 | Admin analytics (DAU/MAU, retention, infra) | ⚠️ | `AdminContentAnalytics`, `GetDashboardStats` — basique |
|
||||
|
||||
**Score : 4/30 plein, 8 partiels (40% avec partiels)**
|
||||
|
||||
### M13 : Administration (F411-F435)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F411-F415 | User management (search, filters, pagination) | ✅ | `GetUsers` avec search/filters |
|
||||
| F416-F420 | Ban, suspend, reset password, role change | ⚠️ | `BanUserModal`, admin role checks — flow incomplet |
|
||||
| F421-F425 | Moderation queue, content moderation | ⚠️ | `ReportHandler`, `ReportService` — queue UI manquante |
|
||||
| F426-F430 | Copyright strikes, appeal system | ❌ | Non implémenté |
|
||||
| F431-F435 | Feature flags, announcements, maintenance | ✅ | Feature flags, maintenance middleware, announcements |
|
||||
|
||||
**Score : 12/25 (48%) — 6 partiels, 3 manquants**
|
||||
|
||||
### M14 : UI/UX (F436-F455)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F436-F438 | Light/dark/system theme | ✅ | `ThemeProvider`, `ui.ts` |
|
||||
| F439-F441 | High contrast, compact/comfortable, accent hue | ✅ | `AppearanceSettingsView` |
|
||||
| F442-F445 | Font size, custom palette, drag-drop layouts | ⚠️ | Font size ✅, reste ❌ |
|
||||
| F446-F450 | Keyboard nav, screen reader, ARIA, focus visible, WCAG | ⚠️ | `contrast.ts`, `aria-*`, `focus-visible`, `useReducedMotion` — partiel |
|
||||
| F451-F455 | Dyslexia font, video transcriptions, auto subtitles | ❌ | Non implémenté |
|
||||
|
||||
**Score : 10/20 (50%) — 4 partiels, 4 manquants**
|
||||
|
||||
### M15 : AI (F456-F470)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F456 | AI Mastering | ❌ | Non implémenté |
|
||||
| F457 | Stem separation | ❌ | Non implémenté |
|
||||
| F458 | Genre detection | ❌ | Non implémenté |
|
||||
| F459 | BPM detection | ⚠️ | `enable_bpm_detection` dans stream server config |
|
||||
| F460 | Key detection | ❌ | Non implémenté |
|
||||
| F461 | Vocal removal | ❌ | Non implémenté |
|
||||
| F462 | Denoising | ❌ | Non implémenté |
|
||||
| F463 | Audio upscaling | ❌ | Non implémenté |
|
||||
| F464 | Auto-tags | ❌ | Non implémenté |
|
||||
| F465 | Similarity detection | ⚠️ | `TrackSimilarityMatrix` dans stream discovery |
|
||||
| F466 | Content ID | ❌ | Non implémenté |
|
||||
| F467 | ML recommendations | ⚠️ | Recommendation services basiques (non ML) |
|
||||
| F468 | Voice synthesis | ❌ | Non implémenté |
|
||||
| F469 | Auto lyrics transcription | ❌ | Non implémenté |
|
||||
| F470 | AI mixing | ❌ | Non implémenté |
|
||||
|
||||
**Score : 0/15 plein, 3 partiels — Module NON IMPLÉMENTÉ**
|
||||
|
||||
### M16 : Livestreaming (F471-F480)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F471 | Live DJ sets | ⚠️ | `LiveStream` model, `GoLiveView`, HLS support |
|
||||
| F472 | Live concerts | ⚠️ | Infrastructure HLS partagée |
|
||||
| F473 | Production sessions | ❌ | Non implémenté |
|
||||
| F474 | Multi-camera | ❌ | Non implémenté |
|
||||
| F475 | Live chat | ❌ | Pas de chat live dédié |
|
||||
| F476 | Donations/tips | ⚠️ | `TipStreamerModal` (UI) |
|
||||
| F477 | VOD replay | ⚠️ | HLS VOD support dans stream server |
|
||||
| F478 | Clipping | ❌ | Non implémenté |
|
||||
| F479 | Scheduling | ❌ | Non implémenté |
|
||||
| F480 | Viewer analytics | ❌ | Non implémenté |
|
||||
|
||||
**Score : 0/10 plein, 4 partiels — Module BASIQUE**
|
||||
|
||||
### M17 : Collaboration (F481-F490)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F481 | DAW collaboration | ❌ | Non implémenté |
|
||||
| F482 | Version control audio | ❌ | Non implémenté |
|
||||
| F483 | Temporal comments | ❌ | Non implémenté |
|
||||
| F484 | Stems sharing | ❌ | Non implémenté |
|
||||
| F485 | Co-edit playlists | ✅ | `CollaboratorManagement`, read/write/admin |
|
||||
| F486 | Whiteboard | ❌ | Non implémenté |
|
||||
| F487 | Video chat | ❌ | Non implémenté |
|
||||
| F488 | Screen sharing | ❌ | Non implémenté |
|
||||
| F489 | Project templates | ❌ | Non implémenté |
|
||||
| F490 | Collaboration workflow | ❌ | Non implémenté |
|
||||
|
||||
**Score : 1/10 (10%) — Uniquement playlist collaboration**
|
||||
|
||||
### M18 : Web3 (F491-F500)
|
||||
|
||||
**Score : 0/10 (0%) — Aucune feature Web3 implémentée**
|
||||
|
||||
(NFT, blockchain, smart contracts, tokens, staking, DAO, IPFS, wallet : tout manquant)
|
||||
|
||||
### M19 : External Integrations (F501-F520)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F501-F504 | OAuth integrations | ✅ | Google, GitHub, Discord, Spotify |
|
||||
| F505-F510 | Social links | ✅ | Profile social links (YouTube, Instagram, Twitter, etc.) |
|
||||
| F511-F520 | DAW plugins, SoundCloud import, DistroKid, Zapier | ❌ | Non implémenté |
|
||||
|
||||
**Score : 4/20 (20%) — OAuth uniquement**
|
||||
|
||||
### M20 : Native Apps (F521-F535)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F521 | PWA | ✅ | `usePWA` hook |
|
||||
| F522-F535 | iOS, Android, React Native, Tauri, Electron | ❌ | Abandonné (voir `V1_LIMITATIONS.md`) |
|
||||
|
||||
**Score : 1/15 (7%) — PWA uniquement, apps natives abandonnées**
|
||||
|
||||
### M21 : Gamification (F536-F550)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F536-F540 | XP, levels, achievements | ⚠️ | `ProfileXPView`, `AchievementCard` (frontend, issues TS) |
|
||||
| F541-F550 | Challenges, leaderboards, rewards, etc. | ❌ | Backend API gamification absent |
|
||||
|
||||
**Score : 0/15 plein, 2 partiels — Supprimé du scope v0.971**
|
||||
|
||||
### M22 : Notifications (F551-F570)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F551-F555 | Real-time, badge counter, center, mark read | ✅ | `NotificationService`, `NotificationMenu`, `NotificationBell` |
|
||||
| F556-F560 | Filters, grouping, persistent, quick actions, history | ⚠️ | Filtres par type ✅, reste partiel |
|
||||
| F561-F570 | Email templates (welcome, confirmation, reset, etc.) | ⚠️ | Password reset, verification ✅ — welcome, follower, purchase, newsletter ❌ |
|
||||
|
||||
**Score : 6/20 (30%) — 4 partiels, email templates incomplets**
|
||||
|
||||
### M23 : Security Advanced (F571-F585)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F571-F573 | Rate limiting global, per-endpoint, DDoS | ✅ | `endpoint_limiter.go`, rate limiting global |
|
||||
| F574-F576 | SQL injection, XSS, CSRF protection | ✅ | GORM params, DOMPurify, CSRF middleware |
|
||||
| F577-F579 | Clickjacking, CSP, HSTS | ⚠️ | HSTS ✅, X-Frame-Options ✅, CSP avec `unsafe-inline` |
|
||||
| F580-F582 | Security headers, audit logs, GDPR | ✅ | Headers complets, audit logs, `data_export_service` |
|
||||
| F583-F585 | CCPA, SOC2, data export | ⚠️ | GDPR export ✅, CCPA/SOC2 ❌ |
|
||||
|
||||
**Score : 10/15 (67%) — 2 partiels (CSP, compliance)**
|
||||
|
||||
### M24 : Developer API (F586-F600)
|
||||
|
||||
| ID | Feature | Statut | Preuve |
|
||||
|---|---|---|---|
|
||||
| F586-F588 | REST API, API keys, webhooks | ✅ | `APIKeyService`, webhooks (register, list, test, stats) |
|
||||
| F589 | OpenAPI/Swagger | ✅ | Documentation Swagger |
|
||||
| F590-F592 | GraphQL, OAuth2 server | ❌ | Non implémenté |
|
||||
| F593-F595 | SDK JS/Python/Go | ❌ | Non implémenté |
|
||||
| F596-F600 | Sandbox, playground, logs, analytics, dashboard | ⚠️ | `APIPlaygroundView`, `DeveloperDashboardView` — partiel |
|
||||
|
||||
**Score : 5/15 (33%) — 4 partiels, GraphQL et SDKs manquants**
|
||||
|
||||
### Synthèse Globale par Module
|
||||
|
||||
| Module | Features ORIGIN | Plein | Partiel | Manquant | % Complet |
|
||||
|---|---|---|---|---|---|
|
||||
| M01 Auth | 30 | 22 | 4 | 4 | 73% |
|
||||
| M02 Profils | 35 | 31 | 2 | 2 | 89% |
|
||||
| M03 File Mgmt | 40 | 11 | 9 | 20 | 39% |
|
||||
| M04 Streaming | 45 | 28 | 4 | 13 | 67% |
|
||||
| M05 Chat | 35 | 8 | 4 | 23 | 29% |
|
||||
| M06 Social | 40 | 6 | 8 | 26 | 25% |
|
||||
| M07 Marketplace | 50 | 22 | 10 | 18 | 54% |
|
||||
| M08 Education | 30 | 0 | 6 | 24 | 10% |
|
||||
| M09 Gear | 25 | 12 | 2 | 11 | 52% |
|
||||
| M10 Cloud | 20 | 8 | 4 | 8 | 50% |
|
||||
| M11 Search | 30 | 10 | 0 | 20 | 33% |
|
||||
| M12 Analytics | 30 | 4 | 8 | 18 | 27% |
|
||||
| M13 Admin | 25 | 12 | 6 | 7 | 60% |
|
||||
| M14 UI/UX | 20 | 10 | 4 | 6 | 60% |
|
||||
| M15 AI | 15 | 0 | 3 | 12 | 10% |
|
||||
| M16 Livestream | 10 | 0 | 4 | 6 | 20% |
|
||||
| M17 Collaboration | 10 | 1 | 0 | 9 | 10% |
|
||||
| M18 Web3 | 10 | 0 | 0 | 10 | 0% |
|
||||
| M19 Integrations | 20 | 4 | 2 | 14 | 25% |
|
||||
| M20 Native Apps | 15 | 1 | 0 | 14 | 7% |
|
||||
| M21 Gamification | 15 | 0 | 2 | 13 | 7% |
|
||||
| M22 Notifications | 20 | 6 | 4 | 10 | 40% |
|
||||
| M23 Security Adv. | 15 | 10 | 2 | 3 | 73% |
|
||||
| M24 Dev API | 15 | 5 | 4 | 6 | 47% |
|
||||
| **TOTAL** | **600** | **211** | **96** | **297** | **43%** |
|
||||
|
||||
---
|
||||
|
||||
## 4. DÉVIATIONS ARCHITECTURALES
|
||||
|
||||
### 4.1 Tableau des Déviations
|
||||
|
||||
| # | Spécification ORIGIN | Réalité Actuelle | Type | Impact | Recommandation |
|
||||
|---|---|---|---|---|---|
|
||||
| **D01** | **Chat Server Rust** (`veza-chat-server`) — service Rust dédié avec Axum/SQLx pour WebSocket | Migré dans le backend Go en v0.502 (ADR-002). Chat via WebSocket Go. Code Rust orphelin dans `/veza-chat-server/` | Pivot majeur | **Positif** — réduit la complexité opérationnelle, 1 service en moins à déployer/maintenir | ✅ **Garder** la décision. Supprimer le code orphelin Rust du monorepo |
|
||||
| **D02** | **Traefik** comme API Gateway avec service discovery, load balancing, TLS automatique | **HAProxy** comme reverse proxy avec blue-green deployments | Substitution | **Neutre** — HAProxy est production-grade, gère le blue-green | ⚠️ **Acceptable** pour l'échelle actuelle. Migrer vers Traefik/Envoy si >10K users pour le service discovery |
|
||||
| **D03** | **Elasticsearch** pour search avec fulltext, phonétique, facettes, autocomplete | **PostgreSQL** pg_trgm + tsvector/tsquery | Simplification | **Négatif à terme** — ne scalera pas au-delà de ~10K tracks pour la recherche phonétique/sémantique | ❌ **Ajouter Elasticsearch** quand le volume de données le justifie (>50K tracks). Critique pour Phase 5 |
|
||||
| **D04** | **gRPC** pour communication inter-services (Go ↔ Rust) | **REST/HTTP** entre Go API et Rust stream server | Simplification | **Neutre** — REST plus simple à debug, suffisant pour le traffic actuel. gRPC nécessaire si latency inter-service critique | ⚠️ **Acceptable**. Migrer vers gRPC uniquement si bottleneck mesuré |
|
||||
| **D05** | **JWT RS256** (asymétrique) pour multi-service JWT validation | **JWT HS256** (symétrique) partagé entre Go et Rust | Risque sécurité | **Négatif** — HS256 nécessite le secret partagé sur tous les services. RS256 permet validation sans partager le secret privé | ❌ **Migrer vers RS256** avant production multi-instance. Effort : ~2 jours |
|
||||
| **D06** | **CQRS** (Command Query Responsibility Segregation) | Pattern requête-réponse traditionnel (Handler→Service→Repository) | Simplification | **Neutre** — CQRS apporte de la complexité sans bénéfice clair à cette échelle | ✅ **Garder** le pattern actuel. CQRS uniquement si read/write loads divergent significativement |
|
||||
| **D07** | **Stripe** comme payment processor unique | **Hyperswitch** (payment router open-source) qui supporte Stripe comme backend | Pivot stratégique | **Positif** — Hyperswitch offre plus de flexibilité (multi-provider), open-source, réduction de vendor lock-in | ✅ **Garder**. Avantage compétitif — permet d'ajouter PayPal, crypto sans changer l'architecture |
|
||||
| **D08** | **Electron** desktop app (Phase 7) | Non développé | Retard planifié | **Neutre** — Phase 7 feature, pas urgente | ⚠️ **Reporter**. Considérer Tauri (plus léger que Electron) quand nécessaire |
|
||||
| **D09** | **React Native** mobile app (Phase 4-7) | Non développé. PWA uniquement (`usePWA` hook) | Fonctionalité manquante | **Négatif** — PWA limité (pas d'accès App Store, audio background limité sur iOS) | ❌ **Décider** : PWA-first ou app native ? Si market B2C audio, app native quasi-obligatoire |
|
||||
| **D10** | **Ansible** pour déploiement automatisé | **Incus/LXD** + Docker Compose, scripts bash | Substitution | **Neutre** — fonctionnel pour 1-2 serveurs. Ansible nécessaire si >3 serveurs | ⚠️ **Acceptable** court terme. Automatiser avec Ansible/Terraform avant Phase 7 |
|
||||
| **D11** | **pgBouncer** pour connection pooling PostgreSQL | **GORM built-in** connection pooling (MaxOpenConns, MaxIdleConns) | Simplification | **Négatif à terme** — GORM pooling suffisant pour <100 connexions. pgBouncer nécessaire pour >500 connexions | ⚠️ **Acceptable** jusqu'à 5K users. Ajouter pgBouncer avant scaling Phase 7 |
|
||||
| **D12** | **Kafka** (optionnel dans ORIGIN) pour event streaming | **RabbitMQ** uniquement | Conforme | **Neutre** — ORIGIN listait Kafka comme optionnel. RabbitMQ couvre les besoins actuels | ✅ **Garder** RabbitMQ. Kafka uniquement si besoin de replay/event sourcing à grande échelle |
|
||||
| **D13** | **Argon2id** pour hashing passwords (Rust services) | **bcrypt** côté Go, Argon2 référencé dans ORIGIN mais pas implémenté | Déviation mineure | **Neutre** — bcrypt avec cost 12 est acceptable. Argon2id est le standard actuel mais bcrypt reste sûr | ⚠️ **Acceptable**. Migrer vers Argon2id lors d'une refonte auth si opportunité |
|
||||
| **D14** | **HashiCorp Vault** pour secrets management | **Variables d'environnement** + fichiers `.env` | Risque opérationnel | **Négatif** — .env en dev acceptable, mais production nécessite un secret manager | ❌ **Ajouter** un secret manager avant production (Vault, AWS Secrets Manager, ou Infisical) |
|
||||
| **D15** | **Redis Cluster** pour haute disponibilité | **Redis standalone** | Simplification | **Neutre** — suffisant pour <10K users. Cluster nécessaire pour HA | ⚠️ **Acceptable** jusqu'à Phase 7. Prévoir Redis Sentinel ou Cluster avant |
|
||||
|
||||
### 4.2 Bilan des Déviations
|
||||
|
||||
| Catégorie | Nombre | Impact Global |
|
||||
|---|---|---|
|
||||
| ✅ Déviations positives (garder) | 4 | Simplification bienvenue |
|
||||
| ⚠️ Déviations acceptables (court terme) | 6 | À adresser avant scaling |
|
||||
| ❌ Déviations à corriger | 5 | Blocantes pour les objectifs ORIGIN |
|
||||
|
||||
### 4.3 Déviations Critiques à Corriger en Priorité
|
||||
|
||||
1. **D05 - JWT RS256** : migration HS256→RS256 avant déploiement multi-instance (~2j)
|
||||
2. **D14 - Secrets Manager** : Vault ou équivalent avant production (~3j)
|
||||
3. **D03 - Elasticsearch** : nécessaire pour les objectifs Phase 5 search (~2 semaines)
|
||||
4. **D09 - Mobile App** : décision stratégique PWA vs native requise
|
||||
5. **D11 - pgBouncer** : avant scaling au-delà de 5K users (~1j)
|
||||
|
||||
---
|
||||
|
||||
## 5. MÉTRIQUES DE QUALITÉ : ORIGIN vs ACTUEL
|
||||
|
||||
### 5.1 Code Quality Metrics
|
||||
|
||||
| Métrique | Objectif ORIGIN | État Actuel | Zone | Écart |
|
||||
|---|---|---|---|---|
|
||||
| **Test Coverage Global** | ≥ 80% (🟢), objectif 90% | ~50% frontend (vitest threshold), Go non mesuré en CI | 🔴 | -30% minimum |
|
||||
| **Test Coverage Go** | ≥ 80% | 306 fichiers test, pas de coverage % enforced | 🟡 | Non mesuré |
|
||||
| **Test Coverage Rust** | ≥ 80% | Pas de CI coverage pour stream server | 🔴 | Non mesuré |
|
||||
| **Test Coverage Frontend** | ≥ 80% | Threshold 50% vitest, stories Storybook pour composants | 🔴 | ~-30% |
|
||||
| **Complexité cyclomatique** | < 10 par fonction | Non mesuré (pas de gocyclo/SonarQube configuré) | ❓ | Non mesuré |
|
||||
| **Duplication code** | < 3% | Non mesuré (pas de jscpd/SonarQube) | ❓ | Non mesuré |
|
||||
| **TODO/FIXME/HACK** | < 50 | ~150+ TODO, ~30 FIXME, ~5 HACK | 🔴 | +100 TODOs au-dessus du seuil |
|
||||
| **Linter errors** | 0 errors, < 5 warnings | ESLint configuré, golangci-lint en CI | 🟡 | Partiellement conforme |
|
||||
| **Code churn** | < 5 changes/semaine/fichier | Non mesuré | ❓ | Non mesuré |
|
||||
|
||||
### 5.2 Performance Metrics
|
||||
|
||||
| Métrique | Objectif ORIGIN | État Actuel | Zone | Écart |
|
||||
|---|---|---|---|---|
|
||||
| **API Latency p95** | < 100ms | Non mesuré en production (pas de Prometheus actif) | ❓ | Non mesurable |
|
||||
| **API Latency p99** | < 200ms | Idem | ❓ | Non mesurable |
|
||||
| **DB Query p95** | < 10ms | Non mesuré (pas de pg_stat_statements) | ❓ | Non mesurable |
|
||||
| **Frontend FCP** | < 1.5s | Non mesuré (pas de Lighthouse CI) | ❓ | Non mesurable |
|
||||
| **Frontend TTI** | < 3.5s | Non mesuré | ❓ | Non mesurable |
|
||||
| **Lighthouse Performance** | ≥ 95 | Non mesuré | ❓ | Non mesurable |
|
||||
| **Lighthouse Accessibility** | ≥ 100 | Non mesuré | ❓ | Non mesurable |
|
||||
| **Bundle JS initial (gzip)** | < 200 KB | Non mesuré (Vite build, code splitting actif) | ❓ | Non mesurable |
|
||||
| **Audio stream start** | < 500ms | Non mesuré | ❓ | Non mesurable |
|
||||
| **Rebuffering rate** | < 0.5% | Non mesuré | ❓ | Non mesurable |
|
||||
| **Throughput** | 10K req/s | Non testé (k6 load tests existent mais pas de baseline) | ❓ | Non mesurable |
|
||||
| **Uptime** | 99.9% | Non mesuré (pre-launch) | ❓ | N/A |
|
||||
|
||||
**Constat critique** : **Aucune métrique de performance n'est mesurée en continu**. Les configs Prometheus/Grafana existent dans docker-compose mais ne sont pas activement utilisées pour du monitoring de production. Les k6 load tests existent mais n'ont pas produit de baseline documentée.
|
||||
|
||||
### 5.3 Security Metrics
|
||||
|
||||
| Métrique | Objectif ORIGIN | État Actuel | Zone | Écart |
|
||||
|---|---|---|---|---|
|
||||
| **Vulnérabilités critiques** | 0 | 2 (VEZA-SEC-001: JWT default Rust, VEZA-SEC-002: JWT mismatch) | 🔴 | -2 |
|
||||
| **Vulnérabilités hautes** | 0 | 1 (VEZA-SEC-003: admin credentials hardcodées dans tests) | 🟡 | -1 |
|
||||
| **Password hashing** | bcrypt cost ≥ 12 ou Argon2id | bcrypt cost 12 ✅ | 🟢 | Conforme |
|
||||
| **JWT expiration access** | 15 min | 15 min configuré ✅ | 🟢 | Conforme |
|
||||
| **JWT expiration refresh** | 7 jours | 7 jours configuré ✅ | 🟢 | Conforme |
|
||||
| **MFA admin/moderator** | Obligatoire | Disponible mais pas obligatoire | 🟡 | Non enforced |
|
||||
| **Rate limiting** | Tous endpoints publics | Login ✅, global ✅, per-endpoint ✅, DDoS ❌ | 🟡 | WAF absent |
|
||||
| **Input validation serveur** | Obligatoire | Go binding tags ✅, validation service ✅ | 🟢 | Conforme |
|
||||
| **Security headers** | CSP, HSTS, X-Frame-Options, etc. | HSTS ✅, X-Frame-Options ✅, CSP ⚠️ (unsafe-inline) | 🟡 | CSP à durcir |
|
||||
| **Secrets dans code** | Jamais | JWT default "veza-stream-jwt-secret" dans config Rust | 🔴 | Violation |
|
||||
| **Audit logs** | Actions sensibles | `audit_log` table, `AuditLog` handler | 🟢 | Conforme |
|
||||
| **GDPR right to erasure** | Obligatoire | `account_deletion_handler.go`, `data_export_service` | 🟢 | Conforme |
|
||||
| **Dependency scanning** | Daily (npm audit, cargo audit, govulncheck) | CI workflows incluent audit | 🟢 | Conforme |
|
||||
| **Penetration testing** | Trimestriel + après major releases | Non réalisé | 🔴 | Non conforme |
|
||||
| **Bug bounty** | HackerOne ou Bugcrowd | Non mis en place | 🔴 | Non conforme |
|
||||
|
||||
### 5.4 Quality Gates (CI/CD)
|
||||
|
||||
| Quality Gate ORIGIN | Objectif | Implémenté ? | Détail |
|
||||
|---|---|---|---|
|
||||
| Tests pass (unit, integration) | Bloquant | ✅ | GitHub Actions run tests |
|
||||
| Coverage ≥ 80% | Bloquant | ❌ | Seuil à 50% frontend, pas enforced Go/Rust |
|
||||
| No linter errors | Bloquant | ⚠️ | ESLint en CI, golangci-lint partiel |
|
||||
| No security vulns (critical/high) | Bloquant | ⚠️ | npm audit en CI, mais 2 vulns critiques non résolues |
|
||||
| Code review 2+ reviewers | Bloquant | ❌ | Solo developer, pas de reviewers |
|
||||
| Pre-deployment smoke tests | Bloquant | ❌ | Pas de smoke tests staging |
|
||||
| Performance tests | Bloquant | ❌ | k6 existe mais pas intégré en gate |
|
||||
| SAST/DAST scan | Bloquant | ⚠️ | SAST partiel (CI), pas de DAST |
|
||||
| Rollback plan | Bloquant | ⚠️ | Blue-green via HAProxy, pas de rollback automatique |
|
||||
|
||||
**Conformité Quality Gates : 1/9 pleinement conforme, 4/9 partiels, 4/9 non conformes**
|
||||
|
||||
### 5.5 Bilan Qualité
|
||||
|
||||
| Domaine | Score ORIGIN requis | Score Actuel Estimé | Écart |
|
||||
|---|---|---|---|
|
||||
| Code Quality | 8/10 | 5/10 | -3 |
|
||||
| Performance | 9/10 | Inconnu (non mesuré) | Non mesurable |
|
||||
| Security | 9/10 | 6/10 | -3 |
|
||||
| UX/Accessibility | 9/10 | 5/10 | -4 |
|
||||
| Infrastructure | 8/10 | 6/10 | -2 |
|
||||
| **Moyenne** | **8.6/10** | **5.5/10** | **-3.1** |
|
||||
|
||||
---
|
||||
|
||||
## 6. ROADMAP POUR ATTEINDRE LES OBJECTIFS ORIGIN
|
||||
|
||||
### 6.1 Stratégie Recommandée : « Phase 3.5 » avant Phase 4
|
||||
|
||||
Le projet ne peut pas sauter directement à Phase 5 pour rattraper le retard. La stratégie recommandée est de consolider ce qui existe (compléter P2-P3 à 100%), puis aborder Phase 4 avec un scope réduit et pragmatique.
|
||||
|
||||
```
|
||||
Timeline Proposée (mars 2026 → mars 2027)
|
||||
|
||||
Mar-Avr 2026 Mai-Jul 2026 Août-Oct 2026 Nov 2026-Mar 2027
|
||||
┌──────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
|
||||
│ PHASE 3.5│ │ PHASE 4R │ │ PHASE 5R │ │ PHASE 6R │
|
||||
│ Consolid.│ │ Social Lite │ │ Analytics + │ │ Premium + │
|
||||
│ Sécurité │ │ + Live │ │ Search + │ │ Dev API + │
|
||||
│ Tests │ │ Streaming │ │ Notifs │ │ Scale │
|
||||
│ ~50 feat │ │ ~60 feat │ │ ~70 feat │ │ ~80 feat │
|
||||
└──────────┘ └──────────────┘ └──────────────┘ └──────────────────┘
|
||||
2 mois 3 mois 3 mois 5 mois
|
||||
```
|
||||
|
||||
### 6.2 Phase 3.5 : Consolidation (Mars-Avril 2026) — 2 mois
|
||||
|
||||
**Objectif** : Corriger les violations ORIGIN critiques, compléter les features P2-P3 manquantes, établir les métriques de qualité.
|
||||
|
||||
#### Sprint A : Sécurité & Quality Gates (2 semaines)
|
||||
|
||||
| Action | Effort | Priorité | Impact |
|
||||
|---|---|---|---|
|
||||
| Corriger VEZA-SEC-001 : remplacer JWT secret par défaut Rust | 0.5j | P0 | Critique |
|
||||
| Corriger VEZA-SEC-002 : aligner JWT issuer/audience Go↔Rust | 1j | P0 | Critique |
|
||||
| Migrer JWT HS256 → RS256 (D05) | 2j | P0 | Architecture |
|
||||
| Configurer coverage CI/CD : Go `go test -coverprofile` + seuil 70% | 1j | P1 | Qualité |
|
||||
| Configurer coverage CI/CD : Rust `cargo tarpaulin` + seuil 60% | 1j | P1 | Qualité |
|
||||
| Monter threshold vitest de 50% à 70% | 1j | P1 | Qualité |
|
||||
| Ajouter Lighthouse CI dans GitHub Actions | 1j | P2 | Performance |
|
||||
| Activer Prometheus metrics dans docker-compose dev | 0.5j | P2 | Monitoring |
|
||||
|
||||
#### Sprint B : Features P2 Manquantes — Chat (2 semaines)
|
||||
|
||||
| Action | Effort | Priorité | Impact |
|
||||
|---|---|---|---|
|
||||
| Chat reactions (emoji) | 2j | P1 | Engagement |
|
||||
| Chat mentions (@user) | 2j | P1 | UX |
|
||||
| Chat typing indicator | 1j | P2 | UX |
|
||||
| Chat read receipts | 2j | P2 | UX |
|
||||
| Chat message search | 2j | P1 | Fonctionnel |
|
||||
|
||||
#### Sprint C : Features P3 Manquantes — Marketplace (2 semaines)
|
||||
|
||||
| Action | Effort | Priorité | Impact |
|
||||
|---|---|---|---|
|
||||
| Education frontend (compléter le module backend existant) | 5j | P1 | Module entier |
|
||||
| Licence history/tracking | 2j | P2 | Business |
|
||||
| Purchase refund flow | 2j | P1 | Business |
|
||||
| Email templates (purchase confirmation, welcome) | 2j | P1 | UX |
|
||||
|
||||
#### Sprint D : Debt & Cleanup (2 semaines)
|
||||
|
||||
| Action | Effort | Priorité | Impact |
|
||||
|---|---|---|---|
|
||||
| Supprimer code mort (`veza-chat-server/`, `soundcloud/`) | 1j | P2 | Maintenabilité |
|
||||
| Convertir 50 TODOs en issues GitHub | 2j | P2 | Tracking |
|
||||
| Uniformiser error handling Go (RespondWithAppError partout) | 3j | P1 | Qualité |
|
||||
| Ajouter secret manager (Infisical ou Vault) | 2j | P0 | Sécurité |
|
||||
| Durcir CSP (supprimer unsafe-inline) | 1j | P1 | Sécurité |
|
||||
|
||||
**Total Phase 3.5 : ~50 actions, 2 mois, 1-2 développeurs**
|
||||
|
||||
### 6.3 Phase 4R : Social & Live Streaming (Mai-Juillet 2026) — 3 mois
|
||||
|
||||
**Scope réduit** : pas de DAW collaboration (trop complexe), pas de Web3, pas de gamification.
|
||||
|
||||
| Module | Features à implémenter | Effort | Priorité |
|
||||
|---|---|---|---|
|
||||
| **Social Posts** | Create/edit/delete post, like, comment, repost, hashtags, explore | 3 semaines | P1 |
|
||||
| **Groups** | Join/leave, roles, member list, group feed | 2 semaines | P1 |
|
||||
| **Live Streaming** | Go live flow, live chat intégré, donations, VOD replay | 4 semaines | P1 |
|
||||
| **Notifications push** | Web push, notification grouping, email follower/comment | 2 semaines | P1 |
|
||||
| **Chat enrichi** | GIFs (Giphy), image sharing, threads UI | 2 semaines | P2 |
|
||||
|
||||
**Total Phase 4R : ~60 features, 3 mois, 2-3 développeurs**
|
||||
|
||||
### 6.4 Phase 5R : Analytics & Search (Août-Octobre 2026) — 3 mois
|
||||
|
||||
| Module | Features à implémenter | Effort | Priorité |
|
||||
|---|---|---|---|
|
||||
| **Elasticsearch** | Setup, indexation tracks/users/playlists, fulltext + phonétique | 3 semaines | P0 |
|
||||
| **Search avancé** | Filtres multi-critères, autocomplete intelligent, spelling correction | 2 semaines | P1 |
|
||||
| **Creator Dashboard** | Plays over time, demographics, device breakdown, export CSV | 3 semaines | P1 |
|
||||
| **Admin Dashboard** | DAU/MAU, retention, revenue, user growth charts | 2 semaines | P1 |
|
||||
| **Notifications email** | Templates complets : welcome, follower, purchase, sale, newsletter | 2 semaines | P2 |
|
||||
| **UI/UX** | WCAG AA compliance, keyboard nav complète, screen reader | 2 semaines | P2 |
|
||||
|
||||
**Total Phase 5R : ~70 features, 3 mois, 2-3 développeurs**
|
||||
|
||||
### 6.5 Phase 6R : Premium & Scale (Novembre 2026-Mars 2027) — 5 mois
|
||||
|
||||
| Module | Features à implémenter | Effort | Priorité |
|
||||
|---|---|---|---|
|
||||
| **Premium tiers** | Free/Premium/Pro, Stripe billing, trial, upgrade/downgrade | 4 semaines | P0 |
|
||||
| **Developer API** | GraphQL, SDK JS, webhook improvements, sandbox | 3 semaines | P1 |
|
||||
| **Admin avancé** | Moderation queue UI, copyright strikes, appeal system | 3 semaines | P1 |
|
||||
| **Cloud Storage** | Nextcloud integration ou S3 sync avancé | 2 semaines | P2 |
|
||||
| **Infrastructure** | pgBouncer, Redis Sentinel, Terraform/Ansible, auto-scaling | 4 semaines | P1 |
|
||||
| **Mobile** | Décision PWA-enhanced ou React Native app | 4 semaines | P1 |
|
||||
| **BPM/Key detection** | Intégrer librosa ou essentia pour détection automatique | 2 semaines | P2 |
|
||||
|
||||
**Total Phase 6R : ~80 features, 5 mois, 2-4 développeurs**
|
||||
|
||||
### 6.6 Estimation Effort Total
|
||||
|
||||
| Phase | Durée | Features | Développeurs | Effort (homme-mois) |
|
||||
|---|---|---|---|---|
|
||||
| Phase 3.5 | 2 mois | ~50 | 1-2 | 3 |
|
||||
| Phase 4R | 3 mois | ~60 | 2-3 | 7 |
|
||||
| Phase 5R | 3 mois | ~70 | 2-3 | 7 |
|
||||
| Phase 6R | 5 mois | ~80 | 2-4 | 12 |
|
||||
| **Total** | **13 mois** | **~260** | — | **~29 homme-mois** |
|
||||
|
||||
Avec ce plan révisé, le projet atteindrait **~490 features** sur 600 d'ici mars 2027, soit **82% de complétion ORIGIN**.
|
||||
|
||||
---
|
||||
|
||||
## 7. RECOMMANDATIONS : GARDER, MODIFIER, OU ABANDONNER
|
||||
|
||||
### 7.1 Objectifs ORIGIN à GARDER tels quels
|
||||
|
||||
| Objectif | Raison |
|
||||
|---|---|
|
||||
| Auth complète (F001-F030) | Fondation critique, déjà à 73% |
|
||||
| Profils (F031-F065) | Déjà à 89%, terminer les derniers % |
|
||||
| Streaming audio (F106-F150) | Core product, déjà à 67% |
|
||||
| Marketplace (F226-F275) | Source de revenue, à 54% |
|
||||
| Security (F571-F585) | Non négociable, déjà à 73% |
|
||||
| Notifications (F551-F570) | Essentiel pour rétention |
|
||||
| Admin (F411-F435) | Nécessaire pour opérations |
|
||||
| Cloud storage (F331-F350) | Différenciateur produit |
|
||||
| Search fulltext (F351-F370) | Critique pour UX |
|
||||
| Analytics (F381-F410) | Nécessaire pour créateurs et vendeurs |
|
||||
| Developer API (F586-F600) | Permet écosystème |
|
||||
| UI/UX accessibilité (F436-F455) | Obligation légale (WCAG), différenciateur |
|
||||
| CI/CD + Quality Gates | Fondation technique non négociable |
|
||||
| Test coverage 80% | Objectif réaliste, protège contre régressions |
|
||||
|
||||
### 7.2 Objectifs ORIGIN à MODIFIER
|
||||
|
||||
| Objectif Original | Modification Proposée | Raison |
|
||||
|---|---|---|
|
||||
| **600 features en 24 mois** | **~490 features en 25 mois** (mars 2027) | Scope irréaliste pour 1-2 développeurs. 82% est un excellent résultat |
|
||||
| **Chat Rust (veza-chat-server)** | **Chat Go** (tel qu'implémenté) | ADR-002 validé, simplification positive |
|
||||
| **Stripe exclusif** | **Hyperswitch** (tel qu'implémenté) | Meilleure flexibilité, open-source |
|
||||
| **Elasticsearch Phase 5** | **Elasticsearch Phase 5R** (août 2026) | Reporter mais ne pas abandonner — critique pour scale |
|
||||
| **Traefik** | **HAProxy** puis migration si besoin | Fonctionnel, migrer uniquement si nécessaire |
|
||||
| **React Native mobile (Phase 4)** | **PWA-enhanced** court terme, natif Phase 6R | Économiser des mois de dev, réévaluer quand MAU > 5K |
|
||||
| **CQRS** | **Pattern request-response** | Complexité injustifiée à cette échelle |
|
||||
| **gRPC inter-service** | **REST** puis migration si bottleneck mesuré | Pragmatisme |
|
||||
| **Gamification (F536-F550)** | **Reporter à Phase 7+** | Pas critique pour le core product, gamification quand base users établie |
|
||||
| **Live Streaming (10 features complètes)** | **Live Streaming Lite (5 features)** | Go live, chat, tips, VOD — le reste en Phase 6R |
|
||||
|
||||
### 7.3 Objectifs ORIGIN à ABANDONNER
|
||||
|
||||
| Objectif | Raison d'Abandon | Alternative |
|
||||
|---|---|---|
|
||||
| **Web3/Blockchain (F491-F500)** | Market crypto incertain, complexité juridique, pas de demande marché prouvée | Surveiller le marché, réintroduire si demande utilisateur |
|
||||
| **VR/AR (E001-E020)** | Marché niche, coût de développement élevé, pas de ROI prévisible | Aucune — technologie prématurée pour une plateforme audio |
|
||||
| **Electron desktop app** | Tauri serait meilleur si besoin, mais PWA couvre 95% des cas | PWA avec offline mode |
|
||||
| **Voice synthesis AI (F468)** | Risques éthiques et juridiques (deepfakes audio) | Focus sur AI utile : BPM/key detection, auto-tags |
|
||||
| **AI mixing assistant (F470)** | Trop ambitieux, nécessite ML expertise dédiée | Partenariat avec service tiers (LANDR, iZotope) |
|
||||
| **DAO governance** | Hors scope produit audio, complexité juridique | Gouvernance classique (advisory board) |
|
||||
| **NFT minting** | Marché en déclin, risque réputationnel | Licensing traditionnel (déjà implémenté) |
|
||||
| **Kafka** | RabbitMQ suffit, pas de besoin event sourcing | Garder RabbitMQ |
|
||||
| **SOC2 certification** | Coût et effort disproportionnés pour une startup pre-revenue | Focus GDPR (obligatoire) + bonnes pratiques sécurité |
|
||||
|
||||
### 7.4 Matrice de Décision Finale
|
||||
|
||||
| Catégorie | Features | Action |
|
||||
|---|---|---|
|
||||
| ✅ Garder | ~430 features (72%) | Continuer le développement selon la roadmap |
|
||||
| ⚠️ Modifier | ~70 features (12%) | Ajuster le scope, l'ordre, ou l'approche |
|
||||
| ❌ Abandonner | ~100 features (17%) | Retirer du backlog, documenter la décision |
|
||||
|
||||
---
|
||||
|
||||
## 8. VIOLATIONS CRITIQUES ORIGIN ET PLAN DE CORRECTION
|
||||
|
||||
### 8.1 Violations des Règles Immuables ORIGIN
|
||||
|
||||
| # | Règle ORIGIN | Violation | Correction | Effort | Deadline |
|
||||
|---|---|---|---|---|---|
|
||||
| V01 | Coverage ≥ 80% toutes phases | Seuil à 50% frontend, non mesuré Go/Rust | Configurer CI coverage, monter progressivement : 60% → 70% → 80% | 5j | Fin avril 2026 |
|
||||
| V02 | 0 vulnérabilités critiques | 2 critiques (JWT) | Corriger VEZA-SEC-001 et VEZA-SEC-002 | 2j | Mi-mars 2026 |
|
||||
| V03 | Pas de feature creep | Features P6-P7 implémentées avant P3-P4 complètes | Consolider P2-P3 avant d'avancer (Phase 3.5) | — | Plan ci-dessus |
|
||||
| V04 | Phase complète à 100% avant suivante | P3 à 65%, P4 démarrée | Compléter P3 dans Phase 3.5 | 4 sem | Fin avril 2026 |
|
||||
| V05 | Security audit externe avant P1, P3, P5, P7 | Aucun audit externe réalisé | Commander un pentest externe | 2 sem + budget | Avant Phase 4R |
|
||||
| V06 | Code review 2+ reviewers | Solo developer | Recruter reviewers (communauté, freelance, AI review) | — | Continu |
|
||||
| V07 | Documentation API à jour | Swagger existe mais pas synchronisé automatiquement | Intégrer swag generate dans CI | 1j | Fin mars 2026 |
|
||||
|
||||
### 8.2 Priorités de Correction
|
||||
|
||||
```
|
||||
URGENT (< 2 semaines)
|
||||
├── V02 : Corriger les 2 vulns JWT critiques
|
||||
├── V07 : Synchroniser Swagger
|
||||
└── Nettoyer secrets hardcodés
|
||||
|
||||
IMPORTANT (< 2 mois)
|
||||
├── V01 : Coverage CI/CD configurée et progressive
|
||||
├── V04 : Compléter Phase 3 features manquantes
|
||||
├── V03 : Consolider avant d'avancer
|
||||
└── V05 : Commander pentest externe
|
||||
|
||||
NÉCESSAIRE (< 6 mois)
|
||||
├── V06 : Trouver des reviewers
|
||||
├── Coverage à 80%
|
||||
└── Lighthouse CI + performance baseline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CONCLUSION
|
||||
|
||||
Le projet Veza a construit une base technique solide avec une architecture clean, un monorepo bien organisé, et des fondations fonctionnelles (auth, streaming, marketplace, infrastructure CI/CD). Cependant, il accuse un retard de ~6 mois sur le plan ORIGIN, principalement dû à l'ambition du scope (600 features / 24 mois) face aux ressources disponibles.
|
||||
|
||||
**Le plan ORIGIN reste un excellent guide directeur**, mais il doit être adapté à la réalité :
|
||||
|
||||
1. **Réduire le scope de 600 à ~490 features** (abandonner Web3, VR/AR, certaines features AI)
|
||||
2. **Consolider avant d'avancer** (Phase 3.5 de stabilisation)
|
||||
3. **Corriger immédiatement les 2 vulnérabilités critiques JWT**
|
||||
4. **Établir les métriques de qualité** (coverage, performance, monitoring)
|
||||
5. **Suivre la roadmap révisée** pour atteindre 82% de complétion d'ici mars 2027
|
||||
|
||||
Le projet est **techniquement viable** et les choix architecturaux (pivots Chat Go, Hyperswitch, simplification gRPC→REST) sont **pragmatiques et justifiés**. L'enjeu principal n'est pas technique mais de **priorisation** : livrer les features qui génèrent le plus de valeur business en premier (marketplace, premium, analytics) tout en maintenant la qualité.
|
||||
|
||||
---
|
||||
|
||||
*Document généré le 4 mars 2026*
|
||||
*Basé sur : Audit Technique VEZA 2026-03-04 + Documentation ORIGIN*
|
||||
*Méthodologie : Analyse statique du code, cross-référencement F001-F600 avec le codebase, comparaison métriques ORIGIN vs mesures actuelles*
|
||||
|
||||
941
PENTEST_REPORT_VEZA_v0.12.6.md
Normal file
941
PENTEST_REPORT_VEZA_v0.12.6.md
Normal file
|
|
@ -0,0 +1,941 @@
|
|||
# RAPPORT DE PENTEST — VEZA v0.12.6
|
||||
|
||||
| Champ | Valeur |
|
||||
|-------|--------|
|
||||
| **Date** | 2026-03-11 |
|
||||
| **Auditeur** | Claude Opus 4.6 — Audit de sécurité interne (remplace prestataire externe) |
|
||||
| **Version analysée** | v0.12.6 (branche `feat/v0.12.6-pentest-audit`) |
|
||||
| **Périmètre** | Backend Go, Stream Server Rust, Frontend React, Infrastructure Docker, CI/CD |
|
||||
| **Méthodologie** | OWASP Top 10 (2021), OWASP API Security Top 10 (2023), ASVS v4.0 Level 2 |
|
||||
| **Classification** | Confidentiel — Usage interne |
|
||||
|
||||
---
|
||||
|
||||
## EXECUTIVE SUMMARY
|
||||
|
||||
### Verdict global
|
||||
|
||||
Le codebase VEZA est **globalement bien sécurisé** avec une architecture de sécurité mature (middleware chain, RBAC, token versioning, CSRF, rate limiting multi-couche). Les vulnérabilités critiques identifiées dans l'audit précédent (mars 2026) ont été corrigées (VEZA-SEC-001 JWT secret par défaut, VEZA-SEC-002 issuer/audience mismatch).
|
||||
|
||||
### Recommandation : **NO-GO — Corriger les CRITIQUES et HAUTS avant v1.0.0**
|
||||
|
||||
L'audit approfondi (6 passes parallèles sur l'intégralité du codebase) identifie **2 findings CRITIQUES**, **10 findings HAUTS**, **12 findings MOYENS**, **6 findings BAS**, et **5 findings INFO**. Les findings CRITIQUES et HAUTS doivent être corrigés impérativement.
|
||||
|
||||
### Résumé des findings
|
||||
|
||||
| Sévérité | Nombre | Détail |
|
||||
|----------|--------|--------|
|
||||
| CRITIQUE | 2 | IDOR chat rooms (lecture conversation privée sans membership), play_count/like_count exposés publiquement (violation éthique) |
|
||||
| HAUTE | 10 | Race conditions marketplace (downloads, promo codes, licence exclusive), production HS256, IP spoofing rate limiter (pas de TrustedProxies), RGPD hard delete incomplet, RTMP callback auth faible, co-écoute host hijack, modérateur self-strike, free trial illimité |
|
||||
| MOYENNE | 12 | Recovery codes math/rand, metrics IP spoofing, ClamAV latest, pagination sans limit max, WebSocket pas de re-auth, CSP Swagger, CI actions non-pinnées, RabbitMQ mgmt, password reset no rate limit, access token in response body, email dans logs, analytics sans k-anonymité |
|
||||
| BASSE | 6 | Password policy mismatch FE/BE, Hyperswitch version, dotenv crate, Elasticsearch sans auth, context.Background dans workers, Redis sans auth dev |
|
||||
| INFO | 5 | Bonnes pratiques confirmées (20+ contrôles), recommandations d'amélioration |
|
||||
|
||||
---
|
||||
|
||||
## FINDINGS DÉTAILLÉS
|
||||
|
||||
---
|
||||
|
||||
### [CRIT-001] : IDOR — Lecture de conversations privées sans vérification de membership
|
||||
|
||||
**Sévérité** : CRITIQUE
|
||||
**CVSS v3.1** : 9.1 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)
|
||||
**CWE** : CWE-639 — Authorization Bypass Through User-Controlled Key
|
||||
**OWASP** : A01:2021 — Broken Access Control
|
||||
**OWASP API** : API1:2023 — Broken Object Level Authorization
|
||||
**Fichier(s)** : `veza-backend-api/internal/handlers/room_handler.go:134-158` (GetRoom), `:255-314` (GetRoomHistory)
|
||||
**Composant** : Chat — Conversations
|
||||
|
||||
**Description**
|
||||
Les endpoints `GET /api/v1/conversations/:id` et `GET /api/v1/conversations/:id/history` ne vérifient **pas** que l'utilisateur authentifié est membre de la room avant de retourner les données. N'importe quel utilisateur authentifié peut lire l'intégralité des messages de n'importe quelle conversation en connaissant son UUID.
|
||||
|
||||
**Preuve de concept**
|
||||
```bash
|
||||
# L'utilisateur B (non membre) accède à la conversation privée de l'utilisateur A
|
||||
curl -H "Cookie: access_token=<jwt_user_B>" \
|
||||
https://veza.fr/api/v1/conversations/<room_uuid_private>/history
|
||||
# Retourne TOUS les messages de la conversation privée
|
||||
```
|
||||
|
||||
**Impact**
|
||||
- Confidentialité : Lecture de messages privés, données financières échangées, informations personnelles
|
||||
- Compliance : Violation RGPD (accès non autorisé aux données personnelles)
|
||||
- Business : Perte de confiance des utilisateurs, risque juridique
|
||||
|
||||
**Remédiation**
|
||||
```go
|
||||
func (h *RoomHandler) GetRoom(c *gin.Context) {
|
||||
roomID, _ := uuid.Parse(c.Param("id"))
|
||||
userID := c.MustGet("user_id").(uuid.UUID)
|
||||
|
||||
// SECURITY: Verify membership before returning room data
|
||||
isMember, err := h.roomService.IsRoomMember(c.Request.Context(), roomID, userID)
|
||||
if err != nil || !isMember {
|
||||
RespondWithAppError(c, apperrors.NewNotFoundError("Conversation"))
|
||||
return
|
||||
}
|
||||
// ... rest of handler
|
||||
}
|
||||
```
|
||||
|
||||
**Priorité de correction** : Immédiate (bloquant v1.0.0)
|
||||
|
||||
---
|
||||
|
||||
### [CRIT-002] : Métriques de popularité (play_count, like_count) exposées publiquement dans l'API
|
||||
|
||||
**Sévérité** : CRITIQUE (éthique VEZA)
|
||||
**CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
|
||||
**CWE** : CWE-497 — Exposure of Sensitive System Information
|
||||
**OWASP** : A01:2021 — Broken Access Control
|
||||
**Fichier(s)** : `veza-backend-api/internal/models/track.go:39-40`
|
||||
**Composant** : API — Tracks
|
||||
|
||||
**Description**
|
||||
Le modèle `Track` expose `play_count` et `like_count` dans les réponses JSON via les tags `json:"play_count"` et `json:"like_count"`. Ces données sont retournées dans toutes les réponses de l'API publique (liste de tracks, détail de track, recherche).
|
||||
|
||||
Cela viole la **règle immuable #4 du CLAUDE.md** : *"JAMAIS de métriques de popularité publiques — Les likes et play counts sont PRIVÉS (visibles uniquement par le créateur dans ses analytics)"*.
|
||||
|
||||
**Impact**
|
||||
- Violation du principe éthique fondamental de VEZA (pas de dark patterns FOMO)
|
||||
- Crée un biais de popularité dans la découverte musicale
|
||||
- Discrimine les artistes émergents
|
||||
|
||||
**Remédiation**
|
||||
```go
|
||||
// Créer un type de réponse publique sans les métriques
|
||||
type TrackPublicResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ArtistID uuid.UUID `json:"artist_id"`
|
||||
// PAS de PlayCount ni LikeCount
|
||||
}
|
||||
|
||||
// N'exposer play_count et like_count que dans /api/v1/me/tracks/:id/analytics (creator only)
|
||||
```
|
||||
|
||||
**Priorité de correction** : Immédiate (bloquant v1.0.0)
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-001] : Race condition TOCTOU sur le compteur de téléchargements marketplace
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 7.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N)
|
||||
**CWE** : CWE-367 — Time-of-check Time-of-use (TOCTOU) Race Condition
|
||||
**OWASP** : A04:2021 — Insecure Design
|
||||
**OWASP API** : API6:2023 — Unrestricted Access to Sensitive Business Flows
|
||||
**Fichier(s)** : `veza-backend-api/internal/core/marketplace/service.go:794-817`
|
||||
**Composant** : Marketplace — Downloads
|
||||
|
||||
**Description**
|
||||
La fonction `GetDownloadURL` vérifie d'abord qu'une licence existe avec `downloads_left > 0` (ligne 794), puis décrémente `downloads_left` séparément (ligne 817). Ces deux opérations ne sont pas atomiques et ne sont pas dans une transaction avec verrouillage.
|
||||
|
||||
**Preuve de concept**
|
||||
```
|
||||
# Deux requêtes simultanées avec downloads_left = 1
|
||||
# T1: SELECT ... WHERE downloads_left > 0 → trouvé (downloads_left = 1)
|
||||
# T2: SELECT ... WHERE downloads_left > 0 → trouvé (downloads_left = 1)
|
||||
# T1: UPDATE downloads_left = downloads_left - 1 → downloads_left = 0
|
||||
# T2: UPDATE downloads_left = downloads_left - 1 → downloads_left = -1
|
||||
# Résultat: 2 téléchargements au lieu de 1
|
||||
```
|
||||
|
||||
**Impact**
|
||||
Un acheteur peut contourner la limite de téléchargements d'une licence en envoyant plusieurs requêtes simultanées. Impact financier direct pour les créateurs vendant des licences exclusives à téléchargements limités.
|
||||
|
||||
**Remédiation**
|
||||
```go
|
||||
// Utiliser une transaction avec SELECT FOR UPDATE
|
||||
tx := s.db.Begin()
|
||||
err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||
Where("buyer_id = ? AND product_id = ? AND downloads_left > 0 AND revoked_at IS NULL", buyerID, productID).
|
||||
First(&license).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return "", ErrNoLicense
|
||||
}
|
||||
// ... generate URL ...
|
||||
tx.Model(&license).Update("downloads_left", gorm.Expr("downloads_left - 1"))
|
||||
tx.Commit()
|
||||
```
|
||||
Alternativement : `UPDATE ... SET downloads_left = downloads_left - 1 WHERE downloads_left > 0 RETURNING *`
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-002] : Production déployée en HS256 au lieu de RS256
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 7.4 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N)
|
||||
**CWE** : CWE-327 — Use of a Broken or Risky Cryptographic Algorithm
|
||||
**OWASP** : A02:2021 — Cryptographic Failures
|
||||
**Fichier(s)** : `docker-compose.prod.yml:158`, `veza-backend-api/internal/services/jwt_service.go:32-79`
|
||||
**Composant** : Authentication — JWT
|
||||
|
||||
**Description**
|
||||
Le `docker-compose.prod.yml` configure `JWT_SECRET` (ligne 158) mais pas `JWT_PRIVATE_KEY_PATH` ni `JWT_PUBLIC_KEY_PATH`. Le code Go préfère RS256 si les clés sont fournies, sinon fallback HS256. En production, c'est HS256 qui est utilisé.
|
||||
|
||||
HS256 utilise un secret partagé symétrique — si le secret est compromis (leak env var, backup, log), un attaquant peut forger des tokens valides. RS256 nécessite la clé privée pour signer mais la clé publique suffit pour vérifier — meilleure séparation des responsabilités.
|
||||
|
||||
**Impact**
|
||||
Compromission du JWT_SECRET = compromission de tous les comptes utilisateurs. Avec RS256, seul le serveur ayant la clé privée peut signer des tokens.
|
||||
|
||||
**Remédiation**
|
||||
1. Générer une paire RSA 2048-bit pour la production
|
||||
2. Ajouter dans `docker-compose.prod.yml` :
|
||||
```yaml
|
||||
- JWT_PRIVATE_KEY_PATH=/secrets/jwt_private.pem
|
||||
- JWT_PUBLIC_KEY_PATH=/secrets/jwt_public.pem
|
||||
```
|
||||
3. Monter les clés via Docker secrets ou volume sécurisé
|
||||
4. Supprimer `JWT_SECRET` de la config production
|
||||
5. Invalider tous les tokens existants (incrémenter token_version globalement)
|
||||
|
||||
**Priorité de correction** : Immédiate (avant v1.0.0)
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-003] : User repository bypass du context de requête
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L)
|
||||
**CWE** : CWE-400 — Uncontrolled Resource Consumption
|
||||
**OWASP** : A04:2021 — Insecure Design
|
||||
**Fichier(s)** : `veza-backend-api/internal/repositories/user_repository.go:125-150`
|
||||
**Composant** : Data Layer — User Repository
|
||||
|
||||
**Description**
|
||||
Six méthodes du UserRepository (`GetByID`, `GetByEmail`, `GetByUsername`, `Create`, `Update`, `Delete`) utilisent `context.Background()` au lieu de propager le context de la requête HTTP. Cela signifie que :
|
||||
1. Les timeouts de requête ne s'appliquent pas aux opérations DB sous-jacentes
|
||||
2. L'annulation de la requête (client disconnect) ne cancelle pas la query DB
|
||||
3. Un attaquant peut déclencher des requêtes DB longues qui ne seront pas annulées
|
||||
|
||||
**Impact**
|
||||
Sous charge, des requêtes lentes peuvent s'accumuler car elles ne sont pas annulées quand le client se déconnecte. Cela peut mener à une saturation du pool de connexions DB (DoS).
|
||||
|
||||
**Remédiation**
|
||||
```go
|
||||
// Avant (ligne 125)
|
||||
func (r *UserRepository) GetByID(userID uuid.UUID) (*models.User, error) {
|
||||
return r.GetUserByID(context.Background(), userID)
|
||||
}
|
||||
|
||||
// Après
|
||||
func (r *UserRepository) GetByID(ctx context.Context, userID uuid.UUID) (*models.User, error) {
|
||||
return r.GetUserByID(ctx, userID)
|
||||
}
|
||||
```
|
||||
Propager le context dans toute la chaîne d'appel (handler → service → repository).
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-004] : Race condition sur les codes promo (TOCTOU)
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 7.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N)
|
||||
**CWE** : CWE-362 — Concurrent Execution Using Shared Resource
|
||||
**Fichier(s)** : `veza-backend-api/internal/core/marketplace/service.go:463-464,753-776`
|
||||
**Composant** : Marketplace — Promo Codes
|
||||
|
||||
**Description**
|
||||
La validation du code promo (`validatePromoCodeTx`) vérifie `UsedCount >= MaxUses` puis incrémente `used_count` séparément. Sans `SELECT FOR UPDATE`, deux requêtes concurrentes peuvent utiliser le même code promo au-delà de sa limite.
|
||||
|
||||
**Remédiation**
|
||||
Ajouter `clause.Locking{Strength: "UPDATE"}` dans la requête de validation du code promo.
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-005] : Pas de protection race condition sur licences exclusives
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 7.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N)
|
||||
**CWE** : CWE-362 — Concurrent Execution Using Shared Resource
|
||||
**Fichier(s)** : `veza-backend-api/internal/core/marketplace/service.go:393-532`
|
||||
**Composant** : Marketplace — Licenses
|
||||
|
||||
**Description**
|
||||
Aucune vérification atomique n'empêche deux acheteurs d'acheter simultanément une licence exclusive du même produit. Deux transactions peuvent créer deux licences exclusives pour le même track.
|
||||
|
||||
**Remédiation**
|
||||
Ajouter un check `SELECT FOR UPDATE` dans la transaction de création de licence :
|
||||
```go
|
||||
var existing License
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("product_id = ? AND type = 'exclusive' AND revoked_at IS NULL", prod.ID).
|
||||
First(&existing).Error; err == nil {
|
||||
return ErrExclusiveLicenseAlreadySold
|
||||
}
|
||||
```
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-006] : Rate limiter bypass — TrustedProxies non configuré
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
|
||||
**CWE** : CWE-346 — Origin Validation Error
|
||||
**Fichier(s)** : `veza-backend-api/internal/middleware/rate_limiter.go:131,265,353,429`
|
||||
**Composant** : Infrastructure — Rate Limiting
|
||||
|
||||
**Description**
|
||||
Gin's `c.ClientIP()` fait confiance à `X-Forwarded-For` automatiquement. Sans appel explicite à `engine.SetTrustedProxies()`, n'importe quel client peut forger son IP et bypasser tous les rate limiters (DDoS, brute force, upload).
|
||||
|
||||
**Remédiation**
|
||||
```go
|
||||
engine.SetTrustedProxies([]string{"127.0.0.1", "10.0.0.0/8"}) // Adapter au proxy réel
|
||||
```
|
||||
|
||||
**Priorité de correction** : Immédiate
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-007] : RGPD hard delete incomplet (Redis, Elasticsearch, audit logs)
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 6.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)
|
||||
**CWE** : CWE-212 — Improper Cross-boundary Removal of Sensitive Data
|
||||
**Fichier(s)** : `veza-backend-api/internal/workers/hard_delete_worker.go:101-121`
|
||||
**Composant** : RGPD — Account Deletion
|
||||
|
||||
**Description**
|
||||
Le hard delete worker anonymise uniquement `users` et `user_profiles` en DB. Les données PII restent dans : Redis (sessions/cache), Elasticsearch (index de recherche), audit_logs (user_id, IP), RabbitMQ (messages en queue).
|
||||
|
||||
**Remédiation**
|
||||
Étendre le hard delete worker pour nettoyer : Redis keys, ES index, audit_logs anonymisation, RabbitMQ purge.
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-008] : RTMP callback auth — fallback à query param "name" (stream key)
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 7.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)
|
||||
**CWE** : CWE-287 — Improper Authentication
|
||||
**Fichier(s)** : `veza-backend-api/internal/handlers/live_stream_callback.go:25-36`
|
||||
**Composant** : Livestream — RTMP Callbacks
|
||||
|
||||
**Description**
|
||||
La fonction `validateCallbackSecret()` fallback à `c.Query("name")` quand le header `X-RTMP-Callback-Secret` est absent. Le paramètre "name" contient en réalité le stream_key de l'utilisateur, pas le secret. Cela permet à quiconque connaissant un stream_key de déclencher des callbacks publish/publish_done.
|
||||
|
||||
De plus, si `RTMP_CALLBACK_SECRET` n'est pas configuré, la validation retourne `true` (fail-open).
|
||||
|
||||
**Remédiation**
|
||||
- Supprimer le fallback à `c.Query("name")`
|
||||
- Retourner `false` si le secret n'est pas configuré (fail-closed)
|
||||
- Utiliser `hmac.Equal()` pour la comparaison
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-009] : Co-écoute — participant non-host peut prendre le contrôle
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 6.5 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N)
|
||||
**CWE** : CWE-863 — Incorrect Authorization
|
||||
**Fichier(s)** : `veza-backend-api/internal/websocket/colistening/hub.go:102-139`, `handlers/co_listening_websocket_handler.go:50-126`
|
||||
**Composant** : Co-écoute WebSocket
|
||||
|
||||
**Description**
|
||||
Le flag `IsHost` est défini à la connexion mais n'est pas re-vérifié lors du traitement des messages. Un listener peut envoyer un message `UpdateHostState` et son état de lecture sera broadcast comme autoritaire à tous les participants.
|
||||
|
||||
**Remédiation**
|
||||
Vérifier `conn.IsHost` avant de traiter les messages de type `UpdateHostState`/`SyncClientStateMsg`.
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [HIGH-010] : Modérateur peut émettre des strikes sans contrôle de conflit d'intérêts
|
||||
|
||||
**Sévérité** : HAUTE
|
||||
**CVSS v3.1** : 6.5 (AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N)
|
||||
**CWE** : CWE-863 — Incorrect Authorization
|
||||
**Fichier(s)** : `veza-backend-api/internal/services/moderation_service.go:725-737`
|
||||
**Composant** : Modération — Strikes
|
||||
|
||||
**Description**
|
||||
Aucune vérification n'empêche un modérateur d'émettre un strike à lui-même ou de résoudre son propre appel. Un modérateur malveillant pourrait abuser du système pour cibler des utilisateurs spécifiques.
|
||||
|
||||
**Remédiation**
|
||||
Ajouter la vérification : `if reportedUserID == moderatorID { return error }`.
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-001] : Codes de récupération 2FA générés avec math/rand
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 5.9 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N)
|
||||
**CWE** : CWE-338 — Use of Cryptographically Weak PRNG
|
||||
**OWASP** : A02:2021 — Cryptographic Failures
|
||||
**ASVS** : V2.6.1
|
||||
**Fichier(s)** : `veza-backend-api/internal/services/two_factor_service.go:200`
|
||||
**Composant** : Authentication — 2FA
|
||||
|
||||
**Description**
|
||||
Les codes de récupération 2FA sont générés avec `mathrand.Intn()` (math/rand) au lieu de `crypto/rand`. `math/rand` utilise un PRNG prédictible — si un attaquant connaît le seed ou peut observer suffisamment de sorties, il peut prédire les codes de récupération futurs.
|
||||
|
||||
**Preuve de concept**
|
||||
```go
|
||||
// Ligne 200 - Utilisation de math/rand
|
||||
code[j] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[mathrand.Intn(36)]
|
||||
```
|
||||
Depuis Go 1.20, `math/rand` auto-seed est non déterministe, réduisant le risque. Cependant, les standards cryptographiques exigent `crypto/rand` pour tout matériel lié à l'authentification.
|
||||
|
||||
**Impact**
|
||||
Prédiction théorique des codes de récupération 2FA, permettant le contournement de l'authentification à deux facteurs.
|
||||
|
||||
**Remédiation**
|
||||
```go
|
||||
import "crypto/rand"
|
||||
import "math/big"
|
||||
|
||||
for j := 0; j < 8; j++ {
|
||||
n, _ := rand.Int(rand.Reader, big.NewInt(36))
|
||||
code[j] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"[n.Int64()]
|
||||
}
|
||||
```
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-002] : Bypass du whitelist IP des métriques via X-Forwarded-For
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
|
||||
**CWE** : CWE-290 — Authentication Bypass by Spoofing
|
||||
**OWASP** : A05:2021 — Security Misconfiguration
|
||||
**Fichier(s)** : `veza-backend-api/internal/middleware/metrics_protection.go:52-54`
|
||||
**Composant** : Infrastructure — Metrics
|
||||
|
||||
**Description**
|
||||
Le middleware de protection des métriques lit `X-Forwarded-For` directement depuis la requête et l'utilise pour vérifier l'IP, remplaçant `c.ClientIP()`. Un attaquant peut forger ce header pour bypasser le whitelist IP.
|
||||
|
||||
```go
|
||||
// Ligne 52-54 — X-Forwarded-For forgeable
|
||||
clientIP := c.ClientIP()
|
||||
if forwarded := c.GetHeader("X-Forwarded-For"); forwarded != "" {
|
||||
clientIP = strings.TrimSpace(strings.Split(forwarded, ",")[0])
|
||||
}
|
||||
```
|
||||
|
||||
**Preuve de concept**
|
||||
```bash
|
||||
curl -H "X-Forwarded-For: 127.0.0.1" https://veza.fr/metrics
|
||||
# Bypasse le whitelist si 127.0.0.1 est autorisé
|
||||
```
|
||||
|
||||
**Impact**
|
||||
Information disclosure : un attaquant peut accéder aux métriques Prometheus (pool DB, error rates, endpoints lents, charge système) pour préparer une attaque ciblée.
|
||||
|
||||
**Remédiation**
|
||||
Supprimer la lecture de `X-Forwarded-For` dans ce middleware. Utiliser uniquement `c.ClientIP()` qui est déjà configuré pour tenir compte du proxy de confiance (via `gin.SetTrustedProxies`).
|
||||
|
||||
```go
|
||||
// Supprimer les lignes 52-55, utiliser uniquement :
|
||||
clientIP := c.ClientIP()
|
||||
```
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-003] : Image Docker ClamAV non pinnée (:latest)
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 4.8 (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N)
|
||||
**CWE** : CWE-1104 — Use of Unmaintained Third Party Components
|
||||
**OWASP** : A06:2021 — Vulnerable and Outdated Components
|
||||
**Fichier(s)** : `docker-compose.yml:60`, `docker-compose.prod.yml:73`
|
||||
**Composant** : Infrastructure — Docker
|
||||
|
||||
**Description**
|
||||
L'image ClamAV utilise le tag `:latest` dans les deux fichiers docker-compose (dev ET production). Cela expose à :
|
||||
1. Des breaking changes non testées
|
||||
2. Un potentiel supply chain attack (image compromise)
|
||||
3. Des builds non reproductibles
|
||||
|
||||
**Impact**
|
||||
Un changement dans l'image ClamAV pourrait casser silencieusement le scan antivirus des uploads, soit en désactivant la protection, soit en bloquant tous les uploads.
|
||||
|
||||
**Remédiation**
|
||||
Pinner l'image à un tag de version spécifique :
|
||||
```yaml
|
||||
clamav:
|
||||
image: clamav/clamav:1.4.2 # Vérifier la dernière version stable
|
||||
```
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-004] : Pagination sans limite maximale explicite
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L)
|
||||
**CWE** : CWE-770 — Allocation of Resources Without Limits
|
||||
**OWASP API** : API4:2023 — Unrestricted Resource Consumption
|
||||
**Fichier(s)** : `veza-backend-api/internal/pagination/` (à vérifier), handlers multiples
|
||||
**Composant** : API — Pagination
|
||||
|
||||
**Description**
|
||||
L'audit précédent signalait l'absence de limite maximale explicite sur la pagination. Si un utilisateur envoie `?limit=100000`, le serveur pourrait retourner une réponse massive consommant mémoire et bande passante.
|
||||
|
||||
**Impact**
|
||||
Un attaquant peut envoyer des requêtes avec un `limit` très élevé pour surcharger le serveur (DoS). Impact amplifié si la requête joint plusieurs tables.
|
||||
|
||||
**Remédiation**
|
||||
Ajouter un cap sur le paramètre `limit` dans le middleware de pagination :
|
||||
```go
|
||||
const MaxPageSize = 100
|
||||
if limit > MaxPageSize {
|
||||
limit = MaxPageSize
|
||||
}
|
||||
```
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-005] : Production JWT utilise HS256 — stream token potentiellement forgeable si secret compromis
|
||||
|
||||
**Sévérité** : MOYENNE (sous-finding de HIGH-002)
|
||||
**CVSS v3.1** : 5.9 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N)
|
||||
**CWE** : CWE-327 — Use of a Broken or Risky Cryptographic Algorithm
|
||||
**OWASP** : A02:2021 — Cryptographic Failures
|
||||
**Fichier(s)** : `veza-backend-api/internal/services/jwt_service.go:253-277`
|
||||
**Composant** : Streaming — Stream Token
|
||||
|
||||
**Description**
|
||||
Le stream token (JWT court, 5 min TTL) pour l'authentification HLS utilise le même secret/clé que le JWT principal. Avec HS256 en production, le compromis du `JWT_SECRET` permet de forger des stream tokens pour accéder à n'importe quel contenu audio.
|
||||
|
||||
Le stream token utilise des claims différents (`iss: veza-platform`, `aud: veza-services`) mais la même clé de signature.
|
||||
|
||||
**Impact**
|
||||
Accès non autorisé au streaming audio de tous les contenus de la plateforme.
|
||||
|
||||
**Remédiation**
|
||||
Migrer vers RS256 (cf. HIGH-002). Alternativement, utiliser un secret séparé pour les stream tokens.
|
||||
|
||||
**Priorité de correction** : Sprint suivant (résolu par HIGH-002)
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-006] : CSP unsafe-inline et unsafe-eval pour les routes Swagger
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 4.7 (AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N)
|
||||
**CWE** : CWE-79 — Cross-site Scripting (XSS)
|
||||
**OWASP** : A05:2021 — Security Misconfiguration
|
||||
**Fichier(s)** : `veza-backend-api/internal/middleware/security_headers.go:78`
|
||||
**Composant** : Infrastructure — Security Headers
|
||||
|
||||
**Description**
|
||||
Les routes Swagger (`/swagger/`, `/docs/`) ont un CSP permissif avec `'unsafe-inline'` et `'unsafe-eval'` pour le JavaScript. Bien que nécessaire pour le fonctionnement de Swagger UI, cela crée un vecteur XSS si un attaquant peut injecter du contenu dans la documentation Swagger.
|
||||
|
||||
```go
|
||||
csp := "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; ..."
|
||||
```
|
||||
|
||||
**Impact**
|
||||
XSS limité aux pages Swagger. L'impact est réduit car Swagger devrait être désactivé en production (déjà le cas selon l'audit précédent).
|
||||
|
||||
**Remédiation**
|
||||
Vérifier que Swagger est effectivement désactivé en production. Ajouter un guard :
|
||||
```go
|
||||
if isProd {
|
||||
// Ne pas servir Swagger en production
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-007] : Actions CI/CD non pinnées par SHA
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 4.8 (AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N)
|
||||
**CWE** : CWE-829 — Inclusion of Functionality from Untrusted Control Sphere
|
||||
**OWASP** : A08:2021 — Software and Data Integrity Failures
|
||||
**Fichier(s)** : `.github/workflows/ci.yml` (et tous les workflows)
|
||||
**Composant** : CI/CD — GitHub Actions
|
||||
|
||||
**Description**
|
||||
Les actions GitHub sont référencées par tag mutable (`@v4`, `@v5`, `@stable`) au lieu de SHA de commit. Un tag peut être déplacé par le mainteneur de l'action, ou en cas de compromission du compte du mainteneur.
|
||||
|
||||
Exemples : `actions/checkout@v4`, `actions/setup-node@v4`, `actions/setup-go@v5`, `dtolnay/rust-toolchain@stable`
|
||||
|
||||
**Impact**
|
||||
Un attaquant compromettant un mainteneur d'action GitHub pourrait injecter du code malveillant dans le pipeline CI (exfiltration de secrets, backdoor dans les artifacts de build).
|
||||
|
||||
**Remédiation**
|
||||
Pinner chaque action par SHA :
|
||||
```yaml
|
||||
# Avant
|
||||
- uses: actions/checkout@v4
|
||||
# Après
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
```
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-008] : RabbitMQ Management UI exposé en dev
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
|
||||
**CWE** : CWE-668 — Exposure of Resource to Wrong Sphere
|
||||
**OWASP** : A05:2021 — Security Misconfiguration
|
||||
**Fichier(s)** : `docker-compose.yml:91`
|
||||
**Composant** : Infrastructure — RabbitMQ
|
||||
|
||||
**Description**
|
||||
Le Management UI de RabbitMQ (port 25672→15672) est exposé sur l'hôte en dev. En production (`docker-compose.prod.yml`), les ports ne sont pas exposés directement, mais le service utilise toujours l'image `rabbitmq:3-management-alpine` qui inclut le plugin management.
|
||||
|
||||
**Impact**
|
||||
En dev : accès au dashboard RabbitMQ exposant la topologie des queues et les messages. En prod : si un attaquant accède au réseau Docker, il peut accéder au management UI.
|
||||
|
||||
**Remédiation**
|
||||
- Prod : utiliser `rabbitmq:3-alpine` (sans management) ou désactiver le plugin management
|
||||
- Dev : OK si réseau local uniquement
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-009] : Free trial réutilisable indéfiniment
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 5.3 (AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N)
|
||||
**CWE** : CWE-863 — Incorrect Authorization
|
||||
**Fichier(s)** : `veza-backend-api/internal/core/subscription/service.go:236-240`
|
||||
**Composant** : Subscriptions
|
||||
|
||||
**Description**
|
||||
Le code accorde un essai gratuit si `plan.TrialDays > 0` sans vérifier si l'utilisateur a déjà bénéficié d'un essai. Un utilisateur peut cycler entre free et premium pour obtenir des essais illimités.
|
||||
|
||||
**Remédiation**
|
||||
Vérifier en DB si l'utilisateur a déjà eu un trial pour ce plan avant d'en accorder un nouveau.
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-010] : WebSocket — pas de re-validation token après connexion
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 5.4 (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N)
|
||||
**CWE** : CWE-613 — Insufficient Session Expiration
|
||||
**Fichier(s)** : `veza-backend-api/internal/handlers/chat_websocket_handler.go:32-66`
|
||||
**Composant** : Chat — WebSocket
|
||||
|
||||
**Description**
|
||||
Le token JWT est validé uniquement à l'upgrade WebSocket. Si le token est révoqué (logout, ban), l'utilisateur reste connecté et peut continuer à envoyer/recevoir des messages.
|
||||
|
||||
**Remédiation**
|
||||
Implémenter une re-validation périodique du token (toutes les 30 secondes) ou écouter les événements de révocation via Redis PubSub.
|
||||
|
||||
**Priorité de correction** : Sprint suivant
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-011] : Email logué en clair dans les tentatives de login
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 4.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
|
||||
**CWE** : CWE-532 — Insertion of Sensitive Information into Log File
|
||||
**Fichier(s)** : `veza-backend-api/internal/handlers/auth.go:52-56`
|
||||
**Composant** : Logging — Auth
|
||||
|
||||
**Description**
|
||||
L'email de l'utilisateur est logué en clair lors des tentatives de login. Le `SecretFilterCore` filtre les champs `password`, `token`, `secret` mais pas `email`.
|
||||
|
||||
**Remédiation**
|
||||
Ajouter `email` à la liste de redaction dans `secret_filter.go`, ou logger un hash de l'email.
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM-012] : Analytics créateur sans k-anonymité (minimum 10 utilisateurs)
|
||||
|
||||
**Sévérité** : MOYENNE
|
||||
**CVSS v3.1** : 4.3 (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N)
|
||||
**CWE** : CWE-359 — Exposure of Private Personal Information to an Unauthorized Actor
|
||||
**Fichier(s)** : `veza-backend-api/internal/handlers/playback_analytics_handler.go`
|
||||
**Composant** : Analytics — Privacy
|
||||
|
||||
**Description**
|
||||
Les analytics créateur affichent des statistiques détaillées (heatmap, écoutes, géographie) même si un track n'a que 1-2 écoutes. Cela pourrait permettre de ré-identifier des auditeurs par recoupement.
|
||||
|
||||
**Remédiation**
|
||||
Supprimer les analytics détaillées si `unique_listeners < 10`. Afficher uniquement des catégories agrégées.
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [LOW-001] : Incohérence politique mot de passe frontend/backend
|
||||
|
||||
**Sévérité** : BASSE
|
||||
**CVSS v3.1** : 3.7 (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N)
|
||||
**CWE** : CWE-521 — Weak Password Requirements
|
||||
**OWASP** : A07:2021 — Identification and Authentication Failures
|
||||
**ASVS** : V2.1.1
|
||||
**Fichier(s)** : `apps/web/src/lib/passwordValidator.ts` vs `veza-backend-api/internal/validators/password_validator.go`
|
||||
**Composant** : Authentication — Password Policy
|
||||
|
||||
**Description**
|
||||
L'audit précédent (VEZA-SEC-005) signalait que le frontend accepte 8 caractères alors que le backend exige 12. Si non corrigé, l'utilisateur reçoit une erreur serveur après avoir rempli le formulaire — mauvaise UX.
|
||||
|
||||
**Remédiation**
|
||||
Aligner le frontend sur 12 caractères minimum.
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [LOW-002] : Version Hyperswitch datée (~1 an)
|
||||
|
||||
**Sévérité** : BASSE
|
||||
**CVSS v3.1** : 3.1 (AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:N)
|
||||
**CWE** : CWE-1104 — Use of Unmaintained Third Party Components
|
||||
**OWASP** : A06:2021 — Vulnerable and Outdated Components
|
||||
**Fichier(s)** : `docker-compose.yml:137`, `docker-compose.prod.yml:117`
|
||||
**Composant** : Infrastructure — Payments
|
||||
|
||||
**Description**
|
||||
L'image Hyperswitch `juspaydotin/hyperswitch-router:2025.01.21.0-standalone` date de janvier 2025 (~14 mois). Des correctifs de sécurité et des améliorations de stabilité ont pu être publiés depuis.
|
||||
|
||||
**Remédiation**
|
||||
Mettre à jour vers la dernière version stable d'Hyperswitch après test en staging.
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [LOW-003] : Dépendance Rust `dotenv 0.15` obsolète
|
||||
|
||||
**Sévérité** : BASSE
|
||||
**CVSS v3.1** : 2.0 (AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:L/A:N)
|
||||
**CWE** : CWE-1104 — Use of Unmaintained Third Party Components
|
||||
**Fichier(s)** : `veza-stream-server/Cargo.toml`
|
||||
**Composant** : Stream Server — Dependencies
|
||||
|
||||
**Description**
|
||||
`dotenv 0.15` date de 2020 et n'est plus maintenu activement. Le fork `dotenvy` est le successeur recommandé.
|
||||
|
||||
**Remédiation**
|
||||
```toml
|
||||
# Cargo.toml
|
||||
dotenvy = "0.15" # Remplacer dotenv par dotenvy
|
||||
```
|
||||
Et mettre à jour les imports dans le code Rust.
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [LOW-004] : Elasticsearch sans authentification dans le réseau Docker
|
||||
|
||||
**Sévérité** : BASSE
|
||||
**CVSS v3.1** : 3.5 (AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
|
||||
**CWE** : CWE-306 — Missing Authentication for Critical Function
|
||||
**OWASP** : A07:2021 — Identification and Authentication Failures
|
||||
**Fichier(s)** : `docker-compose.yml` (Elasticsearch si présent), configuration ES
|
||||
**Composant** : Infrastructure — Elasticsearch
|
||||
|
||||
**Description**
|
||||
Elasticsearch n'a pas d'authentification configurée dans les docker-compose. Tout service du réseau Docker peut accéder aux index et requêter les données directement.
|
||||
|
||||
**Impact**
|
||||
Si un autre container est compromis, l'attaquant peut accéder aux index Elasticsearch contenant potentiellement des données utilisateur (tracks, descriptions, messages indexés).
|
||||
|
||||
**Remédiation**
|
||||
Activer l'authentification native Elasticsearch ou OpenSearch Security.
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [LOW-005] : context.Background() dans les background jobs
|
||||
|
||||
**Sévérité** : BASSE
|
||||
**CVSS v3.1** : 2.0
|
||||
**CWE** : CWE-404 — Improper Resource Shutdown or Release
|
||||
**Fichier(s)** : `veza-backend-api/internal/jobs/*.go`
|
||||
**Composant** : Background Jobs
|
||||
|
||||
**Description**
|
||||
Les jobs de nettoyage (cleanup_password_reset_tokens, cleanup_sessions, cleanup_hls_segments, cleanup_verification_tokens) utilisent `context.Background()`. Cela est acceptable pour des background jobs, mais ils devraient utiliser un context dérivé du shutdown manager pour permettre un arrêt gracieux.
|
||||
|
||||
**Remédiation**
|
||||
Passer un context annulable depuis le shutdown manager.
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [LOW-006] : Redis sans mot de passe en développement
|
||||
|
||||
**Sévérité** : BASSE
|
||||
**CVSS v3.1** : 2.0 (AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N)
|
||||
**CWE** : CWE-287 — Improper Authentication
|
||||
**Fichier(s)** : `docker-compose.yml:36-48`
|
||||
**Composant** : Infrastructure — Redis
|
||||
|
||||
**Description**
|
||||
Redis en dev n'a pas de `requirepass`. Acceptable en développement local, mais le docker-compose.yml de base ne devrait pas être utilisé tel quel en staging/pré-production.
|
||||
|
||||
**Impact**
|
||||
Limité au développement local. La config production (`docker-compose.prod.yml:34`) utilise bien `requirepass`.
|
||||
|
||||
**Remédiation**
|
||||
OK en l'état (déjà protégé en prod). Documenter que `docker-compose.yml` est strictement pour le dev local.
|
||||
|
||||
**Priorité de correction** : Backlog
|
||||
|
||||
---
|
||||
|
||||
### [INFO-001] : Bonnes pratiques de sécurité confirmées
|
||||
|
||||
**Sévérité** : INFO (Positif)
|
||||
|
||||
Les contrôles de sécurité suivants sont correctement implémentés :
|
||||
|
||||
1. **JWT Token Versioning** : Révocation immédiate via `token_version` vérifié contre la DB à chaque requête (`auth.go:218`)
|
||||
2. **Token Blacklist Redis** : Double vérification avec blacklist Redis quand disponible (`auth.go:189`)
|
||||
3. **Session Validation** : Triple vérification (JWT signature → token version → session DB) (`auth.go:175-260`)
|
||||
4. **Security Headers** : Ensemble complet (HSTS, CSP strict pour API, X-Frame-Options DENY, nosniff, Referrer-Policy, Permissions-Policy) (`security_headers.go`)
|
||||
5. **CORS Strict** : Production requiert whitelist explicite, panic si non configuré (`cors.go`)
|
||||
6. **CSRF Protection** : Middleware CSRF avec tokens Redis (`csrf.go`)
|
||||
7. **Rate Limiting Multi-couche** : Global, par IP, par endpoint, par user, upload-specific (`rate_limiter.go`, `ratelimit_redis.go`, `endpoint_limiter.go`)
|
||||
8. **ClamAV Obligatoire** : `CLAMAV_REQUIRED=true` en production (`docker-compose.prod.yml:170`)
|
||||
9. **Bcrypt Cost 12** : Conforme aux recommandations OWASP
|
||||
10. **Path Validation** : `ValidateExecPath()` sur tous les appels `exec.Command` (`utils/sanitizer.go`)
|
||||
11. **Secret Filtering** : Logs filtrés pour les secrets (`logging/secret_filter.go`)
|
||||
12. **Audit Trail** : Middleware d'audit sur POST/PUT/DELETE (`audit.go`)
|
||||
13. **RBAC Middleware** : Contrôle d'accès basé sur les rôles avec middleware dédié (`rbac_middleware.go`)
|
||||
14. **Ownership Checks** : `RequireOwnershipOrAdmin` sur les ressources utilisateur
|
||||
15. **Webhook HMAC-SHA512** : Vérification de signature avec comparaison constant-time
|
||||
16. **Non-root Docker** : Container backend tourne en user `app:1001`
|
||||
17. **Multi-stage Dockerfile** : Build optimisé sans outils de compilation dans l'image finale
|
||||
18. **GORM** : Pas de SQL injection — toutes les queries production utilisent des paramètres préparés
|
||||
19. **DOMPurify** : Sanitisation HTML côté frontend avec whitelist de tags
|
||||
20. **User Enumeration Protection** : Messages d'erreur génériques sur login (pas de "user not found" vs "wrong password")
|
||||
|
||||
---
|
||||
|
||||
### [INFO-002] : Vérification des correctifs de l'audit précédent
|
||||
|
||||
**Sévérité** : INFO
|
||||
|
||||
| ID précédent | Statut | Détail |
|
||||
|---|---|---|
|
||||
| VEZA-SEC-001 (JWT secret par défaut Rust) | ✅ CORRIGÉ | `config_rust.rs:234` — `secret: String::new()`, `load_from_env` exige `JWT_SECRET` |
|
||||
| VEZA-SEC-002 (Issuer/audience mismatch) | ✅ CORRIGÉ | `GenerateStreamToken` utilise `iss: veza-platform`, `aud: veza-services` |
|
||||
| VEZA-SEC-003 (Shutdown AppState Rust) | ⚠️ À VÉRIFIER | Non vérifié dans cet audit |
|
||||
| VEZA-SEC-004 (Webhook worker goroutine) | ⚠️ À VÉRIFIER | Non vérifié dans cet audit |
|
||||
| VEZA-SEC-005 (Password policy mismatch) | ❌ NON CORRIGÉ | Repris comme LOW-001 |
|
||||
| VEZA-SEC-006 (Métriques exposées) | ✅ CORRIGÉ | `metrics_protection.go` avec bearer token + IP whitelist |
|
||||
| VEZA-SEC-007 (context.Background password_reset) | ⚠️ À VÉRIFIER | Non vérifié dans cet audit |
|
||||
| VEZA-SEC-008 (Callback no-op seller) | ⚠️ À VÉRIFIER | Non vérifié dans cet audit |
|
||||
| VEZA-SEC-009 (dotenv obsolète) | ❌ NON CORRIGÉ | Repris comme LOW-003 |
|
||||
|
||||
---
|
||||
|
||||
### [INFO-003] : Recommandation — Ajouter un scan de dépendances dans le pipeline
|
||||
|
||||
**Sévérité** : INFO
|
||||
|
||||
Le CI inclut déjà `govulncheck`, `cargo audit`, et `npm audit`. Recommandation supplémentaire :
|
||||
- Ajouter `npm audit --production` (exclure les devDeps)
|
||||
- Ajouter un check de licence automatisé (ex: `license-checker` pour npm, `golicense` pour Go)
|
||||
- Configurer Dependabot pour les GitHub Actions elles-mêmes
|
||||
|
||||
---
|
||||
|
||||
### [INFO-004] : Recommandation — Tests de sécurité automatisés
|
||||
|
||||
**Sévérité** : INFO
|
||||
|
||||
Des tests de sécurité existent (`tests/security/`, `tests/integration/webhook_security_test.go`). Recommandation :
|
||||
- Ajouter des tests de race condition automatisés pour les scénarios marketplace (HIGH-001)
|
||||
- Ajouter un test DAST automatisé (OWASP ZAP) dans le pipeline CI/CD
|
||||
- Ajouter un test de politique CSP (vérifier que `unsafe-inline` n'apparaît pas sur les routes non-Swagger)
|
||||
|
||||
---
|
||||
|
||||
### [INFO-005] : Aucun `unsafe` dans le code Rust
|
||||
|
||||
**Sévérité** : INFO (Positif)
|
||||
|
||||
Aucun bloc `unsafe` trouvé dans le code applicatif du stream server Rust. Le code utilise exclusivement les abstractions safe de Rust, éliminant les risques de corruption mémoire.
|
||||
|
||||
---
|
||||
|
||||
## ANALYSE PAR CATÉGORIE OWASP TOP 10
|
||||
|
||||
| Catégorie | Verdict | Findings |
|
||||
|-----------|---------|----------|
|
||||
| A01 — Broken Access Control | ❌ FAIL | CRIT-001 (IDOR rooms), CRIT-002 (play_count public), HIGH-009 (co-listen), HIGH-010 (modération) |
|
||||
| A02 — Cryptographic Failures | ⚠️ PARTIEL | HIGH-002 (HS256 prod), MEDIUM-001 (math/rand 2FA) |
|
||||
| A03 — Injection | ✅ PASS | GORM, ValidateExecPath, DOMPurify |
|
||||
| A04 — Insecure Design | ⚠️ PARTIEL | HIGH-001 (race condition), HIGH-003 (context bypass) |
|
||||
| A05 — Security Misconfiguration | ⚠️ PARTIEL | MEDIUM-002, MEDIUM-006, MEDIUM-008 |
|
||||
| A06 — Vulnerable Components | ⚠️ PARTIEL | MEDIUM-003, LOW-002, LOW-003 |
|
||||
| A07 — Auth Failures | ✅ PASS | JWT validation solide, rate limiting, session management |
|
||||
| A08 — Software Integrity | ⚠️ PARTIEL | MEDIUM-007 (CI actions) |
|
||||
| A09 — Logging & Monitoring | ✅ PASS | Audit trail, secret filtering, structured logging |
|
||||
| A10 — SSRF | ✅ PASS | Pas de SSRF identifié |
|
||||
|
||||
## ANALYSE PAR CATÉGORIE OWASP API TOP 10
|
||||
|
||||
| Catégorie | Verdict | Findings |
|
||||
|-----------|---------|----------|
|
||||
| API1 — Broken Object Level Auth | ❌ FAIL | CRIT-001 (IDOR rooms — pas de membership check) |
|
||||
| API2 — Broken Authentication | ⚠️ PARTIEL | HIGH-002 (HS256), MEDIUM-001 (math/rand) |
|
||||
| API3 — Broken Object Property Auth | ✅ PASS | Validation struct tags, pas de mass assignment |
|
||||
| API4 — Unrestricted Resource Consumption | ⚠️ PARTIEL | MEDIUM-004 (pagination limit) |
|
||||
| API5 — Broken Function Level Auth | ✅ PASS | RequireAdmin, RequireRole middleware |
|
||||
| API6 — Unrestricted Business Flows | ⚠️ PARTIEL | HIGH-001 (race condition downloads) |
|
||||
| API7 — SSRF | ✅ PASS | Pas de SSRF identifié |
|
||||
| API8 — Security Misconfiguration | ⚠️ PARTIEL | MEDIUM-002, MEDIUM-006 |
|
||||
| API9 — Improper Inventory | ✅ PASS | API versionnée, pas de endpoints dépréciés exposés |
|
||||
| API10 — Unsafe API Consumption | ✅ PASS | Webhook signatures vérifiées |
|
||||
|
||||
---
|
||||
|
||||
## CRITÈRES GO/NO-GO v1.0.0
|
||||
|
||||
| Critère | Statut | Détail |
|
||||
|---------|--------|--------|
|
||||
| Aucun finding CRITIQUE non résolu | ❌ NO-GO | 2 findings CRITIQUES à corriger (IDOR rooms, play_count public) |
|
||||
| Aucun finding HAUT non résolu | ❌ NO-GO | 10 findings HAUTS à corriger |
|
||||
| Tous les MOYENS ont un plan de remédiation | ✅ GO | 12 findings avec remédiation documentée |
|
||||
| ASVS Level 2 sans FAIL obligatoire | ❌ NO-GO | 3 FAIL (IDOR, race condition, crypto/rand) |
|
||||
|
||||
**Décision : NO-GO — Corriger les 2 CRITIQUES + 10 HAUTS avant v1.0.0 (~30h d'effort total)**
|
||||
|
||||
---
|
||||
|
||||
*Rapport généré le 2026-03-11 par Claude Opus 4.6*
|
||||
*Méthodologie : OWASP Top 10 (2021), OWASP API Security Top 10 (2023), ASVS v4.0 Level 2*
|
||||
*Référence audit précédent : AUDIT_TECHNIQUE_VEZA_2026-03-04.md*
|
||||
35
README.md
35
README.md
|
|
@ -2,19 +2,17 @@
|
|||
|
||||
[](https://github.com/okinrev/veza/actions/workflows/ci.yml)
|
||||
|
||||
**Version courante** : v1.0.4 (cleanup + consolidation post-audit). Voir [CHANGELOG.md](CHANGELOG.md) et [docs/PROJECT_STATE.md](docs/PROJECT_STATE.md).
|
||||
**Version cible** : v0.101 (stabilisation en cours). Voir [docs/V0_101_RELEASE_SCOPE.md](docs/V0_101_RELEASE_SCOPE.md) pour le périmètre.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- **`apps/web`** — Frontend React 18 + Vite 5 + TypeScript strict (source of truth for the UI)
|
||||
- **`veza-backend-api`** — Main Go 1.25 API service (Gin, GORM, Postgres, Redis, RabbitMQ, Elasticsearch). Handles REST, WebSocket, and chat (chat server was merged into this service in v0.502).
|
||||
- **`veza-stream-server`** — Rust streaming server (Axum 0.8, Tokio 1.35, Symphonia) — HLS, HTTP Range, WebSocket, gRPC
|
||||
- **`veza-common`** — Shared Rust types and logging
|
||||
- **`packages/design-system`** — Shared design tokens
|
||||
- **`apps/web`**: The main frontend application (React + Vite). **This is the single source of truth for the UI.**
|
||||
- **`veza-desktop`**: A thin Electron wrapper that loads `apps/web`. It creates the native desktop experience.
|
||||
- **`veza-backend-api`**: Main Go API service.
|
||||
- **`veza-stream-server`**: Rust streaming server.
|
||||
- **`veza-chat-server`**: Rust chat server.
|
||||
|
||||
See [CLAUDE.md](CLAUDE.md) for the full architecture map.
|
||||
|
||||
## Development Setup
|
||||
## Development Setup (v0.9.3)
|
||||
|
||||
Prerequisites: Node 20 (see `.nvmrc`), Go, Rust, Docker. Configure `.env` from `.env.example`.
|
||||
|
||||
|
|
@ -41,14 +39,21 @@ See [docs/ENV_VARIABLES.md](docs/ENV_VARIABLES.md) for required variables. `make
|
|||
|
||||
## Quick Start
|
||||
|
||||
### Frontend only
|
||||
|
||||
### Frontend
|
||||
```bash
|
||||
cd apps/web
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Desktop (Optional)
|
||||
Requires `apps/web` to be running.
|
||||
```bash
|
||||
cd veza-desktop
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Docker Production
|
||||
|
||||
**Canonical production compose file**: `docker-compose.prod.yml`
|
||||
|
|
@ -57,16 +62,16 @@ npm run dev
|
|||
docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
**Deprecated** (use docker-compose.prod.yml):
|
||||
- `docker-compose.production.yml` — legacy, may be removed
|
||||
- `config/docker/docker-compose.production.yml` — legacy config
|
||||
|
||||
See `make/config.mk` for COMPOSE_PROD and deployment docs.
|
||||
|
||||
## CI/CD
|
||||
|
||||
- **Badge** : CI status above. Set `SLACK_WEBHOOK_URL` (Incoming Webhook) in repo secrets to receive Slack notifications on failure.
|
||||
|
||||
### Disabled workflows
|
||||
|
||||
- **Storybook** (`chromatic.yml.disabled`, `storybook-audit.yml.disabled`, `visual-regression.yml.disabled`): deferred until MSW is wired up for `/api/v1/auth/me` and `/api/v1/logs/frontend`, which currently causes ~1 400 network errors in the Storybook build. The npm scripts (`storybook`, `build-storybook`) still work locally for one-off component inspection. To reactivate in CI, fix the MSW handlers and rename the three files back to `.yml`.
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[Developer Onboarding](docs/ONBOARDING.md)** — Setup, architecture, conventions, troubleshooting
|
||||
|
|
|
|||
80
REMEDIATION_MATRIX_v0.12.6.md
Normal file
80
REMEDIATION_MATRIX_v0.12.6.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# MATRICE DE REMÉDIATION — VEZA v0.12.6
|
||||
|
||||
> **Date** : 2026-03-11
|
||||
> **Mise à jour** : 2026-03-12
|
||||
> **Référence** : PENTEST_REPORT_VEZA_v0.12.6.md
|
||||
|
||||
---
|
||||
|
||||
## Matrice de remédiation (triée par sévérité décroissante, puis CVSS)
|
||||
|
||||
| # | Finding | Sévérité | CVSS | Fichier(s) | Effort estimé | Priorité | Assignation suggérée | Statut |
|
||||
|---|---------|----------|------|------------|---------------|----------|---------------------|--------|
|
||||
| **CRIT-001** | **IDOR rooms — lecture conversations privées** | **CRITIQUE** | **9.1** | `room_handler.go:134-314` | 3h | **Immédiate** | Backend dev | ✅ CORRIGÉ |
|
||||
| **CRIT-002** | **play_count/like_count publics (violation éthique)** | **CRITIQUE** | **5.3** | `models/track.go:39-40` | 4h | **Immédiate** | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-001 | Race condition TOCTOU downloads marketplace | HAUTE | 7.5 | `marketplace/service.go:794-817` | 2h | Immédiate | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-002 | Production HS256 au lieu de RS256 | HAUTE | 7.4 | `docker-compose.prod.yml:158`, `jwt_service.go` | 4h | Immédiate | DevOps + Backend | ✅ CORRIGÉ |
|
||||
| HIGH-003 | User repository context.Background() bypass | HAUTE | 5.3 | `user_repository.go:125-150` | 4h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-004 | Race condition codes promo | HAUTE | 7.5 | `marketplace/service.go:463,753` | 2h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-005 | Race condition licence exclusive | HAUTE | 7.5 | `marketplace/service.go:393-532` | 2h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-006 | Rate limiter bypass (TrustedProxies) | HAUTE | 7.5 | `rate_limiter.go:131` | 30min | Immédiate | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-007 | RGPD hard delete incomplet | HAUTE | 6.5 | `hard_delete_worker.go:101` | 4h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-008 | RTMP callback auth faible | HAUTE | 7.3 | `live_stream_callback.go:25-36` | 1h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-009 | Co-écoute host hijack | HAUTE | 6.5 | `colistening/hub.go:102` | 1h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| HIGH-010 | Modérateur self-strike | HAUTE | 6.5 | `moderation_service.go:725` | 1h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| MEDIUM-001 | Recovery codes 2FA avec math/rand | MOYENNE | 5.9 | `two_factor_service.go:200` | 30min | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| MEDIUM-002 | Metrics IP spoofing via X-Forwarded-For | MOYENNE | 5.3 | `metrics_protection.go:52-54` | 15min | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| MEDIUM-004 | Pagination sans limite maximale | MOYENNE | 5.3 | Pagination middleware + handlers | 2h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| MEDIUM-005 | Stream token forgeable (HS256 prod) | MOYENNE | 5.9 | `jwt_service.go:253-277` | — | Sprint suivant | Résolu par HIGH-002 | ✅ CORRIGÉ |
|
||||
| MEDIUM-003 | ClamAV image Docker :latest | MOYENNE | 4.8 | `docker-compose*.yml` | 15min | Sprint suivant | DevOps | ✅ CORRIGÉ |
|
||||
| MEDIUM-007 | CI actions non pinnées par SHA | MOYENNE | 4.8 | `.github/workflows/*.yml` | 1h | Sprint suivant | DevOps | ✅ CORRIGÉ |
|
||||
| MEDIUM-006 | CSP unsafe-inline Swagger routes | MOYENNE | 4.7 | `security_headers.go:78` | 30min | Backlog | Backend dev | ✅ CORRIGÉ |
|
||||
| MEDIUM-008 | RabbitMQ Management UI en prod | MOYENNE | 5.3 | `docker-compose.prod.yml` | 15min | Backlog | DevOps | ✅ CORRIGÉ |
|
||||
| MEDIUM-009 | Free trial réutilisable indéfiniment | MOYENNE | 5.3 | `subscription/service.go:236` | 1h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| MEDIUM-010 | WebSocket pas de re-validation token | MOYENNE | 5.4 | `chat_websocket_handler.go:32` | 2h | Sprint suivant | Backend dev | ✅ CORRIGÉ |
|
||||
| MEDIUM-011 | Email logué en clair | MOYENNE | 4.3 | `handlers/auth.go:52` | 30min | Backlog | Backend dev | ✅ CORRIGÉ |
|
||||
| MEDIUM-012 | Analytics sans k-anonymité | MOYENNE | 4.3 | `playback_analytics_handler.go` | 1h30 | Backlog | Backend dev | ✅ CORRIGÉ |
|
||||
| LOW-001 | Password policy mismatch FE/BE | BASSE | 3.7 | `passwordValidator.ts` | 30min | Backlog | Frontend dev | ✅ CORRIGÉ |
|
||||
| LOW-002 | Hyperswitch version datée | BASSE | 3.1 | `docker-compose*.yml` | 2h (+ tests) | Backlog | DevOps | ✅ CORRIGÉ |
|
||||
| LOW-003 | dotenv 0.15 obsolète (Rust) | BASSE | 2.0 | `Cargo.toml` | 30min | Backlog | Backend dev | ✅ CORRIGÉ |
|
||||
| LOW-004 | Elasticsearch sans auth | BASSE | 3.5 | Docker config | 2h | Backlog | DevOps | ✅ CORRIGÉ |
|
||||
| LOW-005 | context.Background() dans jobs | BASSE | 2.0 | `jobs/*.go` | 1h | Backlog | Backend dev | ✅ CORRIGÉ |
|
||||
| LOW-006 | Redis sans password en dev | BASSE | 2.0 | `docker-compose.yml` | — | Backlog | Accepté (dev only) | ✅ ACCEPTÉ |
|
||||
|
||||
---
|
||||
|
||||
## Résumé des efforts
|
||||
|
||||
| Priorité | Nombre de findings | Corrigés | Restant |
|
||||
|----------|--------------------|----------|---------|
|
||||
| Immédiate (bloquant v1.0.0) | 5 | 5 | 0 |
|
||||
| Sprint suivant | 15 | 15 | 0 |
|
||||
| Backlog | 10 | 10 | 0 |
|
||||
| **Total** | **30** | **30** | **0** |
|
||||
|
||||
---
|
||||
|
||||
## Plan de remédiation recommandé
|
||||
|
||||
### Phase 1 — Immédiate (bloquant — avant merge en main) ✅ COMPLÈTE
|
||||
1. **CRIT-001** : ✅ Vérification membership sur `GetRoom` et `GetRoomHistory`
|
||||
2. **CRIT-002** : ✅ `play_count`/`like_count` supprimés des réponses API publiques
|
||||
3. **HIGH-002** : ✅ Production RS256 (paire RSA 2048-bit)
|
||||
4. **HIGH-006** : ✅ `engine.SetTrustedProxies(nil)` configuré
|
||||
5. **HIGH-001** : ✅ Transaction + `SELECT FOR UPDATE` sur `GetDownloadURL`
|
||||
|
||||
### Phase 2 — Sprint suivant ✅ COMPLÈTE
|
||||
6. **HIGH-004/005** : ✅ Race conditions codes promo et licences protégées
|
||||
7. **HIGH-007** : ✅ Hard delete RGPD complété (sessions, settings, follows, notifications, audit_logs)
|
||||
8. **HIGH-008** : ✅ RTMP callback auth fail-closed, header-only, constant-time
|
||||
9. **HIGH-009/010** : ✅ Co-écoute host vérification + self-strike bloqué
|
||||
10. **MEDIUM-001** : ✅ crypto/rand pour recovery codes 2FA
|
||||
11. **MEDIUM-009/010** : ✅ Free trial reuse check + WebSocket re-validation 60s
|
||||
12-15. ✅ Pagination caps, metrics IP, CI SHA pinning, CSP hardening
|
||||
|
||||
### Phase 3 — Backlog ✅ COMPLÈTE
|
||||
16-30. ✅ Email masking, k-anonymité analytics, password policy FE/BE, dotenv→dotenvy, ES auth, ClamAV pinned, RabbitMQ mgmt UI removed, Hyperswitch 2025.01→2026.03
|
||||
|
||||
---
|
||||
|
||||
*Matrice mise à jour le 2026-03-12 — Réf. PENTEST_REPORT_VEZA_v0.12.6.md*
|
||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.0.8
|
||||
1.0.2
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
[
|
||||
{
|
||||
"path": "dist/assets/index-*.js",
|
||||
"limit": "300 KB",
|
||||
"gzip": true
|
||||
},
|
||||
{
|
||||
"path": "dist/assets/*.css",
|
||||
"limit": "80 KB",
|
||||
"gzip": true
|
||||
}
|
||||
]
|
||||
|
|
@ -29,8 +29,7 @@ const queryClient = new QueryClient({
|
|||
});
|
||||
|
||||
export const StorybookDecorator: Decorator = (Story, context) => {
|
||||
const bgValue = context.globals?.backgrounds?.value;
|
||||
const isDark = bgValue !== 'light'; // only 'light' triggers light mode, everything else = dark
|
||||
const isDark = context.globals?.backgrounds?.value !== '#ffffff';
|
||||
const initialEntries =
|
||||
(context.parameters?.router as { initialEntries?: string[] } | undefined)?.initialEntries ?? ['/'];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
// This file has been automatically migrated to valid ESM format by Storybook.
|
||||
import { createRequire } from "node:module";
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
|
||||
import { dirname, join } from "path"
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
function getAbsolutePath(value: string) {
|
||||
return dirname(require.resolve(join(value, "package.json")))
|
||||
}
|
||||
|
|
@ -16,14 +12,16 @@ const config: StorybookConfig = {
|
|||
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
|
||||
],
|
||||
"addons": [
|
||||
getAbsolutePath('@storybook/addon-essentials'),
|
||||
getAbsolutePath('@storybook/addon-a11y'),
|
||||
getAbsolutePath("@storybook/addon-docs"),
|
||||
getAbsolutePath("@storybook/addon-mcp"),
|
||||
getAbsolutePath('@storybook/addon-interactions'),
|
||||
getAbsolutePath('msw-storybook-addon'),
|
||||
],
|
||||
"staticDirs": ['../public'],
|
||||
"framework": getAbsolutePath('@storybook/react-vite'),
|
||||
"docs": {
|
||||
defaultName: "Documentation"
|
||||
"defaultName": "Documentation",
|
||||
"autodocs": true
|
||||
},
|
||||
"typescript": {
|
||||
"reactDocgen": "react-docgen-typescript",
|
||||
|
|
|
|||
|
|
@ -56,33 +56,27 @@ const preview: Preview = {
|
|||
expanded: true,
|
||||
},
|
||||
a11y: {
|
||||
test: 'error-on-violation',
|
||||
test: 'todo',
|
||||
},
|
||||
viewport: {
|
||||
options: customViewports,
|
||||
viewports: customViewports,
|
||||
},
|
||||
backgrounds: {
|
||||
options: {
|
||||
dark: { name: 'dark', value: '#121215' },
|
||||
light: { name: 'light', value: '#faf9f6' },
|
||||
raised: { name: 'raised', value: '#1a1a1f' }
|
||||
}
|
||||
default: 'dark',
|
||||
values: [
|
||||
{ name: 'dark', value: '#121215' },
|
||||
{ name: 'light', value: '#faf9f6' },
|
||||
{ name: 'raised', value: '#1a1a1f' },
|
||||
],
|
||||
},
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
toc: true, // Enable table of contents in docs
|
||||
},
|
||||
},
|
||||
|
||||
decorators: [StorybookDecorator],
|
||||
tags: ['autodocs'],
|
||||
loaders: [mswLoader],
|
||||
|
||||
initialGlobals: {
|
||||
backgrounds: {
|
||||
value: 'dark'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue