Эта страница ещё не переведена — показана английская версия.
Auth & Players
Вход — это шаг, который можно переопределить, а не чёрный ящик. Провайдеры, сессии, привязка учётных записей, баны. Каждая точка сценария — до и после входа, до и после привязки, до и после слияния, при смене статуса — объявлена как точка расширения определённого вида. Это либо проверка, которая может отклонить шаг, либо наблюдатель, который этого не может.
Когда это нужно
- Игроки (player) должны входить в игру — через устройство, email, Apple, Google, Steam или собственный провайдер. Создание игрока при первом входе (sign-in) включается флагом, а не отдельным сценарием.
- Гостевой аккаунт позже нужно превратить в постоянный:
Linkпривязывает Steam без потери прогресса, а слияние (merge) сводит два аккаунта в одного игрока. - Политика должна выполняться там, где её не обойти: регион проверяется до входа, а стартовый набор выдаётся после того входа, при котором игрок был создан.
- Модерация должна срабатывать сразу: отозвать сессии, приостановить аккаунт, забанить устройство — и событие
bannedуслышат все работающие системы. - Объявленный контекст (регион, платформа, сборка) должен доходить до всех последующих хуков, чтобы ни одному из них не пришлось заново читать игрока ради этих данных.
- Облегчённой замены у этого модуля нет. Все остальные модули узнают, кто их вызывает, через него. Пока хоть одному из них нужен актор-игрок,
authвыключить нельзя: конфигуратор модулей откажет и перечислит зависимые модули.
Кто что делает
| Актор | Что делает на этой странице |
|---|---|
player | входит, привязывает и отвязывает учётные записи (identity), обновляет учётные данные, выходит |
moderator | отзывает сессии, банит, приостанавливает и восстанавливает игроков |
backend-service | закрывает вход из отдельных регионов, создаёт первые строки нового игрока, читает и отзывает сессии |
Коротко
SignIn на провайдера, создание игрока при первом входе включается флагом, а Link добавляет Steam// client — one call per provider; create-on-first-sign-in is a flag
var session = await PlayServ.Auth.SignIn(Provider.Device, create: true);
await PlayServ.Auth.Link(Provider.Steam); // one player, many identities// client — one call per provider; create-on-first-sign-in is a flag
const session = await PlayServ.auth.signIn(Provider.Device, { create: true });
await PlayServ.auth.link(Provider.Steam); // one player, many identities# client — one call per provider; create-on-first-sign-in is a flag
session = await playserv.auth.sign_in(Provider.DEVICE, create=True)
await playserv.auth.link(Provider.STEAM) # one player, many identitiesКак объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.
// client — one call per provider; create-on-first-sign-in is a flag
Client->Auth->SignInWithProvider(FPSProviderId::Device, Credential,
TPSOnResult<FPSSession>::CreateWeakLambda(this, [this](const TPSResult<FPSSession>& Result)
{
if (!Result.HasValue()) { return; }
// one player, many identities — add Steam to the same account
Client->Auth->Providers->Link(FPSProviderId::Steam, SteamCredential);
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.// client — one call per provider; create-on-first-sign-in is a flag
var session = await PlayServ.Auth.SignIn(Provider.Device, create: true);
await PlayServ.Auth.Link(Provider.Steam); // one player, many identitiesКаждая точка настраивается там же, где объявлена. Все формы, которые может принимать обработчик, собраны на странице Extensibility:
[Before(Auth.SignIn)] // a gate: it may refuse, and it is fail-closed
public static Verdict GateRegion(SignInAttempt a) =>
a.Region == "sanctioned"
? Hook.Reject(Problem.Forbidden, "region not served")
: Hook.Continue(a);
[After(Auth.SignIn, created: true)] // an observer: it watches, it cannot refuse
public static async Task GrantStarterPack(Player player)
{
await player.Inventory.Grant("chest.gold", count: 1);
}// a gate: it may refuse, and it is fail-closed
export const gateRegion = before(Auth.signIn, (a: SignInAttempt) =>
a.region === 'sanctioned'
? Hook.reject(Problem.forbidden, 'region not served')
: Hook.continue(a));
// an observer: it watches, it cannot refuse
export const grantStarterPack = after(Auth.signIn, { created: true },
async (player: Player) => {
await player.inventory.grant('chest.gold', { count: 1 });
});@before(auth.sign_in) # a gate: it may refuse, and it is fail-closed
def gate_region(a: SignInAttempt) -> Verdict:
if a.region == "sanctioned":
return Hook.reject(Problem.FORBIDDEN, "region not served")
return Hook.continue_(a)
@after(auth.sign_in, created=True) # an observer: it watches, it cannot refuse
async def grant_starter_pack(player: Player):
await player.inventory.grant("chest.gold", count=1)Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.
И переопределение, и хук — облачные функции: они выполняются на платформе, а не в движке. Напишите их на C#, TypeScript или Python, а Unreal подпишется на события, которые они порождают. В планах — функции, написанные прямо в движке: графы Blueprint и C++, который будет транслироваться в скрипт, проверяемый платформой. Их будут анализировать и загружать так же, как любые объявления, а выполняться они по-прежнему будут на платформе.
И переопределение, и хук — облачные функции: они выполняются на платформе, а не в движке. Напишите их на C#, TypeScript или Python, а Unity подпишется на события, которые они порождают.
Админ-панель показывает именно объявленную форму: у каждой точки видны её обработчики, их вид и итоговый порядок вызова. От вида зависит, что происходит, когда обработчик падает:
| Вид | Если упал сам обработчик | При отказе |
|---|---|---|
| проверка (gate) | шаг отклоняется: если проверку региона не удалось выполнить, она не считается пройденной | код из справочника платформы и понятная человеку причина. Вызывающий код ветвится по коду, а текст причины может меняться и переводиться |
| наблюдатель (observer) | шаг остаётся выполненным: если стартовый набор не выдался, игрок недосчитается сундука, но вход не сорвётся | отказать не может |
Хук никогда не решает, кто вошёл
Проверка лишь отвечает «да» или «нет» про игрока, которого платформа уже опознала. Она не называет игрока, не выдаёт учётную запись и не заменяет подтверждение от провайдера.
Модель
Игрок — это носитель учётных записей, а не строка в вашей схеме (schema). Его идентификатор стабилен и никогда не используется повторно — даже при слиянии: id объединённого игрока по-прежнему разрешается и не становится висячей ссылкой. Привязка — это тройка провайдер · внешний субъект · игрок.
| Всегда | Что это |
|---|---|
провайдер + субъект | уникальная пара, и её уникальность порождает конфликт, а не запрет: если аккаунт уже занят, выход — выбрать слияние |
не больше одной привязки на провайдера у игрока | вторая учётная запись того же провайдера — конфликт |
внешний субъект | никогда не становится идентификатором игрока. Он принадлежит провайдеру: если использовать его как свой, наши id окажутся привязаны к чужим |
тип учётной записи и статус доступа | — разные оси. anonymous или registered — одна, active / suspended / banned — другая, поэтому можно выразить и «забаненного анонима», и «приостановленного зарегистрированного» |
сессия и учётные данные | — разные вещи. Сессия — это запись, а учётные данные (credential) — то, что вы предъявляете. Отзыв сессии аннулирует все её учётные данные и закрывает её открытые подписки (subscription) |
несколько одновременных сессий | каждую можно отозвать отдельно |
утверждения в учётных данных | — объявленный контекст (регион, локаль) и только контекст. Утверждение (claim) никогда не даёт полномочий |
отпечаток устройства | — не учётная запись. Это никогда не повод впустить, только повод отказать. Хранят и сравнивают его в необратимом виде |
Три машины состояний.
| Машина | Состояния |
|---|---|
| тип учётной записи | anonymous → registered, и переход односторонний |
| статус доступа | active ⇄ suspended, а при разбане active → banned → active |
| игрок | alive → merged, причём merged — конечное состояние: объединённый игрок больше не входит |
Что объявляете вы.
| Объявление | Что это |
|---|---|
политика входа | разрешён ли анонимный вход и остальные правила входа. Объявляется атрибутом на точке монтирования модуля — не конфигом рядом с кодом и не во время выполнения |
роль по умолчанию | набор ролей (role), с которым новый игрок приходит при первом входе. У значения по умолчанию нет значения по умолчанию: если ничего не объявить, новые игроки придут без ролей — и это законное объявление, а не упущение |
политика сессий | что происходит, когда достигнут потолок одновременных сессий: вытеснить самую старую с событием (event) или отказать новой. Значения по умолчанию нет |
политика удаления | как удаление игрока распространяется на данные, которые на него ссылаются |
Где настраивается провайдер. На операторском уровне, а не в коде: учётным данным магазина не место в репозитории. Объявленное попадает в админ-консоль только для чтения.
Что даёт выдача роли. Роли — забота не только оператора. У игрока есть операции выдачи и отзыва, поэтому игра может своим кодом назначить офицера гильдии или передать организатору турнира его полномочия.
| Всегда | Что это |
|---|---|
это не самоназначение | чтобы выдать роль, нужно объявленное для этого атомарное разрешение (permission atom). Актор без него получит обычный forbidden, а не молчаливое бездействие |
выдача идемпотентна | выдать роль, которая у игрока уже есть, — успех, а не конфликт. Состояние — это набор ролей, а не история вызовов, поэтому, в отличие от входа, этой операции не нужен ключ идемпотентности |
изменение ролей не мгновенно | оно вступает в силу без перевыпуска учётных данных и становится заметно не позже объявленного предела устаревания кэша прав. Если код выдаёт роль и сразу проверяет её на подключённом клиенте, это окно нужно учитывать |
Из чего состоят роли и что они открывают, описано на странице Access & Roles.
Ошибки
- Нет учётных данных или они истекли — ответ not authenticated, и помогает о бновление. Отозванные учётные данные дают тот же ответ, но обновление уже не поможет — только новый вход.
- Повторно предъявленные учётные данные, которые уже прошли ротацию, — конфликт. Именно поэтому ротацию можно обнаружить, а не молча терпеть.
- Забаненный или приостановленный игрок, как и забаненный отпечаток устройства, получают forbidden, и повторять запрос бессмысленно.
- Пара провайдер+субъект уже занята — это конфликт, и повторить можно после того, как выбрано слияние. Вторая учётная запись того же провайдера — конфликт, который повтор не исправит.
- Отвязать последний способ входа нельзя — это отказ валидации: получился бы аккаунт, в который никто не может войти.
- Слияние уже объединённого игрока — конфликт:
merged— конечное состояние. - Если провайдер недоступен, ответ — unavailable, и стоит повторять с нарастающей задержкой. Если провайдер отверг учётные данные, ответ — not authenticated: стоит повторить один раз, но не в цикле.
- При п ревышении частоты попыток входа ответ относится к категории ограничения частоты и содержит срок, после которого можно повторить.
Ограничения
Конкретные числа появятся вместе с главой об ограничениях платформы, а поведение на каждой границе уже определено.
- Одновременные сессии игрока: по объявленной политике — либо вытеснение самой старой с событием, либо отказ новой. Значения по умолчанию нет.
- Попытки входа за период и попытки привязать занятую пару: ограничение частоты со сроком повтора, а счётчик попыток остаётся в истории.
- Привязки на игрока: привязка ещё одного провайдера отклоняется как конфликт.
- Время жизни учётных данных: not authenticated, после обновления можно повторить. Время жизни учётных данных для обновления: только новый вход.
- Хранение анонимного игрока, который не входит в игру: удаление по объявленной политике, с событием. Политику объявляют явно, значения по умолчанию нет.
- Записи в бан-листе отпечатков: новая запись от клоняется, а старые никогда не вытесняются молча.