feat(auth): add token authentication with SHA-256 hashed storage

This commit is contained in:
2026-03-29 15:54:41 +04:00
parent 6a540eaf81
commit 6f6b7552fc
6 changed files with 187 additions and 13 deletions
+19 -5
View File
@@ -127,6 +127,14 @@ h1 { font-size: 1.4rem; margin-bottom: 16px; color: #fff }
margin-bottom: 16px; position: relative;
}
#header h1 { margin-bottom: 0 }
#logout-btn {
position: absolute; left: 0;
background: none; border: none; cursor: pointer;
color: var(--text-muted); padding: 4px;
display: inline-flex; align-items: center;
transition: color .2s;
}
#logout-btn:hover { color: var(--danger) }
#lang-sw {
position: absolute; right: 0;
display: flex; background: var(--bg-card-hover); border-radius: 6px;
@@ -156,6 +164,7 @@ h1 { font-size: 1.4rem; margin-bottom: 16px; color: #fff }
</symbol>
</svg>
<div id="header">
<button id="logout-btn" title="Logout"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg></button>
<h1 id="title"></h1>
<div id="lang-sw"><span data-l="ru">RU</span><span data-l="en">EN</span></div>
</div>
@@ -218,6 +227,7 @@ const I18N = {
uploadError: 'Ошибка соединения',
deleted: 'Удалено',
perPage: 'На странице:',
logout: 'Выход',
sz: [' Б', ' КБ', ' МБ', ' ГБ'],
},
en: {
@@ -238,6 +248,7 @@ const I18N = {
uploadError: 'Upload error',
deleted: 'Deleted',
perPage: 'Per page:',
logout: 'Logout',
sz: [' B', ' KB', ' MB', ' GB'],
}
};
@@ -254,6 +265,7 @@ function setLang(l) {
$('drop-text').textContent = t().drop;
$('drop-hint').textContent = t().hint;
$('files-title').textContent = t().files;
$('logout-btn').title = t().logout;
loadFiles();
}
@@ -326,7 +338,7 @@ function toPngBlob(blob) {
}
function copyFile(name, type) {
const url = '/dl/' + encodeURIComponent(name);
const url = 'dl/' + encodeURIComponent(name);
if (type === 'text') {
fetch(url).then(r => { if (!r.ok) throw 0; return r.text() }).then(text => copyText(text))
@@ -382,7 +394,7 @@ function upload(fileList) {
};
xhr.onerror = () => { done(); toast(t().uploadError) };
xhr.open('POST', '/upload');
xhr.open('POST', 'upload');
xhr.send(fd);
}
@@ -456,7 +468,7 @@ function renderFiles() {
<span class="fname" data-action="copy" data-name="${esc(f.name)}" data-type="${f.type}" title="${t().copy}">${esc(f.name)}</span>
<div class="meta">
<span class="fsize">${fmtSize(f.size)}</span>
<a class="btn dl" href="/dl/${encodeURIComponent(f.name)}" title="${t().download}"><svg width="14" height="14"><use href="#icon-dl"/></svg></a>
<a class="btn dl" href="dl/${encodeURIComponent(f.name)}" title="${t().download}"><svg width="14" height="14"><use href="#icon-dl"/></svg></a>
<span class="btn del" data-action="del" data-name="${esc(f.name)}" title="${t().delete}"><svg width="14" height="14"><use href="#icon-del"/></svg></span>
</div>
</div>`).join('');
@@ -465,7 +477,7 @@ function renderFiles() {
}
function loadFiles() {
fetch('/files').then(r => r.json()).then(files => {
fetch('files').then(r => r.json()).then(files => {
allFiles = files;
renderFiles();
}).catch(() => {});
@@ -480,7 +492,7 @@ flist.addEventListener('click', e => {
const name = el.dataset.name;
if (action === 'copy') copyFile(name, el.dataset.type);
else if (action === 'del') {
fetch('/del/' + encodeURIComponent(name), { method: 'DELETE' })
fetch('del/' + encodeURIComponent(name), { method: 'DELETE' })
.then(() => { toast(t().deleted); loadFiles() })
.catch(() => {});
}
@@ -532,6 +544,8 @@ document.addEventListener('paste', e => {
}
});
$('logout-btn').addEventListener('click', () => { window.location = 'logout' });
setLang(lang);
let pollId = setInterval(loadFiles, REFRESH_MS);
document.addEventListener('visibilitychange', () => {