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; } }