mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
1) useState call is not destructured into value + setter pair. 2) A fragment with only one child is redundant. 3) Unnecessary '.apply()' and '.call()'. 4) Expected the Promise rejection reason to be an Error.
132 lines
4.7 KiB
JavaScript
132 lines
4.7 KiB
JavaScript
/////////////////////////////////////////////////////////////
|
|
//
|
|
// pgAdmin 4 - PostgreSQL Tools
|
|
//
|
|
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
|
|
// This software is released under the PostgreSQL Licence
|
|
//
|
|
//////////////////////////////////////////////////////////////
|
|
import React, { useState } from 'react';
|
|
import LoginImage from '../../img/login.svg?svgr';
|
|
import { InputSelect, InputText, MESSAGE_TYPE, NotifierMessage } from '../components/FormComponents';
|
|
import BasePage, { SecurityButton } from './BasePage';
|
|
import { useDelayedCaller } from '../custom_hooks';
|
|
import gettext from 'sources/gettext';
|
|
import PropTypes from 'prop-types';
|
|
|
|
function EmailValidateView({mfaView, sendEmailUrl, csrfHeader, csrfToken}) {
|
|
const [inputValue, setInputValue] = useState('');
|
|
const [error, setError] = useState('');
|
|
const [sentMessage, setSentMessage] = useState('');
|
|
const [sending, setSending] = useState(false);
|
|
const [showResend, setShowResend] = useState(false);
|
|
|
|
const showResendAfter = useDelayedCaller(()=>{
|
|
setShowResend(true);
|
|
});
|
|
|
|
const sendCodeToEmail = ()=>{
|
|
setSending(true);
|
|
let accept = 'text/html; charset=utf-8;';
|
|
|
|
fetch(sendEmailUrl, {
|
|
method: 'POST',
|
|
mode: 'cors',
|
|
cache: 'no-cache',
|
|
headers: {
|
|
'Accept': accept,
|
|
'Content-Type': 'application/json; charset=utf-8;',
|
|
[csrfHeader]: csrfToken,
|
|
},
|
|
redirect: 'follow'
|
|
}).then((resp) => {
|
|
if (!resp.ok) {
|
|
resp.text().then(msg => setError(msg));
|
|
return;
|
|
}
|
|
return resp.json();
|
|
}).then((resp) => {
|
|
if (!resp)
|
|
return;
|
|
setSentMessage(resp.message);
|
|
showResendAfter(20000);
|
|
}).finally(()=>{
|
|
setSending(false);
|
|
});
|
|
};
|
|
|
|
return <>
|
|
<div style={{textAlign: 'center'}} data-test="email-validate-view">{mfaView.description}</div>
|
|
{sentMessage && <>
|
|
<div>{sentMessage}</div>
|
|
{showResend && <div>
|
|
<span>{gettext('Haven\'t received an email?')} <a style={{color:'inherit', fontWeight: 'bold'}} href="#" onClick={sendCodeToEmail}>{gettext('Send again')}</a></span>
|
|
</div>}
|
|
<InputText value={inputValue} type="password" name="code" placeholder={mfaView.otp_placeholder}
|
|
onChange={setInputValue} autoFocus
|
|
/>
|
|
<SecurityButton value='Validate'>{gettext('Validate')}</SecurityButton>
|
|
</>}
|
|
{error && <NotifierMessage message={error} type={MESSAGE_TYPE.ERROR} closable={false} />}
|
|
{!sentMessage &&
|
|
<SecurityButton type="button" name="send_code" onClick={sendCodeToEmail} disabled={sending}>
|
|
{sending ? mfaView.button_label_sending : mfaView.button_label}
|
|
</SecurityButton>}
|
|
</>;
|
|
}
|
|
|
|
EmailValidateView.propTypes = {
|
|
mfaView: PropTypes.object,
|
|
sendEmailUrl: PropTypes.string,
|
|
csrfHeader: PropTypes.string,
|
|
csrfToken: PropTypes.string
|
|
};
|
|
|
|
function AuthenticatorValidateView({mfaView}) {
|
|
const [inputValue, setInputValue] = useState('');
|
|
|
|
return <>
|
|
<div data-test="auth-validate-view">{mfaView.auth_description}</div>
|
|
<InputText value={inputValue} type="password" name="code" placeholder={mfaView.otp_placeholder}
|
|
onChange={setInputValue} autoFocus
|
|
/>
|
|
<SecurityButton value='Validate'>{gettext('Validate')}</SecurityButton>
|
|
</>;
|
|
}
|
|
|
|
AuthenticatorValidateView.propTypes = {
|
|
mfaView: PropTypes.object,
|
|
};
|
|
|
|
export default function MfaValidatePage({actionUrl, views, logoutUrl, sendEmailUrl, csrfHeader, csrfToken, ...props}) {
|
|
const [method, setMethod] = useState(Object.values(views).find((v)=>v.selected)?.id);
|
|
return (
|
|
<BasePage title={gettext('Authentication')} pageImage={<LoginImage style={{height: '100%', width: '100%'}} />} {...props}>
|
|
<form style={{display:'flex', gap:'15px', flexDirection:'column', minHeight: 0}} action={actionUrl} method="POST">
|
|
<InputSelect value={method} options={Object.keys(views).map((k)=>({
|
|
label: views[k].label,
|
|
value: views[k].id,
|
|
imageUrl: views[k].icon
|
|
}))} onChange={setMethod} controlProps={{
|
|
allowClear: false,
|
|
}} />
|
|
<div><input type='hidden' name='mfa_method' defaultValue={method} /></div>
|
|
{method == 'email' && <EmailValidateView mfaView={views[method].view} sendEmailUrl={sendEmailUrl} csrfHeader={csrfHeader} csrfToken={csrfToken} />}
|
|
{method == 'authenticator' && <AuthenticatorValidateView mfaView={views[method].view} />}
|
|
<div style={{textAlign: 'right'}}>
|
|
<a style={{color:'inherit'}} href={logoutUrl}>{gettext('Logout')}</a>
|
|
</div>
|
|
</form>
|
|
</BasePage>
|
|
);
|
|
}
|
|
|
|
MfaValidatePage.propTypes = {
|
|
actionUrl: PropTypes.string,
|
|
views: PropTypes.object,
|
|
logoutUrl: PropTypes.string,
|
|
sendEmailUrl: PropTypes.string,
|
|
csrfHeader: PropTypes.string,
|
|
csrfToken: PropTypes.string
|
|
};
|