namespace DodoSSH.Client.Auth;
/// What a token exchange returned.
///
/// The refresh token is the one long-lived credential the client holds, and the only part of this
/// that belongs in the OS keystore. The access token is short-lived and re-obtainable, and the ID
/// token is an assertion rather than a credential. None of them can open the vault: that needs the
/// passphrase, which is never stored anywhere.
///
/// Bearer token for the DodoSSH API.
/// Refresh token, when offline_access was granted.
/// Identity assertion, when openid was requested.
/// When the access token stops being accepted.
/// Scopes actually granted, which may be narrower than those requested.
public sealed record TokenSet(
string AccessToken,
string? RefreshToken,
string? IdToken,
DateTimeOffset ExpiresAtUtc,
string? Scope)
{
///
/// Whether the access token should be refreshed before use.
///
///
/// The margin exists because expiry is checked here and enforced by the server after a network
/// round trip. Without it a token that is valid at the moment of the check is rejected by the
/// time it arrives, which surfaces as a random 401 mid-sync.
///
/// Time source.
/// How far ahead to consider the token already expired.
public bool NeedsRefresh(TimeProvider clock, TimeSpan? margin = null)
{
ArgumentNullException.ThrowIfNull(clock);
return clock.GetUtcNow() + (margin ?? TimeSpan.FromSeconds(60)) >= ExpiresAtUtc;
}
}