added support for 2fa login with browser flow

This commit is contained in:
2026-08-10 20:00:38 +02:00
parent 889d8f67c5
commit ccb22a497d
13 changed files with 805 additions and 35 deletions
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../../model/account_data.dart';
import '../../http_errors.dart';
import '../nextcloud_ocs.dart';
/// Exchanges the user's real Nextcloud password for a scoped app password via
@@ -18,21 +19,29 @@ class GetAppPassword {
GetAppPassword({http.Client? client}) : _client = client ?? http.Client();
/// Returns the freshly minted app password. Throws on any transport or
/// protocol error — callers treat push registration as best-effort and swallow
/// failures.
/// protocol error — a 401 becomes an [AuthException], which the login flow
/// reads as "Nextcloud rejects the password" (two-factor authentication or
/// password mismatch) and answers with the interactive Login Flow v2.
Future<String> run() async {
final response = await _client.get(
NextcloudOcs.uri('core/getapppassword'),
headers: {
...NextcloudOcs.headers(),
// Deliberately NOT the shared Authorization value: that one prefers
// the app password, but an app password cannot mint another one —
// this endpoint requires the real password.
'Authorization': AccountData().getRealPasswordBasicAuthHeader(),
},
);
const label = 'Nextcloud getapppassword';
final response = (await sendGuarded(
label,
() => _client.get(
NextcloudOcs.uri('core/getapppassword'),
headers: {
...NextcloudOcs.headers(),
// Deliberately NOT the shared Authorization value: that one prefers
// the app password, but an app password cannot mint another one —
// this endpoint requires the real password.
'Authorization': AccountData().getRealPasswordBasicAuthHeader(),
},
),
))!;
if (response.statusCode < 200 || response.statusCode >= 300) {
throw Exception('getapppassword HTTP ${response.statusCode}');
throwForStatus(
response.statusCode,
httpErrorDetail(label, response.body, response.statusCode),
);
}
final json = jsonDecode(utf8.decode(response.bodyBytes));
final data = (json as Map)['ocs']?['data'];