{"id":15321220,"url":"https://github.com/reznik99/cloud-storage-ui","last_synced_at":"2026-04-11T21:44:36.249Z","repository":{"id":255464636,"uuid":"851647037","full_name":"reznik99/cloud-storage-ui","owner":"reznik99","description":"End-To-End Encrypted File Storage (UI)","archived":false,"fork":false,"pushed_at":"2025-03-25T05:29:46.000Z","size":1760,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-25T06:26:42.940Z","etag":null,"topics":["client-side-encryption","end-to-end-encryption","file-sharing","mui","react","typescript"],"latest_commit_sha":null,"homepage":"https://storage.francescogorini.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/reznik99.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-03T13:27:34.000Z","updated_at":"2025-03-25T05:29:50.000Z","dependencies_parsed_at":"2024-09-13T21:47:34.990Z","dependency_job_id":"20e81da2-5bc1-4738-8d77-1fc394321325","html_url":"https://github.com/reznik99/cloud-storage-ui","commit_stats":{"total_commits":60,"total_committers":1,"mean_commits":60.0,"dds":0.0,"last_synced_commit":"d43cc005d74aa4ebca535483a234466bbbe29ae7"},"previous_names":["reznik99/cloud-storage-ui"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/reznik99/cloud-storage-ui","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reznik99%2Fcloud-storage-ui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reznik99%2Fcloud-storage-ui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reznik99%2Fcloud-storage-ui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reznik99%2Fcloud-storage-ui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reznik99","download_url":"https://codeload.github.com/reznik99/cloud-storage-ui/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reznik99%2Fcloud-storage-ui/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263922473,"owners_count":23530334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client-side-encryption","end-to-end-encryption","file-sharing","mui","react","typescript"],"created_at":"2024-10-01T09:10:11.022Z","updated_at":"2026-04-11T21:44:36.241Z","avatar_url":"https://github.com/reznik99.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"public/logo.png\" alt=\"GoriniDrive\" width=\"80\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eGoriniDrive\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  Self-hosted, end-to-end encrypted cloud storage.\n  \u003cbr /\u003e\n  Zero-knowledge file storage, link sharing, and peer-to-peer transfers.\n  \u003cbr /\u003e\u003cbr /\u003e\n  \u003ca href=\"https://storage.francescogorini.com\"\u003eLive Demo\u003c/a\u003e · \u003ca href=\"https://github.com/reznik99/cloud-storage-api\"\u003eBackend Repo\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Overview\n\nGoriniDrive is a self-hosted cloud storage platform where files are encrypted client-side before they ever leave the browser. The server only stores ciphertext — it never has access to your plaintext data or encryption keys.\n\nThis repository contains the **frontend** SPA. The corresponding backend API lives at [cloud-storage-api](https://github.com/reznik99/cloud-storage-api).\n\n### Features\n\n- **E2E Encrypted Storage** — Files encrypted in-browser before upload using AES-256-GCM\n- **Shareable Links** — Generate download links for any file; encryption key stays in the URL fragment (never sent to the server)\n- **Peer-to-Peer Transfers** — Share files directly between browsers via WebRTC data channels, no file-size limits\n- **Password-Based Key Derivation** — PBKDF2 (500k iterations, SHA-512) derives all keys from your password\n- **Zero-Knowledge Architecture** — Password reset destroys data by design; the server can't help you recover\n- **Dark / Light Mode**\n\n### Roadmap\n\n- [ ] Folder view — upload and navigate directory structures (like Google Drive)\n\n---\n\n## Tech Stack\n\n| Layer | Technology |\n|---|---|\n| Framework | React 19, TypeScript |\n| Build | Vite 6, SRI via `vite-plugin-csp-guard` |\n| UI | Material UI 6, Emotion |\n| State | Redux Toolkit |\n| Routing | React Router 7 (data router) |\n| HTTP | Axios (cookie-based sessions) |\n| Crypto | Web Crypto API (AES-GCM, AES-KW, PBKDF2, SHA-256/512) |\n| P2P | WebRTC Data Channels, WebSocket signaling |\n| Password Strength | zxcvbn |\n\n---\n\n## Architecture\n\n```\n┌──────────────────────────────────────────────────────────┐\n│                        Browser                           │\n│                                                          │\n│   ┌──────────┐   ┌──────────┐   ┌────────────────────┐   │\n│   │  React   │──▶│  Redux   │──▶│   Web Crypto API │   │\n│   │  UI/MUI  │   │  Store   │   │  (encrypt/decrypt) │   │\n│   └──────────┘   └──────────┘   └────────────────────┘   │\n│        │                                │                │\n│        ▼                                ▼                │\n│   ┌──────────┐                  ┌──────────────────┐     │\n│   │  Axios   │──── REST ───────▶│  Backend API    │     │\n│   └──────────┘                  │  (ciphertext     │     │\n│                                 │   only)          │     │\n│   ┌──────────────────────┐      └──────────────────┘     │\n│   │ WebRTC Data Channel  │◀─ signaling (WS) ──▶ Peer   │\n│   │  (P2P file transfer) │                               │\n│   └──────────────────────┘                               │\n└──────────────────────────────────────────────────────────┘\n```\n\n### File Upload / Download Flow\n\n1. **Upload**: Generate random AES-256-GCM file key → encrypt file → wrap file key with Account Key (AES-KW) → upload ciphertext + wrapped key\n2. **Download**: Fetch ciphertext + wrapped key → unwrap file key with Account Key → decrypt file\n\n### Link Sharing\n\nA shareable link has the form:\n\n```\nhttps://storage.francescogorini.com/share/{access_key}#{file_key}\n```\n\nThe `access_key` (path) authorizes download from the server. The `file_key` (fragment) decrypts the file client-side. Since URL fragments are never sent to the server, the server cannot decrypt the file — even for shared links.\n\n### Peer-to-Peer Sharing\n\nUses WebRTC with WebSocket signaling:\n\n1. Sender creates a data channel and gets a shareable URL (or QR code)\n2. Receiver opens the URL; SDP offer/answer exchange happens via WebSocket\n3. ICE candidates are exchanged, a direct P2P connection is established\n4. File is streamed in 32KB chunks over the data channel\n5. On Chromium, writes stream directly to disk via `FileSystemWritableFileStream`; Firefox buffers in memory\n\nNo authentication or server storage required — files flow directly between peers.\n\n---\n\n## Security\n\n### Key Hierarchy\n\nGoriniDrive uses a 4-tier key hierarchy (inspired by the [Mega whitepaper](https://mega.nz/SecurityWhitepaper.pdf)):\n\n```\nPassword + CRV (Client Random Value)\n        │\n        ▼  PBKDF2-SHA512 (500,000 iterations)\n   ┌─────────────────────────┐\n   │   512-bit derived key   │\n   └────────┬────────────────┘\n            │\n     ┌──────┴──────┐\n     ▼              ▼\n Master Key     Auth Key\n (AES-KW)      (SHA-256 → server)\n     │\n     ▼  unwraps\n Account Key\n (AES-KW, stored encrypted on server)\n     │\n     ▼  unwraps\n File Key₁, File Key₂, ...\n (AES-GCM 256-bit, per-file, stored encrypted on server)\n```\n\n- **Master Key** never leaves the browser. It is derived from your password each session.\n- **Account Key** is stored on the server wrapped (encrypted) by the Master Key.\n- **File Keys** are randomly generated per file and stored wrapped by the Account Key.\n- **Auth Key** is a SHA-256 hash of the derived authentication bytes — the server authenticates you without ever seeing your password.\n\n### Zero-Knowledge Guarantees\n\n- The server stores only ciphertext and wrapped keys.\n- Password reset generates a **new** Account Key — previously uploaded files become permanently inaccessible. This is by design.\n- The CRV salt is domain-separated (padded with the API URL) to prevent cross-site key reuse.\n\n### Content Security Policy\n\nEnforced at build time via `vite-plugin-csp-guard`:\n\n- `script-src 'self'` — no inline scripts, no third-party JS\n- **Subresource Integrity (SRI)** on all built assets\n- WebSocket connections restricted to the backend origin\n- `unsafe-inline` for styles only (required by Material UI)\n\n### Password Policy\n\nPasswords are evaluated client-side using [zxcvbn](https://github.com/dropbox/zxcvbn) (Dropbox's password strength estimator). Minimum 8 characters, minimum strength score of 1/4.\n\n---\n\n## Getting Started\n\n### Prerequisites\n\n- Node.js 18+\n- The [backend API](https://github.com/reznik99/cloud-storage-api) running\n\n### Install \u0026 Run\n\n```bash\ngit clone https://github.com/reznik99/cloud-storage-frontend.git\ncd cloud-storage-frontend\nnpm install\nnpm run dev\n```\n\n### Build\n\n```bash\nnpm run build     # outputs to dist/\nnpm run preview   # preview production build locally\n```\n\n### Project Structure\n\n```\nsrc/\n├── components/      # Reusable UI (dialogs, nav, sidebar, file views)\n├── pages/           # Route-level pages (dashboard, login, settings, etc.)\n├── networking/      # API client (axios) and WebRTC configuration\n├── store/           # Redux store and user state slice\n└── utilities/       # Crypto operations, password validation, helpers\n```\n\n---\n\n## Interface\n\n![Login Screenshot][login]\n\n![Signup Screenshot][signup]\n\n![Dashboard Screenshot][dashboard]\n\n![Upload Screenshot][upload]\n\n![Download Screenshot][download]\n\n![Sharing Screenshot][sharing]\n\n![Sharing Download Screenshot][sharing-download]\n\n![Peer-to-Peer share Screenshot][p2p-sharing]\n\n![Settings Screenshot][settings]\n\n![Deletion Screenshot][deletion]\n\n![Light-mode Screenshot][light-mode]\n\n\n\u003c!-- LINKS --\u003e\n[signup]: 1-readme-src/signup.png\n[login]: 1-readme-src/login.png\n[dashboard]: 1-readme-src/dashboard.png\n[upload]: 1-readme-src/upload.png\n[download]: 1-readme-src/download.png\n[deletion]: 1-readme-src/deletion.png\n[sharing]: 1-readme-src/sharing.png\n[p2p-sharing]: 1-readme-src/p2p-sharing.png\n[sharing-download]: 1-readme-src/sharing-download.png\n[settings]: 1-readme-src/settings.png\n[light-mode]: 1-readme-src/light-mode.png\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freznik99%2Fcloud-storage-ui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freznik99%2Fcloud-storage-ui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freznik99%2Fcloud-storage-ui/lists"}