· Nacho Coll · Guides  · 10 min čitanja

IPFS Upload Tokeni: Sigurno Učitavanje s Klijentske Strane Bez Izlaganja API Ključeva

Naučite kako potpisani upload tokeni omogućuju sigurno učitavanje na IPFS iz preglednika i mobilnih aplikacija bez izlaganja vašeg API ključa.

Naučite kako potpisani upload tokeni omogućuju sigurno učitavanje na IPFS iz preglednika i mobilnih aplikacija bez izlaganja vašeg API ključa.

Izgradnja modernih web aplikacija često zahtijeva učitavanje datoteka izravno iz preglednika korisnika u oblačno spremište. Međutim, kada je u pitanju sigurnost IPFS učitavanja, programeri se suočavaju s izazovnom dilemom: kako omogućiti učitavanje s klijentske strane bez izlaganja vaših vrijednih API ključeva potencijalnoj zlouporabi?

Većina IPFS pinning servisa prisiljava vas na neugodan izbor: obrađivati sva učitavanja na strani poslužitelja (stvarajući uska grla i složenost) ili ugraditi vaš API ključ u klijentski kod (sigurnosna noćna mora). IPFS.NINJA rješava to jedinstvenom značajkom koju nijedan drugi pinning servis ne nudi: potpisani upload tokeni.

IPFS Ninja

Problem s Tradicionalnim IPFS API Ključevima

Prilikom izgradnje aplikacija na klijentskoj strani koje trebaju učitavati na IPFS, programeri se obično susreću s nekoliko sigurnosnih izazova:

Rizik Izlaganja API Ključa

Ugrađivanje API ključeva izravno u JavaScript preglednika znači da svatko može pregledati vaš izvorni kod i izvući vaše vjerodajnice. To može dovesti do:

  • Neovlaštenih učitavanja koja troše vašu kvotu pohrane
  • Potencijalne zlouporabe vašeg računa pinning servisa
  • Kršenja sigurnosne usklađenosti u poslovnim okruženjima

Uska Grla na Strani Poslužitelja

Alternativa — usmjeravanje svih učitavanja kroz vaš backend — stvara nekoliko problema:

  • Povećani troškovi propusnosti poslužitelja
  • Veća latencija za korisnike
  • Složeniji infrastrukturni zahtjevi
  • Potencijalne jedinstvene točke kvara

Sigurnost Mobilnih Aplikacija

Mobilne aplikacije suočavaju se sa sličnim izazovima, gdje se API ključevi pohranjeni u paketima aplikacija mogu izvući obrnutim inženjeringom.

Predstavljamo IPFS Upload Tokene

Potpisani upload tokeni IPFS.NINJA pružaju siguran srednji put. Evo kako funkcioniraju:

  1. Poslužitelj generira token: Vaš backend stvara vremenski ograničen, potpisani token koristeći vaš API ključ
  2. Klijent prima token: Token se sigurno prenosi vašoj frontend aplikaciji
  3. Izravno učitavanje: Klijenti učitavaju izravno na IPFS.NINJA koristeći potpisani token
  4. Automatsko istjecanje: Tokeni istječu nakon zadanog trajanja, ograničavajući prozor izloženosti

Ovaj pristup kombinira sigurnost autentifikacije na strani poslužitelja s prednostima izvedbe izravnog klijentskog učitavanja.

Razumijevanje Sigurnosti Upload Tokena

Potpisani upload tokeni koriste kriptografske potpise za osiguravanje autentičnosti bez izlaganja vašeg glavnog API ključa. Svaki token sadrži:

  • Vremensku oznaku istjecanja: Automatska nevažnost nakon navedenog trajanja
  • Ograničenja korištenja: Opcionalna ograničenja broja datoteka ili ukupne veličine
  • Kriptografski potpis: Sprječava neovlašteno mijenjanje ili krivotvorenje
  • Provjera izdavatelja: Povezuje se s vašim autentificiranim računom

Za razliku od API ključeva, upload tokeni su dizajnirani za sigurno ugrađivanje u kod na klijentskoj strani. Čak i ako se izvuku, pružaju ograničen pristup koji automatski istječe.

Implementacija Backenda: Primjer s Express.js

Izgradimo potpuni primjer koji pokazuje kako implementirati sigurna učitavanja na IPFS s klijentske strane. Prvo, evo Express.js backenda koji generira upload tokene:

// server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(express.json());
app.use(cors());

// Your IPFS.NINJA API key (keep this secure on server-side only)
const IPFS_API_KEY = 'bws_1234567890abcdef1234567890abcdef12345678';

