Add frontend auth, dashboard & router guards

Introduce a complete frontend auth flow and protected dashboard.

- Add auth session module (GUI/src/services/authSession.ts) with fetchCurrentUser, login, logout, caching and structured errors.
- Add Dashboard page (GUI/src/routes/dashboard/Dashboard.vue) and a protected Dashboard route (meta.requiresAuth) at '/'.
- Move public landing page to /welcome and mark it Visibility.Unauthenticated; update 404 and Impressum links.
- Implement router guard (GUI/src/router/index.ts) to redirect unauthenticated users to Login and prevent logged-in users from accessing guest-only pages.
- Update routes layout (GUI/src/plugins/routesLayout.ts) to include authenticated/unauthenticated visibility and dashboard entry.
- Update Layout.vue to track current user, show username/menu, conditionally render sidebar items, add logout flow and error snackbar, and insert visual divider before auth-only items.
- Convert Login.vue into a working login form with loading state, error handling and redirect after success.
- Update codexInfo.md to document the new auth features and related UI/route changes.
This commit is contained in:
Jonas
2026-04-18 22:42:17 +02:00
parent 38ec3741ab
commit 86ed227566
9 changed files with 759 additions and 53 deletions
+89 -17
View File
@@ -1,7 +1,56 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { AuthRequestError, login } from '@/services/authSession'
const showPassword = ref(false)
const userName = ref('')
const password = ref('')
const isSubmitting = ref(false)
const errorMessage = ref('')
const route = useRoute()
const router = useRouter()
const submitDisabled = computed(
() => isSubmitting.value || userName.value.trim().length === 0 || password.value.length === 0,
)
const redirectPath = computed(() => {
const redirectQuery = route.query.redirect
if (typeof redirectQuery === 'string' && redirectQuery.startsWith('/')) {
return redirectQuery
}
return '/'
})
async function handleSubmit() {
if (submitDisabled.value) {
errorMessage.value = 'Bitte Benutzername und Passwort eingeben.'
return
}
isSubmitting.value = true
errorMessage.value = ''
try {
await login({
userName: userName.value.trim(),
password: password.value,
})
await router.replace(redirectPath.value)
} catch (error) {
if (error instanceof AuthRequestError) {
errorMessage.value = error.message
} else {
errorMessage.value = 'Anmeldung fehlgeschlagen. Bitte versuche es erneut.'
}
} finally {
isSubmitting.value = false
}
}
</script>
<template>
@@ -30,22 +79,35 @@ const showPassword = ref(false)
</ul>
</aside>
<v-form class="login-form hoard-panel" @submit.prevent>
<v-form class="login-form hoard-panel" @submit.prevent="handleSubmit">
<div class="form-head">
<h2>Login</h2>
<p>Melde dich mit deinem bestehenden Konto an.</p>
</div>
<v-alert
v-if="errorMessage"
type="error"
variant="tonal"
density="comfortable"
border="start"
>
{{ errorMessage }}
</v-alert>
<v-text-field
label="E-Mail"
type="email"
v-model="userName"
label="Benutzername"
type="text"
variant="outlined"
prepend-inner-icon="mdi-email-outline"
autocomplete="email"
prepend-inner-icon="mdi-account-outline"
autocomplete="username"
required
:disabled="isSubmitting"
/>
<v-text-field
v-model="password"
label="Passwort"
:type="showPassword ? 'text' : 'password'"
variant="outlined"
@@ -53,17 +115,27 @@ const showPassword = ref(false)
:append-inner-icon="showPassword ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
autocomplete="current-password"
required
:disabled="isSubmitting"
@click:append-inner="showPassword = !showPassword"
/>
<div class="form-meta">
<v-checkbox hide-details color="primary" density="compact" label="Angemeldet bleiben" />
<v-btn variant="text" size="small">Passwort vergessen?</v-btn>
<p>Anmeldung erfolgt per sicherem Session-Cookie.</p>
</div>
<v-btn type="submit" color="primary" block size="large" prepend-icon="mdi-login">Anmelden</v-btn>
<v-btn
type="submit"
color="primary"
block
size="large"
prepend-icon="mdi-login"
:loading="isSubmitting"
:disabled="submitDisabled"
>
Anmelden
</v-btn>
<v-btn variant="outlined" block to="/" prepend-icon="mdi-home">Zur Startseite</v-btn>
<v-btn variant="outlined" block to="/welcome" prepend-icon="mdi-home">Zur Startseite</v-btn>
</v-form>
</section>
</v-container>
@@ -132,8 +204,13 @@ h1 {
.form-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
gap: var(--space-2);
}
.form-meta p {
margin: 0;
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}
@media (width <= 960px) {
@@ -179,11 +256,6 @@ h1 {
gap: var(--space-2);
}
:deep(.form-meta .v-btn) {
width: 100%;
min-height: 44px;
}
:deep(.login-form .v-btn) {
min-height: 44px;
}