Fix: isolate auto-login errors from confirmation code errors

- confirmRegistration: wrap auto-login in separate try-catch so a failed
  login after confirmation never shows as 'wrong code' to the user.
  If auto-login fails, user is sent to LoginScreen to log in manually.
- SessionManager.login: always reconnect fresh (disconnect + enableReconnect
  + connect) instead of reusing the stale post-registration TCP connection.
  Fixes login failures caused by server closing the registration connection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
filip
2026-03-11 01:38:48 +01:00
parent b6529dedfc
commit e36dfe1cee
2 changed files with 39 additions and 20 deletions

View File

@@ -64,9 +64,9 @@ class SessionManager @Inject constructor(
// Re-authenticate automatically whenever the connection is (re)established.
// During the initial login() call, lastEmail is null (cleared before connect),
// so this handler is a no-op for the first connection.
connection.onConnected = {
val email = lastEmail ?: return@onConnected
val key = lastRsaPrivateKey ?: return@onConnected
connection.onConnected = reconnect@{
val email = lastEmail ?: return@reconnect
val key = lastRsaPrivateKey ?: return@reconnect
scope.launch {
try {
val session = performAuthHandshake(email, key, lastDeviceId, "Android")
@@ -100,9 +100,13 @@ class SessionManager @Inject constructor(
lastRsaPrivateKey = null
try {
if (connection.state.value != ConnectionManager.State.CONNECTED) {
connection.connect(host, port, useTls)
// Always start with a fresh connection.
// This handles stale post-registration connections and ensures reconnect is armed.
if (connection.state.value == ConnectionManager.State.CONNECTED) {
connection.disconnect()
}
connection.enableReconnect()
connection.connect(host, port, useTls)
val session = performAuthHandshake(email, rsaPrivateKey, deviceId, deviceName)

View File

@@ -211,12 +211,17 @@ class AuthViewModel @Inject constructor(
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, loadingMessage = "Potvrzuji kód…", error = null) }
try {
// Step 1: Verify the email code — this is the only thing that should report
// "wrong code". Any error here is genuinely a bad/expired code.
sessionManager.confirmRegistration(email, code)
// Auto-login immediately after confirmation using the already-decrypted
// key material from register(). This avoids re-asking for the password.
// Step 2: Try auto-login using the key material saved during register().
// This is best-effort — failure here does NOT mean the code was wrong.
// On failure the user is sent back to LoginScreen to log in manually.
val auth = pendingAuth
var loggedIn = false
if (auth != null && auth.email == email) {
try {
_uiState.update { it.copy(loadingMessage = "Přihlašuji se…") }
val state = _uiState.value
sessionManager.login(
@@ -226,17 +231,27 @@ class AuthViewModel @Inject constructor(
port = state.serverPort,
useTls = state.useTls,
)
// Init local DB encryption key (no password needed — bytes already decrypted)
keyStorage.initLocalKey(auth.identityPrivateBytes)
pendingAuth = null // consumed — clear for security
loggedIn = true
} catch (loginEx: Exception) {
// Auto-login failed (e.g. stale connection, transient server error).
// Log it but don't surface to user as a "wrong code" error.
android.util.Log.w(
"AuthViewModel",
"Auto-login after confirm failed, user must log in manually: ${loginEx.message}"
)
} finally {
pendingAuth = null
}
}
_uiState.update {
it.copy(
isLoading = false,
loadingMessage = null,
isLoggedIn = true,
isLoggedIn = loggedIn,
needsConfirmation = false,
hasExistingAccount = true,
)
}
} catch (e: AuthException) {