// Generate a signed upload token
app.post('/api/generate-upload-token', async (req, res) => {
  try {
    const response = await fetch('https://api.ipfs.ninja/upload-tokens', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': IPFS_API_KEY
      },
      body: JSON.stringify({
        expiresIn: '1h', // Token valid for 1 hour
        maxUploads: 10,  // Optional: limit number of uploads
        maxSizeMB: 50    // Optional: limit total upload size
      })
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const tokenData = await response.json();
    
    res.json({
      success: true,
      uploadToken: tokenData.token,
      expiresAt: tokenData.expiresAt
    });
  } catch (error) {
    console.error('Token generation failed:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to generate upload token'
    });
  }
});

// Optional: Endpoint to verify uploads completed successfully
app.post('/api/verify-upload', async (req, res) => {
  const { cid } = req.body;
  
  try {
    const response = await fetch(`https://api.ipfs.ninja/pins/${cid}`, {
      headers: {
        'X-Api-Key': IPFS_API_KEY
      }
    });

    const pinData = await response.json();
    
    res.json({
      success: true,
      verified: pinData.pinned,
      metadata: pinData
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Verification failed'
    });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Implementacija Frontenda: Sigurno Klijentsko Učitavanje

Sada, evo frontend koda koji sigurno učitava datoteke koristeći potpisani token:

<!DOCTYPE html>
<html>
<head>
    <title>Secure IPFS Upload Demo</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
        .upload-area { border: 2px dashed #ccc; padding: 20px; text-align: center; margin: 20px 0; }
        .upload-area.dragover { border-color: #007cba; background: #f0f8ff; }
        button { background: #007cba; color: white; border: none; padding: 10px 20px; cursor: pointer; }
        .status { margin: 10px 0; padding: 10px; border-radius: 4px; }
        .success { background: #d4edda; color: #155724; }
        .error { background: #f8d7da; color: #721c24; }
        .info { background: #d1ecf1; color: #0c5460; }
    </style>
</head>
<body>
    <h1>Secure IPFS Upload with Signed Tokens</h1>
    
    <div class="upload-area" id="uploadArea">
        <p>Drag & drop files here or click to select</p>
        <input type="file" id="fileInput" multiple style="display: none;">
        <button onclick="document.getElementById('fileInput').click()">Select Files</button>
    </div>

    <div id="status"></div>
    <div id="results"></div>

    <script>
        class SecureIPFSUploader {
            constructor() {
                this.uploadToken = null;
                this.tokenExpiry = null;
                this.setupEventListeners();
            }

            setupEventListeners() {
                const uploadArea = document.getElementById('uploadArea');
                const fileInput = document.getElementById('fileInput');

                uploadArea.addEventListener('dragover', (e) => {
                    e.preventDefault();
                    uploadArea.classList.add('dragover');
                });

                uploadArea.addEventListener('dragleave', () => {
                    uploadArea.classList.remove('dragover');
                });

                uploadArea.addEventListener('drop', (e) => {
                    e.preventDefault();
                    uploadArea.classList.remove('dragover');
                    this.handleFiles(Array.from(e.dataTransfer.files));
                });

                fileInput.addEventListener('change', (e) => {
                    this.handleFiles(Array.from(e.target.files));
                });
            }

            async getUploadToken() {
                if (this.uploadToken && this.tokenExpiry && new Date() < new Date(this.tokenExpiry)) {
                    return this.uploadToken;
                }

                try {
                    this.showStatus('Generating secure upload token...', 'info');
                    const response = await fetch('/api/generate-upload-token', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' }
                    });

                    if (!response.ok) throw new Error(`Failed to generate token: ${response.statusText}`);
                    const data = await response.json();
                    if (!data.success) throw new Error(data.error || 'Token generation failed');

                    this.uploadToken = data.uploadToken;
                    this.tokenExpiry = data.expiresAt;
                    return this.uploadToken;
                } catch (error) {
                    this.showStatus(`Token generation failed: ${error.message}`, 'error');
                    throw error;
                }
            }

            async uploadFile(file) {
                try {
                    const token = await this.getUploadToken();
                    const fileBase64 = await this.fileToBase64(file);
                    this.showStatus(`Uploading ${file.name} to IPFS...`, 'info');

                    const response = await fetch('https://api.ipfs.ninja/upload/new', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': `Signed ${token}`
                        },
                        body: JSON.stringify({
                            content: fileBase64,
                            description: `File uploaded via secure token: ${file.name}`,
                            metadata: {
                                filename: file.name, fileType: file.type,
                                uploadedAt: new Date().toISOString(), uploadMethod: 'signed-token'
                            }
                        })
                    });

                    if (!response.ok) {
                        const errorText = await response.text();
                        throw new Error(`Upload failed: ${response.status} ${errorText}`);
                    }

                    const result = await response.json();
                    return {
                        success: true, filename: file.name, cid: result.cid,
                        size: result.sizeMB, ipfsUri: result.uris.ipfs, httpUrl: result.uris.url
                    };
                } catch (error) {
                    return { success: false, filename: file.name, error: error.message };
                }
            }

            async handleFiles(files) {
                if (files.length === 0) return;
                this.clearResults();
                try {
                    const results = await Promise.all(files.map(file => this.uploadFile(file)));
                    this.displayResults(results);
                    const successful = results.filter(r => r.success).length;
                    if (successful === results.length) {
                        this.showStatus(`✅ Successfully uploaded ${successful} file(s) to IPFS!`, 'success');
                    } else {
                        this.showStatus(`⚠️ Uploaded ${successful}/${results.length} files. Check results below.`, 'error');
                    }
                } catch (error) {
                    this.showStatus(`Upload failed: ${error.message}`, 'error');
                }
            }

            fileToBase64(file) {
                return new Promise((resolve, reject) => {
                    const reader = new FileReader();
                    reader.readAsDataURL(file);
                    reader.onload = () => { resolve(reader.result.split(',')[1]); };
                    reader.onerror = error => reject(error);
                });
            }

            showStatus(message, type) {
                const statusDiv = document.getElementById('status');
                statusDiv.className = `status ${type}`;
                statusDiv.textContent = message;
            }

            displayResults(results) {
                const resultsDiv = document.getElementById('results');
                resultsDiv.innerHTML = '<h3>Upload Results:</h3>' + 
                    results.map(result => `
                        <div class="status ${result.success ? 'success' : 'error'}" style="margin: 10px 0;">
                            <strong>${result.filename}</strong><br>
                            ${result.success ? 
                                `✅ CID: ${result.cid}<br>
                                 📊 Size: ${result.size} MB<br>
                                 🔗 URL: <a href="${result.httpUrl}" target="_blank">${result.httpUrl}</a>` :
                                `❌ Error: ${result.error}`}
                        </div>
                    `).join('');
            }

            clearResults() { document.getElementById('results').innerHTML = ''; }
        }

        const uploader = new SecureIPFSUploader();
    </script>
</body>
</html>

Napredna Sigurnosna Razmatranja

Prilikom implementacije sigurnosti IPFS učitavanja s potpisanim tokenima, razmotrite ove dodatne sigurnosne mjere:

Ograničenja Opsega Tokena

Konfigurirajte tokene s odgovarajućim ograničenjima:

// Generate token with specific constraints
const restrictedToken = await fetch('https://api.ipfs.ninja/upload-tokens', {
  method: 'POST',
  headers: {
    'X-Api-Key': IPFS_API_KEY
  },
  body: JSON.stringify({
    expiresIn: '30m',        // Short expiration
    maxUploads: 5,           // Limited upload count
    maxSizeMB: 10,          // Size restriction
    allowedMimeTypes: ['image/jpeg', 'image/png'], // File type restrictions
    ipWhitelist: ['192.168.1.0/24'] // IP-based access control
  })
});

Validacija Sadržaja

Uvijek validirajte učitani sadržaj na vašem backendu:

app.post('/api/validate-upload', async (req, res) => {
  const { cid } = req.body;
  
  try {
    const response = await fetch(`https://ipfs.ninja/ipfs/${cid}`);
    const contentType = response.headers.get('content-type');
    
    if (!isValidContentType(contentType)) {
      await deleteFromIPFS(cid);
      return res.status(400).json({ error: 'Invalid content type' });
    }
    
    res.json({ success: true, validated: true });
  } catch (error) {
    res.status(500).json({ error: 'Validation failed' });
  }
});

Ograničavanje Brzine

Implementirajte dodatno ograničavanje brzine na vašoj krajnjoj točki za generiranje tokena:

const rateLimit = require('express-rate-limit');

const tokenLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 10, // Limit each IP to 10 token requests per windowMs
  message: 'Too many token requests, please try again later'
});

app.use('/api/generate-upload-token', tokenLimiter);

Prednosti u Odnosu na Tradicionalne Pristupe

Potpisani upload tokeni pružaju nekoliko prednosti u odnosu na alternativne metode sigurnosti IPFS učitavanja:

vs. Proxy na Strani Poslužitelja

  • Izvedba: Izravna učitavanja eliminiraju korištenje propusnosti poslužitelja
  • Skalabilnost: Nema uskih grla poslužitelja tijekom razdoblja visokog učitavanja
  • Trošak: Smanjeni troškovi propusnosti i obrade
  • Korisničko iskustvo: Bolje brzine učitavanja i praćenje napretka

vs. API Ključ na Strani Klijenta

  • Sigurnost: Nema rizika od izvlačenja ili zlouporabe API ključa
  • Usklađenost: Ispunjava zahtjeve sigurnosne revizije
  • Kontrola pristupa: Detaljne dozvole i automatsko istjecanje
  • Praćenje: Bolje praćenje izvora i obrazaca učitavanja

vs. Drugi Pinning Servisi

IPFS.NINJA je trenutno jedini veliki pinning servis koji nudi potpisane upload tokene. Konkurenti poput Pinate zahtijevaju ili proxy na strani poslužitelja ili izlaganje API ključa na strani klijenta, što ovo čini jedinstvenim razlikovnim faktorom.

Za više detalja o usporedbi IPFS.NINJA s drugim servisima, pogledajte naš sveobuhvatni vodič za usporedbu.

Savjeti za Produkcijsko Postavljanje

Prilikom postavljanja potpisanih upload tokena u produkciji:

Konfiguracija Okruženja

Pohranite osjetljivu konfiguraciju na siguran način:

// Use environment variables for production
const config = {
  ipfsApiKey: process.env.IPFS_API_KEY,
  tokenExpiry: process.env.UPLOAD_TOKEN_EXPIRY || '1h',
  maxFileSize: process.env.MAX_FILE_SIZE_MB || 50,
  allowedOrigins: process.env.ALLOWED_ORIGINS?.split(',') || ['localhost:3000']
};

Praćenje i Zapisivanje

Implementirajte sveobuhvatno zapisivanje za sigurnosno praćenje:

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'upload-security.log' })
  ]
});

