· Nacho Coll · Guides  · 8 মিনিট পড়ুন

IPFS আপলোড টোকেন: API কী প্রকাশ না করে নিরাপদ ক্লায়েন্ট-সাইড আপলোড

জানুন কিভাবে স্বাক্ষরিত আপলোড টোকেন আপনাকে API কী প্রকাশ না করে ব্রাউজার এবং মোবাইল অ্যাপ থেকে নিরাপদে IPFS-এ আপলোড করতে দেয়।

জানুন কিভাবে স্বাক্ষরিত আপলোড টোকেন আপনাকে API কী প্রকাশ না করে ব্রাউজার এবং মোবাইল অ্যাপ থেকে নিরাপদে IPFS-এ আপলোড করতে দেয়।

আধুনিক ওয়েব অ্যাপ্লিকেশন তৈরি করতে প্রায়ই ব্যবহারকারীদের ব্রাউজার থেকে সরাসরি ক্লাউড স্টোরেজে ফাইল আপলোড করতে হয়। তবে, IPFS আপলোড নিরাপত্তার ক্ষেত্রে, ডেভেলপাররা একটি কঠিন দ্বিধার মুখোমুখি হন: মূল্যবান API কী প্রকাশ না করে কিভাবে ক্লায়েন্ট-সাইড আপলোডের অনুমতি দেবেন?

বেশিরভাগ IPFS পিনিং পরিষেবা আপনাকে একটি অস্বস্তিকর পছন্দ করতে বাধ্য করে: হয় সমস্ত আপলোড সার্ভার-সাইডে পরিচালনা করুন (বাধা এবং জটিলতা তৈরি করে) অথবা ক্লায়েন্ট কোডে আপনার API কী এম্বেড করুন (একটি নিরাপত্তা দুঃস্বপ্ন)। IPFS.NINJA এটি একটি অনন্য বৈশিষ্ট্য দিয়ে সমাধান করে যা অন্য কোনো পিনিং পরিষেবায় নেই: স্বাক্ষরিত আপলোড টোকেন

IPFS Ninja

প্রচলিত IPFS API কী-এর সমস্যা

IPFS-এ আপলোড করতে হয় এমন ক্লায়েন্ট-সাইড অ্যাপ্লিকেশন তৈরি করতে গিয়ে ডেভেলপাররা সাধারণত বেশ কয়েকটি নিরাপত্তা চ্যালেঞ্জের সম্মুখীন হন:

API কী প্রকাশের ঝুঁকি

ব্রাউজার JavaScript-এ সরাসরি API কী এম্বেড করার মানে হল যে কেউ আপনার সোর্স কোড দেখে আপনার credentials বের করতে পারে। এর ফলে হতে পারে:

  • অননুমোদিত আপলোড আপনার স্টোরেজ কোটা ব্যবহার করছে
  • আপনার পিনিং পরিষেবা অ্যাকাউন্টের সম্ভাব্য অপব্যবহার
  • এন্টারপ্রাইজ পরিবেশে নিরাপত্তা সম্মতি লঙ্ঘন

সার্ভার-সাইড বাধা

বিকল্প — সমস্ত আপলোড আপনার ব্যাকএন্ডের মাধ্যমে রাউট করা — বেশ কয়েকটি সমস্যা তৈরি করে:

  • সার্ভার ব্যান্ডউইডথ খরচ বৃদ্ধি
  • ব্যবহারকারীদের জন্য বেশি বিলম্ব
  • আরো জটিল পরিকাঠামো প্রয়োজনীয়তা
  • সম্ভাব্য একক ব্যর্থতার বিন্দু

মোবাইল অ্যাপ নিরাপত্তা

মোবাইল অ্যাপ্লিকেশনগুলিও একই ধরনের চ্যালেঞ্জের মুখোমুখি হয়, যেখানে অ্যাপ বান্ডলে সংরক্ষিত API কী রিভার্স ইঞ্জিনিয়ারিংয়ের মাধ্যমে বের করা যায়।

IPFS আপলোড টোকেনের পরিচয়

