feat(auth): login por credenciais (PASSWORD) na tela de Login

UILoginScreen_Base agora envia uint8(PASSWORD) + string(user) +
string(pass) no C_CHAR_AUTH_REQUEST (em vez de string(token dev:N)).
Servidor valida via AccountService.login (Argon2id + lockout) — contas
são criadas com 'npm run account' no ZeusCharServer.

CharServerOpcodes.h espelha do servidor:
- ZMMOCharAuthMethod::{TOKEN=0, PASSWORD=1}
- ZMMOCharRejectReason::{InvalidToken, AccountBanned, AlreadyOnline,
  InvalidCredentials=4, AccountLocked=5, AccountSuspended=6,
  CredentialsAuthDisabled=7}

S_CHAR_AUTH_REJECT mapeia por switch:
- 4 → InvalidCredentialsText  (Usuário ou senha inválidos.)
- 5 → AccountLockedText       (Conta bloqueada por excesso de tentativas.)
- 6 → AccountSuspendedText    (Conta suspensa.)
- 7 → CredentialsDisabledText (Auth indisponível no servidor.)
- outros → RejectedText + código

MakeDevToken removido (StubTokenValidator continua aceitando dev:N
no servidor para testes diretos via outro cliente).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 01:44:53 -03:00
parent c9152cd27b
commit 518a346cf5
3 changed files with 85 additions and 27 deletions

View File

@@ -0,0 +1,46 @@
#pragma once
#include "CoreMinimal.h"
/**
* Opcodes do CharServer (WebSocket) consumidos pelo cliente. Espelha
* `Server/ZeusCharServer/src/protocol/CharOpcodes.ts` (faixa 2000-2099).
* Mantido mínimo: só os opcodes que o front-end usa hoje (auth da Login).
* Ampliar conforme as telas (lista/criação/seleção de personagem) chegarem.
*
* Wire: string = uint16 LE (len) + UTF-8; inteiros LE. O cabeçalho de 32B
* é responsabilidade do UZeusCharServerSubsystem (Send/OnRawMessage operam
* sobre o payload já sem header).
*/
namespace ZMMOCharOp
{
// Autenticação — payload começa com uint8 method (ver ZMMOCharAuthMethod)
constexpr int32 C_CHAR_AUTH_REQUEST = 2000;
constexpr int32 S_CHAR_AUTH_OK = 2001; // string(accountId)+string(user)+uint64(expiresMs)
constexpr int32 S_CHAR_AUTH_REJECT = 2002; // uint16(reason)
}
/**
* Método de autenticação enviado como uint8 prefix no payload do
* C_CHAR_AUTH_REQUEST. TOKEN mantém o fluxo Stub/JWT (handoff de outro
* serviço); PASSWORD chama AccountService.login direto no CharServer.
* Espelha `CharAuthMethod` em CharOpcodes.ts.
*/
namespace ZMMOCharAuthMethod
{
constexpr uint8 TOKEN = 0;
constexpr uint8 PASSWORD = 1;
}
/** Espelha `CharRejectReason` em CharOpcodes.ts (subset usado no front-end). */
namespace ZMMOCharRejectReason
{
constexpr uint16 Unknown = 0;
constexpr uint16 InvalidToken = 1;
constexpr uint16 AccountBanned = 2;
constexpr uint16 AlreadyOnline = 3;
constexpr uint16 InvalidCredentials = 4;
constexpr uint16 AccountLocked = 5;
constexpr uint16 AccountSuspended = 6;
constexpr uint16 CredentialsAuthDisabled = 7;
}

View File