logger.info('Upload token generated', {
  userId: req.user.id,
  clientIP: req.ip,
  userAgent: req.get('User-Agent'),
  expiresAt: tokenData.expiresAt
});

Rukovanje Greškama

Implementirajte robusno rukovanje greškama koje ne otkriva osjetljive informacije:

app.use((error, req, res, next) => {
  logger.error('Upload token error', {
    error: error.message,
    stack: error.stack,
    userId: req.user?.id,
    endpoint: req.path
  });
  
  res.status(500).json({
    success: false,
    error: 'An internal error occurred. Please try again.'
  });
});

Integracija s Popularnim Okvirima

Potpisani upload tokeni besprijekorno rade s modernim web okvirima. Evo brzih primjera integracije:

React Hook

import { useState, useCallback } from 'react';

export function useSecureIPFSUpload() {
  const [uploading, setUploading] = useState(false);
  const [uploadToken, setUploadToken] = useState(null);

  const getToken = useCallback(async () => {
    if (uploadToken?.expiresAt && new Date() < new Date(uploadToken.expiresAt)) {
      return uploadToken.token;
    }
    const response = await fetch('/api/generate-upload-token', { method: 'POST' });
    const data = await response.json();
    setUploadToken(data);
    return data.uploadToken;
  }, [uploadToken]);

  const uploadFile = useCallback(async (file) => {
    setUploading(true);
    try {
      const token = await getToken();
      // Upload logic here...
    } finally {
      setUploading(false);
    }
  }, [getToken]);

  return { uploadFile, uploading };
}

