Skip to content

feat(link): /link and /unlink a Microsoft account from in-game#23

Merged
hbrombeer merged 1 commit into
mainfrom
feat/minecraft-link
Jul 13, 2026
Merged

feat(link): /link and /unlink a Microsoft account from in-game#23
hbrombeer merged 1 commit into
mainfrom
feat/minecraft-link

Conversation

@hbrombeer

Copy link
Copy Markdown
Member

Part of Minecraft friend sync. Needs groundsgg/grounds-forge#471 (the endpoints this calls).

What

/link → forge returns a Microsoft consent URL → posted in chat as a clickable component.
/unlink → drops the stored token.

The consent must happen in a browser: Microsoft will not issue a token to a game client. So there is no way to make this a pure in-game flow, and the command's job is reduced to fetching a URL and making it clickable.

Why no proof is attached to the request

The UUID sent to link/start is self-asserted — it's a chat command. That's fine, because forge verifies the Minecraft profile behind the completed consent against that UUID and refuses a mismatch. Signing in as someone else gets you nothing. The trust sits at the callback, not here.

Details

  • Async throughout. A Velocity command runs on the netty thread and forge has to reach Microsoft before answering — blocking here would stall the proxy.
  • HTTP client mirrors plugin-grounds-platform's WhitelistApiClient: JDK HttpClient (nothing to shade), and the token read from the env per request rather than held in a field, because a credential in a field ends up in a toString() eventually.
  • No GROUNDS_FORGE_URL → commands are not registered, rather than registered and guaranteed to fail.
  • Player-facing strings go through MessagesConfig (English), not hardcoded.

Heads-up

This is the first command in plugin-playermain has none today. Your feat/permission branch adds commands and touches the same registration site in GroundsPluginPlayer, so expect a trivial conflict whichever lands second.

Verification

./gradlew build green (compile + spotless + existing tests). Not exercised against a live forge — the endpoints are still in review on grounds-forge#471.

Friend sync needs a Microsoft refresh token for the player, and Microsoft
will not hand one to a game client — the consent has to happen in a
browser. So the command's whole job is to fetch a consent URL from forge
and put it in chat as something clickable.

The UUID sent to forge is only a claim. forge verifies it against the
Minecraft profile behind the completed consent and refuses a mismatch, so
nothing here has to be trusted — which is why this can be a plain chat
command with no proof attached.

Every call is async: a Velocity command runs on the netty thread and forge
has to reach Microsoft before it can answer, so blocking would stall the
proxy. The HTTP client mirrors plugin-grounds-platform's WhitelistApiClient
— JDK HttpClient, nothing to shade, and the token read from the environment
per request rather than held in a field, because a credential in a field
ends up in a toString() sooner or later.

Without GROUNDS_FORGE_URL the commands are not registered at all, rather
than registered and guaranteed to fail.

Note: this is the first command in plugin-player. feat/permission adds
commands too and touches the same registration site — expect a trivial
conflict there.
@hbrombeer hbrombeer marked this pull request as ready for review July 13, 2026 19:11
@hbrombeer hbrombeer merged commit 7afa8cd into main Jul 13, 2026
3 checks passed
@hbrombeer hbrombeer deleted the feat/minecraft-link branch July 13, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant