From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 447EF1FF0C1 for ; Tue, 25 Aug 2026 10:09:43 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 4311C21560; Tue, 25 Aug 2026 10:09:04 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=azharul.com; s=t28hkp5; t=1787283737; x=1787888537; darn=lists.proxmox.com; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=Y+mRu0fp2BCODQsHu/HK7sdrPqgqcvmiv8hipjtOjyw=; b=d3Oz/0IUmHXtGq57FmMG9QJ9q2N84FmPS3sclb5FzNt5UzI1fyIsGBt/7dcwOf/1MF c2sAd6mLdUuAJOwrJ5qroiI3hJNtSwnH46Ob1Kw4b3oqv4Kx7MsX6wCv/yE7HIO+ao/V 9DJ3ypeVSkqPmE7vjZkFKAFzi28j9RzEOBRSDPDMg5eBfWguWN/rvC9xWZYfO77/jLQW WRLtr1L8ND4SgdLKd0gJjXwj/e6a8vDV5IXikWvMAK2g9P4RHsHYsT57FpnfMkuVKt5W z57Lzk06drOIliEsOa+zVgiQwylY5R+lAoj7j2tRm4B5Jc7QQLt9WK2L8MphthdoWeMU 0MZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787283737; x=1787888537; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=Y+mRu0fp2BCODQsHu/HK7sdrPqgqcvmiv8hipjtOjyw=; b=iYF++7NrPTithhBhVj69xO0TqcnZ5DiTVL9FKQGPl/GiofW538HYnit1FNa4cVbHZE 7LkDt4F7YlWHN8kpysolKTUuX3wKR8YvJBv4T22gBdusNauUXD+EupUkifGyfG+QtwyP LoLajbUduXwR77BZkMqNZciwpf2gL/7U3gtb/7JzF4nBT056FP/DQi49rHctFBp5QrhM ACtn4pB8JgMczhzhyAiVmvzda8uAuzGowMhCyJ3Ti+Fws0G+fm7/VO+WwIjZ1MXEelq1 TvXRk6oIpo94qmAy690ysrBWiNZboXG6V9gLD7TmG1DuAlzA+T8ZwNQzKTszXi9V1sw8 tnyg== X-Gm-Message-State: AOJu0YztpGN1kXFTZFGBzv31GzXJpzkhBVpA9EniAEiL8/AnEgVifNwa sKCSmcHEWt1z3mWk0jaYH4/z0r+wJzUzRYQ50Huud+8YbhMvmOBC6ShHf0LTnlF7u75sxB35J3y KNq5iQQ== X-Gm-Gg: AR+sD12sxpncLdukycH2vfe+mwFQSjKcNJyQ5yidD7LLsJV63VW9UTqq6q0tXYn7iV0 oFKDs2nOzPfs4MSIGpd832nR/mt560sSBPAR0Zph9wbfA6yOfm66BNCXglbGUkaqYBZyAfZoIjs aFz4upkO+x6l/E6M3IjFrJatiejqkZGPszZIjCiX6uwi+O9oOhgqOzO5tCT6wXhLc0EWvWQYPv6 Tl7bUK6Jc+4kmtVyWEwTgW0MWyCaD+Cq5xnCL4QY3N7g6DwqmOMYvdNmYElJtu2ydSMHzz4elFi Nn0VC9SaWz858JkTw/TWOz+l1VR5JTQZWUoya1P5dJ8Aq1D/8C0nDzMdBiJZDVBnGfGAjHhte73 6O7bszICz8DkgbQuazC/tWj4GvXjwUrV5CMVjT0yOcS87Xz0MNGwmsmQJDg/PpP3Hgvj5gNIcFa Fkh6ASgjYcLPIfkl0dSAGsPum2H/WPnI05Jz9Uk9RSGoqYkVMCZjpRNTWLDMkV1fZhOy8ML2VrS WOuEJipz8WtF/xKjuyz31/rokwmKTfOPvd77oco3BZJGg1zb9G2Y3w8z6+dwoNqWszbe2xxG+52 Jm45v2leqDrgP5Ze2kRAaqW/FHZD/o6gWA++b2WfN2+vFsn3LMo3GF1LOQa0vZs= X-Received: by 2002:a05:620a:31a8:b0:92e:e1d1:49dd with SMTP id af79cd13be357-9373957489amr268559585a.21.1787283737147; Thu, 20 Aug 2026 20:42:17 -0700 (PDT) From: Azharul Haque To: pve-devel@lists.proxmox.com Subject: [PATCH login-manager v3 2/5] refactor: ui: factor out shared login tail into _finishLogin Date: Thu, 20 Aug 2026 23:41:41 -0400 Message-ID: <20260821034147.30194-5-haque@azharul.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260821034147.30194-1-haque@azharul.com> References: <20260810144713.75806-1-haque@azharul.com> <20260821034147.30194-1-haque@azharul.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.450 Adjusted score from AWL reputation of From: address DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: haque@azharul.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation Message-ID-Hash: E2NESKURVCUDF5ZN6Q55EQWUS3RL7JRI X-Message-ID-Hash: E2NESKURVCUDF5ZN6Q55EQWUS3RL7JRI X-Mailman-Approved-At: Tue, 25 Aug 2026 10:08:31 +0200 CC: haque@azharul.com X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Move the code that runs after an authenticated API client exists -- handling a pending TFA challenge, fetching cluster status, persisting the login and closing the login page -- out of _onLoginButtonPressed into a _finishLogin method. No functional change. This prepares for the OpenID Connect login flow added in a following commit, which obtains its client through a browser-based flow instead of a password but finishes the login the same way. Suggested-by: Shan Shaji Signed-off-by: Azharul Haque --- lib/proxmox_login_form.dart | 203 ++++++++++++++++++++---------------- 1 file changed, 111 insertions(+), 92 deletions(-) diff --git a/lib/proxmox_login_form.dart b/lib/proxmox_login_form.dart index 5b9a64e..6039407 100644 --- a/lib/proxmox_login_form.dart +++ b/lib/proxmox_login_form.dart @@ -478,98 +478,13 @@ class _ProxmoxLoginPageState extends State { var client = await proxclient.authenticate( '$username@$realm', password, origin, settings.sslValidation!); - if (client.credentials.tfa != null && - client.credentials.tfa!.kinds().isNotEmpty) { - if (!mounted) return; - ProxmoxApiClient? tfaclient = - await Navigator.of(context).push(MaterialPageRoute( - builder: (context) => ProxmoxTfaForm( - apiClient: client, - ), - )); - - if (tfaclient != null) { - client = tfaclient; - } else { - setState(() { - _progressModel.inProgress -= 1; - }); - return; - } - } - - final status = await client.getClusterStatus(); - final hostname = - status.singleWhereOrNull((element) => element.local ?? false)?.name; - var loginStorage = await ProxmoxLoginStorage.fromLocalStorage(); - - final savePW = enteredPassword != '' && - _savePasswordCB && - enteredPassword != savedPassword; - final deletePW = enteredPassword != '' && !savePW && !_savePasswordCB; - String? id; - - if (widget.isCreate!) { - final newLogin = ProxmoxLoginModel((b) => b - ..origin = origin - ..username = username - ..realm = realm - ..productType = ProxmoxProductType.pve - ..ticket = client.credentials.ticket - ..passwordSaved = savePW - ..hostname = hostname); - - loginStorage = loginStorage!.rebuild((b) => b..logins.add(newLogin)); - id = newLogin.identifier; - } else { - loginStorage = loginStorage!.rebuild((b) => b - ..logins.rebuildWhere( - (m) => m == widget.userModel, - (b) => b - ..ticket = client.credentials.ticket - ..passwordSaved = - savePW || (deletePW ? false : b.passwordSaved ?? false) - ..hostname = hostname)); - id = widget.userModel!.identifier; - } - - if (id != null) { - try { - if (savePW) { - await savePassword(id, enteredPassword); - } else if (deletePW) { - await deletePassword(id); - } - } catch (e) { - if (!mounted) return; - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Password saving error'), - scrollable: true, - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Could not save or delete password.'), - ExpansionTile( - title: const Text('Details'), - children: [Text(e.toString())], - ) - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Continue')), - ], - )); - } - } - await loginStorage.saveToDisk(); - - if (mounted) { - Navigator.of(context).pop(client); - } + await _finishLogin( + client, + realm: realm!, + username: username, + enteredPassword: enteredPassword, + savedPassword: savedPassword, + ); } on proxclient.ProxmoxApiException catch (e) { print(e); if (!mounted) return; @@ -618,6 +533,110 @@ class _ProxmoxLoginPageState extends State { }); } + /// Common tail of the login flow once an authenticated client exists: + /// handles a pending TFA challenge, fetches cluster status, persists the + /// login and closes the login page. Returns early (without closing the + /// page) if the user cancels a TFA challenge. + Future _finishLogin( + ProxmoxApiClient client, { + required String realm, + required String username, + String enteredPassword = '', + String? savedPassword, + }) async { + if (client.credentials.tfa != null && + client.credentials.tfa!.kinds().isNotEmpty) { + if (!mounted) return; + ProxmoxApiClient? tfaclient = + await Navigator.of(context).push(MaterialPageRoute( + builder: (context) => ProxmoxTfaForm( + apiClient: client, + ), + )); + + if (tfaclient != null) { + client = tfaclient; + } else { + return; + } + } + + final status = await client.getClusterStatus(); + final hostname = + status.singleWhereOrNull((element) => element.local ?? false)?.name; + var loginStorage = await ProxmoxLoginStorage.fromLocalStorage(); + + final savePW = enteredPassword != '' && + _savePasswordCB && + enteredPassword != savedPassword; + final deletePW = enteredPassword != '' && !savePW && !_savePasswordCB; + String? id; + + final origin = normalizeUrl(_originController.text.trim()); + + if (widget.isCreate!) { + final newLogin = ProxmoxLoginModel((b) => b + ..origin = origin + ..username = username + ..realm = realm + ..productType = ProxmoxProductType.pve + ..ticket = client.credentials.ticket + ..passwordSaved = savePW + ..hostname = hostname); + + loginStorage = loginStorage!.rebuild((b) => b..logins.add(newLogin)); + id = newLogin.identifier; + } else { + loginStorage = loginStorage!.rebuild((b) => b + ..logins.rebuildWhere( + (m) => m == widget.userModel, + (b) => b + ..ticket = client.credentials.ticket + ..passwordSaved = + savePW || (deletePW ? false : b.passwordSaved ?? false) + ..hostname = hostname)); + id = widget.userModel!.identifier; + } + + if (id != null) { + try { + if (savePW) { + await savePassword(id, enteredPassword); + } else if (deletePW) { + await deletePassword(id); + } + } catch (e) { + if (!mounted) return; + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Password saving error'), + scrollable: true, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Could not save or delete password.'), + ExpansionTile( + title: const Text('Details'), + children: [Text(e.toString())], + ) + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Continue')), + ], + )); + } + } + await loginStorage.saveToDisk(); + + if (mounted) { + Navigator.of(context).pop(client); + } + } + Future?> _loadAccessDomains(Uri uri) async { final settings = await ProxmoxGeneralSettingsModel.fromLocalStorage(); List? response; -- 2.50.1 (Apple Git-155)