Add admin user management and password-change flow
Introduce full admin user listing/detail endpoints and a forced password-change flow. Backend: make CurrentUserResponse.UserName nullable and add ToCurrentUserResponseAsync extension; AppUserController now exposes GET /auth/user (list) and GET /auth/user/{id} (detail) using UserManager and Admin-only policy; AuthController uses the new mapper and after successful password change clears MustChangePassword, updates UpdatedAt and persists changes (with error handling) before updating security stamp. Frontend: add admin users pages (list + detail), ChangePassword page and route, adminUsers and enhanced authSession services (typed responses, changePassword API, error mapping), router guard to redirect users with mustChangePassword=true to the change-password flow, and show success banner on login after password change. UI tweaks: separate admin section in sidebar, add password-change entries in account menu, footer sizing fixes, and various layout/UX improvements. These changes enable admin account management and enforce secure password updates across the app.
This commit is contained in:
+50
-6
@@ -8,6 +8,7 @@ import iconImage from '@/assets/images/icon.svg'
|
||||
import { Visibility, routes } from '@/plugins/routesLayout'
|
||||
import {
|
||||
AuthRequestError,
|
||||
ROLE_ADMIN,
|
||||
fetchCurrentUser,
|
||||
hasRole,
|
||||
logout,
|
||||
@@ -97,8 +98,18 @@ const sidebarRoutes = computed(() =>
|
||||
)
|
||||
}),
|
||||
)
|
||||
const firstAuthenticatedSidebarIndex = computed(() =>
|
||||
sidebarRoutes.value.findIndex((item) => item.visible === Visibility.Authenticated),
|
||||
const adminSidebarRoutes = computed(() =>
|
||||
sidebarRoutes.value.filter(
|
||||
(item) =>
|
||||
item.visible === Visibility.Authorized &&
|
||||
Array.isArray(item.requiredRoles) &&
|
||||
item.requiredRoles.some((role) => role.trim().toLowerCase() === ROLE_ADMIN),
|
||||
),
|
||||
)
|
||||
const primarySidebarRoutes = computed(() =>
|
||||
sidebarRoutes.value.filter(
|
||||
(item) => !adminSidebarRoutes.value.some((adminItem) => adminItem.path === item.path),
|
||||
),
|
||||
)
|
||||
const footerRoutes = computed(() => routes.filter((x) => x.visible === Visibility.Footer))
|
||||
|
||||
@@ -290,6 +301,12 @@ watch(
|
||||
</template>
|
||||
|
||||
<v-list density="compact" class="account-menu-list">
|
||||
<v-list-item
|
||||
prepend-icon="mdi-lock-reset"
|
||||
title="Passwort ändern"
|
||||
:to="{ name: 'ChangePassword' }"
|
||||
/>
|
||||
<v-divider />
|
||||
<v-list-item
|
||||
prepend-icon="mdi-logout"
|
||||
:title="isLoggingOut ? 'Abmelden...' : 'Abmelden'"
|
||||
@@ -316,6 +333,11 @@ watch(
|
||||
title="Zum Dashboard"
|
||||
:to="{ name: 'Dashboard' }"
|
||||
/>
|
||||
<v-list-item
|
||||
prepend-icon="mdi-lock-reset"
|
||||
title="Passwort ändern"
|
||||
:to="{ name: 'ChangePassword' }"
|
||||
/>
|
||||
<v-divider />
|
||||
<v-list-item
|
||||
prepend-icon="mdi-logout"
|
||||
@@ -363,13 +385,27 @@ watch(
|
||||
</div>
|
||||
|
||||
<v-list nav :density="display.mobile.value ? 'default' : 'comfortable'" class="px-1">
|
||||
<template v-for="(item, index) in sidebarRoutes" :key="item.path">
|
||||
<v-divider
|
||||
v-if="firstAuthenticatedSidebarIndex > 0 && index === firstAuthenticatedSidebarIndex"
|
||||
class="my-2 mx-2"
|
||||
<template v-for="item in primarySidebarRoutes" :key="item.path">
|
||||
<v-list-item
|
||||
:to="item.path"
|
||||
:active="route.path === item.path"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
class="hoard-nav-item"
|
||||
rounded="lg"
|
||||
link
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="adminSidebarRoutes.length > 0">
|
||||
<v-divider class="my-2 mx-2" />
|
||||
<div class="drawer-section-head">
|
||||
<p class="drawer-kicker hoard-kicker hoard-kicker--xs">Admin</p>
|
||||
</div>
|
||||
|
||||
<v-list-item
|
||||
v-for="item in adminSidebarRoutes"
|
||||
:key="item.path"
|
||||
:to="item.path"
|
||||
:active="route.path === item.path"
|
||||
:prepend-icon="item.icon"
|
||||
@@ -586,6 +622,10 @@ watch(
|
||||
border-top: 1px solid color-mix(in srgb, var(--color-border) 90%, white 10%);
|
||||
}
|
||||
|
||||
.drawer-section-head {
|
||||
padding: var(--space-2) var(--space-3) var(--space-1);
|
||||
}
|
||||
|
||||
:deep(.hoard-nav-item) {
|
||||
min-height: 44px;
|
||||
}
|
||||
@@ -606,6 +646,10 @@ watch(
|
||||
}
|
||||
|
||||
.hoard-footer {
|
||||
flex: 0 0 auto;
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
max-height: 88px;
|
||||
padding-inline: var(--space-6);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user