From 06b9a4bba1e441e04eba6e2c2a3f41fc362b7674 Mon Sep 17 00:00:00 2001 From: elf Pavlik Date: Sun, 3 Dec 2023 12:34:11 -0600 Subject: [PATCH] [ui/authorization] ux: adjust access request elements --- examples/vuejectron/package.json | 2 +- packages/api-messages/src/payloads.ts | 4 +- packages/css-storage-fixture/config.json | 2 +- packages/data-model/src/crud/container.ts | 20 ++++-- packages/interfaces/package.json | 2 +- packages/service/package.json | 2 +- .../service/src/services/data-registries.ts | 2 +- .../service/src/services/social-agents.ts | 2 + .../test/unit/services/social-agents-test.ts | 9 ++- packages/utils/src/sparql-update.ts | 12 +--- packages/utils/test/sparql-update-test.ts | 12 +--- pnpm-lock.yaml | 64 +++++++++++-------- ui/authorization/package.json | 2 +- .../src/components/AuthorizeApp.vue | 2 +- ui/authorization/src/plugins/vuetify.ts | 5 +- ui/authorization/src/store/app.ts | 20 +++--- .../src/views/DataRegistryList.vue | 52 +++++++++++++-- .../src/views/SocialAgentList.vue | 60 +++++++---------- 18 files changed, 156 insertions(+), 118 deletions(-) diff --git a/examples/vuejectron/package.json b/examples/vuejectron/package.json index 6fefe588..bb23b683 100644 --- a/examples/vuejectron/package.json +++ b/examples/vuejectron/package.json @@ -20,7 +20,7 @@ "roboto-fontface": "*", "vue": "^3.3.4", "vue-router": "^4.2.5", - "vuetify": "^3.3.21", + "vuetify": "^3.4.4", "webfontloader": "^1.6.28" }, "devDependencies": { diff --git a/packages/api-messages/src/payloads.ts b/packages/api-messages/src/payloads.ts index 9a7b3dcc..8f77a415 100644 --- a/packages/api-messages/src/payloads.ts +++ b/packages/api-messages/src/payloads.ts @@ -21,7 +21,9 @@ export interface SocialAgent extends UniqueId { label: string; note?: string; accessNeedGroup?: IRI; - authorizationDate: string; // interop:registeredAt + accessRequested: boolean; + accessGrant?: IRI; + authorizationDate: string; // interop:registeredAt TODO: rename to not imply access lastUpdateDate?: string; // interop:updatedAt } diff --git a/packages/css-storage-fixture/config.json b/packages/css-storage-fixture/config.json index 3cff127e..88edbb50 100644 --- a/packages/css-storage-fixture/config.json +++ b/packages/css-storage-fixture/config.json @@ -30,7 +30,7 @@ "css:config/util/index/default.json", "css:config/util/logging/winston.json", "css:config/util/representation-conversion/default.json", - "css:config/util/resource-locker/file.json", + "css:config/util/resource-locker/memory.json", "css:config/util/variables/default.json" ], "@graph": [] diff --git a/packages/data-model/src/crud/container.ts b/packages/data-model/src/crud/container.ts index 8d6f3a3a..ec6fdc7d 100644 --- a/packages/data-model/src/crud/container.ts +++ b/packages/data-model/src/crud/container.ts @@ -13,17 +13,23 @@ export class CRUDContainer extends CRUDResource { return containedIri; } - public async applyPatch(sparqlUpdate: string): Promise { + public async applyPatch(sparqlUpdate: string, create = false): Promise { await this.discoverDescriptionResource(); // update the Description Resource - const { ok } = await this.fetch(this.descriptionResourceIri, { + const headers: { [key: string]: string } = { + 'Content-Type': 'application/sparql-update' + }; + if (create) { + headers['If-None-Match'] = '*'; + } else { + headers['If-Match'] = '*'; + } + const response = await this.fetch(this.descriptionResourceIri, { method: 'PATCH', body: sparqlUpdate, - headers: { - 'Content-Type': 'application/sparql-update' - } + headers }); - if (!ok) { + if (!response.ok) { throw new Error(`failed to patch ${this.descriptionResourceIri}`); } } @@ -69,6 +75,6 @@ export class CRUDContainer extends CRUDResource { await this.discoverDescriptionResource(); const sparqlUpdate = await insertPatch(this.dataset); - await this.applyPatch(sparqlUpdate); + await this.applyPatch(sparqlUpdate, true); } } diff --git a/packages/interfaces/package.json b/packages/interfaces/package.json index 73b529ac..8570397a 100644 --- a/packages/interfaces/package.json +++ b/packages/interfaces/package.json @@ -13,7 +13,7 @@ "author": "", "license": "MIT", "dependencies": { - "@inrupt/solid-client-authn-node": "^1.17.4", + "@inrupt/solid-client-authn-node": "^1.17.5", "@janeirodigital/interop-authorization-agent": "^1.0.0-rc.22" }, "devDependencies": { diff --git a/packages/service/package.json b/packages/service/package.json index fb737098..6548de80 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -40,7 +40,7 @@ "@digita-ai/handlersjs-core": "^0.19.3", "@digita-ai/handlersjs-http": "^0.19.3", "@digita-ai/handlersjs-logging": "^0.19.3", - "@inrupt/solid-client-authn-node": "^1.17.4", + "@inrupt/solid-client-authn-node": "^1.17.5", "@janeirodigital/interop-authorization-agent": "^1.0.0-rc.22", "@janeirodigital/interop-data-model": "^1.0.0-rc.22", "@janeirodigital/interop-utils": "^1.0.0-rc.22", diff --git a/packages/service/src/services/data-registries.ts b/packages/service/src/services/data-registries.ts index a4633ee8..4ba93f18 100644 --- a/packages/service/src/services/data-registries.ts +++ b/packages/service/src/services/data-registries.ts @@ -62,7 +62,7 @@ export const getDataRegistries = async (agentId: string, descriptionsLang: strin throw new Error(`missing social agent registration: ${agentId}`); } if (!socialAgentRegistration.accessGrant) { - throw new Error(`missing access grant for social agent: ${agentId}`); + return []; } const dataGrantIndex = socialAgentRegistration.accessGrant.hasDataGrant.reduce( (acc, dataGrant) => { diff --git a/packages/service/src/services/social-agents.ts b/packages/service/src/services/social-agents.ts index 9c33651d..9b943334 100644 --- a/packages/service/src/services/social-agents.ts +++ b/packages/service/src/services/social-agents.ts @@ -13,6 +13,8 @@ const buildSocialAgentProfile = (registration: CRUDSocialAgentRegistration): Soc note: registration.note, authorizationDate: registration.registeredAt!.toISOString(), lastUpdateDate: registration.updatedAt?.toISOString(), + accessGrant: registration.accessGrant?.iri, + accessRequested: !!registration.hasAccessNeedGroup, accessNeedGroup: registration.reciprocalRegistration?.hasAccessNeedGroup }); export const getSocialAgents = async (saiSession: AuthorizationAgent) => { diff --git a/packages/service/test/unit/services/social-agents-test.ts b/packages/service/test/unit/services/social-agents-test.ts index 52ec7cd0..04b6c2ab 100644 --- a/packages/service/test/unit/services/social-agents-test.ts +++ b/packages/service/test/unit/services/social-agents-test.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-extraneous-dependencies */ import { jest } from '@jest/globals'; import { AuthorizationAgent } from '@janeirodigital/interop-authorization-agent'; import { addSocialAgent, getSocialAgents } from '../../../src/services/social-agents'; @@ -33,11 +34,13 @@ describe('getSocialAgents', () => { id: 'https://alice.example', label: 'Alice', note: 'A friend from a football team', + accessRequested: false, authorizationDate: '2020-04-04T20:15:47.000Z' }, { id: 'https://bob.example', label: 'Bob', + accessRequested: false, authorizationDate: '2021-07-02T10:12:21.000Z', lastUpdateDate: '2021-09-24T11:12:21.000Z' } @@ -65,7 +68,8 @@ describe('addSocialAgent', () => { expect(result).toEqual({ id: existing.registeredAgent, label: existing.label, - authorizationDate: existing.registeredAt.toISOString() + authorizationDate: existing.registeredAt.toISOString(), + accessRequested: false }); expect(addSocialAgentRegistration).not.toBeCalled(); }); @@ -83,7 +87,8 @@ describe('addSocialAgent', () => { id: bobWebId, label, note, - authorizationDate: expect.any(String) + authorizationDate: expect.any(String), + accessRequested: false }); expect(addSocialAgentRegistration).toBeCalledWith(bobWebId, label, note); }); diff --git a/packages/utils/src/sparql-update.ts b/packages/utils/src/sparql-update.ts index 40435a68..0ae47693 100644 --- a/packages/utils/src/sparql-update.ts +++ b/packages/utils/src/sparql-update.ts @@ -2,17 +2,9 @@ import { DatasetCore } from '@rdfjs/types'; import { serializeTurtle } from './turtle-serializer'; export async function insertPatch(dataset: DatasetCore): Promise { - return ` - INSERT DATA { - ${await serializeTurtle(dataset)} - } - `; + return `INSERT DATA { ${await serializeTurtle(dataset)} }`; } export async function deletePatch(dataset: DatasetCore): Promise { - return ` - DELETE DATA { - ${await serializeTurtle(dataset)} - } - `; + return `DELETE DATA { ${await serializeTurtle(dataset)} }`; } diff --git a/packages/utils/test/sparql-update-test.ts b/packages/utils/test/sparql-update-test.ts index d442c1c5..351c8d1a 100644 --- a/packages/utils/test/sparql-update-test.ts +++ b/packages/utils/test/sparql-update-test.ts @@ -16,21 +16,13 @@ const snippet = ` test('insertPatch', async () => { const dataset = await parseTurtle(snippet); const patch = await insertPatch(dataset); - const expected = ` - INSERT DATA { - ${await serializeTurtle(dataset)} - } - `; + const expected = `INSERT DATA { ${await serializeTurtle(dataset)} }`; expect(patch).toBe(expected); }); test('deletePatch', async () => { const dataset = await parseTurtle(snippet); const patch = await deletePatch(dataset); - const expected = ` - DELETE DATA { - ${await serializeTurtle(dataset)} - } - `; + const expected = `DELETE DATA { ${await serializeTurtle(dataset)} }`; expect(patch).toBe(expected); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4999d29..040c3c29 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,8 +105,8 @@ importers: specifier: ^4.2.5 version: 4.2.5(vue@3.3.4) vuetify: - specifier: ^3.3.21 - version: 3.3.21(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + specifier: ^3.4.4 + version: 3.4.4(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) webfontloader: specifier: ^1.6.28 version: 1.6.28 @@ -143,7 +143,7 @@ importers: version: 4.4.11(@types/node@20.8.6)(sass@1.69.3) vite-plugin-vuetify: specifier: ^1.0.2 - version: 1.0.2(vite@4.4.11)(vue@3.3.4)(vuetify@3.3.21) + version: 1.0.2(vite@4.4.11)(vue@3.3.4)(vuetify@3.4.4) vue-tsc: specifier: ^1.8.19 version: 1.8.19(typescript@5.2.2) @@ -228,8 +228,8 @@ importers: packages/interfaces: dependencies: '@inrupt/solid-client-authn-node': - specifier: ^1.17.4 - version: 1.17.4 + specifier: ^1.17.5 + version: 1.17.5 '@janeirodigital/interop-authorization-agent': specifier: ^1.0.0-rc.22 version: link:../authorization-agent @@ -256,8 +256,8 @@ importers: specifier: ^0.19.3 version: 0.19.3 '@inrupt/solid-client-authn-node': - specifier: ^1.17.4 - version: 1.17.4 + specifier: ^1.17.5 + version: 1.17.5 '@janeirodigital/interop-authorization-agent': specifier: ^1.0.0-rc.22 version: link:../authorization-agent @@ -388,8 +388,8 @@ importers: specifier: ^4.2.5 version: 4.2.5(vue@3.3.4) vuetify: - specifier: ^3.3.21 - version: 3.3.21(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + specifier: ^3.4.4 + version: 3.4.4(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) webfontloader: specifier: ^1.6.28 version: 1.6.28 @@ -423,7 +423,7 @@ importers: version: 4.4.11(@types/node@20.8.6)(sass@1.69.3) vite-plugin-vuetify: specifier: ^1.0.2 - version: 1.0.2(vite@4.4.11)(vue@3.3.4)(vuetify@3.3.21) + version: 1.0.2(vite@4.4.11)(vue@3.3.4)(vuetify@3.4.4) vue-tsc: specifier: ^1.8.19 version: 1.8.19(typescript@5.2.2) @@ -3099,11 +3099,23 @@ packages: - encoding dev: false - /@inrupt/solid-client-authn-node@1.17.4: - resolution: {integrity: sha512-wvG3FvW0YXq8GEV8daFwg0ndvsaAFn+48M6K7FR1xp7ve/S6rEMDZov8F05JhvDyZv3PyB7RnWZSUnDGLM3Y+g==} + /@inrupt/solid-client-authn-core@1.17.5: + resolution: {integrity: sha512-g3WShcPAqGuarPYlw12vUCo+et4elQLI+WYcHkCHGLuQQFF73r2iTicuKpkydQdIrZ5AZgxhwr315jmkx/vcFQ==} engines: {node: ^16.0.0 || ^18.0.0 || ^20.0.0} dependencies: - '@inrupt/solid-client-authn-core': 1.17.4 + '@inrupt/universal-fetch': 1.0.3 + events: 3.3.0 + jose: 4.15.4 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + dev: false + + /@inrupt/solid-client-authn-node@1.17.5: + resolution: {integrity: sha512-lY2H/Uwy4vemKr2uvQ76e1QbiWZ6oevLzqh3K3JHwRLSh1CSLBNSfNKRiTTWNHOfPBGhBuwL5amA5yl4y208rQ==} + engines: {node: ^16.0.0 || ^18.0.0 || ^20.0.0} + dependencies: + '@inrupt/solid-client-authn-core': 1.17.5 '@inrupt/universal-fetch': 1.0.3 jose: 4.15.4 openid-client: 5.5.0 @@ -3153,8 +3165,8 @@ packages: engines: {node: '>=8'} dev: true - /@janeirodigital/interop-utils@1.0.0-rc.21: - resolution: {integrity: sha512-+DTzWtoYmW6m2yCNuI/V0S8reZKPWvsgWOd3D5F6ChVMKTdwpe3DxkmJvgF1wo9jjS5ZeyUHiPSI5sSMDxYzvg==} + /@janeirodigital/interop-utils@1.0.0-rc.22: + resolution: {integrity: sha512-yXTeihFUIoPyU7wJf8uJlmmnbVAmSdd0FafahuqE1DbDNFH1hQ5eSRGryjLUnXgiaavTysWh+uFUqSFTn0VQwQ==} dependencies: http-link-header: 1.1.1 jsonld-streaming-parser: 3.2.1 @@ -3582,7 +3594,7 @@ packages: /@solid-notifications/discovery@0.1.0: resolution: {integrity: sha512-n96D0LABrGCrJwRUm92cNE3LoeyE77TmisnJzA7NuHEHbFmf6q0CyUWE6K75aWyBp7MjrCOS/6pIzBTp5so/xg==} dependencies: - '@janeirodigital/interop-utils': 1.0.0-rc.21 + '@janeirodigital/interop-utils': 1.0.0-rc.22 n3: 1.17.1 transitivePeerDependencies: - encoding @@ -3591,7 +3603,7 @@ packages: /@solid-notifications/subscription@0.1.0: resolution: {integrity: sha512-BVXhM1FpLlpfjhGZ36SBjwDGTbvCfyuNQoOu+MCAZBntYjyzaUJ3OESvx4/C+RyvkvGsCKCEVKPrnEzsPSNZAw==} dependencies: - '@janeirodigital/interop-utils': 1.0.0-rc.21 + '@janeirodigital/interop-utils': 1.0.0-rc.22 '@solid-notifications/discovery': 0.1.0 n3: 1.17.1 transitivePeerDependencies: @@ -4414,7 +4426,7 @@ packages: - typescript dev: true - /@vuetify/loader-shared@1.7.1(vue@3.3.4)(vuetify@3.3.21): + /@vuetify/loader-shared@1.7.1(vue@3.3.4)(vuetify@3.4.4): resolution: {integrity: sha512-kLUvuAed6RCvkeeTNJzuy14pqnkur8lTuner7v7pNE/kVhPR97TuyXwBSBMR1cJeiLiOfu6SF5XlCYbXByEx1g==} peerDependencies: vue: ^3.0.0 @@ -4423,7 +4435,7 @@ packages: find-cache-dir: 3.3.2 upath: 2.0.1 vue: 3.3.4 - vuetify: 3.3.21(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + vuetify: 3.4.4(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) /@vueuse/core@10.5.0(vue@3.3.4): resolution: {integrity: sha512-z/tI2eSvxwLRjOhDm0h/SXAjNm8N5ld6/SC/JQs6o6kpJ6Ya50LnEL8g5hoYu005i28L0zqB5L5yAl8Jl26K3A==} @@ -9377,18 +9389,18 @@ packages: engines: {node: '>= 0.8'} dev: false - /vite-plugin-vuetify@1.0.2(vite@4.4.11)(vue@3.3.4)(vuetify@3.3.21): + /vite-plugin-vuetify@1.0.2(vite@4.4.11)(vue@3.3.4)(vuetify@3.4.4): resolution: {integrity: sha512-MubIcKD33O8wtgQXlbEXE7ccTEpHZ8nPpe77y9Wy3my2MWw/PgehP9VqTp92BLqr0R1dSL970Lynvisx3UxBFw==} engines: {node: '>=12'} peerDependencies: vite: ^2.7.0 || ^3.0.0 || ^4.0.0 vuetify: ^3.0.0-beta.4 dependencies: - '@vuetify/loader-shared': 1.7.1(vue@3.3.4)(vuetify@3.3.21) + '@vuetify/loader-shared': 1.7.1(vue@3.3.4)(vuetify@3.4.4) debug: 4.3.4 upath: 2.0.1 vite: 4.4.11(@types/node@20.8.6)(sass@1.69.3) - vuetify: 3.3.21(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + vuetify: 3.4.4(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) transitivePeerDependencies: - supports-color - vue @@ -9499,13 +9511,13 @@ packages: '@vue/server-renderer': 3.3.4(vue@3.3.4) '@vue/shared': 3.3.4 - /vuetify@3.3.21(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4): - resolution: {integrity: sha512-CVC6MDC45H+IY8Nq5QO/lBhbSd/dg+IV46GKd+LIndriLoQXG+tFv31NQY++tOQgrKeZr6w2SUBMBxSZyHErfg==} + /vuetify@3.4.4(typescript@5.2.2)(vite-plugin-vuetify@1.0.2)(vue@3.3.4): + resolution: {integrity: sha512-qnQy3iPOLMcyKUAb5Oi2hw+2qjc9NQK/M2i95ZUvjpHEF5mTBvGwCJ2xXV4Yn9eaGhuUtWBqgR/3Op42S3d41g==} engines: {node: ^12.20 || >=14.13} peerDependencies: typescript: '>=4.7' vite-plugin-vuetify: ^1.0.0-alpha.12 - vue: ^3.2.0 + vue: ^3.3.0 vue-i18n: ^9.0.0 webpack-plugin-vuetify: ^2.0.0-alpha.11 peerDependenciesMeta: @@ -9519,7 +9531,7 @@ packages: optional: true dependencies: typescript: 5.2.2 - vite-plugin-vuetify: 1.0.2(vite@4.4.11)(vue@3.3.4)(vuetify@3.3.21) + vite-plugin-vuetify: 1.0.2(vite@4.4.11)(vue@3.3.4)(vuetify@3.4.4) vue: 3.3.4 /walker@1.0.8: diff --git a/ui/authorization/package.json b/ui/authorization/package.json index 0b1e5166..e6a8958e 100644 --- a/ui/authorization/package.json +++ b/ui/authorization/package.json @@ -15,7 +15,7 @@ "roboto-fontface": "*", "vue": "^3.3.4", "vue-router": "^4.2.5", - "vuetify": "^3.3.21", + "vuetify": "^3.4.4", "webfontloader": "^1.6.28" }, "devDependencies": { diff --git a/ui/authorization/src/components/AuthorizeApp.vue b/ui/authorization/src/components/AuthorizeApp.vue index a47616e2..954bb20d 100644 --- a/ui/authorization/src/components/AuthorizeApp.vue +++ b/ui/authorization/src/components/AuthorizeApp.vue @@ -185,7 +185,7 @@ span.label { {{ dataInstance.label }} @@ -51,6 +51,30 @@ span.label { + + + Access requested + + + + + + + + + @@ -58,17 +82,35 @@ span.label { import { useCoreStore } from '@/store/core'; import { useAppStore } from '@/store/app'; import { useRoute } from 'vue-router'; -import { watch } from 'vue'; +import { computed, ref, watch } from 'vue'; const appStore = useAppStore() const coreStore = useCoreStore() const route = useRoute() +const appListDialog = ref(false) + +appStore.listSocialAgents() +appStore.listApplications() + +function requestAccess(applicationId: string) { + if (!route.query.agent) return + appStore.requestAccess(applicationId, route.query.agent as string) + appListDialog.value = false +} + +const agent = computed(() => { + return appStore.socialAgentList.find((a) => a.id === route.query.agent); +}); + +const hasData = computed(() => { + return !!appStore.dataRegistryList[route.query.agent as string]?.length +}); watch( () => route.query.agent, - async (agent) => { - if (agent) { - await appStore.listDataRegistries(agent as string, coreStore.lang) + async (agentId) => { + if (agentId) { + await appStore.listDataRegistries(agentId as string, coreStore.lang) } }, { immediate: true } diff --git a/ui/authorization/src/views/SocialAgentList.vue b/ui/authorization/src/views/SocialAgentList.vue index dfed99d3..89861f44 100644 --- a/ui/authorization/src/views/SocialAgentList.vue +++ b/ui/authorization/src/views/SocialAgentList.vue @@ -1,57 +1,43 @@