Skip to content

Commit

Permalink
Merge pull request #530 from aranaravi/ES-496
Browse files Browse the repository at this point in the history
[ES-501]Creating new form component to support dynamic json in login page for…
  • Loading branch information
gsasikumar authored Jan 3, 2024
2 parents a5c3ce5 + dc94b8c commit 6d5ef63
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 11 deletions.
2 changes: 2 additions & 0 deletions oidc-ui/.env
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ REACT_APP_WALLET_LOGO_URL=""
REACT_APP_CONSENT_SCREEN_TIME_OUT_BUFFER_IN_SEC=5

REACT_APP_WALLET_QR_CODE_AUTO_REFRESH_LIMIT=3

REACT_APP_ESIGNET_API_URL=/v1/esignet
3 changes: 2 additions & 1 deletion oidc-ui/.env.development
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
REACT_APP_SBI_ENV=Staging
REACT_APP_SBI_ENV=Staging
REACT_APP_ESIGNET_API_URL=/v1/esignet
10 changes: 10 additions & 0 deletions oidc-ui/public/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@
"uin_placeholder": "VID",
"password_placeholder": "كلمة المرور"
},
"Form": {
"sign_in_with_details": "Login with Details",
"login": "Login",
"policyNumber": "Enter Policy Number",
"name": "Enter Name",
"dob": "Enter DOB",
"policyNumber_placeholder": "Policy Number",
"name_placeholder": "Name",
"dob_placeholder": "DOB"
},
"LoginQRCode": {
"scan_with_wallet": "قم بالمسح باستخدام {{walletName}} لتسجيل الدخول",
"dont_have_wallet": "؟{{walletName}} ليس لديك",
Expand Down
11 changes: 11 additions & 0 deletions oidc-ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@
"uin_placeholder": "VID",
"password_placeholder": "Password"
},
"Form": {
"sign_in_with_details": "Login with Details",
"login": "Login",
"policyNumber": "Enter Policy Number",
"name": "Enter Name",
"dob": "Enter DOB",
"policyNumber_placeholder": "Policy Number",
"name_placeholder": "Name",
"dob_placeholder": "DOB"
},
"LoginQRCode": {
"scan_with_wallet": "Scan with {{walletName}} to login",
"dont_have_wallet": "Don't Have {{walletName}}?",
Expand Down Expand Up @@ -107,6 +117,7 @@
"QRCode": "QR Code",
"OTP": "OTP",
"PWD": "Password",
"KBA": "Details",
"preferred_mode_of_login": "Select a preferred mode of login",
"more_ways_to_sign_in": "More ways to sign in",
"or": "OR",
Expand Down
30 changes: 30 additions & 0 deletions oidc-ui/src/common/ErrorBanner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useTranslation } from "react-i18next";

const ErrorBanner = ({
showBanner,
errorCode,
onCloseHandle,
customClass = "",
i18nKeyPrefix = "errors",
}) => {
const { t } = useTranslation("translation", { keyPrefix: i18nKeyPrefix });

return showBanner && (
<div
className={
"flex justify-between items-center px-5 lg:-mx-5 md:-mx-4 sm:-mx-3 -mx-3 error-banner " +
customClass
}
>
<div className="error-banner-text">{t(errorCode)}</div>
<img
onClick={onCloseHandle}
className="h-2.5 w-2.5"
alt=""
src="images/cross_icon.svg"
/>
</div>
);
};