IPFS.NINJA-এর স্বাক্ষরিত আপলোড টোকেন একটি নিরাপদ মধ্যবর্তী সমাধান প্রদান করে। এটি কিভাবে কাজ করে:

  1. সার্ভার টোকেন তৈরি করে: আপনার ব্যাকএন্ড আপনার API কী ব্যবহার করে একটি সময়-সীমিত, স্বাক্ষরিত টোকেন তৈরি করে
  2. ক্লায়েন্ট টোকেন গ্রহণ করে: টোকেনটি নিরাপদে আপনার ফ্রন্টএন্ড অ্যাপ্লিকেশনে প্রেরণ করা হয়
  3. সরাসরি আপলোড: ক্লায়েন্ট স্বাক্ষরিত টোকেন ব্যবহার করে সরাসরি IPFS.NINJA-তে আপলোড করে
  4. স্বয়ংক্রিয় মেয়াদ উত্তীর্ণ: টোকেন একটি নির্দিষ্ট সময়ের পর মেয়াদ উত্তীর্ণ হয়

এই পদ্ধতি সার্ভার-সাইড প্রমাণীকরণের নিরাপত্তাকে সরাসরি ক্লায়েন্ট আপলোডের কর্মক্ষমতা সুবিধার সাথে একত্রিত করে।

আপলোড টোকেন নিরাপত্তা বোঝা

স্বাক্ষরিত আপলোড টোকেন আপনার প্রধান API কী প্রকাশ না করে সত্যতা নিশ্চিত করতে ক্রিপ্টোগ্রাফিক স্বাক্ষর ব্যবহার করে। প্রতিটি টোকেনে থাকে:

  • মেয়াদ উত্তীর্ণের টাইমস্ট্যাম্প: নির্দিষ্ট সময়ের পর স্বয়ংক্রিয় অবৈধকরণ
  • ব্যবহারের সীমাবদ্ধতা: ফাইল সংখ্যা বা মোট আকারের ঐচ্ছিক সীমা
  • ক্রিপ্টোগ্রাফিক স্বাক্ষর: পরিবর্তন বা জালিয়াতি প্রতিরোধ
  • জারিকারী যাচাইকরণ: আপনার প্রমাণীকৃত অ্যাকাউন্টে ফিরে লিঙ্ক করে

ব্যাকএন্ড বাস্তবায়ন: Express.js উদাহরণ

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

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

const IPFS_API_KEY = 'bws_1234567890abcdef1234567890abcdef12345678';

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',
        maxUploads: 10,
        maxSizeMB: 50
      })
    });

    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'
    });
  }
});

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');
});

ফ্রন্টএন্ড বাস্তবায়ন: নিরাপদ ক্লায়েন্ট আপলোড

<!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 s = document.getElementById('status'); s.className = `status ${type}`; s.textContent = message; }
            displayResults(results) {
                document.getElementById('results').innerHTML = '<h3>Upload Results:</h3>' + results.map(r => `<div class="status ${r.success ? 'success' : 'error'}" style="margin: 10px 0;"><strong>${r.filename}</strong><br>${r.success ? `✅ CID: ${r.cid}<br>📊 Size: ${r.size} MB<br>🔗 URL: <a href="${r.httpUrl}" target="_blank">${r.httpUrl}</a>` : `❌ Error: ${r.error}`}</div>`).join('');
            }
            clearResults() { document.getElementById('results').innerHTML = ''; }
        }
        const uploader = new SecureIPFSUploader();
    </script>
</body>
</html>

উন্নত নিরাপত্তা বিবেচনা

টোকেন স্কোপ সীমাবদ্ধতা

const restrictedToken = await fetch('https://api.ipfs.ninja/upload-tokens', {
  method: 'POST',
  headers: { 'X-Api-Key': IPFS_API_KEY },
  body: JSON.stringify({
    expiresIn: '30m', maxUploads: 5, maxSizeMB: 10,
    allowedMimeTypes: ['image/jpeg', 'image/png'],
    ipWhitelist: ['192.168.1.0/24']
  })
});

বিষয়বস্তু যাচাইকরণ

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' }); }
});

রেট লিমিটিং

