diff --git a/Changelog.md b/Changelog.md index 79fbd740..d13f9a12 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,12 @@ ## [Unreleased] +### [3.12.0]- 2026-07-03 +### Added +- Added toggle for requesting fields for selective disclosure +### Fixed +- Fixed a bug where a section was removed from the consent screen. + ### [3.11.1] - 2026-06-18 ### Added - Show tile for zkp diff --git a/package.json b/package.json index 56f96f1d..901cfcb1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "entity-developer-dashboard", - "version": "3.11.1", + "version": "3.12.0", "private": true, "scripts": { "serve": "vue-cli-service serve --mode production", diff --git a/public/Entity_c.png b/public/Entity_c.png index 7ee0c74c..8ecf9f47 100644 Binary files a/public/Entity_c.png and b/public/Entity_c.png differ diff --git a/src/assets/Entity_full.png b/src/assets/Entity_full.png index 53c81ab9..a263895b 100644 Binary files a/src/assets/Entity_full.png and b/src/assets/Entity_full.png differ diff --git a/src/views/ServiceConfig.vue b/src/views/ServiceConfig.vue index 3a2e49c2..4f441235 100644 --- a/src/views/ServiceConfig.vue +++ b/src/views/ServiceConfig.vue @@ -633,10 +633,11 @@ export default { const isEditing = this.isEditing; const isLogoChanged = isEditing && this.formData.logoUrl !== this.backupData?.logoUrl; const isIssuerChanged = isEditing && this.formData.issuerDid !== this.backupData?.issuerDid; + const isVerificationMethodChanged = isEditing && this.formData.issuerVerificationMethodId !== this.backupData?.issuerVerificationMethodId; const isAppNameChanged = isEditing && this.formData.appName !== this.backupData?.appName; const isDomainChanged = isEditing && this.formData.domain !== this.backupData?.domain; await this.updateAnAppOnServer({ ...this.formData }) - if (!isLogoChanged && !isIssuerChanged && !isAppNameChanged && !isDomainChanged) { + if (!isLogoChanged && !isIssuerChanged && !isVerificationMethodChanged && !isAppNameChanged && !isDomainChanged) { this.isEditing = false; return this.notifySuccess("Service configuration updated successfully!"); } @@ -644,7 +645,7 @@ export default { if (Object.keys(this.widgetConfig).length > 0) { let shouldUpdateWidgetConfig = false; let updatedWidgetConfig = { ...this.widgetConfig }; - if (isIssuerChanged && this.formData.issuerDid) { + if ((isIssuerChanged || isVerificationMethodChanged) && this.formData.issuerDid) { updatedWidgetConfig.issuerDID = this.formData.issuerDid; updatedWidgetConfig.issuerVerificationMethodId = this.formData.issuerVerificationMethodId || @@ -675,7 +676,7 @@ export default { if(Object.keys(this.kybWidgetConfig).length > 0 ){ let updatedKybWidgetConfig = { ...this.kybWidgetConfig }; let shouldUpdateKybWidgetConfig = false; - if (isIssuerChanged && this.formData.issuerDid) { + if ((isIssuerChanged || isVerificationMethodChanged) && this.formData.issuerDid) { updatedKybWidgetConfig.issuerDID = this.formData.issuerDid; updatedKybWidgetConfig.issuerVerificationMethodId = this.formData.issuerVerificationMethodId || diff --git a/src/views/playground/WidgetConfig/Index.vue b/src/views/playground/WidgetConfig/Index.vue index 50f9eee0..ab3639b8 100644 --- a/src/views/playground/WidgetConfig/Index.vue +++ b/src/views/playground/WidgetConfig/Index.vue @@ -117,6 +117,20 @@ ul { list-style-type: none; } +.selective-field-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 8px 12px; +} + +.selective-field-badge { + border: 1px solid #d8dee8; + color: #334155; + margin-right: 6px; + margin-bottom: 6px; + padding: 6px 8px; +} + /* .zkbadge { background-color: lightblue; margin-left: 3px; @@ -182,7 +196,6 @@ ul { -
  • @@ -244,7 +257,7 @@ ul {
    - +
    @@ -252,6 +265,31 @@ ul {
  • +
  • +
    +
    + + {{ widgetConfigUI.selectiveDisclosure.label }} + + +
    +
    + + +
    +
    +
  • +
  • @@ -400,10 +438,32 @@ export default { this.widgetConfigTemp.zkProof.proofs = []; this.widgetConfigTemp.onChainId.enabled = false; - this.widgetConfigTemp.onChainId.selectedOnChainKYCconfiguration = null; + this.widgetConfigTemp.onChainId.selectedOnChainKYCconfiguration = null; + + this.widgetConfigTemp.selectiveDisclosure.enabled = false; + this.widgetConfigTemp.selectiveDisclosure.fields = []; + this.widgetConfigTemp.selectiveDisclosure.frame = null; } } }, + + 'widgetConfigTemp.selectiveDisclosure.enabled': { + handler(enabled) { + if (enabled && !this.widgetConfigTemp.idOcr.enabled) { + this.widgetConfigTemp.selectiveDisclosure.enabled = false; + return; + } + + if (enabled && this.widgetConfigTemp.selectiveDisclosure.fields.length === 0) { + this.widgetConfigTemp.selectiveDisclosure.fields = [...this.defaultSelectiveDisclosureFields] + } + + if (!enabled) { + this.widgetConfigTemp.selectiveDisclosure.fields = []; + this.widgetConfigTemp.selectiveDisclosure.frame = null; + } + } + }, 'widgetConfigTemp.isWidgetLogin': { handler(newValue) { this.widgetConfigTemp.isVaultEnabled = newValue !== false @@ -505,6 +565,7 @@ export default { this.widgetConfigTemp.trustedIssuer = this.widgetConfigTemp.issuerDID ? true : false; + this.ensureSelectiveDisclosure() if (!this.widgetConfigTemp.zkProof.proofs) { @@ -552,6 +613,10 @@ export default { description: "Enable users to prove they meet a minimum age requirement without sharing their exact date of birth. Read more here." } }, + selectiveDisclosure: { + label: "Request only the information you need", + description: "Choose which verified identity attributes users will share with your application after completing identity verification. Attributes that are not selected remain private and are not included in the verification response." + }, emailNotification: { label: "Enable Email Notifications", description: "Notify users via email regarding the status of their ID verification. When enabled, users will receive automated updates upon the successful completion or rejection of their verification attempt." @@ -588,6 +653,11 @@ export default { enabled: false, proofs: [] }, + selectiveDisclosure: { + enabled: false, + fields: [], + frame: null, + }, trustedIssuer: true, isWidgetLogin: true, isVaultEnabled: true, @@ -597,6 +667,15 @@ export default { }, selectedIssuerDids: new Set(), ageProofCriteria: '', + defaultSelectiveDisclosureFields: ['givenNames', 'surname'], + selectiveDisclosureFieldOptions: [ + { value: 'docModel', text: 'Document Type' }, + { value: 'givenNames', text: 'Given Name(s)' }, + { value: 'surname', text: 'Surname' }, + { value: 'sex', text: 'Gender' }, + { value: 'nationality', text: 'Nationality' }, + { value: 'dateOfBirth', text: 'Date of Birth' }, + ], documentTypeOptions: [ { value: null, @@ -642,6 +721,74 @@ export default { .filter(did => !!did && !!did.trim()) .join(',') }, + ensureSelectiveDisclosure() { + if (!this.widgetConfigTemp.selectiveDisclosure) { + this.$set(this.widgetConfigTemp, 'selectiveDisclosure', { + enabled: false, + fields: [], + frame: null, + }) + } + + if (!Array.isArray(this.widgetConfigTemp.selectiveDisclosure.fields)) { + this.$set(this.widgetConfigTemp.selectiveDisclosure, 'fields', []) + } + + if (!Object.prototype.hasOwnProperty.call(this.widgetConfigTemp.selectiveDisclosure, 'frame')) { + this.$set(this.widgetConfigTemp.selectiveDisclosure, 'frame', null) + } + + if ( + this.widgetConfigTemp.selectiveDisclosure.enabled && + this.widgetConfigTemp.selectiveDisclosure.fields.length === 0 + ) { + const framedFields = Object.keys(this.widgetConfigTemp.selectiveDisclosure.frame?.credentialSubject || {}) + .filter(field => field !== '@explicit' && this.defaultSelectiveDisclosureFields.includes(field)) + + this.widgetConfigTemp.selectiveDisclosure.fields = framedFields.length > 0 + ? framedFields + : [...this.defaultSelectiveDisclosureFields] + } + }, + getSelectiveDisclosureFieldLabel(field) { + const option = this.selectiveDisclosureFieldOptions.find(item => item.value === field) + return option ? option.text : field + }, + generateSelectiveDisclosureFrame(fields) { + const credentialSubject = fields.reduce((frame, field) => { + frame[field] = {} + return frame + }, { + '@explicit': true, + }) + + return { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://raw.githubusercontent.com/hypersign-protocol/hypersign-contexts/main/BJJSignature2021.jsonld', + 'https://schema.org' + ], + type: [ + 'VerifiableCredential' + ], + credentialSubject, + } + }, + syncSelectiveDisclosure() { + this.ensureSelectiveDisclosure() + + if (!this.widgetConfigTemp.selectiveDisclosure.enabled) { + this.widgetConfigTemp.selectiveDisclosure.fields = [] + this.widgetConfigTemp.selectiveDisclosure.frame = null + return + } + + const selectedFields = this.widgetConfigTemp.selectiveDisclosure.fields + .filter(field => this.defaultSelectiveDisclosureFields.includes(field)) + + this.widgetConfigTemp.selectiveDisclosure.fields = selectedFields + this.widgetConfigTemp.selectiveDisclosure.frame = this.generateSelectiveDisclosureFrame(selectedFields) + }, syncAgeProof() { if (!this.widgetConfigTemp.zkProof.proofs) { this.widgetConfigTemp.zkProof.proofs = [] @@ -671,6 +818,16 @@ export default { } } + if (this.widgetConfigTemp.selectiveDisclosure.enabled) { + if (!this.widgetConfigTemp.idOcr.enabled) { + throw new Error('Selective disclosure requires ID document verification. Please enable ID document verification to proceed.') + } + + if (this.widgetConfigTemp.selectiveDisclosure.fields.length === 0) { + throw new Error('Kindly select information to request through selective disclosure') + } + } + if (!this.widgetConfigTemp.onChainId.enabled) { this.widgetConfigTemp.onChainId.selectedOnChainKYCconfiguration = null this.widgetConfigTemp.onChainId.enabled = false @@ -702,6 +859,7 @@ export default { //TODO validate all fields this.isLoading = true; this.syncAgeProof() + this.syncSelectiveDisclosure() this.validateField() this.setWidgetConfig(this.widgetConfigTemp) await this.createAppsWidgetConfig() @@ -722,6 +880,7 @@ export default { //TODO validate all field this.isLoading = true; this.syncAgeProof() + this.syncSelectiveDisclosure() this.validateField() this.setWidgetConfig(this.widgetConfigTemp)