78 lines
3.0 KiB
Dart
78 lines
3.0 KiB
Dart
import 'dart:convert';
|
|
|
|
/// Nextcloud identity of a session. Immutable; the session manager swaps in a
|
|
/// new instance whenever an app password is minted or revoked.
|
|
class NextcloudCredentials {
|
|
final String username;
|
|
|
|
/// The real account password. Invalid against Nextcloud when
|
|
/// [usesLoginFlow] is set (2FA accounts), where only [appPassword] works.
|
|
final String password;
|
|
final String? appPassword;
|
|
|
|
/// Backs the second (apptype=talk) push registration — Nextcloud binds each
|
|
/// push subscription to its session token, so two registrations need two
|
|
/// app passwords.
|
|
final String? appPasswordTalk;
|
|
|
|
/// True when the credentials came from Login Flow v2 (browser login, e.g.
|
|
/// because the account has two-factor authentication).
|
|
final bool usesLoginFlow;
|
|
|
|
const NextcloudCredentials({
|
|
required this.username,
|
|
required this.password,
|
|
this.appPassword,
|
|
this.appPasswordTalk,
|
|
this.usesLoginFlow = false,
|
|
});
|
|
|
|
bool get hasAppPassword => appPassword != null && appPassword!.isNotEmpty;
|
|
|
|
bool get hasAppPasswordTalk =>
|
|
appPasswordTalk != null && appPasswordTalk!.isNotEmpty;
|
|
|
|
/// The app password once available (minted or flow-issued), otherwise the
|
|
/// real password. It survives real-password rotation and is what the push
|
|
/// registration is bound to.
|
|
String get secret => hasAppPassword ? appPassword! : password;
|
|
|
|
/// HTTP Basic header value. Prefer headers over credentials in URLs — error
|
|
/// logs and crash reports often capture the URL but not headers.
|
|
String get basicAuthHeader => _basicAuth(secret);
|
|
|
|
Map<String, String> get authHeaders => {'Authorization': basicAuthHeader};
|
|
|
|
/// Authenticates the apptype=talk push registration (and its unregister).
|
|
/// Throws when the talk password has not been minted yet; callers treat that
|
|
/// as a failed talk registration and retry on the next start.
|
|
String get talkBasicAuthHeader {
|
|
if (hasAppPasswordTalk) return _basicAuth(appPasswordTalk!);
|
|
// Login-flow account whose second (talk) flow pass was skipped: no silent
|
|
// minting possible, the talk registration shares the flow credential.
|
|
if (usesLoginFlow && hasAppPassword) return _basicAuth(appPassword!);
|
|
throw StateError('Talk app password not available yet');
|
|
}
|
|
|
|
/// Always the real password. Needed to mint the app password via
|
|
/// `core/getapppassword` — an app password cannot mint another.
|
|
String get realPasswordBasicAuthHeader => _basicAuth(password);
|
|
|
|
NextcloudCredentials copyWith({
|
|
String? Function()? appPassword,
|
|
String? Function()? appPasswordTalk,
|
|
bool? usesLoginFlow,
|
|
}) => NextcloudCredentials(
|
|
username: username,
|
|
password: password,
|
|
appPassword: appPassword != null ? appPassword() : this.appPassword,
|
|
appPasswordTalk: appPasswordTalk != null
|
|
? appPasswordTalk()
|
|
: this.appPasswordTalk,
|
|
usesLoginFlow: usesLoginFlow ?? this.usesLoginFlow,
|
|
);
|
|
|
|
String _basicAuth(String secret) =>
|
|
'Basic ${base64Encode(utf8.encode('$username:$secret'))}';
|
|
}
|