From 91d785b27fb4655ef2cbd2e83acc24476369d66e Mon Sep 17 00:00:00 2001 From: anshulv1401 <31562315+anshulv1401@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:30:38 +0530 Subject: [PATCH] [ES-21] QRCode buffer configuration added (#274) * QRCode buffer configuration added * config key name updated * README updated * sbi-integrator version updated * application properties updated --- .../main/resources/application-dev.properties | 7 +- .../resources/application-local.properties | 7 +- oidc-ui/.env | 4 +- oidc-ui/README.md | 14 ++- oidc-ui/package.json | 2 +- oidc-ui/public/locales/ar.json | 6 +- oidc-ui/public/locales/en.json | 6 +- oidc-ui/src/components/LoginQRCode.js | 115 +++++++++++++----- oidc-ui/src/constants/clientConstants.js | 4 +- 9 files changed, 122 insertions(+), 43 deletions(-) diff --git a/esignet-service/src/main/resources/application-dev.properties b/esignet-service/src/main/resources/application-dev.properties index 4858fc2b8..63777ea7d 100644 --- a/esignet-service/src/main/resources/application-dev.properties +++ b/esignet-service/src/main/resources/application-dev.properties @@ -229,14 +229,17 @@ mosip.keymanager.dao.enabled=false crypto.PrependThumbprint.enable=true ## -------------------------------------------- IDP-UI config ---------------------------------------------------------- +# NOTE: +# 1. linked-transaction-expire-in-secs value should be a sum of mosip.esignet.authentication-expire-in-secs and linked cache expire in seconds under mosip.esignet.cache.expire-in-seconds property +# 2. A new Qrcode will be autogenerated before the expiry of current qr-code, and the time difference in seconds for the same is defined in wallet.qr-code-buffer-in-secs property mosip.esignet.ui.config.key-values={'sbi.env': 'Developer', 'sbi.timeout.DISC': 30, \ 'sbi.timeout.DINFO': 30, 'sbi.timeout.CAPTURE': 30, 'sbi.capture.count.face': 1, 'sbi.capture.count.finger': 2, \ 'sbi.capture.count.iris': 1, 'sbi.capture.score.face': 70, 'sbi.capture.score.finger':70, 'sbi.capture.score.iris':70, 'wallet.logo-url': 'inji_logo.png', \ 'send.otp.channels':'email,phone', 'consent.screen.timeout-in-secs':${mosip.esignet.authentication-expire-in-secs}, \ 'consent.screen.timeout-buffer-in-secs': 5, 'sbi.port.range': 4501-4600, 'sbi.bio.subtypes.iris': 'UNKNOWN', 'sbi.bio.subtypes.finger': 'UNKNOWN', \ - 'resend.otp.delay.secs': 120, 'captcha.enable': 'OTP', 'captcha.sitekey': '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', \ - 'mosip.esignet.link-auth-code-expire-in-secs': 120, 'mosip.esignet.link-status-deferred-response-timeout-secs': 25, \ + 'resend.otp.delay.secs': 120, 'captcha.enable': '', 'captcha.sitekey': '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', \ + 'linked-transaction-expire-in-secs': 120, 'wallet.qr-code-buffer-in-secs': 10, \ 'mosip.esignet.qr-code.deep-link-uri': 'inji://landing-page-name?linkCode=LINK_CODE&linkExpireDateTime=LINK_EXPIRE_DT', \ 'mosip.esignet.qr-code.download-uri': '#', 'mosip.esignet.qr-code.enable': 'true', 'auth.txnid.length': 10, \ 'otp.length': 6, 'password.regex': ''} \ No newline at end of file diff --git a/esignet-service/src/main/resources/application-local.properties b/esignet-service/src/main/resources/application-local.properties index de54f69e4..000d4864d 100644 --- a/esignet-service/src/main/resources/application-local.properties +++ b/esignet-service/src/main/resources/application-local.properties @@ -224,14 +224,17 @@ mosip.keymanager.dao.enabled=false crypto.PrependThumbprint.enable=true ## -------------------------------------------- IDP-UI config ---------------------------------------------------------- +# NOTE: +# 1. linked-transaction-expire-in-secs value should be a sum of mosip.esignet.authentication-expire-in-secs and linked cache expire in seconds under mosip.esignet.cache.expire-in-seconds property +# 2. A new Qrcode will be autogenerated before the expiry of current qr-code, and the time difference in seconds for the same is defined in wallet.qr-code-buffer-in-secs property mosip.esignet.ui.config.key-values={'sbi.env': 'Developer', 'sbi.timeout.DISC': 30, \ 'sbi.timeout.DINFO': 30, 'sbi.timeout.CAPTURE': 30, 'sbi.capture.count.face': 1, 'sbi.capture.count.finger': 2, \ 'sbi.capture.count.iris': 1, 'sbi.capture.score.face': 70, 'sbi.capture.score.finger':70, 'sbi.capture.score.iris':70, 'wallet.logo-url': 'inji_logo.png', \ 'send.otp.channels':'email,phone', 'consent.screen.timeout-in-secs':${mosip.esignet.authentication-expire-in-secs}, \ 'consent.screen.timeout-buffer-in-secs': 5, 'sbi.port.range': 4501-4600, 'sbi.bio.subtypes.iris': 'UNKNOWN', 'sbi.bio.subtypes.finger': 'UNKNOWN', \ - 'resend.otp.delay.secs': 120, 'captcha.enable': 'OTP', 'captcha.sitekey': '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', \ - 'mosip.esignet.link-auth-code-expire-in-secs': 120, 'mosip.esignet.link-status-deferred-response-timeout-secs': 25, \ + 'resend.otp.delay.secs': 120, 'captcha.enable': '', 'captcha.sitekey': '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', \ + 'linked-transaction-expire-in-secs': 120, 'wallet.qr-code-buffer-in-secs': 10, \ 'mosip.esignet.qr-code.deep-link-uri': 'inji://landing-page-name?linkCode=LINK_CODE&linkExpireDateTime=LINK_EXPIRE_DT', \ 'mosip.esignet.qr-code.download-uri': '#', 'mosip.esignet.qr-code.enable': 'true', 'auth.txnid.length': 10, \ 'otp.length': 6, 'password.regex': ''} diff --git a/oidc-ui/.env b/oidc-ui/.env index ea304d605..0d1fef5fc 100644 --- a/oidc-ui/.env +++ b/oidc-ui/.env @@ -17,8 +17,8 @@ REACT_APP_SBI_IRIS_CAPTURE_SCORE=70 REACT_APP_SBI_IRIS_BIO_SUBTYPES="UNKNOWN" REACT_APP_SBI_FINGER_BIO_SUBTYPES="UNKNOWN" -REACT_APP_LINK_AUTH_CODE_TIMEOUT_IN_SEC=120 -REACT_APP_LINK_CODE_DEFERRED_TIMEOUT_IN_SEC=25 +REACT_APP_LINKED_TRANSACTION_EXPIRE_IN_SEC=120 +REACT_APP_QR_CODE_BUFFER_IN_SEC=10 REACT_APP_QRCODE_DEEP_LINK_URI="inji://landing-page-name?linkCode=LINK_CODE&linkExpireDateTime=LINK_EXPIRE_DT" REACT_APP_QRCODE_APP_DOWNLOAD_URI="#" REACT_APP_QRCODE_ENABLE="true" diff --git a/oidc-ui/README.md b/oidc-ui/README.md index 41f38cbd0..09bc39ffa 100644 --- a/oidc-ui/README.md +++ b/oidc-ui/README.md @@ -60,7 +60,13 @@ The application runs on PORT=3000 by default. ``` - Build and run on the local system: - Update ".env.development" file, add REACT_APP_ESIGNET_API_URL=<'Complete URL of Esignet Services'> - ``` - $ npm start - ``` + - Update ".env.development" file, add REACT_APP_ESIGNET_API_URL=<'Complete URL of Esignet Services'> + - Start oidc-ui + ``` + $ npm start + ``` + - Run the browser with web-security disabled. For Google chrome the command is + ``` + chrome.exe --user-data-dir="C://Chrome dev session" --disable-web-security + ``` + - Open URL http://localhost:3000 \ No newline at end of file diff --git a/oidc-ui/package.json b/oidc-ui/package.json index 5d307f69f..80d31f444 100644 --- a/oidc-ui/package.json +++ b/oidc-ui/package.json @@ -13,7 +13,7 @@ "i18next-browser-languagedetector": "^7.0.0", "i18next-http-backend": "^2.0.1", "jose": "^4.9.3", - "secure-biometric-interface-integrator": "^0.1.0", + "secure-biometric-interface-integrator": "^0.9.0", "qrcode": "^1.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/oidc-ui/public/locales/ar.json b/oidc-ui/public/locales/ar.json index f5e27a48d..6598543e7 100644 --- a/oidc-ui/public/locales/ar.json +++ b/oidc-ui/public/locales/ar.json @@ -244,6 +244,7 @@ "invalid_value": "قيمة غير صالحة", "duplicate_individual_id": "المعرف الفردي موجود بالفعل.", "password_error_msg": "رمز مرور خاطئ", + "invalid_qrcode_config": "تكوين QRCode غير صالح. يرجى إبلاغ مدير الموقع.", "0": "النجاح", "100": "الجهاز غير مسجل", "101": "غير قادر على الكشف عن كائن القياسات الحيوية", @@ -344,6 +345,9 @@ "IDA-RST-005": "المهلة غير صالحة", "IDA-RST-006": "4XX - حدث خطأ في العميل", "IDA-RST-007": "5XX - حدث خطأ في الخادم", - "IDA-RST-008": "انتهت مدة الاتصال" + "IDA-RST-008": "انتهت مدة الاتصال", + "IDA-KBT-001": "لم يتم العثور على الشهادة المنضمة", + "IDA-KBT-002": "الرمز المميز الموقع الصادر في (iat) ليس في النطاق الزمني المسموح به.", + "IDA-KBT-003": "خطأ في التحقق من الرمز المميز المرتبط بالمفتاح." } } diff --git a/oidc-ui/public/locales/en.json b/oidc-ui/public/locales/en.json index 8bf37f89f..0ea5d2264 100644 --- a/oidc-ui/public/locales/en.json +++ b/oidc-ui/public/locales/en.json @@ -244,6 +244,7 @@ "invalid_value": "Invalid value", "duplicate_individual_id": "Individual ID already exists.", "password_error_msg": "Invalid Password", + "invalid_qrcode_config": "Invalid QRCode configuration. Please report to the site admin.", "0": "Success", "100": "Device Not Registered", "101": "Unable to Detect a Biometrics", @@ -344,6 +345,9 @@ "IDA-RST-005": "Timeout is invalid", "IDA-RST-006": "4XX - Client Error occurred", "IDA-RST-007": "5XX - Server Error occurred", - "IDA-RST-008": "Connection timed out" + "IDA-RST-008": "Connection timed out", + "IDA-KBT-001": "Bound certificate not found", + "IDA-KBT-002": "Signed token issued at (iat) is not in allowed time range.", + "IDA-KBT-003": "Error verifying key binded token." } } diff --git a/oidc-ui/src/components/LoginQRCode.js b/oidc-ui/src/components/LoginQRCode.js index a067bc9b1..c6aa2ea57 100644 --- a/oidc-ui/src/components/LoginQRCode.js +++ b/oidc-ui/src/components/LoginQRCode.js @@ -24,26 +24,32 @@ export default function LoginQRCode({ const [qr, setQr] = useState(""); const [status, setStatus] = useState({ state: states.LOADED, msg: "" }); const [error, setError] = useState(null); + const [qrCodeTimeOut, setQrCodeTimeout] = useState(); - const linkAuthCodeExpireInSec = + const linkedTransactionExpireInSec = openIDConnectService.getEsignetConfiguration( - configurationKeys.linkAuthCodeExpireInSec - ) ?? process.env.REACT_APP_LINK_AUTH_CODE_TIMEOUT_IN_SEC; + configurationKeys.linkedTransactionExpireInSecs + ) ?? process.env.REACT_APP_LINKED_TRANSACTION_EXPIRE_IN_SEC; /* - linkCodeDeferredTimeoutInSec is link_status Grace period. link-status request will not be triggered - if the linkCode is going to expire within grace period. + The QRCode will be valid even after expiring on the UI for the period of qrCodeBufferInSecs. */ - const linkCodeDeferredTimeoutInSec = + const qrCodeBufferInSecs = openIDConnectService.getEsignetConfiguration( - configurationKeys.linkCodeDeferredTimeoutInSec - ) ?? process.env.REACT_APP_LINK_CODE_DEFERRED_TIMEOUT_IN_SEC; + configurationKeys.qrCodeBufferInSecs + ) ?? process.env.REACT_APP_QR_CODE_BUFFER_IN_SEC; - let parseTimeout = parseInt(linkCodeDeferredTimeoutInSec); + const qrCodeBuffer = + parseInt(qrCodeBufferInSecs) !== "NaN" + ? parseInt(qrCodeBufferInSecs) + : process.env.REACT_APP_QR_CODE_BUFFER_IN_SEC; - const linkStatusGracePeriod = parseTimeout !== "NaN" ? parseTimeout : 25; + const walletLogoURL = + openIDConnectService.getEsignetConfiguration( + configurationKeys.walletLogoURL + ) ?? process.env.REACT_APP_WALLET_LOGO_URL; - const GenerateQRCode = (response) => { + const GenerateQRCode = (response, logoUrl) => { let text = openIDConnectService.getEsignetConfiguration( configurationKeys.qrCodeDeepLinkURI @@ -56,7 +62,9 @@ export default function LoginQRCode({ response.expireDateTime ); - QRCode.toDataURL( + const canvas = document.createElement("canvas"); + QRCode.toCanvas( + canvas, text, { width: 500, @@ -65,20 +73,54 @@ export default function LoginQRCode({ dark: "#000000", }, }, - (err, text) => { + (err) => { if (err) { setError({ errorCode: "link_code_refresh_failed", }); return; } - setQr(text); + if (logoUrl) { + const logo = new Image(); + logo.src = logoUrl; + logo.onload = () => { + const ctx = canvas.getContext("2d"); + const size = canvas.width / 6; + const x = (canvas.width - size) / 2; + const y = (canvas.height - size) / 2; + // Create a new canvas to filter the logo image + const filterCanvas = document.createElement("canvas"); + filterCanvas.width = logo.width; + filterCanvas.height = logo.height; + const filterCtx = filterCanvas.getContext("2d"); + filterCtx.drawImage(logo, 0, 0); + ctx.fillStyle = "#000000"; + ctx.fillRect(x - 6, y - 6, size + 12, size + 12); + // Draw the filtered image onto the QR code canvas + ctx.fillStyle = "#ffffff"; + ctx.fillRect(200, 200, 100, 100); + ctx.drawImage(filterCanvas, x, y, size, size); + setQr(canvas.toDataURL()); + }; + logo.onerror = () => { + // If there's an error fetching the logo, generate QR code without the logo + setQr(canvas.toDataURL()); + }; + } else { + // If logoUrl is not configured, generate QR code without the logo + setQr(canvas.toDataURL()); + } } ); }; useEffect(() => { fetchQRCode(); + + return () => { + //clearing timeout before component unmount + clearTimeout(qrCodeTimeOut); + }; }, []); const fetchQRCode = async () => { @@ -99,13 +141,42 @@ export default function LoginQRCode({ defaultMsg: errors[0].errorMessage, }); } else { - GenerateQRCode(response); + let qrCodeExpiryDateTime = new Date(response.expireDateTime); + let timeLeft = (qrCodeExpiryDateTime - new Date()) / 1000; // timeleft in sec + + if (qrCodeBuffer > (timeLeft + 1) / 2) { + /* + qrCodeBuffer should not be greater then the half of link-code-expire-in-secs. + It reduces the chances of more then 2 active link-status polling request at a time. + */ + setError({ + errorCode: "invalid_qrcode_config", + defaultMsg: + "Invalid QRCode configuration. Please report to the site admin.", + }); + return; + } + + /* + considering buffer time before next qrcode render, which will allow previous + qrcode's link-status polling request to be active for the buffer period. + */ + let timeLeftWithBuffer = timeLeft - qrCodeBuffer; + + GenerateQRCode(response, walletLogoURL); setStatus({ state: states.LOADED, msg: "" }); triggerLinkStatus( response.transactionId, response.linkCode, response.expireDateTime ); + + clearTimeout(qrCodeTimeOut); + let _timer = setTimeout(() => { + if (linkAuthTriggered) return; + fetchQRCode(); + }, timeLeftWithBuffer * 1000); + setQrCodeTimeout(_timer); } } catch (error) { setError({ @@ -124,7 +195,6 @@ export default function LoginQRCode({ try { let expiryDateTime = new Date(linkCodeExpiryDateTime); let timeLeft = (expiryDateTime - new Date()) / 1000; // timeleft in sec - let qrExpired = false; let linkStatusResponse; while (timeLeft > 0) { try { @@ -150,17 +220,6 @@ export default function LoginQRCode({ } timeLeft = (expiryDateTime - new Date()) / 1000; - if ( - !qrExpired && - timeLeft < linkStatusGracePeriod && - (!linkStatusResponse || !linkStatusResponse?.response) - ) { - qrExpired = true; - // setError({ - // errorCode: "qr_code_expired", - // }); - fetchQRCode(); - } } if (linkAuthTriggered) return; @@ -203,7 +262,7 @@ export default function LoginQRCode({ try { let codeExpiryDateTime = new Date(); codeExpiryDateTime.setSeconds( - codeExpiryDateTime.getSeconds() + Number(linkAuthCodeExpireInSec) + codeExpiryDateTime.getSeconds() + Number(linkedTransactionExpireInSec) ); let timeLeft = (codeExpiryDateTime - new Date()) / 1000; let linkAuthResponse; diff --git a/oidc-ui/src/constants/clientConstants.js b/oidc-ui/src/constants/clientConstants.js index a1486601d..dabe9e2c5 100644 --- a/oidc-ui/src/constants/clientConstants.js +++ b/oidc-ui/src/constants/clientConstants.js @@ -64,8 +64,8 @@ const configurationKeys = { captchaEnableComponents: "captcha.enable", //comma separated list of components where captcha needs to be shown captchaSiteKey: "captcha.sitekey", //site key for ReCAPTCHA - linkAuthCodeExpireInSec: "mosip.esignet.link-auth-code-expire-in-secs", - linkCodeDeferredTimeoutInSec: "mosip.esignet.link-status-deferred-response-timeout-secs", + linkedTransactionExpireInSecs: "linked-transaction-expire-in-secs", + qrCodeBufferInSecs: "wallet.qr-code-buffer-in-secs", qrCodeDeepLinkURI: "mosip.esignet.qr-code.deep-link-uri", appDownloadURI: "mosip.esignet.qr-code.download-uri", signInWithQRCodeEnable: "mosip.esignet.qr-code.enable",