diff --git a/.env-sample b/.env-sample new file mode 100644 index 0000000..57de507 --- /dev/null +++ b/.env-sample @@ -0,0 +1,60 @@ +# .env + +NODE_ENV=development +PORT=3000 + +# === CONFIGURATION DES TESTS STARTUP === +RUN_STARTUP_TESTS=false +TEST_CLEANUP_DELAY_MS=100 +TEST_TIMEOUT_MS=30000 +TEST_USER_PASSWORD=SecureTempPass123! +TEST_EMAIL_DOMAIN=dcb-test.com +TEST_DEFAULT_PASSWORD=SecureTempPass123! + +# === CONFIGURATION DE SÉCURITÉ === +RUN_SECURITY_TESTS=false +SECURITY_TEST_TIMEOUT=300000 + +# === VALIDATION DES ENTREES === +MAX_USERNAME_LENGTH=50 +MIN_USERNAME_LENGTH=3 +ALLOWED_EMAIL_DOMAINS=dcb-test.com,pixpay.sn + +# === RATE LIMITING === +MAX_REQUESTS_PER_MINUTE=60 +RATE_LIMIT_BLOCK_DURATION=300000 + +# === SÉCURITÉ DES SESSIONS === +SESSION_TIMEOUT=900000 +JWT_EXPIRATION=3600000 + +# === SURVEILLANCE === +LOG_SECURITY_EVENTS=true +SECURITY_EVENT_RETENTION_DAYS=30 + +# === CONFIGURATION KEYCLOAK === + +KEYCLOAK_SERVER_URL=https://iam.dcb.pixpay.sn +KEYCLOAK_REALM=dcb-prod + +KEYCLOAK_JWKS_URI=https://iam.dcb.pixpay.sn/realms/dcb-prod/protocol/openid-connect/certs +KEYCLOAK_ISSUER=https://iam.dcb.pixpay.sn/realms/dcb-prod + +KEYCLOAK_PUBLIC_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA01nspe5Sol9YAzm98wnQO1MvhRgJZSaOhozOHJEBm5VW5wLEEfcTlakzr/xXRjFYB9jySeaDWyhE6qGKuRK2Kx20qt3CuwT52ZSy97dKjJbgCxBCOymxKLJRdDfwtKOAayk5oCHqGp+cJTShnd9jVggYyTdqGqMWlpeiBKqvpgyldndwIfvDxPpPwsx/mwKV7S4sSTsONxSIB6zK+RumeYKOF0BskIxBw4tG3V5eicrECCKX/jP8rYFclBPXhxnLbbaHa21XAwQHfOioip3YfwPYF9GKTJEhM8ziJdTKikAtiwFm/Zvn1foLaF1MDLpV9yLrK0H1oa3y7j5p7tqHbQIDAQAB + +KEYCLOAK_CLIENT_ID=dcb-user-service-cc-app +KEYCLOAK_CLIENT_SECRET=IFNQWjBbcW6dXqQO76X5OZb1lL0esO30 + +KEYCLOAK_VALIDATION_MODE=offline + +KEYCLOAK_TOKEN_BUFFER_SECONDS=30 + +KEYCLOAK_TEST_USER_ADMIN=bo-admin +KEYCLOAK_TEST_PASSWORD_ADMIN=@BOAdmin2025 + +KEYCLOAK_TEST_USER_MERCHANT=bo-partner +KEYCLOAK_TEST_PASSWORD_MERCHANT=@BOPartner2025 + +KEYCLOAK_TEST_USER_SUPPORT=bo-support +KEYCLOAK_TEST_PASSWORD=@BOSupport2025 + diff --git a/package-lock.json b/package-lock.json index 9f44eed..ed0fba7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -233,6 +233,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -733,7 +734,6 @@ "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.1.0.tgz", "integrity": "sha512-ZdxfOiaarMqMj+H7qwlt5EBKWaeGihSYVHdQv5lUsbn8MJJOTW82OIwirQ39U5tMZkNvy3bQE+ryzC+xTAb9/g==", "license": "MIT", - "peer": true, "dependencies": { "keyv": "^5.5.3" } @@ -1370,8 +1370,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@isaacs/balanced-match": { "version": "4.0.1", @@ -2081,8 +2080,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@lukeed/csprng": { "version": "1.1.0", @@ -2110,8 +2108,7 @@ "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { "version": "3.0.3", @@ -2124,8 +2121,7 @@ "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { "version": "3.0.3", @@ -2138,8 +2134,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { "version": "3.0.3", @@ -2152,8 +2147,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { "version": "3.0.3", @@ -2166,8 +2160,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { "version": "3.0.3", @@ -2180,8 +2173,7 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", @@ -2300,6 +2292,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -2484,6 +2477,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.7.tgz", "integrity": "sha512-lwlObwGgIlpXSXYOTpfzdCepUyWomz6bv9qzGzzvpgspUxkj0Uz0fUJcvD44V8Ps7QhKW3lZBoYbXrH25UZrbA==", "license": "MIT", + "peer": true, "dependencies": { "file-type": "21.0.0", "iterare": "1.2.1", @@ -2543,6 +2537,7 @@ "integrity": "sha512-TyXFOwjhHv/goSgJ8i20K78jwTM0iSpk9GBcC2h3mf4MxNy+znI8m7nWjfoACjTkb89cTwDQetfTHtSfGLLaiA==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -2639,6 +2634,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.7.tgz", "integrity": "sha512-5T+GLdvTiGPKB4/P4PM9ftKUKNHJy8ThEFhZA3vQnXVL7Vf0rDr07TfVTySVu+XTh85m1lpFVuyFM6u6wLNsRA==", "license": "MIT", + "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.1.0", @@ -3027,6 +3023,7 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", "license": "MIT", + "peer": true, "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -3261,6 +3258,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -3402,6 +3400,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz", "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3553,6 +3552,7 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -4235,6 +4235,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4284,6 +4285,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4729,6 +4731,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -5004,6 +5007,7 @@ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -5061,13 +5065,15 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/class-validator": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", "license": "MIT", + "peer": true, "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.11.1", @@ -5449,7 +5455,6 @@ "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", "license": "MIT", - "peer": true, "dependencies": { "luxon": "^3.2.1" }, @@ -5565,7 +5570,6 @@ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=0.10" } @@ -5592,7 +5596,6 @@ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "optional": true, - "peer": true, "engines": { "node": ">=8" } @@ -5859,6 +5862,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5919,6 +5923,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6468,7 +6473,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=4.0" }, @@ -6717,7 +6721,6 @@ "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -7107,7 +7110,6 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", "license": "MIT", - "peer": true, "dependencies": { "@ioredis/commands": "1.4.0", "cluster-key-slot": "^1.1.0", @@ -7347,6 +7349,7 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -8368,8 +8371,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -8381,8 +8383,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", @@ -8709,7 +8710,6 @@ "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", "license": "MIT", - "peer": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" } @@ -8721,7 +8721,6 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, @@ -8897,7 +8896,6 @@ "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "detect-libc": "^2.0.1" }, @@ -9318,8 +9316,7 @@ "node_modules/pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==", - "peer": true + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, "node_modules/perfect-debounce": { "version": "1.0.0", @@ -9465,6 +9462,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9523,6 +9521,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "6.17.1", "@prisma/engines": "6.17.1" @@ -9559,8 +9558,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", @@ -9727,7 +9725,6 @@ "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -9737,7 +9734,6 @@ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", "license": "MIT", - "peer": true, "dependencies": { "redis-errors": "^1.0.0" }, @@ -9881,6 +9877,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -10178,8 +10175,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", @@ -10567,6 +10563,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -10881,6 +10878,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11028,6 +11026,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11210,7 +11209,6 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4.0" } @@ -11220,7 +11218,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "license": "MIT", - "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -11393,7 +11390,6 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -11412,7 +11408,6 @@ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -11426,7 +11421,6 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -11441,7 +11435,6 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -11451,8 +11444,7 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/webpack/node_modules/mime-db": { "version": "1.52.0", @@ -11460,7 +11452,6 @@ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -11471,7 +11462,6 @@ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -11485,7 +11475,6 @@ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", diff --git a/src/merchant/interfaces/ user.service.interface.ts b/src/merchant/interfaces/user.service.interface.ts similarity index 100% rename from src/merchant/interfaces/ user.service.interface.ts rename to src/merchant/interfaces/user.service.interface.ts diff --git a/src/merchant/services/merchant.service.ts b/src/merchant/services/merchant.service.ts index 7d7def5..a3384a1 100644 --- a/src/merchant/services/merchant.service.ts +++ b/src/merchant/services/merchant.service.ts @@ -1,7 +1,7 @@ import { Injectable, NotFoundException, BadRequestException, ConflictException, Inject } from '@nestjs/common'; import { MerchantPartnerWithRelations, MerchantUserWithInfo } from '../entities/merchant.entity'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import type { UserServiceClient } from '../interfaces/ user.service.interface'; +import type { UserServiceClient } from '../interfaces/user.service.interface'; import { CreateMerchantPartnerDto } from '../dto/create.merchant.dto'; import { UpdateMerchantPartnerDto } from '../dto/ update.merchant.dto'; import { AddUserToMerchantDto, UpdateUserRoleDto } from '../dto/merchant.user.dto'; diff --git a/src/merchant/services/user.service.client.ts b/src/merchant/services/user.service.client.ts index 6f1ede1..0daa651 100644 --- a/src/merchant/services/user.service.client.ts +++ b/src/merchant/services/user.service.client.ts @@ -2,29 +2,99 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { firstValueFrom } from 'rxjs'; -import { UserInfo, UserServiceClient } from '../interfaces/ user.service.interface'; - +import { UserInfo, UserServiceClient } from '../interfaces/user.service.interface'; + @Injectable() export class HttpUserServiceClient implements UserServiceClient { private readonly baseUrl: string; + private readonly keycloakUrl: string; + private readonly keycloakRealm: string; + private readonly clientId: string; + private readonly clientSecret: string; + + private accessToken: string | null = null; + private tokenExpiry: number = 0; constructor( private readonly httpService: HttpService, private readonly configService: ConfigService, ) { - this.baseUrl = this.configService.get('USER_SERVICE_URL') || 'http://localhost:3001'; + this.baseUrl = this.configService.get('USER_SERVICE') || 'http://localhost:3001'; + + const keycloakUrl = this.configService.get('KEYCLOAK_SERVER_URL'); + const keycloakRealm = this.configService.get('KEYCLOAK_REALM'); + const clientId = this.configService.get('KEYCLOAK_CLIENT_ID'); + const clientSecret = this.configService.get('KEYCLOAK_CLIENT_SECRET'); + + if (!keycloakUrl || !keycloakRealm || !clientId || !clientSecret) { + throw new Error('Missing required Keycloak configuration'); + } + + this.keycloakUrl = keycloakUrl; + this.keycloakRealm = keycloakRealm; + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + private async getAccessToken(): Promise { + // Vérifier si le token est encore valide (avec une marge de 30 secondes) + if (this.accessToken !== null && Date.now() < this.tokenExpiry - 30000) { + return this.accessToken; + } + + try { + const tokenUrl = `${this.keycloakUrl}/realms/${this.keycloakRealm}/protocol/openid-connect/token`; + + const params = new URLSearchParams(); + params.append('grant_type', 'client_credentials'); + params.append('client_id', this.clientId); + params.append('client_secret', this.clientSecret); + + const response = await firstValueFrom( + this.httpService.post(tokenUrl, params.toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }), + ); + + this.accessToken = response.data.access_token; + // Calculer l'expiration du token (expires_in est en secondes) + this.tokenExpiry = Date.now() + (response.data.expires_in * 1000); + + return this.accessToken || ''; + } catch (error) { + throw new HttpException( + 'Failed to authenticate with Keycloak', + HttpStatus.UNAUTHORIZED, + ); + } + } + + private async getAuthHeaders(): Promise> { + const token = await this.getAccessToken(); + return { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }; } async verifyUserExists(userId: string): Promise { try { + const headers = await this.getAuthHeaders(); const response = await firstValueFrom( - this.httpService.get(`${this.baseUrl}/users/${userId}/exists`), + this.httpService.get(`${this.baseUrl}/users/${userId}/exists`, { headers }), ); return response.data.exists === true; } catch (error) { if (error.response?.status === 404) { return false; } + if (error.response?.status === 401) { + // Token invalide, réessayer une fois après rafraîchissement + this.accessToken = null; + return this.verifyUserExists(userId); + } throw new HttpException( 'Failed to verify user existence', HttpStatus.SERVICE_UNAVAILABLE, @@ -34,14 +104,19 @@ export class HttpUserServiceClient implements UserServiceClient { async getUserInfo(userId: string): Promise { try { + const headers = await this.getAuthHeaders(); const response = await firstValueFrom( - this.httpService.get(`${this.baseUrl}/users/${userId}`), + this.httpService.get(`${this.baseUrl}/users/${userId}`, { headers }), ); return this.mapToUserInfo(response.data); } catch (error) { if (error.response?.status === 404) { throw new HttpException(`User ${userId} not found`, HttpStatus.NOT_FOUND); } + if (error.response?.status === 401) { + this.accessToken = null; + return this.getUserInfo(userId); + } throw new HttpException( 'Failed to get user information', HttpStatus.SERVICE_UNAVAILABLE, @@ -55,11 +130,16 @@ export class HttpUserServiceClient implements UserServiceClient { } try { + const headers = await this.getAuthHeaders(); const response = await firstValueFrom( - this.httpService.post(`${this.baseUrl}/users/batch`, { userIds }), + this.httpService.post(`${this.baseUrl}/users/batch`, { userIds }, { headers }), ); return response.data.map(user => this.mapToUserInfo(user)); } catch (error) { + if (error.response?.status === 401) { + this.accessToken = null; + return this.getUsersInfo(userIds); + } throw new HttpException( 'Failed to get users information', HttpStatus.SERVICE_UNAVAILABLE,