From c772f28a326ae82654c1ed4d7edbbd611d1117d0 Mon Sep 17 00:00:00 2001 From: Alban Mouton Date: Tue, 24 Feb 2026 14:50:45 +0100 Subject: [PATCH] chore: segregated env ports management --- .env | 18 +++ .zellij.kdl | 2 +- CONTRIBUTING.md | 12 +- api/config/default.cjs | 10 +- api/config/development.cjs | 37 +++++-- api/config/test.cjs | 27 ++++- api/src/storages/ldap.ts | 3 + dev/delete-worktree.sh | 29 +++++ dev/init-env.sh | 25 +++++ .../{nginx.conf => nginx.conf.template} | 88 +++++---------- dev/resources/organizations.json | 2 +- dev/worktree.sh | 42 +++++++ docker-compose.yml | 44 +++++--- package-lock.json | 104 ++++++++++++++---- package.json | 6 +- test-it/invitations.ts | 34 +++--- test-it/jwks.ts | 6 +- test-it/ldap-per-org-mongo.ts | 2 +- test-it/oidc-core-id.ts | 8 +- test-it/sites.ts | 8 +- test-it/users.ts | 6 +- test-it/utils/index.ts | 17 +-- ui/package.json | 2 +- ui/src/components/organization-storage.vue | 2 +- ui/src/pages/admin/sites/index.vue | 1 - 25 files changed, 361 insertions(+), 174 deletions(-) create mode 100644 .env create mode 100755 dev/delete-worktree.sh create mode 100755 dev/init-env.sh rename dev/resources/{nginx.conf => nginx.conf.template} (68%) create mode 100755 dev/worktree.sh diff --git a/.env b/.env new file mode 100644 index 00000000..707be32a --- /dev/null +++ b/.env @@ -0,0 +1,18 @@ +NGINX_PORT1=22212 +NGINX_PORT2=22213 +NGINX_PORT3=22214 +NGINX_PORT4=22215 + +DEV_API_PORT=22222 +DEV_UI_PORT=22223 +DEV_OBSERVER_PORT=22224 +MAILDEV_UI_PORT=22225 +MAILDEV_SMTP_PORT=22226 + +MONGO_PORT=22232 +LDAP_PORT=22233 +LDAP_ADMIN_PORT=22235 +OIDC_PROVIDER_PORT=22236 +KEYCLOAK_PORT=22237 + +EVENTS_PORT=22242 diff --git a/.zellij.kdl b/.zellij.kdl index 052d7535..bc04b32d 100644 --- a/.zellij.kdl +++ b/.zellij.kdl @@ -23,6 +23,6 @@ layout { } pane size=1 borderless=true { command "bash" - args "-ic" "echo -n -e \"Dev server available at \\e[1;96mhttp://localhost:5689\\033[0m\"" + args "-ic" "echo -n -e \"Dev server available at \\e[1;96mhttp://localhost:$NGINX_PORT\\033[0m\"" } } \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f142c16..08e50ebd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,14 +24,6 @@ Run the 2 development servers with these commands et separate shells: npm run dev-server npm run dev-client -When both servers are ready, go to [http://localhost:5689](http://localhost:5689). - -Test the multi-site mode: - -curl -H "Content-Type: application/json" -XPOST "http://localhost:5689/simple-directory/api/sites?key=secret-sites" -d '{"_id":"devsite","owner":{"type":"organization","id":"admins-org","name":"Admins organization"},"host":"localhost:5989","theme":{"primaryColor":"#004D40"}}' -curl -H "Content-Type: application/json" -XPOST "http://localhost:5689/simple-directory/api/sites?key=secret-sites" -d ' -{"_id":"devsite2","owner":{"type":"organization","id":"admins-org","name":"Admins organization"},"host":"localhost:5999","theme":{"primaryColor":"#FF4D40"}}' - ## Docker image Test building the docker image: @@ -44,6 +36,10 @@ docker build --progress=plain -t sd-dev . docker run --network=host --env PORT=8081 sd-dev ``` +## Working with Git Worktrees + +This project supports git worktrees with fully isolated port allocations, allowing multiple branches to run concurrently (useful for AI agents or parallel development). Run `./dev/worktree.sh ` to create a new worktree with its own `.env`, Docker Compose project, and randomized port range. When setting up for the first time, not in a worktree, you can run `./dev/init-env.sh`. + ## Git quality checks This project uses [husky](https://typicode.github.io/husky/) and to ensure quality of commits. The pre-commit hook runs the docker image build, this way we get linting, testing, and building all checked in 1 step. diff --git a/api/config/default.cjs b/api/config/default.cjs index d22ccd6b..685eb021 100644 --- a/api/config/default.cjs +++ b/api/config/default.cjs @@ -271,18 +271,12 @@ module.exports = { locales: ['fr', 'en', 'es', 'pt', 'it', 'de'] }, mails: { - from: 'no-reply@test.com', + // from: 'no-reply@test.com' // transport is a full configuration object for createTransport of nodemailer // cf https://nodemailer.com/smtp/ - transport: { - port: 1025, - ignoreTLS: true, - host: 'localhost' - }, - extraParams: {} + transport: {} }, maildev: { - url: 'http://localhost:1080', active: false }, quotas: { diff --git a/api/config/development.cjs b/api/config/development.cjs index ba267599..27c43e66 100644 --- a/api/config/development.cjs +++ b/api/config/development.cjs @@ -1,9 +1,13 @@ +require('dotenv').config({ quiet: true }) + +if (!process.env.DEV_API_PORT) throw new Error('missing DEV_API_PORT env variable, use "source dev/init-env.sh" to init .env file') + module.exports = { mongo: { - url: 'mongodb://localhost:27017/simple-directory-' + (process.env.NODE_ENV || 'development') + url: 'mongodb://localhost:' + process.env.MONGO_PORT + '/simple-directory-' + (process.env.NODE_ENV || 'development') }, - port: 5690, - publicUrl: 'http://localhost:5689/simple-directory', + port: process.env.DEV_API_PORT, + publicUrl: 'http://localhost:' + process.env.NGINX_PORT1 + '/simple-directory', // use this host when debugging a data-fair inside a virtualbox vm // publicUrl: 'http://10.0.2.2:5689', admins: ['alban.mouton@koumoul.com', 'alban.mouton@gmail.com', 'superadmin@test.com', 'admin@test.com'], @@ -32,8 +36,18 @@ module.exports = { i18n: { // defaultLocale: 'en' }, + mails: { + from: 'no-reply@test.com', + transport: { + port: parseInt(process.env.MAILDEV_SMTP_PORT || '1025'), + ignoreTLS: true, + host: 'localhost' + }, + extraParams: {} + }, maildev: { - active: true + active: true, + url: 'http://localhost:' + (process.env.MAILDEV_UI_PORT), }, storage: { type: 'mongo', @@ -43,7 +57,7 @@ module.exports = { organizations: './dev/resources/organizations.json' }, ldap: { - url: 'ldap://localhost:389', + url: 'ldap://localhost:' + process.env.LDAP_PORT, searchUserDN: 'cn=admin,dc=example,dc=org', searchUserPassword: 'admin', cacheMS: 1000, @@ -104,8 +118,8 @@ module.exports = { sites: 'secret-sites' }, perOrgStorageTypes: ['ldap'], - cipherPassword: 'SiK7LwFcuYnktJuxcNaF/Q==', - privateEventsUrl: 'http://localhost:8088', + cipherPassword: 'test', + privateEventsUrl: 'http://localhost:' + process.env.EVENTS_PORT, plannedDeletionDelay: 1, cleanup: { cron: '*/1 * * * *', @@ -159,7 +173,7 @@ module.exports = { title: 'Test OIDC IDP', color: '#D92929', img: 'https://cdn-icons-png.flaticon.com/512/25/25231.png', - discovery: 'http://localhost:9009/.well-known/openid-configuration', + discovery: 'http://localhost:' + process.env.OIDC_PROVIDER_PORT + '/.well-known/openid-configuration', client: { id: 'foo', secret: 'bar' @@ -193,7 +207,7 @@ module.exports = { title: 'Test OIDC Keycloak', color: '#D92929', img: 'https://upload.wikimedia.org/wikipedia/commons/2/29/Keycloak_Logo.png', - discovery: 'http://localhost:8888/realms/master/.well-known/openid-configuration', + discovery: 'http://localhost:' + process.env.KEYCLOAK_PORT + '/realms/master/.well-known/openid-configuration', client: { id: 'test-sd', secret: 'x17YcfoJRPPrZoiXFTBwhWxSJGwTa5bi' @@ -224,5 +238,8 @@ module.exports = { }, siteOrgs: true, siteAdmin: true, - multiRoles: true + multiRoles: true, + observer: { + port: process.env.DEV_OBSERVER_PORT + } } diff --git a/api/config/test.cjs b/api/config/test.cjs index db25ebfc..068d767a 100644 --- a/api/config/test.cjs +++ b/api/config/test.cjs @@ -1,9 +1,13 @@ +require('dotenv').config({ quiet: true }) + +if (!process.env.DEV_API_PORT) throw new Error('missing DEV_API_PORT env variable, use "source dev/init-env.sh" to init .env file') + module.exports = { mongo: { - url: 'mongodb://localhost:27017/data-fair-' + (process.env.NODE_ENV || 'test') + url: 'mongodb://localhost:' + process.env.MONGO_PORT + '/simple-directory-' + (process.env.NODE_ENV || 'development') }, - port: 5690, - publicUrl: 'http://localhost:5689/simple-directory', + port: process.env.DEV_API_PORT, + publicUrl: 'http://localhost:' + process.env.NGINX_PORT1 + '/simple-directory', admins: ['admin@test.com'], adminCredentials: { email: '_superadmin@test.com', @@ -25,7 +29,7 @@ module.exports = { }, mongo: {}, ldap: { - url: 'ldap://localhost:389', + url: 'ldap://localhost:' + process.env.LDAP_PORT, cacheMS: 0, searchUserDN: 'cn=admin,dc=example,dc=org', searchUserPassword: 'admin', @@ -68,14 +72,25 @@ module.exports = { duration: 60 }, perOrgStorageTypes: ['ldap'], + mails: { + from: 'no-reply@test.com', + transport: { + port: parseInt(process.env.MAILDEV_SMTP_PORT || '1025'), + ignoreTLS: true, + host: 'localhost' + }, + extraParams: {} + }, maildev: { - active: true + active: true, + url: 'http://localhost:' + (process.env.MAILDEV_UI_PORT), }, manageSites: true, managePartners: true, cipherPassword: 'test', observer: { - active: false + active: false, + port: process.env.DEV_OBSERVER_PORT }, passwordValidation: { entropy: 40, diff --git a/api/src/storages/ldap.ts b/api/src/storages/ldap.ts index e12a523a..6561a726 100644 --- a/api/src/storages/ldap.ts +++ b/api/src/storages/ldap.ts @@ -125,6 +125,9 @@ export class LdapStorage implements SdStorage { private organizationCaptureRegex: RegExp | undefined constructor (params: LdapParams, org?: Organization) { + if (process.env.NODE_ENV === 'test') { + params.url = params.url.replace('{LDAP_PORT}', process.env.LDAP_PORT!) + } this.ldapParams = params this.org = org console.log('Connecting to ldap ' + params.url) diff --git a/dev/delete-worktree.sh b/dev/delete-worktree.sh new file mode 100755 index 00000000..33218f3b --- /dev/null +++ b/dev/delete-worktree.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +BRANCH_NAME=$1 + +if [ -z "$BRANCH_NAME" ]; then + echo "Error: Please provide a branch name." + echo "Usage: ./dev/delete-worktree.sh feat-xyz" + exit 1 +fi + +REPO_NAME=$(basename "$PWD") +TARGET_DIR="../${REPO_NAME}_${BRANCH_NAME}" + +if [ ! -d "$TARGET_DIR" ]; then + echo "Error: Worktree directory $TARGET_DIR does not exist." + exit 1 +fi + +echo "Stopping docker compose services in $TARGET_DIR" +cd "$TARGET_DIR" +docker compose --profile dev down + +echo "Removing git worktree at $TARGET_DIR" +cd "$PWD" +git worktree remove "$TARGET_DIR" + +echo "-----------------------------------------------" +echo "✅ Worktree $BRANCH_NAME deleted!" +echo "-----------------------------------------------" diff --git a/dev/init-env.sh b/dev/init-env.sh new file mode 100755 index 00000000..c1888c12 --- /dev/null +++ b/dev/init-env.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +RANDOM_NB=$((1024 + RANDOM % 48000)) +echo "Use random base port $RANDOM_NB" + +cat < ".env" +NGINX_PORT1=$((RANDOM_NB)) +NGINX_PORT2=$((RANDOM_NB + 1)) +NGINX_PORT3=$((RANDOM_NB + 2)) +NGINX_PORT4=$((RANDOM_NB + 3)) + +DEV_API_PORT=$((RANDOM_NB + 10)) +DEV_UI_PORT=$((RANDOM_NB + 11)) +DEV_OBSERVER_PORT=$((RANDOM_NB + 12)) +MAILDEV_UI_PORT=$((RANDOM_NB + 13)) +MAILDEV_SMTP_PORT=$((RANDOM_NB + 14)) + +MONGO_PORT=$((RANDOM_NB + 20)) +LDAP_PORT=$((RANDOM_NB + 21)) +LDAP_ADMIN_PORT=$((RANDOM_NB + 22)) +OIDC_PROVIDER_PORT=$((RANDOM_NB + 23)) +KEYCLOAK_PORT=$((RANDOM_NB + 24)) + +EVENTS_PORT=$((RANDOM_NB + 30)) +EOF \ No newline at end of file diff --git a/dev/resources/nginx.conf b/dev/resources/nginx.conf.template similarity index 68% rename from dev/resources/nginx.conf rename to dev/resources/nginx.conf.template index c0c05d10..2c89fe24 100644 --- a/dev/resources/nginx.conf +++ b/dev/resources/nginx.conf.template @@ -1,38 +1,7 @@ -# nginx configuration file for Data Fair development and test environment -user nginx; -worker_processes auto; - -error_log /var/log/nginx/error.log notice; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # use header origin if referer is empty - map $http_referer $reqref { - default $http_referer; - "" $http_origin; - } - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - map $http_upgrade $connection_upgrade { - default upgrade; - '' close; - } - - # first origin http://localhost:5689 + # first origin server { - listen 5689; + listen ${NGINX_PORT1}; server_name _; # Transmit host, protocol and user ip, we use it for routing, rate limiting, etc. @@ -52,31 +21,31 @@ http { } location /simple-directory/api { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location /simple-directory/.well-known { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location ~ /simple-directory/(.*)-ui-config.js { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location /simple-directory { - # port 6220 to use vite dev server - # port 5690 to use built application - proxy_pass http://localhost:6220; + # port ${DEV_UI_PORT} to use vite dev server + # port ${DEV_API_PORT} to use built application + proxy_pass http://localhost:${DEV_UI_PORT}; } location /mails { rewrite ^/mails/(.*) /$1 break; - proxy_pass http://localhost:1080/; + proxy_pass http://localhost:${MAILDEV_UI_PORT}/; } location /events { - proxy_pass http://localhost:8088; + proxy_pass http://localhost:${EVENTS_PORT}; } } # another one to simulate multi-site usage server { - listen 5989; + listen ${NGINX_PORT2}; server_name _; # Transmit host, protocol and user ip, we use it for routing, rate limiting, etc. @@ -96,26 +65,26 @@ http { } location /simple-directory/api { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location /simple-directory/.well-known { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location /simple-directory { - proxy_pass http://localhost:6220; + proxy_pass http://localhost:${DEV_UI_PORT}; } location /mails { rewrite ^/mails/(.*) /$1 break; - proxy_pass http://localhost:1080/; + proxy_pass http://localhost:${MAILDEV_UI_PORT}/; } location /events { - proxy_pass http://localhost:8088/; + proxy_pass http://localhost:${EVENTS_PORT}/; } } # another one to simulate multi-site usage server { - listen 5999; + listen ${NGINX_PORT3}; server_name _; # Transmit host, protocol and user ip, we use it for routing, rate limiting, etc. @@ -135,27 +104,27 @@ http { } location /simple-directory/api { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location /simple-directory/.well-known { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location /simple-directory { - proxy_pass http://localhost:6220; + proxy_pass http://localhost:${DEV_UI_PORT}; } location /mails { rewrite ^/mails/(.*) /$1 break; - proxy_pass http://localhost:1080/; + proxy_pass http://localhost:${MAILDEV_UI_PORT}/; } location /events { - proxy_pass http://localhost:8088/; + proxy_pass http://localhost:${EVENTS_PORT}/; } } # another one to simulate multi-site with a path prefix # to test this one edit devSitePath in vite.config.ts server { - listen 6099; + listen ${NGINX_PORT4}; server_name _; # Transmit host, protocol and user ip, we use it for routing, rate limiting, etc. @@ -178,20 +147,19 @@ http { } location /site-prefix/simple-directory/api { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location /site-prefix/simple-directory/.well-known { - proxy_pass http://localhost:5690; + proxy_pass http://localhost:${DEV_API_PORT}; } location /site-prefix/simple-directory { - proxy_pass http://localhost:6220; + proxy_pass http://localhost:${DEV_UI_PORT}; } location /site-prefix/mails { rewrite ^/mails/(.*) /$1 break; - proxy_pass http://localhost:1080/; + proxy_pass http://localhost:${MAILDEV_UI_PORT}/; } location /site-prefix/events { - proxy_pass http://localhost:8088/; + proxy_pass http://localhost:${EVENTS_PORT}/; } } -} \ No newline at end of file diff --git a/dev/resources/organizations.json b/dev/resources/organizations.json index 6a5c08b9..5151992c 100644 --- a/dev/resources/organizations.json +++ b/dev/resources/organizations.json @@ -100,7 +100,7 @@ "type": "ldap", "active": true, "config": { - "url": "ldap://localhost:389", + "url": "ldap://localhost:{LDAP_PORT}", "baseDN": "dc=example,dc=org", "searchUserDN": "cn=admin,dc=example,dc=org", "searchUserPassword": "admin", diff --git a/dev/worktree.sh b/dev/worktree.sh new file mode 100755 index 00000000..55182e83 --- /dev/null +++ b/dev/worktree.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +BRANCH_NAME=$1 + +if [ -z "$BRANCH_NAME" ]; then + echo "Error: Please provide a branch name." + echo "Usage: ./dev/worktree.sh feat-xyz" + exit 1 +fi + +SOURCE_BRANCH=$(git branch --show-current) +REPO_NAME=$(basename "$PWD") +TARGET_DIR="../${REPO_NAME}_${BRANCH_NAME}" + +echo "Creating worktree at $TARGET_DIR from branch $SOURCE_BRANCH" +git worktree add -b "$BRANCH_NAME" "$TARGET_DIR" $SOURCE_BRANCH + +cd $TARGET_DIR + +echo "Create .env file" +./dev/init-env.sh + +echo "npm ci" +npm ci + +echo "npm run build-types" +npm run build-types + +echo "npm -w embed-ui run build" +npm -w embed-ui run build + +echo "npm run test-deps" +npm run test-deps + +echo "-----------------------------------------------" +echo "✅ Setup Complete!" +echo "Location: $TARGET_DIR" +echo "Branch: $BRANCH_NAME" +echo "-----------------------------------------------" +echo "Next step:" +echo " cd $TARGET_DIR" +echo "" diff --git a/docker-compose.yml b/docker-compose.yml index 6a4688d7..5fb7cfca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,8 +9,17 @@ services: - dev - test network_mode: host + environment: + - NGINX_PORT1=${NGINX_PORT1} + - NGINX_PORT2=${NGINX_PORT2} + - NGINX_PORT3=${NGINX_PORT3} + - NGINX_PORT4=${NGINX_PORT4} + - DEV_API_PORT=${DEV_API_PORT} + - DEV_UI_PORT=${DEV_UI_PORT} + - MAILDEV_UI_PORT=${MAILDEV_UI_PORT} + - EVENTS_PORT=${EVENTS_PORT} volumes: - - ./dev/resources/nginx.conf:/etc/nginx/nginx.conf:ro + - ./dev/resources/nginx.conf.template:/etc/nginx/templates/default.conf.template:ro ##### # mail server mock @@ -22,8 +31,8 @@ services: - test image: maildev/maildev:2.1.0 ports: - - 1080:1080 - - 1025:1025 + - ${MAILDEV_UI_PORT}:1080 + - ${MAILDEV_SMTP_PORT}:1025 # https://github.com/maildev/maildev/issues/484 healthcheck: test: 'wget -O - http://127.0.0.1:$${MAILDEV_WEB_PORT}$${MAILDEV_BASE_PATHNAME}/healthz || exit 1' @@ -42,8 +51,7 @@ services: - ./dev/data/slapd.d:/etc/ldap/slapd.d - ./dev/resources:/test-resources ports: - - 389:389 - - 636:636 + - ${LDAP_PORT}:389 ldap-admin: profiles: - dev @@ -51,7 +59,7 @@ services: ports: # open over HTTPS https://localhost:6443/ # log with cn=admin,dc=example,dc=org / admin - - 6443:443 + - ${LDAP_ADMIN_PORT}:443 environment: - PHPLDAPADMIN_LDAP_HOSTS=ldap @@ -66,9 +74,9 @@ services: image: kristophjunge/test-saml-idp:1.15 network_mode: host environment: - - SIMPLESAMLPHP_SP_ENTITY_ID=http://localhost:5689/simple-directory/api/auth/saml2-metadata.xml - - SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=http://localhost:5689/simple-directory/api/auth/saml2-assert - - SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE=http://localhost:5689/simple-directory/api/auth/saml2-logout + - SIMPLESAMLPHP_SP_ENTITY_ID=http://localhost:${NGINX_PORT1}/simple-directory/api/auth/saml2-metadata.xml + - SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=http://localhost:${NGINX_PORT1}/simple-directory/api/auth/saml2-assert + - SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE=http://localhost:${NGINX_PORT1}/simple-directory/api/auth/saml2-logout # WARNING: does not work on a recent chrome, this provider tries to use a cookie with samesite=none option and this is not permitted without https # list of users : harley@qlik.example @@ -80,9 +88,9 @@ services: image: qlik/simple-oidc-provider:0.2.5 network_mode: host environment: - - REDIRECTS=http://localhost:5689/simple-directory/api/auth/oauth-callback - - PORT=9009 - - IDP_NAME=http://localhost:9009 + - REDIRECTS=http://localhost:${NGINX_PORT1}/simple-directory/api/auth/oauth-callback + - PORT=${OIDC_PROVIDER_PORT} + - IDP_NAME=http://localhost:${OIDC_PROVIDER_PORT} keycloak: profiles: @@ -96,7 +104,7 @@ services: network_mode: host volumes: - ./dev/data/keycloak:/opt/keycloak/data/ - command: ["start-dev", "--http-port=8888"] + command: ["start-dev", "--http-port=${KEYCLOAK_PORT}"] ##### # related services from the data-fair stack @@ -108,10 +116,10 @@ services: image: ghcr.io/data-fair/events:main network_mode: host environment: - - PORT=8088 - - PUBLIC_URL=http://localhost:5689/notify - - WS_PUBLIC_URL=ws://localhost:5689/notify - - PRIVATE_DIRECTORY_URL=http://localhost:5689/simple-directory + - PORT=${EVENTS_PORT} + - PUBLIC_URL=http://localhost:${NGINX_PORT1}/notify + - WS_PUBLIC_URL=ws://localhost:${NGINX_PORT1}/notify + - PRIVATE_DIRECTORY_URL=http://localhost:${NGINX_PORT1}/simple-directory - SECRET_EVENTS=secret-events - SECRET_IDENTITIES=secret-identities - SECRET_SENDMAILS=secret-sendmails @@ -127,7 +135,7 @@ services: - test image: mongo:4.2 ports: - - 27017:27017 + - ${MONGO_PORT}:27017 volumes: - mongo-data:/data/db diff --git a/package-lock.json b/package-lock.json index f0aa208e..ba6901c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,8 @@ "@types/tinycolor2": "^1.4.6", "@types/useragent": "^2.3.4", "commitlint": "^19.2.2", + "dotenv": "^17.3.1", + "dotenv-cli": "^11.0.0", "eslint": "^9.10.0", "eslint-plugin-vue": "^9.29.0", "eslint-plugin-vuetify": "^2.5.1", @@ -702,6 +704,7 @@ "resolved": "https://registry.npmjs.org/@data-fair/lib-node/-/lib-node-2.10.2.tgz", "integrity": "sha512-PAyVkLS0WgsfVPSU+UG6HPcrj00MQbVAVz51/Js9QuDF+FDpjL0gC9hH3EPDN+a+G/ldiG8G3y5tc7S6ckP3xw==", "license": "MIT", + "peer": true, "dependencies": { "@data-fair/lib-common-types": "^1.8.4", "@data-fair/lib-utils": "^1.1.0", @@ -773,20 +776,6 @@ } } }, - "node_modules/@data-fair/lib-types-builder/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@data-fair/lib-utils": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@data-fair/lib-utils/-/lib-utils-1.6.1.tgz", @@ -808,6 +797,7 @@ "resolved": "https://registry.npmjs.org/@data-fair/lib-vue/-/lib-vue-1.24.2.tgz", "integrity": "sha512-yHptHJy9mwK1BacwlrzYXdPRU+vLHOf7dbM7x3ZlrXEExPQFy8dKQYoBxnHuWFGT0Z/kZJqxWcfHRW1Hbr3r8Q==", "license": "MIT", + "peer": true, "dependencies": { "@data-fair/lib-common-types": "^1.7.1", "@data-fair/lib-utils": "^1.0.0", @@ -1798,6 +1788,7 @@ "resolved": "https://registry.npmjs.org/@json-layout/vocabulary/-/vocabulary-2.3.3.tgz", "integrity": "sha512-M2IqHnPcpKUAOrFSSH7Nv7yu9nkQll8R4kPcz5FRWE223rpg9KYV5cmTXzZ0HAglN949I48a2u8l4ua2PknKpQ==", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.17.1", "ajv-errors": "^3.0.0", @@ -1837,6 +1828,7 @@ "resolved": "https://registry.npmjs.org/@koumoul/vjsf-compiler/-/vjsf-compiler-1.2.3.tgz", "integrity": "sha512-fsN0+paFY3v0iZk/O4mPREKWInkAnrLBBfqgcoSxIgworq77t/J8AbCby3lFtwSChrEZ4lUBEgfJ0drxe5wN2w==", "license": "MIT", + "peer": true, "dependencies": { "@json-layout/core": "^2.0.0", "@json-layout/vocabulary": "^2.8.0", @@ -1868,6 +1860,7 @@ "resolved": "https://registry.npmjs.org/@json-layout/vocabulary/-/vocabulary-2.8.0.tgz", "integrity": "sha512-7jJ/719ksvlz+BKTBy+2HlQGxmfS2xmFCQvWlP4lOxvy8R+E/YAfR4n73W0qFl4B/F8f6YrBYIszhaVsPXhs4A==", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.17.1", "ajv-errors": "^3.0.0", @@ -2596,6 +2589,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz", "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -2809,6 +2803,7 @@ "version": "8.9.0", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.9.0", "@typescript-eslint/types": "8.9.0", @@ -3543,6 +3538,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3572,6 +3568,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -4456,6 +4453,7 @@ "version": "9.0.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -4632,7 +4630,8 @@ }, "node_modules/dayjs": { "version": "1.11.13", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/de-indent": { "version": "1.0.2", @@ -4729,8 +4728,7 @@ }, "node_modules/destr": { "version": "2.0.3", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/destroy": { "version": "1.2.0", @@ -4817,6 +4815,64 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-cli": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-11.0.0.tgz", + "integrity": "sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^17.1.0", + "dotenv-expand": "^12.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5239,6 +5295,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6482,6 +6539,7 @@ "version": "4.4.5", "hasInstallScript": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.0" }, @@ -8311,8 +8369,7 @@ }, "node_modules/node-fetch-native": { "version": "1.6.4", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", @@ -9568,6 +9625,7 @@ "node_modules/rollup": { "version": "4.24.0", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.6" }, @@ -10614,6 +10672,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10675,6 +10734,7 @@ "version": "5.0.0", "devOptional": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "tldts": "^6.1.32" }, @@ -10826,6 +10886,7 @@ "node_modules/typescript": { "version": "5.6.3", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11218,6 +11279,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -11298,6 +11360,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", @@ -11383,6 +11446,7 @@ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.8.tgz", "integrity": "sha512-mIjy4utxMz9lMMo6G9vYePv7gUFt4ztOMhY9/4czDJxZ26xPeJ49MAGa9wBAE3XuXbYCrtVPmPxNjej7JJJkZQ==", "license": "MIT", + "peer": true, "dependencies": { "@intlify/core-base": "10.0.8", "@intlify/shared": "10.0.8", @@ -11401,6 +11465,7 @@ "node_modules/vue-router": { "version": "4.4.5", "license": "MIT", + "peer": true, "dependencies": { "@vue/devtools-api": "^6.6.4" }, @@ -11431,6 +11496,7 @@ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.13.tgz", "integrity": "sha512-4+RuQU+zLtXhlN2eZUpKXums9ftzUzhMeiNEJvvJY4XdOzVwUCth2dTnEZkSF6EKdLHk3WhtRk0cIWXZxpBvcw==", "license": "MIT", + "peer": true, "engines": { "node": "^12.20 || >=14.13" }, diff --git a/package.json b/package.json index 3d1b1c9c..8ee2ed4f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Easy users and organizations management for modern Web-oriented architectures.", "type": "module", "scripts": { - "test-base": "NODE_ENV=test EVENTS_LOG_LEVEL=alert node --disable-warning=ExperimentalWarning --test-force-exit --test-concurrency=1 --test", + "test-base": "dotenv -v NODE_ENV=test -v EVENTS_LOG_LEVEL=alert -- node --disable-warning=ExperimentalWarning --test-force-exit --test-concurrency=1 --test", "test": "npm run test-base test-it/*.ts", "test-deps": "docker compose --profile test up -d --wait", "report": "nyc report --reporter=html", @@ -14,7 +14,7 @@ "dev-ui": "npm -w ui run dev", "dev-deps": "docker compose --profile dev up -d", "stop-dev-deps": "docker compose --profile dev stop", - "dev-zellij": "zellij --layout .zellij.kdl", + "dev-zellij": "dotenv -- zellij --layout .zellij.kdl", "build-types": "mkdir -p ui/src/components/vjsf && rm -f ui/src/components/vjsf/* && df-build-types . --vjsf-dir ui/src/components/vjsf", "prepare": "husky || true", "check-types": "tsc && npm -w ui run check-types", @@ -62,6 +62,8 @@ "@types/tinycolor2": "^1.4.6", "@types/useragent": "^2.3.4", "commitlint": "^19.2.2", + "dotenv": "^17.3.1", + "dotenv-cli": "^11.0.0", "eslint": "^9.10.0", "eslint-plugin-vue": "^9.29.0", "eslint-plugin-vuetify": "^2.5.1", diff --git a/test-it/invitations.ts b/test-it/invitations.ts index e3d9b69a..c72cf01d 100644 --- a/test-it/invitations.ts +++ b/test-it/invitations.ts @@ -1,6 +1,6 @@ import { strict as assert } from 'node:assert' import { it, describe, before, beforeEach, after } from 'node:test' -import { axios, axiosAuth, clean, startApiServer, stopApiServer, createUser, waitForMail } from './utils/index.ts' +import { axios, axiosAuth, clean, startApiServer, stopApiServer, createUser, waitForMail, directoryUrl } from './utils/index.ts' process.env.STORAGE_TYPE = 'mongo' @@ -284,7 +284,7 @@ describe('invitations', () => { ax.setOrg(org.id) await anonymousAx.post('/api/sites', - { _id: 'test', owner: { type: 'organization', id: org.id, name: org.name }, host: '127.0.0.1:5989', theme: { primaryColor: '#FF00FF' } }, + { _id: 'test', owner: { type: 'organization', id: org.id, name: org.name }, host: '127.0.0.1:' + process.env.NGINX_PORT2, theme: { primaryColor: '#FF00FF' } }, { params: { key: config.secretKeys.sites } }) await adminAx.patch('/api/sites/test', { authMode: 'ssoBackOffice' }) const mailPromise = waitForMail() @@ -293,10 +293,10 @@ describe('invitations', () => { name: org.name, email: 'test-invit11@test.com', role: 'user', - redirect: 'http://127.0.0.1:5989' + redirect: 'http://127.0.0.1:' + process.env.NGINX_PORT2 }) const mail = await mailPromise - assert.ok(mail.link.startsWith('http://127.0.0.1:5989/simple-directory/api/invitations/_accept')) + assert.ok(mail.link.startsWith(`http://127.0.0.1:${process.env.NGINX_PORT2}/simple-directory/api/invitations/_accept`)) // when clicking on the link the person is redirected to a page to create their user // the invitation token is forwarded to be re-sent with the user creation requests @@ -304,14 +304,14 @@ describe('invitations', () => { await assert.rejects(anonymousAx.get(mail.link), (res: any) => { assert.equal(res.status, 302) redirect = res.headers.location - assert.ok(redirect.startsWith('http://127.0.0.1:5989/simple-directory/login?step=createUser&invit_token=')) + assert.ok(redirect.startsWith(`http://127.0.0.1:${process.env.NGINX_PORT2}/simple-directory/login?step=createUser&invit_token=`)) return true }) const invitToken = new URL(mail.link).searchParams.get('invit_token') // finalize user creation and invitation - await anonymousAx.post('http://127.0.0.1:5989/simple-directory/api/users', { email: 'test-invit11@test.com', password: 'Test1234' }, { params: { invit_token: invitToken } }) + await anonymousAx.post(`http://127.0.0.1:${process.env.NGINX_PORT2}/simple-directory/api/users`, { email: 'test-invit11@test.com', password: 'Test1234' }, { params: { invit_token: invitToken } }) // after accepting the user is a member const members = (await ax.get(`/api/organizations/${org.id}/members`)).data.results @@ -319,7 +319,7 @@ describe('invitations', () => { const newMember = members.find(m => m.email === 'test-invit11@test.com') assert.ok(newMember) assert.equal(newMember.role, 'user') - assert.equal(newMember.host, '127.0.0.1:5989') + assert.equal(newMember.host, '127.0.0.1:' + process.env.NGINX_PORT2) }) it('should reject duplicate invitation', async () => { @@ -367,7 +367,7 @@ describe('invitations', () => { ax.setOrg(org.id) await anonymousAx.post('/api/sites', - { _id: 'test', owner: { type: 'organization', id: org.id, name: org.name }, host: '127.0.0.1:5989', theme: { primaryColor: '#FF00FF' } }, + { _id: 'test', owner: { type: 'organization', id: org.id, name: org.name }, host: '127.0.0.1:' + process.env.NGINX_PORT2, theme: { primaryColor: '#FF00FF' } }, { params: { key: config.secretKeys.sites } }); (await import('../api/src/sites/service.ts')).getSiteByHost.clear() await adminAx.patch('/api/sites/test', { authMode: 'onlyBackOffice' }) @@ -377,10 +377,10 @@ describe('invitations', () => { name: org.name, email: 'test-invit14@test.com', role: 'user', - redirect: 'http://127.0.0.1:5989' + redirect: 'http://127.0.0.1:' + process.env.NGINX_PORT2 }) const mail = await mailPromise - assert.ok(mail.link.startsWith('http://localhost:5689/simple-directory/api/invitations/_accept')) + assert.ok(mail.link.startsWith(directoryUrl + '/api/invitations/_accept')) // when clicking on the link the person is redirected to a page to create their user // the invitation token is forwarded to be re-sent with the user creation requests @@ -388,17 +388,17 @@ describe('invitations', () => { await assert.rejects(anonymousAx.get(mail.link), (res: any) => { assert.equal(res.status, 302) redirect = res.headers.location - assert.ok(redirect.startsWith('http://localhost:5689/simple-directory/login?step=createUser&invit_token=')) + assert.ok(redirect.startsWith(directoryUrl + '/login?step=createUser&invit_token=')) const redirectUrl = new URL(redirect) const reboundRedirect = redirectUrl.searchParams.get('redirect') - assert.ok(reboundRedirect?.startsWith('http://127.0.0.1:5989/')) + assert.ok(reboundRedirect?.startsWith('http://127.0.0.1:' + process.env.NGINX_PORT2)) return true }) const invitToken = new URL(mail.link).searchParams.get('invit_token') // finalize user creation and invitation - await anonymousAx.post('http://localhost:5689/simple-directory/api/users', { email: 'test-invit14@test.com', password: 'Test1234' }, { params: { invit_token: invitToken } }) + await anonymousAx.post('/api/users', { email: 'test-invit14@test.com', password: 'Test1234' }, { params: { invit_token: invitToken } }) // after accepting the user is a member const members = (await ax.get(`/api/organizations/${org.id}/members`)).data.results @@ -418,20 +418,20 @@ describe('invitations', () => { name: org2.name, email: 'test-invit14@test.com', role: 'contrib', - redirect: 'http://127.0.0.1:5989' + redirect: 'http://127.0.0.1:' + process.env.NGINX_PORT2 }) const mail2 = await mailPromise2 - assert.ok(mail2.link.startsWith('http://localhost:5689/simple-directory/api/invitations/_accept')) + assert.ok(mail2.link.startsWith(directoryUrl + '/api/invitations/_accept')) // when clicking on the link the person is redirected to a page to accept the invitation (not create the user as it already exists) let redirect2 await assert.rejects(anonymousAx.get(mail2.link), (res: any) => { assert.equal(res.status, 302) redirect2 = res.headers.location - assert.ok(redirect2.startsWith('http://localhost:5689/simple-directory/login?email=test-invit14')) + assert.ok(redirect2.startsWith(directoryUrl + '/login?email=test-invit14')) const redirectUrl = new URL(redirect2) const reboundRedirect = redirectUrl.searchParams.get('redirect') - assert.ok(reboundRedirect?.startsWith('http://127.0.0.1:5989/')) + assert.ok(reboundRedirect?.startsWith('http://127.0.0.1:' + process.env.NGINX_PORT2)) return true }) diff --git a/test-it/jwks.ts b/test-it/jwks.ts index aa5c31d3..35b275ab 100644 --- a/test-it/jwks.ts +++ b/test-it/jwks.ts @@ -1,6 +1,6 @@ import { strict as assert } from 'node:assert' import { it, describe, before, beforeEach, after } from 'node:test' -import { axios, axiosAuth, clean, startApiServer, stopApiServer } from './utils/index.ts' +import { axios, axiosAuth, clean, startApiServer, stopApiServer, devApiUrl } from './utils/index.ts' import jwt from 'jsonwebtoken' describe('JWKS router and keys management', () => { @@ -30,7 +30,7 @@ describe('JWKS router and keys management', () => { // force keys rotation (normally it is base on a delay) await keysManager.rotateKeys() await keysManager.getSignatureKeys.clear() - session.init('http://localhost:5690') + session.init(devApiUrl) res = await ax.get('/.well-known/jwks.json') assert.equal(res.data.keys?.length, 2) assert.equal(key1.kid, res.data.keys[1].kid) @@ -53,7 +53,7 @@ describe('JWKS router and keys management', () => { // force a second keys rotation await keysManager.rotateKeys() await keysManager.getSignatureKeys.clear() - session.init('http://localhost:5690') + session.init(devApiUrl) res = await ax.get('/.well-known/jwks.json') assert.equal(res.data.keys?.length, 2) assert.equal(key2.kid, res.data.keys[1].kid) diff --git a/test-it/ldap-per-org-mongo.ts b/test-it/ldap-per-org-mongo.ts index e59596a1..035ca546 100644 --- a/test-it/ldap-per-org-mongo.ts +++ b/test-it/ldap-per-org-mongo.ts @@ -9,7 +9,7 @@ const config = (await import('../api/src/config.ts')).default const ldapConfig = JSON.parse(JSON.stringify(config.storage.ldap)) const orgLdapConfig = { - url: 'ldap://localhost:389', + url: 'ldap://localhost:' + process.env.LDAP_PORT, baseDN: 'dc=example,dc=org', searchUserDN: 'cn=admin,dc=example,dc=org', searchUserPassword: 'admin', diff --git a/test-it/oidc-core-id.ts b/test-it/oidc-core-id.ts index caf6e5db..a70db57c 100644 --- a/test-it/oidc-core-id.ts +++ b/test-it/oidc-core-id.ts @@ -4,6 +4,8 @@ import { axios, clean, startApiServer, stopApiServer, createUser } from './utils import { OAuth2Server } from 'oauth2-mock-server' import { CookieJar } from 'tough-cookie' +const nginxPort = process.env.NGINX_PORT1 + process.env.STORAGE_TYPE = 'mongo' process.env.OAUTH_PROVIDERS = '["github"]' process.env.OIDC_PROVIDERS = JSON.stringify([{ @@ -65,16 +67,16 @@ describe('global OIDC configuration in coredIdProvider mode', () => { // redirect to the provider with proper params assert.equal(providerAuthUrl.host, 'localhost:8998') assert.equal(providerAuthUrl.pathname, '/authorize') - assert.equal(providerAuthUrl.searchParams.get('redirect_uri'), 'http://localhost:5689/simple-directory/api/auth/oauth-callback') + assert.equal(providerAuthUrl.searchParams.get('redirect_uri'), 'http://localhost:' + nginxPort + '/simple-directory/api/auth/oauth-callback') // successful login on the provider followed by redirect to our callback url const loginProvider = await anonymousAx(providerAuthUrl.href, { validateStatus: (status) => status === 302 }) const providerAuthRedirect = new URL(loginProvider.headers.location) - assert.equal(providerAuthRedirect.host, 'localhost:5689') + assert.equal(providerAuthRedirect.host, 'localhost:' + nginxPort) assert.equal(providerAuthRedirect.pathname, '/simple-directory/api/auth/oauth-callback') // open our callback url that produces a temporary token to be transformed in a session token by a token_callback url const oauthCallback = await anonymousAx(providerAuthRedirect.href, { validateStatus: (status) => status === 302 }) const callbackRedirect = new URL(oauthCallback.headers.location) - assert.equal(callbackRedirect.host, 'localhost:5689') + assert.equal(callbackRedirect.host, 'localhost:' + nginxPort) assert.equal(callbackRedirect.pathname, '/simple-directory/api/auth/token_callback') // finally the token_callback url will set cookies and redirect to our final destination const tokenCallback = await anonymousAx(callbackRedirect.href, { validateStatus: (status) => status === 302 }) diff --git a/test-it/sites.ts b/test-it/sites.ts index a07c82ce..1690e731 100644 --- a/test-it/sites.ts +++ b/test-it/sites.ts @@ -14,8 +14,8 @@ describe('sites api', () => { const { ax: adminAx } = await createUser('admin@test.com', true) const anonymousAx = await axios() - await assert.rejects(anonymousAx.post('/api/sites', { host: '127.0.0.1:5989' }), { status: 401 }) - await assert.rejects(anonymousAx.post('/api/sites', { host: '127.0.0.1:5989' }, { params: { key: config.secretKeys.sites } }), { status: 400 }) + await assert.rejects(anonymousAx.post('/api/sites', { host: '127.0.0.1:' + process.env.NGINX_PORT2 }), { status: 401 }) + await assert.rejects(anonymousAx.post('/api/sites', { host: '127.0.0.1:' + process.env.NGINX_PORT2 }, { params: { key: config.secretKeys.sites } }), { status: 400 }) const { ax } = await createUser('test-site@test.com') const org = (await ax.post('/api/organizations', { name: 'test' })).data @@ -23,13 +23,13 @@ describe('sites api', () => { const owner = { type: 'organization', id: org.id, name: org.name } await anonymousAx.post('/api/sites', - { _id: 'test', owner, host: '127.0.0.1:5989', theme: { primaryColor: '#FF00FF' } }, + { _id: 'test', owner, host: '127.0.0.1:' + process.env.NGINX_PORT2, theme: { primaryColor: '#FF00FF' } }, { params: { key: config.secretKeys.sites } }) await assert.rejects(anonymousAx.get('/api/sites'), { status: 401 }) // anonymous user can access the public info (host, theme and later auth providers) so that we can display custom login page - const siteDirectoryUrl = 'http://127.0.0.1:5989/simple-directory' + const siteDirectoryUrl = `http://127.0.0.1:${process.env.NGINX_PORT2}/simple-directory` const publicSite = (await anonymousAx.get(`${siteDirectoryUrl}/api/sites/_public`)).data assert.equal(publicSite.authMode, 'onlyBackOffice') assert.ok(publicSite.theme.colors.primary) diff --git a/test-it/users.ts b/test-it/users.ts index 51ce941a..bc0e3714 100644 --- a/test-it/users.ts +++ b/test-it/users.ts @@ -79,11 +79,11 @@ describe('users api', () => { const owner = { type: 'organization', id: org.id, name: org.name } await anonymousAx.post('/api/sites', - { _id: 'test', owner, host: '127.0.0.1:5989', theme: { primaryColor: '#FF00FF' } }, + { _id: 'test', owner, host: '127.0.0.1:' + process.env.NGINX_PORT2, theme: { primaryColor: '#FF00FF' } }, { params: { key: config.secretKeys.sites } }) const mailPromise = waitForMail() - await anonymousAx.post('http://127.0.0.1:5989/simple-directory/api/auth/action', { email: 'user3@test.com', action: 'changePassword', target: config.publicUrl + '/login' }) + await anonymousAx.post(`http://127.0.0.1:${process.env.NGINX_PORT2}/simple-directory/api/auth/action`, { email: 'user3@test.com', action: 'changePassword', target: config.publicUrl + '/login' }) const mail = await mailPromise assert.ok(mail.link.includes('action_token')) const actionToken = (new URL(mail.link)).searchParams.get('action_token') @@ -95,7 +95,7 @@ describe('users api', () => { await ax.post( `/api/users/${user.id}/host`, - { host: '127.0.0.1:5989' }, + { host: '127.0.0.1:' + process.env.NGINX_PORT2 }, { params: { action_token: actionToken } } ) }) diff --git a/test-it/utils/index.ts b/test-it/utils/index.ts index c6dc5e0c..b2aa69db 100644 --- a/test-it/utils/index.ts +++ b/test-it/utils/index.ts @@ -5,7 +5,9 @@ import { axiosAuth as _axiosAuth } from '@data-fair/lib-node/axios-auth.js' import eventPromise from '@data-fair/lib-utils/event-promise.js' import { CookieJar } from 'tough-cookie' -const directoryUrl = 'http://localhost:5689/simple-directory' +export const directoryUrl = `http://localhost:${process.env.NGINX_PORT1}/simple-directory` + +export const devApiUrl = `http://localhost:${process.env.DEV_API_PORT}` const axiosOpts = { baseURL: directoryUrl } @@ -89,14 +91,14 @@ export const waitForMail = async () => { export const getAllEmails = async () => { const ax = await axios() - return (await ax.get('http://localhost:1080/email')).data + return (await ax.get(`http://localhost:${process.env.MAILDEV_UI_PORT}/email`)).data } export const deleteAllEmails = async () => { const ax = await axios() - const emails = (await ax.get('http://localhost:1080/email')).data + const emails = (await ax.get(`http://localhost:${process.env.MAILDEV_UI_PORT}/email`)).data for (const email of emails) { - await ax.delete('http://localhost:1080/email/' + email.id) + await ax.delete(`http://localhost:${process.env.MAILDEV_UI_PORT}/email/` + email.id) } } @@ -114,6 +116,7 @@ export const passwordLogin = async (ax: AxiosAuthInstance, email: string, passwo export const loginWithOIDC = async (port: number) => { const anonymousAx = await axios() + const nginxPort = process.env.NGINX_PORT1 // request a login from the provider const loginInitial = await anonymousAx.get(`/api/auth/oauth/localhost${port}/login`, { validateStatus: (status) => status === 302 }) @@ -121,16 +124,16 @@ export const loginWithOIDC = async (port: number) => { // redirect to the provider with proper params assert.equal(providerAuthUrl.host, 'localhost:' + port) assert.equal(providerAuthUrl.pathname, '/authorize') - assert.equal(providerAuthUrl.searchParams.get('redirect_uri'), 'http://localhost:5689/simple-directory/api/auth/oauth-callback') + assert.equal(providerAuthUrl.searchParams.get('redirect_uri'), 'http://localhost:' + nginxPort + '/simple-directory/api/auth/oauth-callback') // successful login on the provider followed by redirect to our callback url const loginProvider = await anonymousAx(providerAuthUrl.href, { validateStatus: (status) => status === 302 }) const providerAuthRedirect = new URL(loginProvider.headers.location) - assert.equal(providerAuthRedirect.host, 'localhost:5689') + assert.equal(providerAuthRedirect.host, 'localhost:' + nginxPort) assert.equal(providerAuthRedirect.pathname, '/simple-directory/api/auth/oauth-callback') // open our callback url that produces a temporary token to be transformed in a session token by a token_callback url const oauthCallback = await anonymousAx(providerAuthRedirect.href, { validateStatus: (status) => status === 302 }) const callbackRedirect = new URL(oauthCallback.headers.location) - assert.equal(callbackRedirect.host, 'localhost:5689') + assert.equal(callbackRedirect.host, 'localhost:' + nginxPort) assert.equal(callbackRedirect.pathname, '/simple-directory/api/auth/token_callback') // finally the token_callback url will set cookies and redirect to our final destination const tokenCallback = await anonymousAx(callbackRedirect.href, { validateStatus: (status) => status === 302 }) diff --git a/ui/package.json b/ui/package.json index 1160e958..bd4dc691 100644 --- a/ui/package.json +++ b/ui/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "NODE_CONFIG_DIR=../api/config/ vite --port 6220", + "dev": "NODE_CONFIG_DIR=../api/config/ vite --port $DEV_UI_PORT", "build": "vue-tsc -b && vite build", "prepare-webfont-css": "", "preview": "vite preview", diff --git a/ui/src/components/organization-storage.vue b/ui/src/components/organization-storage.vue index f263ba29..89d53e4c 100644 --- a/ui/src/components/organization-storage.vue +++ b/ui/src/components/organization-storage.vue @@ -59,7 +59,7 @@ const orgStorage = ref(orga.orgStorage type: 'ldap', readonly: true, config: { - url: 'ldap://ldap:389', + url: 'ldap://ldap:' + process.env.LDAP_STORAGE, searchUserDN: 'cn=admin,dc=example,dc=org', searchUserPassword: '', baseDN: 'dc=example,dc=org', diff --git a/ui/src/pages/admin/sites/index.vue b/ui/src/pages/admin/sites/index.vue index 1c007e2a..8400c389 100644 --- a/ui/src/pages/admin/sites/index.vue +++ b/ui/src/pages/admin/sites/index.vue @@ -166,7 +166,6 @@ const headers: { title: string, value?: string, sortable?: boolean }[] = [ const siteRedirect = (site: Site) => { $fetch('auth/site_redirect', { method: 'POST', body: { redirect: `${window.location.protocol}//${site.host}` } }) .then((res) => { - console.log('RES', res) window.location.replace(res) }) }