diff --git a/package-lock.json b/package-lock.json
index 110560bfb225df090d54e783967c11562364058b..a77dac2e553a39500bc91d16bf0f9c7c1a80ea95 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -56,7 +56,7 @@
         "rollup-plugin-node-polyfills": "~0.2.1",
         "sass": "^1.66.0",
         "typescript": "~5.4.0",
-        "vite": "~5.2.0",
+        "vite": "~5.3.0",
         "vue-tsc": "~2.0.0"
       }
     },
@@ -973,7 +973,6 @@
       "os": [
         "aix"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -990,7 +989,6 @@
       "os": [
         "android"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1007,7 +1005,6 @@
       "os": [
         "android"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1024,7 +1021,6 @@
       "os": [
         "android"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1041,7 +1037,6 @@
       "os": [
         "darwin"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1058,7 +1053,6 @@
       "os": [
         "darwin"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1075,7 +1069,6 @@
       "os": [
         "freebsd"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1092,7 +1085,6 @@
       "os": [
         "freebsd"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1109,7 +1101,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1126,7 +1117,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1143,7 +1133,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1160,7 +1149,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1177,7 +1165,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1194,7 +1181,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1211,7 +1197,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1228,7 +1213,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1245,7 +1229,6 @@
       "os": [
         "linux"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1262,7 +1245,6 @@
       "os": [
         "netbsd"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1279,7 +1261,6 @@
       "os": [
         "openbsd"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1296,7 +1277,6 @@
       "os": [
         "sunos"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1313,7 +1293,6 @@
       "os": [
         "win32"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1330,7 +1309,6 @@
       "os": [
         "win32"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -1347,7 +1325,6 @@
       "os": [
         "win32"
       ],
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -2744,12 +2721,12 @@
       }
     },
     "node_modules/@vue/compiler-core": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz",
-      "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==",
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz",
+      "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==",
       "dependencies": {
-        "@babel/parser": "^7.24.4",
-        "@vue/shared": "3.4.27",
+        "@babel/parser": "^7.24.7",
+        "@vue/shared": "3.4.29",
         "entities": "^4.5.0",
         "estree-walker": "^2.0.2",
         "source-map-js": "^1.2.0"
@@ -2761,24 +2738,24 @@
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
     "node_modules/@vue/compiler-dom": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz",
-      "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==",
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz",
+      "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==",
       "dependencies": {
-        "@vue/compiler-core": "3.4.27",
-        "@vue/shared": "3.4.27"
+        "@vue/compiler-core": "3.4.29",
+        "@vue/shared": "3.4.29"
       }
     },
     "node_modules/@vue/compiler-sfc": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz",
-      "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==",
-      "dependencies": {
-        "@babel/parser": "^7.24.4",
-        "@vue/compiler-core": "3.4.27",
-        "@vue/compiler-dom": "3.4.27",
-        "@vue/compiler-ssr": "3.4.27",
-        "@vue/shared": "3.4.27",
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz",
+      "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==",
+      "dependencies": {
+        "@babel/parser": "^7.24.7",
+        "@vue/compiler-core": "3.4.29",
+        "@vue/compiler-dom": "3.4.29",
+        "@vue/compiler-ssr": "3.4.29",
+        "@vue/shared": "3.4.29",
         "estree-walker": "^2.0.2",
         "magic-string": "^0.30.10",
         "postcss": "^8.4.38",
@@ -2799,12 +2776,12 @@
       }
     },
     "node_modules/@vue/compiler-ssr": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz",
-      "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==",
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz",
+      "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.27",
-        "@vue/shared": "3.4.27"
+        "@vue/compiler-dom": "3.4.29",
+        "@vue/shared": "3.4.29"
       }
     },
     "node_modules/@vue/devtools-api": {
@@ -2874,48 +2851,49 @@
       }
     },
     "node_modules/@vue/reactivity": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz",
-      "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==",
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz",
+      "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==",
       "dependencies": {
-        "@vue/shared": "3.4.27"
+        "@vue/shared": "3.4.29"
       }
     },
     "node_modules/@vue/runtime-core": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz",
-      "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==",
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.29.tgz",
+      "integrity": "sha512-s8fmX3YVR/Rk5ig0ic0NuzTNjK2M7iLuVSZyMmCzN/+Mjuqqif1JasCtEtmtoJWF32pAtUjyuT2ljNKNLeOmnQ==",
       "dependencies": {
-        "@vue/reactivity": "3.4.27",
-        "@vue/shared": "3.4.27"
+        "@vue/reactivity": "3.4.29",
+        "@vue/shared": "3.4.29"
       }
     },
     "node_modules/@vue/runtime-dom": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz",
-      "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==",
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.29.tgz",
+      "integrity": "sha512-gI10atCrtOLf/2MPPMM+dpz3NGulo9ZZR9d1dWo4fYvm+xkfvRrw1ZmJ7mkWtiJVXSsdmPbcK1p5dZzOCKDN0g==",
       "dependencies": {
-        "@vue/runtime-core": "3.4.27",
-        "@vue/shared": "3.4.27",
+        "@vue/reactivity": "3.4.29",
+        "@vue/runtime-core": "3.4.29",
+        "@vue/shared": "3.4.29",
         "csstype": "^3.1.3"
       }
     },
     "node_modules/@vue/server-renderer": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz",
-      "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==",
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.29.tgz",
+      "integrity": "sha512-HMLCmPI2j/k8PVkSBysrA2RxcxC5DgBiCdj7n7H2QtR8bQQPqKAe8qoaxLcInzouBmzwJ+J0x20ygN/B5mYBng==",
       "dependencies": {
-        "@vue/compiler-ssr": "3.4.27",
-        "@vue/shared": "3.4.27"
+        "@vue/compiler-ssr": "3.4.29",
+        "@vue/shared": "3.4.29"
       },
       "peerDependencies": {
-        "vue": "3.4.27"
+        "vue": "3.4.29"
       }
     },
     "node_modules/@vue/shared": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz",
-      "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA=="
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz",
+      "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA=="
     },
     "node_modules/@vue/tsconfig": {
       "version": "0.5.1",
@@ -2924,9 +2902,9 @@
       "dev": true
     },
     "node_modules/acorn": {
-      "version": "8.11.3",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
-      "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+      "version": "8.12.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
+      "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
       "dev": true,
       "bin": {
         "acorn": "bin/acorn"
@@ -3680,7 +3658,6 @@
       "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
       "dev": true,
       "hasInstallScript": true,
-      "peer": true,
       "bin": {
         "esbuild": "bin/esbuild"
       },
@@ -6057,9 +6034,9 @@
       }
     },
     "node_modules/sass": {
-      "version": "1.77.4",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.4.tgz",
-      "integrity": "sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==",
+      "version": "1.77.5",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.5.tgz",
+      "integrity": "sha512-oDfX1mukIlxacPdQqNb6mV2tVCrnE+P3nVYioy72V5tlk56CPNcO4TCuFcaCRKKfJ1M3lH95CleRS+dVKL2qMg==",
       "dev": true,
       "dependencies": {
         "chokidar": ">=3.0.0 <4.0.0",
@@ -6643,12 +6620,12 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.2.13",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz",
-      "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==",
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz",
+      "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==",
       "dev": true,
       "dependencies": {
-        "esbuild": "^0.20.1",
+        "esbuild": "^0.21.3",
         "postcss": "^8.4.38",
         "rollup": "^4.13.0"
       },
@@ -6697,412 +6674,6 @@
         }
       }
     },
-    "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
-      "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "aix"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/android-arm": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
-      "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/android-arm64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
-      "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/android-x64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
-      "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
-      "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/darwin-x64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
-      "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
-      "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
-      "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-arm": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
-      "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-arm64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
-      "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-ia32": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
-      "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-loong64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
-      "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
-      "cpu": [
-        "loong64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
-      "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
-      "cpu": [
-        "mips64el"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
-      "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
-      "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-s390x": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
-      "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
-      "cpu": [
-        "s390x"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/linux-x64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
-      "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
-      "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
-      "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/sunos-x64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
-      "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "sunos"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/win32-arm64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
-      "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/win32-ia32": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
-      "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/@esbuild/win32-x64": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
-      "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/vite/node_modules/esbuild": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
-      "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
-      "dev": true,
-      "hasInstallScript": true,
-      "bin": {
-        "esbuild": "bin/esbuild"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "optionalDependencies": {
-        "@esbuild/aix-ppc64": "0.20.2",
-        "@esbuild/android-arm": "0.20.2",
-        "@esbuild/android-arm64": "0.20.2",
-        "@esbuild/android-x64": "0.20.2",
-        "@esbuild/darwin-arm64": "0.20.2",
-        "@esbuild/darwin-x64": "0.20.2",
-        "@esbuild/freebsd-arm64": "0.20.2",
-        "@esbuild/freebsd-x64": "0.20.2",
-        "@esbuild/linux-arm": "0.20.2",
-        "@esbuild/linux-arm64": "0.20.2",
-        "@esbuild/linux-ia32": "0.20.2",
-        "@esbuild/linux-loong64": "0.20.2",
-        "@esbuild/linux-mips64el": "0.20.2",
-        "@esbuild/linux-ppc64": "0.20.2",
-        "@esbuild/linux-riscv64": "0.20.2",
-        "@esbuild/linux-s390x": "0.20.2",
-        "@esbuild/linux-x64": "0.20.2",
-        "@esbuild/netbsd-x64": "0.20.2",
-        "@esbuild/openbsd-x64": "0.20.2",
-        "@esbuild/sunos-x64": "0.20.2",
-        "@esbuild/win32-arm64": "0.20.2",
-        "@esbuild/win32-ia32": "0.20.2",
-        "@esbuild/win32-x64": "0.20.2"
-      }
-    },
     "node_modules/vscode-uri": {
       "version": "3.0.8",
       "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
@@ -7110,15 +6681,15 @@
       "dev": true
     },
     "node_modules/vue": {
-      "version": "3.4.27",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz",
-      "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==",
-      "dependencies": {
-        "@vue/compiler-dom": "3.4.27",
-        "@vue/compiler-sfc": "3.4.27",
-        "@vue/runtime-dom": "3.4.27",
-        "@vue/server-renderer": "3.4.27",
-        "@vue/shared": "3.4.27"
+      "version": "3.4.29",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.29.tgz",
+      "integrity": "sha512-8QUYfRcYzNlYuzKPfge1UWC6nF9ym0lx7mpGVPJYNhddxEf3DD0+kU07NTL0sXuiT2HuJuKr/iEO8WvXvT0RSQ==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.29",
+        "@vue/compiler-sfc": "3.4.29",
+        "@vue/runtime-dom": "3.4.29",
+        "@vue/server-renderer": "3.4.29",
+        "@vue/shared": "3.4.29"
       },
       "peerDependencies": {
         "typescript": "*"
diff --git a/package.json b/package.json
index fece0f765c7be49bec6b7c3abae08de28887b33c..1d9479a6f5d65fd4741320d34c7233fe5a4f218d 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
     "rollup-plugin-node-polyfills": "~0.2.1",
     "sass": "^1.66.0",
     "typescript": "~5.4.0",
-    "vite": "~5.2.0",
+    "vite": "~5.3.0",
     "vue-tsc": "~2.0.0"
   }
 }
diff --git a/src/App.vue b/src/App.vue
index d48b90e30e90cbab56645584b3f1e2e547849cf8..f679a7aebb1acd2a24d1ef92ab348399f7b1cf3b 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -26,28 +26,31 @@ const s3KeyRepository = useS3KeyStore();
 
 onBeforeMount(() => {
   OpenAPI.BASE = environment.API_BASE_URL;
+  userRepository.updateJWT(cookies.get("clowm-jwt"));
+  if (userRepository.authenticated) {
+    userRepository.getCurrentUser();
+  }
   axios.interceptors.response.use(
     (res) => res,
     (err) => {
       if (
-        (err.response.status === 400 || err.response.status === 403) &&
-        err.response.data.detail?.includes("JWT")
+        err.response.status === 401 ||
+        (err.response.status === 400 &&
+          err.response.data.detail?.includes("JWT"))
       ) {
         userRepository.logout();
-        cookies.remove("bearer");
         router.push({
           name: "login",
           query: {
             login_error:
-              err.response.status === 400 ? "token_invalid" : "token_expired",
-            next: route.name != "login" ? encodeURI(route.path) : undefined,
+              err.response.status === 401 ? "token_invalid" : "token_expired",
+            next: route.name !== "login" ? encodeURI(route.path) : undefined,
           },
         });
       }
       return Promise.reject(err);
     },
   );
-  userRepository.setToken(cookies.get("bearer"));
   router.afterEach((to, from) => {
     window._paq.push(["setReferrerUrl", from.path]);
     window._paq.push(["deleteCustomVariables", "page"]);
@@ -82,7 +85,7 @@ onBeforeMount(() => {
       return { name: "workflows" };
     } else if (
       to.meta.requiresReviewerRole &&
-      !(userRepository.rewiewer || userRepository.admin)
+      !(userRepository.reviewer || userRepository.admin)
     ) {
       return "/";
     } else if (
diff --git a/src/client/index.ts b/src/client/index.ts
index 4e8c4b36cd6a701bba0ad2f7b46e078f6fcd73e9..98752ac488193a71a44ea94354a30ae46310a955 100644
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@ -8,6 +8,9 @@ export { OpenAPI } from './core/OpenAPI';
 export type { OpenAPIConfig } from './core/OpenAPI';
 
 export type { AnonymizedWorkflowExecution } from './models/AnonymizedWorkflowExecution';
+export type { ApiTokenIn } from './models/ApiTokenIn';
+export type { ApiTokenOut } from './models/ApiTokenOut';
+export type { ApiTokenPrivateOut } from './models/ApiTokenPrivateOut';
 export type { Body_Bucket_update_bucket_public_state } from './models/Body_Bucket_update_bucket_public_state';
 export type { Body_Workflow_Version_upload_workflow_version_icon } from './models/Body_Workflow_Version_upload_workflow_version_icon';
 export type { BucketIn } from './models/BucketIn';
@@ -34,6 +37,7 @@ export type { ResourceVersionOut } from './models/ResourceVersionOut';
 export { ResourceVersionStatus } from './models/ResourceVersionStatus';
 export { RoleEnum } from './models/RoleEnum';
 export type { S3Key } from './models/S3Key';
+export { ScopeEnum } from './models/ScopeEnum';
 export type { UserIn } from './models/UserIn';
 export type { UserOut } from './models/UserOut';
 export type { UserOutExtended } from './models/UserOutExtended';
@@ -57,6 +61,7 @@ export type { WorkflowVersion } from './models/WorkflowVersion';
 export { WorkflowVersionStatus } from './models/WorkflowVersionStatus';
 export type { WorkflowVersionStatusSchema } from './models/WorkflowVersionStatusSchema';
 
+export { ApiTokenService } from './services/ApiTokenService';
 export { AuthService } from './services/AuthService';
 export { BucketService } from './services/BucketService';
 export { BucketPermissionService } from './services/BucketPermissionService';
diff --git a/src/client/models/ApiTokenIn.ts b/src/client/models/ApiTokenIn.ts
new file mode 100644
index 0000000000000000000000000000000000000000..38a68cae7c7cb1a5d59cc3983b88d7442ba74ffc
--- /dev/null
+++ b/src/client/models/ApiTokenIn.ts
@@ -0,0 +1,20 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { ScopeEnum } from './ScopeEnum';
+export type ApiTokenIn = {
+    /**
+     * Short name for the API token
+     */
+    name: string;
+    /**
+     * Unix timestamp when the token should expire
+     */
+    expires_at?: (number | null);
+    /**
+     * List of scopes this Api token has
+     */
+    scopes: Array<ScopeEnum>;
+};
+
diff --git a/src/client/models/ApiTokenOut.ts b/src/client/models/ApiTokenOut.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d10e0620c8c81ce53f3fa550bb7446fef01746ad
--- /dev/null
+++ b/src/client/models/ApiTokenOut.ts
@@ -0,0 +1,32 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { ScopeEnum } from './ScopeEnum';
+export type ApiTokenOut = {
+    /**
+     * Short name for the API token
+     */
+    name: string;
+    /**
+     * Unix timestamp when the token should expire
+     */
+    expires_at?: (number | null);
+    /**
+     * List of scopes this Api token has
+     */
+    scopes: Array<ScopeEnum>;
+    /**
+     * The ID of the token
+     */
+    token_id: string;
+    /**
+     * The ID of the owner
+     */
+    uid: string;
+    /**
+     * The UNIX timestamp when this token was created
+     */
+    created_at: number;
+};
+
diff --git a/src/client/models/ApiTokenPrivateOut.ts b/src/client/models/ApiTokenPrivateOut.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d2d22e9b6b6811d9128805f2f800581d18842eb
--- /dev/null
+++ b/src/client/models/ApiTokenPrivateOut.ts
@@ -0,0 +1,36 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { ScopeEnum } from './ScopeEnum';
+export type ApiTokenPrivateOut = {
+    /**
+     * Short name for the API token
+     */
+    name: string;
+    /**
+     * Unix timestamp when the token should expire
+     */
+    expires_at?: (number | null);
+    /**
+     * List of scopes this Api token has
+     */
+    scopes: Array<ScopeEnum>;
+    /**
+     * The ID of the token
+     */
+    token_id: string;
+    /**
+     * The ID of the owner
+     */
+    uid: string;
+    /**
+     * The UNIX timestamp when this token was created
+     */
+    created_at: number;
+    /**
+     * The actual token used for authentication
+     */
+    token: string;
+};
+
diff --git a/src/client/models/ScopeEnum.ts b/src/client/models/ScopeEnum.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4dc5a355e7933c5ba9bd3959c1b16cb7835c8b61
--- /dev/null
+++ b/src/client/models/ScopeEnum.ts
@@ -0,0 +1,9 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export enum ScopeEnum {
+    USER = 'user',
+    TOKEN = 'token',
+    X = 'x',
+}
diff --git a/src/client/models/UserOutExtended.ts b/src/client/models/UserOutExtended.ts
index 4b0aa20a5c17d903740c1b1cfef598e178b62337..a05ecb616dfa92c76dde11b89fa03407d39cd2dd 100644
--- a/src/client/models/UserOutExtended.ts
+++ b/src/client/models/UserOutExtended.ts
@@ -24,5 +24,9 @@ export type UserOutExtended = {
      * Timestamp when the invitation token was created as UNIX timestamp
      */
     invitation_token_created_at?: (number | null);
+    /**
+     * URL to the gravatar avatar based on the users email
+     */
+    gravatar_url?: (string | null);
 };
 
diff --git a/src/client/services/ApiTokenService.ts b/src/client/services/ApiTokenService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a088864e6bfe05b57cc85b40635d80da8ed11188
--- /dev/null
+++ b/src/client/services/ApiTokenService.ts
@@ -0,0 +1,121 @@
+/* generated using openapi-typescript-codegen -- do not edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { ApiTokenIn } from '../models/ApiTokenIn';
+import type { ApiTokenOut } from '../models/ApiTokenOut';
+import type { ApiTokenPrivateOut } from '../models/ApiTokenPrivateOut';
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+export class ApiTokenService {
+    /**
+     * List API token
+     * List meta information about all API token.
+     *
+     * Permissions `api_token:list_all` required. See parameter `uid` for exception.
+     * @param uid UID of the user to filter for. Permission `api_token:list` required if current users is the target.
+     * @returns ApiTokenOut Successful Response
+     * @throws ApiError
+     */
+    public static apiTokenListToken(
+        uid?: string,
+    ): CancelablePromise<Array<ApiTokenOut>> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/tokens',
+            query: {
+                'uid': uid,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                401: `Not Authenticated`,
+                403: `Not Authorized`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+    /**
+     * Create new API token
+     * Create a new API token for the current user.
+     *
+     * Permission `api_token:create` required.
+     * @param requestBody
+     * @returns ApiTokenPrivateOut Successful Response
+     * @throws ApiError
+     */
+    public static apiTokenCreateToken(
+        requestBody: ApiTokenIn,
+    ): CancelablePromise<ApiTokenPrivateOut> {
+        return __request(OpenAPI, {
+            method: 'POST',
+            url: '/tokens',
+            body: requestBody,
+            mediaType: 'application/json',
+            errors: {
+                400: `Error decoding JWT Token`,
+                401: `Not Authenticated`,
+                403: `Not Authorized`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+    /**
+     * Get API token
+     * Get an API token by id.
+     *
+     * Permission `api_token:read` required if the current user is the owner of the API token,
+     * otherwise `api_token:read_any` required.
+     * @param tid ID of an API token
+     * @returns ApiTokenOut Successful Response
+     * @throws ApiError
+     */
+    public static apiTokenGetToken(
+        tid: string,
+    ): CancelablePromise<ApiTokenOut> {
+        return __request(OpenAPI, {
+            method: 'GET',
+            url: '/tokens/{tid}',
+            path: {
+                'tid': tid,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                401: `Not Authenticated`,
+                403: `Not Authorized`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+    /**
+     * Delete API token
+     * Delete an API token by id.
+     *
+     * Permission `api_token:delete` required if the current user is the owner of the API token,
+     * otherwise `api_token:delete_any` required.
+     * @param tid ID of an API token
+     * @returns void
+     * @throws ApiError
+     */
+    public static apiTokenDeleteToken(
+        tid: string,
+    ): CancelablePromise<void> {
+        return __request(OpenAPI, {
+            method: 'DELETE',
+            url: '/tokens/{tid}',
+            path: {
+                'tid': tid,
+            },
+            errors: {
+                400: `Error decoding JWT Token`,
+                401: `Not Authenticated`,
+                403: `Not Authorized`,
+                404: `Entity not Found`,
+                422: `Validation Error`,
+            },
+        });
+    }
+}
diff --git a/src/client/services/WorkflowCredentialsService.ts b/src/client/services/WorkflowCredentialsService.ts
index 4d3b74cb483295567066b646fbc2e683753c0e6e..01e4e44bec76bf5ee746fa2d9edfab0e5f49a688 100644
--- a/src/client/services/WorkflowCredentialsService.ts
+++ b/src/client/services/WorkflowCredentialsService.ts
@@ -70,7 +70,7 @@ export class WorkflowCredentialsService {
      * Delete the credentials of a workflow
      * Delete the credentials for the repository of a workflow.
      *
-     * Permission `workflow:delete` required if the developer of the workflow is the same as the the current user,
+     * Permission `workflow:delete` required if the developer of the workflow is the same as the current user,
      * other `workflow:delete_any`.
      * @param wid ID of a workflow
      * @returns void
diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue
index e8799490aeb9b59ad9ce9dc4575ab055f0b2012b..4312813b0e10c45c3bd1845a9b7f52ea93c8b66e 100644
--- a/src/components/AppHeader.vue
+++ b/src/components/AppHeader.vue
@@ -1,18 +1,18 @@
 <script setup lang="ts">
 import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
 import { useUserStore } from "@/stores/users";
-import { useRoute } from "vue-router";
+import { useRoute, useRouter } from "vue-router";
 import { watch, ref, computed } from "vue";
 import BootstrapModal from "@/components/modals/BootstrapModal.vue";
 import { OpenAPI } from "@/client";
-import CopyToClipboardIcon from "@/components/CopyToClipboardIcon.vue";
-import dayjs from "dayjs";
 
 const userRepository = useUserStore();
 const route = useRoute();
+const router = useRouter();
 
 function logout() {
   userRepository.logout();
+  router.push({ name: "login" });
 }
 
 const activeRoute = ref("");
@@ -136,7 +136,7 @@ watch(
               <li
                 v-if="
                   userRepository.workflowDev ||
-                  userRepository.rewiewer ||
+                  userRepository.reviewer ||
                   userRepository.admin
                 "
               >
@@ -149,7 +149,7 @@ watch(
                   >My Workflows
                 </router-link>
               </li>
-              <li v-if="userRepository.rewiewer || userRepository.admin">
+              <li v-if="userRepository.reviewer || userRepository.admin">
                 <router-link
                   class="dropdown-item"
                   :to="{ name: 'workflows-reviewer' }"
@@ -183,7 +183,7 @@ watch(
               <li
                 v-if="
                   userRepository.resourceMaintainer ||
-                  userRepository.rewiewer ||
+                  userRepository.reviewer ||
                   userRepository.admin
                 "
               >
@@ -198,7 +198,7 @@ watch(
                   >My Resources
                 </router-link>
               </li>
-              <li v-if="userRepository.rewiewer || userRepository.admin">
+              <li v-if="userRepository.reviewer || userRepository.admin">
                 <router-link
                   class="dropdown-item"
                   :to="{ name: 'resource-review' }"
@@ -265,8 +265,14 @@ watch(
           data-bs-toggle="dropdown"
           aria-expanded="false"
         >
-          <strong class="me-2">{{ userRepository.user?.display_name }}</strong>
-          <font-awesome-icon icon="fa-solid fa-circle-user" class="fs-5" />
+          <strong class="me-2">{{ userRepository.user.display_name }}</strong>
+          <img
+            :src="userRepository.user.gravatar_url + '?d=mp&s=32'"
+            class="rounded-circle"
+            height="32"
+            width="32"
+            alt="profile picture"
+          />
         </a>
         <ul
           class="dropdown-menu text-small shadow"
@@ -333,29 +339,6 @@ watch(
           </tr>
         </tbody>
       </table>
-      <div class="mt-4">
-        <label for="clowm-jwt" class="form-label"
-          >JWT for Services (expires at
-          {{
-            dayjs
-              .unix(userRepository.decodedToken!.exp)
-              .format("DD.MM.YYYY HH:mm")
-          }})</label
-        >
-        <div class="input-group">
-          <input
-            type="text"
-            readonly
-            class="form-control text-truncate"
-            id="clowm-jwt"
-            :value="userRepository.token"
-            aria-describedby="clowm-jwt-copy"
-          />
-          <span class="input-group-text" id="clowm-jwt-copy"
-            ><copy-to-clipboard-icon :text="userRepository.token ?? ''"
-          /></span>
-        </div>
-      </div>
     </template>
     <template v-slot:footer>
       <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
diff --git a/src/stores/users.ts b/src/stores/users.ts
index 259749fe1e00891ec6a8f38397f7680ee29609ba..de3c95da90d614f00428b41ced312af99f1e1ea5 100644
--- a/src/stores/users.ts
+++ b/src/stores/users.ts
@@ -1,6 +1,6 @@
 import { defineStore } from "pinia";
 import type { UserOutExtended, UserOut, UserIn } from "@/client";
-import { OpenAPI, UserService, RoleEnum } from "@/client";
+import { UserService, RoleEnum } from "@/client";
 import { useWorkflowExecutionStore } from "@/stores/workflowExecutions";
 import { useBucketStore } from "@/stores/buckets";
 import { useWorkflowStore } from "@/stores/workflows";
@@ -35,56 +35,64 @@ export const useUserStore = defineStore({
   id: "user",
   state: () =>
     ({
-      token: null,
-      decodedToken: null,
       user: null,
+      token: null,
+      roles: [],
     }) as {
-      token: string | null;
-      decodedToken: DecodedToken | null;
       user: UserOutExtended | null;
+      token: DecodedToken | null;
+      roles: RoleEnum[];
     },
   getters: {
     roles(): string[] {
-      return this.user?.roles?.map((role) => role.toString()) ?? [];
+      return (
+        this.user?.roles?.map((role) => role.toString()) ??
+        JSON.parse(localStorage.getItem("roles") ?? "[]")
+      );
+    },
+    authenticated(): boolean {
+      return this.token != null || this.user != null;
     },
-    authenticated: (state) => state.token != null,
     currentUID(): string {
-      return this.user?.uid ?? this.decodedToken?.["sub"] ?? "";
-    },
-    foreignUser: (state) => (state.user?.roles ?? []).length == 0,
-    normalUser: (state) => state.user?.roles.includes(RoleEnum.USER) ?? false,
-    rewiewer: (state) => state.user?.roles.includes(RoleEnum.REVIEWER) ?? false,
-    workflowDev: (state) =>
-      state.user?.roles.includes(RoleEnum.DEVELOPER) ?? false,
-    resourceMaintainer: (state) =>
-      state.user?.roles.includes(RoleEnum.DB_MAINTAINER) ?? false,
-    admin: (state) =>
-      state.user?.roles.includes(RoleEnum.ADMINISTRATOR) ?? false,
+      return this.user?.uid ?? this.token?.sub ?? "";
+    },
+    foreignUser(): boolean {
+      return this.roles.length === 0;
+    },
+    normalUser(): boolean {
+      return this.roles.includes(RoleEnum.USER);
+    },
+    reviewer(): boolean {
+      return this.roles.includes(RoleEnum.REVIEWER);
+    },
+    workflowDev(): boolean {
+      return this.roles.includes(RoleEnum.DEVELOPER);
+    },
+    resourceMaintainer(): boolean {
+      return this.roles.includes(RoleEnum.DB_MAINTAINER);
+    },
+    admin(): boolean {
+      return this.roles.includes(RoleEnum.ADMINISTRATOR);
+    },
   },
   actions: {
-    setToken(token: string | null) {
-      if (token != null) {
-        this.token = token;
-        this.decodedToken = parseJwt(token);
-        OpenAPI.TOKEN = token;
-        UserService.userGetLoggedInUser()
-          .then((user) => {
-            this.updateUser(user);
-          })
-          .catch(() => {
-            this.token = null;
-          });
-      } else {
-        this.logout();
-      }
+    getCurrentUser(): Promise<UserOutExtended> {
+      return UserService.userGetLoggedInUser().then((user) => {
+        this.updateUser(user);
+        return user;
+      });
+    },
+    updateJWT(jwt: string | null) {
+      this.token = jwt != null ? parseJwt(jwt) : null;
     },
     updateUser(user: UserOutExtended) {
       this.user = user;
+      localStorage.setItem("currentUser", user.uid);
+      localStorage.setItem("roles", JSON.stringify(user.roles));
       useNameStore().addNameToMapping(user.uid, user.display_name);
     },
     logout() {
       window._paq.push(["resetUserId"]);
-      OpenAPI.TOKEN = undefined;
       this.$reset();
       localStorage.clear();
       dbclear();
diff --git a/src/views/workflows/WorkflowView.vue b/src/views/workflows/WorkflowView.vue
index f99d1d2d2741ca39d5bda18b3e8b4c6fded9e789..f0c7afda308dd9f4482e4180b9babe66b9f8469c 100644
--- a/src/views/workflows/WorkflowView.vue
+++ b/src/views/workflows/WorkflowView.vue
@@ -98,7 +98,7 @@ const gitIcon = computed<string>(() =>
 
 const allowVersionDeprecation = computed<boolean>(() => {
   if (activeVersion.value?.status === WorkflowVersionStatus.PUBLISHED) {
-    if (userRepository.rewiewer || userRepository.admin) {
+    if (userRepository.reviewer || userRepository.admin) {
       return true;
     } else if (
       userRepository.workflowDev &&