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:
@@ -64,9 +64,9 @@ class SessionManager @Inject constructor(
|
|||||||
// Re-authenticate automatically whenever the connection is (re)established.
|
// Re-authenticate automatically whenever the connection is (re)established.
|
||||||
// During the initial login() call, lastEmail is null (cleared before connect),
|
// During the initial login() call, lastEmail is null (cleared before connect),
|
||||||
// so this handler is a no-op for the first connection.
|
// so this handler is a no-op for the first connection.
|
||||||
connection.onConnected = {
|
connection.onConnected = reconnect@{
|
||||||
val email = lastEmail ?: return@onConnected
|
val email = lastEmail ?: return@reconnect
|
||||||
val key = lastRsaPrivateKey ?: return@onConnected
|
val key = lastRsaPrivateKey ?: return@reconnect
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
val session = performAuthHandshake(email, key, lastDeviceId, "Android")
|
val session = performAuthHandshake(email, key, lastDeviceId, "Android")
|
||||||
@@ -100,9 +100,13 @@ class SessionManager @Inject constructor(
|
|||||||
lastRsaPrivateKey = null
|
lastRsaPrivateKey = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (connection.state.value != ConnectionManager.State.CONNECTED) {
|
// Always start with a fresh connection.
|
||||||
connection.connect(host, port, useTls)
|
// 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)
|
val session = performAuthHandshake(email, rsaPrivateKey, deviceId, deviceName)
|
||||||
|
|
||||||
|
|||||||
@@ -211,32 +211,47 @@ class AuthViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_uiState.update { it.copy(isLoading = true, loadingMessage = "Potvrzuji kód…", error = null) }
|
_uiState.update { it.copy(isLoading = true, loadingMessage = "Potvrzuji kód…", error = null) }
|
||||||
try {
|
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)
|
sessionManager.confirmRegistration(email, code)
|
||||||
|
|
||||||
// Auto-login immediately after confirmation using the already-decrypted
|
// Step 2: Try auto-login using the key material saved during register().
|
||||||
// key material from register(). This avoids re-asking for the password.
|
// 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
|
val auth = pendingAuth
|
||||||
|
var loggedIn = false
|
||||||
if (auth != null && auth.email == email) {
|
if (auth != null && auth.email == email) {
|
||||||
_uiState.update { it.copy(loadingMessage = "Přihlašuji se…") }
|
try {
|
||||||
val state = _uiState.value
|
_uiState.update { it.copy(loadingMessage = "Přihlašuji se…") }
|
||||||
sessionManager.login(
|
val state = _uiState.value
|
||||||
email = email,
|
sessionManager.login(
|
||||||
rsaPrivateKey = auth.rsaPrivate,
|
email = email,
|
||||||
host = state.serverHost,
|
rsaPrivateKey = auth.rsaPrivate,
|
||||||
port = state.serverPort,
|
host = state.serverHost,
|
||||||
useTls = state.useTls,
|
port = state.serverPort,
|
||||||
)
|
useTls = state.useTls,
|
||||||
// Init local DB encryption key (no password needed — bytes already decrypted)
|
)
|
||||||
keyStorage.initLocalKey(auth.identityPrivateBytes)
|
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 {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
loadingMessage = null,
|
loadingMessage = null,
|
||||||
isLoggedIn = true,
|
isLoggedIn = loggedIn,
|
||||||
needsConfirmation = false,
|
needsConfirmation = false,
|
||||||
|
hasExistingAccount = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: AuthException) {
|
} catch (e: AuthException) {
|
||||||
|
|||||||
Reference in New Issue
Block a user