/
Coderdev
/
web-nodejs-labs
Обзор
Документация
Войти
/
Coderdev
/
web-nodejs-labs
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
public/lab11/task1/index.html
289 строк
14 KB
Coderdev
1
12 май 2026, 12:34
12 май 2026, 12:34
f5e899f
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Задание 1 — Аутентификация</title> <style> *{box-sizing:border-box;margin:0;padding:0} body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:#f0f2f5;color:#1e293b;min-height:100vh} a.back{display:inline-block;margin:16px 20px 0;color:#2563eb;font-size:0.88rem;text-decoration:none} .page{max-width:960px;margin:0 auto;padding:20px} h1{font-size:1.4rem;font-weight:700;margin-bottom:4px} .sub{color:#64748b;font-size:0.85rem;margin-bottom:24px} /* Две колонки */ .cols{display:grid;grid-template-columns:1fr 1fr;gap:16px} @media(max-width:700px){.cols{grid-template-columns:1fr}} .card{background:#fff;border-radius:14px;border:1px solid #e2e8f0;padding:20px; display:flex;flex-direction:column;gap:12px} .card h2{font-size:1rem;font-weight:700;border-bottom:1px solid #f1f5f9;padding-bottom:8px} label{font-size:0.8rem;color:#64748b;font-weight:500;display:flex;flex-direction:column;gap:3px} input[type=text],input[type=password]{ padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px; font-size:0.9rem;width:100%} input:focus{outline:2px solid #2563eb;border-color:#2563eb} button{padding:9px 16px;border:none;border-radius:8px;cursor:pointer; font-size:0.88rem;font-weight:500;transition:background .15s} .btn{background:#2563eb;color:#fff}.btn:hover{background:#1d4ed8} .btn-red{background:#dc2626;color:#fff}.btn-red:hover{background:#b91c1c} .btn-outline{background:#fff;border:1px solid #e2e8f0;color:#475569} .btn-outline:hover{background:#f8fafc} button:disabled{opacity:.5;cursor:not-allowed} .status{padding:8px 12px;border-radius:8px;font-size:0.83rem;word-break:break-all} .ok{background:#dcfce7;color:#15803d} .err{background:#fee2e2;color:#b91c1c} .info{background:#dbeafe;color:#1d4ed8} /* Токен-панель */ .token-card{background:#fff;border-radius:14px;border:1px solid #e2e8f0;padding:20px;margin-top:16px} .token-card h2{font-size:1rem;font-weight:700;margin-bottom:10px} .token-box{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px; padding:10px;font-size:0.75rem;font-family:monospace;word-break:break-all; color:#475569;max-height:80px;overflow-y:auto} .user-info{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:8px} .ui-badge{background:#f1f5f9;padding:4px 10px;border-radius:8px;font-size:0.82rem} .ui-badge b{color:#2563eb} /* Лог запросов */ .log-card{background:#fff;border-radius:14px;border:1px solid #e2e8f0; padding:20px;margin-top:16px} .log-card h2{font-size:1rem;font-weight:700;margin-bottom:10px; display:flex;justify-content:space-between;align-items:center} .log-list{display:flex;flex-direction:column;gap:6px;max-height:300px;overflow-y:auto} .log-item{background:#f8fafc;border-radius:8px;padding:10px;font-size:0.8rem} .log-item .method{font-weight:700;color:#2563eb;font-size:0.75rem} .log-item .url{color:#475569;font-size:0.75rem;margin-bottom:4px} .log-item pre{background:#1e293b;color:#e2e8f0;border-radius:6px; padding:6px 8px;font-size:0.72rem;overflow-x:auto;margin-top:4px;white-space:pre-wrap} .code-ok{color:#16a34a;font-weight:700} .code-err{color:#dc2626;font-weight:700} </style> </head> <body> <a class="back" href="/lab11/">← Назад</a> <div class="page"> <h1>Задание 1 — Аутентификация JWT</h1> <p class="sub">Регистрация, вход, профиль, смена пароля, выход</p> <!-- Токен-панель --> <div class="token-card" id="tokenPanel" style="display:none"> <h2>🔑 Текущий токен</h2> <div class="token-box" id="tokenBox"></div> <div class="user-info" id="userInfo"></div> <div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap"> <button class="btn-outline" onclick="testProfile()">GET /profile</button> <button class="btn-outline" onclick="testAdmin()">GET /admin</button> <button class="btn-red btn-sm" onclick="doLogout()">Выйти</button> </div> <div id="tokenStatus" style="margin-top:8px"></div> </div> <div class="cols" style="margin-top:16px"> <!-- Регистрация --> <div class="card"> <h2>📝 Регистрация</h2> <label>Логин <input type="text" id="reg-login" placeholder="минимум 3 символа"></label> <label>Пароль <input type="password" id="reg-pass" placeholder="минимум 6 символов"></label> <button class="btn" onclick="doRegister()">Зарегистрироваться</button> <div id="reg-status"></div> </div> <!-- Вход --> <div class="card"> <h2>🔐 Вход</h2> <label>Логин <input type="text" id="login-login" placeholder="ваш логин"></label> <label>Пароль <input type="password" id="login-pass" placeholder="ваш пароль"></label> <button class="btn" onclick="doLogin()">Войти</button> <div id="login-status"></div> </div> <!-- Смена пароля --> <div class="card"> <h2>🔄 Смена пароля</h2> <label>Текущий пароль <input type="password" id="cp-old" placeholder="текущий пароль"></label> <label>Новый пароль <input type="password" id="cp-new" placeholder="новый пароль (мин. 6)"></label> <button class="btn" onclick="doChangePassword()">Сменить пароль</button> <div id="cp-status"></div> </div> <!-- Ручной ввод токена --> <div class="card"> <h2>🛠 Ручной токен</h2> <label>Вставьте JWT токен <input type="text" id="manual-token" placeholder="eyJhbGci..."> </label> <button class="btn-outline" onclick="setManualToken()">Использовать токен</button> <div id="manual-status"></div> </div> </div> <!-- Лог запросов --> <div class="log-card"> <h2> 📋 Лог запросов <button class="btn-outline" style="font-size:0.75rem;padding:3px 10px" onclick="clearLog()">Очистить</button> </h2> <div class="log-list" id="logList"><p style="color:#94a3b8;font-size:0.85rem">Запросов пока нет</p></div> </div> </div> <script> const API = '/api/lab11'; let token = localStorage.getItem('lab11_token') || null; // ── Утилиты ─────────────────────────────────────────────────────────── function setStatus(id, msg, type) { const el = document.getElementById(id); el.className = 'status ' + type; el.textContent = msg; } function addLog(method, url, code, reqBody, resBody) { const list = document.getElementById('logList'); if (list.querySelector('p')) list.innerHTML = ''; const item = document.createElement('div'); item.className = 'log-item'; const codeClass = code >= 200 && code < 300 ? 'code-ok' : 'code-err'; item.innerHTML = ` <div><span class="method">${method}</span> <span class="url">${url}</span> <span class="${codeClass}" style="float:right">${code}</span></div> ${reqBody ? `<pre>REQ: ${JSON.stringify(reqBody, null, 2)}</pre>` : ''} <pre>RES: ${JSON.stringify(resBody, null, 2)}</pre>`; list.prepend(item); } async function apiFetch(method, path, body, useToken = false) { const headers = { 'Content-Type': 'application/json' }; if (useToken && token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${API}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }); const data = await res.json().catch(() => ({})); addLog(method, `/api/lab11${path}`, res.status, body || null, data); return { status: res.status, data }; } // ── Токен-панель ────────────────────────────────────────────────────── function updateTokenPanel() { const panel = document.getElementById('tokenPanel'); if (!token) { panel.style.display = 'none'; return; } panel.style.display = ''; document.getElementById('tokenBox').textContent = token; // Декодируем payload try { const payload = JSON.parse(atob(token.split('.')[1])); const exp = payload.exp ? new Date(payload.exp * 1000).toLocaleTimeString('ru-RU') : '—'; document.getElementById('userInfo').innerHTML = ` <div class="ui-badge">ID: <b>${payload.id}</b></div> <div class="ui-badge">Логин: <b>${payload.login}</b></div> <div class="ui-badge">Роль: <b>${payload.role}</b></div> <div class="ui-badge">Истекает: <b>${exp}</b></div>`; } catch(_) {} } // ── Регистрация ─────────────────────────────────────────────────────── async function doRegister() { const login = document.getElementById('reg-login').value.trim(); const password = document.getElementById('reg-pass').value; if (!login || !password) return setStatus('reg-status', 'Заполните все поля', 'err'); const { status, data } = await apiFetch('POST', '/register', { login, password }); if (status === 201) { setStatus('reg-status', `✓ Зарегистрирован: ${data.login} (id=${data.id})`, 'ok'); } else { setStatus('reg-status', '✕ ' + (data.error || 'Ошибка'), 'err'); } } // ── Вход ────────────────────────────────────────────────────────────── async function doLogin() { const login = document.getElementById('login-login').value.trim(); const password = document.getElementById('login-pass').value; if (!login || !password) return setStatus('login-status', 'Заполните все поля', 'err'); const { status, data } = await apiFetch('POST', '/login', { login, password }); if (status === 200 && data.token) { token = data.token; localStorage.setItem('lab11_token', token); updateTokenPanel(); setStatus('login-status', `✓ Вход выполнен как ${data.user.login} (${data.user.role})`, 'ok'); } else { setStatus('login-status', '✕ ' + (data.error || 'Ошибка'), 'err'); } } // ── Выход ───────────────────────────────────────────────────────────── async function doLogout() { await apiFetch('POST', '/logout', null, true); token = null; localStorage.removeItem('lab11_token'); updateTokenPanel(); setStatus('login-status', 'Вы вышли из системы', 'info'); } // ── Смена пароля ────────────────────────────────────────────────────── async function doChangePassword() { if (!token) return setStatus('cp-status', 'Сначала войдите в систему', 'err'); const oldPassword = document.getElementById('cp-old').value; const newPassword = document.getElementById('cp-new').value; if (!oldPassword || !newPassword) return setStatus('cp-status', 'Заполните оба поля', 'err'); const { status, data } = await apiFetch('POST', '/change-password', { oldPassword, newPassword }, true); if (status === 200) { setStatus('cp-status', '✓ ' + data.message, 'ok'); document.getElementById('cp-old').value = ''; document.getElementById('cp-new').value = ''; } else { setStatus('cp-status', '✕ ' + (data.error || 'Ошибка'), 'err'); } } // ── Тест /profile ───────────────────────────────────────────────────── async function testProfile() { const { status, data } = await apiFetch('GET', '/profile', null, true); const el = document.getElementById('tokenStatus'); el.className = 'status ' + (status === 200 ? 'ok' : 'err'); el.textContent = status === 200 ? `✓ Профиль: ${data.login} (${data.role})` : '✕ ' + (data.error || 'Ошибка'); } // ── Тест /admin ─────────────────────────────────────────────────────── async function testAdmin() { const { status, data } = await apiFetch('GET', '/admin', null, true); const el = document.getElementById('tokenStatus'); el.className = 'status ' + (status === 200 ? 'ok' : 'err'); el.textContent = status === 200 ? '✓ ' + data.message : '✕ ' + (data.error || 'Ошибка'); } // ── Ручной токен ────────────────────────────────────────────────────── function setManualToken() { const val = document.getElementById('manual-token').value.trim(); if (!val) return setStatus('manual-status', 'Вставьте токен', 'err'); token = val; localStorage.setItem('lab11_token', token); updateTokenPanel(); setStatus('manual-status', '✓ Токен установлен', 'ok'); } function clearLog() { document.getElementById('logList').innerHTML = '<p style="color:#94a3b8;font-size:0.85rem">Запросов пока нет</p>'; } // ── Инициализация ───────────────────────────────────────────────────── updateTokenPanel(); </script> </body> </html>