Public Access
Let the recovery code be copied, and give the phone a clipboard to copy to
Both screens had made the code selectable and both said why: a person who cannot get it out of the box photographs the screen, and a screenshot is a worse home for it than a clipboard. This finishes that argument. Selecting 64 characters of letter-spaced monospace with a thumb is the version of "possible" people give up on halfway — and on the phone the screen blocks screenshots, so the honest remaining options were retyping it or losing it. It is the one secret this application deliberately offers to a clipboard, and the contrast with the keychain's copy is the whole argument rather than an inconsistency. There, copying the private half is refused outright, because installing a key means pasting the public one and the private one has no business leaving the vault. Here there is no better route: the code exists for one screen, is stored nowhere, and has to reach a password manager. The clipboard is the intended destination rather than a way round the design. The sentence afterwards matters as much as the copy, and is asserted: a clipboard is a staging post, this screen is the only place the code exists, and the next thing copied replaces it. Somebody who copies and does nothing has not saved it. The phone had no clipboard delegate at all — the desktop passed one and this head passed null — so COPY PUBLIC KEY on the keychain answered "this machine has no clipboard" on a device that plainly has one. Nothing about that was platform shaped: Android has a clipboard and Avalonia surfaces it through the same TopLevel. Wiring it fixes that copy too. The test fixture built its shell without a clipboard, which modelled the bug rather than the product, so it has one now and the public-key test asserts what lands there instead of the refusal. The refusal keeps its own test, on a shell built without one, because the view model reads the delegate's absence rather than an empty result — and because a button that silently does nothing on this screen is worse than one that refuses.
This commit is contained in:
@@ -1848,6 +1848,52 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
}).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the recovery code on the clipboard.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// ◆ <b>The one secret in this application that is deliberately offered to the clipboard, and the
|
||||
/// contrast with <c>VaultViewModel.CopyPublicKeyAsync</c> is the whole argument.</b> There, copying the
|
||||
/// <em>private</em> key is refused outright, because installing a key means pasting the public half and
|
||||
/// the private one has no business leaving the vault. Here there is no better route: the code exists for
|
||||
/// one screen, is stored nowhere, and has to reach a password manager — so the clipboard is the intended
|
||||
/// destination rather than a way around the design.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Both screens already made the code selectable, and both said why: a person who cannot get it out of
|
||||
/// the box photographs the screen, and a screenshot is a far worse home for it than a clipboard. This is
|
||||
/// that argument finished. Selecting a monospaced, letter-spaced string with a thumb is the version of
|
||||
/// "possible" that people give up on.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It says what it did, including the case where there is nothing to say it to — a machine with no
|
||||
/// clipboard has to be told so rather than left with a button that appears to do nothing, which is the
|
||||
/// same rule the keychain's copy already follows. And the sentence names what has to happen next,
|
||||
/// because a clipboard is not somewhere a recovery code may stay: this screen is the only moment it
|
||||
/// exists, and the next thing copied replaces it.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[RelayCommand]
|
||||
private async Task CopyRecoveryCodeAsync()
|
||||
{
|
||||
if (RecoveryCode is not { Length: > 0 } code)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (copyToClipboard is null)
|
||||
{
|
||||
StatusMessage = "This machine has no clipboard. Select the code and copy it by hand.";
|
||||
return;
|
||||
}
|
||||
|
||||
await copyToClipboard(code).ConfigureAwait(true);
|
||||
|
||||
StatusMessage = "Copied. Paste it into your password manager now — this screen is the only place "
|
||||
+ "it exists, and the next thing you copy replaces it.";
|
||||
}
|
||||
|
||||
/// <summary>Leaves the recovery-code screen, once the user says they have it.</summary>
|
||||
[RelayCommand]
|
||||
private void ConfirmRecoveryCode()
|
||||
|
||||
Reference in New Issue
Block a user