feat: test connection before hand

This commit is contained in:
AP 2019-12-14 13:43:33 +01:00
parent e91d5f156f
commit 37ec4a78d3
6 changed files with 275 additions and 166 deletions

View file

@ -1,13 +1,16 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { Input, SingleLine, Sub4Title } from '../../components/generic/Styled'; import { Input, SingleLine, Sub4Title } from '../../components/generic/Styled';
import { useAccount } from '../../context/AccountContext'; import { useAccount } from '../../context/AccountContext';
import { getConfigLnd, saveUserAuth } from '../../utils/auth'; import { getConfigLnd, saveUserAuth, getAuthString } from '../../utils/auth';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import { LoginButton, PasswordInput } from './Password'; import { LoginButton, PasswordInput } from './Password';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useLazyQuery } from '@apollo/react-hooks';
import { GET_CAN_CONNECT } from '../../graphql/query';
import { getErrorContent } from '../../utils/error';
interface AuthProps { interface AuthProps {
available?: number; available: number;
callback?: () => void; callback?: () => void;
} }
@ -20,9 +23,54 @@ export const BTCLoginForm = ({ available, callback }: AuthProps) => {
const [hasInfo, setHasInfo] = useState(false); const [hasInfo, setHasInfo] = useState(false);
const [isPass, setPass] = useState(''); const [isPass, setPass] = useState('');
if (!available) return null; const [tryToConnect, { data, loading }] = useLazyQuery(GET_CAN_CONNECT, {
onError: error => toast.error(getErrorContent(error)),
});
const canConnect = isJson !== '' && !!available; useEffect(() => {
if (!loading && data && data.getNodeInfo && data.getNodeInfo.alias) {
const { cert, macaroon, readMacaroon, host } = getConfigLnd(isJson);
if (!host) {
toast.error('Invalid connection credentials');
return;
}
const encryptedAdmin =
macaroon && isPass !== ''
? CryptoJS.AES.encrypt(macaroon, isPass).toString()
: undefined;
saveUserAuth({
available,
name: isName,
host,
admin: encryptedAdmin,
read: readMacaroon,
cert,
});
setAccount({
loggedIn: true,
host,
admin: macaroon,
read: readMacaroon,
cert,
});
toast.success('Connected!');
callback && callback();
}
}, [
data,
loading,
available,
callback,
isJson,
isName,
isPass,
setAccount,
]);
const handleClick = () => { const handleClick = () => {
try { try {
@ -34,66 +82,50 @@ export const BTCLoginForm = ({ available, callback }: AuthProps) => {
}; };
const handleConnect = () => { const handleConnect = () => {
const { cert, macaroon, readMacaroon, host } = getConfigLnd(isJson); const { cert, readMacaroon, host } = getConfigLnd(isJson);
const encryptedAdmin =
macaroon && isPass !== ''
? CryptoJS.AES.encrypt(macaroon, isPass).toString()
: undefined;
if (!host) { if (!host) {
toast.error('Invalid connection credentials'); toast.error('Invalid connection credentials');
return; return;
} }
saveUserAuth({ tryToConnect({
available, variables: { auth: getAuthString(host, readMacaroon, cert) },
name: isName,
host,
admin: encryptedAdmin,
read: readMacaroon,
cert,
}); });
setAccount({
loggedIn: true,
host,
admin: macaroon,
read: readMacaroon,
cert,
});
callback && callback();
}; };
const renderContent = () => ( const renderContent = () => {
<> const canConnect = isJson !== '' && !!available;
<SingleLine> return (
<Sub4Title>Name:</Sub4Title> <>
<Input onChange={e => setName(e.target.value)} /> <SingleLine>
</SingleLine> <Sub4Title>Name:</Sub4Title>
<SingleLine> <Input onChange={e => setName(e.target.value)} />
<Sub4Title>BTCPay Connect Url:</Sub4Title> </SingleLine>
<Input onChange={e => setJson(e.target.value)} /> <SingleLine>
</SingleLine> <Sub4Title>BTCPay Connect Url:</Sub4Title>
{canConnect && ( <Input onChange={e => setJson(e.target.value)} />
<LoginButton </SingleLine>
disabled={!canConnect} {canConnect && (
enabled={canConnect} <LoginButton
onClick={handleClick} disabled={!canConnect}
color={'yellow'} enabled={canConnect}
> onClick={handleClick}
Connect color={'yellow'}
</LoginButton> >
)} Connect
</> </LoginButton>
); )}
</>
);
};
return hasInfo ? ( return hasInfo ? (
<PasswordInput <PasswordInput
isPass={isPass} isPass={isPass}
setPass={setPass} setPass={setPass}
callback={handleConnect} callback={handleConnect}
loading={loading}
/> />
) : ( ) : (
renderContent() renderContent()

View file

@ -1,16 +1,21 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { Input, SingleLine, Sub4Title } from '../../components/generic/Styled'; import { Input, SingleLine, Sub4Title } from '../../components/generic/Styled';
import { useAccount } from '../../context/AccountContext'; import { useAccount } from '../../context/AccountContext';
import { import {
getAuthLnd, getAuthLnd,
getBase64CertfromDerFormat, getBase64CertfromDerFormat,
saveUserAuth, saveUserAuth,
getAuthString,
} from '../../utils/auth'; } from '../../utils/auth';
import { LoginButton, PasswordInput } from './Password'; import { LoginButton, PasswordInput } from './Password';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import { useLazyQuery } from '@apollo/react-hooks';
import { GET_CAN_CONNECT } from '../../graphql/query';
import { toast } from 'react-toastify';
import { getErrorContent } from '../../utils/error';
interface AuthProps { interface AuthProps {
available?: number; available: number;
callback?: () => void; callback?: () => void;
} }
@ -23,71 +28,86 @@ export const ConnectLoginForm = ({ available, callback }: AuthProps) => {
const [hasInfo, setHasInfo] = useState(false); const [hasInfo, setHasInfo] = useState(false);
const [isPass, setPass] = useState(''); const [isPass, setPass] = useState('');
if (!available) return null; const [tryToConnect, { data, loading }] = useLazyQuery(GET_CAN_CONNECT, {
onError: error => toast.error(getErrorContent(error)),
});
const canConnect = isUrl !== '' && !!available; useEffect(() => {
if (!loading && data && data.getNodeInfo && data.getNodeInfo.alias) {
const { cert, macaroon, socket } = getAuthLnd(isUrl);
const base64Cert = getBase64CertfromDerFormat(cert) || '';
const encryptedAdmin = CryptoJS.AES.encrypt(
macaroon,
isPass,
).toString();
saveUserAuth({
available,
name: isName,
host: socket,
admin: encryptedAdmin,
cert: base64Cert,
});
sessionStorage.setItem('session', macaroon);
setAccount({
loggedIn: true,
host: socket,
admin: encryptedAdmin,
read: macaroon,
cert: base64Cert,
});
toast.success('Connected!');
callback && callback();
}
}, [data, loading, available, callback, isName, isPass, isUrl, setAccount]);
const handleConnect = () => { const handleConnect = () => {
const { cert, macaroon, socket } = getAuthLnd(isUrl); const { cert, macaroon, socket } = getAuthLnd(isUrl);
const base64Cert = getBase64CertfromDerFormat(cert) || ''; const base64Cert = getBase64CertfromDerFormat(cert) || '';
const encryptedAdmin = CryptoJS.AES.encrypt( tryToConnect({
macaroon, variables: { auth: getAuthString(socket, macaroon, base64Cert) },
isPass,
).toString();
console.log(encryptedAdmin);
saveUserAuth({
available,
name: isName,
host: socket,
admin: encryptedAdmin,
cert: base64Cert,
}); });
sessionStorage.setItem('session', macaroon);
setAccount({
loggedIn: true,
host: socket,
admin: encryptedAdmin,
read: macaroon,
cert: base64Cert,
});
callback && callback();
}; };
const renderContent = () => ( const renderContent = () => {
<> const canConnect = isUrl !== '' && !!available;
<SingleLine> return (
<Sub4Title>Name:</Sub4Title> <>
<Input onChange={e => setName(e.target.value)} /> <SingleLine>
</SingleLine> <Sub4Title>Name:</Sub4Title>
<SingleLine> <Input onChange={e => setName(e.target.value)} />
<Sub4Title>LN Connect Url:</Sub4Title> </SingleLine>
<Input onChange={e => setUrl(e.target.value)} /> <SingleLine>
</SingleLine> <Sub4Title>LN Connect Url:</Sub4Title>
{canConnect && ( <Input onChange={e => setUrl(e.target.value)} />
<LoginButton </SingleLine>
disabled={!canConnect} {canConnect && (
enabled={canConnect} <LoginButton
onClick={() => setHasInfo(true)} disabled={!canConnect}
color={'yellow'} enabled={canConnect}
> onClick={() => setHasInfo(true)}
Connect color={'yellow'}
</LoginButton> >
)} Connect
</> </LoginButton>
); )}
</>
);
};
return hasInfo ? ( return hasInfo ? (
<PasswordInput <PasswordInput
isPass={isPass} isPass={isPass}
setPass={setPass} setPass={setPass}
callback={handleConnect} callback={handleConnect}
loading={loading}
/> />
) : ( ) : (
renderContent() renderContent()

View file

@ -1,13 +1,17 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { Input, SingleLine, Sub4Title } from '../generic/Styled'; import { Input, SingleLine, Sub4Title } from '../generic/Styled';
import { useAccount } from '../../context/AccountContext'; import { useAccount } from '../../context/AccountContext';
import { saveUserAuth } from '../../utils/auth'; import { saveUserAuth, getAuthString } from '../../utils/auth';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import base64url from 'base64url'; import base64url from 'base64url';
import { PasswordInput, LoginButton } from './Password'; import { PasswordInput, LoginButton } from './Password';
import { useLazyQuery } from '@apollo/react-hooks';
import { GET_CAN_CONNECT } from '../../graphql/query';
import { getErrorContent } from '../../utils/error';
import { toast } from 'react-toastify';
interface AuthProps { interface AuthProps {
available?: number; available: number;
callback?: () => void; callback?: () => void;
} }
@ -23,10 +27,9 @@ export const LoginForm = ({ available, callback }: AuthProps) => {
const [hasInfo, setHasInfo] = useState(false); const [hasInfo, setHasInfo] = useState(false);
const [isPass, setPass] = useState(''); const [isPass, setPass] = useState('');
if (!available) return null; const [tryToConnect, { data, loading }] = useLazyQuery(GET_CAN_CONNECT, {
onError: error => toast.error(getErrorContent(error)),
const canConnect = });
isName !== '' && isHost !== '' && isRead !== '' && !!available;
const handleClick = () => { const handleClick = () => {
if (isAdmin !== '') { if (isAdmin !== '') {
@ -36,76 +39,110 @@ export const LoginForm = ({ available, callback }: AuthProps) => {
} }
}; };
useEffect(() => {
if (!loading && data && data.getNodeInfo && data.getNodeInfo.alias) {
const admin = base64url.fromBase64(isAdmin);
const read = base64url.fromBase64(isRead);
const cert = base64url.fromBase64(isCert);
const encryptedAdmin =
admin && isPass !== ''
? CryptoJS.AES.encrypt(admin, isPass).toString()
: undefined;
saveUserAuth({
available,
name: isName,
host: isHost,
admin: encryptedAdmin,
read,
cert,
});
setAccount({
loggedIn: true,
host: isHost,
admin: encryptedAdmin,
read,
cert,
});
toast.success('Connected!');
callback && callback();
}
}, [
data,
loading,
available,
callback,
isAdmin,
isCert,
isHost,
isName,
isPass,
isRead,
setAccount,
]);
const handleConnect = () => { const handleConnect = () => {
const admin = base64url.fromBase64(isAdmin); const admin = base64url.fromBase64(isAdmin);
const read = base64url.fromBase64(isRead); const read = base64url.fromBase64(isRead);
const cert = base64url.fromBase64(isCert); const cert = base64url.fromBase64(isCert);
const encryptedAdmin = const correctMacaroon = read ? read : admin;
admin && isPass !== ''
? CryptoJS.AES.encrypt(admin, isPass).toString()
: undefined;
saveUserAuth({ tryToConnect({
available, variables: {
name: isName, auth: getAuthString(isHost, correctMacaroon, cert),
host: isHost, },
admin: encryptedAdmin,
read,
cert,
}); });
setAccount({
loggedIn: true,
host: isHost,
admin: encryptedAdmin,
read,
cert,
});
callback && callback();
}; };
const renderContent = () => ( const renderContent = () => {
<> const canConnect =
<SingleLine> isName !== '' && isHost !== '' && isRead !== '' && !!available;
<Sub4Title>Name:</Sub4Title> return (
<Input onChange={e => setName(e.target.value)} /> <>
</SingleLine> <SingleLine>
<SingleLine> <Sub4Title>Name:</Sub4Title>
<Sub4Title>Host:</Sub4Title> <Input onChange={e => setName(e.target.value)} />
<Input onChange={e => setHost(e.target.value)} /> </SingleLine>
</SingleLine> <SingleLine>
<SingleLine> <Sub4Title>Host:</Sub4Title>
<Sub4Title>Admin:</Sub4Title> <Input onChange={e => setHost(e.target.value)} />
<Input onChange={e => setAdmin(e.target.value)} /> </SingleLine>
</SingleLine> <SingleLine>
<SingleLine> <Sub4Title>Admin:</Sub4Title>
<Sub4Title>Readonly:</Sub4Title> <Input onChange={e => setAdmin(e.target.value)} />
<Input onChange={e => setRead(e.target.value)} /> </SingleLine>
</SingleLine> <SingleLine>
<SingleLine> <Sub4Title>Readonly:</Sub4Title>
<Sub4Title>Certificate:</Sub4Title> <Input onChange={e => setRead(e.target.value)} />
<Input onChange={e => setCert(e.target.value)} /> </SingleLine>
</SingleLine> <SingleLine>
{canConnect && ( <Sub4Title>Certificate:</Sub4Title>
<LoginButton <Input onChange={e => setCert(e.target.value)} />
disabled={!canConnect} </SingleLine>
enabled={canConnect} {canConnect && (
onClick={handleClick} <LoginButton
color={'yellow'} disabled={!canConnect}
> enabled={canConnect}
Connect onClick={handleClick}
</LoginButton> color={'yellow'}
)} >
</> Connect
); </LoginButton>
)}
</>
);
};
return hasInfo ? ( return hasInfo ? (
<PasswordInput <PasswordInput
isPass={isPass} isPass={isPass}
setPass={setPass} setPass={setPass}
callback={handleConnect} callback={handleConnect}
loading={loading}
/> />
) : ( ) : (
renderContent() renderContent()

View file

@ -9,6 +9,7 @@ import {
import zxcvbn from 'zxcvbn'; import zxcvbn from 'zxcvbn';
import styled from 'styled-components'; import styled from 'styled-components';
import { progressBackground, textColor } from '../../styles/Themes'; import { progressBackground, textColor } from '../../styles/Themes';
import { Loader } from '../generic/Icons';
const Progress = styled.div` const Progress = styled.div`
width: 80%; width: 80%;
@ -65,9 +66,15 @@ interface PasswordProps {
isPass: string; isPass: string;
setPass: (pass: string) => void; setPass: (pass: string) => void;
callback: () => void; callback: () => void;
loading: boolean;
} }
export const PasswordInput = ({ isPass, setPass, callback }: PasswordProps) => { export const PasswordInput = ({
isPass,
setPass,
callback,
loading = false,
}: PasswordProps) => {
const strength = (100 * Math.min(zxcvbn(isPass).guesses_log10, 40)) / 40; const strength = (100 * Math.min(zxcvbn(isPass).guesses_log10, 40)) / 40;
const needed = 1; const needed = 1;
return ( return (
@ -86,7 +93,7 @@ export const PasswordInput = ({ isPass, setPass, callback }: PasswordProps) => {
/> />
</Progress> </Progress>
</SingleLine> </SingleLine>
{strength >= needed && ( {strength >= needed && !loading && (
<LoginButton <LoginButton
disabled={strength < needed} disabled={strength < needed}
enabled={strength >= needed} enabled={strength >= needed}
@ -96,6 +103,11 @@ export const PasswordInput = ({ isPass, setPass, callback }: PasswordProps) => {
Connect Connect
</LoginButton> </LoginButton>
)} )}
{loading && (
<LoginButton disabled={true} color={'grey'}>
<Loader />
</LoginButton>
)}
</> </>
); );
}; };

View file

@ -99,7 +99,7 @@ export const ForwardChannelsReport = ({ isTime, isType, color }: Props) => {
const renderRoute = (parsed: {}[]) => { const renderRoute = (parsed: {}[]) => {
const routes = parsed.map((channel: any, index: number) => ( const routes = parsed.map((channel: any, index: number) => (
<ChannelRow> <ChannelRow key={index}>
<TableLine>{channel.aliasIn}</TableLine> <TableLine>{channel.aliasIn}</TableLine>
<TableLine>{channel.aliasOut}</TableLine> <TableLine>{channel.aliasOut}</TableLine>
<LastTableLine> <LastTableLine>

View file

@ -15,6 +15,14 @@ export const GET_NETWORK_INFO = gql`
} }
`; `;
export const GET_CAN_CONNECT = gql`
query GetNodeInfo($auth: String!) {
getNodeInfo(auth: $auth) {
alias
}
}
`;
export const GET_NODE_INFO = gql` export const GET_NODE_INFO = gql`
query GetNodeInfo($auth: String!) { query GetNodeInfo($auth: String!) {
getNodeInfo(auth: $auth) { getNodeInfo(auth: $auth) {