diff --git a/vault/CLAUDE.md b/vault/CLAUDE.md new file mode 100644 index 0000000..e20d380 --- /dev/null +++ b/vault/CLAUDE.md @@ -0,0 +1,283 @@ +# CVault — Private Code Snippet Manager + +## Overview +CVault is a private alternative to GitHub Gist. Snippets are shareable via secret links or password-protected. The app now includes a local backend with username/password accounts, Redis-backed CAPTCHA/session/rate-limit state, HTTP-only sessions, server-side snippet persistence, and server-side password/expiration checks. + +## Tech Stack +- Next.js 16 (App Router, TypeScript, Tailwind CSS v4) +- shadcn/ui — component library (Radix + Tailwind) +- Framer Motion — animations (page transitions, stagger, layout, hover) +- @uiw/react-codemirror — code editor with syntax highlighting +- next-themes — dark/light mode toggle +- Lucide React — icons +- Geist + Geist Mono — local fonts (no CDN, served from /public/fonts/) + +## Color Palette (No Purple) +- Primary: green-500 (#22c55e) / green-600 hover +- Accent: amber-500 (#f59e0b) — for password/highlights +- Background dark: zinc-950 / light: zinc-50 +- Card dark: zinc-900 / light: white + +## Routes +| Route | Purpose | +|---|---| +| `/` | Landing — hero, feature cards, recent snippets | +| `/login` | Username/password sign-in with local CAPTCHA | +| `/register` | Local account creation with CAPTCHA | +| `/snippets` | Library — grid/list, search, filter | +| `/snippets/new` | Create — code/text toggle, CodeMirror or textarea | +| `/snippets/[id]` | Owner view — syntax highlight, copy, share, edit, delete | +| `/snippets/[id]/edit` | Edit — pre-filled form | +| `/s/[id]` | Shared — minimal chrome, password gate | +| `/raw/[id]` | Raw plain-text output — 403 for password-protected, 404 if expired | + +## Key Components +| Component | File | Notes | +|---|---|---| +| Header | `components/header.tsx` | Nav, theme toggle, mobile menu | +| Footer | `components/footer.tsx` | Coaxys credit + arsamsabbagh.ir link | +| Hero | `components/hero.tsx` | Gradient text, stagger cards | +| SnippetCard | `components/snippet-card.tsx` | Layout animation, grid/list | +| CodeEditor | `components/code-editor.tsx` | CodeMirror wrapper, 13+ langs | +| CodeViewer | `components/code-viewer.tsx` | Read-only + copy button | +| PrivacyToggle | `components/privacy-toggle.tsx` | Secret/Password slider | +| PasswordGate | `components/password-gate.tsx` | Password modal | +| SearchBar | `components/search-bar.tsx` | Live filter | +| ViewToggle | `components/view-toggle.tsx` | Grid/list switch | +| TagInput | `components/tag-input.tsx` | Comma-separated tags | +| LanguageSelector | `components/language-selector.tsx` | Language dropdown | +| ExpirationSelector | `components/expiration-selector.tsx` | Expiry dropdown | +| EmptyState | `components/empty-state.tsx` | Floating icon illustration | +| ThemeProvider | `components/theme-provider.tsx` | next-themes wrapper | +| PageTransition | `components/page-transition.tsx` | AnimatePresence wrapper | + +## Animation Strategy +- All Framer Motion `initial` props set to `false` — content visible by default on SSR, animations play on client mount +- `whileHover` / `whileTap` micro-interactions on buttons/cards +- Stagger children on card grids +- Layout animations on grid/list toggle +- Hero gradient shimmer via CSS keyframes + +## Data Layer +- `lib/types.ts` — Snippet interface, PrivacyType, Expiration, LANGUAGES list +- `lib/mock-data.ts` — legacy demo snippets only; production does not seed demo users/snippets +- `lib/server-db.ts` — local JSON datastore in `data/db.json`, expiration helpers, public snippet sanitizer +- `lib/server-redis.ts` — Redis client with in-memory fallback +- `lib/server-security.ts` — HTTP-only sessions and local CAPTCHA backed by Redis +- `lib/server-rate-limit.ts` — Redis-backed rate limiting +- `lib/server-password.ts` — password hashing and verification +- `lib/api-client.ts` — client-side API wrapper +- `app/api/**` — auth, snippet CRUD, and shared-snippet route handlers +- `lib/utils.ts` — cn() from shadcn + +## Deployment +- Public URL: `https://snp.coaxys.ir` +- Production build: `npm run build` +- Process manager: `cvault.service` systemd unit +- App upstream: `next start -H 127.0.0.1 -p 3005` +- Nginx reverse proxy: HTTPS on port 443 → `127.0.0.1:3005` +- Redis: `REDIS_URL=redis://127.0.0.1:6379` +- Service env: `CVAULT_SECRET`, `CVAULT_SECURE_COOKIES=true`, `REDIS_URL` +- All runtime dependencies local (fonts in `/public/fonts/`, icons and editor bundled from npm, no CDN/remote ESM) +- CSP restricts scripts, styles, fonts, images, media, and API connections to local/self sources + +## Production Commands +- Rebuild app: `npm run build` +- Restart app: `systemctl restart cvault.service` +- Check app: `systemctl status cvault.service --no-pager -l` +- Check nginx: `nginx -t && systemctl reload nginx` +- Check Redis: `redis-cli ping` +- Inspect Redis CVault keys: `redis-cli --scan --pattern 'cvault:*'` + +## Deployment Guide + +### Prerequisites +- Ubuntu/Debian server +- Node.js 20+ (`node --version`) +- npm 10+ (`npm --version`) +- Redis (`apt install redis-server`) +- Nginx (`apt install nginx`) +- Certbot for SSL (`apt install certbot python3-certbot-nginx`) + +### 1. Transfer / Clone the Repo + +```bash +# Option A — git clone (once you push to a remote) +git clone /srv/CVault/vault + +# Option B — rsync from old server +rsync -av --exclude='.next' --exclude='node_modules' --exclude='data/' \ + user@old-server:/srv/CVault/vault/ /srv/CVault/vault/ +``` + +### 2. Install Dependencies + +```bash +cd /srv/CVault/vault +npm ci # uses package-lock.json exactly — never npm install in production +``` + +### 3. Environment Variables + +Create `/etc/cvault.env` (owned by root, mode 600): + +```bash +cat > /etc/cvault.env << 'EOF' +CVAULT_SECRET= +CVAULT_SECURE_COOKIES=true +REDIS_URL=redis://127.0.0.1:6379 +NODE_ENV=production +EOF +chmod 600 /etc/cvault.env +``` + +### 4. Build the App + +```bash +cd /srv/CVault/vault +npm run build +``` + +### 5. systemd Service + +Create `/etc/systemd/system/cvault.service`: + +```ini +[Unit] +Description=CVault Next.js application +After=network.target redis.service +Requires=redis.service + +[Service] +Type=simple +User=www-data +WorkingDirectory=/srv/CVault/vault +EnvironmentFile=/etc/cvault.env +ExecStart=/usr/bin/npm start -- -H 127.0.0.1 -p 3005 +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +```bash +systemctl daemon-reload +systemctl enable cvault.service +systemctl start cvault.service +systemctl status cvault.service --no-pager -l +``` + +### 6. Nginx + SSL + +```bash +# Obtain certificate first +certbot certonly --nginx -d snp.coaxys.ir +``` + +Create `/etc/nginx/sites-available/cvault`: + +```nginx +server { + listen 80; + server_name snp.coaxys.ir; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name snp.coaxys.ir; + + ssl_certificate /etc/letsencrypt/live/snp.coaxys.ir/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/snp.coaxys.ir/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + client_max_body_size 2m; + + location / { + proxy_pass http://127.0.0.1:3005; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + } +} +``` + +```bash +ln -s /etc/nginx/sites-available/cvault /etc/nginx/sites-enabled/cvault +nginx -t && systemctl reload nginx +``` + +### 7. Verify + +```bash +curl -s https://snp.coaxys.ir/api/auth/me | jq . # should return 401 +redis-cli ping # PONG +systemctl status cvault.service --no-pager -l +``` + +### 8. File Permissions + +```bash +# App directory owned by www-data so the service can write data/db.json +chown -R www-data:www-data /srv/CVault/vault +chmod -R 755 /srv/CVault/vault +chmod 700 /srv/CVault/vault/data # restrict db access +``` + +### SSL Renewal (auto via systemd timer) + +```bash +systemctl status certbot.timer # should be active +# Manual renewal test: +certbot renew --dry-run +``` + +## What's Done +- Full frontend with all routes +- Code + Text snippet creation toggle +- CodeMirror editor with 13 language extensions +- Privacy toggle (secret link / password-protected) +- Expiration options (never, 1h, 1d, 1w) +- Tag input +- Snippet library with grid/list view + search +- View snippet with copy, share, edit, delete +- Password gate modal +- Shared snippet minimal view +- Dark/light theme (green/amber palette, zero purple) +- Footer with Coaxys + arsamsabbagh.ir link +- Responsive on mobile/tablet/desktop +- Framer Motion animations (visible-then-animate pattern) +- All fonts/resources served locally (no CDN) +- Production build + nginx proxy (HTTPS, snp.coaxys.ir) +- systemd service enabled for boot +- No demo account or demo snippets seeded in production +- `/raw/[id]` plain-text endpoint for curl/scripts +- CodeMirror theme selector — 6 themes, persisted to localStorage +- Inline share URL bar with copy button on owner view +- Live expiration countdown on cards, owner view, and shared view +- `lib/expiry-format.ts` — shared expiry formatting utility + +## Backend Status +- Local username/password authentication with HTTP-only session cookie +- Fully local CAPTCHA for login and registration, stored in Redis +- Redis-backed session storage, CAPTCHA storage, and rate limits +- Server-side hashed user and snippet passwords +- Persistent server datastore in `data/db.json` +- API endpoints for snippet CRUD and shared snippet access +- Server-side password verification for shared snippets +- Server-side expiration enforcement + +## What's Remaining +- PostgreSQL adapter if this needs multi-instance deployment +- Redis persistence hardening/monitoring if sessions must survive Redis restarts +- Real-time collaboration +- Comments on snippets +- Embed widgets for shared snippets +- Snippet templates