feat(protocol2): website account provisioning & unlinking (Part A)
Adds the account-provisioning plane from docs/PROTOCOL_2.md Part A: the website can create game accounts and unlink them, gated by a shard-wide signup mode. The existing [link flow is unchanged. Overlay: - BridgeConfig: SignupMode (website|game|hybrid, default hybrid; unrecognized falls back to game), AccountCreateEnabled (mode-following default), RequireIpForCreate, name/password caps, and a boot warning when the core Accounts.AutoCreateAccounts setting contradicts the mode. - BridgeAccounts (new): account.create (mode gate, actor required, char-safety mirrored from AccountHandler, collision check, per-IP cap via CanCreate/ LogAccess with fail-closed missing/loopback IP, create + WebsiteUserId link, account.audit; password never logged or echoed) and account.unlink (Owner floor via BridgeAdmin.Protected, clears the tag). - BridgeAccountLink: in-game [unlink command, emits account.unlinked. - BridgeAdmin: Protected / ResolveTargetAccount promoted to public for reuse. Sidecar: - POST /accounts/create, DELETE /link/:account, respond_account status mapping (409 collision / 429 ip cap / 403 disabled|protected / 404 not-linked / 400). - store.record_unlink drops the mirrored link row. - PROTOCOL_VERSION -> 2 (outbound events additive; new endpoints need v2). Docs: INTEGRATION.md protocol bump, account.* events, endpoints, 409/429; PROTOCOL_2.md Part A marked built. Verified: sidecar cargo check clean; overlay compiles in the full ServUO Scripts tree (0 errors, 0 warnings). Live end-to-end run still pending. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ namespace Server.Custom.Bridge
|
||||
return;
|
||||
|
||||
CommandSystem.Register("link", AccessLevel.Player, OnLinkCommand);
|
||||
CommandSystem.Register("unlink", AccessLevel.Player, OnUnlinkCommand);
|
||||
BridgeBoot.RegisterHandler("link.confirm", OnLinkConfirm);
|
||||
|
||||
// Purge expired codes so an unconfirmed spam of [link cannot grow the table forever.
|
||||
@@ -127,6 +128,53 @@ namespace Server.Custom.Bridge
|
||||
url, (int)CodeTtl.TotalMinutes);
|
||||
}
|
||||
|
||||
// ---- [unlink ----
|
||||
|
||||
[Usage("unlink")]
|
||||
[Description("Unlinks this game account from your website account.")]
|
||||
private static void OnUnlinkCommand(CommandEventArgs e)
|
||||
{
|
||||
Unlink(e.Mobile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the WebsiteUserId tie from the caller's own account and tells the sidecar, so
|
||||
/// the website can reconcile a player-initiated unlink. Player-scoped (own account only),
|
||||
/// so it needs no access floor. After unlinking, [link works again.
|
||||
/// </summary>
|
||||
public static void Unlink(Mobile m)
|
||||
{
|
||||
if (m == null)
|
||||
return;
|
||||
|
||||
var acct = m.Account as Account;
|
||||
|
||||
if (acct == null)
|
||||
{
|
||||
m.SendMessage("Bridge: no account on this character.");
|
||||
return;
|
||||
}
|
||||
|
||||
var existing = acct.GetTag(Tag);
|
||||
if (existing == null)
|
||||
{
|
||||
m.SendMessage("Your account is not linked to a website account.");
|
||||
return;
|
||||
}
|
||||
|
||||
acct.RemoveTag(Tag);
|
||||
DropCodesFor(acct.Username); // drop any pending codes so nothing dangles
|
||||
|
||||
BridgeLink.Emit(BridgeJson.Begin("account.unlinked")
|
||||
.Str("origin", "in-game")
|
||||
.Str("account", acct.Username)
|
||||
.Str("websiteUserId", existing)
|
||||
.Str("char", m.Name)
|
||||
.End());
|
||||
|
||||
m.SendMessage(0x40, "Your account is no longer linked to website user {0}.", existing);
|
||||
}
|
||||
|
||||
// ---- inbound link.confirm ----
|
||||
|
||||
private static void OnLinkConfirm(Dictionary<string, object> o)
|
||||
|
||||
Reference in New Issue
Block a user