@@ -51,19 +51,6 @@ namespace
return bHas ? DesignStyle : Fallback;
}
// Token de DEV: o StubTokenValidator aceita `dev:<numero>` ou `dev:<uuid>`.
// Um nome de usuário comum NÃO valida — então em dev mapeamos para
// `dev:<numero>` (o número digitado, se houver; senão 1). Auth real
// (HTTP /auth/login + JWT) entra em tarefa dedicada.
FString MakeDevToken(const FString& User)
{
const FString Trimmed = User.TrimStartAndEnd();
if (!Trimmed.IsEmpty() && Trimmed.IsNumeric())
{
return FString::Printf(TEXT("dev:%s"), *Trimmed);
}
return TEXT("dev:1");
}
}
void UUILoginScreen_Base::NativePreConstruct()
@@ -210,12 +197,10 @@ void UUILoginScreen_Base::HandleLoginClicked()
// padrão dos launchers comerciais).
SaveOrClearRememberedAccess(User);
// Escopo dev: token aceito pelo StubTokenValidator (senha ignorada no
// stub; mantida na UI para o fluxo real futuro).
const FString Token = MakeDevToken(User);
FZeusPacketWriter W;
W.WriteStringUtf8(Token);
W.WriteUInt8(ZMMOCharAuthMethod::PASSWORD);
W.WriteStringUtf8(User.TrimStartAndEnd());
W.WriteStringUtf8(Pass);
const bool bSent = Char->SendCharRequest(
ZMMOCharOp::C_CHAR_AUTH_REQUEST, W.GetBuffer());
@@ -229,7 +214,7 @@ void UUILoginScreen_Base::HandleLoginClicked()
bAwaitingAuth = true;
SetBusy(true);
SetStatus(AuthenticatingText);
UE_LOG(LogZMMO, Log, TEXT("Login: C_CHAR_AUTH_REQUEST enviado (token dev)."));
UE_LOG(LogZMMO, Log, TEXT("Login: C_CHAR_AUTH_REQUEST enviado (PASSWORD)."));
}
void UUILoginScreen_Base::HandleBackClicked()
@@ -278,9 +263,22 @@ void UUILoginScreen_Base::HandleCharRawMessage(int32 Opcode, const TArray<uint8>
R.ReadUInt16(Reason);
UE_LOG(LogZMMO, Warning, TEXT("Login: S_CHAR_AUTH_REJECT (reason=%u)."), Reason);
SetBusy(false);
SetStatus(FText::Format(
NSLOCTEXT("ZMMO", "LoginRejectFmt", "{0} (cód. {1})"),
RejectedText, FText::AsNumber(Reason)));
FText Message;
switch (Reason)
{
case ZMMOCharRejectReason::InvalidCredentials: Message = InvalidCredentialsText; break;
case ZMMOCharRejectReason::AccountLocked: Message = AccountLockedText; break;
case ZMMOCharRejectReason::AccountSuspended: Message = AccountSuspendedText; break;
case ZMMOCharRejectReason::AccountBanned: Message = AccountSuspendedText; break;
case ZMMOCharRejectReason::CredentialsAuthDisabled: Message = CredentialsDisabledText; break;
default:
Message = FText::Format(
NSLOCTEXT("ZMMO", "LoginRejectFmt", "{0} (cód. {1})"),
RejectedText, FText::AsNumber(Reason));
break;
}
SetStatus(Message);
}
}

View File

@@ -18,11 +18,13 @@ class UZeusCharServerSubsystem;
* UI_CheckBox_Master). Camada Abstract; o WBP concreto (WBP_Login) herda
* DIRETO desta classe (ARQUITETURA.md §3.3).
*
* Auth (escopo atual — dev): ao clicar "Entrar", envia C_CHAR_AUTH_REQUEST
* (opcode 2000) pelo WebSocket do UZeusCharServerSubsystem com um token
* `dev:<n>` (StubTokenValidator aceita em dev; auth real virá em tarefa
* dedicada). S_CHAR_AUTH_OK → Flow::RequestEnterServerSelect; reject →
* mensagem de erro. Sem hard-ref de asset de tema (§5).
* Auth: ao clicar "Entrar", envia C_CHAR_AUTH_REQUEST (opcode 2000) pelo
* WebSocket do UZeusCharServerSubsystem como `uint8(PASSWORD) +
* string(user) + string(password)`. CharServer chama AccountService.login
* (Argon2id + lockout). S_CHAR_AUTH_OK → Flow::RequestEnterServerSelect;
* reject (InvalidCredentials/AccountLocked/Suspended/etc.) → status
* mapeado. Sem hard-ref de asset de tema (§5). Contas são criadas no
* servidor via `npm run account`.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUILoginScreen_Base : public UUIActivatableScreen_Base
@@ -51,6 +53,18 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText RejectedText = FText::FromString(TEXT("Acesso negado pelo Gateway."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText InvalidCredentialsText = FText::FromString(TEXT("Usuário ou senha inválidos."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText AccountLockedText = FText::FromString(TEXT("Conta bloqueada por excesso de tentativas. Aguarde e tente novamente."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText AccountSuspendedText = FText::FromString(TEXT("Conta suspensa. Contate o suporte."));
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Login")
FText CredentialsDisabledText = FText::FromString(TEXT("Autenticação por credenciais indisponível no servidor."));
// ---- Widgets do WBP (nomes esperados) ----
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UBorder> Border_Bg;