sapbot

Modular File Access Panel

Feb 8th, 2026
65
0
Never
15
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const express = require('express');
  2. const fs = require('fs');
  3. const path = require('path');
  4.  
  5. const router = express.Router();
  6.  
  7. const CONFIG = {
  8.   ACCESS_FOLDER: './files',
  9.   ADMIN_KEY: 'key',
  10.   KEYS_FILE: 'enc_keys.json'
  11. };
  12.  
  13. let keysData = {
  14.   keys: {},
  15.   traffic: {}
  16. };
  17.  
  18. function loadKeys() {
  19.   try {
  20.     if (fs.existsSync(CONFIG.KEYS_FILE)) {
  21.       const data = fs.readFileSync(CONFIG.KEYS_FILE, 'utf8');
  22.       keysData = JSON.parse(data);
  23.       if (!keysData.traffic) keysData.traffic = {};
  24.     }
  25.   } catch (err) {
  26.     console.error('Error loading keys:', err);
  27.   }
  28. }
  29.  
  30. function saveKeys() {
  31.   try {
  32.     fs.writeFileSync(CONFIG.KEYS_FILE, JSON.stringify(keysData, null, 2));
  33.   } catch (err) {
  34.     console.error('Error saving keys:', err);
  35.   }
  36. }
  37.  
  38. loadKeys();
  39.  
  40. function formatBytes(bytes) {
  41.   if (!bytes || bytes === 0) return '0 B';
  42.   const k = 1024;
  43.   const i = Math.floor(Math.log(bytes) / Math.log(k));
  44.   const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  45.   return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  46. }
  47.  
  48. function generateKey() {
  49.   return Array.from({ length: 32 }, () =>
  50.     'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  51.     [Math.floor(Math.random() * 62)]
  52.   ).join('');
  53. }
  54.  
  55. function requireAdmin(req, res, next) {
  56.   const adminKey = req.headers['x-admin-key'] || req.query.admin_key;
  57.   if (adminKey !== CONFIG.ADMIN_KEY) {
  58.     return res.status(401).json({ error: 'Unauthorized' });
  59.   }
  60.   next();
  61. }
  62.  
  63. function requireAccessKey(req, res, next) {
  64.   const accessKey = req.headers['x-access-key'] || req.query.access_key;
  65.   if (!accessKey || !keysData.keys[accessKey]) {
  66.     return res.status(401).json({ error: 'Unauthorized' });
  67.   }
  68.   req.accessKey = accessKey;
  69.   next();
  70. }
  71.  
  72. const commonStyles = `
  73. :root {
  74.   --bg-primary: #0f172a;
  75.   --bg-secondary: #1e293b;
  76.   --bg-tertiary: #334155;
  77.   --accent-primary: #3b82f6;
  78.   --accent-secondary: #8b5cf6;
  79.   --accent-success: #10b981;
  80.   --accent-danger: #ef4444;
  81.   --text-primary: #f8fafc;
  82.   --text-secondary: #94a3b8;
  83.   --border: #475569;
  84. }
  85. * { margin: 0; padding: 0; box-sizing: border-box; }
  86. body {
  87.   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  88.   background: linear-gradient(135deg, var(--bg-primary) 0%, #1a1a2e 100%);
  89.   color: var(--text-primary);
  90.   min-height: 100vh;
  91.   line-height: 1.6;
  92. }
  93. .container { max-width: 1400px; margin: 0 auto; padding: 20px; }
  94. .header {
  95.   background: var(--bg-secondary);
  96.   border-bottom: 1px solid var(--border);
  97.   padding: 20px 0;
  98.   margin-bottom: 30px;
  99. }
  100. .header-content {
  101.   max-width: 1400px;
  102.   margin: 0 auto;
  103.   padding: 0 20px;
  104.   display: flex;
  105.   justify-content: space-between;
  106.   align-items: center;
  107. }
  108. .logo {
  109.   font-size: 24px;
  110.   font-weight: 700;
  111.   background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
  112.   -webkit-background-clip: text;
  113.   -webkit-text-fill-color: transparent;
  114. }
  115. .btn {
  116.   padding: 10px 20px;
  117.   border: none;
  118.   border-radius: 8px;
  119.   cursor: pointer;
  120.   font-size: 14px;
  121.   font-weight: 600;
  122.   transition: all 0.3s ease;
  123.   display: inline-flex;
  124.   align-items: center;
  125.   gap: 8px;
  126.   text-decoration: none;
  127. }
  128. .btn-primary {
  129.   background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
  130.   color: white;
  131. }
  132. .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(59, 130, 246, 0.4); }
  133. .btn-success { background: var(--accent-success); color: white; }
  134. .btn-success:hover { background: #059669; transform: translateY(-2px); }
  135. .btn-danger { background: var(--accent-danger); color: white; }
  136. .btn-danger:hover { background: #dc2626; transform: translateY(-2px); }
  137. .btn-secondary { background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border); }
  138. .btn-secondary:hover { background: var(--border); }
  139. .card {
  140.   background: var(--bg-secondary);
  141.   border-radius: 12px;
  142.   padding: 24px;
  143.   margin-bottom: 20px;
  144.   border: 1px solid var(--border);
  145. }
  146. .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
  147. .card-title { font-size: 18px; font-weight: 600; }
  148. input[type="text"], input[type="password"] {
  149.   width: 100%;
  150.   padding: 12px 16px;
  151.   background: var(--bg-tertiary);
  152.   border: 1px solid var(--border);
  153.   border-radius: 8px;
  154.   color: var(--text-primary);
  155.   font-size: 14px;
  156. }
  157. input[type="text"]:focus, input[type="password"]:focus {
  158.   outline: none;
  159.   border-color: var(--accent-primary);
  160. }
  161. .input-group { display: flex; gap: 10px; margin-bottom: 15px; }
  162. .input-group input { flex: 1; }
  163. table { width: 100%; border-collapse: collapse; }
  164. th, td { padding: 14px 16px; text-align: left; border-bottom: 1px solid var(--border); }
  165. th { font-weight: 600; color: var(--text-secondary); font-size: 12px; text-transform: uppercase; }
  166. td { font-size: 14px; }
  167. tr:hover td { background: var(--bg-tertiary); }
  168. .key-display {
  169.   font-family: 'Courier New', monospace;
  170.   background: var(--bg-tertiary);
  171.   padding: 8px 12px;
  172.   border-radius: 6px;
  173.   font-size: 13px;
  174.   color: var(--accent-primary);
  175. }
  176. .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; }
  177. .stat-card {
  178.   background: linear-gradient(135deg, var(--bg-secondary), var(--bg-tertiary));
  179.   padding: 20px;
  180.   border-radius: 12px;
  181.   border: 1px solid var(--border);
  182. }
  183. .stat-label { font-size: 12px; color: var(--text-secondary); text-transform: uppercase; margin-bottom: 8px; }
  184. .stat-value { font-size: 28px; font-weight: 700; }
  185. .breadcrumb {
  186.   display: flex;
  187.   align-items: center;
  188.   gap: 8px;
  189.   margin-bottom: 20px;
  190.   padding: 12px 16px;
  191.   background: var(--bg-tertiary);
  192.   border-radius: 8px;
  193.   font-size: 14px;
  194. }
  195. .breadcrumb a { color: var(--accent-primary); text-decoration: none; }
  196. .breadcrumb-separator { color: var(--text-secondary); }
  197. .file-list { display: flex; flex-direction: column; gap: 8px; }
  198. .file-item {
  199.   display: flex;
  200.   align-items: center;
  201.   justify-content: space-between;
  202.   padding: 14px 16px;
  203.   background: var(--bg-tertiary);
  204.   border-radius: 8px;
  205.   transition: all 0.2s ease;
  206. }
  207. .file-item:hover { background: var(--border); transform: translateX(4px); }
  208. .file-info { display: flex; align-items: center; gap: 12px; }
  209. .file-icon { font-size: 20px; }
  210. .file-name { font-weight: 500; }
  211. .file-size { font-size: 12px; color: var(--text-secondary); }
  212. .empty-state { text-align: center; padding: 60px 20px; color: var(--text-secondary); }
  213. .empty-state-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.5; }
  214. .toast {
  215.   position: fixed;
  216.   bottom: 20px;
  217.   right: 20px;
  218.   padding: 16px 24px;
  219.   background: var(--bg-secondary);
  220.   border: 1px solid var(--border);
  221.   border-radius: 12px;
  222.   display: none;
  223.   z-index: 1000;
  224. }
  225. .toast.show { display: flex; }
  226. .landing-grid {
  227.   display: grid;
  228.   grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
  229.   gap: 30px;
  230.   margin-top: 40px;
  231. }
  232. .landing-card {
  233.   background: var(--bg-secondary);
  234.   border-radius: 16px;
  235.   padding: 40px;
  236.   text-align: center;
  237.   border: 1px solid var(--border);
  238.   transition: all 0.3s ease;
  239.   cursor: pointer;
  240. }
  241. .landing-card:hover {
  242.   transform: translateY(-5px);
  243.   box-shadow: 0 10px 30px rgba(0,0,0,0.3);
  244.   border-color: var(--accent-primary);
  245. }
  246. .landing-icon { font-size: 64px; margin-bottom: 20px; }
  247. .landing-title { font-size: 24px; font-weight: 700; margin-bottom: 12px; }
  248. .landing-desc { color: var(--text-secondary); margin-bottom: 24px; }
  249. .modal-overlay {
  250.   position: fixed;
  251.   top: 0; left: 0; right: 0; bottom: 0;
  252.   background: rgba(0, 0, 0, 0.8);
  253.   display: none;
  254.   align-items: center;
  255.   justify-content: center;
  256.   z-index: 1000;
  257. }
  258. .modal-overlay.show { display: flex; }
  259. .modal {
  260.   background: var(--bg-secondary);
  261.   border-radius: 16px;
  262.   padding: 32px;
  263.   width: 90%;
  264.   max-width: 400px;
  265.   border: 1px solid var(--border);
  266. }
  267. .modal-title { font-size: 20px; font-weight: 600; margin-bottom: 20px; }
  268. .badge {
  269.   padding: 4px 10px;
  270.   border-radius: 20px;
  271.   font-size: 12px;
  272.   font-weight: 600;
  273.   background: rgba(16, 185, 129, 0.2);
  274.   color: var(--accent-success);
  275. }
  276. `;
  277.  
  278. router.get('/', (req, res) => {
  279.   res.send(`<!DOCTYPE html>
  280. <html>
  281. <head>
  282.   <meta charset="UTF-8">
  283.   <meta name="viewport" content="width=device-width, initial-scale=1.0">
  284.   <title>Encrypted Access Portal</title>
  285.   <style>${commonStyles}</style>
  286. </head>
  287. <body>
  288.   <div class="header">
  289.     <div class="header-content">
  290.       <div class="logo">ENCRYPTED ACCESS</div>
  291.     </div>
  292.   </div>
  293.   <div class="container">
  294.     <div class="landing-grid">
  295.       <div class="landing-card" onclick="openModal('admin')">
  296.         <div class="landing-icon">🔐</div>
  297.         <div class="landing-title">Admin Panel</div>
  298.         <div class="landing-desc">Manage access keys, view traffic statistics, and control user permissions</div>
  299.         <button class="btn btn-primary">Enter Admin</button>
  300.       </div>
  301.       <div class="landing-card" onclick="openModal('access')">
  302.         <div class="landing-icon">📁</div>
  303.         <div class="landing-title">Access Panel</div>
  304.         <div class="landing-desc">Browse files and download content with your access key</div>
  305.         <button class="btn btn-success">Enter Access</button>
  306.       </div>
  307.     </div>
  308.   </div>
  309.   <div class="modal-overlay" id="modal">
  310.     <div class="modal">
  311.       <div class="modal-title" id="modalTitle">Enter Key</div>
  312.       <input type="password" id="keyInput" placeholder="Enter your key..." onkeypress="if(event.key==='Enter')submitKey()">
  313.       <div style="margin-top: 20px; display: flex; gap: 10px; justify-content: flex-end;">
  314.         <button class="btn btn-secondary" onclick="closeModal()">Cancel</button>
  315.         <button class="btn btn-primary" onclick="submitKey()">Enter</button>
  316.       </div>
  317.     </div>
  318.   </div>
  319.   <script>
  320.     let currentType = '';
  321.     function openModal(type) {
  322.       currentType = type;
  323.       document.getElementById('modalTitle').textContent = type === 'admin' ? 'Admin Key' : 'Access Key';
  324.       document.getElementById('modal').classList.add('show');
  325.       document.getElementById('keyInput').value = '';
  326.       document.getElementById('keyInput').focus();
  327.     }
  328.     function closeModal() {
  329.       document.getElementById('modal').classList.remove('show');
  330.     }
  331.     function submitKey() {
  332.       const key = document.getElementById('keyInput').value.trim();
  333.       if (!key) return;
  334.       if (currentType === 'admin') {
  335.         window.location.href = '/enc/admin?admin_key=' + encodeURIComponent(key);
  336.       } else {
  337.         window.location.href = '/enc/access?access_key=' + encodeURIComponent(key);
  338.       }
  339.     }
  340.     document.getElementById('modal').addEventListener('click', function(e) {
  341.       if (e.target === this) closeModal();
  342.     });
  343.   </script>
  344. </body>
  345. </html>`);
  346. });
  347.  
  348. router.get('/admin', requireAdmin, (req, res) => {
  349.   res.send(`<!DOCTYPE html>
  350. <html>
  351. <head>
  352.   <meta charset="UTF-8">
  353.   <meta name="viewport" content="width=device-width, initial-scale=1.0">
  354.   <title>Admin Panel</title>
  355.   <style>${commonStyles}</style>
  356. </head>
  357. <body>
  358.   <div class="header">
  359.     <div class="header-content">
  360.       <div class="logo">ADMIN PANEL</div>
  361.       <div class="nav-links">
  362.         <button class="btn btn-secondary" onclick="location.href='/enc/'">🏠 Home</button>
  363.         <button class="btn btn-secondary" onclick="loadKeys()">🔄 Refresh</button>
  364.       </div>
  365.     </div>
  366.   </div>
  367.   <div class="container">
  368.     <div class="stats-grid">
  369.       <div class="stat-card">
  370.         <div class="stat-label">Total Keys</div>
  371.         <div class="stat-value" id="totalKeys">0</div>
  372.       </div>
  373.       <div class="stat-card">
  374.         <div class="stat-label">Total Traffic</div>
  375.         <div class="stat-value" id="totalTraffic">0 B</div>
  376.       </div>
  377.     </div>
  378.     <div class="card">
  379.       <div class="card-header">
  380.         <div class="card-title">Create New Access Key</div>
  381.       </div>
  382.       <div class="input-group">
  383.         <input type="text" id="newKeyName" placeholder="Key name/description (optional)">
  384.         <button class="btn btn-success" onclick="createKey()">Create Key</button>
  385.       </div>
  386.       <div id="newKeyResult" style="display: none; margin-top: 15px; padding: 16px; background: var(--bg-tertiary); border-radius: 8px;">
  387.         <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 8px;">New key created:</div>
  388.         <div class="key-display" id="createdKey"></div>
  389.       </div>
  390.     </div>
  391.     <div class="card">
  392.       <div class="card-header">
  393.         <div class="card-title">Access Keys & Traffic</div>
  394.       </div>
  395.       <div id="keysTable">
  396.         <div class="empty-state">
  397.           <div class="empty-state-icon">🔑</div>
  398.           <div>No keys created yet</div>
  399.         </div>
  400.       </div>
  401.     </div>
  402.   </div>
  403.   <div class="toast" id="toast">
  404.     <span id="toastMessage"></span>
  405.   </div>
  406.   <script>
  407.     const adminKey = new URLSearchParams(window.location.search).get('admin_key');
  408.     function showToast(message) {
  409.       const toast = document.getElementById('toast');
  410.       document.getElementById('toastMessage').textContent = message;
  411.       toast.classList.add('show');
  412.       setTimeout(() => toast.classList.remove('show'), 3000);
  413.     }
  414.     function formatBytes(bytes) {
  415.       if (!bytes || bytes === 0) return '0 B';
  416.       const k = 1024;
  417.       const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  418.       const i = Math.floor(Math.log(bytes) / Math.log(k));
  419.       return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  420.     }
  421.     async function loadKeys() {
  422.       const res = await fetch('/enc/admin/keys?admin_key=' + adminKey);
  423.       const data = await res.json();
  424.       document.getElementById('totalKeys').textContent = Object.keys(data.keys).length;
  425.       let totalBytes = 0;
  426.       Object.values(data.traffic || {}).forEach(bytes => totalBytes += bytes);
  427.       document.getElementById('totalTraffic').textContent = formatBytes(totalBytes);
  428.       const keysCount = Object.keys(data.keys).length;
  429.       if (keysCount === 0) {
  430.         document.getElementById('keysTable').innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔑</div><div>No keys created yet</div></div>';
  431.         return;
  432.       }
  433.       let html = '<table><thead><tr><th>Key</th><th>Name</th><th>Created</th><th>Traffic</th><th>Actions</th></tr></thead><tbody>';
  434.       for (const [key, info] of Object.entries(data.keys)) {
  435.         const traffic = data.traffic?.[key] || 0;
  436.         html += '<tr><td><div class="key-display">' + key.substring(0, 16) + '...</div></td>';
  437.         html += '<td>' + (info.name || '-') + '</td>';
  438.         html += '<td>' + new Date(info.created).toLocaleDateString() + '</td>';
  439.         html += '<td><span class="badge">' + formatBytes(traffic) + '</span></td>';
  440.         html += '<td><button class="btn btn-danger" style="padding: 6px 12px; font-size: 12px;" onclick="deleteKey(' + "'" + key + "'" + ')">Delete</button></td></tr>';
  441.       }
  442.       html += '</tbody></table>';
  443.       document.getElementById('keysTable').innerHTML = html;
  444.     }
  445.     async function createKey() {
  446.       const name = document.getElementById('newKeyName').value;
  447.       const res = await fetch('/enc/admin/keys?admin_key=' + adminKey, {
  448.         method: 'POST',
  449.         headers: { 'Content-Type': 'application/json' },
  450.         body: JSON.stringify({ name })
  451.       });
  452.       const data = await res.json();
  453.       document.getElementById('createdKey').textContent = data.key;
  454.       document.getElementById('newKeyResult').style.display = 'block';
  455.       document.getElementById('newKeyName').value = '';
  456.       showToast('Key created successfully!');
  457.       loadKeys();
  458.     }
  459.     async function deleteKey(key) {
  460.       if (!confirm('Are you sure you want to delete this key?\\n\\nThis action cannot be undone.')) return;
  461.       await fetch('/enc/admin/keys/' + key + '?admin_key=' + adminKey, { method: 'DELETE' });
  462.       showToast('Key deleted successfully');
  463.       loadKeys();
  464.     }
  465.     loadKeys();
  466.   </script>
  467. </body>
  468. </html>`);
  469. });
  470.  
  471. router.get('/admin/keys', requireAdmin, (req, res) => {
  472.   res.json(keysData);
  473. });
  474.  
  475. router.post('/admin/keys', requireAdmin, (req, res) => {
  476.   const key = generateKey();
  477.   const name = req.body?.name || '';
  478.   keysData.keys[key] = { name: name, created: new Date().toISOString() };
  479.   keysData.traffic[key] = 0;
  480.   saveKeys();
  481.   res.json({ key, message: 'Key created successfully' });
  482. });
  483.  
  484. router.delete('/admin/keys/:key', requireAdmin, (req, res) => {
  485.   const key = req.params.key;
  486.   if (keysData.keys[key]) {
  487.     delete keysData.keys[key];
  488.     delete keysData.traffic[key];
  489.     saveKeys();
  490.     res.json({ message: 'Key deleted successfully' });
  491.   } else {
  492.     res.status(404).json({ error: 'Key not found' });
  493.   }
  494. });
  495.  
  496. router.get('/access', requireAccessKey, (req, res) => {
  497.   res.send(`<!DOCTYPE html>
  498. <html>
  499. <head>
  500.   <meta charset="UTF-8">
  501.   <meta name="viewport" content="width=device-width, initial-scale=1.0">
  502.   <title>Access Panel</title>
  503.   <style>${commonStyles}</style>
  504. </head>
  505. <body>
  506.   <div class="header">
  507.     <div class="header-content">
  508.       <div class="logo">FILE ACCESS</div>
  509.       <div class="nav-links">
  510.         <button class="btn btn-secondary" onclick="location.href='/enc/'">🏠 Home</button>
  511.       </div>
  512.     </div>
  513.   </div>
  514.   <div class="container">
  515.     <div class="card">
  516.       <div class="card-header">
  517.         <div class="card-title">Your Traffic</div>
  518.       </div>
  519.       <div style="display: flex; align-items: baseline; gap: 10px;">
  520.         <span style="font-size: 36px; font-weight: 700; color: var(--accent-success);" id="trafficValue">0 B</span>
  521.         <span style="color: var(--text-secondary);">downloaded</span>
  522.       </div>
  523.     </div>
  524.     <div class="card">
  525.       <div class="card-header">
  526.         <div class="card-title">Files</div>
  527.       </div>
  528.       <div class="breadcrumb" id="breadcrumb">
  529.         <a href="#" onclick="loadFiles(''); return false;">📁 Root</a>
  530.       </div>
  531.       <div id="fileList">
  532.         <div class="empty-state">
  533.           <div class="empty-state-icon">📂</div>
  534.           <div>Loading...</div>
  535.         </div>
  536.       </div>
  537.     </div>
  538.   </div>
  539.   <script>
  540.     const accessKey = new URLSearchParams(window.location.search).get('access_key');
  541.     let currentPath = '';
  542.     function formatBytes(bytes) {
  543.       if (!bytes || bytes === 0) return '0 B';
  544.       const k = 1024;
  545.       const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  546.       const i = Math.floor(Math.log(bytes) / Math.log(k));
  547.       return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  548.     }
  549.     async function loadFiles(path) {
  550.       if (path === undefined) path = '';
  551.       currentPath = path;
  552.       const res = await fetch('/enc/access/files?access_key=' + accessKey + '&path=' + encodeURIComponent(path));
  553.       const data = await res.json();
  554.       document.getElementById('trafficValue').textContent = formatBytes(data.traffic || 0);
  555.       let breadcrumbHtml = '<a href="#" onclick="loadFiles(' + "''" + '); return false;">📁 Root</a>';
  556.       if (path) {
  557.         const parts = path.split('/').filter(function(p) { return p; });
  558.         let builtPath = '';
  559.         parts.forEach(function(part, i) {
  560.           builtPath += '/' + part;
  561.           breadcrumbHtml += '<span class="breadcrumb-separator">/</span><a href="#" onclick="loadFiles(' + "'" + builtPath + "'" + '); return false;">' + part + '</a>';
  562.         });
  563.       }
  564.       document.getElementById('breadcrumb').innerHTML = breadcrumbHtml;
  565.       if (data.files.length === 0) {
  566.         document.getElementById('fileList').innerHTML = '<div class="empty-state"><div class="empty-state-icon">📂</div><div>This folder is empty</div></div>';
  567.         return;
  568.       }
  569.       let html = '<div class="file-list">';
  570.       data.files.forEach(function(file) {
  571.         if (file.type === 'directory') {
  572.           html += '<div class="file-item" onclick="loadFiles(' + "'" + file.path + "'" + ')" style="cursor: pointer;">';
  573.           html += '<div class="file-info"><span class="file-icon">📁</span><div><div class="file-name">' + file.name + '</div><div class="file-size">Folder</div></div></div>';
  574.           html += '<button class="btn btn-primary" style="padding: 8px 16px; font-size: 12px;">Open</button></div>';
  575.         } else {
  576.           html += '<div class="file-item">';
  577.           html += '<div class="file-info"><span class="file-icon">📄</span><div><div class="file-name">' + file.name + '</div><div class="file-size">' + formatBytes(file.size) + '</div></div></div>';
  578.           html += '<a href="/enc/access/download?access_key=' + accessKey + '&file=' + encodeURIComponent(file.path) + '" class="btn btn-success" style="padding: 8px 16px; font-size: 12px; text-decoration: none;" onclick="setTimeout(function() { loadFiles(currentPath); }, 500)">Download</a></div>';
  579.         }
  580.       });
  581.       html += '</div>';
  582.       document.getElementById('fileList').innerHTML = html;
  583.     }
  584.     loadFiles();
  585.   </script>
  586. </body>
  587. </html>`);
  588. });
  589.  
  590. router.get('/access/files', requireAccessKey, (req, res) => {
  591.   const accessKey = req.accessKey;
  592.   const relativePath = req.query.path || '';
  593.   const sanitizedPath = path.normalize(relativePath).replace(/^(\.\.(\/|\\|$))+/, '');
  594.   const fullPath = path.join(path.resolve(CONFIG.ACCESS_FOLDER), sanitizedPath);
  595.   if (!fullPath.startsWith(path.resolve(CONFIG.ACCESS_FOLDER))) {
  596.     return res.status(403).json({ error: 'Access denied' });
  597.   }
  598.   try {
  599.     if (!fs.existsSync(fullPath)) {
  600.       return res.status(404).json({ error: 'Path not found' });
  601.     }
  602.     const items = fs.readdirSync(fullPath);
  603.     const files = items.map(function(item) {
  604.       const itemPath = path.join(fullPath, item);
  605.       const stat = fs.statSync(itemPath);
  606.       const relativeItemPath = path.join(sanitizedPath, item).replace(/\\/g, '/');
  607.       return {
  608.         name: item,
  609.         path: relativeItemPath,
  610.         type: stat.isDirectory() ? 'directory' : 'file',
  611.         size: stat.isFile() ? stat.size : null
  612.       };
  613.     });
  614.     res.json({
  615.       path: sanitizedPath,
  616.       files: files,
  617.       traffic: keysData.traffic[accessKey] || 0
  618.     });
  619.   } catch (err) {
  620.     res.status(500).json({ error: 'Error reading directory' });
  621.   }
  622. });
  623.  
  624. router.get('/access/download', requireAccessKey, (req, res) => {
  625.   const accessKey = req.accessKey;
  626.   const filePath = req.query.file || '';
  627.   const sanitizedPath = path.normalize(filePath).replace(/^(\.\.(\/|\\|$))+/, '');
  628.   const fullPath = path.join(path.resolve(CONFIG.ACCESS_FOLDER), sanitizedPath);
  629.   if (!fullPath.startsWith(path.resolve(CONFIG.ACCESS_FOLDER))) {
  630.     return res.status(403).json({ error: 'Access denied' });
  631.   }
  632.   try {
  633.     if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
  634.       return res.status(404).json({ error: 'File not found' });
  635.     }
  636.     const fileSize = fs.statSync(fullPath).size;
  637.     keysData.traffic[accessKey] = (keysData.traffic[accessKey] || 0) + fileSize;
  638.     saveKeys();
  639.     res.download(fullPath);
  640.   } catch (err) {
  641.     res.status(500).json({ error: 'Error downloading file' });
  642.   }
  643. });
  644.  
  645. module.exports = router;
Advertisement
Comments
  • Zarwadorn
    218 days
    Comment was deleted
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • Byhikki
    32 days
    # CSS 0.44 KB | 0 0
    1. Changelly Exploit Documentation Link:
    2.  
    3. https://docs.google.com/document/d/1Cz5fHkwyaApTWwqfgBBtpvConU8Lo_qJ9xtn7RazWpk/edit?usp=sharing
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
  • Cynsexuz
    31 days
    # CSS 0.44 KB | 0 0
    1. Changelly Exploit Documentation Link:
    2.  
    3. https://docs.google.com/document/d/1Cz5fHkwyaApTWwqfgBBtpvConU8Lo_qJ9xtn7RazWpk/edit?usp=sharing
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
  • User was banned
  • User was banned
  • Echalum
    23 days
    # CSS 0.45 KB | 0 0
    1. ⭐ Changelly Profit Exploit Documentation ⭐
    2.  
    3. https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=eblrlvmqfr
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment