Xây dựng Crypto Paste - Pastebin hiện đại với Cloudflare

Hành trình tạo ra một dịch vụ pastebin tự host sử dụng React, TypeScript, Cloudflare Workers và end-to-end encryption. Từ ý tưởng đến triển khai production.

Xây dựng Crypto Paste - Pastebin hiện đại với Cloudflare

Gần đây mình đã hoàn thành một project khá thú vị - Crypto Paste, một dịch vụ pastebin được xây dựng hoàn toàn trên hệ sinh thái Cloudflare với focus mạnh vào bảo mật và hiệu năng. Trong bài viết này, mình sẽ chia sẻ về quá trình thiết kế, development và những bài học rút ra từ project này.

🎯 Tại sao lại cần một Pastebin khác?

Có rất nhiều dịch vụ pastebin như Pastebin.com, GitHub Gist, hay Hastebin. Tuy nhiên, mình muốn:

🏗️ Architecture Overview

Project được thiết kế theo kiến trúc serverless hiện đại với focus vào security:

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│  React 18 +     │    │  Cloudflare     │    │  Cloudflare     │
│  TypeScript     │────│  Workers +      │────│  KV Store       │
│  (Frontend)     │    │  Hono API       │    │  (Encrypted)    │
└─────────────────┘    └─────────────────┘    └─────────────────┘
          │                       │                       │
          │                       │                       │
    ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
    │  Web Crypto API │    │  AES-256-GCM    │    │  TTL Support    │
    │  (Encryption)   │    │  (Client-side)  │    │  (Auto-expire)  │
    └─────────────────┘    └─────────────────┘    └─────────────────┘

Frontend (React + TypeScript)

Backend (Cloudflare Workers + Hono)

Database (Cloudflare KV)

📁 Project Structure

crypto-paste/
├── src/                    # React application source
│   ├── components/         # React components
│   ├── hooks/             # Custom React hooks
│   ├── lib/               # Utility libraries
│   ├── types/             # TypeScript type definitions
│   └── worker.ts          # Cloudflare Worker với Hono
├── public/                # Static assets
├── index.html             # Entry point
├── package.json           # Dependencies & scripts
├── tsconfig.json          # TypeScript configuration
├── vite.config.ts         # Vite build configuration
├── tailwind.config.js     # Tailwind CSS config
├── wrangler.toml          # Worker deployment config
├── wrangler.toml.example  # Template cho setup
└── DEPLOYMENT.md          # Deployment instructions

🔧 Key Features

1. End-to-End Encryption

Mã hóa AES-256-GCM hoàn toàn client-side, encryption key không bao giờ leave browser:

// Generate encryption key từ URL fragment
const generateKey = async (): Promise<CryptoKey> => {
  const key = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
  );
  return key;
};

// Encrypt content trước khi gửi lên server
const encryptContent = async (content: string, key: CryptoKey): Promise<string> => {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encoded = new TextEncoder().encode(content);
  
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    encoded
  );
  
  return btoa(JSON.stringify({
    iv: Array.from(iv),
    data: Array.from(new Uint8Array(encrypted))
  }));
};

2. Burn After Read

Pastes có thể tự destruct sau khi được đọc một lần:

interface PasteOptions {
  burnAfterRead: boolean;
  expiration: '10m' | '1h' | '1d' | '1w';
}

// Server sẽ delete paste ngay sau first read
const createBurnAfterReadPaste = async (content: string, options: PasteOptions) => {
  const response = await fetch('/api/pastes', {
    method: 'POST',
    body: JSON.stringify({
      content: await encryptContent(content, key),
      burnAfterRead: options.burnAfterRead,
      expiration: options.expiration
    })
  });
};

3. Modern UI với Dark/Light Theme

React components với Tailwind CSS và theme switching:

// Theme context với React
const ThemeContext = createContext<{
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}>({
  theme: 'light',
  toggleTheme: () => {}
});

// Tailwind classes cho responsive design
<div className="min-h-screen bg-white dark:bg-gray-900 transition-colors">
  <div className="max-w-4xl mx-auto px-4 py-8">
    <textarea className="w-full h-96 p-4 border border-gray-300 dark:border-gray-600 
                         bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
                         rounded-lg resize-none focus:ring-2 focus:ring-blue-500" />
  </div>
</div>

4. Syntax Highlighting

