Public Access
Spelunk sign-in: end it with a refused DodoSSH resume, keep the token the check rotated, and send the token by the sign-in's own address
A DodoSSH resume the provider refuses now signs the device out of every Spelunk as well, as the check before a Spelunk connection already did, so the README's "an account the provider no longer signs in is signed out of DodoSSH and of every Spelunk" holds whichever finds it first. The forced refresh that check makes is written down at once rather than at the next sync pass, so a crash in between cannot cost a browser sign-in on the next launch. The tunnel is opened at the sign-in's own address rather than the route's: the same string, but the token going where it came from is now said where it is sent. Relaunches in the shell tests use the scripted Spelunk sign-in too, so none can open a browser or send a sign-out anywhere real. Fixes a doc comment the account-refused handler had split, a README line still pointing at the key picker, and the Android composition's comment on which redirect Spelunk allows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
57d3d3c801
commit
abd7e56bb5
7 files changed
+84
-15
No files matched your search
@@ -3844,16 +3844,7 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
}
|
||||
catch (OidcException exception) when (IsRefusedGrant(exception))
|
||||
{
|
||||
// The provider answered and said no: the session was revoked, or the token was rotated and
|
||||
// this machine kept the old one. Retrying costs a round trip a minute and can only ever get
|
||||
// the same answer, so the token goes and the user is told the one thing that fixes it.
|
||||
await ForgetSignInAsync(cancellationToken).ConfigureAwait(true);
|
||||
|
||||
var message = $"Your sign-in has expired, so this machine is offline: {exception.Message} "
|
||||
+ "Sign in again from Preferences to start syncing.";
|
||||
|
||||
vault.OfflineReason = message;
|
||||
Announce(message);
|
||||
await OnResumeRefusedAsync(vault, exception, cancellationToken).ConfigureAwait(true);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -3872,6 +3863,45 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The provider answered a resume and said no.</summary>
|
||||
/// <remarks>
|
||||
/// The session was revoked, or the token was rotated and this machine kept the old one. Retrying costs a
|
||||
/// round trip a minute and can only ever get the same answer, so the token goes and the user is told the one
|
||||
/// thing that fixes it.
|
||||
/// </remarks>
|
||||
private async Task OnResumeRefusedAsync(VaultViewModel vault, OidcException exception, CancellationToken cancellationToken)
|
||||
{
|
||||
await ForgetSignInAsync(cancellationToken).ConfigureAwait(true);
|
||||
|
||||
// And with it every Spelunk this device is signed in to, as when the vault finds the account refused
|
||||
// before a Spelunk connection: a sign-in there is only ever used on behalf of a DodoSSH account that
|
||||
// still stands, and this one, for now, does not.
|
||||
var spelunks = await SignOutOfEverySpelunkQuietlyAsync(vault).ConfigureAwait(true);
|
||||
|
||||
var message = $"Your sign-in has expired, so this machine is offline: {exception.Message} "
|
||||
+ (spelunks ? "This device has been signed out of Spelunk too. " : string.Empty)
|
||||
+ "Sign in again from Preferences to start syncing.";
|
||||
|
||||
vault.OfflineReason = message;
|
||||
Announce(message);
|
||||
}
|
||||
|
||||
/// <summary>Signs out of every Spelunk, for a DodoSSH sign-in the provider refused; whether there were any.</summary>
|
||||
/// <remarks>Never throws: it runs where the refusal is already being reported, and a cache that will not take it is wiped on sign-out anyway.</remarks>
|
||||
private static async Task<bool> SignOutOfEverySpelunkQuietlyAsync(VaultViewModel vault)
|
||||
{
|
||||
try
|
||||
{
|
||||
var any = vault.HasSpelunkSignIns;
|
||||
await vault.SignOutOfEverySpelunkAsync(CancellationToken.None).ConfigureAwait(true);
|
||||
return any;
|
||||
}
|
||||
catch (Exception exception) when (exception is not OutOfMemoryException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether a failed resume was the provider refusing the token, rather than failing to answer.
|
||||
/// </summary>
|
||||
@@ -3913,7 +3943,6 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Drops the remembered sign-in, so nothing tries to resume it again.</summary>
|
||||
/// <summary>
|
||||
/// The identity provider refused this account when the vault checked it — before a Spelunk connection.
|
||||
/// </summary>
|
||||
@@ -3941,6 +3970,7 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
RaiseSyncState();
|
||||
}
|
||||
|
||||
/// <summary>Drops the remembered sign-in, so nothing tries to resume it again.</summary>
|
||||
private async Task ForgetSignInAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
rememberedToken = null;
|
||||
|
||||
Reference in new issue
Block a user