Login nur fuer Mitarbeiter (Label), Kunden-Konten werden abgewiesen

Kunden haben Konten im selben Appwrite-Projekt und konnten sich mit ihrem
Portal-Passwort im Ticketsystem anmelden; dabei entstand sogar automatisch ein
employees-Eintrag.

- lib/staffAccess.js: Zugang nur mit Label "mitarbeiter" oder "admin"
- AuthContext: Konto ohne Label -> Sitzung sofort beenden, Hinweis aufs
  Kundenportal (Login, Neuladen, nach dem 2FA-Code); employees-Eintrag nur
  noch fuer Mitarbeiter
- setup-legal.mjs: Rechte nur fuer die Labels (ein erneuter Lauf oeffnet
  nichts mehr fuer "users")

Die eigentliche Sperre sind die Appwrite-Rechte (Kundenportal
scripts/mitarbeiter-zugang.mjs --rechte).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-03 10:54:16 +00:00
1 parent 65e9931f2c
commit 78bc99a73b
5 files changed
+87 -6

No files matched your search

+25 -2
View File
@@ -11,6 +11,7 @@ import {
client,
} from '../lib/appwrite'
import { createMfaApi, isMoreFactorsError, maskEmail, mfaErrorMessage } from '../lib/mfa'
import { isStaffAccount, NO_STAFF_ACCESS } from '../lib/staffAccess'
const AuthContext = createContext()
const mfa = createMfaApi(client)
@@ -52,8 +53,16 @@ async function createSessionWithCleanup(email, password) {
}
}
// Hilfsfunktion: Fügt User automatisch zur employees Collection hinzu und liefert
// den Mitarbeiter-Eintrag (Kürzel = Gitea-Name, siehe isWorksheetCreator) oder null
// Konto ohne Mitarbeiter-Label (z. B. Kunde mit Portal-Passwort): Sitzung sofort beenden
async function endSession() {
try {
await account.deleteSession('current')
} catch { /* Sitzung ggf. schon weg */ }
clearAllLocalAppwriteSessions()
}
// Hilfsfunktion: Fügt einen Mitarbeiter (nur nach isStaffAccount) automatisch zur employees Collection hinzu und
// liefert den Mitarbeiter-Eintrag (Kürzel = Gitea-Name, siehe isWorksheetCreator) oder null
async function ensureEmployeeExists(user) {
if (!user) return null
@@ -109,6 +118,11 @@ export function AuthProvider({ children }) {
try {
const session = await account.get()
if (!isStaffAccount(session)) {
await endSession()
setUser(null)
return
}
setUser(session)
setEmployee(await ensureEmployeeExists(session))
} catch (error) {
@@ -147,6 +161,10 @@ export function AuthProvider({ children }) {
const sent = await sendCode()
return { success: false, mfaRequired: true, error: sent.error }
}
if (!isStaffAccount(session)) {
await endSession()
return { success: false, error: NO_STAFF_ACCESS }
}
setUser(session)
setEmployee(await ensureEmployeeExists(session))
@@ -191,6 +209,11 @@ export function AuthProvider({ children }) {
try {
await mfa.completeChallenge(mfaPending.challengeId, String(otp || '').trim())
const session = await account.get()
if (!isStaffAccount(session)) {
await endSession()
setMfaPending(null)
return { success: false, error: NO_STAFF_ACCESS }
}
setUser(session)
setEmployee(await ensureEmployeeExists(session))
setMfaPending(null)
+21
View File
@@ -0,0 +1,21 @@
/**
* Zugang zum Ticketsystem nur fuer Mitarbeiter: Appwrite-Label "mitarbeiter" oder "admin". Kunden haben Konten
* im selben Appwrite-Projekt (Kundenportal) und konnten sich mit ihrem Portal-Passwort hier anmelden. Labels
* setzt nur der Server-Key; dieselben Labels tragen die Rechte der Collections und Buckets
* (Kundenportal scripts/mitarbeiter-zugang.mjs) und der Team-Eingang des Portals.
* Bewusst ohne Import von appwrite.js, damit Node dieses Modul in den Tests laden kann.
*/
export const STAFF_LABELS = ['mitarbeiter', 'admin']
export const NO_STAFF_ACCESS =
'Dieses Konto hat keinen Zugang zum Ticketsystem. Kunden melden sich unter project.webklar.com an.'
export function isStaffAccount(account) {
const labels = Array.isArray(account?.labels) ? account.labels : []
return STAFF_LABELS.some((label) => labels.includes(label))
}
/** Appwrite-Rechte nur fuer Mitarbeiter, z. B. staffPermissions('read', 'create') */
export function staffPermissions(...actions) {
return actions.flatMap((action) => STAFF_LABELS.map((label) => `${action}("label:${label}")`))
}
+4 -1
View File
@@ -3,6 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
import LetterGlitch from '../components/LetterGlitch'
import { safeReturnPath } from '../lib/returnPath'
import { NO_STAFF_ACCESS } from '../lib/staffAccess'
export default function LoginPage() {
const [email, setEmail] = useState('')
@@ -33,7 +34,9 @@ export default function LoginPage() {
// Bessere Fehlermeldungen
let errorMessage = result.error || 'Login fehlgeschlagen'
if (errorMessage.includes('Invalid credentials') || errorMessage.includes('401')) {
if (errorMessage === NO_STAFF_ACCESS) {
// Kunden-Konto: Hinweis unveraendert anzeigen
} else if (errorMessage.includes('Invalid credentials') || errorMessage.includes('401')) {
errorMessage = 'Ungültige Email oder Passwort. Bitte überprüfe deine Eingaben.'
} else if (errorMessage.includes('Email/Password') || errorMessage.includes('auth')) {
errorMessage = 'Email/Password Authentifizierung ist möglicherweise nicht aktiviert. Bitte überprüfe deine Appwrite-Konfiguration.'