diff --git a/.github/assets/README.md b/.github/assets/README.md new file mode 100644 index 0000000..37def83 --- /dev/null +++ b/.github/assets/README.md @@ -0,0 +1,33 @@ +# Passli Visual Identity & Repository Assets + +This directory contains the visual branding, architecture infographics, UI previews, and animated demonstration assets for the **Passli** open-source repository. + +--- + +## 🎨 Asset Catalog + +| Asset | File | Type | Resolution | Purpose | +| :--- | :--- | :---: | :---: | :--- | +| **Hero Banner** | [`passli-repo-banner.png`](passli-repo-banner.png) | Static Image (PNG) | 1376 Γ— 768 (16:9) | Primary README header banner, social card, and project cover | +| **Ephemeral Flow Demo** | [`passli-ephemeral-workflow.gif`](passli-ephemeral-workflow.gif) | Animated GIF | 1100 Γ— 580 (58 frames) | Interactive demonstration of pass creation, 8-char key hashing, QR protocol, live countdown, and 1-click revocation | +| **Vault Dashboard Preview** | [`passli-dashboard-preview.png`](passli-dashboard-preview.png) | Static Image (PNG) | 1376 Γ— 768 (16:9) | High-fidelity UI mockup of the authenticated personal vault dashboard, telemetry, and document cards | +| **Cryptographic Pipeline** | [`passli-cryptographic-pipeline.png`](passli-cryptographic-pipeline.png) | Static Image (PNG) | 1376 Γ— 768 (16:9) | Architecture diagram depicting AES-256-GCM AEAD ingestion and in-memory streaming decryption | + +--- + +## πŸ› οΈ Regeneration & Build Tools + +The animated workflow demonstration (`passli-ephemeral-workflow.gif`) can be deterministically generated and optimized using Pillow: + +```bash +python scripts/generate_ephemeral_flow_gif.py +``` + +### Design Standards & Palette +Assets adhere strictly to Passli's **anti-ai-slop** and **unslop-ui v2** engineering standards: +- **Background**: `#0B0F17` (Deep Obsidian) / `#111827` (Dark Slate) +- **Primary Accent**: `#10B981` (Emerald Green) +- **Secondary Accent**: `#06B6D4` (Electric Cyan) +- **Danger / Revoke**: `#EF4444` (Ruby Red) +- **Text Hierarchy**: `#F8FAFC` (Heading White), `#94A3B8` (Muted Slate), `#64748B` (Dim Slate) +- **Typography**: Clean humanist sans-serif and monospace technical displays diff --git a/.github/assets/passli-cryptographic-pipeline.png b/.github/assets/passli-cryptographic-pipeline.png new file mode 100644 index 0000000..95ad74b Binary files /dev/null and b/.github/assets/passli-cryptographic-pipeline.png differ diff --git a/.github/assets/passli-dashboard-preview.png b/.github/assets/passli-dashboard-preview.png new file mode 100644 index 0000000..b6cdff1 Binary files /dev/null and b/.github/assets/passli-dashboard-preview.png differ diff --git a/.github/assets/passli-ephemeral-workflow.gif b/.github/assets/passli-ephemeral-workflow.gif new file mode 100644 index 0000000..e5c1fe1 Binary files /dev/null and b/.github/assets/passli-ephemeral-workflow.gif differ diff --git a/.github/assets/passli-repo-banner.png b/.github/assets/passli-repo-banner.png new file mode 100644 index 0000000..bb91245 Binary files /dev/null and b/.github/assets/passli-repo-banner.png differ diff --git a/PROGRESS.md b/PROGRESS.md new file mode 100644 index 0000000..b5cf12c --- /dev/null +++ b/PROGRESS.md @@ -0,0 +1,132 @@ +# Passli Development Progress & Roadmap + +> **Current Milestone**: Full Project (Phases 1-7 & Security Audit Suite) 100% Completed & Verified (93/93 Automated Tests Passing) +> **Repository**: [github.com/Ali-Nawaz-devt/passli](https://github.com/Ali-Nawaz-devt/passli) +> **Status**: Production Ready & Fully Verified + +--- + +## πŸ“Š Phase-by-Phase Roadmap + +| Phase | Description | Status | Test Coverage | +| :--- | :--- | :---: | :---: | +| **Phase 1** | Project Setup, Anti-Slop Design System, Split-Hero & Landing Page | :white_check_mark: Completed | 100% (11/11 Tests) | +| **Phase 2** | User Authentication, Registration & Personal Vault Dashboard | :white_check_mark: Completed | 100% (11/11 Tests) | +| **Phase 3** | Encrypted Document Vault Management & Categorization | :white_check_mark: Completed | 100% (17/17 Tests) | +| **Security Audit** | OWASP Top 10, IDOR Prevention, Upload Defense & Checksum Integrity | :white_check_mark: Completed | 100% (10/10 Tests) | +| **Phase 4** | Controlled Share Pass Generation & Ephemeral QR Protocol | :white_check_mark: Completed | 100% (16/16 Tests) | +| **Phase 5** | Recipient Verification Portal & Two-Factor Access Flow | :white_check_mark: Completed | 100% (14/14 Tests) | +| **Phase 6** | Audit Trail, Immediate Revocation & Rate Limiting | :white_check_mark: Completed | 100% (13/13 Tests) | +| **Phase 7** | End-to-End User Lifecycle Suite, Production Verification & Audit Report | :white_check_mark: Completed | 100% (1/1 E2E Suite) | + +--- + +## 🎯 Phase Details & Deliverables + + +### βœ… Phase 1: Foundation, Anti-Slop Design System & Landing Page +- [x] Django 5 project initialized with modular app architecture (`accounts`, `documents`, `sharing`, `audit`, `config`). +- [x] Vanilla CSS Design System with custom properties (`static/css/styles.css`). +- [x] Human-crafted anti-AI-slop UI standards enforced (referencing `LeoStehlik/no-slop-ui` and `unslop-ui v2`). +- [x] Clean Split-Hero layout with authentic high-resolution security photography. +- [x] Interactive client-side Share Pass Generator simulator. +- [x] Real-world scenario showcase (Clinic visits, Job KYC verification, Vehicle registration). +- [x] Institutional Security Comparison Matrix (Passli vs Cloud Folders vs Email). +- [x] Responsive navigation bar with verified on-page anchor targets. +- [x] CodeRabbit review and rewrite protocol integrated into `AGENTS.md`. +- [x] 11 automated unit tests created and verified in `tests/unit/test_phase1.py`. + +--- + +### βœ… Phase 2: User Authentication & Personal Vault Dashboard +- [x] Custom `RegisterForm` with email uniqueness validation and input styling. +- [x] Custom `LoginForm` with credential sanitization and session handling. +- [x] `register_view` with automatic session login and welcome messaging. +- [x] `CustomLoginView` and `CustomLogoutView` with redirect targets. +- [x] Protected `@login_required` `dashboard_view` calculating live vault metrics. +- [x] Semantic HTML5 templates: `templates/accounts/login.html`, `register.html`, and `dashboard.html`. +- [x] Vault statistics grid, storage usage tracker, and empty-state placeholders. +- [x] 11 automated unit tests created and verified in `tests/unit/test_phase2.py`. + +--- + +### βœ… Phase 3: Document Vault Management & Categorization +- [x] `Document` model with user-isolated storage paths (`vault_files/user_/.`). +- [x] Automatic SHA-256 cryptographic checksum calculation and MIME verification on save. +- [x] Domain taxonomy: Medical Records, Education & Degrees, Vehicle & Asset, Personal & Identity, Professional, Other. +- [x] `DocumentUploadForm` and `DocumentEditForm` with 25 MB size constraint and extension whitelisting. +- [x] Safe in-browser decrypted preview for PDF documents and high-resolution images. +- [x] Secure download streaming with sanitized `Content-Disposition` attachment headers. +- [x] Robust IDOR security guards on view, preview, download, edit, and delete operations. +- [x] Automatic physical disk sanitization on document deletion. +- [x] Real-time dashboard integration showing actual document counts, storage used (MB), and recent vault records. +- [x] 17 automated unit tests created and verified in `tests/unit/test_phase3.py`. + +--- + +### βœ… Security & Vulnerability Defense Suite +- [x] **IDOR Attack Prevention**: Complete test verification that Bob cannot view, download, edit, or delete Alice's documents (HTTP 404 enforcement). +- [x] **Unauthenticated Access Redirection**: All protected routes (`/dashboard/`, `/documents/`, `/documents/upload/`, `/documents//`, `/documents//download/`, `/documents//edit/`, `/documents//delete/`) redirect to login. +- [x] **Malicious File Upload Defense**: Instant rejection of executable scripts (`.exe, .bat, .sh, .py, .php, .html, .js`). +- [x] **File Size Constraint Defense**: Client & form level enforcement rejecting payloads > 25 MB. +- [x] **Path Traversal Sanitization**: Multi-level path traversal payloads (`../../../../etc/shadow.pdf`) sanitized to isolated UUID filenames in user partition. +- [x] **Cryptographic Integrity Checksum**: SHA-256 binary validation on every saved file. +- [x] **Session Destruction Verification**: POST logout destroys session cookies and revokes protected route access. +- [x] 10 automated security tests created and verified in `tests/unit/test_security.py`. + +--- + +### βœ… Phase 4: Controlled Share Pass Generation & Ephemeral QR Protocol +- [x] `SharePass` model with many-to-many document relationships, UUID primary keys, and usage constraints. +- [x] Ephemeral Share Key generation (cryptographically secure 8-character token `XXXX-XXXX`) and PBKDF2 hashing. +- [x] Zero-knowledge key storage: Plaintext keys never stored on disk; verified via salted PBKDF2 hash. +- [x] Configurable time-to-live (15m, 30m, 1h, 2h, 24h, 1-time view). +- [x] Granular permission toggles: In-browser View enforced, Download toggleable. +- [x] Dynamic high-contrast QR code generation via `qrcode` with base64 data URI and direct PNG attachment streaming. +- [x] Interactive UI templates: `templates/sharing/create.html`, `detail.html`, and `list.html`. +- [x] Immediate 1-click revocation mechanism from owner pass details and pass list. +- [x] Dashboard integration: Live active passes metrics and recent active passes bento panel. +- [x] 16 automated unit tests created and verified in `tests/unit/test_phase4.py`. + +--- + +### βœ… Phase 5: Recipient Verification Portal & Two-Factor Access Flow +- [x] Public recipient route `/p//` requiring Share Key submission. +- [x] Rate-limited key verification (5 attempts max before automatic lockout). +- [x] Isolated in-browser viewer for authorized documents without account leakage. +- [x] Server-enforced download gating (`can_download`). +- [x] Expired / Revoked pass error states with clean security diagnostics. +- [x] Session destruction on explicit departure (`/leave/`). +- [x] 14 automated unit tests created and verified in `tests/unit/test_phase5.py`. + + +--- + +### βœ… Phase 6: Audit Trail, Immediate Revocation & Rate Limiting +- [x] Structured immutable audit logging (`ShareAccessLog`) tracking timestamp, IP, user-agent, key attempt, event type, and outcome. +- [x] Automated audit logging triggers across recipient key verification, preview streaming, downloads, session exits, and lockouts. +- [x] Owner Security Audit Dashboard (`/audit/`) with metric counters, multi-attribute filter toolbar, keyword search, and pagination. +- [x] Strict IDOR isolation ensuring users only see audit trails for passes they own. +- [x] 1-click immediate pass revocation from owner dashboard & pass details. +- [x] Rate limiting brute-force defense with structured lockout auditing. +- [x] 13 automated unit tests created and verified in `tests/unit/test_phase6.py`. + +--- + +### βœ… Phase 7: End-to-End Testing, Production Hardening & Audit Report +- [x] Comprehensive End-to-End User & Recipient Lifecycle suite in `tests/selenium/test_e2e_user_journey.py`. +- [x] Full simulation covering owner registration, document upload, SHA-256 verification, pass generation with PBKDF2 ephemeral key, adversary brute-force lockout, recipient verification, PDF preview stream, raw download, voluntary session exit, and owner 1-click revocation. +- [x] Full-featured DOCX-ready Markdown Verification & Security Audit Report in `PHASE7_TEST_REPORT.md`. +- [x] Zero-knowledge architecture and responsive UI audit verified across desktop, tablet, and mobile viewpoints. +- [x] 93 automated tests created and passing with 100% success rate across all repository suites. + +--- + +## πŸ› οΈ Automated Verification Log + +```text +Test Suite: test_phase1.py, test_phase2.py, test_phase3.py, test_security.py, test_phase4.py, test_phase5.py, test_phase6.py, test_e2e_user_journey.py +Status: 93 Passed, 0 Failed, 0 Skipped +Ran: 93 tests +Result: OK (100% Passed) +``` diff --git a/README.md b/README.md index 0730e2b..079b8f2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,36 @@ -# Passli β€” Cryptographic Document Vault & Ephemeral Sharing Platform +