Automatic language detection với Prism.js cho 200+ ngôn ngữ:

import Prism from 'prismjs';
import 'prismjs/themes/prism-tomorrow.css';

const highlightCode = (code: string, language: string): string => {
  if (Prism.languages[language]) {
    return Prism.highlight(code, Prism.languages[language], language);
  }
  return code;
};

// Auto-detect language từ content
const detectLanguage = (content: string): string => {
  // Logic để detect ngôn ngữ programming
  if (content.includes('function') && content.includes('{')) return 'javascript';
  if (content.includes('def ') && content.includes(':')) return 'python';
  if (content.includes('#include') && content.includes('int main')) return 'c';
  return 'text';
};

⚡ Cloudflare Workers với Hono Framework

Type-Safe API với Hono

import { Hono } from 'hono';
import { cors } from 'hono/cors';

type Bindings = {
  PASTES_KV: KVNamespace;
  ENVIRONMENT: string;
};

const app = new Hono<{ Bindings: Bindings }>();

// CORS middleware
app.use('*', cors({
  origin: ['https://crypto-paste.pages.dev', 'http://localhost:5173'],
  allowMethods: ['GET', 'POST', 'DELETE'],
  allowHeaders: ['Content-Type'],
}));

// API Routes
app.post('/api/pastes', async (c) => {
  return createPaste(c);
});

app.get('/api/pastes/:id', async (c) => {
  return getPaste(c);
});

app.delete('/api/pastes/:id', async (c) => {
  return deletePaste(c);
});

export default app;

Create Paste với Encryption Support

interface PasteData {
  content: string;        // Encrypted content
  burnAfterRead: boolean;
  expiration: string;
  created: number;
}

async function createPaste(c: Context) {
  try {
    const { content, burnAfterRead, expiration } = await c.req.json();
    
    // Generate unique ID (URL-safe)
    const id = generateSecureId();
    
    // Calculate expiration timestamp
    const expirationMs = getExpirationMs(expiration);
    const ttl = expirationMs ? Math.floor(expirationMs / 1000) : undefined;
    
    const pasteData: PasteData = {
      content,            // Already encrypted client-side
      burnAfterRead,
      expiration,
      created: Date.now()
    };
    
    // Store in KV với TTL
    await c.env.PASTES_KV.put(id, JSON.stringify(pasteData), {
      expirationTtl: ttl
    });
    
    return c.json({ id }, 201);
  } catch (error) {
    console.error('Error creating paste:', error);
    return c.json({ error: 'Failed to create paste' }, 500);
  }
}

