Files
fission-console/doc/structure/login-flow.md
T

561 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Логин в Fission Console — Полное описание логики
**Дата:** май 2026
**Версия консоли:** v1.3.20+
**Статус:** Рабочая конфигурация (необходимо восстановить функции логина в app.js)
---
## 1. Архитектура логина
```
Пользователь вводит токен
[login.html] форма (вводится токен + стенд)
[onclick="doLogin()"] → JavaScript функция
POST /console/api/auth {token, env}
API Cloud: проверка токена (валидность, срок действия)
Успех? → localStorage сохранение → showMain()
Ошибка? → errEl.textContent → showError()
```
---
## 2. Первоначальная загрузка (инициализация)
### 2.1 Загрузка HTML и JS
1. **index.html** загружается (основной SPA файл)
2. **css/styles.css** импортируется через `<link>` в `<head>`
3. **app.js** загружается в конце `<body>` через `<script src="js/app.js"></script>`
4. **login.html** подгружается динамически:
```javascript
// app.js, строка 433
fetch('templates/modals/login.html')
.then(r => r.text())
.then(html => {
document.body.insertAdjacentHTML('afterbegin', html);
});
```
### 2.2 Инициализация приложения
При загрузке app.js (строка 790-794):
```javascript
initializeEventListeners(); // Event delegation для data-* атрибутов
checkAuth(); // Проверка авторизации
```
**checkAuth()** функция (строка 648-658):
```javascript
function checkAuth() {
var storedToken = localStorage.getItem('auth_token');
if (!storedToken) {
showLoginOverlay(); // Показать форму логина
return;
}
setUserAvatar(storedToken); // Установить аватар с email
hideLoginOverlay(); // Скрыть форму логина
reloadAll(); // Загрузить данные
}
```
---
## 3. HTML структура логина
### 3.1 Форма входа (templates/modals/login.html)
```html
<div id="login-overlay">
<!-- Выбор стенда -->
<select id="l-env">
<option value="dev">Dev</option>
<option value="test" selected>Test</option>
<option value="prod">Prod</option>
</select>
<!-- Ввод токена -->
<textarea id="l-token" placeholder="Введите токен..."></textarea>
<!-- Вывод ошибок -->
<div id="l-error" style="display:none; color:#ff6b6b;">
<!-- Здесь показываются ошибки валидации -->
</div>
<!-- Кнопка входа -->
<button id="l-btn" onclick="doLogin()">Войти</button>
</div>
```
### 3.2 Интеграция с UI
- **Стенды:** dev/test/prod (соответствуют разным окружениям облака)
- **Токен:** JWT токен пользователя из личного кабинета
- **Email:** извлекается из JWT при успешном логине
---
## 4. Процесс логина (подробно)
### 4.1 Нажатие кнопки "Войти"
**HTML:**
```html
<button id="l-btn" onclick="doLogin()">Войти</button>
```
**Функция doLogin()** (из auth.js, строка 5):
```javascript
async function doLogin() {
// 1. Получить значения из формы
var btn = document.getElementById('l-btn');
var errEl = document.getElementById('l-error');
var token = (document.getElementById('l-token').value || '').trim();
var env = document.getElementById('l-env').value;
// 2. Базовая валидация (не пусто)
if (!token) {
errEl.textContent = 'Введите токен';
errEl.style.display = 'block';
return;
}
// 3. Заблокировать кнопку, очистить ошибки
btn.disabled = true;
errEl.style.display = 'none';
try {
// 4. ОТПРАВИТЬ НА ПРОВЕРКУ К API ОБЛАКА
const res = await fetch(API_BASE + '/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: token, env: env })
});
// 5. Обработать ответ
if (!res.ok) {
const d = await res.json().catch(() => ({}));
throw new Error(d.error || 'Ошибка входа');
}
// 6. УСПЕХ: СОХРАНИТЬ В localStorage
localStorage.setItem('auth_token', token);
localStorage.setItem('auth_env', env);
// 7. ПОКАЗАТЬ АВАТАР С EMAIL
setUserAvatar(token);
// 8. Скрыть форму логина
hideLoginOverlay();
// 9. ПРОВЕРИТЬ СТАТУС NAMESPACE
// Если namespace не готов (not ready) → показать init overlay
// и ждать пока инициализируется
try {
var sr = await fetch(API_BASE + '/ns/status',
{ headers: { 'X-Auth-Token': token, 'X-Auth-Env': env } });
var sd = await sr.json();
if (!sd.ready) {
pollNSStatus(); // Начать полинг (ждать готовности)
return;
}
} catch (_) { }
// 10. ЗАГРУЗИТЬ ОСНОВНОЙ ИНТЕРФЕЙС
reloadAll();
} catch (e) {
// Ошибка: показать сообщение
errEl.textContent = e.message;
errEl.style.display = 'block';
} finally {
// Разблокировать кнопку
btn.disabled = false;
}
}
```
---
## 5. Сохранение состояния в браузер
### 5.1 localStorage: Что сохраняется
```javascript
// В момент успешного логина:
localStorage.setItem('auth_token', 'eyJhbGc...'); // JWT токен пользователя
localStorage.setItem('auth_env', 'test'); // Выбранное окружение
```
**Как долго сохраняется:**
- localStorage хранит данные до явного удаления (`localStorage.removeItem()`)
- Не удаляется при закрытии браузера (в отличие от sessionStorage)
- Восстанавливается при перезагрузке страницы
### 5.2 Восстановление сессии при перезагрузке
**Сценарий:**
1. Пользователь вошёл (токен в localStorage)
2. Закрыл браузер
3. Открыл консоль снова
**Процесс:**
1. Загружается app.js
2. Вызывается `checkAuth()` (строка 794)
3. Функция проверяет `localStorage.getItem('auth_token')`
4. Если токен есть → автоматический вход (setUserAvatar + hideLoginOverlay + reloadAll)
5. Если токена нет → показать форму логина
---
## 6. Использование токена в API запросах
### 6.1 authHeaders() функция
Каждый API запрос содержит авторизационные заголовки:
```javascript
function authHeaders() {
return {
'X-Auth-Token': localStorage.getItem('auth_token') || '',
'X-Auth-Env': localStorage.getItem('auth_env') || 'test'
};
}
```
### 6.2 Типы API запросов
**1. getJSON(url)** — GET с авторизацией
```javascript
async function getJSON(url) {
const r = await fetch(url, { headers: authHeaders() });
if (r.status === 401) { doLogout(); /* сессия истекла */ }
// ...
}
```
**2. requestJSON(url, method, body)** — POST/PUT/DELETE с авторизацией
```javascript
async function requestJSON(url, method, body) {
const r = await fetch(url, {
method: method,
headers: Object.assign(
{ 'Content-Type': 'application/json' },
authHeaders()
),
body: body ? JSON.stringify(body) : undefined
});
if (r.status === 401) { doLogout(); /* сессия истекла */ }
// ...
}
```
---
## 7. Проверка токена на API облака
### 7.1 Эндпоинт /console/api/auth
**Запрос:**
```http
POST /console/api/auth
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"env": "test"
}
```
**Ответ (успех — 200):**
```json
{
"success": true
}
```
**Ответ (ошибка — 400/401):**
```json
{
"error": "Invalid token" | "Token expired" | "Unknown environment"
}
```
### 7.2 Что проверяет API облака
1. **Валидность JWT токена** — подпись, структура
2. **Срок действия токена** — не истёк ли
3. **Существование пользователя** — есть ли в базе
4. **Окружение** — существует ли такой env (dev/test/prod)
5. **Права доступа** — может ли этот пользователь войти в это окружение
---
## 8. Извлечение email из JWT
### 8.1 jwtEmail() функция
```javascript
function jwtEmail(token) {
try {
// JWT формат: header.payload.signature
// payload — base64url кодирован
var payload = JSON.parse(
atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))
);
// Попытаться вытащить email или sub (subject = идентификатор)
return payload.email || payload.sub || null;
} catch (_) {
return null;
}
}
```
### 8.2 setUserAvatar() функция
```javascript
function setUserAvatar(token) {
var el = document.getElementById('user-avatar');
if (!el) return;
var email = token ? jwtEmail(token) : null;
if (email) {
// Показать email в аватаре (правый верхний угол)
el.textContent = email;
el.title = email;
el.style.fontSize = '0.7rem';
el.style.minWidth = '60px';
el.style.padding = '0 8px';
} else {
// Вернуть в дефолтное состояние (буква "N")
el.textContent = 'N';
el.title = '';
el.style.fontSize = '';
el.style.minWidth = '';
el.style.padding = '';
}
}
```
---
## 9. Логаут
### 9.1 doLogout() функция
```javascript
function doLogout() {
// 1. Удалить токен и окружение из localStorage
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_env');
// 2. Очистить форму логина
try { document.getElementById('l-token').value = ''; } catch (_) { }
// 3. Вернуть аватар в дефолт
setUserAvatar(null);
// 4. Показать форму логина
showLoginOverlay();
}
```
### 9.2 Автоматический логаут
Если API вернёт **401 (Unauthorized)**:
```javascript
// В getJSON()
if (r.status === 401) { doLogout(); throw new Error('Сессия истекла'); }
// В requestJSON()
if (r.status === 401) { doLogout(); throw new Error('Сессия истекла'); }
```
**Причины 401:**
- Токен истёк
- Токен был отозван (revoked) в облаке
- Пользователь был заблокирован
---
## 10. Показ/скрытие интерфейса
### 10.1 showLoginOverlay()
```javascript
function showLoginOverlay() {
document.getElementById('login-overlay').style.display = 'flex';
}
```
### 10.2 hideLoginOverlay()
```javascript
function hideLoginOverlay() {
document.getElementById('login-overlay').style.display = 'none';
}
```
---
## 11. Проверка статуса namespace
### 11.1 pollNSStatus() функция (строка 600)
После логина API проверяет готовность namespace пользователя:
```javascript
try {
var sr = await fetch(API_BASE + '/ns/status',
{ headers: { 'X-Auth-Token': token, 'X-Auth-Env': env } });
var sd = await sr.json();
if (!sd.ready) {
pollNSStatus(); // ← Начать полинг
return;
}
} catch (_) { }
```
**pollNSStatus()** (строка 600):
```javascript
function pollNSStatus() {
// Показать инит-оверлей (ждём создания namespace)
document.getElementById('init-overlay').style.display = 'flex';
// Полинг каждые 5 сек
const pollInterval = setInterval(async function() {
try {
var statusResp = await fetch(API_BASE + '/ns/status',
{ headers: authHeaders() });
var debugResp = await fetch(API_BASE + '/ns/debug',
{ headers: authHeaders() });
var statusData = await statusResp.json();
var debugData = await debugResp.json();
// Обновить UI с информацией о статусе
// Когда ready === true → загрузить основной UI
if (statusData.ready) {
clearInterval(pollInterval);
document.getElementById('init-overlay').style.display = 'none';
reloadAll(); // Загрузить данные
}
} catch (_) { }
}, 5000);
}
```
---
## 12. Схема данных в браузере
```
localStorage
├── auth_token: "eyJhbGc..." ← JWT токен пользователя
├── auth_env: "test" ← Выбранное окружение
└── (другие данные могут быть)
DOM элементы:
├── #login-overlay ← Форма логина
│ ├── #l-env ← Dropdown стендов
│ ├── #l-token ← Textarea с токеном
│ ├── #l-error ← Div для ошибок
│ └── #l-btn ← Кнопка "Войти"
├── #init-overlay ← Ожидание инициализации NS
└── #user-avatar ← Отображение email в navbar
```
---
## 13. Полный flow диаграмма
```
START
[Загрузка index.html]
[Загрузка app.js]
[Загрузка login.html через fetch]
[initializeEventListeners() — event delegation]
[checkAuth()] — Есть ли токен в localStorage?
├─ НЕТ → [showLoginOverlay()]
│ ↓
│ [Пользователь вводит токен + env]
│ ↓
│ [onclick="doLogin()"]
│ ↓
│ [POST /console/api/auth {token, env}]
│ ↓
│ [API облака проверяет токен]
│ ├─ ОШИБКА → [errEl.display=block; errEl.textContent=...]
│ │ ↓
│ │ [Пользователь вводит заново]
│ │
│ └─ OK → [localStorage.setItem(auth_token, token)]
│ [localStorage.setItem(auth_env, env)]
│ ↓
│ [setUserAvatar(token)] — извлечь email из JWT
│ ↓
│ [hideLoginOverlay()]
│ ↓
│ [Проверить /ns/status]
│ ├─ NOT READY → [pollNSStatus()] — ждать 5сек * N
│ └─ READY → [reloadAll()]
└─ ДА → [setUserAvatar(token)]
[hideLoginOverlay()]
[reloadAll()] — загрузить основной UI
[Пользователь работает с консолью]
[Все запросы содержат authHeaders(): X-Auth-Token + X-Auth-Env]
[Если API вернёт 401]
├─ → [doLogout()] — удалить токены, вернуть на логин
[При нажатии "Выход"]
├─ → [doLogout()]
END
```
---
## 14. ТЕКУЩЕЕ СОСТОЯНИЕ (v1.3.20)
### Проблема
Функции логина **отсутствуют в app.js**:
- ❌ `doLogin()` — НЕ НАЙДЕНА
- ❌ `showLoginOverlay()` — НЕ НАЙДЕНА
- ❌ `hideLoginOverlay()` — НЕ НАЙДЕНА
- ❌ `setUserAvatar()` — НЕ НАЙДЕНА
- ❌ `jwtEmail()` — НЕ НАЙДЕНА
- ❌ `doLogout()` — НЕ НАЙДЕНА
- ❌ `pollNSStatus()` — НЕ НАЙДЕНА (строка 600 пустая)
### Почему логин всё ещё работает?
- Кнопка "Войти" имеет `onclick="doLogin()"` (inline обработчик, не через event delegation)
- Функции должны быть где-то определены, но их нет в app.js
- Возможно: отсутствуют в текущей версии после рефакторинга
### Необходимо восстановить
Все 6 функций должны быть в app.js перед инициализацией (до `initializeEventListeners()` и `checkAuth()`).
---
## 15. Ссылки на код
- [index.html](../../console/ui/index.html) — основной HTML
- [app.js](../../console/ui/js/app.js) — основная логика
- [login.html template](../../console/ui/templates/modals/login.html) — форма логина
- [auth.js (коммит f8c4b3e)](../../.git/refs/heads/main) — оригинальные функции логина