mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 13:59:40 +00:00
feat(secubox-toolbox): 3 QR code endpoints + splash QR display + 9th dashboard widget (device fingerprint) + README poster integration (ref #495 #497)
## QR endpoints (python-qrcode)
GET /qr/splash.png → QR encodes the splash URL (Cabine join)
GET /qr/cert.png → QR encodes /ca/mobileconfig (iOS profile)
GET /qr/webclip.png → QR encodes /ca/webclip-cabine.mobileconfig
GET /qr/{target}.png → generic : maps splash/cert/webclip/report/
fingerprint to fixed URLs, or encodes literal
PNG output, Cache-Control: public, max-age=3600.
Verified : 232x232 1-bit grayscale, 384-553 bytes each.
debian/control adds python3-qrcode dependency.
## Splash QR card
The cert install card on the splash now shows 3 QR codes (① SPLASH,
② CERT iPHONE, ③ WEBCLIP HOME) on a white background grid below the
test button. Scannable from another device for cross-installation
(useful at the cabine : user shows QR to others without typing URLs).
## Dashboard hero : 9th widget (device fingerprint)
Mirroring the poster layout, added a full-width 9th widget below the
2 rows of 4 KPI widgets :
┌─────────────────────────────────────────────────────────┐
│ 📱 📱 Empreinte device │
│ iPhone iOS 26.3 · 🟢 Chrome 148 │
│ 21 UAs distincts observés │
└─────────────────────────────────────────────────────────┘
Now matches the 9-widget metrics layout from the public poster :
🌐 connexions · 📡 hôtes · ✅ OK 2xx · 🔒 cert-pin
📺 apps · 🍪 trackers · 🌍 pays · 🎯 score
📱 device (full-width)
## README + assets/poster integration (#497)
- README.md : new section at top with poster image embed + 3 doc links
- docs/assets/poster/README.md : print specs + variants list + license
- Placeholder docs/assets/poster/village3b-A2.png expected (user provides)
Wiki integration pending : will be added via gh wiki API once issue #497
phase 2 launches.
## Rebase
Branch was rebased onto master (which now has all PR #493 Phase 3
commits). control merged : Phase 3 fonts + Phase 5 lxc deps + new
python3-qrcode in one cohesive Depends block.
This commit is contained in:
parent
448aee77e9
commit
3d2ecd574f
19
README.md
19
README.md
|
|
@ -14,6 +14,25 @@
|
|||
|
||||
---
|
||||
|
||||
## 📡 VILLAGE3B — Cabine Numérique Gondwana ToolBoX
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/poster/village3b-A2.png" alt="Poster grand public VILLAGE3B" width="600">
|
||||
</p>
|
||||
|
||||
> **Diagnostic compromission iPhone gratuit · Anonyme · Open Source**
|
||||
>
|
||||
> 3 niveaux d'opt-in (R0 bypass complet, R1 analyse passive ✓ recommandée, R2 TLS-break + bandeau).
|
||||
> Rapport téléchargeable avec 9 widgets metrics : 🌐 connexions · 📡 hôtes · ✅ OK 2xx · 🔒 cert-pinning · 📺 apps · 🍪 trackers · 🌍 pays · 🎯 score · 📱 device.
|
||||
> Conformité CSPN ANSSI + LCEN. Aucune donnée externe.
|
||||
>
|
||||
> - Détails techniques : [docs/AI-HANDOVER-cabine-evolution.md](docs/AI-HANDOVER-cabine-evolution.md)
|
||||
> - Brief poster + variantes : [docs/marketing/POSTER-grand-public-village3b.md](docs/marketing/POSTER-grand-public-village3b.md)
|
||||
> - Press kit + candidatures France.gouv : [docs/marketing/PROMPT-claude-presse-gouv.md](docs/marketing/PROMPT-claude-presse-gouv.md)
|
||||
> - Issue tracking poster : [#497](https://github.com/CyberMind-FR/secubox-deb/issues/497)
|
||||
|
||||
---
|
||||
|
||||
## License — CyberMind Source-Disclosed (CMSD-1.0)
|
||||
|
||||
> **Source disclosed, rights reserved.**
|
||||
|
|
|
|||
55
docs/assets/poster/README.md
Normal file
55
docs/assets/poster/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Posters VILLAGE3B — assets
|
||||
|
||||
Référence pour le poster public grand format basé sur le brief
|
||||
`docs/marketing/POSTER-grand-public-village3b.md`.
|
||||
|
||||
## Fichiers attendus dans ce dossier
|
||||
|
||||
- `village3b-A2.png` — poster A2 portrait haute résolution (300dpi, ~5MB)
|
||||
- `village3b-A2.svg` — source vectoriel Inkscape éditable
|
||||
- `village3b-A4.pdf` — variante A4 print-ready
|
||||
- `village3b-social-1080x1080.png` — variante Instagram/LinkedIn
|
||||
- `village3b-story-1080x1920.png` — variante Story Instagram/Snapchat
|
||||
|
||||
## Brief layout (6 zones)
|
||||
|
||||
Voir `docs/marketing/POSTER-grand-public-village3b.md` pour le détail des
|
||||
6 zones (titre, niveaux R0/R1/R2, 9 widgets metrics, transparence,
|
||||
conformité, QR codes, footer).
|
||||
|
||||
## QR codes embedded
|
||||
|
||||
Le poster contient 3 QR codes pointant vers les endpoints FastAPI :
|
||||
- ① SPLASH → `http://village3b/`
|
||||
- ② CERT iPHONE → `http://village3b/ca/mobileconfig`
|
||||
- ③ WEBCLIP HOME → `http://village3b/ca/webclip-cabine.mobileconfig`
|
||||
|
||||
Les URLs sont auto-générées par les routes `/qr/{splash,cert,webclip}.png`
|
||||
de l'API (Phase 3.x #497 + #495).
|
||||
|
||||
## Charte couleurs
|
||||
|
||||
```
|
||||
--cosmos-black: #0a0a0f /* fond */
|
||||
--gold-hermetic: #c9a84c /* accents titre */
|
||||
--matrix-green: #00ff41 /* texte principal */
|
||||
--phos: #00dd44 /* bordures P31 */
|
||||
--phos-hot: #00ff55 /* highlights */
|
||||
--amber: #ffb347 /* warning R2 */
|
||||
--cinnabar: #e63946 /* alertes risque HIGH */
|
||||
```
|
||||
|
||||
## Print specs
|
||||
|
||||
- A2 portrait, 420×594 mm, marges 15mm, 3mm bleed
|
||||
- 300dpi minimum
|
||||
- CMYK pour print, RGB pour digital
|
||||
- Papier mat 200g/m² recommandé
|
||||
|
||||
## Licence
|
||||
|
||||
LicenseRef-CMSD-1.0 — image dérivée du logiciel cabine ToolBox.
|
||||
|
||||
## Issue tracking
|
||||
|
||||
[#497 — Poster grand public VILLAGE3B](https://github.com/CyberMind-FR/secubox-deb/issues/497)
|
||||
|
|
@ -98,6 +98,24 @@ li::before{content:"▸ ";color:var(--phos);text-shadow:0 0 4px var(--phos)}
|
|||
<div style="font-size:0.65rem;color:var(--dim)">risque /100</div>
|
||||
</div>
|
||||
</div>
|
||||
{# 9th widget (poster sync #497) : 📱 empreinte device — full-width below #}
|
||||
{% set _avatar = avatar_analysis or {} %}
|
||||
{% set _dev_emj = _avatar.most_common_emoji or '❔' %}
|
||||
{% set _dev_label = _avatar.most_common or 'unknown' %}
|
||||
{% set _devices = _avatar.devices or {} %}
|
||||
{% set _dev_info = _devices.get(_dev_label, {}) %}
|
||||
{% set _os_label = _dev_info.os_label or _dev_label %}
|
||||
{% set _browsers = _avatar.browsers or {} %}
|
||||
<div style="margin-top:0.5rem;padding:0.7rem;background:rgba(45,25,60,0.5);border-radius:4px;text-align:center">
|
||||
<div style="font-size:1.4rem">📱 <b style="color:#cbb6ff">{{ _dev_emj }} Empreinte device</b></div>
|
||||
<div style="font-size:0.92rem;color:var(--phos-hot);margin-top:0.3rem;text-shadow:0 0 3px var(--phos)">
|
||||
{{ _os_label }}
|
||||
{% for br, info in _browsers.items() if br != 'unknown' %}
|
||||
· {{ info.emoji or '' }} {{ info.label or br }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div style="font-size:0.65rem;color:var(--dim);margin-top:0.2rem">{{ _avatar.raw_count or 0 }} UAs distincts observés</div>
|
||||
</div>
|
||||
{# Top device + app + ASN line #}
|
||||
{% set avatar = avatar_analysis or {} %}
|
||||
{% set top_dev = avatar.most_common or '?' %}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,25 @@ button.lvl-r2{border-color:var(--amber);color:#ffd6a0}
|
|||
<span id="cert-text">Auto-test en cours...</span>
|
||||
</div>
|
||||
|
||||
{# Phase 3.x (#497) : 3 QR codes définitifs synchronisés avec le poster #}
|
||||
<p style="font-size:0.78rem;color:#cbb6ff;margin-top:0.8rem;text-align:center;border-top:1px solid rgba(158,118,255,0.3);padding-top:0.5rem">
|
||||
📲 <b>QR codes</b> — scanne depuis un autre appareil
|
||||
</p>
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:0.5rem;margin-top:0.4rem">
|
||||
<div style="text-align:center;background:rgba(255,255,255,0.95);padding:0.4rem;border-radius:4px">
|
||||
<img src="/qr/splash.png" alt="QR splash" style="width:100%;max-width:120px;display:block;margin:auto"/>
|
||||
<p style="font-size:0.62rem;color:#0a0a0f;margin-top:0.2rem;font-weight:bold">① SPLASH</p>
|
||||
</div>
|
||||
<div style="text-align:center;background:rgba(255,255,255,0.95);padding:0.4rem;border-radius:4px">
|
||||
<img src="/qr/cert.png" alt="QR cert" style="width:100%;max-width:120px;display:block;margin:auto"/>
|
||||
<p style="font-size:0.62rem;color:#0a0a0f;margin-top:0.2rem;font-weight:bold">② CERT iPHONE</p>
|
||||
</div>
|
||||
<div style="text-align:center;background:rgba(255,255,255,0.95);padding:0.4rem;border-radius:4px">
|
||||
<img src="/qr/webclip.png" alt="QR webclip" style="width:100%;max-width:120px;display:block;margin:auto"/>
|
||||
<p style="font-size:0.62rem;color:#0a0a0f;margin-top:0.2rem;font-weight:bold">③ WEBCLIP HOME</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style="font-size:0.7rem;opacity:0.85;margin-top:0.6rem;line-height:1.5">
|
||||
⚠ L'auto-test est <b>best-effort</b> (probe HTTPS). Pour <b>certifier</b>
|
||||
que le CA est bien installé + activé sur iPhone :
|
||||
|
|
|
|||
|
|
@ -299,6 +299,76 @@ async def ca_fingerprint() -> dict:
|
|||
return {"sha1": sha1, "sha256": sha256, "subject": subject}
|
||||
|
||||
|
||||
# Phase 3.x (#497) : 3 QR code endpoints — splash, cert install, webclip
|
||||
# Used by the splash page + reports + the public poster (POSTER-grand-public).
|
||||
|
||||
def _qr_png(payload: str, size: int = 8, border: int = 2) -> bytes:
|
||||
"""Generate a PNG QR code for the given payload. Returns bytes."""
|
||||
import qrcode
|
||||
qr = qrcode.QRCode(
|
||||
version=None,
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_M,
|
||||
box_size=size,
|
||||
border=border,
|
||||
)
|
||||
qr.add_data(payload)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
import io
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format="PNG")
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def _portal_url(request: "Request", path: str = "/") -> str:
|
||||
"""Build absolute URL based on request Host header — works regardless of
|
||||
whether the portal is reached via 10.99.0.1, village3b, or external."""
|
||||
host = request.headers.get("host", "10.99.0.1:8088")
|
||||
scheme = "http" # captive portal is HTTP-only by design
|
||||
return f"{scheme}://{host.rstrip('/')}{path}"
|
||||
|
||||
|
||||
@router.get("/qr/splash.png")
|
||||
async def qr_splash(request: Request) -> Response:
|
||||
"""QR encodes the splash URL — scannable from another device to join VILLAGE3B."""
|
||||
return Response(content=_qr_png(_portal_url(request, "/")),
|
||||
media_type="image/png",
|
||||
headers={"Cache-Control": "public, max-age=3600"})
|
||||
|
||||
|
||||
@router.get("/qr/cert.png")
|
||||
async def qr_cert(request: Request) -> Response:
|
||||
"""QR encodes the CA install URL (.mobileconfig)."""
|
||||
return Response(content=_qr_png(_portal_url(request, "/ca/mobileconfig")),
|
||||
media_type="image/png",
|
||||
headers={"Cache-Control": "public, max-age=3600"})
|
||||
|
||||
|
||||
@router.get("/qr/webclip.png")
|
||||
async def qr_webclip(request: Request) -> Response:
|
||||
"""QR encodes the webclip URL (Add-to-Home-Screen profile)."""
|
||||
return Response(content=_qr_png(_portal_url(request, "/ca/webclip-cabine.mobileconfig")),
|
||||
media_type="image/png",
|
||||
headers={"Cache-Control": "public, max-age=3600"})
|
||||
|
||||
|
||||
@router.get("/qr/{target}")
|
||||
async def qr_generic(target: str, request: Request) -> Response:
|
||||
"""Generic fallback : ?target=splash|cert|webclip|fingerprint maps to fixed
|
||||
URLs ; anything else encodes the literal target string."""
|
||||
targets = {
|
||||
"splash": _portal_url(request, "/"),
|
||||
"cert": _portal_url(request, "/ca/mobileconfig"),
|
||||
"webclip": _portal_url(request, "/ca/webclip-cabine.mobileconfig"),
|
||||
"report": _portal_url(request, "/report/me/html"),
|
||||
"fingerprint": _portal_url(request, "/ca/fingerprint"),
|
||||
}
|
||||
payload = targets.get(target.removesuffix(".png"), target)
|
||||
return Response(content=_qr_png(payload),
|
||||
media_type="image/png",
|
||||
headers={"Cache-Control": "public, max-age=3600"})
|
||||
|
||||
|
||||
@router.get("/client-status")
|
||||
async def client_status(request: Request) -> dict:
|
||||
"""Health-banner-friendly endpoint : detect captive subnet + R2 consent +
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user