const rateLimit = require('express-rate-limit');
const tokenLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, max: 10,
  message: 'Too many token requests, please try again later'
});
app.use('/api/generate-upload-token', tokenLimiter);

প্রচলিত পদ্ধতির উপর সুবিধা

সার্ভার-সাইড প্রক্সির তুলনায়

  • কর্মক্ষমতা: সরাসরি আপলোড সার্ভার ব্যান্ডউইডথ ব্যবহার দূর করে
  • স্কেলেবিলিটি: উচ্চ আপলোডের সময় কোনো সার্ভার বাধা নেই
  • খরচ: কম ব্যান্ডউইডথ এবং প্রক্রিয়াকরণ খরচ
  • ব্যবহারকারী অভিজ্ঞতা: দ্রুত আপলোড গতি এবং অগ্রগতি ট্র্যাকিং

ক্লায়েন্ট-সাইড API কী-এর তুলনায়

  • নিরাপত্তা: API কী নিষ্কাশন বা অপব্যবহারের কোনো ঝুঁকি নেই
  • সম্মতি: নিরাপত্তা নিরীক্ষা প্রয়োজনীয়তা পূরণ করে
  • অ্যাক্সেস নিয়ন্ত্রণ: সূক্ষ্ম অনুমতি এবং স্বয়ংক্রিয় মেয়াদ উত্তীর্ণ

অন্যান্য পিনিং পরিষেবার তুলনায়

IPFS.NINJA বর্তমানে স্বাক্ষরিত আপলোড টোকেন প্রদানকারী একমাত্র প্রধান পিনিং পরিষেবা।

আরো বিস্তারিত জানতে আমাদের ব্যাপক তুলনা গাইড দেখুন।

প্রোডাকশন ডিপ্লয়মেন্ট টিপস

পরিবেশ কনফিগারেশন

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']
};

মনিটরিং এবং লগিং

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 });

ত্রুটি পরিচালনা

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.' });
});

জনপ্রিয় ফ্রেমওয়ার্কের সাথে ইন্টিগ্রেশন

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 };
}

উপসংহার

স্বাক্ষরিত আপলোড টোকেন বিকেন্দ্রীকৃত অ্যাপ্লিকেশন ডেভেলপমেন্টে একটি গুরুত্বপূর্ণ নিরাপত্তা চ্যালেঞ্জ সমাধান করে। API কী প্রকাশ না করে IPFS-এ সরাসরি ক্লায়েন্ট-সাইড আপলোড সক্ষম করার একটি নিরাপদ উপায় প্রদান করে, তারা আধুনিক ওয়েব অ্যাপ্লিকেশনের জন্য নতুন আর্কিটেকচারাল সম্ভাবনা উন্মুক্ত করে।

IPFS মৌলিক বিষয় সম্পর্কে আরো জানতে, IPFS পিনিং কী আমাদের গাইড দেখুন বা আমাদের সম্পূর্ণ API টিউটোরিয়াল দেখুন। বিভিন্ন বিকল্প মূল্যায়নকারী ডেভেলপারদের জন্য, আমাদের সেরা IPFS পিনিং পরিষেবা তুলনা বিস্তৃত অন্তর্দৃষ্টি প্রদান করে।

পিনিং শুরু করতে প্রস্তুত? বিনামূল্যে অ্যাকাউন্ট তৈরি করুন — ৫০০টি ফাইল, ১ GB স্টোরেজ, ডেডিকেটেড গেটওয়ে। ক্রেডিট কার্ড প্রয়োজন নেই।

ব্লগে ফিরুন

সম্পর্কিত নিবন্ধ

সব নিবন্ধ দেখুন »
IPFS পিনিং কী? ২০২৬ সালে আপনার যা জানা দরকার

IPFS পিনিং কী? ২০২৬ সালে আপনার যা জানা দরকার

জানুন IPFS পিনিং কী, কেন পিনিং ছাড়া ফাইল হারিয়ে যায়, রিমোট পিনিং পরিষেবা কিভাবে কাজ করে, এবং কিভাবে আপনার প্রথম ফাইল পিন করবেন। উদাহরণসহ সম্পূর্ণ গাইড।