Vue.js Composable

import { ref } from 'vue';

export function useSecureUpload() {
  const uploading = ref(false);
  const uploadProgress = ref(0);

  const uploadFile = async (file) => {
    uploading.value = true;
    // Implementation here...
  };

  return {
    uploading: readonly(uploading),
    uploadProgress: readonly(uploadProgress),
    uploadFile
  };
}

Zaključak

Potpisani upload tokeni rješavaju kritični sigurnosni izazov u razvoju decentraliziranih aplikacija. Pružajući siguran način omogućavanja izravnih učitavanja s klijentske strane na IPFS bez izlaganja API ključeva, otvaraju nove arhitekturne mogućnosti za moderne web aplikacije.

Bilo da gradite sustav za upravljanje sadržajem, NFT tržište ili bilo koju aplikaciju koja zahtijeva sigurna učitavanja datoteka, upload tokeni IPFS.NINJA pružaju sigurnost i fleksibilnost koja vam je potrebna. Implementacija je jednostavna, sigurnosne prednosti su značajne, a poboljšanja izvedbe su znatna.

Da biste saznali više o osnovama IPFS-a, pogledajte naš vodič o što je IPFS pinning ili istražite naš potpuni API vodič. Za programere koji procjenjuju različite opcije, naša usporedba najboljih IPFS pinning servisa pruža sveobuhvatne uvide.

Jeste li spremni implementirati sigurna učitavanja na IPFS s klijentske strane u svojoj aplikaciji? Kombinacija modernih sigurnosnih praksi i decentralizirane pohrane čini ovaj pristup idealnim za produkcijske aplikacije koje trebaju rasti uz održavanje sigurnosnih standarda.

Spremni za početak pinninga? Stvorite besplatan račun --- 500 datoteka, 1 GB pohrane, namjenski gateway. Kreditna kartica nije potrebna.

Natrag na Blog

Povezani članci

Pogledaj sve članke »