From ffddd9e8a5cdd04b51ee4183f56da990a7aa792e Mon Sep 17 00:00:00 2001 From: Joseph Livecchi <joewashear007@gmail.com> Date: Sat, 4 May 2024 15:22:18 -0400 Subject: [PATCH] Added Header Authentication - Client Part (#2362) * Updated Login to handle header auth --- .../src/components/manager/ManagementApp.jsx | 2 +- .../components/manager/subscribe/Login.tsx | 109 ++++++++++++++---- .../components/manager/subscribe/common.tsx | 11 +- packages/loot-core/src/server/main.ts | 12 +- .../loot-core/src/types/server-handlers.d.ts | 5 +- upcoming-release-notes/2362.md | 6 + 6 files changed, 113 insertions(+), 32 deletions(-) create mode 100644 upcoming-release-notes/2362.md diff --git a/packages/desktop-client/src/components/manager/ManagementApp.jsx b/packages/desktop-client/src/components/manager/ManagementApp.jsx index c5200986f..2fb3e9165 100644 --- a/packages/desktop-client/src/components/manager/ManagementApp.jsx +++ b/packages/desktop-client/src/components/manager/ManagementApp.jsx @@ -184,7 +184,7 @@ export function ManagementApp({ isLoading }) { </> ) : ( <Routes> - <Route path="/login" element={<Login />} /> + <Route path="/login/:method?" element={<Login />} /> <Route path="/error" element={<Error />} /> <Route path="/config-server" element={<ConfigServer />} /> <Route path="/bootstrap" element={<Bootstrap />} /> diff --git a/packages/desktop-client/src/components/manager/subscribe/Login.tsx b/packages/desktop-client/src/components/manager/subscribe/Login.tsx index 45cc2405d..15871a5ba 100644 --- a/packages/desktop-client/src/components/manager/subscribe/Login.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/Login.tsx @@ -1,14 +1,17 @@ // @ts-strict-ignore -import React, { type ChangeEvent, useState } from 'react'; +import React, { type ChangeEvent, useState, useEffect } from 'react'; import { useDispatch } from 'react-redux'; +import { useParams, useSearchParams } from 'react-router-dom'; import { createBudget } from 'loot-core/src/client/actions/budgets'; import { loggedIn } from 'loot-core/src/client/actions/user'; import { send } from 'loot-core/src/platform/client/fetch'; +import { AnimatedLoading } from '../../../icons/AnimatedLoading'; import { theme } from '../../../style'; import { Button, ButtonWithLoading } from '../../common/Button'; import { BigInput } from '../../common/Input'; +import { Link } from '../../common/Link'; import { Text } from '../../common/Text'; import { View } from '../../common/View'; @@ -16,14 +19,41 @@ import { useBootstrapped, Title } from './common'; export function Login() { const dispatch = useDispatch(); + const { method = 'password' } = useParams(); + const [searchParams, _setSearchParams] = useSearchParams(); const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(searchParams.get('error')); + const { checked } = useBootstrapped(!searchParams.has('error')); - const { checked } = useBootstrapped(); + useEffect(() => { + if (checked && !searchParams.has('error')) { + (async () => { + if (method === 'header') { + setError(null); + setLoading(true); + const { error } = await send('subscribe-sign-in', { + password: '', + loginMethod: method, + }); + setLoading(false); + + if (error) { + setError(error); + } else { + dispatch(loggedIn()); + } + } + })(); + } + }, [checked, searchParams, method, dispatch]); function getErrorMessage(error) { switch (error) { + case 'invalid-header': + return 'Auto login failed - No header sent'; + case 'proxy-not-trusted': + return 'Auto login failed - Proxy not trusted'; case 'invalid-password': return 'Invalid password'; case 'network-failure': @@ -41,7 +71,10 @@ export function Login() { setError(null); setLoading(true); - const { error } = await send('subscribe-sign-in', { password }); + const { error } = await send('subscribe-sign-in', { + password, + loginMethod: method, + }); setLoading(false); if (error) { @@ -86,27 +119,55 @@ export function Login() { </Text> )} - <form - style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }} - onSubmit={onSubmit} - > - <BigInput - autoFocus={true} - placeholder="Password" - type="password" - onChange={(e: ChangeEvent<HTMLInputElement>) => - setPassword(e.target.value) - } - style={{ flex: 1, marginRight: 10 }} - /> - <ButtonWithLoading - type="primary" - loading={loading} - style={{ fontSize: 15 }} + {method === 'password' && ( + <form + style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }} + onSubmit={onSubmit} > - Sign in - </ButtonWithLoading> - </form> + <BigInput + autoFocus={true} + placeholder="Password" + type="password" + onChange={(e: ChangeEvent<HTMLInputElement>) => + setPassword(e.target.value) + } + style={{ flex: 1, marginRight: 10 }} + /> + <ButtonWithLoading + type="primary" + loading={loading} + style={{ fontSize: 15 }} + > + Sign in + </ButtonWithLoading> + </form> + )} + {method === 'header' && ( + <View + style={{ + flexDirection: 'row', + justifyContent: 'center', + marginTop: 15, + }} + > + {error && ( + <Link + variant="button" + type="primary" + style={{ fontSize: 15 }} + to={'/login/password?error=' + error} + > + Login with Password + </Link> + )} + {!error && ( + <span> + Checking Header Token Login ...{' '} + <AnimatedLoading style={{ width: 20, height: 20 }} /> + </span> + )} + </View> + )} <View style={{ flexDirection: 'row', diff --git a/packages/desktop-client/src/components/manager/subscribe/common.tsx b/packages/desktop-client/src/components/manager/subscribe/common.tsx index 8ebecc3e7..929b4bbbe 100644 --- a/packages/desktop-client/src/components/manager/subscribe/common.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/common.tsx @@ -17,7 +17,7 @@ import { useSetServerURL } from '../../ServerContext'; // password. Both pages will redirect to the other depending on state; // they will also potentially redirect to other pages which do *not* // do any checks. -export function useBootstrapped() { +export function useBootstrapped(redirect = true) { const [checked, setChecked] = useState(false); const navigate = useNavigate(); const location = useLocation(); @@ -27,7 +27,9 @@ export function useBootstrapped() { async function run() { const ensure = url => { if (location.pathname !== url) { - navigate(url); + if (redirect) { + navigate(url); + } } else { setChecked(true); } @@ -41,6 +43,7 @@ export function useBootstrapped() { const result = await send('subscribe-needs-bootstrap', { url: serverURL, }); + if ('error' in result || !result.hasServer) { console.log('error' in result && result.error); navigate('/config-server'); @@ -50,7 +53,7 @@ export function useBootstrapped() { await setServerURL(serverURL, { validate: false }); if (result.bootstrapped) { - ensure('/login'); + ensure(`/login/${result.loginMethod}`); } else { ensure('/bootstrap'); } @@ -59,7 +62,7 @@ export function useBootstrapped() { if ('error' in result) { navigate('/error', { state: { error: result.error } }); } else if (result.bootstrapped) { - ensure('/login'); + ensure(`/login/${result.loginMethod}`); } else { ensure('/bootstrap'); } diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index 59e8feb2e..01ec57e50 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1410,7 +1410,11 @@ handlers['subscribe-needs-bootstrap'] = async function ({ return { error: res.reason }; } - return { bootstrapped: res.data.bootstrapped, hasServer: true }; + return { + bootstrapped: res.data.bootstrapped, + loginMethod: res.data.loginMethod || 'password', + hasServer: true, + }; }; handlers['subscribe-bootstrap'] = async function ({ password }) { @@ -1482,11 +1486,15 @@ handlers['subscribe-change-password'] = async function ({ password }) { return {}; }; -handlers['subscribe-sign-in'] = async function ({ password }) { +handlers['subscribe-sign-in'] = async function ({ password, loginMethod }) { + if (typeof loginMethod !== 'string' || loginMethod == null) { + loginMethod = 'password'; + } let res; try { res = await post(getServer().SIGNUP_SERVER + '/login', { + loginMethod, password, }); } catch (err) { diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index f03c088aa..78c51f399 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -264,7 +264,10 @@ export interface ServerHandlers { password; }) => Promise<{ error?: string }>; - 'subscribe-sign-in': (arg: { password }) => Promise<{ error?: string }>; + 'subscribe-sign-in': (arg: { + password; + loginMethod?: string; + }) => Promise<{ error?: string }>; 'subscribe-sign-out': () => Promise<'ok'>; diff --git a/upcoming-release-notes/2362.md b/upcoming-release-notes/2362.md new file mode 100644 index 000000000..e50069fe2 --- /dev/null +++ b/upcoming-release-notes/2362.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [joewashear007] +--- + +Add option to authenticate with HTTP header `X-ACTUAL-PASSWORD` -- GitLab