9.6 KiB
Логика авторизации — Fission Console
⚠️ ЛЕГАСИ (актуально до ~апреля 2026): описание ниже относится к старой схеме с Deck API + JWT. Актуальное описание — выше этого блока, в разделе "АКТУАЛЬНО (май 2026+)".
АКТУАЛЬНО (май 2026+)
Что принимает сервер
Токен передаётся одним из двух способов:
- Заголовок
X-Auth-Token: <токен> - Заголовок
Authorization: Bearer <токен>
X-Auth-Env — необязателен, по умолчанию test.
Типы токенов и маршрутизация (MultiAuthenticator)
Сервер использует MultiAuthenticator, который определяет тип по форме токена:
| Форма токена | Authenticator | Описание |
|---|---|---|
JWT (три части через .) |
DeckAuthenticator или TestAuthenticator |
Реальный облачный токен или тест-JWT |
| Любая строка ≥6 символов | DemoAuthenticator |
Demo-логин без внешних запросов |
| Строка <6 символов | — | Ошибка 401 |
DemoAuthenticator (текущий основной режим)
Принимает любую строку ≥6 символов как логин. Не ходит во внешние сервисы.
login (≥6 символов)
│
▼
Sub = UUID v5(login) ← детерминированный, фиксированный UUID namespace
Email = "demo-lo***in" ← маскированный для отображения в UI
│
▼
Namespace = "fission-" + hex(SHA256(Sub)[:8])
Пример:
- login =
livetest@test.local - Sub =
uuidV5("livetest@test.local")= фиксированный UUID - Namespace =
fission-c3fce59430e41b0f
Каждый логин → один и тот же namespace (детерминированно).
DeckAuthenticator (production JWT)
Для JWT-токенов из облака NUBES:
JWT токен
│
├─► validateToken → GET {deckAPI}/index.cfm/instances
│ Authorization: Bearer <token>
│ 401 → ошибка | другой → ОК
│ Кэш на 5 минут (sync.Map, ключ "env:token")
│
└─► identityFromJWT → decode payload (base64url, без верификации подписи)
→ claim "sub" → Sub
→ claim "email" → Email (опционально)
Deck API URL по стендам:
prod→https://deck-api.ngcloud.ru/api/v1dev→https://deck-api-dev.ngcloud.ru/api/v1test→https://deck-api-test.ngcloud.ru/api/v1
TestAuthenticator (тест-режим, устаревший)
Принимает JWT — декодирует sub из payload. Или строку с @ — использует как sub напрямую.
Не ходит в Deck API. Включался через FISSION_TEST_MODE=true.
⚠️ Сейчас не используется — заменён на DemoAuthenticator.
Вычисление namespace: NamespaceForSub
func NamespaceForSub(sub string) string {
h := sha256.Sum256([]byte(sub))
return "fission-" + hex.EncodeToString(h[:8]) // 16 hex символов
}
Namespace детерминирован: одинаковый логин → всегда один namespace.
authMiddleware — что происходит на каждый запрос
Входящий HTTP запрос
│
├─► authTokenFromRequest → X-Auth-Token || Authorization: Bearer
│
├─► authenticator.Authenticate(token, env) → UserIdentity{Sub, Email}
│ ошибка → 401
│
├─► NamespaceForSub(Sub) → "fission-XXXXXXXXXXXXXXXX"
│
├─► nsManager.EnsureUserNS(namespace)
│ → создаёт K8s namespace + RBAC если нет
│ ошибка → 502
│
└─► контекст запроса: ctxKeyNS=namespace, ctxKeyIdentity=identity
→ handler работает в namespace пользователя
POST /auth — явный вход
POST /console/api/auth
Body: { "token": "...", "env": "test" }
│
└─► то же что authMiddleware, возвращает:
{ ok: true, namespace: "fission-...", email: "demo-lo***in" }
Итого — полная схема (актуальная)
Пользователь передаёт токен/логин
│
▼
MultiAuthenticator
├── JWT? → DeckAuthenticator (Deck API + JWT decode)
└── нет → DemoAuthenticator (≥6 символов → UUID v5)
│
▼
UserIdentity { Sub, Email }
│
▼
NamespaceForSub(Sub) = "fission-" + hex(SHA256(Sub)[:8])
│
▼
EnsureUserNS → K8s namespace существует
│
▼
Все хендлеры работают с namespace из context
ЛЕГАСИ (до апреля 2026) — не удалять, для анализа
Что вводит пользователь
В форме логина (index.html, overlay #login-overlay):
- Стенд (
#l-env):dev/test/prod - Токен (
#l-token): JWT-токен из личного кабинета NUBES (Профиль → Токены)
Фронтенд: doLogin() → js/auth.js
- Берёт токен и стенд из формы
- Делает
POST /console/api/authс{ token, env }в теле - Если ответ OK:
- Сохраняет
auth_tokenиauth_envв localStorage браузера - Вызывает
setUserAvatar(token)— декодирует email из JWT payload и показывает в navbar - Скрывает overlay логина
- Сохраняет
- Проверяет
GET /console/api/ns/status— если namespace не готов, показывает overlay инициализации
Бэкенд: handleAuth → handlers.go:1022
- Принимает
POST /console/api/auth - Валидирует токен через
resolveNamespaceForToken(token, env, testMode) - Вызывает
EnsureUserNS— гарантирует что K8s namespace + RBAC + quota существуют - Возвращает JSON:
{ ok: true, env: "test", namespace: "fission-..." }
Валидация токена: validateDeckToken → server.go:279
Deck API — внешний сервис облачной платформы NUBES:
dev → https://deck-api-dev.ngcloud.ru/api/v1
test → https://deck-api-test.ngcloud.ru/api/v1
prod → https://deck-api.ngcloud.ru/api/v1
Проверка: GET {deckAPI}/index.cfm/instances с заголовком Authorization: Bearer {token}
401→ токен невалиден- Любой другой ответ → токен принят
- Результат кэшируется на 5 минут (
sync.Map)
Вычисление namespace: namespaceFromJWT → auth.go:131
- JWT токен не верифицируется по подписи — Deck API уже это сделал
- Декодируется payload (base64url)
- Извлекается claim
"sub"(идентификатор пользователя в облаке) - Namespace =
"fission-" + hex(SHA256(sub)[:8])
Пример: sub = c3fce59430e41b0f... → namespace fission-c3fce59430e41b0f
Email в navbar: setUserAvatar / jwtEmail → auth.js
jwtEmail(token)— декодирует JWT payload на фронтенде (без обращения к серверу)- Берёт
payload.emailилиpayload.sub - Отображает в
#user-avatarв левом верхнем углу (вместо синего кружка)
Последующие запросы
Каждый API-запрос от фронтенда включает заголовки:
X-Auth-Token: <токен из localStorage>
X-Auth-Env: <стенд из localStorage>
authMiddleware (auth.go) снова валидирует токен (но из кэша — без нового HTTP-запроса к Deck API),
вычисляет namespace и кладёт его в context.Context → все хендлеры работают в нужном namespace.
Test-режим (FISSION_TEST_MODE=true)
Вместо реального Deck API — проверяет заголовок X-Test-Sub.
Если токен содержит @ — трактуется как email и используется как sub для вычисления namespace.
Используется в тестах и для live-тестирования без реальных токенов.
Итого — схема
Пользователь вводит токен + стенд
│
▼
POST /console/api/auth
│
├─► validateDeckToken → Deck API ({env}.ngcloud.ru) → 200 = ОК
│
├─► namespaceFromJWT → decode JWT payload → claim "sub" → SHA256[:8] → "fission-XXXXXXXX"
│
├─► EnsureUserNS → K8s namespace + RBAC создаются если нет
│
└─► { ok: true, namespace: "fission-..." }
Фронтенд:
├─► localStorage.setItem('auth_token', token)
├─► localStorage.setItem('auth_env', env)
├─► jwtEmail(token) → email из JWT payload → показать в navbar
└─► reloadAll() — загрузить функции