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

17 KiB
Raw Blame History

Логин в 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 подгружается динамически:
    // 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):

initializeEventListeners();  // Event delegation для data-* атрибутов
checkAuth();                  // Проверка авторизации

checkAuth() функция (строка 648-658):

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)

<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:

<button id="l-btn" onclick="doLogin()">Войти</button>

Функция doLogin() (из auth.js, строка 5):

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: Что сохраняется

// В момент успешного логина:
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 запрос содержит авторизационные заголовки:

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 с авторизацией

async function getJSON(url) {
  const r = await fetch(url, { headers: authHeaders() });
  if (r.status === 401) { doLogout(); /* сессия истекла */ }
  // ...
}

2. requestJSON(url, method, body) — POST/PUT/DELETE с авторизацией

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

Запрос:

POST /console/api/auth
Content-Type: application/json

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "env": "test"
}

Ответ (успех — 200):

{
  "success": true
}

Ответ (ошибка — 400/401):

{
  "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() функция

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() функция

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() функция

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):

// В getJSON()
if (r.status === 401) { doLogout(); throw new Error('Сессия истекла'); }

// В requestJSON()
if (r.status === 401) { doLogout(); throw new Error('Сессия истекла'); }

Причины 401:

  • Токен истёк
  • Токен был отозван (revoked) в облаке
  • Пользователь был заблокирован

10. Показ/скрытие интерфейса

10.1 showLoginOverlay()

function showLoginOverlay() {
  document.getElementById('login-overlay').style.display = 'flex';
}

10.2 hideLoginOverlay()

function hideLoginOverlay() {
  document.getElementById('login-overlay').style.display = 'none';
}

11. Проверка статуса namespace

11.1 pollNSStatus() функция (строка 600)

После логина API проверяет готовность namespace пользователя:

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):

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. Ссылки на код