export default ErrorBanner;
257 changes: 257 additions & 0 deletions oidc-ui/src/components/Form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import { useEffect, useState, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import ErrorIndicator from "../common/ErrorIndicator";
import LoadingIndicator from "../common/LoadingIndicator";
import {
buttonTypes,
challengeFormats,
challengeTypes,
configurationKeys,
} from "../constants/clientConstants";
import { LoadingStates as states } from "../constants/states";
import FormAction from "./FormAction";
import InputWithImage from "./InputWithImage";
import ReCAPTCHA from "react-google-recaptcha";
import ErrorBanner from "../common/ErrorBanner";

let fieldsState = {};

export default function Form({
authService,
openIDConnectService,
handleBackButtonClick,
i18nKeyPrefix = "Form",
}) {
const { t, i18n } = useTranslation("translation", {
keyPrefix: i18nKeyPrefix,
});

const inputCustomClass =
"h-10 border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[hsla(0, 0%, 51%)] focus-visible:outline-none disabled:cursor-not-allowed disabled:bg-muted-light-gray shadow-none";

const fields = openIDConnectService.getEsignetConfiguration(configurationKeys.authFactorKnowledgeFieldDetails) ?? [];
fields.forEach((field) => (fieldsState["_form_" + field.id] = ""));
const post_AuthenticateUser = authService.post_AuthenticateUser;
const buildRedirectParams = authService.buildRedirectParams;

const [loginState, setLoginState] = useState(fieldsState);
const [error, setError] = useState(null);
const [errorBanner, setErrorBanner] = useState([]);
const [status, setStatus] = useState(states.LOADED);
const [invalidState, setInvalidState] = useState(true);

useEffect(() => {
}, []);


const navigate = useNavigate();

const handleChange = (e) => {
setLoginState({ ...loginState, [e.target.id]: e.target.value });
};

const handleSubmit = (e) => {
e.preventDefault();
authenticateUser();
};
const captchaEnableComponents =
openIDConnectService.getEsignetConfiguration(
configurationKeys.captchaEnableComponents
) ?? process.env.REACT_APP_CAPTCHA_ENABLE;

const captchaEnableComponentsList = captchaEnableComponents
.split(",")
.map((x) => x.trim().toLowerCase());

const [showCaptcha, setShowCaptcha] = useState(
captchaEnableComponentsList.indexOf("pwd") !== -1
);

const captchaSiteKey =
openIDConnectService.getEsignetConfiguration(
configurationKeys.captchaSiteKey
) ?? process.env.REACT_APP_CAPTCHA_SITE_KEY;

const [captchaToken, setCaptchaToken] = useState(null);
const _reCaptchaRef = useRef(null);
const handleCaptchaChange = (value) => {
setCaptchaToken(value);
};

//Handle Login API Integration here
const authenticateUser = async () => {
try {
let transactionId = openIDConnectService.getTransactionId();
let uin = loginState["_form_"+openIDConnectService.getEsignetConfiguration(configurationKeys.authFactorKnowledgeIndividualIdField) ?? ""];
let challengeManipulate = {};
fields.forEach(function(field) {
if(field.id !== openIDConnectService.getEsignetConfiguration(configurationKeys.authFactorKnowledgeIndividualIdField)){
challengeManipulate[field.id] = loginState["_form_"+field.id]
}
});
let challenge = btoa(JSON.stringify(challengeManipulate));

let challengeList = [
{
authFactorType: "KBA",
challenge: challenge,
format: "base64url-encoded-json",
},
];

setStatus(states.LOADING);
const authenticateResponse = await post_AuthenticateUser(
transactionId,
uin,
challengeList,
captchaToken
);

setStatus(states.LOADED);

const { response, errors } = authenticateResponse;

if (errors != null && errors.length > 0) {
setError({
prefix: "authentication_failed_msg",
errorCode: errors[0].errorCode,
defaultMsg: errors[0].errorMessage,
});
return;
} else {
setError(null);

let nonce = openIDConnectService.getNonce();
let state = openIDConnectService.getState();

let params = buildRedirectParams(
nonce,
state,
openIDConnectService.getOAuthDetails(),
response.consentAction
);

navigate(process.env.PUBLIC_URL + "/consent" + params, {
replace: true,
});
}
} catch (error) {
setError({
prefix: "authentication_failed_msg",
errorCode: error.message,
defaultMsg: error.message,
});
setStatus(states.ERROR);
}
};

useEffect(() => {
let loadComponent = async () => {
i18n.on("languageChanged", () => {
if (showCaptcha) {
setShowCaptcha(true);
}
});
};

loadComponent();
}, []);

useEffect(() => {
setInvalidState(!Object.values(loginState).every((value) => value?.trim()));
}, [loginState]);

const onCloseHandle = () => {
let tempBanner = errorBanner.map((_) => _);
tempBanner[0].show = false;
setErrorBanner(tempBanner);
};

return (
<>
<div className="grid grid-cols-8 items-center">
<div className="h-6 items-center text-center flex items-start">
<button
onClick={() => handleBackButtonClick()}
className="text-sky-600 text-2xl font-semibold justify-left rtl:rotate-180"
>
&#8592;
</button>
</div>
<div className="h-6 flex justify-center col-start-2 col-span-6 h-fit">
<h1
className="text-center text-sky-600 font-semibold line-clamp-2"
title={t("sign_in_with_details")}
>
{t("sign_in_with_details")}
</h1>
</div>
</div>

{errorBanner.length > 0 && (
<ErrorBanner
showBanner={errorBanner[0]?.show}
errorCode={errorBanner[0]?.errorCode}
onCloseHandle={onCloseHandle}
/>
)}

<form className="mt-6 space-y-6" onSubmit={handleSubmit}>
{fields.map((field) => (
<div className="-space-y-px">
<InputWithImage
key={"_form_" + field.id}
handleChange={handleChange}
value={loginState["_form_" + field.id]}
labelText={t(field.id)}
labelFor={field.id}
id={"_form_" + field.id}
type={field.type}
isRequired={true}
placeholder={t(field.id)}
customClass={inputCustomClass}
imgPath={null}
icon={field.infoIcon}
/>
</div>
))}

{showCaptcha && (
<div className="block password-google-reCaptcha">
<ReCAPTCHA
hl={i18n.language}
ref={_reCaptchaRef}
onChange={handleCaptchaChange}
sitekey={captchaSiteKey}
className="flex place-content-center"
/>
</div>
)}

<FormAction
type={buttonTypes.submit}
text={t("login")}
id="verify_form"
disabled={
invalidState ||
(errorBanner && errorBanner.length > 0) ||
(showCaptcha && captchaToken === null)
}
/>
</form>
{status === states.LOADING && (
<div className="mt-2">
<LoadingIndicator size="medium" message="authenticating_msg" />
</div>
)}
{status !== states.LOADING && error && (
<ErrorIndicator
prefix={error.prefix}
errorCode={error.errorCode}
defaultMsg={error.defaultMsg}
/>
)}
</>
);
}
7 changes: 5 additions & 2 deletions oidc-ui/src/constants/clientConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const validAuthFactors = {
OTP: "OTP",
BIO: "BIO",
PWD: "PWD",
WLA: "WLA"
WLA: "WLA",
KBA: "KBA"
};

const buttonTypes = {
Expand Down Expand Up @@ -76,7 +77,9 @@ const configurationKeys = {
consentScreenExpireInSec: "consent.screen.timeout-in-secs",
consentScreenTimeOutBufferInSec: "consent.screen.timeout-buffer-in-secs",
walletQrCodeAutoRefreshLimit: "wallet.qr-code.auto-refresh-limit",
walletConfig: "wallet.config"
walletConfig: "wallet.config",
authFactorKnowledgeFieldDetails: "auth.factor.kba.field-details",
authFactorKnowledgeIndividualIdField: "auth.factor.kba.individual-id-field"
};

export {
Expand Down
Loading

0 comments on commit 6d5ef63

Please sign in to comment.