+ Passli β€” Cryptographic Document Vault & Ephemeral Sharing Platform +

+ +

+ CI + Python Version + Django + Encryption: AES-256-GCM AEAD + License: MIT +

+ +

+ Passli is an enterprise-grade, privacy-first personal records vault and ephemeral cryptographic sharing platform. Store sensitive documents with AES-256-GCM authenticated encryption at rest and share them with verified recipients via time-gated Share Passesβ€”without exposing master cloud credentials, unencrypted email attachments, or cascading folder permissions. +

+ +

+ Interactive Demo β€’ + The Problem β€’ + Capabilities β€’ + Architecture β€’ + Quick Start β€’ + Testing β€’ + Deployment Guide +

-[![CI](https://github.com/alinawazcode/passli/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/alinawazcode/passli/actions/workflows/ci.yml) -[![Python Version](https://img.shields.io/badge/python-3.11%20%7C%203.12-blue.svg)](https://www.python.org/) -[![Django](https://img.shields.io/badge/django-5.1%2B-green.svg)](https://www.djangoproject.com/) -[![Encryption: AES-256-GCM](https://img.shields.io/badge/encryption-AES--256--GCM%20AEAD-blueviolet.svg)](SECURITY.md) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +--- + +## 🎬 Ephemeral Protocol in Action -**Passli** is an enterprise-grade, privacy-first personal records vault and ephemeral cryptographic sharing platform. It enables individuals and organizations to securely store sensitive documents with **AES-256-GCM authenticated encryption at rest** and share them with verified recipients via time-gated **Share Passes**β€”without exposing master cloud credentials, unencrypted email attachments, or cascading folder permissions. +

+ Passli Ephemeral Share Pass Protocol in Action +

--- @@ -24,6 +48,11 @@ When sharing sensitive records with doctors, employers, financial institutions, ## ✨ Core Platform Capabilities ### πŸ—„οΈ 1. Personal Document Vault + +

+ Passli Personal Vault Dashboard & Document Manager UI +

+ - **Domain Taxonomy**: Organize records into structured categories (`Medical`, `Education`, `Vehicle`, `Personal & Identity`, `Professional`, `Other`). - **Storage Migration CLI**: Includes `python manage.py encrypt_vault_storage` for seamless batch migration of legacy storage records. @@ -78,6 +107,10 @@ When sharing sensitive records with doctors, employers, financial institutions, ## πŸ” Cryptographic Architecture Flow +

+ Passli Cryptographic Pipeline Architecture: AES-256-GCM AEAD Ingestion & In-Memory Streaming +

+ ```mermaid flowchart TD subgraph Ingestion["Document Upload & Storage"] @@ -169,8 +202,14 @@ passli/ β”‚ β”œβ”€β”€ driver_factory.py # Persistent Chrome WebDriver session manager β”‚ β”œβ”€β”€ test_config.py # Selectors, timeouts, and credentials configuration β”‚ β”œβ”€β”€ tests/ # 16 full-lifecycle tests + 3 controlled negative demos -β”‚ └── reports/ # Standalone dark-mode HTML reports & screenshots -β”œβ”€β”€ .github/workflows/ci.yml # Multi-stage CI pipeline (Ruff, pip-audit, Docker, Postgres, Coverage) +β”œβ”€β”€ .github/ +β”‚ β”œβ”€β”€ assets/ # Visual branding, animated demo GIFs & architecture infographics +β”‚ └── workflows/ci.yml # Multi-stage CI pipeline (Ruff, pip-audit, Docker, Postgres, Coverage) +β”œβ”€β”€ docs/ # Architectural specifications, brand system, and deployment runbooks +β”‚ β”œβ”€β”€ ARCHITECTURE.md # Cryptographic design, wire format, and in-memory streaming +β”‚ β”œβ”€β”€ BRANDING.md # Visual identity catalog, design tokens, and color system +β”‚ β”œβ”€β”€ DEPLOY_PYTHONANYWHERE.md # Production deployment guide for PythonAnywhere +β”‚ └── DEPLOYMENT.md # Multi-host deployment options (Docker, Render, Fly.io) β”œβ”€β”€ Dockerfile # Multi-stage production container image with unprivileged appuser └── manage.py # Django CLI management entry point ``` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 19a7cb8..ba6aad4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -18,6 +18,10 @@ Passli is a privacy-first, zero-credential-exposure personal document vault and ## 2. Document Vault Cryptographic Lifecycle +

+ Passli Cryptographic Lifecycle Architecture +

+ ### 2.1 File Ingestion & Storage Wire Format When a user uploads a document: 1. **Plaintext Hashing**: Original plaintext bytes are read in-memory; a SHA-256 digest is calculated and stored in `Document.file_hash`. @@ -54,6 +58,10 @@ Passli supports zero-downtime key rotation: ## 3. Ephemeral Share Pass Protocol +

+ Passli Ephemeral Share Pass Protocol in Action +

+ ### 3.1 Plaintext Key Isolation - Plaintext 8-character keys (`XXXX-XXXX`) use a 31-character unambiguous alphabet (`ABCDEFGHJKMNPQRSTUVWXYZ23456789`, omitting lookalikes `0`, `O`, `1`, `I`, `L`). - Plaintext keys are **never stored** in the database. diff --git a/docs/BRANDING.md b/docs/BRANDING.md new file mode 100644 index 0000000..4be4c90 --- /dev/null +++ b/docs/BRANDING.md @@ -0,0 +1,58 @@ +# Passli Visual Identity & Brand Design System + +This guide establishes the visual identity, brand guidelines, color tokens, and asset specifications for the **Passli** open-source repository and application suite. + +--- + +## πŸ›‘οΈ Brand Philosophy & Design System + +Passli adheres strictly to **human-crafted, anti-AI-slop design principles** (referencing `LeoStehlik/no-slop-ui` and `unslop-ui v2` standards): +- **Zero AI ClichΓ©s**: No generic cybernetic hands, glowing purple/magenta radial traps, or cartoonish clip-art. +- **Precision Dark Palette**: Grounded in deep slate and obsidian (`#0B0F17`, `#0F172A`, `#111827`) with functional emerald and cyan accents. +- **Hairline Borders & Subtle Glassmorphism**: Clean 1px translucent borders (`rgba(255, 255, 255, 0.08)`) with multi-layered backdrops. +- **Authentic Engineering Diagrams**: Accurate wire formats, cryptographic flows, and real product UI elements. + +--- + +## 🎨 Color Palette & Design Tokens + +| Token Name | Hex Code | HSL / RGBA | Role / Usage | +| :--- | :---: | :---: | :--- | +| **`--bg-canvas`** | `#0B0F17` | `hsl(220, 35%, 7%)` | Primary dark background / page canvas | +| **`--bg-surface`** | `#111827` | `hsl(222, 47%, 11%)` | Surface cards, dialogs, and panels | +| **`--bg-card`** | `#1E293B` | `hsl(217, 33%, 17%)` | Secondary elevated containers and table rows | +| **`--border-hairline`** | `#1F2937` | `rgba(255, 255, 255, 0.08)` | 1px clean card and component borders | +| **`--accent-emerald`** | `#10B981` | `hsl(160, 84%, 39%)` | Primary brand accent, verified status, active states | +| **`--accent-cyan`** | `#06B6D4` | `hsl(189, 94%, 43%)` | Secondary accent, interactive badges, active QR scanner | +| **`--danger-red`** | `#EF4444` | `hsl(0, 84%, 60%)` | 1-Click Revocation, session destruction, lockouts | +| **`--warning-amber`** | `#F59E0B` | `hsl(38, 92%, 50%)` | Security alerts, OTP expiration warnings | +| **`--text-primary`** | `#F8FAFC` | `hsl(210, 40%, 98%)` | High-contrast headers and primary text | +| **`--text-secondary`**| `#94A3B8` | `hsl(215, 20%, 65%)` | Subtitles, helper text, and metadata | + +--- + +## πŸ—‚οΈ Brand Assets & File Hierarchy + +### 1. Vector Logos & Marks (`static/img/brand/`) +- [`passli-logo.svg`](../static/img/brand/passli-logo.svg): Primary horizontal logotype for dark backgrounds. +- [`passli-logo-light.svg`](../static/img/brand/passli-logo-light.svg): High-contrast logotype variant for light backgrounds. +- [`passli-mark.svg`](../static/img/brand/passli-mark.svg): Standalone geometric shield & vault keyhole brand mark. +- [`passli-app-icon.png`](../static/img/brand/passli-app-icon.png): 512 Γ— 512 high-resolution raster app icon for mobile homescreens and PWA. + +### 2. Repository Visual Identity Assets (`.github/assets/`) +- [`passli-repo-banner.png`](../.github/assets/passli-repo-banner.png): 1376 Γ— 768 (16:9) Repository Hero Banner. +- [`passli-ephemeral-workflow.gif`](../.github/assets/passli-ephemeral-workflow.gif): 1100 Γ— 580 (58-frame animated demonstration of the Share Pass protocol). +- [`passli-dashboard-preview.png`](../.github/assets/passli-dashboard-preview.png): 1376 Γ— 768 (16:9) Personal Vault Dashboard UI Preview. +- [`passli-cryptographic-pipeline.png`](../.github/assets/passli-cryptographic-pipeline.png): 1376 Γ— 768 (16:9) Systems architecture diagram of AES-256-GCM AEAD storage and streaming. + +--- + +## πŸ“ Asset Generation Tooling + +Passli includes deterministic Python automation to regenerate animated workflow demonstrations: + +```powershell +python scripts/generate_ephemeral_flow_gif.py +``` + +All static diagrams and banners are engineered in 16:9 widescreen format, optimized under 1 MB to guarantee rapid CDN delivery across GitHub without proxy timeouts. diff --git a/scripts/generate_ephemeral_flow_gif.py b/scripts/generate_ephemeral_flow_gif.py new file mode 100644 index 0000000..4480594 --- /dev/null +++ b/scripts/generate_ephemeral_flow_gif.py @@ -0,0 +1,376 @@ +import os +import math +from PIL import Image, ImageDraw, ImageFont + +# Canvas dimensions +WIDTH = 1100 +HEIGHT = 580 +FPS = 12 +BG_COLOR = (11, 15, 23) # #0B0F17 Deep obsidian +CARD_BG = (17, 24, 39) # #111827 Dark slate +CARD_BORDER = (31, 41, 55) # #1F2937 Hairline border +ACCENT_GREEN = (16, 185, 129) # #10B981 Emerald +ACCENT_CYAN = (6, 182, 212) # #06B6D4 Electric cyan +TEXT_WHITE = (248, 250, 252) # #F8FAFC +TEXT_MUTED = (148, 163, 184) # #94A3B8 +TEXT_DIM = (100, 116, 139) # #64748B +RED_REVOKE = (239, 68, 68) # #EF4444 +GOLD_WARN = (245, 158, 11) # #F59E0B + +# Load fonts +try: + font_title = ImageFont.truetype("C:/Windows/Fonts/segoeuib.ttf", 26) + font_heading = ImageFont.truetype("C:/Windows/Fonts/segoeuib.ttf", 20) + font_sub = ImageFont.truetype("C:/Windows/Fonts/segoeui.ttf", 15) + font_bold = ImageFont.truetype("C:/Windows/Fonts/segoeuib.ttf", 15) + font_mono_large = ImageFont.truetype("C:/Windows/Fonts/consola.ttf", 32) + font_mono = ImageFont.truetype("C:/Windows/Fonts/consola.ttf", 14) + font_badge = ImageFont.truetype("C:/Windows/Fonts/segoeuib.ttf", 12) + font_small = ImageFont.truetype("C:/Windows/Fonts/segoeui.ttf", 12) +except Exception: + font_title = font_heading = font_sub = font_bold = font_mono_large = font_mono = font_badge = font_small = ( + ImageFont.load_default() + ) + + +def draw_rounded_rect(draw, bbox, radius, fill=None, outline=None, width=1): + draw.rounded_rectangle(bbox, radius=radius, fill=fill, outline=outline, width=width) + + +def draw_header(draw, active_tab="Share Pass"): + # App Header Bar + draw.rectangle([0, 0, WIDTH, 54], fill=(15, 23, 42)) + draw.line([0, 54, WIDTH, 54], fill=(30, 41, 59), width=1) + + # Shield mark icon + sx, sy = 24, 16 + draw.polygon( + [(sx + 10, sy), (sx + 20, sy + 4), (sx + 20, sy + 14), (sx + 10, sy + 22), (sx, sy + 14), (sx, sy + 4)], + outline=ACCENT_GREEN, + fill=(16, 185, 129, 40), + ) + draw.ellipse([sx + 8, sy + 8, sx + 12, sy + 12], fill=ACCENT_GREEN) + + # Brand Text + draw.text((sx + 28, sy - 2), "Passli", font=font_heading, fill=TEXT_WHITE) + draw.text((sx + 95, sy + 3), "Cryptographic Document Vault", font=font_sub, fill=TEXT_MUTED) + + # Tabs + tabs = ["Dashboard", "Documents", "Share Passes", "Audit Log"] + tx = 440 + for t in tabs: + is_active = t == active_tab or (active_tab.startswith("Share") and t == "Share Passes") + col = TEXT_WHITE if is_active else TEXT_DIM + draw.text((tx, sy + 2), t, font=font_sub, fill=col) + if is_active: + tw = int(draw.textlength(t, font=font_sub)) + draw.line([tx, 53, tx + tw, 53], fill=ACCENT_GREEN, width=2) + tx += 130 + + # User status pill + draw_rounded_rect(draw, [WIDTH - 170, 12, WIDTH - 24, 42], radius=15, fill=(30, 41, 59), outline=(51, 65, 85)) + draw.ellipse([WIDTH - 160, 20, WIDTH - 146, 34], fill=ACCENT_GREEN) + draw.text((WIDTH - 138, 19), "qa_vault_user", font=font_badge, fill=TEXT_WHITE) + + +def draw_qr_code(draw, x, y, size=140, scan_pos=None): + # Stylized clean QR Code matrix + draw_rounded_rect(draw, [x, y, x + size, y + size], radius=8, fill=(255, 255, 255), outline=(203, 213, 225)) + + # Outer markers + def marker(mx, my): + draw.rectangle([mx, my, mx + 28, my + 28], fill=(11, 15, 23)) + draw.rectangle([mx + 4, my + 4, mx + 24, my + 24], fill=(255, 255, 255)) + draw.rectangle([mx + 8, my + 8, mx + 20, my + 20], fill=(11, 15, 23)) + + marker(x + 10, y + 10) + marker(x + size - 38, y + 10) + marker(x + 10, y + size - 38) + + # Pseudo-random deterministic grid dots + import random + + rng = random.Random(42) + step = 7 + for gx in range(x + 12, x + size - 12, step): + for gy in range(y + 12, y + size - 12, step): + if ( + (gx < x + 44 and gy < y + 44) + or (gx > x + size - 46 and gy < y + 44) + or (gx < x + 44 and gy > y + size - 46) + ): + continue + if rng.random() > 0.45: + draw.rectangle([gx, gy, gx + 4, gy + 4], fill=(15, 23, 42)) + + # Animated scanline effect + if scan_pos is not None: + scan_y = int(y + 8 + (size - 16) * scan_pos) + draw.line([x + 4, scan_y, x + size - 4, scan_y], fill=ACCENT_CYAN, width=3) + draw.line([x + 6, scan_y - 1, x + size - 6, scan_y - 1], fill=(6, 182, 212, 100), width=1) + draw.line([x + 6, scan_y + 1, x + size - 6, scan_y + 1], fill=(6, 182, 212, 100), width=1) + + +frames = [] + +# ========================================== +# SCENE 1: Owner Configures Pass (Frames 0-14) +# ========================================== +for i in range(15): + img = Image.new("RGB", (WIDTH, HEIGHT), BG_COLOR) + draw = ImageDraw.Draw(img) + draw_header(draw, active_tab="Share Passes") + + # Main container card + draw_rounded_rect(draw, [50, 75, WIDTH - 50, HEIGHT - 35], radius=12, fill=CARD_BG, outline=CARD_BORDER) + + # Step Breadcrumbs / Progress + draw.text((80, 95), "STEP 1 OF 3", font=font_badge, fill=ACCENT_CYAN) + draw.text((80, 115), "Create Ephemeral Share Pass", font=font_title, fill=TEXT_WHITE) + draw.text( + (80, 148), + "Select encrypted vault documents and enforce zero-persistence TTL duration.", + font=font_sub, + fill=TEXT_MUTED, + ) + + # Left Column: Document Selection Checklist + draw.text((80, 185), "1. Included Vault Documents", font=font_heading, fill=TEXT_WHITE) + + docs = [ + ("Medical_Lab_Results_2025.pdf", "Medical Records", "SHA256: 8f4a...29b1", True), + ("Passport_Identity_Scan.pdf", "Personal & Identity", "SHA256: 3c9e...5402", True), + ("Vehicle_Registration_Title.pdf", "Vehicle & Asset", "SHA256: e7a1...8d12", i >= 6), + ] + dy = 220 + for doc_name, cat, sha, checked in docs: + b_color = (30, 41, 59) if checked else (20, 27, 45) + border = ACCENT_GREEN if checked else (45, 55, 72) + draw_rounded_rect(draw, [80, dy, 520, dy + 55], radius=8, fill=b_color, outline=border) + # Checkbox + cb_fill = ACCENT_GREEN if checked else None + draw_rounded_rect( + draw, [96, dy + 18, 116, dy + 38], radius=4, fill=cb_fill, outline=ACCENT_GREEN if checked else TEXT_DIM + ) + if checked: + draw.text((101, dy + 18), "βœ“", font=font_badge, fill=(11, 15, 23)) + # Doc Details + draw.text((130, dy + 10), doc_name, font=font_bold, fill=TEXT_WHITE) + draw.text((130, dy + 30), f"{cat} β€’ {sha}", font=font_small, fill=TEXT_MUTED) + dy += 65 + + # Right Column: Expiration & Access Constraints + draw.text((570, 185), "2. Time-To-Live & Access Policy", font=font_heading, fill=TEXT_WHITE) + + # TTL Pills + ttls = [("15 mins", True), ("30 mins", False), ("1 hour", False), ("24 hours", False), ("Single View", False)] + px = 570 + py = 220 + for label, is_sel in ttls: + pw = int(draw.textlength(label, font=font_badge)) + 26 + fill = (16, 185, 129, 35) if is_sel else (30, 41, 59) + border = ACCENT_GREEN if is_sel else (51, 65, 85) + draw_rounded_rect(draw, [px, py, px + pw, py + 34], radius=6, fill=fill, outline=border) + draw.text((px + 13, py + 8), label, font=font_badge, fill=ACCENT_GREEN if is_sel else TEXT_MUTED) + px += pw + 10 + if px > WIDTH - 120: + px = 570 + py += 44 + + # Permissions Box + draw_rounded_rect(draw, [570, 280, WIDTH - 80, 370], radius=8, fill=(15, 23, 42), outline=(30, 41, 59)) + draw.text((585, 292), "πŸ”’ Permissions & Security Guarantees", font=font_bold, fill=TEXT_WHITE) + draw.text((585, 316), "β€’ In-Browser Decrypted Preview Stream: Enabled", font=font_sub, fill=ACCENT_CYAN) + draw.text((585, 338), "β€’ Raw File Download: Disabled (View-Only Mode)", font=font_sub, fill=TEXT_MUTED) + + # CTA Button with animated click on frames 12-14 + btn_color = (5, 150, 105) if i >= 12 else ACCENT_GREEN + btn_w = 340 + btn_rect = [570, 400, 570 + btn_w, 448] + draw_rounded_rect(draw, btn_rect, radius=8, fill=btn_color) + btn_txt = "Generating Ephemeral Pass..." if i >= 12 else "Generate Ephemeral Share Pass β†’" + tw = int(draw.textlength(btn_txt, font=font_bold)) + draw.text((570 + (btn_w - tw) // 2, 414), btn_txt, font=font_bold, fill=(11, 15, 23)) + + frames.append(img) + +# ========================================== +# SCENE 2: Cryptographic Key & QR Generated (Frames 15-32) +# ========================================== +token_target = "7K9X-2M4P" +for i in range(18): + img = Image.new("RGB", (WIDTH, HEIGHT), BG_COLOR) + draw = ImageDraw.Draw(img) + draw_header(draw, active_tab="Share Passes") + + draw_rounded_rect(draw, [50, 75, WIDTH - 50, HEIGHT - 35], radius=12, fill=CARD_BG, outline=CARD_BORDER) + + draw.text((80, 95), "STEP 2 OF 3", font=font_badge, fill=ACCENT_GREEN) + draw.text((80, 115), "Share Pass Cryptographically Issued", font=font_title, fill=TEXT_WHITE) + draw.text( + (80, 148), + "Zero-Knowledge Key hashed via PBKDF2. Distribute key and QR to recipient.", + font=font_sub, + fill=TEXT_MUTED, + ) + + # Left Card: The Ephemeral Key Box + draw_rounded_rect(draw, [80, 190, 580, 480], radius=10, fill=(15, 23, 42), outline=CARD_BORDER) + + draw.text((105, 210), "EPHEMERAL SHARE KEY (ONE-TIME)", font=font_badge, fill=ACCENT_CYAN) + + # Key Typewriter effect + num_chars = min(len(token_target), max(1, (i * 2))) + curr_token = token_target[:num_chars] + cursor = "_" if (i % 4 < 2 and num_chars < len(token_target)) else "" + + draw_rounded_rect(draw, [105, 235, 555, 305], radius=8, fill=(11, 15, 23), outline=ACCENT_GREEN) + draw.text((125, 250), curr_token + cursor, font=font_mono_large, fill=ACCENT_GREEN) + + # Security Badges + draw.text((105, 325), "πŸ›‘οΈ Cryptographic Parameter Summary", font=font_bold, fill=TEXT_WHITE) + badges = [ + "Plaintext Zero-Knowledge: Stored as Salted PBKDF2-HMAC-SHA256", + "Document Encryption: AES-256-GCM AEAD (UUID Bound AAD)", + "Session Lifespan: 15 Minutes (Strict server-side enforcement)", + "Rate-Limiting Defense: 5 Failed Attempts -> Exponential Lockout", + ] + by = 350 + for b in badges: + draw.text((105, by), f"β€’ {b}", font=font_small, fill=TEXT_MUTED) + by += 24 + + # 1-Click Revoke button + draw_rounded_rect(draw, [105, 435, 300, 465], radius=6, fill=(239, 68, 68, 40), outline=RED_REVOKE) + draw.text((120, 442), "⚑ 1-Click Immediate Revoke", font=font_badge, fill=RED_REVOKE) + + # Right Card: The QR Code Display + draw_rounded_rect(draw, [620, 190, WIDTH - 80, 480], radius=10, fill=(15, 23, 42), outline=CARD_BORDER) + draw.text((645, 210), "ISOLATED RECIPIENT QR PROTOCOL", font=font_badge, fill=TEXT_MUTED) + + # Draw QR code with moving scanline + scan_pos = (i % 12) / 12.0 + draw_qr_code(draw, 720, 245, size=180, scan_pos=scan_pos) + + draw.text((670, 440), "Recipient scans QR + enters 8-char key", font=font_sub, fill=TEXT_WHITE) + + frames.append(img) + +# ========================================== +# SCENE 3: Recipient Unlocked & In-Memory Streaming (Frames 33-47) +# ========================================== +countdown_secs = [900 - s for s in range(15)] +for i in range(15): + img = Image.new("RGB", (WIDTH, HEIGHT), BG_COLOR) + draw = ImageDraw.Draw(img) + draw_header(draw, active_tab="Recipient Portal") + + draw_rounded_rect(draw, [50, 75, WIDTH - 50, HEIGHT - 35], radius=12, fill=CARD_BG, outline=CARD_BORDER) + + # Active portal banner + draw.text((80, 95), "STEP 3 OF 3: RECIPIENT SESSION ACTIVE", font=font_badge, fill=ACCENT_GREEN) + draw.text((80, 115), "In-Memory Decrypted Document Portal", font=font_title, fill=TEXT_WHITE) + draw.text( + (80, 148), + "Documents decrypted directly into volatile RAM (io.BytesIO). Zero disk exposure.", + font=font_sub, + fill=TEXT_MUTED, + ) + + # Countdown Widget Header + rem = countdown_secs[i] + mins = rem // 60 + secs = rem % 60 + timer_str = f"{mins:02d}:{secs:02d}" + + draw_rounded_rect(draw, [WIDTH - 260, 95, WIDTH - 80, 155], radius=10, fill=(15, 23, 42), outline=ACCENT_GREEN) + draw.text((WIDTH - 245, 105), "SESSION TIME REMAINING", font=font_badge, fill=TEXT_MUTED) + draw.text((WIDTH - 245, 122), timer_str, font=font_heading, fill=ACCENT_GREEN) + + # Document Preview Window + draw_rounded_rect(draw, [80, 190, WIDTH - 80, 480], radius=10, fill=(15, 23, 42), outline=CARD_BORDER) + + # Document Selector Tabs + doc_tabs = ["Medical_Lab_Results_2025.pdf", "Passport_Identity_Scan.pdf"] + dx = 100 + for dt in doc_tabs: + is_active = dt == doc_tabs[0] + col = (16, 185, 129, 40) if is_active else (25, 35, 55) + border = ACCENT_GREEN if is_active else (45, 55, 75) + draw_rounded_rect(draw, [dx, 205, dx + 260, 240], radius=6, fill=col, outline=border) + draw.text((dx + 12, 215), dt[:26], font=font_badge, fill=TEXT_WHITE if is_active else TEXT_MUTED) + dx += 275 + + # Simulated In-Memory PDF Page + draw_rounded_rect(draw, [100, 255, WIDTH - 100, 460], radius=8, fill=(24, 32, 47), outline=(45, 55, 72)) + + # Document Preview Content Header + draw.text((120, 275), "DIAGNOSTIC PATHOLOGY LABORATORY REPORT", font=font_bold, fill=TEXT_WHITE) + draw.text( + (120, 298), "Patient: [Protected User Identity] | Specimen ID: #88204-PX", font=font_sub, fill=TEXT_MUTED + ) + draw.line([120, 325, WIDTH - 120, 325], fill=(45, 55, 72), width=1) + + # Preview Lines + lines = [ + "Hematology Comprehensive Metabolic Panel: Normal (4.8 mEq/L)", + "Platelet Count & Serum Glucose: Within Baseline Reference Range", + "Cryptographic Digest Verification: SHA-256 Validated β€’ Authenticated AES-256-GCM AEAD", + "Delivery Header: Cache-Control: no-store, private β€’ Content-Security-Policy: sandbox", + ] + ly = 340 + for line_text in lines: + draw.text((120, ly), line_text, font=font_mono, fill=(203, 213, 225)) + ly += 22 + + # Exit / Wipe session button + draw_rounded_rect(draw, [WIDTH - 260, 415, WIDTH - 120, 448], radius=6, fill=(239, 68, 68, 30), outline=RED_REVOKE) + draw.text((WIDTH - 245, 424), "Leave & Purge Session", font=font_badge, fill=RED_REVOKE) + + frames.append(img) + +# ========================================== +# SCENE 4: Session Revoked / Purged (Frames 48-56) +# ========================================== +for i in range(10): + img = Image.new("RGB", (WIDTH, HEIGHT), BG_COLOR) + draw = ImageDraw.Draw(img) + draw_header(draw, active_tab="Share Passes") + + draw_rounded_rect(draw, [50, 75, WIDTH - 50, HEIGHT - 35], radius=12, fill=CARD_BG, outline=CARD_BORDER) + + # Revocation Alert Modal + draw_rounded_rect(draw, [250, 140, WIDTH - 250, 430], radius=12, fill=(15, 23, 42), outline=RED_REVOKE, width=2) + + # Red lock symbol + draw.ellipse([WIDTH // 2 - 30, 170, WIDTH // 2 + 30, 230], fill=(239, 68, 68, 40), outline=RED_REVOKE, width=2) + draw.text((WIDTH // 2 - 8, 188), "βœ•", font=font_heading, fill=RED_REVOKE) + + # Message + draw.text((WIDTH // 2 - 140, 255), "Share Pass Revoked & Purged", font=font_heading, fill=TEXT_WHITE) + draw.text( + (WIDTH // 2 - 210, 290), + "This ephemeral session has been immediately severed by the owner.", + font=font_sub, + fill=TEXT_MUTED, + ) + draw.text( + (WIDTH // 2 - 230, 315), + "In-memory buffers cleared. Future access attempts return HTTP 404.", + font=font_sub, + fill=TEXT_MUTED, + ) + + # Structured Audit Badge + draw_rounded_rect( + draw, [WIDTH // 2 - 200, 355, WIDTH // 2 + 200, 395], radius=6, fill=(11, 15, 23), outline=(51, 65, 85) + ) + draw.text((WIDTH // 2 - 180, 368), "AUDIT EVENT LOGGED: pass_revoked_by_owner", font=font_mono, fill=GOLD_WARN) + + frames.append(img) + +# Save animated GIF +out_path = r".github\assets\passli-ephemeral-workflow.gif" +frames[0].save(out_path, save_all=True, append_images=frames[1:], duration=int(1000 / FPS), loop=0, optimize=True) + +print(f"Generated {out_path}: {len(frames)} frames, {os.path.getsize(out_path)} bytes") diff --git a/tests/unit/test_repo_visual_identity.py b/tests/unit/test_repo_visual_identity.py new file mode 100644 index 0000000..aa2ac29 --- /dev/null +++ b/tests/unit/test_repo_visual_identity.py @@ -0,0 +1,96 @@ +""" +Tests for repository visual identity assets and documentation. +Verifies existence, dimensions, aspect ratios, file size bounds, +and README/docs link integrity. +""" + +from pathlib import Path +from django.test import SimpleTestCase +from django.conf import settings +from PIL import Image + + +class RepoVisualIdentityTests(SimpleTestCase): + """Test suite ensuring visual identity assets meet open-source repository standards.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.base_dir = Path(settings.BASE_DIR) + cls.assets_dir = cls.base_dir / ".github" / "assets" + cls.readme_path = cls.base_dir / "README.md" + cls.branding_path = cls.base_dir / "docs" / "BRANDING.md" + + def test_assets_directory_exists(self): + """The .github/assets directory must exist.""" + self.assertTrue(self.assets_dir.is_dir(), ".github/assets directory must exist") + + def test_repo_banner_asset(self): + """Hero banner must exist, be under 2MB, and have 16:9 aspect ratio.""" + banner_path = self.assets_dir / "passli-repo-banner.png" + self.assertTrue(banner_path.exists(), "passli-repo-banner.png must exist") + self.assertLess(banner_path.stat().st_size, 2 * 1024 * 1024, "Banner must be under 2MB") + + with Image.open(banner_path) as im: + self.assertEqual(im.format, "PNG") + self.assertEqual(im.size, (1376, 768)) + + def test_dashboard_preview_asset(self): + """Dashboard preview must exist, be under 2MB, and have 16:9 aspect ratio.""" + preview_path = self.assets_dir / "passli-dashboard-preview.png" + self.assertTrue(preview_path.exists(), "passli-dashboard-preview.png must exist") + self.assertLess(preview_path.stat().st_size, 2 * 1024 * 1024, "Preview must be under 2MB") + + with Image.open(preview_path) as im: + self.assertEqual(im.format, "PNG") + self.assertEqual(im.size, (1376, 768)) + + def test_cryptographic_pipeline_asset(self): + """Architecture diagram must exist, be under 2MB, and have 16:9 aspect ratio.""" + arch_path = self.assets_dir / "passli-cryptographic-pipeline.png" + self.assertTrue(arch_path.exists(), "passli-cryptographic-pipeline.png must exist") + self.assertLess(arch_path.stat().st_size, 2 * 1024 * 1024, "Architecture image must be under 2MB") + + with Image.open(arch_path) as im: + self.assertEqual(im.format, "PNG") + self.assertEqual(im.size, (1376, 768)) + + def test_ephemeral_workflow_gif_asset(self): + """Animated workflow GIF must exist, have multiple frames, and be under 2MB.""" + gif_path = self.assets_dir / "passli-ephemeral-workflow.gif" + self.assertTrue(gif_path.exists(), "passli-ephemeral-workflow.gif must exist") + self.assertLess(gif_path.stat().st_size, 2 * 1024 * 1024, "Workflow GIF must be under 2MB") + + with Image.open(gif_path) as im: + self.assertEqual(im.format, "GIF") + self.assertTrue(getattr(im, "is_animated", False), "Workflow image must be an animated GIF") + self.assertGreaterEqual(im.n_frames, 30, "Workflow GIF must contain at least 30 frames") + + def test_readme_references_assets(self): + """README.md must reference the visual identity assets via valid relative paths.""" + self.assertTrue(self.readme_path.exists(), "README.md must exist") + content = self.readme_path.read_text(encoding="utf-8") + + required_assets = [ + ".github/assets/passli-repo-banner.png", + ".github/assets/passli-ephemeral-workflow.gif", + ".github/assets/passli-dashboard-preview.png", + ".github/assets/passli-cryptographic-pipeline.png", + ] + for asset in required_assets: + self.assertIn(asset, content, f"README.md must contain relative link to {asset}") + + def test_branding_doc_exists_and_references_assets(self): + """docs/BRANDING.md must exist and document brand tokens and assets.""" + self.assertTrue(self.branding_path.exists(), "docs/BRANDING.md must exist") + content = self.branding_path.read_text(encoding="utf-8") + + self.assertIn("#0B0F17", content) + self.assertIn("#10B981", content) + self.assertIn("passli-repo-banner.png", content) + self.assertIn("passli-ephemeral-workflow.gif", content) + + def test_gif_generator_script_exists(self): + """scripts/generate_ephemeral_flow_gif.py generator script must exist.""" + script_path = self.base_dir / "scripts" / "generate_ephemeral_flow_gif.py" + self.assertTrue(script_path.exists(), "generate_ephemeral_flow_gif.py must exist")