Add in-client account registration - #1300
HarleyGilpin wants to merge 20 commits into
Conversation
The 634 client's login screen already contains the "New user" form and sends two single-reply requests: opcode 28 to check an email address and opcode 22 to create the account. Answer both from a RegistrationServer wired into GameServer. - Accounts registered from the client use the email address as the account name; login accepts email-shaped names beyond 12 characters. - Storage.create persists a new account atomically for files and the database (name/reporter columns widened to 254 with migrations). - A display name is derived from the email and the player is prompted to choose their own on first login before character creation. The rename command now also rejects taken names. - Password rules (5-20 alphanumeric, not guessable), minimum age and a per-IP creation limit are enforced server-side; registration can be disabled with accounts.registration.
Accounts registered with an email address now pick their display name on the "Character Name" panel at the end of character creation instead of a dialogue prompt. The panel is part of interface 1028: confirming the appearance runs the client script that shows it, six server generated suggestions are sent as client strings, and Continue or a suggestion click runs the submit script which returns the typed name as a string entry. Taken or invalid names re-prompt with a message and fresh suggestions; the dialogue prompt remains as a fallback when character creation is disabled.
The suggestion container in the character creation name panel is hidden in the cache behind the naming rules text, so the server now toggles the two when the panel opens and reveals "Previous Suggestions" once a second page exists. Suggestions are generated from the player's name or the rejected name with a mix of styles: stem plus numbers, stem plus a class or creature, an adjective plus the stem, and adjective plus noun.
|
Amazing stuff!! Some parts I can understand and some not but it doesn't matter to me. Its awesome. This is very appreciated. ❤️ |
|
I am going to do some work off this branch to add Ktor restful web API to enable web registration, player counts, and adventure logs. So I'm converting this PR into a draft. |
| saveHistories(list, playerIds) | ||
| true |
There was a problem hiding this comment.
This'll need updating with the latest stats sections
There was a problem hiding this comment.
Extracted a shared saveSections used by both save and create so the two can't drift again, a1b4020
| * Creates accounts registered from the client's login screen. | ||
| * The email address is the account name; a display name is derived from it until the player chooses their own on first login. | ||
| */ | ||
| class PlayerAccountCreator( |
There was a problem hiding this comment.
I don't follow why this is all necessary? There are already uniqueness checks for usernames, which are case insensitive and we can just check if the username is an email we assign an incrementing player1/2/3 as the displayName and rename it once they're in-game and have picked a name?
There was a problem hiding this comment.
Fair point, simplified in eb403ad:
- Placeholder name is now the next free
Player1/Player2/… via the existing case-insensitiveAccountDefinitions.get;DisplayNames.uniqueand the email local-part derivation are gone. - Dropped the in-flight set and the extra
storage.existslookup. The availability check andaccountDefinitions.addnow run together on the game thread, withStorage.createreturningfalseon a duplicate as the backstop. - Removed the unread
registeredvariable.
The rename happens on first login as before: choose_name opens the Character Name panel at the end of character creation.
There was a problem hiding this comment.
Went back to deriving the placeholder from the email in 1320c3f: since the name panel suggestions build on the current name, "Bob smith" gives better suggestions than "Player7". Still the existing case-insensitive checks plus a numeric suffix if taken; the in-flight set and extra storage lookup stay gone.
Registered accounts get the next free PlayerN display name instead of one derived from the email, and rely on the existing case-insensitive account checks plus the atomic Storage.create instead of a separate in-flight set and storage lookup. Drops the unread registered variable.
| single { PlayerAccountLoader(get(), get(), get(), get(), get(), Contexts.Game, get()) } | ||
| single { PlayerAccountCreator(get(), get(), get(), gameContext = Contexts.Game) } |
There was a problem hiding this comment.
Yeah, merged them in d93879b. PlayerAccountCreator is gone: PlayerAccountLoader implements AccountCreator (reserve the email in AccountDefinitions on the game thread, Storage.create, roll back on failure) and AccountManager.create sets the placeholder display name for email accounts, so the login path with development.accountCreation and the registration path share it.
The client shows response 11 as "Your password is an extremely common choice" so names over 12 characters or malformed emails looked like a password problem. Drops the unreachable duplicate check in LoginServer.
|
8447e46 fixes the "Your password is an extremely common choice" message from #1307: |
Registered players start as the part before the @ (sanitised, numbered if taken) rather than PlayerN so the name panel suggestions are based on something they recognise.
The client queues client string updates until it has read every packet in the frame but runs scripts as soon as they arrive, so running the render script in the same tick showed the strings from the previous call (or the previous player on the same client).
An account created since startup only exists in memory until its first save, so validating it returned account disabled before the already-online check could run. The in-memory hash is checked instead.
|
Two more from testing:
|
AccountManager.create gives email accounts their placeholder display name so both the login and registration paths share it, and PlayerAccountLoader implements AccountCreator rather than a second class creating accounts.
The create account form's Continue script (2967) sends anyone who enters an age under 13 to a herotopia.ws redirect instead of the create account packet and never restores the button, so it sits on "Please wait...". RemoveRegistrationAgeLimit strips that branch from the cache (wired into CacheBuilder) and the server no longer refuses under 13s.
|
Cache patch was required to fix the issue. see updated cache files here: https://dl.00002011.xyz/cache.zip |
| val temp = directory.resolve("${account.name.lowercase()}.toml.tmp") | ||
| try { | ||
| account.save(temp) | ||
| Files.move(temp.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING) | ||
| } catch (e: Exception) { | ||
| temp.delete() | ||
| file.delete() | ||
| throw e | ||
| } |
There was a problem hiding this comment.
Shouldn't the temp be in finally so it's cleared up after the move is complete?
There was a problem hiding this comment.
Yes, moved to a finally in 9d7c969 (the atomic move already consumes it on success, so the delete is a no-op there and cleanup on failure).
| /** | ||
| * Accounts registered with an email address are given a placeholder display name until the player picks one on first login | ||
| */ | ||
| suspend fun Player.chooseDisplayName() { |
There was a problem hiding this comment.
This can be moved inside of Introduction and the tests can access it by createPlayer/playerSpawn
There was a problem hiding this comment.
Moved into Introduction as a private suspend function in 6bd9f5b; DisplayNameSelection.kt is gone and the test (now IntroductionTest) goes through createPlayer with world.start.creation off so playerSpawn drives the prompt.

Summary
Adds account creation from the client's login screen ("New user").
Changes
RegistrationServerdispatched fromGameServerfor opcodes 28/22,RegistrationResponsecodes,RegistrationValidator, per-IPRegistrationLimiter,AccountCreatorinterface. Login (LoginServer/PasswordManager) accepts email-shaped account names beyond 12 characters viaAccountNames. Invalid names now get response 3 ("Invalid username or password") instead of 11, which the client shows as the "password is an extremely common choice" warning (the message behind Cannot relogin to accounts, getting "Invalid Login or Password" or "Password too common" #1307).PlayerAccountLoaderalso implementsAccountCreator: it reserves the email inAccountDefinitionson the game thread and persists viaStorage.create.AccountManager.creategives email accounts a placeholder display name from the local part (numbered if taken) and flagschoose_name, so login-created and registered email accounts behave the same.Storage.createpersists a brand-new account atomically (FileStorage:createNewFile+ temp file + atomic move;DatabaseStorage: single transaction, unique-violation →false).AccountDefinitions.removefor rollback.DisplayNamesholds the name rules and suggestion generator.accounts.nameandabuse_reports.reporterwidened to 254 with idempotent migrations inconnect.rename/nameTakenextracted fromNameChange; the::renamecommand now rejects taken names too. New settings:development.accountCreation(auto-create on unknown login) is unchanged.Testing
GameServerdispatch, login with email names, registration viaPlayerAccountLoader,DisplayNames(rules + suggestions),AccountDefinitions.remove,Storage.createon both backends, andWorldTests for the name panel (confirm, taken/invalid/accepted names, suggestion paging and picking) and the dialogue fallback.