using System.Diagnostics.CodeAnalysis;
namespace DodoSSH.Client.Domain;
///
/// A username and password as the user sees it, decrypted.
///
///
///
/// The same bargain documents applies here and is worth not repeating in full:
/// the password is an ordinary managed string, it cannot be wiped, and a process dump taken while the vault
/// is unlocked contains it. What that buys is that it never reaches the disk or the server in a form either
/// can read.
///
///
/// is optional and overrides the host's when set, which is the reason this is a
/// separate item rather than two more fields on a host: one credential is very often the same account on
/// twenty machines, and duplicating it per host means rotating it in twenty places and missing one.
///
///
public sealed record CredentialSecret : IVaultSecret
{
private readonly string? username;
/// What the user calls this credential.
public required string Label { get; init; }
///
/// The password.
///
///
/// Required, and an empty one is not valid — see . A credential with no password
/// is not a credential, and storing one would produce an item that looks usable and fails at the
/// handshake with an error about authentication rather than about the vault.
///
public required string Password { get; init; }
///
/// The account this credential is for, when it is not the host's own username.
///
///
/// Empty is normalised to null, as is and for the same kind of
/// reason: blank and absent mean one thing here — "use the host's username" — and two spellings of one
/// state would give two clients different payload bytes for an identical credential, and make
/// Username is not null an unreliable answer to "does this override the host?".
///
public string? Username
{
get => username;
init => username = string.IsNullOrEmpty(value) ? null : value;
}
/// Free text.
public string? Notes { get; init; }
/// Whether this is storable, and why not if it is not.
public bool TryValidate([NotNullWhen(false)] out string? reason)
{
if (string.IsNullOrWhiteSpace(Label))
{
reason = "A credential needs a name.";
return false;
}
if (Password.Length == 0)
{
// Length rather than IsNullOrWhiteSpace: a password of spaces is a password, and refusing it
// would lock someone out of a host over a validation opinion.
reason = "A credential needs a password.";
return false;
}
reason = null;
return true;
}
}