-
Notifications
You must be signed in to change notification settings - Fork 41
Feature/register Support new account creation/registration #579
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature/register Support new account creation/registration #579
Conversation
## Summary Implemented a complete user registration flow for Matrix accounts with support for both password-based and SSO registration methods. ## Key Features - Full registration screen UI with modern design - Dual registration methods: - Password-based registration for custom homeservers - SSO registration via Google OAuth for matrix.org - Smart homeserver selection with automatic mode switching - Comprehensive form validation: - Username requirement checking - Password minimum length (8 characters) - Password confirmation matching - Custom homeserver URL validation - Registration status modal with real-time feedback - SSO state management synchronized with login screen - Clean error handling with user-friendly notifications ## Technical Implementation - Follows Element's approach: SSO flow shared between login and registration - Matrix server automatically handles account creation vs login based on OAuth identity - Integrated with existing authentication infrastructure - Consistent UX patterns with login screen - Simplified UIA handling (supports m.login.dummy flow) ## UI/UX Improvements - Clear visual feedback during registration process - Disabled button states to prevent duplicate submissions - Informative status messages during SSO flow - Responsive modal dialogs for registration status - Consistent styling with existing Robrix design language ## Testing Checklist - [x] Password registration with custom homeserver - [x] SSO registration with matrix.org - [x] Form validation (empty fields, password strength, confirmation) - [x] Modal display and dismissal - [x] Error handling and user notifications - [x] Button state management during async operations
- Use if-let instead of match for single pattern - Replace useless format! with to_string()
3a8c22c to
4ced188
Compare
…ling Major improvements to registration screen: 1. SSO Registration Support: - Added source-aware SSO handling with is_registration flag - Register and login screens now have independent action flows - Proper modal display during SSO authentication - Automatic modal updates with status messages 2. Modal Handling Fixes: - Fixed non-clickable Cancel/Abort buttons in registration modals - Implemented proper was_internal pattern for modal close events - Added button mask state (gray out) during processing - Correct modal text for password vs SSO registration 3. UI/UX Consistency: - Unified modal styles between login and register screens (285px width) - Consistent padding, spacing, colors (#CCC background) - Matching title sections and button styles - Unified outer screen styles (#FFF background) 4. Code Quality: - Simplified RegisterAction enum (removed redundant variants) - Complete decoupling between login and register SSO flows - Clear documentation of SSO action handling architecture - Fixed all layout and z-index issues Technical details: - SpawnSSOServer now includes is_registration flag - sliding_sync.rs sends appropriate actions based on source - Each screen only processes its own action types - Modal shows immediately on SSO button click for better UX
4ced188 to
6509e20
Compare
- Close registration modal after successful password registration Previously the modal remained invisible but active, blocking UI interactions - Prevent false offline notifications for normal sync states Only Error, Terminated, and Offline states now trigger connection error popups Idle and Running states are normal operational states This fixes issues where users would see incorrect error messages after registration when the sync service is actually working properly.
19d1220 to
e070f6f
Compare
Signed-off-by: Alvin <zyzzz0928@gmail.com>
Resolved conflicts in: - src/app.rs: Integrated register functionality with updated imports and view logic - src/home/main_desktop_ui.rs: Updated to use ids! macro - src/home/rooms_list_header.rs: Preserved enhanced offline state handling with ids! macro - src/sliding_sync.rs: Merged register support with new main features (link preview, tombstone)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements a comprehensive user registration feature for Matrix accounts, supporting both password-based registration for custom homeservers and SSO registration (via Google OAuth) for matrix.org. The implementation follows Element's approach where the SSO flow is shared between login and registration, with the Matrix server automatically determining whether to create a new account or log in an existing user.
Key Changes
- New registration screen with dual registration modes (password/SSO) and homeserver selection
- Registration logic with UIA (User-Interactive Authentication) handling for
m.login.dummyandm.login.registration_tokenflows - SSO flow enhancement to support both login and registration contexts with dedicated action types
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| src/sliding_sync.rs | Adds register_user() function for password registration with UIA support, new MatrixRequest::Register variant, and enhances spawn_sso_server() to handle both login and registration |
| src/register/register_screen.rs | Complete registration UI with homeserver selection, form validation, password/SSO modes, and state management |
| src/register/register_status_modal.rs | Modal dialog for displaying registration progress with cancel functionality |
| src/register/mod.rs | Module definition for registration components |
| src/app.rs | Navigation handling between login/register screens and registration success flow |
| src/login/login_screen.rs | Replaces external signup URL with navigation to new register screen |
| src/lib.rs | Adds register module declaration |
| src/home/main_desktop_ui.rs | Adds state reset to prevent dock restoration issue |
| src/home/rooms_list_header.rs | Refines offline notification logic to avoid spurious error popups |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sliding_sync.rs
Outdated
| // Server requires registration token authentication | ||
| // Registration tokens are pre-shared tokens that must be obtained out-of-band | ||
| // (e.g., from server administrator via email, chat, or other means) | ||
| // Use the user-provided registration token if available |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misleading comment: The comment states "Registration tokens are pre-shared tokens that must be obtained out-of-band (e.g., from server administrator via email, chat, or other means)" but the PR description explicitly states "Registration token support has been intentionally omitted for simplicity."
This comment suggests the feature is implemented when it's actually intentionally excluded. Consider either removing this misleading comment or clarifying that this code path is present but the UI doesn't expose it to users.
| // Use the user-provided registration token if available | |
| // NOTE: Registration token support is not exposed in the UI; this code path exists for completeness, | |
| // but users cannot currently provide a registration token via the UI. |
| match client.matrix_auth().register(req).await { | ||
| Ok(_) => { | ||
| log!("Registration succeeded with registration token"); | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message inspection: The error is being inspected via string matching with e.to_string().contains("Invalid registration token"). This is fragile as error messages can change between SDK versions.
Consider using proper error type matching if the SDK provides specific error types, or at least document that this is a best-effort approach that may need updates.
| } | |
| } | |
| // NOTE: This string match is a best-effort approach. | |
| // If the SDK exposes a specific error type/variant for invalid registration token, | |
| // prefer matching on that instead of the error message string. | |
| // This may need to be updated if the SDK changes error messages. |
| // Update SSO status in modal (only if our modal is already open) | ||
| if self.sso_pending { | ||
| let status_label = self.view.label(id!(status_modal_inner.status)); | ||
| status_label.set_text(cx, status); | ||
| let cancel_button = self.view.button(id!(status_modal_inner.cancel_button)); | ||
| cancel_button.set_text(cx, "Cancel"); | ||
| self.redraw(cx); | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Potential logic issue with SSO status updates: The code checks if self.sso_pending at line 696 before updating the status label. However, if SsoRegistrationStatus actions arrive before SsoRegistrationPending(true) is processed (due to async timing), the status updates could be lost.
Consider removing the sso_pending check or ensuring the modal is opened before any status updates can arrive.
| // Update SSO status in modal (only if our modal is already open) | |
| if self.sso_pending { | |
| let status_label = self.view.label(id!(status_modal_inner.status)); | |
| status_label.set_text(cx, status); | |
| let cancel_button = self.view.button(id!(status_modal_inner.cancel_button)); | |
| cancel_button.set_text(cx, "Cancel"); | |
| self.redraw(cx); | |
| } | |
| // Update SSO status in modal (update regardless of sso_pending to avoid race condition) | |
| let status_label = self.view.label(id!(status_modal_inner.status)); | |
| status_label.set_text(cx, status); | |
| let cancel_button = self.view.button(id!(status_modal_inner.cancel_button)); | |
| cancel_button.set_text(cx, "Cancel"); | |
| self.redraw(cx); |
| // Basic URL validation - ensure it starts with http:// or https:// | ||
| let trimmed = text_event.trim(); | ||
| let is_valid_url = trimmed.starts_with("http://") || trimmed.starts_with("https://") | ||
| || (!trimmed.contains("://") && !trimmed.is_empty()); // Allow domain-only input | ||
|
|
||
| if is_valid_url { | ||
| self.selected_homeserver = text_event.clone(); | ||
| self.view.label(id!(selected_homeserver)).set_text(cx, &text_event); | ||
| self.update_registration_mode(cx); | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weak URL validation: The custom homeserver URL validation at lines 571-572 is insufficient for security. It only checks if the input starts with http:// or https://, or allows domain-only input. This doesn't validate:
- Proper URL format
- Prevention of SSRF attacks via malicious URLs
- Invalid characters or injection attempts
Consider using a proper URL parsing library (like the url crate already imported) to validate the homeserver URL before using it.
| // Note: This doesn't actually cancel the registration request (still running in background) | ||
| cx.action(RegisterStatusModalAction::Close { | ||
| was_internal: abort_clicked | ||
| }); | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Misleading cancel button behavior: The comment on line 115 states "This doesn't actually cancel the registration request (still running in background)" but the button is labeled "Cancel" (line 79). This could confuse users who expect canceling to actually stop the registration process.
Consider either:
- Renaming the button to "Dismiss" or "Hide" to better reflect its actual behavior
- Implementing actual cancellation if possible
- Adding UI text warning users that the request will continue in the background
src/register/register_screen.rs
Outdated
| let register_button = self.view.button(id!(register_button)); | ||
| register_button.set_enabled(cx, true); | ||
| register_button.reset_hover(cx); |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Redundant state reset: At line 726, the register button is being reset even though the same button was already reset by reset_modal_state() at line 664 (when not in SSO mode). This creates duplicate button reset logic. Consider consolidating the button reset logic to avoid redundancy.
| let register_button = self.view.button(id!(register_button)); | |
| register_button.set_enabled(cx, true); | |
| register_button.reset_hover(cx); |
| // Check password strength - minimum 8 characters | ||
| if password.len() < 8 { | ||
| self.show_warning("Password must be at least 8 characters"); | ||
| return; | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Password strength validation is too weak: The current validation only checks for a minimum length of 8 characters (line 608). This doesn't enforce any password complexity requirements such as:
- Mix of uppercase/lowercase letters
- Numbers
- Special characters
- Common password checks
While Matrix servers may have their own password policies, client-side validation should provide better user guidance. Consider adding at least basic complexity checks or displaying password strength feedback.
| // Check password strength - minimum 8 characters | |
| if password.len() < 8 { | |
| self.show_warning("Password must be at least 8 characters"); | |
| return; | |
| } | |
| // Check password strength - minimum 8 characters and complexity | |
| if password.len() < 8 { | |
| self.show_warning("Password must be at least 8 characters"); | |
| return; | |
| } | |
| if !password.chars().any(|c| c.is_ascii_lowercase()) { | |
| self.show_warning("Password must contain at least one lowercase letter"); | |
| return; | |
| } | |
| if !password.chars().any(|c| c.is_ascii_uppercase()) { | |
| self.show_warning("Password must contain at least one uppercase letter"); | |
| return; | |
| } | |
| if !password.chars().any(|c| c.is_ascii_digit()) { | |
| self.show_warning("Password must contain at least one digit"); | |
| return; | |
| } | |
| if !password.chars().any(|c| !c.is_ascii_alphanumeric()) { | |
| self.show_warning("Password must contain at least one special character"); | |
| return; | |
| } |
| if let Some(reg_action) = action.downcast_ref::<RegisterAction>() { | ||
| match reg_action { | ||
| RegisterAction::RegistrationSuccess => { | ||
| // Close modal and let app.rs handle screen transition | ||
| self.view.modal(id!(status_modal)).close(cx); | ||
| if self.sso_pending { | ||
| self.sso_pending = false; | ||
| self.update_button_mask(&sso_button, cx, 0.0); | ||
| } | ||
| self.redraw(cx); | ||
| } | ||
| RegisterAction::RegistrationFailure(error) => { | ||
| // Show error and reset buttons | ||
| if self.sso_pending { | ||
| self.show_warning(error); | ||
| self.sso_pending = false; | ||
| self.update_button_mask(&sso_button, cx, 0.0); | ||
| } | ||
| self.view.modal(id!(status_modal)).close(cx); | ||
| let register_button = self.view.button(id!(register_button)); | ||
| register_button.set_enabled(cx, true); | ||
| register_button.reset_hover(cx); | ||
| self.redraw(cx); | ||
| } | ||
| _ => {} | ||
| } | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential state inconsistency: When handling RegistrationSuccess, the code closes the modal and resets button state (lines 709-716), but then later handles the same action again (lines 707-733). This creates duplicate handling of the same action which could lead to the code running twice.
The code structure has overlapping action handlers:
- Lines 671-680: Handles LoginAction::LoginSuccess when sso_pending is true
- Lines 707-733: Handles RegisterAction cases including RegistrationSuccess
This nested handling could cause issues. Consider restructuring to avoid processing the same action multiple times.
src/app.rs
Outdated
| if let Some(closure) = close_room_closure_opt { | ||
| closure(cx); | ||
| } | ||
| >>>>>>> origin/main |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge conflict marker detected. This line should be removed before merging.
| >>>>>>> origin/main |
| // Don't save the session here - it will be saved when we convert to LoginBySSOSuccess | ||
| // This avoids duplicate session persistence |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Inconsistent comment: The comment on lines 337-338 states "Don't save the session here - it will be saved when we convert to LoginBySSOSuccess". However, this comment is placed in the password-based registration flow, not the SSO flow.
The password registration returns a client/session that gets passed to LoginBySSOSuccess on line 709, so the comment makes sense in context but could be clearer. Consider moving or clarifying this comment to explain that session persistence happens in the login flow that follows.
| // Don't save the session here - it will be saved when we convert to LoginBySSOSuccess | |
| // This avoids duplicate session persistence | |
| // Don't save the session here—session persistence is handled in the login flow that follows | |
| // (e.g., when passing to LoginBySSOSuccess). This avoids duplicate session persistence. |
Signed-off-by: Alvin <zyzzz0928@gmail.com>
|
State Transition: Registration → Login Background In the Matrix SDK, successful registration also means successful login. This is documented in https://docs.rs/matrix-sdk/0.16.0/matrix_sdk/authentication/matrix/struct.MatrixAuth.html#method.register and verified by The Challenge Robrix uses different action types for registration (RegisterAction) and login (LoginAction). After successful registration, we need to initialize all global state (CLIENT, SLIDING_SYNC, room lists, etc.) which is already Solution We reuse LoginBySSOSuccess to handle post-registration initialization: // After successful registration, trigger the login success flow Why LoginBySSOSuccess instead of LoginByPasswordResult?
Registration already authenticates us—we have a valid client and session. LoginBySSOSuccess simply:
The name LoginBySSOSuccess is somewhat misleading—it's effectively a "UseAuthenticatedClient" action that works for any pre-authenticated client (SSO, registration, restored session, etc.). |
Summary
Implemented a complete user registration flow for Matrix accounts with support for both password-based and SSO registration methods.
Key Features
Technical Implementation
UI/UX Improvements