diff --git a/src/main/frontend/angular.json b/src/main/frontend/angular.json index f82a595..c2cb00f 100644 --- a/src/main/frontend/angular.json +++ b/src/main/frontend/angular.json @@ -25,7 +25,8 @@ "aot": true, "assets": [ "src/favicon.ico", - "src/assets" + "src/assets", + { "glob": "mdi.svg", "input": "./node_modules/@mdi/angular-material", "output": "./assets"} ], "styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", diff --git a/src/main/frontend/package-lock.json b/src/main/frontend/package-lock.json index 7f01a74..dac2081 100644 --- a/src/main/frontend/package-lock.json +++ b/src/main/frontend/package-lock.json @@ -94,6 +94,115 @@ "worker-plugin": "3.2.0" }, "dependencies": { + "copy-webpack-plugin": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", + "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", + "dev": true, + "requires": { + "cacache": "^12.0.3", + "find-cache-dir": "^2.1.0", + "glob-parent": "^3.1.0", + "globby": "^7.1.1", + "is-glob": "^4.0.1", + "loader-utils": "^1.2.3", + "minimatch": "^3.0.4", + "normalize-path": "^3.0.0", + "p-limit": "^2.2.1", + "schema-utils": "^1.0.0", + "serialize-javascript": "^2.1.2", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "rxjs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", @@ -102,6 +211,21 @@ "requires": { "tslib": "^1.9.0" } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } } } }, @@ -1282,6 +1406,11 @@ "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "dev": true }, + "@mdi/angular-material": { + "version": "5.7.55", + "resolved": "https://registry.npmjs.org/@mdi/angular-material/-/angular-material-5.7.55.tgz", + "integrity": "sha512-ZcEJbVmDWy9/psdXW7LyvF8oxdT1lf2NgtreIrL7D+OkTkwY3IE52RXREDLT+8EGl30tbKFgfsIbHjleblBf4Q==" + }, "@ngtools/webpack": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.0.7.tgz", @@ -1305,6 +1434,44 @@ } } }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "requires": { + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, "@schematics/angular": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.0.7.tgz", @@ -1397,8 +1564,7 @@ "@types/json-schema": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", - "dev": true + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" }, "@types/minimatch": { "version": "3.0.3", @@ -1703,7 +1869,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -1730,8 +1895,7 @@ "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" }, "alphanum-sort": { "version": "1.0.2", @@ -2080,8 +2244,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -2183,8 +2346,7 @@ "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, "binary-extensions": { "version": "2.1.0", @@ -2295,7 +2457,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2305,7 +2466,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -2733,8 +2893,7 @@ "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, "cli-cursor": { "version": "3.1.0", @@ -2919,8 +3078,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, "compare-versions": { "version": "3.6.0", @@ -2990,8 +3148,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -3120,75 +3277,248 @@ "dev": true }, "copy-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", - "dev": true, + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.2.1.tgz", + "integrity": "sha512-VH2ZTMIBsx4p++Lmpg77adZ0KUyM5gFR/9cuTrbneNnJlcQXUFvsNariPqq2dq2kV3F2skHiDGPQCyKWy1+U0Q==", "requires": { - "cacache": "^12.0.3", - "find-cache-dir": "^2.1.0", - "glob-parent": "^3.1.0", - "globby": "^7.1.1", - "is-glob": "^4.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", + "cacache": "^15.0.5", + "fast-glob": "^3.2.4", + "find-cache-dir": "^3.3.1", + "glob-parent": "^5.1.1", + "globby": "^11.0.1", + "loader-utils": "^2.0.0", "normalize-path": "^3.0.0", - "p-limit": "^2.2.1", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "webpack-log": "^2.0.0" + "p-limit": "^3.0.2", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "webpack-sources": "^1.4.3" }, "dependencies": { - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "requires": { "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" } }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { "glob": "^7.1.3" } }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "requires": { - "figgy-pudding": "^3.5.1" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "ssri": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "requires": { + "minipass": "^3.1.1" + } + }, + "tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" } } } @@ -3881,12 +4211,11 @@ } }, "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "requires": { - "path-type": "^3.0.0" + "path-type": "^4.0.0" } }, "dns-equal": { @@ -4633,11 +4962,42 @@ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fastparse": { "version": "1.1.2", @@ -4645,6 +5005,14 @@ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", "dev": true }, + "fastq": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "requires": { + "reusify": "^1.0.4" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -4724,7 +5092,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -4926,7 +5293,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "requires": { "minipass": "^3.0.0" } @@ -4946,8 +5312,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.3", @@ -5002,7 +5367,6 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5040,24 +5404,22 @@ "dev": true }, "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" } } }, @@ -5491,10 +5853,9 @@ "dev": true }, "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "ignore-walk": { "version": "3.0.3", @@ -5559,14 +5920,12 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" }, "indexes-of": { "version": "1.0.1", @@ -5583,14 +5942,12 @@ "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -5599,8 +5956,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -5864,8 +6220,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -5877,7 +6232,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -5897,8 +6251,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-obj": { "version": "2.0.0", @@ -6298,8 +6651,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stringify-safe": { "version": "5.0.1", @@ -6842,6 +7194,11 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -7040,7 +7397,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7048,14 +7404,12 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -7064,7 +7418,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, "requires": { "minipass": "^3.0.0" } @@ -7073,7 +7426,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, "requires": { "minipass": "^3.0.0" } @@ -7082,7 +7434,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, "requires": { "minipass": "^3.0.0" } @@ -7246,6 +7597,21 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "ngx-cookie": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ngx-cookie/-/ngx-cookie-5.0.0.tgz", + "integrity": "sha512-wzHC3u9n8H6O2YNfoptNM78re/wnRs1guo8Qg1yThtH64eL/E34JPuzAa/g085beIGhsXMR1YDJGVLnMbluo2A==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -7343,8 +7709,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -7672,7 +8037,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -7867,8 +8231,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "pacote": { "version": "9.5.8", @@ -8069,8 +8432,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -8097,21 +8459,9 @@ "dev": true }, "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "pbkdf2": { "version": "3.1.1", @@ -8135,8 +8485,7 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pify": { "version": "4.0.1", @@ -8806,8 +9155,7 @@ "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "promise-retry": { "version": "1.1.1", @@ -9092,8 +9440,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "q": { "version": "1.5.1", @@ -9145,7 +9492,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -9533,6 +9879,11 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rfdc": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", @@ -9587,6 +9938,11 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -9607,8 +9963,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -9754,8 +10109,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "semver-dsl": { "version": "1.0.1", @@ -10016,10 +10370,9 @@ } }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "smart-buffer": { "version": "4.1.0", @@ -10364,8 +10717,7 @@ "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" }, "source-map": { "version": "0.7.3", @@ -11235,7 +11587,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -11435,7 +11786,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, "requires": { "unique-slug": "^2.0.0" } @@ -11444,7 +11794,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, "requires": { "imurmurhash": "^0.1.4" } @@ -11528,7 +11877,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -12329,7 +12677,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" @@ -12338,8 +12685,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -12449,8 +12795,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "6.2.1", @@ -12498,8 +12843,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "12.0.5", diff --git a/src/main/frontend/package.json b/src/main/frontend/package.json index 98f5917..25e03c9 100644 --- a/src/main/frontend/package.json +++ b/src/main/frontend/package.json @@ -21,7 +21,10 @@ "@angular/platform-browser": "~9.0.5", "@angular/platform-browser-dynamic": "~9.0.5", "@angular/router": "~9.0.5", + "@mdi/angular-material": "^5.7.55", "bootstrap": "^4.5.2", + "copy-webpack-plugin": "^6.2.1", + "ngx-cookie": "^5.0.0", "rxjs": "~6.5.4", "tslib": "^1.10.0", "zone.js": "~0.10.2" diff --git a/src/main/frontend/proxy.conf.json b/src/main/frontend/proxy.conf.json index 5312a5c..8dc3e85 100644 --- a/src/main/frontend/proxy.conf.json +++ b/src/main/frontend/proxy.conf.json @@ -1,6 +1,6 @@ { "/api": { - "target": "http://localhost:9090", + "target": "http://localhost:9090/api", "secure": false } } diff --git a/src/main/frontend/src/app/app-routing.module.ts b/src/main/frontend/src/app/app-routing.module.ts index 906472b..5a09a55 100644 --- a/src/main/frontend/src/app/app-routing.module.ts +++ b/src/main/frontend/src/app/app-routing.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -const routes: Routes = [{ path: 'color', loadChildren: () => import('./modules/colors/colors.module').then(m => m.ColorsModule) }]; +const routes: Routes = [{ path: 'color', loadChildren: () => import('./modules/colors/colors.module').then(m => m.ColorsModule) }, { path: 'account', loadChildren: () => import('./modules/accounts/accounts.module').then(m => m.AccountsModule) }]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/src/main/frontend/src/app/app.module.ts b/src/main/frontend/src/app/app.module.ts index 56ec3f9..1a679dc 100644 --- a/src/main/frontend/src/app/app.module.ts +++ b/src/main/frontend/src/app/app.module.ts @@ -1,10 +1,13 @@ -import { BrowserModule } from '@angular/platform-browser'; +import {BrowserModule, DomSanitizer} from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { SharedModule } from './modules/shared/shared.module'; +import {HttpClientModule} from "@angular/common/http"; +import {CookieModule} from "ngx-cookie"; +import {MatIconRegistry} from "@angular/material/icon"; @NgModule({ declarations: [ @@ -14,9 +17,17 @@ import { SharedModule } from './modules/shared/shared.module'; BrowserModule, AppRoutingModule, BrowserAnimationsModule, - SharedModule + SharedModule, + HttpClientModule, + CookieModule.forRoot() ], providers: [], bootstrap: [AppComponent] }) -export class AppModule { } +export class AppModule { + constructor(matIconRegistry: MatIconRegistry, domSanitizer: DomSanitizer) { + matIconRegistry.addSvgIconSet( + domSanitizer.bypassSecurityTrustResourceUrl('./assets/mdi.svg') + ) + } +} diff --git a/src/main/frontend/src/app/modules/accounts/accounts-routing.module.ts b/src/main/frontend/src/app/modules/accounts/accounts-routing.module.ts new file mode 100644 index 0000000..843486b --- /dev/null +++ b/src/main/frontend/src/app/modules/accounts/accounts-routing.module.ts @@ -0,0 +1,14 @@ +import {NgModule} from '@angular/core'; +import {Routes, RouterModule} from '@angular/router'; + +import {LoginComponent} from './pages/login/login.component'; +import {LogoutComponent} from "./pages/logout/logout.component"; + +const routes: Routes = [{path: 'login', component: LoginComponent}, {path: 'logout', component: LogoutComponent}, {path: '', redirectTo: 'login'}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AccountsRoutingModule { +} diff --git a/src/main/frontend/src/app/modules/accounts/accounts.module.ts b/src/main/frontend/src/app/modules/accounts/accounts.module.ts new file mode 100644 index 0000000..33f0a16 --- /dev/null +++ b/src/main/frontend/src/app/modules/accounts/accounts.module.ts @@ -0,0 +1,19 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {AccountsRoutingModule} from './accounts-routing.module'; +import {LoginComponent} from './pages/login/login.component'; +import {SharedModule} from "../shared/shared.module"; +import { LogoutComponent } from './pages/logout/logout.component'; + + +@NgModule({ + declarations: [LoginComponent, LogoutComponent], + imports: [ + CommonModule, + SharedModule, + AccountsRoutingModule + ] +}) +export class AccountsModule { +} diff --git a/src/main/frontend/src/app/modules/accounts/pages/login/login.component.html b/src/main/frontend/src/app/modules/accounts/pages/login/login.component.html new file mode 100644 index 0000000..030ac9a --- /dev/null +++ b/src/main/frontend/src/app/modules/accounts/pages/login/login.component.html @@ -0,0 +1,40 @@ +
+ + + Connexion au système + + +
+

Les identifiants entrés sont invalides.

+
+
+ + Numéro d'employé + + person + + Un numéro d'employé est requis + Le numéro d'employé doit être un nombre + + + + Mot de passe + + lock + + Un mot de passe est requis + + +
+
+ + + +
diff --git a/src/main/frontend/src/app/modules/accounts/pages/login/login.component.sass b/src/main/frontend/src/app/modules/accounts/pages/login/login.component.sass new file mode 100644 index 0000000..c95ac6f --- /dev/null +++ b/src/main/frontend/src/app/modules/accounts/pages/login/login.component.sass @@ -0,0 +1,13 @@ +mat-card + width: 25rem + + &.centered + margin: 50vh auto auto + position: relative + transform: translateY(-70%) + + .alert p + margin: 0 + + mat-form-field + width: 100% diff --git a/src/main/frontend/src/app/modules/accounts/pages/login/login.component.ts b/src/main/frontend/src/app/modules/accounts/pages/login/login.component.ts new file mode 100644 index 0000000..8cd46c3 --- /dev/null +++ b/src/main/frontend/src/app/modules/accounts/pages/login/login.component.ts @@ -0,0 +1,43 @@ +import {Component, OnInit} from '@angular/core'; +import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; +import {AccountService} from "../../services/account.service"; +import {take} from "rxjs/operators"; +import {Router} from "@angular/router"; + +@Component({ + selector: 'cre-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.sass'] +}) +export class LoginComponent implements OnInit { + form: FormGroup + idFormControl: FormControl + passwordFormControl: FormControl + + invalidCredentials = false + + constructor( + private formBuilder: FormBuilder, + private accountService: AccountService, + private router: Router + ) { + } + + ngOnInit(): void { + this.idFormControl = this.formBuilder.control(null, Validators.compose([Validators.required, Validators.pattern(new RegExp('^[0-9]+$'))])) + this.passwordFormControl = this.formBuilder.control(null, Validators.required) + this.form = this.formBuilder.group({ + id: this.idFormControl, + password: this.passwordFormControl + }) + } + + submit() { + this.accountService.login( + this.idFormControl.value, + this.passwordFormControl.value, + () => this.router.navigate(["/color"]), + err => this.invalidCredentials = err.status === 401 + ) + } +} diff --git a/src/main/frontend/src/app/modules/accounts/pages/logout/logout.component.html b/src/main/frontend/src/app/modules/accounts/pages/logout/logout.component.html new file mode 100644 index 0000000..e69de29 diff --git a/src/main/frontend/src/app/modules/accounts/pages/logout/logout.component.sass b/src/main/frontend/src/app/modules/accounts/pages/logout/logout.component.sass new file mode 100644 index 0000000..e69de29 diff --git a/src/main/frontend/src/app/modules/accounts/pages/logout/logout.component.ts b/src/main/frontend/src/app/modules/accounts/pages/logout/logout.component.ts new file mode 100644 index 0000000..283064c --- /dev/null +++ b/src/main/frontend/src/app/modules/accounts/pages/logout/logout.component.ts @@ -0,0 +1,24 @@ +import {Component, OnInit} from '@angular/core'; +import {AccountService} from "../../services/account.service"; +import {Router} from "@angular/router"; + +@Component({ + selector: 'cre-logout', + templateUrl: './logout.component.html', + styleUrls: ['./logout.component.sass'] +}) +export class LogoutComponent implements OnInit { + + constructor( + private accountService: AccountService, + private router: Router + ) { + } + + ngOnInit(): void { + this.accountService.logout(() => { + this.router.navigate(['/account/login']) + }) + } + +} diff --git a/src/main/frontend/src/app/modules/accounts/services/account.service.ts b/src/main/frontend/src/app/modules/accounts/services/account.service.ts new file mode 100644 index 0000000..76809e6 --- /dev/null +++ b/src/main/frontend/src/app/modules/accounts/services/account.service.ts @@ -0,0 +1,70 @@ +import {Injectable, OnDestroy} from '@angular/core'; +import {Subject} from "rxjs"; +import {take, takeUntil} from "rxjs/operators"; +import {AppState} from "../../shared/app-state"; +import {HttpClient, HttpResponse} from "@angular/common/http"; +import {environment} from "../../../../environments/environment"; +import {ApiService} from "../../shared/service/api.service"; +import {Employee} from "../../shared/model/employee"; + +@Injectable({ + providedIn: 'root' +}) +export class AccountService implements OnDestroy { + private $destroy = new Subject() + + constructor( + private http: HttpClient, + private api: ApiService, + private appState: AppState + ) { + } + + ngOnDestroy(): void { + this.$destroy.next(true) + this.$destroy.complete() + } + + login(id: number, password: string, success: () => void, error: (err) => void) { + const loginForm = {id, password} + this.http.post(`${environment.apiUrl}/login`, loginForm, { + withCredentials: true, + observe: 'response' as 'body' + }) + .pipe( + take(1), + takeUntil(this.$destroy) + ) + .subscribe({ + next: (response: HttpResponse) => { + this.appState.authenticationExpiration = parseInt(response.headers.get("X-Authentication-Expiration")) + this.appState.isAuthenticated = true + this.setLoggedInEmployeeFromApi() + success() + }, + error: err => error(err) + }) + } + + logout(success: () => void) { + this.appState.isAuthenticated = false + this.appState.authenticationExpiration = -1 + this.appState.authenticatedEmployee = null + success() + } + + private setLoggedInEmployeeFromApi() { + this.api.get("/employee/current", true) + .pipe( + take(1), + takeUntil(this.$destroy) + ) + .subscribe({ + next: employee => this.appState.authenticatedEmployee = employee, + error: err => { + console.error("Could not get the logged in employee from the API: ") + console.error(err) + } + }) + } +} diff --git a/src/main/frontend/src/app/modules/colors/colors.component.ts b/src/main/frontend/src/app/modules/colors/colors.component.ts index f4e98e8..c8d9021 100644 --- a/src/main/frontend/src/app/modules/colors/colors.component.ts +++ b/src/main/frontend/src/app/modules/colors/colors.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, OnInit} from '@angular/core'; @Component({ selector: 'cre-colors', @@ -7,9 +7,9 @@ import { Component, OnInit } from '@angular/core'; }) export class ColorsComponent implements OnInit { - constructor() { } + constructor() { + } ngOnInit(): void { } - } diff --git a/src/main/frontend/src/app/modules/shared/app-state.ts b/src/main/frontend/src/app/modules/shared/app-state.ts new file mode 100644 index 0000000..26ad68c --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/app-state.ts @@ -0,0 +1,51 @@ +import {Injectable} from "@angular/core"; +import {Employee} from "./model/employee"; +import {Subject} from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class AppState { + private readonly KEY_AUTHENTICATED = "authenticated" + private readonly KEY_AUTHENTICATION_EXPIRATION = "authentication-expiration" + private readonly KEY_LOGGED_IN_EMPLOYEE = "logged-in-employee" + + authenticatedUser$ = new Subject<{ authenticated: boolean, authenticatedUser: Employee }>() + + get isAuthenticated(): boolean { + return sessionStorage.getItem(this.KEY_AUTHENTICATED) === "true" + } + + set isAuthenticated(value: boolean) { + sessionStorage.setItem(this.KEY_AUTHENTICATED, value.toString()) + this.authenticatedUser$.next({ + authenticated: value, + authenticatedUser: this.authenticatedEmployee + }) + } + + get authenticationExpiration(): number { + return parseInt(sessionStorage.getItem(this.KEY_AUTHENTICATION_EXPIRATION)) + } + + set authenticationExpiration(value: number) { + sessionStorage.setItem(this.KEY_AUTHENTICATION_EXPIRATION, value.toString()) + } + + get authenticatedEmployee(): Employee { + const employeeString = sessionStorage.getItem(this.KEY_LOGGED_IN_EMPLOYEE) + return employeeString ? JSON.parse(employeeString) : null + } + + set authenticatedEmployee(value: Employee) { + if (value === null) { + sessionStorage.removeItem(this.KEY_LOGGED_IN_EMPLOYEE) + return + } + sessionStorage.setItem(this.KEY_LOGGED_IN_EMPLOYEE, JSON.stringify(value)) + this.authenticatedUser$.next({ + authenticated: this.isAuthenticated, + authenticatedUser: value + }) + } +} diff --git a/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.html b/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.html new file mode 100644 index 0000000..e8bf432 --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.html @@ -0,0 +1,7 @@ +
+ +
+ + +
+
diff --git a/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.sass b/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.sass new file mode 100644 index 0000000..462ef51 --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.sass @@ -0,0 +1,12 @@ +@import "../../../../../custom-theme" + +p, labeled-icon + margin: 0 + color: $light-primary-text + +.employee-info-container + margin-top: .85rem + margin-right: 1rem + +.employee-info-group + margin-left: 0.7rem diff --git a/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.ts b/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.ts new file mode 100644 index 0000000..a4dda93 --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/components/employee-info/employee-info.component.ts @@ -0,0 +1,45 @@ +import {Component, OnDestroy, OnInit} from '@angular/core'; +import {AppState} from "../../app-state"; +import {Employee} from "../../model/employee"; +import {Subject} from "rxjs"; +import {takeUntil} from "rxjs/operators"; + +@Component({ + selector: 'cre-employee-info', + templateUrl: './employee-info.component.html', + styleUrls: ['./employee-info.component.sass'] +}) +export class EmployeeInfoComponent implements OnInit, OnDestroy { + authenticated = false + employee: Employee = null + employeeInGroup = false + + private destroy$ = new Subject() + + constructor( + public appState: AppState + ) { + } + + ngOnInit(): void { + this.authenticationState(this.appState.isAuthenticated, this.appState.authenticatedEmployee) + this.appState.authenticatedUser$ + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: authentication => this.authenticationState(authentication.authenticated, authentication.authenticatedUser) + }) + } + + ngOnDestroy(): void { + this.destroy$.next(true) + this.destroy$.complete() + } + + private authenticationState(authenticated: boolean, employee: Employee) { + this.authenticated = authenticated + this.employee = employee + if (this.employee != null) { + this.employeeInGroup = this.employee.group != null + } + } +} diff --git a/src/main/frontend/src/app/modules/shared/components/header/header.component.html b/src/main/frontend/src/app/modules/shared/components/header/header.component.html index d6b7ffa..a0858a7 100644 --- a/src/main/frontend/src/app/modules/shared/components/header/header.component.html +++ b/src/main/frontend/src/app/modules/shared/components/header/header.component.html @@ -2,15 +2,19 @@ + Logo diff --git a/src/main/frontend/src/app/modules/shared/components/header/header.component.ts b/src/main/frontend/src/app/modules/shared/components/header/header.component.ts index a7469f8..90df0f2 100644 --- a/src/main/frontend/src/app/modules/shared/components/header/header.component.ts +++ b/src/main/frontend/src/app/modules/shared/components/header/header.component.ts @@ -1,24 +1,41 @@ -import {Component} from '@angular/core'; +import {Component, OnInit} from '@angular/core'; import {Router} from "@angular/router"; +import {AppState} from "../../app-state"; @Component({ selector: 'cre-header', templateUrl: './header.component.html', styleUrls: ['./header.component.sass'] }) -export class HeaderComponent { +export class HeaderComponent implements OnInit { links = [ - {route: 'color', title: 'Couleurs'}, - {route: 'inventory', title: 'Inventaire'}, - {route: 'account', title: 'Connexion'} + {route: 'color', title: 'Couleurs', enabled: true}, + {route: 'inventory', title: 'Inventaire', enabled: true}, + {route: 'account/login', title: 'Connexion', enabled: true}, + {route: 'account/logout', title: 'Déconnexion', enabled: false} ]; _activeLink = this.links[0].route; constructor( - private router: Router + private router: Router, + private appState: AppState ) { } + ngOnInit(): void { + const loginLink = this.links[2] + const logoutLink = this.links[3] + loginLink.enabled = !this.appState.isAuthenticated + logoutLink.enabled = this.appState.isAuthenticated + + this.appState.authenticatedUser$.subscribe({ + next: authentication => { + loginLink.enabled = !authentication.authenticated + logoutLink.enabled = authentication.authenticated + } + }) + } + set activeLink(link: string) { this._activeLink = link; this.router.navigate([link]); @@ -27,4 +44,8 @@ export class HeaderComponent { get activeLink() { return this._activeLink; } + + test(link: any) { + console.log(link.condition ? link.condition() : true) + } } diff --git a/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.html b/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.html new file mode 100644 index 0000000..ae21cd3 --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.html @@ -0,0 +1,4 @@ +
+ +

{{label}}

+
diff --git a/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.sass b/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.sass new file mode 100644 index 0000000..aa93e1a --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.sass @@ -0,0 +1,6 @@ +mat-icon + width: 1em + +p + margin: 4px 0 0 .3em + font-size: .8em diff --git a/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.ts b/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.ts new file mode 100644 index 0000000..0d1e56a --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/components/labeled-icon/labeled-icon.component.ts @@ -0,0 +1,11 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'labeled-icon', + templateUrl: './labeled-icon.component.html', + styleUrls: ['./labeled-icon.component.sass'] +}) +export class LabeledIconComponent { + @Input() icon: string + @Input() label: string +} diff --git a/src/main/frontend/src/app/modules/shared/model/employee.ts b/src/main/frontend/src/app/modules/shared/model/employee.ts new file mode 100644 index 0000000..7189614 --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/model/employee.ts @@ -0,0 +1,37 @@ +export class Employee { + constructor( + public id: number, + public firstName: string, + public lastName: string, + public permissions: EmployeePermission[], + public excludedPermissions: EmployeePermission[], + public group?: EmployeeGroup, + public lastLoginTime?: Date + ) { + } +} + +export class EmployeeGroup { + constructor( + public id: number, + public name: string, + public permissions: EmployeePermission[] + ) { + } +} + +export enum EmployeePermission { + VIEW_EMPLOYEE, + VIEW_EMPLOYEE_GROUP, + VIEW, + + EDIT_EMPLOYEE, + EDIT_EMPLOYEE_GROUP, + EDIT, + + REMOVE_EMPLOYEE, + REMOVE_EMPLOYEE_GROUP, + REMOVE, + + ADMIN +} diff --git a/src/main/frontend/src/app/modules/shared/service/api.service.ts b/src/main/frontend/src/app/modules/shared/service/api.service.ts new file mode 100644 index 0000000..5f0f8ee --- /dev/null +++ b/src/main/frontend/src/app/modules/shared/service/api.service.ts @@ -0,0 +1,61 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {environment} from "../../../../environments/environment"; +import {AppState} from "../app-state"; +import {Router} from "@angular/router"; + +@Injectable({ + providedIn: 'root' +}) +export class ApiService { + constructor( + private http: HttpClient, + private appState: AppState, + private router: Router + ) { + } + + get(url: string, needAuthentication = false, options: any = {}): Observable { + if (this.checkAuthenticated(needAuthentication, options)) { + // @ts-ignore + return this.http.get(environment.apiUrl + url, options) + } + } + + post(url: string, body: any, needAuthentication = true, options: any = {}): Observable { + if (this.checkAuthenticated(needAuthentication, options)) { + // @ts-ignore + return this.http.post(environment.apiUrl + url, body, options) + } + } + + put(url: string, body: any, needAuthentication = true, options: any = {}): Observable { + if (this.checkAuthenticated(needAuthentication, options)) { + // @ts-ignore + return this.http.put(environment.apiUrl + url, body, options) + } + } + + delete(url: string, needAuthentication = true, options: any = {}): Observable { + if (this.checkAuthenticated(needAuthentication, options)) { + // @ts-ignore + return this.http.delete(environment.apiUrl + url, options) + } + } + + private checkAuthenticated(needAuthentication: boolean, httpOptions: any): boolean { + if (needAuthentication) { + if (!this.appState.isAuthenticated || Date.now() > this.appState.authenticationExpiration) { + this.navigateToLogin() + return false + } + httpOptions.withCredentials = true + } + return true + } + + private navigateToLogin() { + this.router.navigate(['/account/login']) + } +} diff --git a/src/main/frontend/src/app/modules/shared/shared.module.ts b/src/main/frontend/src/app/modules/shared/shared.module.ts index a23ea38..0369918 100644 --- a/src/main/frontend/src/app/modules/shared/shared.module.ts +++ b/src/main/frontend/src/app/modules/shared/shared.module.ts @@ -2,17 +2,34 @@ import { NgModule } from '@angular/core'; import { HeaderComponent } from './components/header/header.component'; import {MatTabsModule} from "@angular/material/tabs"; import {CommonModule} from "@angular/common"; +import {MatCardModule} from "@angular/material/card"; +import {MatButtonModule} from "@angular/material/button"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatInputModule} from "@angular/material/input"; +import {MatIconModule} from "@angular/material/icon"; +import {ReactiveFormsModule} from "@angular/forms"; +import {RouterModule} from "@angular/router"; +import { EmployeeInfoComponent } from './components/employee-info/employee-info.component'; +import { LabeledIconComponent } from './components/labeled-icon/labeled-icon.component'; @NgModule({ - declarations: [HeaderComponent], + declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent], exports: [ - HeaderComponent + HeaderComponent, + MatCardModule, + MatButtonModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + ReactiveFormsModule, + RouterModule ], imports: [ MatTabsModule, - CommonModule + CommonModule, + MatIconModule ] }) export class SharedModule { } diff --git a/src/main/frontend/src/custom-theme.scss b/src/main/frontend/src/custom-theme.scss index c3b3aa9..a44e030 100644 --- a/src/main/frontend/src/custom-theme.scss +++ b/src/main/frontend/src/custom-theme.scss @@ -15,7 +15,7 @@ $custom-typography: mat-typography-config( // Define the palettes for your theme using the Material Design palettes available in palette.scss // (imported above). For each palette, you can optionally specify a default, lighter, and darker // hue. Available color palettes: https://material.io/design/color/ -$color-recipes-explorer-frontend-primary: mat-palette(( +$theme-primary: mat-palette(( 50 : #e0e0e0, 100 : #b3b3b3, 200 : #808080, @@ -46,7 +46,7 @@ $color-recipes-explorer-frontend-primary: mat-palette(( A400 : #ffffff, A700 : #ffffff, ))); -$color-recipes-explorer-frontend-accent: mat-palette(( +$theme-accent: mat-palette(( 50 : #edf9e0, 100 : #d1f0b3, 200 : #b3e680, @@ -80,16 +80,19 @@ $color-recipes-explorer-frontend-accent: mat-palette(( )); // The warn palette is optional (defaults to red). -$color-recipes-explorer-frontend-warn: mat-palette($mat-red); +$theme-warn: mat-palette($mat-red); // Create the theme object (a Sass map containing all of the palettes). -$color-recipes-explorer-frontend-theme: mat-light-theme($color-recipes-explorer-frontend-primary, $color-recipes-explorer-frontend-accent, $color-recipes-explorer-frontend-warn); +$color-recipes-explorer-frontend-theme: mat-light-theme($theme-primary, $theme-accent, $theme-warn); // Include theme styles for core and each component used in your app. // Alternatively, you can import and @include the theme mixins for each component // that you are using. @include angular-material-theme($color-recipes-explorer-frontend-theme); +$color-primary: map-get($theme-primary, 500); +$color-accent: map-get($theme-accent, 500); + html, body { height: 100%; diff --git a/src/main/frontend/src/environments/environment.prod.ts b/src/main/frontend/src/environments/environment.prod.ts index 3612073..932df64 100644 --- a/src/main/frontend/src/environments/environment.prod.ts +++ b/src/main/frontend/src/environments/environment.prod.ts @@ -1,3 +1,4 @@ export const environment = { - production: true + production: true, + apiUrl: '/api' }; diff --git a/src/main/frontend/src/environments/environment.ts b/src/main/frontend/src/environments/environment.ts index 7b4f817..07d5091 100644 --- a/src/main/frontend/src/environments/environment.ts +++ b/src/main/frontend/src/environments/environment.ts @@ -3,7 +3,8 @@ // The list of file replacements can be found in `angular.json`. export const environment = { - production: false + production: false, + apiUrl: 'http://localhost:9090/api' }; /* diff --git a/src/main/frontend/src/styles.sass b/src/main/frontend/src/styles.sass index 90d4ee0..a903f5e 100644 --- a/src/main/frontend/src/styles.sass +++ b/src/main/frontend/src/styles.sass @@ -1 +1,31 @@ -/* You can add global styles to this file, and also import other style files */ +@import "custom-theme" + +mat-card + padding: 0 !important + + mat-card-header + background-color: $color-primary + color: $light-primary-text + padding: 16px 16px 0 16px + border-radius: 4px 4px 0 0 + + mat-card-content + margin-top: 16px + padding: 0 16px + + mat-card-actions + display: flex !important + padding: 0 24px 16px 24px !important + flex-direction: row + + button + text-transform: uppercase + letter-spacing: 1.25px + +.dark-background + position: fixed + width: 100% + height: 100% + top: 0 + background-color: black + opacity: 0.05 diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/WebSecurityConfig.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/WebSecurityConfig.kt index bae22ca..2c2258b 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/WebSecurityConfig.kt +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/configuration/WebSecurityConfig.kt @@ -38,9 +38,13 @@ import org.springframework.util.Assert import org.springframework.web.cors.CorsConfiguration import org.springframework.web.cors.CorsConfigurationSource import org.springframework.web.cors.UrlBasedCorsConfigurationSource +import org.springframework.web.util.WebUtils import java.util.* import javax.annotation.PostConstruct +import javax.servlet.Filter import javax.servlet.FilterChain +import javax.servlet.ServletRequest +import javax.servlet.ServletResponse import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse @@ -75,19 +79,42 @@ class WebSecurityConfig( @Bean fun corsConfigurationSource(): CorsConfigurationSource { return UrlBasedCorsConfigurationSource().apply { - registerCorsConfiguration("/**", CorsConfiguration().applyPermitDefaultValues()) + registerCorsConfiguration("/**", CorsConfiguration().apply { + allowedOrigins = listOf("http://localhost:4200") // Angular development server + allowedMethods = listOf( + HttpMethod.GET.name, + HttpMethod.POST.name, + HttpMethod.PUT.name, + HttpMethod.DELETE.name, + HttpMethod.OPTIONS.name, + HttpMethod.HEAD.name + ) + allowCredentials = true + }.applyPermitDefaultValues()) } } @PostConstruct - fun createRootUser() { - val rootUserCredentials = securityConfigurationProperties.root - Assert.notNull(rootUserCredentials, "No root user has been defined.") - Assert.notNull(rootUserCredentials!!.id, "The root user has no identifier defined.") - Assert.notNull(rootUserCredentials.password, "The root user has no password defined.") - if (!employeeService.existsById(rootUserCredentials.id!!)) { - employeeService.save(Employee(rootUserCredentials.id!!, "Root", "Employee", passwordEncoder().encode(rootUserCredentials.password!!), true, permissions = mutableListOf(EmployeePermission.ADMIN))) + fun createSystemUsers() { + fun createUser(credentials: SecurityConfigurationProperties.SystemUserCredentials?, firstName: String, lastName: String, permissions: List) { + Assert.notNull(credentials, "No root user has been defined.") + credentials!! + Assert.notNull(credentials.id, "The root user has no identifier defined.") + Assert.notNull(credentials.password, "The root user has no password defined.") + if (!employeeService.existsById(credentials.id!!)) { + employeeService.save(Employee( + id = credentials.id!!, + firstName = firstName, + lastName = lastName, + password = passwordEncoder().encode(credentials.password!!), + isSystemUser = true, + permissions = permissions.toMutableList() + )) + } } + + createUser(securityConfigurationProperties.root, "Root", "User", listOf(EmployeePermission.ADMIN)) + createUser(securityConfigurationProperties.common, "Common", "User", listOf(EmployeePermission.VIEW)) } override fun configure(http: HttpSecurity) { @@ -104,6 +131,7 @@ class WebSecurityConfig( } http +// .addFilterBefore(CorsFilter()) .cors() .and() .csrf().disable() @@ -124,6 +152,20 @@ class RestAuthenticationEntryPoint : AuthenticationEntryPoint { override fun commence(request: HttpServletRequest, response: HttpServletResponse, authException: AuthenticationException) = response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized") } +class CorsFilter : Filter { + override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { + response as HttpServletResponse + + response.setHeader("Access-Control-Allow-Origin", "http://localhost:4200") + response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS") + response.setHeader("Access-Control-Allow-Headers", "*") + response.setHeader("Access-Control-Allow-Credentials", true.toString()) + response.setHeader("Access-Control-Max-Age", 180.toString()) + + chain.doFilter(request, response) + } +} + class JwtAuthenticationFilter( val authManager: AuthenticationManager, val employeeService: EmployeeService, @@ -145,12 +187,17 @@ class JwtAuthenticationFilter( Assert.notNull(jwtDuration, "No JWT duration has been defined.") val employeeId = (authResult.principal as User).username employeeService.updateLastLoginTime(employeeId.toLong()) + val expirationMs = System.currentTimeMillis() + jwtDuration!! + val expirationDate = Date(expirationMs) val token = Jwts.builder() .setSubject(employeeId) - .setExpiration(Date(System.currentTimeMillis() + jwtDuration!!)) + .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, jwtSecret!!.toByteArray()) .compact() + response.addHeader("Access-Control-Expose-Headers", "X-Authentication-Expiration") + response.addHeader("Set-Cookie", "Authorization=Bearer$token; Max-Age=${jwtDuration / 1000}; HttpOnly; Secure; SameSite=strict") response.addHeader("Authorization", "Bearer $token") + response.addHeader("X-Authentication-Expiration", "$expirationMs") } } @@ -160,30 +207,32 @@ class JwtAuthorizationFilter( authenticationManager: AuthenticationManager ) : BasicAuthenticationFilter(authenticationManager) { override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) { - val header = request.getHeader("Authorization") - if (header != null && header.startsWith("Bearer")) { - val authenticationToken = getAuthentication(request) - SecurityContextHolder.getContext().authentication = authenticationToken + val authorizationCookie = WebUtils.getCookie(request, "Authorization") + val authorizationValue = if (authorizationCookie != null) authorizationCookie.value else request.getHeader("Authorization") + val authenticationToken = if (authorizationValue != null && authorizationValue.startsWith("Bearer")) { + getAuthentication(authorizationValue) + } else { + // Load common user if there is no valid authentication data + getAuthenticationToken(securityConfigurationProperties.common!!.id!!.toString()) } + SecurityContextHolder.getContext().authentication = authenticationToken chain.doFilter(request, response) } - private fun getAuthentication(request: HttpServletRequest): UsernamePasswordAuthenticationToken? { + private fun getAuthentication(token: String): UsernamePasswordAuthenticationToken? { val jwtSecret = securityConfigurationProperties.jwtSecret Assert.notNull(jwtSecret, "No JWT secret has been defined.") - val token = request.getHeader("Authorization") - if (token != null) { - val employeeId = Jwts.parser() - .setSigningKey(jwtSecret!!.toByteArray()) - .parseClaimsJws(token.replace("Bearer", "")) - .body - .subject - return if (employeeId != null) { - val employeeDetails = userDetailsService.loadUserByUsername(employeeId) - UsernamePasswordAuthenticationToken(employeeDetails.username, null, employeeDetails.authorities) - } else null - } - return null + val employeeId = Jwts.parser() + .setSigningKey(jwtSecret!!.toByteArray()) + .parseClaimsJws(token.replace("Bearer", "")) + .body + .subject + return if (employeeId != null) getAuthenticationToken(employeeId) else null + } + + private fun getAuthenticationToken(employeeId: String): UsernamePasswordAuthenticationToken { + val employeeDetails = userDetailsService.loadUserByEmployeeId(employeeId.toLong(), true) + return UsernamePasswordAuthenticationToken(employeeDetails.username, null, employeeDetails.authorities) } } @@ -191,9 +240,10 @@ class JwtAuthorizationFilter( class SecurityConfigurationProperties { var jwtSecret: String? = null var jwtDuration: Long? = null - var root: RootUserCredentials? = null + var root: SystemUserCredentials? = null + var common: SystemUserCredentials? = null - class RootUserCredentials(var id: Long? = null, var password: String? = null) + class SystemUserCredentials(var id: Long? = null, var password: String? = null) } private enum class ControllerAuthorizations( diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/AccountsModel.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/AccountsModel.kt index 2833f8e..cff6a66 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/AccountsModel.kt +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/model/AccountsModel.kt @@ -32,7 +32,7 @@ class Employee( val password: String = "", @JsonIgnore - val isRoot: Boolean = false, + val isSystemUser: Boolean = false, @field:ManyToOne @Fetch(FetchMode.SELECT) diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AccountsService.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AccountsService.kt index f8aaba1..42e93c6 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AccountsService.kt +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/core/services/AccountsService.kt @@ -1,5 +1,6 @@ package dev.fyloz.trial.colorrecipesexplorer.core.services +import dev.fyloz.trial.colorrecipesexplorer.core.configuration.SecurityConfigurationProperties import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.* import dev.fyloz.trial.colorrecipesexplorer.core.model.* import dev.fyloz.trial.colorrecipesexplorer.dao.EmployeeGroupRepository @@ -22,7 +23,7 @@ class EmployeeService(val employeeRepository: EmployeeRepository, val passwordEn } override fun getAll(): Collection { - return super.getAll().filter { !it.isRoot } + return super.getAll().filter { !it.isSystemUser } } override fun getById(id: Long): Employee { @@ -30,9 +31,9 @@ class EmployeeService(val employeeRepository: EmployeeRepository, val passwordEn } /** Gets the employee with the given [id]. */ - fun getById(id: Long, ignoreRoot: Boolean): Employee { + fun getById(id: Long, ignoreSystemUsers: Boolean): Employee { return super.getById(id).apply { - if (ignoreRoot && isRoot) throw EntityNotFoundRestException(id) + if (ignoreSystemUsers && isSystemUser) throw EntityNotFoundRestException(id) } } @@ -60,8 +61,8 @@ class EmployeeService(val employeeRepository: EmployeeRepository, val passwordEn } /** Updates de given [entity]. **/ - fun update(entity: Employee, ignoreRoot: Boolean): Employee { - val persistedEmployee = getById(entity.id, ignoreRoot) + fun update(entity: Employee, ignoreSystemUsers: Boolean): Employee { + val persistedEmployee = getById(entity.id, ignoreSystemUsers) with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) { if (this != null && id != entity.id) throw EntityAlreadyExistsRestException("${entity.firstName} ${entity.lastName}") @@ -73,7 +74,7 @@ class EmployeeService(val employeeRepository: EmployeeRepository, val passwordEn if (firstName.isNotBlank()) firstName else persistedEmployee.firstName, if (lastName.isNotBlank()) lastName else persistedEmployee.lastName, persistedEmployee.password, - if (ignoreRoot) false else persistedEmployee.isRoot, + if (ignoreSystemUsers) false else persistedEmployee.isSystemUser, persistedEmployee.group, if (permissions.isNotEmpty()) permissions else persistedEmployee.permissions, if (excludedPermissions.isNotEmpty()) excludedPermissions else persistedEmployee.excludedPermissions, @@ -136,7 +137,7 @@ class EmployeeGroupService(val employeeGroupRepository: EmployeeGroupRepository, } @Service -class EmployeeUserDetailsService(val employeeService: EmployeeService) : UserDetailsService { +class EmployeeUserDetailsService(val employeeService: EmployeeService, val securityConfigurationProperties: SecurityConfigurationProperties) : UserDetailsService { override fun loadUserByUsername(username: String): UserDetails { try { return loadUserByEmployeeId(username.toLong()) @@ -146,7 +147,9 @@ class EmployeeUserDetailsService(val employeeService: EmployeeService) : UserDet } /** Loads an [User] for the given [employeeId]. */ - fun loadUserByEmployeeId(employeeId: Long): UserDetails { + fun loadUserByEmployeeId(employeeId: Long, allowCommonUser: Boolean = false): UserDetails { + if (!allowCommonUser && employeeId == securityConfigurationProperties.common!!.id!!) + throw UsernameNotFoundException(employeeId.toString()) val employee = employeeService.getById(employeeId, false) return User(employee.id.toString(), employee.password, employee.getAuthorities()) } diff --git a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/AccountControllers.kt b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/AccountControllers.kt index d39bc26..b276038 100644 --- a/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/AccountControllers.kt +++ b/src/main/java/dev/fyloz/trial/colorrecipesexplorer/rest/AccountControllers.kt @@ -25,7 +25,7 @@ class EmployeeController(employeeService: EmployeeService) : AbstractRestModelController(employeeService, EMPLOYEE_CONTROLLER_PATH) { @GetMapping("current") @ResponseStatus(HttpStatus.OK) - fun getCurrent(loggedInEmployee: Principal): ResponseEntity = getById(loggedInEmployee.name.toLong()) + fun getCurrent(loggedInEmployee: Principal): ResponseEntity = ResponseEntity.ok(service.getById(loggedInEmployee.name.toLong(), false)) @PostMapping @ResponseStatus(HttpStatus.CREATED) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0befda7..f6205e7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -13,8 +13,12 @@ cre.server.url-use-port=true cre.server.url-use-https=false cre.security.jwt-secret=CtnvGQjgZ44A1fh295gE cre.security.jwt-duration=18000000 +# Root user cre.security.root.id=9999 cre.security.root.password=password +# Common user +cre.security.common.id=9998 +cre.security.common.password=common # TYPES DE PRODUIT PAR DÉFAUT entities.material-types.defaults[0].name=Aucun entities.material-types.defaults[0].prefix=