# AuthService

## Обзор класса

`Minecraft_launcher` поддерживает три способа входа: офлайн-профиль без пароля, учётную запись
Ely.by и учётную запись Microsoft. `AuthService` закрывает первые два: это Yggdrasil-клиент Ely.by
плюс офлайн-режим. За третий отвечает [MsaAuthService](MsaAuthService.md).

Результат любого способа — структура `AuthResult`, объявленная в этом же заголовке. Она содержит
ровно то, что подставляется в аргументы запуска вида `${auth_*}`, поэтому дальше запуск игры идёт
по общему пути независимо от того, как пользователь вошёл.

Класс также умеет скачивать `authlib-injector` — библиотеку, которая перенаправляет обращения игры
к серверу авторизации на Ely.by.

## Место в проекте и зависимости

Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md); других владельцев нет.
Заголовок подключает [MsaAuthService](MsaAuthService.md) — ради общей структуры `AuthResult` — и
[GameLauncher](GameLauncher.md) через поля `LaunchOptions`, которые заполняются из результата
авторизации.

Требования сборки: `Qt6::Core` (`QDateTime`, `QJsonObject`, `QString`) и `Qt6::Network`
(`QNetworkAccessManager`).

## Иерархия и роль

Наследует `QObject`: даёт мета-объектную систему, сигнал `progress` и владение по родителю.
Виртуальных методов базового класса не переопределяет.

## Публичные структуры

### AuthResult

Результат авторизации — то, что подставляется в `${auth_*}` аргументы запуска.

| Поле | Тип | Описание |
|------|-----|----------|
| `ok` | `bool` | Авторизация удалась |
| `twoFactorRequired` | `bool` | Ely.by отклонил пароль с пометкой two factor — нужен одноразовый код |
| `licenseMissing` | `bool` | Вход в Microsoft прошёл, но копии игры на аккаунте нет. Обрабатывается отдельно от прочих ошибок, потому что чинится только покупкой |
| `error` | `QString` | Текст ошибки, когда `ok` равен `false` |
| `playerName` | `QString` | Подставляется в `${auth_player_name}` |
| `uuid` | `QString` | Подставляется в `${auth_uuid}`; hex без дефисов |
| `accessToken` | `QString` | Подставляется в `${auth_access_token}` |
| `clientToken` | `QString` | Подставляется в `${clientid}` |
| `userType` | `QString` | Подставляется в `${user_type}`; принимает значения `legacy` (офлайн), `msa` (Microsoft) и `ELYBY` |
| `refreshToken` | `QString` | Только для аккаунтов Microsoft: продлевает сессию без ввода пароля |
| `xuid` | `QString` | Только для Microsoft; подставляется в `${auth_xuid}` |
| `expiresAt` | `QDateTime` | Только для Microsoft: UTC-время, когда протухает `accessToken` |

## Псевдонимы типов

`AuthService::Callback` — `std::function<void(const AuthResult &)>`. Все сетевые методы
асинхронные: колбэк вызывается ровно один раз и всегда в потоке GUI.

## Публичные методы

#### explicit AuthService(QObject \*parent = nullptr)

Создаёт сервис и его `QNetworkAccessManager`. Конструктор помечен `explicit`.

#### static AuthResult offline(const QString &nickname)

Готовит результат для офлайн-профиля без единого сетевого запроса. UUID выводится из ника так же,
как это делает сам Minecraft в офлайне: `UUID.nameUUIDFromBytes(("OfflinePlayer:" + name)
.getBytes(UTF_8))`. Благодаря этому один и тот же ник всегда даёт один и тот же UUID, и прогресс
на сервере не теряется.

#### static QString generateClientToken()

Случайный `clientToken` лаунчера. Генерируется один раз на профиль и хранится вместе с ним:
Yggdrasil связывает выданный `accessToken` именно с этим значением.

#### void loginElyBy(const QString &login, const QString &password, const QString &clientToken, const QString &accessToken, Callback callback)

Полный цикл входа в Ely.by: сначала проверка имеющегося токена, затем его продление, и только при
неудаче — авторизация по паролю. Пароль можно оставить пустым, если уже есть рабочий
`accessToken`, — тогда пользователю не придётся вводить его заново.

