From 14916eb12884d859ae16a81a5ea88c54291dcb64 Mon Sep 17 00:00:00 2001 From: Alban Mouton Date: Thu, 19 Dec 2024 13:49:39 +0100 Subject: [PATCH] feat: work on multi-site management --- api/types/site/schema.js | 44 ++++++++++++++++++----------- dev/resources/nginx.conf | 48 +++++++++++++++++++++++++++++--- package-lock.json | 24 ++++++++-------- ui/index.html | 2 +- ui/package.json | 2 +- ui/src/components/site-patch.vue | 5 +++- ui/src/pages/admin/sites.vue | 2 +- ui/src/pages/login.vue | 5 ++-- ui/vite.config.ts | 7 +++-- 9 files changed, 97 insertions(+), 42 deletions(-) diff --git a/api/types/site/schema.js b/api/types/site/schema.js index 36bd6e5e..fdd04a0e 100644 --- a/api/types/site/schema.js +++ b/api/types/site/schema.js @@ -91,10 +91,12 @@ export default { ] }, authOnlyOtherSite: { - 'x-if': "parent.value.authMode === 'onlyOtherSite'", + layout: { + if: 'parent.data.authMode === "onlyOtherSite"', + getItems: 'context.otherSites' + }, type: 'string', - title: "Autre site pour l'authentification", - 'x-fromData': 'context.otherSites' + title: "Autre site pour l'authentification" }, reducedPersonalInfoAtCreation: { type: 'boolean', @@ -103,12 +105,14 @@ export default { }, tosMessage: { type: 'string', - 'x-display': 'textarea', + layout: 'textarea', title: "Message des conditions d'utilisation", description: "Vous pouvez remplacer le message des conditions d'utilisation par défaut." }, authProviders: { - 'x-if': "parent.value.authMode !== 'onlyOtherSite' && parent.value.authMode !== 'onlyBackOffice'", + layout: { + if: "parent.data.authMode !== 'onlyOtherSite' && parent.data.authMode !== 'onlyBackOffice'" + }, type: 'array', title: "Fournisseurs d'identité (SSO)", items: { @@ -128,6 +132,9 @@ export default { title: 'Nom' } }, + oneOfLayout: { + label: 'Type de fournisseur', + }, oneOf: [ { $ref: '#/$defs/oidcProvider' @@ -141,13 +148,14 @@ export default { properties: { type: { type: 'string', - title: 'Type de fournisseur', const: 'otherSite' }, site: { type: 'string', title: 'Site', - 'x-fromData': 'context.otherSites' + layout: { + getItems: 'context.otherSites' + } } } }, @@ -160,19 +168,22 @@ export default { properties: { type: { type: 'string', - title: 'Type de fournisseur', const: 'otherSiteProvider' }, site: { type: 'string', title: 'Site', - 'x-fromData': 'context.otherSites' + layout: { + getItems: 'context.otherSites' + } }, provider: { type: 'string', title: 'Fournisseur', - 'x-if': 'parent.value.site', - 'x-fromData': 'context.otherSitesProviders[parent.value.site]' + layout: { + if: 'parent.data.site', + getItems: 'context.otherSitesProviders[parent.data.site]' + } } } } @@ -189,20 +200,19 @@ export default { 'client' ], properties: { + type: { + type: 'string', + const: 'oidc' + }, color: { type: 'string', title: 'Couleur', - 'x-display': 'color-picker' + layout: 'color-picker' }, img: { type: 'string', title: 'URL du logo (petite taille)' }, - type: { - type: 'string', - title: 'Type de fournisseur', - const: 'oidc' - }, discovery: { type: 'string', title: 'URL de découverte', diff --git a/dev/resources/nginx.conf b/dev/resources/nginx.conf index b0f10fb3..0abb1e58 100644 --- a/dev/resources/nginx.conf +++ b/dev/resources/nginx.conf @@ -103,8 +103,7 @@ http { rewrite ^/mails/(.*) /$1 break; proxy_pass http://localhost:1080/; } - location /notify { - rewrite ^/notify/(.*) /$1 break; + location /events { proxy_pass http://localhost:8088/; } } @@ -143,8 +142,49 @@ http { rewrite ^/mails/(.*) /$1 break; proxy_pass http://localhost:1080/; } - location /notify { - rewrite ^/notify/(.*) /$1 break; + location /events { + proxy_pass http://localhost:8088/; + } + } + + # another one to simulate multi-site with a path prefix + server { + listen 6099; + server_name _; + + # Transmit host, protocol and user ip, we use it for routing, rate limiting, etc. + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Client-IP $remote_addr; + # web socket support + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + + # redirect root to /simple-directory/ + location = / { + return 302 /site-prefix/simple-directory/; + } + location = /site-prefix { + return 302 /site-prefix/simple-directory/; + } + + location /site-prefix/simple-directory/api { + proxy_pass http://localhost:5690; + } + location /site-prefix/simple-directory/.well-known { + proxy_pass http://localhost:5690; + } + location /site-prefix/simple-directory { + proxy_pass http://localhost:6220; + } + location /site-prefix/mails { + rewrite ^/mails/(.*) /$1 break; + proxy_pass http://localhost:1080/; + } + location /site-prefix/events { proxy_pass http://localhost:8088/; } } diff --git a/package-lock.json b/package-lock.json index 29134387..a93a0245 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1448,12 +1448,12 @@ "license": "MIT" }, "node_modules/@json-layout/core": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@json-layout/core/-/core-1.2.2.tgz", - "integrity": "sha512-aVXS0YzK3m9NDl+pX7UHiElzXJIFqF/e45MVMmIKk1mGtlw5ZuW69gdsWE7qlZo5u5iDT4f0nGE32ZnYXD2p4g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@json-layout/core/-/core-1.3.0.tgz", + "integrity": "sha512-1iW7rnWER4d9eXOWjuTPLsBE2RZwVa5NIOd48XTebK1VBT5JTTo+UKfkmX6lk5rYiepLZkXt2w3LJ9n8tfCyEA==", "license": "MIT", "dependencies": { - "@json-layout/vocabulary": "~1.2.1", + "@json-layout/vocabulary": "~1.3.0", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", @@ -1482,9 +1482,9 @@ } }, "node_modules/@json-layout/vocabulary": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@json-layout/vocabulary/-/vocabulary-1.2.1.tgz", - "integrity": "sha512-9M0XfKRaPX8fDnBWfJVfJ9+3d/CQrg0O6iINAkvsgSNlQhMKVxouuXEowV9gFSwIWPn72DZXsJG2BL+PUGHrdQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@json-layout/vocabulary/-/vocabulary-1.3.0.tgz", + "integrity": "sha512-TbrLquRMz6JwPedSeVMZgTiCJ0h12V1T8FZueqLqH6Rrmxhlr+6goo+9/6Bk2PAJHey0q7byq7pEZ3yPborMCg==", "license": "MIT", "dependencies": { "ajv": "^8.12.0", @@ -1524,12 +1524,12 @@ } }, "node_modules/@koumoul/vjsf": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@koumoul/vjsf/-/vjsf-3.5.0.tgz", - "integrity": "sha512-VULSOoTIoLvEMq8MAOnNC3Iz4DG6LtVR231p9OFdyxGUO/cHTExgEJT8ROytG7YSAy57fUZ8tz7Qexvu0cMnAg==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@koumoul/vjsf/-/vjsf-3.6.2.tgz", + "integrity": "sha512-6UetgXyTlxq7rRkUsHmCL/KuEP7ccMVwAAbDo3QSqz5BfR4SPXnbhoT0l/r/uSvM3kEPErYKw2MqCmyhzaoilA==", "license": "MIT", "dependencies": { - "@json-layout/core": "~1.2.1", + "@json-layout/core": "~1.3.0", "@vueuse/core": "^10.5.0", "debug": "^4.3.4" }, @@ -12693,7 +12693,7 @@ "@data-fair/lib-vuetify": "^1.6.6", "@intlify/unplugin-vue-i18n": "^5.2.0", "@koumoul/v-iframe": "^2.4.4", - "@koumoul/vjsf": "^3.5.0", + "@koumoul/vjsf": "^3.6.2", "@mdi/js": "^7.4.47", "@types/config": "^3.3.5", "@unhead/vue": "^1.11.10", diff --git a/ui/index.html b/ui/index.html index 63d2de0e..df61413a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -11,6 +11,6 @@ window.__UI_CONFIG={UI_CONFIG}; window.__SITE_PATH="{SITE_PATH}"; - + diff --git a/ui/package.json b/ui/package.json index b067f759..03a013c5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -18,7 +18,7 @@ "@data-fair/lib-vuetify": "^1.6.6", "@intlify/unplugin-vue-i18n": "^5.2.0", "@koumoul/v-iframe": "^2.4.4", - "@koumoul/vjsf": "^3.5.0", + "@koumoul/vjsf": "^3.6.2", "@mdi/js": "^7.4.47", "@types/config": "^3.3.5", "@unhead/vue": "^1.11.10", diff --git a/ui/src/components/site-patch.vue b/ui/src/components/site-patch.vue index 265f0f4e..cc81234a 100644 --- a/ui/src/components/site-patch.vue +++ b/ui/src/components/site-patch.vue @@ -77,6 +77,7 @@ const valid = ref(false) const form = ref>() const vjsfOptions = computed(() => ({ + density: 'comfortable', context: { otherSites: sites.filter(s => s._id !== site._id).map(site => site.host), otherSitesProviders: sites.reduce((a, site) => { a[site.host] = (site.authProviders || []).filter(p => p.type === 'oidc').map(p => `${p.type}:${p.id}`); return a }, {} as Record) @@ -85,7 +86,9 @@ const vjsfOptions = computed(() => ({ watch(menu, () => { if (!menu.value) return - patch.value = JSON.parse(JSON.stringify(site)) + const siteClone = JSON.parse(JSON.stringify(site)) + delete siteClone._id + patch.value = siteClone }) const confirmEdit = async () => { diff --git a/ui/src/pages/admin/sites.vue b/ui/src/pages/admin/sites.vue index 3dda43e3..6bb022bf 100644 --- a/ui/src/pages/admin/sites.vue +++ b/ui/src/pages/admin/sites.vue @@ -35,7 +35,7 @@ /> {{ props.item.host }} diff --git a/ui/src/pages/login.vue b/ui/src/pages/login.vue index 6615792e..74cdc44d 100644 --- a/ui/src/pages/login.vue +++ b/ui/src/pages/login.vue @@ -919,11 +919,10 @@ watch(separateEmailPasswordSteps, (value) => { let redirectToOtherSite = false if (sitePublic.value?.authMode === 'onlyBackOffice') { redirectToOtherSite = true - const mainLoginUrl = new URL(window.location.href) - mainLoginUrl.host = mainPublicUrl.host - window.location.replace(mainLoginUrl.href) + window.location.replace(window.location.href.replace($sdUrl, mainPublicUrl.href)) } if (sitePublic.value?.authMode === 'onlyOtherSite' && sitePublic.value?.authOnlyOtherSite) { + // WARNING: this auth mode is not yet compatible with sites that have a path prefix redirectToOtherSite = true const otherSiteLoginUrl = new URL(window.location.href) otherSiteLoginUrl.host = sitePublic.value?.authOnlyOtherSite diff --git a/ui/vite.config.ts b/ui/vite.config.ts index eacc07f7..a922ec15 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -12,9 +12,12 @@ import microTemplate from '@data-fair/lib-utils/micro-template.js' import { autoImports } from '@data-fair/lib-vuetify/vite.js' import { commonjsDeps } from '@koumoul/vjsf/utils/build.js' +// const devSitePath = '/site-prefix' +const devSitePath = '' + // https://vitejs.dev/config/ export default defineConfig({ - base: '/simple-directory', + base: devSitePath + '/simple-directory', optimizeDeps: { include: commonjsDeps }, build: { rollupOptions: { @@ -108,7 +111,7 @@ export default defineConfig({ // in production this injection will be performed by an express middleware if (process.env.NODE_ENV !== 'development') return html const { uiConfig } = await import('../api/src/ui-config.ts') - return microTemplate(html, { SITE_PATH: '', UI_CONFIG: JSON.stringify(uiConfig) }) + return microTemplate(html, { SITE_PATH: devSitePath, UI_CONFIG: JSON.stringify(uiConfig) }) } } ],