Repository navigation
Conversation
An AccountExternalLink links an account to a user of an external service like Discord, with a pending one-time code for the linking. Like the cash shop grants, it's a separate aggregate which refers to its account by id, so the external service can change it while a game server holds the account. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
The chat command creates a one-time code, which the player enters in Discord. /discord unlink removes the link and publishes an AccountUnlinkedEvent, so that the Discord bot can remove the role of the user. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
…er role Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Deploying openmudocs with
|
| Latest commit: |
d486b12
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://32f847ce.openmudocs.pages.dev |
| Branch Preview URL: | https://claude-sweet-cray-w8crms-m4.openmudocs.pages.dev |
sven-n
commented
Oct 10, 2026
sven-n
left a comment
Member
Author
There was a problem hiding this comment.
Part of a review pass over the whole Discord stack. This is the security-sensitive one, so I went through the link flow in detail; it stands up well.
What I checked and found correct:
- Code strength. 8 characters from a 32-symbol alphabet is ~2^40, valid for 10 minutes, generated with
RandomNumberGenerator, stored only as a SHA-256 hash, and cleared on use. Confusable characters are excluded from the alphabet, andNormalizeCodemeans case and the separator don't matter on entry. Brute force isn't a practical concern at that size. - Uniqueness. The unique indexes on
(AccountId, Provider)and(Provider, ExternalUserId)are both there, so the "user already linked elsewhere" race inLinkAsynccan't produce duplicate rows — the second writer fails at the database instead. The second index relies on PostgreSQL treating NULLs as distinct so that multiple pending links coexist, which is what you want and is what it does. - Character ownership.
SelectCharacterAsyncverifiesGetAccountIdByCharacterNameAsync(characterName) == link.AccountId, so/charactercan only select characters of the linked account. This matters a lot later, because M6 keys game-master authority off the selected character. - Secrecy of the code. Both
DeferAsyncandFollowupAsyncpassephemeral: truefor the account commands, and the in-game side uses a blue message to the requesting player only.
Two comments inline. The admin-panel one is a real gap; the other is cosmetic.
One thing I'd consider as follow-up rather than a change here: nothing prunes CodeHash/CodeExpiresAt once a code expires unused, so stale hashes accumulate on rows that were never confirmed. Harmless (they can't be used, and the entropy makes them uninteresting even to someone reading the database), but a periodic cleanup would keep the table honest.
Generated by Claude Code
…rms-m4 The migration AddAccountExternalLink is generated again, so that it follows the migrations of master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
AccountLinkService.UnlinkAccountAsync publishes the AccountUnlinkedEvent, so the admin panel and the chat command both make the bot remove the roles. The all-in-one deployment registers its event publisher for it. /discord unlink tells when there was no link, and a failed account command gets an answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Milestone M4 of the Discord integration (#1102). Stacked on #1157 (M3).
What
AccountExternalLink, refers to its account by id like the cash shop grants. Fields: provider, external user id and name, character name, linked at, plus the hash and expiry of a pending one-time code. Unique(AccountId, Provider)and(Provider, ExternalUserId). Includes the EF migrationAddAccountExternalLink, regenerated persistence model classes and the compiled models ofEntityDataContext,AccountContextandTradeContext. The links are inIPlayerContext(EF and in-memory).AccountLinkService(GameLogic):ABCD-EFGH. It is valid for 10 minutes, stored as SHA-256, usable only once, and a new code invalidates the previous one. Entering it is case- and separator-insensitive./discordhas three forms:/discord linkshows a code,/discord unlinkremoves the link and publishes a newAccountUnlinkedEventgame event, and/discordshows the link./link <code>and/unlink; answers are ephemeral./character <name>selects another character of the same account (validated).linked(Linked Player) to the user in the routing guild./unlink, replacing a link, orAccountUnlinkedEventfrom the game remove the role again. The bot is now also anIGameEventListener./accounts/{login}/links, which shows the links and removes them (operator policy).Notes
Tests
AccountLinkServiceTest: linking, one-time use, expiry and replaced codes, relinking, unlinking from both sides, character selection.DiscordAccountCommandsTest: answers and role changes.DiscordChatCommandPlugInTest: the in-game command.Not tested: the migration wasn't applied to a real PostgreSQL database here, and nothing was run against a real Discord bot.
🤖 Generated with Claude Code
https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Generated by Claude Code