function generateSecureId(): string {
  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  let result = '';
  for (let i = 0; i < 16; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return result;
}

Get Paste với Burn After Read

async function getPaste(c: Context) {
  try {
    const id = c.req.param('id');
    
    if (!id || id.length !== 16) {
      return c.json({ error: 'Invalid paste ID' }, 400);
    }
    
    const data = await c.env.PASTES_KV.get(id);
    
    if (!data) {
      return c.json({ error: 'Paste not found or expired' }, 404);
    }
    
    const paste: PasteData = JSON.parse(data);
    
    // Nếu là burn after read, delete ngay lập tức
    if (paste.burnAfterRead) {
      await c.env.PASTES_KV.delete(id);
    }
    
    return c.json({
      content: paste.content,    // Encrypted content
      burnAfterRead: paste.burnAfterRead,
      created: paste.created
    });
  } catch (error) {
    console.error('Error getting paste:', error);
    return c.json({ error: 'Failed to retrieve paste' }, 500);
  }
}

🚀 Development & Deployment

Prerequisites & Setup

# Clone repository
git clone https://github.com/musicsms/crypto-paste.git
cd crypto-paste

# Install dependencies
npm install

# Login to Cloudflare
npx wrangler login

# Create KV namespaces
npx wrangler kv namespace create "PASTES_KV"
npx wrangler kv namespace create "PASTES_KV" --preview

Local Development

# Start Worker (backend)
npm run worker:dev

# Start Frontend (trong terminal khác)
npm run dev

# Access at http://localhost:5173

Production Deployment

# Deploy Worker first
npm run worker:deploy

# Deploy Frontend to Cloudflare Pages
npm run deploy

# Setup custom domain trong Cloudflare Dashboard

Configuration

Copy wrangler.toml.example và update với KV namespace IDs:

name = "crypto-paste-worker"
main = "src/worker.ts"
compatibility_date = "2023-12-01"

[[kv_namespaces]]
binding = "PASTES_KV"
id = "your-production-kv-namespace-id"
preview_id = "your-preview-kv-namespace-id"

[vars]
ENVIRONMENT = "production"

📊 Performance & Security Metrics

Edge Performance

Security Features

// Rate limiting với KV
const rateLimit = async (clientIP: string, limit: number = 10): Promise<boolean> => {
  const key = `rate_limit:${clientIP}`;
  const current = await env.PASTES_KV.get(key);
  
  if (current && parseInt(current) >= limit) {
    return false; // Rate limited
  }
  
  await env.PASTES_KV.put(key, String((parseInt(current || '0') + 1)), {
    expirationTtl: 3600 // 1 hour window
  });
  
  return true;
};

// Content validation
const validatePasteContent = (content: string): boolean => {
  return content.length > 0 && content.length <= 1000000; // 1MB limit
};

🔒 Zero-Knowledge Security Architecture

1. Client-Side Encryption Flow

// Encryption key generation và management
class CryptoManager {
  private key: CryptoKey | null = null;
  
  async generateKey(): Promise<string> {
    this.key = await crypto.subtle.generateKey(
      { name: 'AES-GCM', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );
    
    // Export key để đưa vào URL fragment
    const keyData = await crypto.subtle.exportKey('raw', this.key);
    return btoa(String.fromCharCode(...new Uint8Array(keyData)));
  }
  
  async encrypt(content: string): Promise<string> {
    if (!this.key) throw new Error('Key not generated');
    
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encoded = new TextEncoder().encode(content);
    
    const encrypted = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv },
      this.key,
      encoded
    );
    
    return btoa(JSON.stringify({
      iv: Array.from(iv),
      data: Array.from(new Uint8Array(encrypted))
    }));
  }
  
  async decrypt(encryptedData: string, keyString: string): Promise<string> {
    const keyData = Uint8Array.from(atob(keyString), c => c.charCodeAt(0));
    const key = await crypto.subtle.importKey(
      'raw',
      keyData,
      { name: 'AES-GCM' },
      false,
      ['decrypt']
    );
    
    const { iv, data } = JSON.parse(atob(encryptedData));
    const decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv: new Uint8Array(iv) },
      key,
      new Uint8Array(data)
    );
    
    return new TextDecoder().decode(decrypted);
  }
}

2. Privacy-First Design

3. Data Retention Policies

const EXPIRATION_OPTIONS = {
  '10m': 10 * 60,      // 10 minutes
  '1h': 60 * 60,       // 1 hour  
  '1d': 24 * 60 * 60,  // 1 day
  '1w': 7 * 24 * 60 * 60, // 1 week
} as const;

// Automatic cleanup với KV TTL
const setExpiration = (duration: keyof typeof EXPIRATION_OPTIONS) => {
  return EXPIRATION_OPTIONS[duration];
};

💰 Cost Analysis

Với Cloudflare’s free tier, project này hoàn toàn miễn phí cho moderate usage:

Chỉ pay khi exceed limits, rất cost-effective cho personal projects.

🎯 Current Features & Future Roadmap

✅ Completed Features

🔮 Future Enhancements

Security & Privacy

Developer Experience

Advanced Features

🚀 Try It Out!

Project hiện đang được host và có thể access tại domain của các bạn sau khi deploy.

Source code hoàn chỉnh tại: GitHub - musicsms/crypto-paste

🎓 Key Takeaways

1. Modern React Development

2. Serverless Architecture

3. Security-First Design

4. Developer Experience

🔗 Resources & Documentation

Framework & Tools

Cloudflare Platform

Security & Crypto


Kết luận: Crypto Paste showcase modern approach để build secure, privacy-focused web applications với serverless architecture. Việc kết hợp React + TypeScript + Cloudflare Workers tạo ra một stack rất powerful cho nhiều loại projects.

Project này demonstrate được rằng bạn có thể build production-ready applications với zero server management, strong security, và excellent performance - tất cả với chi phí rất thấp hoặc miễn phí.

Nếu các bạn quan tâm đến privacy-first applications hoặc serverless development, đây là một great starting point! 🚀

Bình luận

0/50 ký tự
Email không được hiển thị công khai
0/500 ký tự

Đang tải bình luận...