По ходу работы испускает `progress` с описанием текущего шага. Результат приходит в `callback`
один раз; при ответе с пометкой двухфакторной аутентификации в нём выставлен
`twoFactorRequired`, и вызывающий код должен спросить у пользователя код и продолжить через
`loginElyByWithTotp()`.

#### void loginElyByWithTotp(const QString &login, const QString &password, const QString &totp, const QString &clientToken, Callback callback)

Повтор авторизации с одноразовым кодом двухфакторной аутентификации. Пароль и код объединяются в
одно поле в формате «пароль:код», как того требует Ely.by.

#### void ensureAuthlibInjector(const QString &targetDir, std::function&lt;void(const QString &path, const QString &error)&gt; callback)

Скачивает `authlib-injector` в `targetDir`, если его там ещё нет. Колбэк получает либо путь к
готовому jar, либо текст ошибки — заполнено всегда ровно одно из двух.

Библиотека нужна только для профилей Ely.by: она подключается к JVM аргументом `-javaagent` и
перенаправляет обращения игры к серверу авторизации.

## Сигналы

#### progress(const QString &message)

Описание текущего шага авторизации. Испускается по ходу всех сетевых операций.

Обработчик показывает сообщение пользователю: в главном окне лаунчера оно попадает в плашку
статуса и держится до следующего сообщения, потому что шаг может занять заметное время.

## Владение и время жизни

Класс наследует `QObject` и принимает `parent` в конструкторе — родитель его и удалит.
`QNetworkAccessManager` создаётся в конструкторе с сервисом в роли родителя и уничтожается вместе
с ним.

Колбэки захватываются по значению и живут до своего единственного вызова. Уничтожение сервиса во
время незавершённого запроса отменяет запрос вместе с менеджером сети — колбэк в этом случае не
вызывается, поэтому захватывать в него сырые указатели на объекты с меньшим временем жизни, чем у
сервиса, нельзя.

## Потокобезопасность

Только поток GUI. Все сетевые методы асинхронные, и их колбэки вызываются в том же потоке, в
котором создан сервис. Собственной синхронизации в классе нет.

## Взаимодействие с другими классами

`LauncherBackend` вызывает методы входа при запуске игры и переправляет сигнал `progress` в
интерфейс. Полученный `AuthResult` он раскладывает по полям `LaunchOptions`, которые уходят в
[GameLauncher](GameLauncher.md). Путь, возвращённый `ensureAuthlibInjector()`, попадает в поле
`authlibInjectorPath` тех же параметров запуска.

Сохранением токенов между запусками занимается `LauncherBackend`: сам сервис ничего не пишет на
диск, кроме скачанного jar.

## Внешнее взаимодействие

**Сеть, исходящие запросы.** Класс общается с сервером авторизации Ely.by через
`QNetworkAccessManager`. Формат — JSON поверх HTTPS, запросы инициирует всегда лаунчер. Внутренний
помощник `postJson()` разделяет три исхода: успешный ответ, ответ с кодом ошибки и транспортную
ошибку — последняя отдаётся отдельным параметром, чтобы отличить недоступную сеть от отказа
сервера.

Отдельным каналом идёт загрузка `authlib-injector` — обычная HTTPS-загрузка файла в
`targetDir`. Повторных попыток при неудаче класс не делает: решение о повторе принимает вызывающий
код.

Все сигналы и колбэки приходят в поток GUI.

## Пример использования

```cpp
auto *auth = new AuthService(this);
connect(auth, &AuthService::progress, this, &Backend::showStatus);

auth->loginElyBy(profile.login, profile.password,
                 profile.clientToken, profile.accessToken,
                 [this](const AuthResult &result) {
                     if (result.twoFactorRequired) {
                         emit twoFactorRequired(m_pendingProfileName);
                         return;
                     }
                     if (!result.ok) {
                         emit launchError(result.error);
                         return;
                     }
                     continueLaunch(result);
                 });
```

---

При создании этого документа использовался ИИ.
