Files
cinny/src/app/pages/KeychainUnavailable.tsx
T
Lotus CIandClaude Opus 5.5 5f3c5d370a
CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Build & Quality Checks (pull_request) Successful in 3m50s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 12s
CI / Playwright smoke (e2e) (pull_request) Successful in 20m30s
feat(desktop): keep the login tokens only in the OS keychain (#105 step 2)
On the desktop app with a working keychain (Windows Credential Manager),
the tokens leave localStorage: it keeps a token-less shell blob
(tokensIn: keychain) and the full session lives in memory, read from the
keychain at boot before anything reads the session, so getFallbackSession
stays synchronous. Web, Linux and older desktop builds are unchanged.

- A new session (login, token refresh) is written to localStorage in full
  first; the mirror verifies the keychain copy and only then strips the
  tokens, so one durable store always holds the newest tokens.
- Existing installs migrate on first boot the same way; a failing keychain
  write keeps the tokens in localStorage.
- If a shell is there but the keychain entry is missing or unreadable, the
  app shows Retry / Sign in again instead of logging out, and the mirror
  is not started (it would clear the entry).

Fixes #105

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-23 20:02:35 -04:00

70 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react';
import { Box, Button, Text, config } from 'folds';
import { removeFallbackSession } from '../state/sessions';
import type { KeychainBootResult } from '../state/keychainSession';
import type { KeychainInvoke } from '../state/keychainMirror';
/**
* [Gitea #105] Shown instead of the app when the desktop app's sign-in is kept
* in the OS keychain but couldn't be read this time. Never a silent logout:
* Retry boots again; signing in again is an explicit choice.
*/
export function KeychainUnavailable({
boot,
invoke,
}: {
boot: KeychainBootResult;
invoke: KeychainInvoke | undefined;
}) {
const [busy, setBusy] = useState(false);
const missing = boot.mode === 'missing';
const signInAgain = async () => {
setBusy(true);
removeFallbackSession();
await invoke?.('secure_session_clear').catch(() => undefined);
window.location.reload();
};
return (
<Box
direction="Column"
alignItems="Center"
justifyContent="Center"
gap="400"
style={{ minHeight: '100vh', padding: config.space.S700, textAlign: 'center' }}
>
<Text size="H3">Couldn&apos;t read your sign-in</Text>
<Text size="T300" style={{ maxWidth: 460 }}>
{missing
? 'Lotus Chat keeps your sign-in in Windows Credential Manager, but the entry is missing. If you removed it, sign in again.'
: 'Lotus Chat keeps your sign-in in Windows Credential Manager and couldn’t read it just now. This is usually temporary.'}
</Text>
{boot.mode === 'error' && (
<Text size="T200" priority="300">
{boot.error}
</Text>
)}
<Box gap="200">
<Button
variant="Primary"
size="400"
radii="300"
disabled={busy}
onClick={() => window.location.reload()}
>
<Text size="B400">Retry</Text>
</Button>
<Button
variant="Secondary"
fill="Soft"
size="400"
radii="300"
disabled={busy}
onClick={signInAgain}
>
<Text size="B400">Sign in again</Text>
</Button>
</Box>
</Box>
);
}