mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: finish a webmail hand-off with a TOTP code instead of failing
This commit is contained in:
@@ -140,12 +140,27 @@ export default function LoginScreen({ onLogin, isAddMode = false, onCancel }: Lo
|
||||
// that's a cancellation, not a failure, so leave them where they were.
|
||||
finishIfSignedIn(wasAuthenticated);
|
||||
} catch (err) {
|
||||
// The webmail hands back the password even when the account needs a
|
||||
// second factor; finish the sign-in here with a code instead of
|
||||
// failing (the user already proved the password once).
|
||||
const pending = useAuthStore.getState().pendingTotpLogin;
|
||||
if (err instanceof Error && err.name === 'TotpRequiredError' && pending) {
|
||||
setEmail(pending.username);
|
||||
setPassword(pending.password);
|
||||
setServerUrl(pending.serverUrl);
|
||||
setTotpRequired(true);
|
||||
setBusy(null);
|
||||
setNotice(describeLoginError(err, { serverUrl: target }));
|
||||
setHistory((prev) => [...prev, step]);
|
||||
setStep('password');
|
||||
return;
|
||||
}
|
||||
setNotice(describeLoginError(err, { serverUrl: target }));
|
||||
} finally {
|
||||
setBusy(null);
|
||||
}
|
||||
},
|
||||
[finishIfSignedIn, isAddMode, loginViaWebmail],
|
||||
[finishIfSignedIn, isAddMode, loginViaWebmail, step],
|
||||
);
|
||||
|
||||
const handleEmailContinue = React.useCallback(async () => {
|
||||
|
||||
@@ -41,6 +41,12 @@ export interface AuthState {
|
||||
accountId: string | null;
|
||||
activeAccountId: string | null;
|
||||
client: typeof jmapClient | null;
|
||||
/**
|
||||
* Set when a password sign-in was refused with "MFA code required"; the
|
||||
* login screen re-runs it with a code via `login(..., { totp })`. Cleared
|
||||
* on the next successful sign-in.
|
||||
*/
|
||||
pendingTotpLogin: { serverUrl: string; username: string; password: string } | null;
|
||||
|
||||
login: (
|
||||
serverUrl: string,
|
||||
@@ -232,6 +238,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
accountId: null,
|
||||
activeAccountId: null,
|
||||
client: null,
|
||||
pendingTotpLogin: null,
|
||||
|
||||
login: async (serverUrl, username, password, opts) => {
|
||||
set({ isLoading: true, error: null });
|
||||
@@ -270,8 +277,14 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
useEmailStore.getState().setActiveAccount(accountId);
|
||||
|
||||
applyConnectedState(set, session, serverUrl.replace(/\/+$/, ''), username, accountId);
|
||||
set({ pendingTotpLogin: null });
|
||||
void syncAccountDisplayName(accountId);
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.name === 'TotpRequiredError') {
|
||||
// Keep what the user (or the webmail hand-off) supplied so the code
|
||||
// step doesn't make them retype the password.
|
||||
set({ pendingTotpLogin: { serverUrl: serverUrl.replace(/\/+$/, ''), username, password } });
|
||||
}
|
||||
const message = err instanceof Error && err.name === 'TotpRequiredError'
|
||||
? 'Two-factor code required'
|
||||
: err instanceof AuthenticationError
|
||||
|
||||
Reference in New Issue
Block a user