From 5a0e78c80e4eebb186c2ea3a950cc01b67667003 Mon Sep 17 00:00:00 2001 From: varsha <91577031+varsha766@users.noreply.github.com> Date: Wed, 20 May 2026 17:31:52 +0530 Subject: [PATCH 01/34] Fix did page bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sync develop with master (#165) * Feature/logo upload (#109) * Logo URL uploader fixed * fixed some designs * Feature/email otp (#110) * implemeted UI for Customer onboarding API * added basic st. for email otp input * implemented emailOTPRequest and verify actions * fixed minor ui * shifted super admin tooling in setting page * moreved reset fields * added code for mfa verification after email otp login --------- Co-authored-by: Varsha Kumari * UI bug fix (#112) * removed space from url before validating it * allowing whitelisted cors to update * allowing to update issuer * removed * from whitelisted cors * enabled whitelistedCors edit in appConfig * fix: bugs realted to issuer config and --------- Co-authored-by: Pratap2018 * Feature/user analytics (#115) * implemented user analytics * fixed small bug --------- Co-authored-by: vishwas1 * Feature/sa/provide credit (#116) * implemeted UI for Customer onboarding API * WIP --> remove jose and call dashboard api pnce its ready for credit * added basic st. for email otp input * implemented emailOTPRequest and verify actions * fixed minor ui * shifted super admin tooling in setting page * moreved reset fields * added code for mfa verification after email otp login * implamented api for credit recharge and removed unused variables (#114) --------- Co-authored-by: vishwas1 * fix * implemented mode toggel * Fix: Update Ui * Analytics empty data handling (#117) * updated kyc side nav (#120) * updated kyc side nav * chnaged the position of solution section * enabled kyb nav * modified kyc credit page (#119) * Feature/api usage analytics (#118) * added new analytics design * fixed date change issue with new design * fixed date picker issue --------- Co-authored-by: vishwas1 * update dockerfile * Update Dockerfile * redesigned APIkey and webhook config pages and getting started page * fixed nav * hidingi kyb widget temporariy * fixed design * Feature/sa/kyb compliance (#128) * added Design for kyb compliance check * added design change * redesinged approve customer onbboarding * implemented fetch company api * added missing import * fixed ui for geberating kyb token * added missed code * Feat/implement compliance api (#122) * Feat/implement companyapi (#121) * added missing import * fixed ui for geberating kyb token * added missed code * fixed conflict * small bug fixed * fix * added missing api path * added missing brances after resolving conflict * removed un used code * implemented api for updating compliance detail * fixed token issue * implmentd api for finalizing company review * added tempate to show error message * fixed bug * fixed compliance page bug * fixed url issue * fixed markdown rendering issue * minor design update on nav * fixed minor design * nav bug fix * widget config design fix * redeisgned business list page * design update * fixed business details page * updated design * Feature/kyb custom page (#129) * widget config design fix * kyb verifier page * fix: Kyb generator --------- Co-authored-by: vishwas1 * chore proper naming * removed unused check --------- Co-authored-by: vishwas1 Co-authored-by: Pratap Mridha * fix store and state for compilance * fix-ui * Feature/sa/kyb compliance (#134) * added Design for kyb compliance check * added design change * redesinged approve customer onbboarding * implemented fetch company api * added missing import * fixed ui for geberating kyb token * added missed code * Feat/implement compliance api (#122) * Feat/implement companyapi (#121) * added missing import * fixed ui for geberating kyb token * added missed code * fixed conflict * small bug fixed * fix * added missing api path * added missing brances after resolving conflict * removed un used code * implemented api for updating compliance detail * fixed token issue * implmentd api for finalizing company review * added tempate to show error message * fixed bug * fixed compliance page bug * fixed url issue * fixed markdown rendering issue * minor design update on nav * fixed minor design * nav bug fix * widget config design fix * redeisgned business list page * design update * fixed business details page * updated design * Feature/kyb custom page (#129) * widget config design fix * kyb verifier page * fix: Kyb generator --------- Co-authored-by: vishwas1 * chore proper naming * removed unused check * Feature/design/login page (#132) * new login page deesign * designed session details * session page redesign * fixed backgroupnd * fixed profile popup --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> Co-authored-by: Pratap Mridha * design update * fixed design | Added reusable ID solution * minor design udpate * API usage design * added email notification toggel (#135) * Implement/sa/seamless onboarding (#138) * added button for seamless onboarding for super admin * ui fix * enabling superadmin to add multiple onboardign details * updated func name * fixed typo * Fix UI bug (#137) * updated the text color of credit request messaget * fixed loader issue in onboarding page in superadmin tooling * keeping admins own trusted issuer at the top * fixed issue of getting please pass authtoken (#136) * fixed issue of getting please pass authtoken * code formatted * code formatted * regenrating kyc service access token once it is expired * Minor improvement (#139) * reset form data once error enconter * fixed issue of missing personalInfo in report if user has used govIf * fixed seamless onboarding button color * removed data logs * removed log * added env mode in api * regenrating kyc service access token once it is expired (#140) * regenrating kyc service access token once it is expired * updated variable name * update ssi related function to regenrate token if expired before setting in header * fixed some issue * Remove log (#141) * updated variable name * update ssi related function to regenrate token if expired before setting in header * fixed some issue * removed console log and used error in catch * fixed bug * fixed issue of wrong token getting passed in case of ssi * feat: handle logout (#142) * remove comment * fmt * remove debug info * refactor: clean up styles and improve API usage display in analytics (#145) * refactor: clean up styles and improve API usage display in analytics * fix: emphasize estimated ID verifications in KYC dashboard * update UI * Fix/bug (#146) * fixed-env while fetching ubo details * Refactor API usage analytics and playground components - Added conditional rendering for production environment in ApiUsage.vue. - Enhanced status badge styles in Business.vue for better UI consistency. - Simplified approval and rejection logic in BusinessDetails.vue, improving error handling and user notifications. - Corrected description text in KybWidgetConfig/Index.vue for clarity. - Integrated loading component in ApproveCustomerOnboarding.vue and CreditRecharge.vue, improving user experience during async operations. - Removed redundant feedback message handling in ComplianceCheck.vue, replacing it with direct notifications. * feat: enhance Business overview with dynamic status-based styling and… (#148) * feat: enhance Business overview with dynamic status-based styling and hover effects * refactor: improve code readability and formatting in APIKey.vue * style: update UI elements for improved consistency and readability across various components * feat: update country display in BusinessInformation component with name and flag * Fix/UI improvement 2 (#151) * Refactor code structure for improved readability and maintainability * Refactor code structure for improved readability and maintainability * Refactor code structure for improved readability and maintainability * chore: update Node.js version to 20.19 in Dockerfile * feat: add tooltip for approved business status and update breadcrumb link hover color for clarity * Refactor SessionsDetails.vue: Improve key formatting and enhance PDF generation - Updated key formatting in user personal data tables to include spaces before capital letters and capitalize the first letter. - Modified PDFMake import to include additional font support for Devanagari. - Adjusted page margins and background dimensions in PDF document definition. - Reduced logo width in PDF to improve layout. - Cleaned up default style settings for PDF generation. * fix: update icon for 'No Business Found' message to Font Awesome * style: update status colors in UboDetails component (#153) * style: update status colors in UboDetails component fix: update language keys for identity and business verification style: adjust padding width in various views style: unify padding width for .py-3 class across multiple components chore: update dependencies in yarn.lock * Feat/automation cookie injection (#154) * removed unused tests * Added test for kyc, login and setting module * updated file name * updated readme * fixed bug * updated readme with prerequisites * updated did and schema test * updated test file by removing login nad logout from each test * onboarding success with headed * fixed approve onboarding flow * updated test file * fixed * updated genrateApi key file * updated test based on recent chnages * added readme and prompt to run test * Added readme and updated run-test.js * updated readme * fixed issue of profile path in different os * fixed prompt value updation issue * made test execution parallel and used cookie injection * code updated --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> * style: update status colors in UboDetails component (#155) fix: update language keys for identity and business verification style: adjust padding width in various views style: unify padding width for .py-3 class across multiple components chore: update dependencies in yarn.lock * style: refine sidebar menu styles and remove unnecessary shadows * refactor: remove unnecessary header from menu * Implement feature X to enhance user experience and optimize performance * style: enhance loading and empty states in UboDetails component * style: add validation for Telegram profile URL in StepCompanyDetails component --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> * Feature/testing (#162) * Fix/improve UI (#159) * style: update status colors in UboDetails component fix: update language keys for identity and business verification style: adjust padding width in various views style: unify padding width for .py-3 class across multiple components chore: update dependencies in yarn.lock * Feat/automation cookie injection (#154) * removed unused tests * Added test for kyc, login and setting module * updated file name * updated readme * fixed bug * updated readme with prerequisites * updated did and schema test * updated test file by removing login nad logout from each test * onboarding success with headed * fixed approve onboarding flow * updated test file * fixed * updated genrateApi key file * updated test based on recent chnages * added readme and prompt to run test * Added readme and updated run-test.js * updated readme * fixed issue of profile path in different os * fixed prompt value updation issue * made test execution parallel and used cookie injection * code updated --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> * style: update status colors in UboDetails component (#155) fix: update language keys for identity and business verification style: adjust padding width in various views style: unify padding width for .py-3 class across multiple components chore: update dependencies in yarn.lock * style: refine sidebar menu styles and remove unnecessary shadows * refactor: remove unnecessary header from menu * Implement feature X to enhance user experience and optimize performance --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> * Feat/automation cookie injection (#160) * removed unused tests * Added test for kyc, login and setting module * updated file name * updated readme * fixed bug * updated readme with prerequisites * updated did and schema test * updated test file by removing login nad logout from each test * onboarding success with headed * fixed approve onboarding flow * updated test file * fixed * updated genrateApi key file * updated test based on recent chnages * added readme and prompt to run test * Added readme and updated run-test.js * updated readme * fixed issue of profile path in different os * fixed prompt value updation issue * made test execution parallel and used cookie injection * code updated * updated service name * fixes * updated readme * fixed timming issue * updated test based on recent chnage in nav * updated readme * updated logo url --------- Co-authored-by: Pratap Mridha * updated test case and fixed delete app testcase * added some wait before performing action * fixed issue of button name update * fix: improve error handling in credit request processing * refactor: simplify side menu structure by removing unnecessary parent open checks * fix: adjust navbar positioning and z-index for improved layout (#164) * fix: adjust navbar positioning and z-index for improved layout * fix: enhance hover effects for sidebar menu links in expanded and collapsed states * fixed issues of side bar opening below the nav --------- Co-authored-by: Varsha Kumari --------- Co-authored-by: Vishwas Anand Bhushan Co-authored-by: Pratap2018 * Develop (#168) * Feature/logo upload (#109) * Logo URL uploader fixed * fixed some designs * Feature/email otp (#110) * implemeted UI for Customer onboarding API * added basic st. for email otp input * implemented emailOTPRequest and verify actions * fixed minor ui * shifted super admin tooling in setting page * moreved reset fields * added code for mfa verification after email otp login --------- Co-authored-by: Varsha Kumari * UI bug fix (#112) * removed space from url before validating it * allowing whitelisted cors to update * allowing to update issuer * removed * from whitelisted cors * enabled whitelistedCors edit in appConfig * fix: bugs realted to issuer config and --------- Co-authored-by: Pratap2018 * Feature/user analytics (#115) * implemented user analytics * fixed small bug --------- Co-authored-by: vishwas1 * Feature/sa/provide credit (#116) * implemeted UI for Customer onboarding API * WIP --> remove jose and call dashboard api pnce its ready for credit * added basic st. for email otp input * implemented emailOTPRequest and verify actions * fixed minor ui * shifted super admin tooling in setting page * moreved reset fields * added code for mfa verification after email otp login * implamented api for credit recharge and removed unused variables (#114) --------- Co-authored-by: vishwas1 * fix * implemented mode toggel * Fix: Update Ui * Analytics empty data handling (#117) * updated kyc side nav (#120) * updated kyc side nav * chnaged the position of solution section * enabled kyb nav * modified kyc credit page (#119) * Feature/api usage analytics (#118) * added new analytics design * fixed date change issue with new design * fixed date picker issue --------- Co-authored-by: vishwas1 * update dockerfile * Update Dockerfile * redesigned APIkey and webhook config pages and getting started page * fixed nav * hidingi kyb widget temporariy * fixed design * Feature/sa/kyb compliance (#128) * added Design for kyb compliance check * added design change * redesinged approve customer onbboarding * implemented fetch company api * added missing import * fixed ui for geberating kyb token * added missed code * Feat/implement compliance api (#122) * Feat/implement companyapi (#121) * added missing import * fixed ui for geberating kyb token * added missed code * fixed conflict * small bug fixed * fix * added missing api path * added missing brances after resolving conflict * removed un used code * implemented api for updating compliance detail * fixed token issue * implmentd api for finalizing company review * added tempate to show error message * fixed bug * fixed compliance page bug * fixed url issue * fixed markdown rendering issue * minor design update on nav * fixed minor design * nav bug fix * widget config design fix * redeisgned business list page * design update * fixed business details page * updated design * Feature/kyb custom page (#129) * widget config design fix * kyb verifier page * fix: Kyb generator --------- Co-authored-by: vishwas1 * chore proper naming * removed unused check --------- Co-authored-by: vishwas1 Co-authored-by: Pratap Mridha * fix store and state for compilance * fix-ui * Feature/sa/kyb compliance (#134) * added Design for kyb compliance check * added design change * redesinged approve customer onbboarding * implemented fetch company api * added missing import * fixed ui for geberating kyb token * added missed code * Feat/implement compliance api (#122) * Feat/implement companyapi (#121) * added missing import * fixed ui for geberating kyb token * added missed code * fixed conflict * small bug fixed * fix * added missing api path * added missing brances after resolving conflict * removed un used code * implemented api for updating compliance detail * fixed token issue * implmentd api for finalizing company review * added tempate to show error message * fixed bug * fixed compliance page bug * fixed url issue * fixed markdown rendering issue * minor design update on nav * fixed minor design * nav bug fix * widget config design fix * redeisgned business list page * design update * fixed business details page * updated design * Feature/kyb custom page (#129) * widget config design fix * kyb verifier page * fix: Kyb generator --------- Co-authored-by: vishwas1 * chore proper naming * removed unused check * Feature/design/login page (#132) * new login page deesign * designed session details * session page redesign * fixed backgroupnd * fixed profile popup --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> Co-authored-by: Pratap Mridha * design update * fixed design | Added reusable ID solution * minor design udpate * API usage design * added email notification toggel (#135) * Implement/sa/seamless onboarding (#138) * added button for seamless onboarding for super admin * ui fix * enabling superadmin to add multiple onboardign details * updated func name * fixed typo * Fix UI bug (#137) * updated the text color of credit request messaget * fixed loader issue in onboarding page in superadmin tooling * keeping admins own trusted issuer at the top * fixed issue of getting please pass authtoken (#136) * fixed issue of getting please pass authtoken * code formatted * code formatted * regenrating kyc service access token once it is expired * Minor improvement (#139) * reset form data once error enconter * fixed issue of missing personalInfo in report if user has used govIf * fixed seamless onboarding button color * removed data logs * removed log * added env mode in api * regenrating kyc service access token once it is expired (#140) * regenrating kyc service access token once it is expired * updated variable name * update ssi related function to regenrate token if expired before setting in header * fixed some issue * Remove log (#141) * updated variable name * update ssi related function to regenrate token if expired before setting in header * fixed some issue * removed console log and used error in catch * fixed bug * fixed issue of wrong token getting passed in case of ssi * feat: handle logout (#142) * remove comment * fmt * remove debug info * refactor: clean up styles and improve API usage display in analytics (#145) * refactor: clean up styles and improve API usage display in analytics * fix: emphasize estimated ID verifications in KYC dashboard * update UI * Fix/bug (#146) * fixed-env while fetching ubo details * Refactor API usage analytics and playground components - Added conditional rendering for production environment in ApiUsage.vue. - Enhanced status badge styles in Business.vue for better UI consistency. - Simplified approval and rejection logic in BusinessDetails.vue, improving error handling and user notifications. - Corrected description text in KybWidgetConfig/Index.vue for clarity. - Integrated loading component in ApproveCustomerOnboarding.vue and CreditRecharge.vue, improving user experience during async operations. - Removed redundant feedback message handling in ComplianceCheck.vue, replacing it with direct notifications. * feat: enhance Business overview with dynamic status-based styling and… (#148) * feat: enhance Business overview with dynamic status-based styling and hover effects * refactor: improve code readability and formatting in APIKey.vue * style: update UI elements for improved consistency and readability across various components * feat: update country display in BusinessInformation component with name and flag * Fix/UI improvement 2 (#151) * Refactor code structure for improved readability and maintainability * Refactor code structure for improved readability and maintainability * Refactor code structure for improved readability and maintainability * chore: update Node.js version to 20.19 in Dockerfile * feat: add tooltip for approved business status and update breadcrumb link hover color for clarity * Refactor SessionsDetails.vue: Improve key formatting and enhance PDF generation - Updated key formatting in user personal data tables to include spaces before capital letters and capitalize the first letter. - Modified PDFMake import to include additional font support for Devanagari. - Adjusted page margins and background dimensions in PDF document definition. - Reduced logo width in PDF to improve layout. - Cleaned up default style settings for PDF generation. * fix: update icon for 'No Business Found' message to Font Awesome * style: update status colors in UboDetails component (#153) * style: update status colors in UboDetails component fix: update language keys for identity and business verification style: adjust padding width in various views style: unify padding width for .py-3 class across multiple components chore: update dependencies in yarn.lock * Feat/automation cookie injection (#154) * removed unused tests * Added test for kyc, login and setting module * updated file name * updated readme * fixed bug * updated readme with prerequisites * updated did and schema test * updated test file by removing login nad logout from each test * onboarding success with headed * fixed approve onboarding flow * updated test file * fixed * updated genrateApi key file * updated test based on recent chnages * added readme and prompt to run test * Added readme and updated run-test.js * updated readme * fixed issue of profile path in different os * fixed prompt value updation issue * made test execution parallel and used cookie injection * code updated --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> * style: update status colors in UboDetails component (#155) fix: update language keys for identity and business verification style: adjust padding width in various views style: unify padding width for .py-3 class across multiple components chore: update dependencies in yarn.lock * style: refine sidebar menu styles and remove unnecessary shadows * refactor: remove unnecessary header from menu * Implement feature X to enhance user experience and optimize performance * style: enhance loading and empty states in UboDetails component * style: add validation for Telegram profile URL in StepCompanyDetails component --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> * Feature/testing (#162) * Fix/improve UI (#159) * style: update status colors in UboDetails component fix: update language keys for identity and business verification style: adjust padding width in various views style: unify padding width for .py-3 class across multiple components chore: update dependencies in yarn.lock * Feat/automation cookie injection (#154) * removed unused tests * Added test for kyc, login and setting module * updated file name * updated readme * fixed bug * updated readme with prerequisites * updated did and schema test * updated test file by removing login nad logout from each test * onboarding success with headed * fixed approve onboarding flow * updated test file * fixed * updated genrateApi key file * updated test based on recent chnages * added readme and prompt to run test * Added readme and updated run-test.js * updated readme * fixed issue of profile path in different os * fixed prompt value updation issue * made test execution parallel and used cookie injection * code updated --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> * style: update status colors in UboDetails component (#155) fix: update language keys for identity and business verification style: adjust padding width in various views style: unify padding width for .py-3 class across multiple components chore: update dependencies in yarn.lock * style: refine sidebar menu styles and remove unnecessary shadows * refactor: remove unnecessary header from menu * Implement feature X to enhance user experience and optimize performance --------- Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> * Feat/automation cookie injection (#160) * removed unused tests * Added test for kyc, login and setting module * updated file name * updated readme * fixed bug * updated readme with prerequisites * updated did and schema test * updated test file by removing login nad logout from each test * onboarding success with headed * fixed approve onboarding flow * updated test file * fixed * updated genrateApi key file * updated test based on recent chnages * added readme and prompt to run test * Added readme and updated run-test.js * updated readme * fixed issue of profile path in different os * fixed prompt value updation issue * made test execution parallel and used cookie injection * code updated * updated service name * fixes * updated readme * fixed timming issue * updated test based on recent chnage in nav * updated readme * updated logo url --------- Co-authored-by: Pratap Mridha * updated test case and fixed delete app testcase * added some wait before performing action * fixed issue of button name update * fix: improve error handling in credit request processing * refactor: simplify side menu structure by removing unnecessary parent open checks * fix: adjust navbar positioning and z-index for improved layout (#164) * fix: adjust navbar positioning and z-index for improved layout * fix: enhance hover effects for sidebar menu links in expanded and collapsed states * fixed issues of side bar opening below the nav --------- Co-authored-by: Varsha Kumari * Feat/hygiene & bump version (#166) * feat: add PR hygiene checks workflow and changelog to track updates * feat: add pull_request_review event to PR hygiene checks workflow Co-authored-by: Copilot * feat: enhance PR hygiene workflow with additional checks and improved structure * feat: refine PR hygiene workflow with improved naming and streamlined steps * feat: enhance package.json version check in PR hygiene workflow --------- Co-authored-by: Copilot * Fixbug (#167) * logo can be uploaded only if isEditing is true * updating kyc and kyb widgit logo once we update app logo * updating app name in kyb widget * bug fix * fixed issue of empty widget data on app page * fix: domain editing functionality with verification process Co-authored-by: Copilot * chore: update version to 3.7.27 and add changelog entries for bug fixes Co-authored-by: Copilot * fix: enhance verification method handling and error messaging in ServiceConfig Co-authored-by: Copilot * Removed extra space from side nav of ssi (#169) * fixed extra space present in side nav in ssi * hide onchain configuration * added chnage log * App config bug/fix (#170) * using hf-popup in place of drop down to show domain verification guide * updating kyc widget on domain update * updated change log * updated change log file * added space in popup * fixed bug of hfpopup close issue * updated change log --------- Co-authored-by: Vishwas Anand Bhushan Co-authored-by: Varsha Kumari Co-authored-by: varsha <91577031+varsha766@users.noreply.github.com> Co-authored-by: Copilot * fixed did page bug * used common serch box in both id and business page * added chnage log * updated version --------- Co-authored-by: Vishwas Anand Bhushan Co-authored-by: Pratap2018 Co-authored-by: Copilot --- Changelog.md | 11 +++++--- package.json | 2 +- src/assets/css/gblStyle.css | 43 +++++++++++++++++++++++++++++++ src/views/ServiceConfig.vue | 1 - src/views/playground/Business.vue | 28 +++----------------- src/views/playground/DID.vue | 27 ++++++++++++++----- src/views/playground/Sessions.vue | 35 ------------------------- 7 files changed, 76 insertions(+), 71 deletions(-) diff --git a/Changelog.md b/Changelog.md index e14c108c..56b65aef 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,13 @@ ## [Unreleased] +## [3.7.31] - 2026-06-20 + +### Fixed + +- Fixed the search box on the business page +- Fixed the issue where the name field disappeared when clicking buttons + ## [3.7.27] - 2026-05-05 ### Fixed @@ -17,13 +24,9 @@ - Updating kyc widget once we update domain in appConfig - Added space in domain verification guid popup - Updated hf-popup to accept hide event -- Fixed the duplicate ALL access issue on the profile page. ## [3.7.23] - 2026-04-28 ### Added - Changelog added to the project to track changes and updates. - -- Changed the whitelisted CORS input from a normal textbox to a chip-based input. -- Added a reusable chip component that can be used in multiple places. \ No newline at end of file diff --git a/package.json b/package.json index 9cb4a53a..d4615a9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "entity-developer-dashboard", - "version": "3.7.28", + "version": "3.7.31", "private": true, "scripts": { "serve": "vue-cli-service serve --mode production", diff --git a/src/assets/css/gblStyle.css b/src/assets/css/gblStyle.css index 32f34d37..588ef41e 100644 --- a/src/assets/css/gblStyle.css +++ b/src/assets/css/gblStyle.css @@ -235,3 +235,46 @@ a { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; } + +/* Shared search control used by playground listing pages. */ +.search-wrap { + display: flex; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 8px; + overflow: hidden; + width: 300px; + height: 40px; +} + +.search-wrap--business { + width: 100%; + max-width: 280px; +} + +.search-wrap input { + flex: 1; + min-width: 0; + border: none; + padding: 8px 12px; + font-size: 0.84rem; + color: #374151; + outline: none; + background: transparent; +} + +.search-wrap .search-btn { + padding: 0 13px; + background: #f8fafc; + border: none; + border-left: 1px solid #e2e8f0; + color: #6b7280; + cursor: pointer; + display: flex; + align-items: center; +} + +.search-wrap .search-btn:hover { + background: #f1f5f9; + color: #374151; +} diff --git a/src/views/ServiceConfig.vue b/src/views/ServiceConfig.vue index 11557a7f..6da3c29f 100644 --- a/src/views/ServiceConfig.vue +++ b/src/views/ServiceConfig.vue @@ -859,7 +859,6 @@ export default { console.warn("Widget config not found or failed to fetch:", e.message); } }, - }, mixins: [UtilsMixin] }; diff --git a/src/views/playground/Business.vue b/src/views/playground/Business.vue index b429ca23..d114761a 100644 --- a/src/views/playground/Business.vue +++ b/src/views/playground/Business.vue @@ -10,13 +10,15 @@
- + +
+
+ + +
+
+ +
+ + {{ removeAuthError }} + +
+
+ + Cancel + + Remove +
+
+ + + @@ -243,6 +298,97 @@ justify-content: flex-end; } +/* confirm modal */ +.confirm-body { + padding: 0; +} + +.confirm-text { + font-size: 14px; + color: #374151; + line-height: 1.6; + margin-bottom: 20px; +} + +.confirm-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 8px; +} + +.remove-auth-label { + display: block; + font-size: 14px; + color: #374151; + margin-bottom: 8px; +} + +.remove-auth-error { + color: red; +} + +.form-group label { + margin-bottom: 8px; + display: inline-block; +} +.remove-auth-card { + width: 100%; + margin: 0; + border-radius: 14px; + border: 1px solid #e5e7eb; + box-shadow: none; +} +.remove-auth-card .card-body { + padding: 20px; +} + +.form-group .tool-tip, +.form-group .tooltip-wrapper { + display: inline-flex; + align-items: center; + vertical-align: middle; + margin-right: 4px; +} + +.form-group { + margin-bottom: 20px; +} + +/* Ensure select and PIN inputs inside the confirm modal have equal left/right spacing */ +.confirm-body .form-group > select.custom-select, +.confirm-body .form-group .pin-wrapper { + width: 100%; + box-sizing: border-box; + padding-left: 12px; + padding-right: 12px; +} + + +.confirm-body .form-group .pin-wrapper { + display: flex; + align-items: center; + gap: 8px; + padding-top: 6px; /* small top padding to match vertical rhythm */ + padding-bottom: 6px; +} + +/* Limit only the Remove Auth popup card width and center it (scoped) */ +.confirm-body .remove-auth-card { + max-width: 480px; + width: 100%; + margin: 0 auto; + box-sizing: border-box; +} + + + +/* Ensure images inside the confirm body are responsive */ +.confirm-body img { + max-width: 100%; + height: auto; +} + /* RESPONSIVE ADJUSTMENTS */ @media (max-width: 960px) { /* make avatar center & relative on small screens */ @@ -262,28 +408,51 @@ - diff --git a/src/components/stepper/StepCompanyDetails.vue b/src/components/stepper/StepCompanyDetails.vue index c72fa863..74448f9b 100644 --- a/src/components/stepper/StepCompanyDetails.vue +++ b/src/components/stepper/StepCompanyDetails.vue @@ -343,6 +343,7 @@ export default { COLLECT_WALLET: "Collect Wallet Address", AGE_VERIFICATION: "Age Verification", FRAUD_PREVENTION: "Fraud Prevention", + PROOF_OF_PERSONHOOD: "Proof of Personhood", }, BUSINESS_EST_YEARLY_VOLUME: { ZERO_ONEK: "0 - 1,000", @@ -539,7 +540,7 @@ export default { const c = this.localCompany; if (!c.name?.trim()) return this.showToast("Please enter a company/community name"); - if (!c.logo?.trim()) return this.showToast("Please provide a logo URL"); + if (!c.logo?.trim()) return this.showToast("Please upload logo."); if (c.type == this.BUSINESS_TYPE.BUSINESS && !c.country) return this.showToast("Please select a country"); if (c.type == this.BUSINESS_TYPE.BUSINESS && !c.registration_number) return this.showToast("Please enter your company registration number"); diff --git a/src/components/teams/AdminTeams.vue b/src/components/teams/AdminTeams.vue index bcf9a18f..f049e023 100644 --- a/src/components/teams/AdminTeams.vue +++ b/src/components/teams/AdminTeams.vue @@ -6,7 +6,7 @@
- + @@ -37,7 +37,7 @@ Edit + @click="promptDeleteRole(role._id, role.roleName)"> Delete
@@ -51,6 +51,27 @@ + + + + + mdi-alert-circle-outline + Delete Role + + + Are you sure you want to delete "{{ roleToDeleteName }}"? + All users linked with this role will lose access immediately. + + + + Cancel + + Delete + + + + +
@@ -65,25 +86,101 @@ Upto 200 chars -
-
    -
  • - - -
    - - + + +
    + +
    +
    +
    +
    {{ eachService.name }}
    +
    +
    {{ group.label }}
    +
    + + +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    {{ eachService.name }}
    +
    +
    {{ group.label }}
    +
    + + +
    +
    -
  • -
-
+
+ + Save @@ -98,6 +195,148 @@ import { mapGetters, mapActions } from "vuex/dist/vuex.common.js"; import StudioSideBar from "../element/StudioSideBar.vue"; import UtilsMixin from "../../mixins/utils"; +import config from "../../config"; + +const PREDEFINED_ROLES = [ + { + key: "viewer", + name: "Viewer", + icon: "mdi-eye-outline", + description: "Minimal read-only access for business stakeholders.", + badge: "Limited view", + recommendedFor: "Founders, management, or external auditors", + permissions: ["READ_ANALYTICS", "READ_USAGE", "READ_COMPANY", "READ_COMPLIANCE"] + }, + { + key: "analyst", + name: "Analyst", + icon: "mdi-chart-line", + description: "Read-only access to verification, compliance, company, analytics, and usage data.", + badge: "Read-only", + recommendedFor: "Audit, support, or monitoring team", + permissions: ["READ_VERIFIED_USER", "READ_ANALYTICS", "READ_USAGE", "READ_COMPANY", "READ_COMPANY_EXECUTIVES", "READ_DOCUMENT", "READ_COMPLIANCE"] + }, + { + key: "finance", + name: "Finance", + icon: "mdi-cash-multiple", + description: "Can view usage, analytics, credits, and company billing-related information.", + badge: "Billing access", + recommendedFor: "Finance or accounts team", + permissions: ["READ_ANALYTICS", "READ_USAGE", "READ_CREDIT", "READ_COMPANY"] + }, + { + key: "developer", + name: "Developer", + icon: "mdi-code-tags", + description: "Can configure widgets and webhooks for technical integration.", + badge: "Integration access", + recommendedFor: "Engineering team", + permissions: ["READ_WIDGET_CONFIG", "WRITE_WIDGET_CONFIG", "UPDATE_WIDGET_CONFIG", "READ_WEBHOOK_CONFIG", "WRITE_WEBHOOK_CONFIG", "UPDATE_WEBHOOK_CONFIG", "DELETE_WEBHOOK_CONFIG", "READ_USAGE"] + }, + { + key: "compliance_manager", + name: "Compliance Manager", + icon: "mdi-shield-check-outline", + description: "Can review KYC/KYB cases, verify documents, and access compliance data.", + badge: "KYC/KYB review", + recommendedFor: "Compliance or operations team", + permissions: ["READ_VERIFIED_USER", "READ_ANALYTICS", "READ_USAGE", "READ_COMPANY", "UPDATE_COMPANY_STATUS", "READ_COMPANY_EXECUTIVES", "READ_DOCUMENT", "VERIFY_DOCUMENT", "READ_COMPLIANCE"] + }, + { + key: "admin", + name: "Admin", + icon: "mdi-shield-crown-outline", + description: "Full access to manage team, settings, verification, billing, and company account.", + badge: "Full access", + recommendedFor: "Organization owners / super admins", + permissions: ["READ_VERIFIED_USER", "READ_WIDGET_CONFIG", "WRITE_WIDGET_CONFIG", "UPDATE_WIDGET_CONFIG", "READ_WEBHOOK_CONFIG", "WRITE_WEBHOOK_CONFIG", "UPDATE_WEBHOOK_CONFIG", "DELETE_WEBHOOK_CONFIG", "READ_ANALYTICS", "READ_USAGE", "READ_CREDIT", "READ_COMPANY", "DELETE_COMPANY", "UPDATE_COMPANY_STATUS", "READ_COMPANY_EXECUTIVES", "READ_DOCUMENT", "VERIFY_DOCUMENT", "READ_COMPLIANCE"] + }, + { + key: "custom", + name: "Custom Role", + icon: "mdi-tune-variant", + description: "Start with no permissions and configure manually.", + badge: "Advanced", + recommendedFor: "Custom enterprise setups", + permissions: [] + } +]; + +const SSI_PREDEFINED_ROLES = [ + { + key: "auditor", + name: "Auditor", + icon: "mdi-clipboard-text-search-outline", + description: "Read-only access to SSI operations and verification records.", + badge: "Read-only", + recommendedFor: "Audit and compliance teams", + permissions: ["READ_DID", "READ_SCHEMA", "READ_CREDENTIAL", "READ_USAGE", "READ_TX"] + }, + { + key: "developer", + name: "Developer", + icon: "mdi-laptop", + description: "Technical integration and transaction monitoring access.", + badge: "Developer access", + recommendedFor: "Engineering teams", + permissions: ["READ_DID", "READ_SCHEMA", "READ_CREDENTIAL", "READ_USAGE", "READ_TX", "CHECK_LIVE_STATUS"] + }, + { + key: "verifier", + name: "Verifier", + icon: "mdi-check-decagram-outline", + description: "Can verify credentials, presentations, and DID signatures.", + badge: "Verification access", + recommendedFor: "Verification services", + permissions: ["READ_DID", "VERIFY_DID_SIGNATURE", "READ_SCHEMA", "READ_CREDENTIAL", "VERIFY_CREDENTIAL", "VERIFY_PRESENTATION", "READ_USAGE", "CHECK_LIVE_STATUS"] + }, + { + key: "credential_issuer", + name: "Credential Issuer", + icon: "mdi-card-account-details-outline", + description: "Can issue and manage credentials and presentations.", + badge: "Credential issuance", + recommendedFor: "Issuing authorities", + permissions: ["READ_DID", "VERIFY_DID_SIGNATURE", "READ_SCHEMA", "READ_CREDENTIAL", "WRITE_CREDENTIAL", "WRITE_PRESENTATION", "READ_USAGE", "CHECK_LIVE_STATUS"] + }, + { + key: "billing_manager", + name: "Billing Manager", + icon: "mdi-credit-card-settings-outline", + description: "Manage SSI credits and monitor service usage.", + badge: "Billing access", + recommendedFor: "Finance teams", + permissions: ["READ_CREDIT", "WRITE_CREDIT", "READ_USAGE", "CHECK_LIVE_STATUS"] + }, + { + key: "ssi_admin", + name: "SSI Admin", + icon: "mdi-shield-star-outline", + description: "Full access to DIDs, schemas, credentials, presentations, usage, and credits.", + badge: "Full access", + recommendedFor: "Organization owners / SSI admins", + permissions: ["ALL"] + }, + { + key: "identity_manager", + name: "Identity Manager", + icon: "mdi-identifier", + description: "Manage decentralized identifiers, schemas, and credential lifecycle.", + badge: "Identity management", + recommendedFor: "SSI operations teams", + permissions: ["READ_DID", "WRITE_DID", "VERIFY_DID_SIGNATURE", "ISSUE_DID_JWT", "READ_SCHEMA", "WRITE_SCHEMA", "READ_CREDENTIAL", "WRITE_CREDENTIAL", "VERIFY_CREDENTIAL", "WRITE_PRESENTATION", "VERIFY_PRESENTATION", "READ_USAGE", "READ_TX", "CHECK_LIVE_STATUS"] + }, + { + key: "custom", + name: "Custom Role", + icon: "mdi-tune-variant", + description: "Start with no permissions and configure manually.", + badge: "Advanced", + recommendedFor: "Custom enterprise setups", + permissions: [] + } +]; export default { name: "AdminTeams", @@ -107,12 +346,60 @@ export default { }, computed: { ...mapGetters("mainStore", ["getAllServices", "getAllRoles"]), - + PREDEFINED_ROLES() { + return PREDEFINED_ROLES; + }, + SSI_PREDEFINED_ROLES() { + return SSI_PREDEFINED_ROLES; + }, + categorizedServices() { + const ssiServices = this.localAllServices.filter(s => s.id === config.SERVICE_TYPES.SSI_API); + const idServices = this.localAllServices.filter( + s => s.id === config.SERVICE_TYPES.CAVACH_API || s.id === 'CAVACH_KYB_API' + ); + const otherServices = this.localAllServices.filter( + s => s.id !== config.SERVICE_TYPES.SSI_API && + s.id !== config.SERVICE_TYPES.CAVACH_API && + s.id !== 'CAVACH_KYB_API' + ); + const categories = []; + if (ssiServices.length) categories.push({ label: 'SSI Service', icon: 'mdi-link-variant', iconColor: '#3b82f6', services: ssiServices }); + if (idServices.length) categories.push({ label: 'ID Service', icon: 'mdi-shield-account-outline', iconColor: '#10b981', services: idServices }); + if (otherServices.length) categories.push({ label: 'Other Services', icon: 'mdi-apps', iconColor: '#6b7280', services: otherServices }); + return categories; + }, + hasSSIService() { + return this.localAllServices.some(s => s.id === config.SERVICE_TYPES.SSI_API); + }, + hasIDService() { + return this.localAllServices.some( + s => s.id !== config.SERVICE_TYPES.SSI_API && s.id !== config.SERVICE_TYPES.QUEST + ); + }, + ssiServices() { + return this.localAllServices.filter(s => s.id === config.SERVICE_TYPES.SSI_API); + }, + idServices() { + return this.localAllServices.filter( + s => s.id !== config.SERVICE_TYPES.SSI_API && s.id !== config.SERVICE_TYPES.QUEST + ); + } + }, + watch: { + getAllServices: { + handler(services) { + this.localAllServices = this.getRoleServices(services); + }, + immediate: true + } }, data() { return { isLoading: false, edit: false, + confirmDialog: false, + roleToDelete: null, + roleToDeleteName: '', roleModel: { "roleName": "", "roleDescription": "", @@ -121,7 +408,11 @@ export default { "servicePermissions": [] }, localAllServices: [], - checked: true + checked: true, + selectedRoles: { + id: 'viewer', + ssi: 'auditor' + } } }, mounted() { @@ -129,10 +420,52 @@ export default { this.fetchServicesList() } - this.localAllServices = this.getAllServices + this.localAllServices = this.getRoleServices(this.getAllServices) }, methods: { ...mapActions("mainStore", ["getMyRolesAction", "createARole", "deleteARole", "fetchServicesList", "updateARole",]), + getRoleServices(services = []) { + return services.filter(service => service.id !== config.SERVICE_TYPES.QUEST); + }, + getPermSubGroups(serviceId, accessList) { + const allKeys = Object.keys(accessList); + const SSI_GROUPS = [ + { label: 'General', icon: 'mdi-star-outline', iconColor: '#6366f1', keys: ['ALL'] }, + { label: 'DID', icon: 'mdi-identifier', iconColor: '#3b82f6', keys: ['READ_DID', 'WRITE_DID', 'VERIFY_DID_SIGNATURE', 'ISSUE_DID_JWT'] }, + { label: 'Schema', icon: 'mdi-file-tree-outline', iconColor: '#8b5cf6', keys: ['READ_SCHEMA', 'WRITE_SCHEMA'] }, + { label: 'Credential', icon: 'mdi-card-account-details-outline', iconColor: '#059669', keys: ['READ_CREDENTIAL', 'VERIFY_CREDENTIAL', 'WRITE_CREDENTIAL'] }, + { label: 'Presentation', icon: 'mdi-presentation', iconColor: '#0891b2', keys: ['WRITE_PRESENTATION', 'VERIFY_PRESENTATION'] }, + { label: 'Credit', icon: 'mdi-credit-card-outline', iconColor: '#d97706', keys: ['WRITE_CREDIT', 'READ_CREDIT'] }, + { label: 'Usage & Tx', icon: 'mdi-chart-line', iconColor: '#dc2626', keys: ['READ_USAGE', 'READ_TX', 'CHECK_LIVE_STATUS'] }, + ]; + const ID_GROUPS = [ + { label: 'General', icon: 'mdi-star-outline', iconColor: '#6366f1', keys: ['ALL'] }, + { label: 'Session & Users', icon: 'mdi-account-multiple-outline', iconColor: '#3b82f6', keys: ['READ_VERIFIED_USER'] }, + { label: 'Widget Config', icon: 'mdi-widgets-outline', iconColor: '#059669', keys: ['READ_WIDGET_CONFIG', 'WRITE_WIDGET_CONFIG', 'UPDATE_WIDGET_CONFIG'] }, + { label: 'Webhook Config', icon: 'mdi-webhook', iconColor: '#0891b2', keys: ['WRITE_WEBHOOK_CONFIG', 'READ_WEBHOOK_CONFIG', 'UPDATE_WEBHOOK_CONFIG', 'DELETE_WEBHOOK_CONFIG'] }, + { label: 'Analytics & Usage', icon: 'mdi-chart-bar', iconColor: '#d97706', keys: ['READ_ANALYTICS', 'READ_USAGE'] }, + { label: 'Credit', icon: 'mdi-credit-card-outline', iconColor: '#dc2626', keys: ['READ_CREDIT'] }, + { label: 'Company', icon: 'mdi-domain', iconColor: '#7c3aed', keys: ['READ_COMPANY', 'DELETE_COMPANY', 'UPDATE_COMPANY_STATUS'] }, + { label: 'Company Executives', icon: 'mdi-account-tie-outline', iconColor: '#065f46', keys: ['READ_COMPANY_EXECUTIVES'] }, + { label: 'Document', icon: 'mdi-file-document-outline', iconColor: '#92400e', keys: ['READ_DOCUMENT', 'VERIFY_DOCUMENT'] }, + { label: 'Compliance', icon: 'mdi-shield-check-outline', iconColor: '#1d4ed8', keys: ['READ_COMPLIANCE'] }, + ]; + const groupDefs = serviceId === config.SERVICE_TYPES.SSI_API ? SSI_GROUPS : ID_GROUPS; + const assignedKeys = new Set(); + const result = []; + for (const group of groupDefs) { + const perms = group.keys.filter(k => allKeys.includes(k)); + if (perms.length) { + result.push({ ...group, permissions: perms }); + perms.forEach(k => assignedKeys.add(k)); + } + } + // const remaining = allKeys.filter(k => !assignedKeys.has(k)); + // if (remaining.length) { + // result.push({ label: 'Other', icon: 'mdi-dots-horizontal', iconColor: '#6b7280', permissions: remaining }); + // } + return result; + }, createTeamPopup() { this.$root.$emit("bv::show::modal", "create-team"); }, @@ -144,10 +477,21 @@ export default { openSlider(action = 'add') { if (action == 'add') { this.resetData() + this.applyDefaultPredefinedRoles(); this.edit = false; this.$root.$emit("bv::toggle::collapse", "sidebar-right"); } }, + applyDefaultPredefinedRoles() { + if (this.hasIDService) { + const idDefault = PREDEFINED_ROLES.find(r => r.key === 'viewer'); + if (idDefault) this.selectPredefinedRole(idDefault, 'id'); + } + if (this.hasSSIService) { + const ssiDefault = SSI_PREDEFINED_ROLES.find(r => r.key === 'auditor'); + if (ssiDefault) this.selectPredefinedRole(ssiDefault, 'ssi'); + } + }, checkIfAccessIsThereInThatService(access, serviceId) { @@ -163,8 +507,59 @@ export default { this.resetData() this.edit = true; this.roleModel = { ...role }; + this.selectedRoles.id = this.detectSelectedPredefinedRole('id'); + this.selectedRoles.ssi = this.detectSelectedPredefinedRole('ssi'); this.$root.$emit("bv::toggle::collapse", "sidebar-right"); }, + getServiceIdsByType(serviceType) { + return (serviceType === 'ssi' ? this.ssiServices : this.idServices).map(s => s.id); + }, + detectSelectedPredefinedRole(serviceType) { + const serviceIds = new Set(this.getServiceIdsByType(serviceType)); + const currentPermissions = (this.roleModel.permissions || []) + .filter(p => serviceIds.has(p.serviceType)) + .map(p => p.access); + const currentSet = new Set(currentPermissions); + const roles = (serviceType === 'ssi' ? SSI_PREDEFINED_ROLES : PREDEFINED_ROLES).filter(r => r.key !== 'custom'); + if (!currentSet.size) return 'custom'; + const matched = roles.find(role => { + if (role.permissions.includes('ALL')) { + return currentSet.has('ALL'); + } + if (role.permissions.length !== currentSet.size) return false; + return role.permissions.every(p => currentSet.has(p)); + }); + return matched ? matched.key : 'custom'; + }, + selectPredefinedRole(role, serviceType) { + this.selectedRoles[serviceType] = role.key; + const serviceIds = new Set(this.getServiceIdsByType(serviceType)); + this.roleModel.permissions = (this.roleModel.permissions || []).filter(p => !serviceIds.has(p.serviceType)); + if (role.key === 'custom') { + return; + } + this.roleModel.permissions = [ + ...this.roleModel.permissions, + ...this.buildPermissionsForRole(role, serviceType) + ]; + }, + buildPermissionsForRole(role, serviceType) { + const permissions = []; + const targetServices = serviceType === 'ssi' ? this.ssiServices : this.idServices; + + targetServices.forEach(service => { + if (role.permissions.includes('ALL') && service.accessList.ALL !== undefined) { + permissions.push({ serviceType: service.id, access: 'ALL' }); + return; + } + role.permissions.forEach(access => { + if (service.accessList[access] !== undefined) { + permissions.push({ serviceType: service.id, access }); + } + }); + }); + return permissions; + }, closeSlider() { this.edit = false; @@ -172,14 +567,96 @@ export default { this.$root.$emit("bv::toggle::collapse", "sidebar-right"); }, - onCheck(event) { - const ev = event.target._value - if (ev) { - const index = this.roleModel.permissions.findIndex(x => ((x.serviceType == ev.serviceType) && (x.access == ev.access))) - if (index > -1) { - this.roleModel.permissions.splice(index, 1) - } else { - this.roleModel.permissions.push(ev) + isUpdateForcedByDelete(accessKey, serviceId) { + if (!accessKey.startsWith('UPDATE_')) return false; + const deleteKey = 'DELETE_' + accessKey.substring(7); // 'UPDATE_'.length = 7 + return this.checkIfAccessIsThereInThatService(deleteKey, serviceId); + }, + + isReadForcedByWrite(accessKey, serviceId) { + if (!accessKey.startsWith('READ_')) return false; + const writeKey = 'WRITE_' + accessKey.substring(5); + return this.checkIfAccessIsThereInThatService(writeKey, serviceId); + }, + + isWriteForcedByHigherPerm(accessKey, serviceId) { + if (!accessKey.startsWith('WRITE_')) return false; + const suffix = accessKey.substring(6); // 'WRITE_'.length = 6 + const deleteKey = 'DELETE_' + suffix; + const updateKey = 'UPDATE_' + suffix; + return this.checkIfAccessIsThereInThatService(deleteKey, serviceId) || + this.checkIfAccessIsThereInThatService(updateKey, serviceId); + }, + + onCheck(event, service) { + const ev = event.target._value; + if (!ev) return; + + if (ev.access === 'ALL') { + const allKeys = Object.keys(service.accessList); + this.roleModel.permissions = this.roleModel.permissions.filter(x => x.serviceType !== ev.serviceType); + if (event.target.checked) { + allKeys.forEach(key => { + this.roleModel.permissions.push({ serviceType: ev.serviceType, access: key }); + }); + } + return; + } + + const index = this.roleModel.permissions.findIndex(x => x.serviceType == ev.serviceType && x.access == ev.access); + if (index > -1) { + this.roleModel.permissions.splice(index, 1); + } else { + this.roleModel.permissions.push(ev); + } + + // Auto-manage READ when WRITE is toggled + if (ev.access.startsWith('WRITE_')) { + const readKey = 'READ_' + ev.access.substring(6); + if (service.accessList[readKey] !== undefined && event.target.checked) { + const readIdx = this.roleModel.permissions.findIndex( + x => x.serviceType == ev.serviceType && x.access == readKey + ); + if (readIdx === -1) { + this.roleModel.permissions.push({ serviceType: ev.serviceType, access: readKey }); + } + } + } + + // Auto-manage WRITE (and READ) when DELETE or UPDATE is toggled + if (ev.access.startsWith('DELETE_') || ev.access.startsWith('UPDATE_')) { + const suffix = ev.access.substring(7); // both 'DELETE_' and 'UPDATE_' are 7 chars + const writeKey = 'WRITE_' + suffix; + if (service.accessList[writeKey] !== undefined && event.target.checked) { + // Auto-add WRITE + const writeIdx = this.roleModel.permissions.findIndex( + x => x.serviceType == ev.serviceType && x.access == writeKey + ); + if (writeIdx === -1) { + this.roleModel.permissions.push({ serviceType: ev.serviceType, access: writeKey }); + } + // Auto-add READ (cascade) + const readKey = 'READ_' + suffix; + if (service.accessList[readKey] !== undefined) { + const readIdx = this.roleModel.permissions.findIndex( + x => x.serviceType == ev.serviceType && x.access == readKey + ); + if (readIdx === -1) { + this.roleModel.permissions.push({ serviceType: ev.serviceType, access: readKey }); + } + } + } + // Auto-add UPDATE when DELETE is toggled + if (ev.access.startsWith('DELETE_') && event.target.checked) { + const updateKey = 'UPDATE_' + suffix; + if (service.accessList[updateKey] !== undefined) { + const updateIdx = this.roleModel.permissions.findIndex( + x => x.serviceType == ev.serviceType && x.access == updateKey + ); + if (updateIdx === -1) { + this.roleModel.permissions.push({ serviceType: ev.serviceType, access: updateKey }); + } + } } } }, @@ -219,22 +696,24 @@ export default { } }, - async deleteThisRole(roleId) { + promptDeleteRole(roleId, roleName) { + this.roleToDelete = roleId; + this.roleToDeleteName = roleName; + this.confirmDialog = true; + }, + async confirmDeleteRole() { try { - const result = confirm('Are you sure you want to delete this role? Once deleted, all users linked with this role will loose access.') - if (result) { - // do all validations... - this.isLoading = true - await this.deleteARole(roleId) - this.isLoading = false - this.notifySuccess('Role is deleted successfully') - } - - + this.isLoading = true; + await this.deleteARole(this.roleToDelete); + this.isLoading = false; + this.confirmDialog = false; + this.roleToDelete = null; + this.roleToDeleteName = ''; + this.notifySuccess('Role deleted successfully'); } catch (e) { - this.notifyErr(e.message) - this.isLoading = false + this.notifyErr(e.message); + this.isLoading = false; } }, @@ -246,9 +725,142 @@ export default { ], "servicePermissions": [] } + this.selectedRoles = { id: 'viewer', ssi: 'auditor' }; this.edit = false; }, }, mixins: [UtilsMixin] } - \ No newline at end of file + + + diff --git a/src/components/teams/LiveAccessStatus.vue b/src/components/teams/LiveAccessStatus.vue new file mode 100644 index 00000000..387ffed6 --- /dev/null +++ b/src/components/teams/LiveAccessStatus.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/components/teams/MyInvitions.vue b/src/components/teams/MyInvitions.vue index 80da0afe..4541abc0 100644 --- a/src/components/teams/MyInvitions.vue +++ b/src/components/teams/MyInvitions.vue @@ -5,7 +5,7 @@
- + diff --git a/src/components/teams/TeamMembers.vue b/src/components/teams/TeamMembers.vue index 69bad0cd..31dc843e 100644 --- a/src/components/teams/TeamMembers.vue +++ b/src/components/teams/TeamMembers.vue @@ -10,7 +10,7 @@ --> - + diff --git a/src/components/teams/TeamUser.vue b/src/components/teams/TeamUser.vue index 5bf254c9..36f7b3f1 100644 --- a/src/components/teams/TeamUser.vue +++ b/src/components/teams/TeamUser.vue @@ -33,6 +33,17 @@ Pending + + + + + + diff --git a/src/views/Apps.vue b/src/views/Apps.vue index e87c4338..aa7486ef 100644 --- a/src/views/Apps.vue +++ b/src/views/Apps.vue @@ -1221,6 +1221,7 @@ export default { this.$router.push({ name: "GettingStarted", + params: { appId }, }); break; } diff --git a/src/views/SettingConfig.vue b/src/views/SettingConfig.vue index f0585ab3..cc075757 100644 --- a/src/views/SettingConfig.vue +++ b/src/views/SettingConfig.vue @@ -9,23 +9,6 @@ Profile - @@ -50,6 +33,13 @@ + + + + @@ -86,7 +76,7 @@ @@ -114,6 +104,7 @@ import TeamMembers from '../components/teams/TeamMembers.vue'; import MyInvitions from '../components/teams/MyInvitions.vue'; import AdminTeams from '../components/teams/AdminTeams.vue'; +import LiveAccessStatus from '../components/teams/LiveAccessStatus.vue'; import { mapMutations, mapGetters } from "vuex"; import OnlySSIApps from '../components/settings/OnlySSIApps.vue'; import UserProfile from '../components/settings/UserProfile.vue'; @@ -129,6 +120,7 @@ export default { components: { TeamMembers, AdminTeams, + LiveAccessStatus, MyInvitions, UserProfile, OnlySSIApps, @@ -153,32 +145,39 @@ export default { sentInvitiationCode: "" }; }, + watch: { + '$route.query.ref'(ref) { + this.applyRouteRef(ref); + } + }, methods: { ...mapMutations("mainStore", ["setMainSideNavBar"]), - ...mapActions('mainStore', ['getMyRolesAction', 'getPeopleMembers',]) + ...mapActions('mainStore', ['getMyRolesAction', 'getPeopleMembers',]), + applyRouteRef(ref) { + if (ref === 'invitions') { + this.$nextTick(() => { + this.activeMainTab = 2; + }) + } else if (ref === 'roles') { + this.activeMainTab = 1; + this.$nextTick(() => { + this.activeMembersSubTab = 1; + }); + } else if (ref === 'mfa') { + this.activeMainTab = 0; + this.$nextTick(() => { + this.activeProfileSubTab = 0; + }); + } else { + this.activeMainTab = 1; + this.activeMembersSubTab = 0; + } + } }, async mounted() { this.setMainSideNavBar(false); - const ref = this.$route.query.ref; - if (ref === 'invitions') { - this.$nextTick(() => { - this.activeMainTab = 2; - }) - } else if (ref === 'roles') { - this.activeMainTab = 1; - this.$nextTick(() => { - this.activeMembersSubTab = 1; - }); - } else if (ref === 'mfa') { - this.activeMainTab = 0; - this.$nextTick(() => { - this.activeProfileSubTab = 0; - }); - } else { - this.activeMainTab = 1; - this.activeMembersSubTab = 0; - } + this.applyRouteRef(this.$route.query.ref); try{ await this.getMyRolesAction() @@ -189,4 +188,4 @@ export default { } } }; - \ No newline at end of file + diff --git a/src/views/analytics/UserAnalytics.vue b/src/views/analytics/UserAnalytics.vue index 6801a37a..6ab0f631 100644 --- a/src/views/analytics/UserAnalytics.vue +++ b/src/views/analytics/UserAnalytics.vue @@ -1,5 +1,7 @@ \ No newline at end of file + diff --git a/src/views/analytics/components/DeviceStats.vue b/src/views/analytics/components/DeviceStats.vue index a3067007..dda7e9c6 100644 --- a/src/views/analytics/components/DeviceStats.vue +++ b/src/views/analytics/components/DeviceStats.vue @@ -55,6 +55,7 @@ \ No newline at end of file + diff --git a/src/views/playground/SSIDashboardUsages.vue b/src/views/playground/SSIDashboardUsages.vue index a34008a6..26460353 100644 --- a/src/views/playground/SSIDashboardUsages.vue +++ b/src/views/playground/SSIDashboardUsages.vue @@ -129,6 +129,9 @@ h5 span { + + +
@@ -186,14 +189,14 @@ h5 span {
-

+

API Consumptions

No usage found!

-
+
@@ -226,6 +229,7 @@ h5 span { + @@ -238,9 +242,12 @@ import { mapState, mapActions, mapMutations } from "vuex"; import { mapGetters } from 'vuex/dist/vuex.common.js'; import UtilsMixin from '../../mixins/utils'; +import AccessDenied from '../AccessDenied.vue'; +import { isAccessDeniedError } from '../../utils/accessDenied'; export default { name: "SSIDashboardCredit", components: { + AccessDenied }, computed: { @@ -394,6 +401,8 @@ export default { user: {}, fullPage: true, isLoading: false, + accessDenied: false, + accessDeniedMsg: '', startDate: "", endDate: "", @@ -432,6 +441,11 @@ export default { this.isLoading = true this.setDate() await this.fetchUsageForASSIService({ startDate: this.startDate, endDate: this.endDate }).then((data) => { + // fetchUsageForASSIService returns the error object instead of throwing on 403 + if (data && (data.statusCode >= 400 || data.error)) { + const msg = Array.isArray(data.message) ? data.message.join(', ') : (data.message || data.error || 'Access denied'); + throw new Error(msg); + } this.usageDetails = data; }) await this.fetchUsageDetailsForASSIService({ startDate: this.startDate, endDate: this.endDate }); @@ -440,8 +454,7 @@ export default { this.isLoading = false } catch (e) { this.isLoading = false - this.notifyErr(e.message) - this.$router.push({ path: '/studio/dashboard' }); + this.handleApiError(e, 'GET') } }, @@ -470,6 +483,23 @@ export default { ...mapMutations('playgroundStore', ['updateSideNavStatus', 'shiftContainer']), + handleApiError(error, method = 'GET') { + const message = typeof error === 'string' ? error : error?.message || 'Something went wrong'; + if (method.toUpperCase() === 'GET' && isAccessDeniedError(error)) { + this.accessDenied = true; + this.accessDeniedMsg = message; + return; + } + + this.notifyErr(message); + }, + + throwIfAccessDeniedResponse(data) { + if (data && (data.statusCode >= 400 || data.error)) { + const msg = Array.isArray(data.message) ? data.message.join(', ') : (data.message || data.error || 'Access denied'); + throw new Error(msg); + } + }, changeGraph(chartType) { if (chartType == 'line') { @@ -675,7 +705,9 @@ export default { this.endDate = (new Date(this.endDate)); this.isLoading = true - this.usageDetails = await this.fetchUsageForASSIService({ startDate: this.startDate, endDate: this.endDate }) + const usageDetails = await this.fetchUsageForASSIService({ startDate: this.startDate, endDate: this.endDate }) + this.throwIfAccessDeniedResponse(usageDetails); + this.usageDetails = usageDetails; await this.fetchUsageDetailsForASSIService({ startDate: this.startDate, endDate: this.endDate }); this.isLoading = false @@ -684,7 +716,7 @@ export default { this.renderUsageDetailsChart() } catch (e) { this.isLoading = false - this.notifyErr(e.message) + this.handleApiError(e, 'GET') } }, @@ -889,4 +921,4 @@ export default { } - \ No newline at end of file + diff --git a/src/views/playground/Schema.vue b/src/views/playground/Schema.vue index de0e63ed..ebdecd6b 100644 --- a/src/views/playground/Schema.vue +++ b/src/views/playground/Schema.vue @@ -140,7 +140,8 @@ @@ -149,11 +152,14 @@ import UtilsMixin from '../../../mixins/utils.js'; import { mapGetters, mapActions } from "vuex"; import HfButtons from '../../../components/element/HfButtons.vue'; import { isValidURL } from '../../../mixins/fieldValidation.js' +import AccessDenied from '../../AccessDenied.vue'; +import { isAccessDeniedError } from '../../../utils/accessDenied'; export default { name: "WEbhookConfig", mixins: [UtilsMixin], components: { HfButtons, + AccessDenied, }, computed: { ...mapGetters('mainStore', ['getWebhookConfig']), @@ -169,11 +175,7 @@ export default { this.isLoading = false } catch (e) { this.isLoading = false - console.error(e) - if (e.message) { - this.notifyErr(e.message) - } - // this.$router.push({ path: '/studio/dashboard' }); + return this.handleApiError(e, 'GET') } this.formatConfig() @@ -184,6 +186,8 @@ export default { return { fullPage: true, isLoading: false, + accessDenied: false, + accessDeniedMsg: '', headers: [ { key: "", value: "" }, // Initial header ], @@ -194,6 +198,16 @@ export default { }, methods: { ...mapActions('mainStore', ['fetchAppWebhookConfig', 'createAppWebhookConfig', 'deleteAppWebhookConfig', 'updateAppWebhookConfig']), + handleApiError(error, method = 'GET') { + const message = typeof error === 'string' ? error : error?.message || 'Something went wrong'; + if (method.toUpperCase() === 'GET' && isAccessDeniedError(error)) { + this.accessDenied = true; + this.accessDeniedMsg = message; + return; + } + + this.notifyErr(message) + }, addHeader() { this.headers.push({ key: "", value: "" }); }, diff --git a/src/views/sa/components/CreditRecharge.vue b/src/views/sa/components/CreditRecharge.vue index 67e599e5..0d4519d4 100644 --- a/src/views/sa/components/CreditRecharge.vue +++ b/src/views/sa/components/CreditRecharge.vue @@ -6,7 +6,9 @@

Service Credit Recharge

-
+ + +

Allocate credits and set validity periods for your registered backend services.

@@ -71,6 +73,33 @@ @@ -885,7 +1382,12 @@ const FaicalAuthenticationError = { 4: "Failed to perform face check due to the pose of the face", 5: "Failed due to problems in the extraction of the facial pattern", 6: "Duplicate document was used", - 7: 'Failed, because the date of birth could not be read correctly from the document' + 7: 'Failed, because the date of birth could not be read correctly from the document', + 8: "Failed, because document issuing country is restricted", + 9: "Failed, because nationality is restricted", + 10: "Failed, because document issuing country is not allowed", + 11: "Failed, because nationality is not allowed", + 12: "Failed, because jurisdiction restriction requires manual review", }; const ZkpVerificationResultEnum = { @@ -1138,6 +1640,41 @@ export default { (a, b) => new Date(a.createdAt) - new Date(b.createdAt) ); }, + normalizedRiskFlags() { + const flags = this.session?.riskFlags || []; + if (!Array.isArray(flags)) return []; + + return flags.map((flag, index) => this.normalizeRiskFlag(flag, index)); + }, + hasRiskFlags() { + return this.normalizedRiskFlags.length > 0; + }, + riskFlagCount() { + return this.normalizedRiskFlags.length; + }, + normalizedManualDecisions() { + const decisions = this.session?.manualDecisions || []; + if (!Array.isArray(decisions)) return []; + + return decisions + .map((decision, index) => ({ + ...decision, + key: `${decision.action || "DECISION"}-${decision.decidedAt || index}`, + action: decision.action || "", + reasonCode: decision.reasonCode || "", + comments: decision.comments || "", + decidedBy: decision.decidedBy || "", + decidedAt: decision.decidedAt || "", + })) + .sort((a, b) => { + const aTime = Date.parse(a.decidedAt) || 0; + const bTime = Date.parse(b.decidedAt) || 0; + return bTime - aTime; + }); + }, + hasManualDecisions() { + return this.normalizedManualDecisions.length > 0; + }, }, data() { return { @@ -1153,6 +1690,19 @@ export default { sessionId: "", env: "prod", session: {}, + showManualReviewModal: false, + isSubmittingReviewDecision: false, + reviewDecision: "REJECTED", + reviewReasonCode: "", + reviewComments: "", + reviewReasonOptions: [ + { label: "Suspicious document", value: "SUSPICIOUS_DOCUMENT" }, + { label: "Identity could not be verified", value: "IDENTITY_NOT_VERIFIED" }, + { label: "Biometric mismatch", value: "BIOMETRIC_MISMATCH" }, + { label: "Restricted jurisdiction", value: "RESTRICTED_JURISDICTION" }, + { label: "Fraud / duplicate identity", value: "FRAUD_DUPLICATE_IDENTITY" }, + { label: "Other", value: "OTHER" }, + ], popupHeader: "", popupImage: "", deviceDetails: { @@ -1278,12 +1828,187 @@ export default { }); }, methods: { - ...mapActions("mainStore", ["fetchSessionsDetailsById"]), + ...mapActions("mainStore", ["fetchSessionsDetailsById", "submitManualReviewDecision"]), formatDate(date) { if (!date) return "-"; const d = new Date(date); return d.toLocaleString(); // Customize formatting if needed }, + openManualReviewModal() { + this.showManualReviewModal = true; + }, + closeManualReviewModal() { + this.showManualReviewModal = false; + }, + async submitReviewDecision() { + if (this.reviewDecision === "REJECTED" && !this.reviewReasonCode) { + this.notifyErr("Please select a reason for rejection."); + return; + } + + const decisionSessionId = this.session?.latestSessionId || this.session?.sessionId || this.sessionId; + const reasonCode = this.reviewDecision === "REJECTED" + ? this.reviewReasonCode + : "MANUAL_REVIEW_CLEARED"; + + try { + this.isSubmittingReviewDecision = true; + await this.submitManualReviewDecision({ + sessionId: decisionSessionId, + action: this.reviewDecision, + reasonCode, + comments: this.reviewComments, + }); + + this.session = await this.fetchSessionsDetailsById({ + sessionId: this.sessionId, + env: this.env, + businessId: this.isValidBusinessID ? this.companyId : undefined, + }) || {}; + + this.notifySuccess("Manual review decision submitted successfully."); + this.closeManualReviewModal(); + } catch (e) { + this.notifyErr(e.message || "Unable to submit manual review decision."); + } finally { + this.isSubmittingReviewDecision = false; + } + }, + normalizeRiskFlag(flag, index) { + const code = flag?.code || flag?.reason || "RISK_FLAG"; + const severity = flag?.severity || "MEDIUM"; + const metadata = flag?.metadata || {}; + const metaLine = this.getRiskMetaLine(code, metadata); + + return { + ...flag, + key: `${code}-${index}`, + code, + severity, + source: flag?.source || flag?.associatedStep || flag?.step || "", + associatedStep: flag?.associatedStep || flag?.step || "", + title: this.formatRiskCode(code), + description: flag?.message || this.getRiskFlagDescription(code), + metadata, + metaLine, + }; + }, + formatRiskCode(code) { + const labels = { + JURISDICTION_RESTRICTION_MATCH: "Jurisdiction restriction", + RESTRICTED_NATIONALITY: "Jurisdiction restriction", + RESTRICTED_ISSUING_COUNTRY: "Jurisdiction restriction", + LOW_OCR_CONFIDENCE: "Low OCR confidence", + BIOMETRIC_MATCH_BELOW_THRESHOLD: "Biometric match below threshold", + BORDERLINE_LIVENESS: "Borderline liveness", + FACIAL_SPOOF_SUSPECTED: "Facial spoof suspected", + DUPLICATE_FACE_DETECTED: "Duplicate face detected", + DOCUMENT_EXPIRING_SOON: "Document expiring soon", + NATIONALITY_MISMATCH: "Nationality mismatch", + ZKP_PROOF_WARNING: "Age verification warning", + }; + + if (labels[code]) return labels[code]; + return String(code) + .toLowerCase() + .split("_") + .filter(Boolean) + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(" "); + }, + getRiskFlagDescription(code) { + const descriptions = { + JURISDICTION_RESTRICTION_MATCH: + "Document issuing country or nationality matches a configured manual review policy.", + RESTRICTED_NATIONALITY: + "Document nationality matches a configured manual review policy.", + RESTRICTED_ISSUING_COUNTRY: + "Document issuing country matches a configured manual review policy.", + LOW_OCR_CONFIDENCE: + "Document field confidence is below the configured threshold.", + BIOMETRIC_MATCH_BELOW_THRESHOLD: + "Face similarity score is below the auto-approval threshold.", + BORDERLINE_LIVENESS: + "Liveness confidence is inconclusive and requires manual inspection.", + FACIAL_SPOOF_SUSPECTED: + "Potential presentation attack was detected with soft certainty.", + DUPLICATE_FACE_DETECTED: + "A matching face or document may already exist for another user.", + DOCUMENT_EXPIRING_SOON: + "The identity document is valid but close to expiry.", + NATIONALITY_MISMATCH: + "Document issuing country differs from declared nationality.", + ZKP_PROOF_WARNING: + "Age verification produced a non-fatal warning.", + }; + + return descriptions[code] || "This verification triggered a risk flag that requires review."; + }, + getRiskMetaLine(code, metadata) { + const country = metadata.matchedCountry || metadata.evaluatedNationality || metadata.evaluatedIssuingCountry; + const field = metadata.matchedField; + const score = metadata.score || metadata.faceSimilarity || metadata.faceSimilarityScore; + + if (score) { + const numericScore = Number(String(score).replace("%", "")); + if (!Number.isNaN(numericScore)) { + return `Face similarity: ${numericScore > 1 ? numericScore : Math.round(numericScore * 1000) / 10}%`; + } + return `Face similarity: ${score}`; + } + if (country && field) return `${this.formatRiskCode(field)}: ${country}`; + if (country) return `Matched country: ${country}`; + if (metadata.threshold) return `Threshold: ${metadata.threshold}`; + return ""; + }, + formatSeverity(severity) { + return String(severity || "MEDIUM").toLowerCase().replace(/^./, (char) => char.toUpperCase()); + }, + riskSeverityClass(severity) { + const value = String(severity || "MEDIUM").toLowerCase(); + return { + "badge-outline-danger": value === "high", + "badge-outline-warning": value === "medium", + "badge-outline-secondary": value === "low", + }; + }, + riskFlagCardClass(severity) { + const value = String(severity || "MEDIUM").toLowerCase(); + return { + "border-danger": value === "high", + "border-warning": value === "medium", + "border-secondary": value === "low", + }; + }, + riskFlagIconClass(severity) { + const value = String(severity || "MEDIUM").toLowerCase(); + return value === "high" ? "fa fa-exclamation-circle" : "fa fa-exclamation-triangle"; + }, + decisionStatusLabel(action) { + const labels = { + MANUAL_REVIEW: "Manual Review", + APPROVED: "Approved", + Success: "Approved", + REJECTED: "Rejected", + }; + + if (labels[action]) return labels[action]; + return this.formatRiskCode(action || "Decision"); + }, + decisionStatusClass(action) { + if (action === "REJECTED") return "badge-outline-danger"; + if (action === "APPROVED" || action === "Success") return "badge-outline-success"; + return "badge-outline-warning"; + }, + formatDecisionReason(reasonCode) { + const reason = this.reviewReasonOptions.find((item) => item.value === reasonCode); + if (reason) return reason.label; + + return this.formatRiskCode(reasonCode); + }, + isManualReviewCleared(decision) { + return decision?.reasonCode === "MANUAL_REVIEW_CLEARED"; + }, applySessionFailureToTimeline() { if (this.session?.status !== "Failed") return; diff --git a/src/views/playground/WidgetConfig/Index.vue b/src/views/playground/WidgetConfig/Index.vue index b62b5096..01cc5443 100644 --- a/src/views/playground/WidgetConfig/Index.vue +++ b/src/views/playground/WidgetConfig/Index.vue @@ -131,6 +131,216 @@ ul { padding: 6px 8px; } +.jurisdiction-panel { + background: #fff; + min-width: 0; +} + +.jurisdiction-description { + color: #6c757d; + font-size: 80%; + line-height: 1.45; + margin: 4px 0 22px; +} + +.jurisdiction-label { + color: #374151; + font-weight: 700; + margin-bottom: 10px; +} + +.jurisdiction-config-grid { + display: grid; + gap: 24px; + grid-template-columns: minmax(300px, 5fr) minmax(0, 7fr); +} + +.jurisdiction-column { + min-width: 0; +} + +.strategy-options { + display: grid; + gap: 12px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.strategy-card { + align-items: flex-start; + border: 1px solid #e5e7eb; + border-radius: 8px; + cursor: pointer; + display: flex; + margin: 0; + min-height: 120px; + padding: 16px; + transition: border-color 0.15s ease, box-shadow 0.15s ease; + width: 100%; +} + +.strategy-card.active { + border-color: #8ab9ff; + box-shadow: 0 0 0 1px #8ab9ff; +} + +.strategy-radio { + flex: 0 0 auto; + margin-right: 14px; + margin-top: 2px; +} + +.strategy-title { + color: #374151; + font-weight: 700; + margin-bottom: 18px; +} + +.strategy-help { + color: #6b7280; + font-size: 0.92rem; + line-height: 1.35; +} + +.country-chip-row { + display: flex; + flex-wrap: nowrap; + gap: 12px; + margin-bottom: 12px; + min-height: 58px; + overflow-x: auto; + padding: 1px 0 8px; + scrollbar-width: thin; + width: 100%; +} + +.country-chip { + align-items: center; + border: 1px solid #e5e7eb; + border-radius: 8px; + color: #374151; + display: inline-flex; + flex: 0 0 auto; + font-weight: 600; + min-height: 44px; + padding: 10px 12px; +} + +.country-chip-flag { + font-size: 1.45rem; + line-height: 1; + margin-right: 10px; +} + +.country-chip-remove { + color: #374151; + cursor: pointer; + font-size: 1.2rem; + line-height: 1; + margin-left: 18px; +} + +.country-dropdown-item { + align-items: center; + display: flex; + justify-content: space-between; + width: 100%; +} + +.jurisdiction-country-search-wrap { + position: relative; +} + +.jurisdiction-country-search-hint { + color: #7c8798; + font-size: 10px; + left: 44px; + line-height: 1; + pointer-events: none; + position: absolute; + top: 7px; + z-index: 2; +} + +::v-deep(.jurisdiction-country-select.v-text-field--outlined > .v-input__control > .v-input__slot) { + background: #f8fafc; + border-radius: 8px; + min-height: 44px; + transition: background-color 0.15s ease, box-shadow 0.15s ease; +} + +::v-deep(.jurisdiction-country-select.v-text-field--outlined:not(.v-input--is-focused) fieldset) { + border-color: #e2e8f0; +} + +::v-deep(.jurisdiction-country-select.v-text-field--outlined.v-input--is-focused fieldset) { + border-color: #94a3b8; + border-width: 1px; +} + +::v-deep(.jurisdiction-country-select.v-input--is-focused > .v-input__control > .v-input__slot) { + background: #fff; + box-shadow: 0 0 0 3px rgba(148, 163, 184, 0.14); +} + +::v-deep(.jurisdiction-country-select:not(.v-input--is-focused):hover fieldset) { + border-color: #cbd5e1; +} + +::v-deep(.jurisdiction-country-select .v-input__prepend-inner), +::v-deep(.jurisdiction-country-select .v-input__append-inner) { + align-self: center; + margin-top: 0; +} + +::v-deep(.jurisdiction-country-select .v-input__prepend-inner) { + padding-left: 2px; + padding-right: 10px; +} + +::v-deep(.jurisdiction-country-select input) { + color: #374151; + font-size: 13px; + line-height: 20px; + margin-top: 11px; + max-height: 32px; +} + +::v-deep(.jurisdiction-country-select .v-icon) { + color: #7c8798 !important; +} + +.jurisdiction-action-column { + grid-column: 1 / -1; + max-width: 440px; +} + +.jurisdiction-action-help { + color: #6b7280; + font-size: 0.86rem; + line-height: 1.4; + margin-top: 8px; +} + +@media (max-width: 991.98px) { + .jurisdiction-config-grid { + grid-template-columns: 1fr; + } + + .jurisdiction-action-column { + grid-column: auto; + } +} + +@media (max-width: 575.98px) { + .strategy-options { + grid-template-columns: 1fr; + } + + .strategy-card { + min-height: auto; + } +} + /* .zkbadge { background-color: lightblue; margin-left: 3px; @@ -372,6 +582,119 @@ ul {
+ +
  • +
    + + {{ widgetConfigUI.jurisdictionRules.label }} + +

    + {{ widgetConfigUI.jurisdictionRules.description }} +
    + {{ widgetConfigUI.jurisdictionRules.secondaryDescription }} +

    + +
    +
    +
    Enforcement Strategy
    +
    + +
    +
    + +
    +
    + Target Countries ({{ selectedJurisdictionCountries.length }}) +
    +
    + + {{ country.flag }} + {{ country.text }} ({{ country.value }}) + + × + + +
    +
    + + Select or search country... + + + + + + +
    +
    + +
    +
    Action on Match
    + +
    + {{ selectedJurisdictionActionDescription }} +
    +
    +
    +
  • @@ -421,6 +744,10 @@ import { isAccessDeniedError } from '../../../utils/accessDenied'; import MarketplaceList from '../../../components/MarketplaceList.vue'; import HFBeta from '../../../components/element/HFBeta.vue'; import config from '../../../config'; +import countries from 'i18n-iso-countries'; +import enLocale from 'i18n-iso-countries/langs/en.json'; + +countries.registerLocale(enLocale); export default { name: "WidgetConfig", @@ -495,7 +822,7 @@ export default { }, immediate: true - } + }, }, computed: { ...mapState({ @@ -523,6 +850,39 @@ export default { }); return options }, + countryOptions() { + return Object.entries(countries.getNames('en', { select: 'alias' })) + .map(([alpha2, name]) => { + const alpha3 = countries.alpha2ToAlpha3(alpha2) + const displayName = alpha2 === 'AE' ? 'United Arab Emirates' : name + + return { + value: alpha3, + text: displayName, + displayText: `${displayName} (${alpha3})`, + flag: this.countryFlagFromAlpha2(alpha2), + } + }) + .filter(country => !!country.value) + .sort((a, b) => a.text.localeCompare(b.text)) + }, + selectedJurisdictionCountries() { + const selected = this.widgetConfigTemp.jurisdictionRules?.countries || [] + return selected.map(code => { + const option = this.countryOptions.find(country => country.value === code) + return option || { + value: code, + text: code, + displayText: code, + flag: this.countryFlagFromAlpha3(code), + } + }) + }, + selectedJurisdictionActionDescription() { + const action = this.widgetConfigTemp.jurisdictionRules?.actionOnRestriction + const option = this.jurisdictionActionOptions.find(option => option.value === action) + return option?.description || '' + }, }, async mounted() { @@ -550,6 +910,7 @@ export default { if (typeof this.widgetConfigTemp.isMobileAssistedVerification !== 'boolean') { this.$set(this.widgetConfigTemp, 'isMobileAssistedVerification', true) } + this.ensureJurisdictionRules() this.trustedIssuersList = [...this.getMarketPlaceApps]; this.appId = this.$route.params.appId; @@ -659,6 +1020,11 @@ export default { label: "Mobile-Assisted Verification", description: "When enabled, users who start verification on a desktop will continue ID verification on their mobile device by scanning a QR code. This improves verification success rates, provides a better user experience, and strengthens proof of possession. Disable to allow the entire verification process to be completed on the desktop." }, + jurisdictionRules: { + label: "Enable Jurisdictional Restrictions", + description: "Enforce compliance policies by restricting access based on document issuing country and nationality.", + secondaryDescription: "Choose to either block high-risk regions (Blocklist) or restrict onboarding to specific permitted territories (Allowlist)." + }, }, fullPage: true, isLoading: false, @@ -698,6 +1064,12 @@ export default { isVaultEnabled: true, isEmailNotificationEnabled: true, isMobileAssistedVerification: true, + jurisdictionRules: { + enabled: false, + strategy: 'BLOCKLIST', + countries: [], + actionOnRestriction: 'HARD_REJECT', + }, issuerDID: "", issuerVerificationMethodId: "", }, @@ -726,6 +1098,30 @@ export default { text: "Government ID" }, ], + jurisdictionStrategyOptions: [ + { + value: 'BLOCKLIST', + text: 'Blocklist', + description: 'Block selected countries', + }, + { + value: 'ALLOWLIST', + text: 'Allowlist', + description: 'Only permit selected countries', + }, + ], + jurisdictionActionOptions: [ + { + value: 'HARD_REJECT', + text: 'Hard Reject (Stop Onboarding)', + description: 'Rejects the session immediately if the user matches the configured jurisdiction rule.', + }, + { + value: 'MANUAL_REVIEW', + text: 'Manual Review', + description: 'Allows the user to complete verification, then sends the session to the manual review queue.', + }, + ], } }, @@ -742,6 +1138,84 @@ export default { this.notifyErr(message) }, + defaultJurisdictionRules() { + return { + enabled: false, + strategy: 'BLOCKLIST', + countries: [], + actionOnRestriction: 'HARD_REJECT', + } + }, + ensureJurisdictionRules() { + const defaults = this.defaultJurisdictionRules() + + if (!this.widgetConfigTemp.jurisdictionRules) { + this.$set(this.widgetConfigTemp, 'jurisdictionRules', defaults) + return + } + + const existingRules = this.widgetConfigTemp.jurisdictionRules + Object.keys(defaults).forEach(key => { + if (!Object.prototype.hasOwnProperty.call(existingRules, key)) { + this.$set(existingRules, key, defaults[key]) + } + }) + + existingRules.enabled = existingRules.enabled === true + existingRules.strategy = ['BLOCKLIST', 'ALLOWLIST'].includes(existingRules.strategy) + ? existingRules.strategy + : defaults.strategy + existingRules.countries = Array.isArray(existingRules.countries) + ? existingRules.countries.map(country => String(country).toUpperCase()).filter(country => !!country) + : [] + existingRules.actionOnRestriction = ['HARD_REJECT', 'MANUAL_REVIEW'].includes(existingRules.actionOnRestriction) + ? existingRules.actionOnRestriction + : defaults.actionOnRestriction + }, + countryFlagFromAlpha2(alpha2) { + if (!alpha2 || alpha2.length !== 2) return '' + return alpha2 + .toUpperCase() + .replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt())) + }, + countryFlagFromAlpha3(alpha3) { + const alpha2 = countries.alpha3ToAlpha2(alpha3) + return this.countryFlagFromAlpha2(alpha2) + }, + removeJurisdictionCountry(countryCode) { + const selectedCountries = this.widgetConfigTemp.jurisdictionRules.countries || [] + this.widgetConfigTemp.jurisdictionRules.countries = selectedCountries.filter(country => country !== countryCode) + }, + isJurisdictionCountrySelected(countryCode) { + return (this.widgetConfigTemp.jurisdictionRules.countries || []).includes(countryCode) + }, + validateJurisdictionRules() { + this.ensureJurisdictionRules() + const rules = this.widgetConfigTemp.jurisdictionRules + + if (!rules.enabled) { + rules.countries = [] + rules.actionOnRestriction = rules.actionOnRestriction || 'HARD_REJECT' + return + } + + if (!['BLOCKLIST', 'ALLOWLIST'].includes(rules.strategy)) { + throw new Error('Kindly select a jurisdiction enforcement strategy') + } + + if (!Array.isArray(rules.countries) || rules.countries.length === 0) { + throw new Error('Kindly select at least one target country for jurisdiction restrictions') + } + + const invalidCountries = rules.countries.filter(country => !/^[A-Z]{3}$/.test(country) || !countries.alpha3ToAlpha2(country)) + if (invalidCountries.length > 0) { + throw new Error(`Invalid country code(s): ${invalidCountries.join(', ')}`) + } + + if (!['HARD_REJECT', 'MANUAL_REVIEW'].includes(rules.actionOnRestriction)) { + throw new Error('Kindly select a jurisdiction action on match') + } + }, selectedServiceEventHandler(event) { // Guard: ignore entries with no issuerDid @@ -865,6 +1339,7 @@ export default { this.widgetConfigTemp.isWidgetLogin = this.widgetConfigTemp.isWidgetLogin !== false this.widgetConfigTemp.isMobileAssistedVerification = this.widgetConfigTemp.isMobileAssistedVerification !== false this.migratedocumentUploadMode() + this.validateJurisdictionRules() if (!this.widgetConfigTemp.issuerDID) { throw new Error('Issuer DID is required') } @@ -924,6 +1399,7 @@ export default { await this.createAppsWidgetConfig() if (this.widgetConfig) { this.widgetConfigTemp = JSON.parse(JSON.stringify(this.widgetConfig)) + this.ensureJurisdictionRules() } this.isLoading = false @@ -947,6 +1423,7 @@ export default { if (this.widgetConfig) { // this.widgetConfigTemp = { ...this.widgetConfig } this.widgetConfigTemp.trustedIssuer = this.widgetConfigTemp.issuerDID ? true : false; + this.ensureJurisdictionRules() } this.isLoading = false } catch (e) { From 8b98f5d9fe408098bd3d306eab6f78742bf6f300 Mon Sep 17 00:00:00 2001 From: varsha <91577031+varsha766@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:15:52 +0530 Subject: [PATCH 33/34] Adr007 (#219) * implemented UI. to support Juridiction restriction * Adr008 (#214) * commented risk flagged iscon * fixed UI * integrated decion api * implemetned action drop adown as well * ui updates * update ui * update --------- Co-authored-by: vishwas1 * Adr008 (#217) * commented risk flagged iscon * fixed UI * integrated decion api * implemetned action drop adown as well * ui updates * update ui * update --------- Co-authored-by: vishwas1 Co-authored-by: Pratap2018 --------- Co-authored-by: vishwas1 Co-authored-by: Pratap Mridha From 89ad35721f7e27a5d2f7519ae88da5f2119da0e3 Mon Sep 17 00:00:00 2001 From: varsha <91577031+varsha766@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:16:43 +0530 Subject: [PATCH 34/34] Adr008 (#218) * implemented UI. to support Juridiction restriction * commented risk flagged iscon * fixed UI * integrated decion api * implemetned action drop adown as well * ui updates * update ui * update --------- Co-authored-by: vishwas1 Co-authored-by: Pratap2018