v1.1: Rebrand Hinterstein Immobilien + echtes Auth-System

- Rebrand: Kunden-Logo, Joseph-Hinterstein-Portrait, Kontakt-Banner, Texte
- Login-Pflicht auf /login (Route-Guard), Gruppen Verwaltung/Mieter
- Mieter-Registrierung per einmaligem Registrierungscode
- Benutzerverwaltung als 6. Modul (/benutzer, nur Verwaltung)
- Demo-Zugänge mit Passwort 0815 (j.hinterstein, a.berger, m.wolf)
- Mail-Server-API: Token-Auth (/send, X-Api-Token), Mail-Stack live auf Server
- Store-Key hinterstein-mieterportal-v2, Doku (kimi.md, README) nachgeführt
This commit is contained in:
2026-10-02 12:11:43 +02:00
parent ccfe08ad24
commit fbe033ebd6
22 changed files with 1067 additions and 179 deletions
+56
View File
@@ -0,0 +1,56 @@
/** Authentifizierung + Session, Muster aus dem Schulportal. */
import type { Benutzer, BenutzerRolle } from '../types'
export const SESSION_KEY = 'mp_benutzer_session'
export const SESSION_DAUER_MS = 8 * 60 * 60 * 1000 // 8 h
export const BENUTZER_CHANGED_EVENT = 'mp-benutzer-changed'
/** Demo-Passwort aller Demo-Benutzer (wie Schulportal sha256("0815")). */
export const DEMO_PASSWORT = '0815'
export async function sha256(text: string): Promise<string> {
const daten = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))
return Array.from(new Uint8Array(daten))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
}
export interface Session {
benutzerId: string
rolle: BenutzerRolle
bis: number
}
export function ladeSession(): Session | null {
try {
const raw = localStorage.getItem(SESSION_KEY)
if (!raw) return null
const s = JSON.parse(raw) as Session
if (s.bis < Date.now()) {
localStorage.removeItem(SESSION_KEY)
return null
}
return s
} catch {
return null
}
}
export function speichereSession(benutzerId: string, rolle: BenutzerRolle) {
const s: Session = { benutzerId, rolle, bis: Date.now() + SESSION_DAUER_MS }
localStorage.setItem(SESSION_KEY, JSON.stringify(s))
window.dispatchEvent(new Event(BENUTZER_CHANGED_EVENT))
}
export function beendeSession() {
localStorage.removeItem(SESSION_KEY)
window.dispatchEvent(new Event(BENUTZER_CHANGED_EVENT))
}
export function aktuellerBenutzer(alle: Benutzer[]): Benutzer | null {
const s = ladeSession()
if (!s) return null
const b = alle.find((x) => x.id === s.benutzerId && x.aktiv)
return b ?? null
}