Portal-Login: Mitarbeiter kommen ins Admin-Panel, Admin-Label bleibt WOMS-Rolle

Bisher liess /api/auth/login nur Appwrite-User mit dem Label "admin" in die
Admin-Ansicht; alle anderen fielen in den Kunden-Login und bekamen "Kein
Kundenkonto fuer diesen Login gefunden". Mitarbeiter ohne Admin-Haken (z.B.
nico@webklar.com) kamen damit nicht auf /admin.html.

Jetzt gilt: Wer in der employees-Collection steht, ist Backoffice-Nutzer. Das
Label "admin" ist nur noch die WOMS-Adminrolle (Mitarbeiterverwaltung) und wird
als isAdmin-Flag in der Portal-Session bzw. in den Preview-Token-Labels
mitgefuehrt - inklusive "Als Kunde ansehen" und zurueck.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-11 18:26:59 +00:00
parent ac264dc9ba
commit 87aa7a7821
3 changed files with 69 additions and 14 deletions

View File

@@ -4,6 +4,8 @@ import {
getCustomerByAppwriteUserId, getCustomerByAppwriteUserId,
getCustomerByEmail, getCustomerByEmail,
getDocument, getDocument,
getEmployeeByEmail,
getEmployeeByUserId,
getPortalAccessByCustomerId, getPortalAccessByCustomerId,
getUserById, getUserById,
listDocuments, listDocuments,
@@ -40,6 +42,32 @@ function sanitizeCustomer(customer) {
} }
} }
/**
* Backoffice-Login: Jeder Mitarbeiter aus der employees-Collection darf ins
* Admin-Panel (/admin.html). Das Appwrite-Label "admin" ist ausschliesslich die
* WOMS-Adminrolle (Mitarbeiterverwaltung) und KEINE Voraussetzung fuer den
* Portalzugang - es wird nur als Flag in der Session mitgefuehrt.
* Gibt null zurueck, wenn der Login kein Mitarbeiter ist (dann greift der
* normale Kunden-Login).
*/
async function resolveStaffAccount(appwriteUserId, email) {
const account = await getUserById(appwriteUserId).catch(() => null)
const isAdmin = Array.isArray(account?.labels) && account.labels.includes('admin')
let employee = await getEmployeeByUserId(appwriteUserId).catch(() => null)
if (!employee) {
employee = await getEmployeeByEmail(account?.email || email).catch(() => null)
}
if (!employee && !isAdmin) return null
return {
isAdmin,
name: employee?.displayName || account?.name || 'Mitarbeiter',
email: account?.email || employee?.email || email,
}
}
async function validatePortalAccess(appwriteUserId, email) { async function validatePortalAccess(appwriteUserId, email) {
let customer = await getCustomerByAppwriteUserId(appwriteUserId) let customer = await getCustomerByAppwriteUserId(appwriteUserId)
if (!customer && email) { if (!customer && email) {
@@ -149,28 +177,31 @@ router.post('/login', async (req, res) => {
try { try {
const user = await loginWithAppwrite(email.trim(), password) const user = await loginWithAppwrite(email.trim(), password)
// Ticketsystem-Admins (Appwrite-Label "admin") bekommen die Admin-Ansicht // Ticketsystem-Mitarbeiter (employees-Collection) bekommen die Admin-Ansicht
const account = await getUserById(user.$id).catch(() => null) const staff = await resolveStaffAccount(user.$id, email.trim())
if (Array.isArray(account?.labels) && account.labels.includes('admin')) { if (staff) {
const adminName = account.name || 'Admin'
const adminEmail = account.email || email.trim()
setPortalSession(res, { setPortalSession(res, {
role: 'admin', role: 'admin',
isAdmin: staff.isAdmin,
appwriteUserId: user.$id, appwriteUserId: user.$id,
name: adminName, name: staff.name,
email: adminEmail, email: staff.email,
}) })
const adminPreviewToken = await createPreviewSessionToken({ const staffPreviewToken = await createPreviewSessionToken({
userId: user.$id, userId: user.$id,
customerId: '', customerId: '',
email: adminEmail, email: staff.email,
name: adminName, name: staff.name,
role: 'admin', role: 'admin',
labels: ['admin'], labels: staff.isAdmin ? ['admin'] : [],
authSource: 'kundenbereich-admin', authSource: 'kundenbereich-admin',
}) })
res.cookie(PREVIEW_COOKIE_NAME, adminPreviewToken, previewSessionCookieOptions()) res.cookie(PREVIEW_COOKIE_NAME, staffPreviewToken, previewSessionCookieOptions())
return res.json({ success: true, role: 'admin', admin: { name: adminName, email: adminEmail } }) return res.json({
success: true,
role: 'admin',
admin: { name: staff.name, email: staff.email, isAdmin: staff.isAdmin },
})
} }
let { customer, portalAccess } = await validatePortalAccess(user.$id, email.trim()) let { customer, portalAccess } = await validatePortalAccess(user.$id, email.trim())
@@ -324,6 +355,9 @@ router.get('/me', async (req, res) => {
// Admin-Session (Ticketsystem-Mitarbeiter), optional im "Als Kunde ansehen"-Modus // Admin-Session (Ticketsystem-Mitarbeiter), optional im "Als Kunde ansehen"-Modus
if (session.role === 'admin' && session.appwriteUserId) { if (session.role === 'admin' && session.appwriteUserId) {
// Sessions von vor der Mitarbeiter-Freigabe kennen das Flag nicht - die
// konnten nur Admins bekommen, also als Admin werten.
const isAdmin = session.isAdmin === undefined ? true : Boolean(session.isAdmin)
let customer = null let customer = null
if (session.customerId) { if (session.customerId) {
const doc = await getDocument(config.collections.customers, session.customerId).catch(() => null) const doc = await getDocument(config.collections.customers, session.customerId).catch(() => null)
@@ -335,7 +369,7 @@ router.get('/me', async (req, res) => {
email: session.adminEmail || session.email || '', email: session.adminEmail || session.email || '',
name: session.adminName || session.name || 'Admin', name: session.adminName || session.name || 'Admin',
role: 'admin', role: 'admin',
labels: ['admin'], labels: isAdmin ? ['admin'] : [],
authSource: 'kundenbereich-admin', authSource: 'kundenbereich-admin',
}) })
return res.json({ return res.json({
@@ -345,6 +379,7 @@ router.get('/me', async (req, res) => {
admin: { admin: {
name: session.adminName || session.name || 'Admin', name: session.adminName || session.name || 'Admin',
email: session.adminEmail || session.email || '', email: session.adminEmail || session.email || '',
isAdmin,
}, },
customer, customer,
}) })

View File

@@ -408,6 +408,7 @@ router.post('/view-as/:customerId', async (req, res) => {
setPortalSession(res, { setPortalSession(res, {
role: 'admin', role: 'admin',
viewAs: true, viewAs: true,
isAdmin: session.isAdmin,
appwriteUserId: session.appwriteUserId, appwriteUserId: session.appwriteUserId,
adminName: session.adminName || session.name || 'Admin', adminName: session.adminName || session.name || 'Admin',
adminEmail: session.adminEmail || session.email || '', adminEmail: session.adminEmail || session.email || '',
@@ -425,6 +426,7 @@ router.post('/exit-view-as', (req, res) => {
const session = parsePortalSession(req) || req.session const session = parsePortalSession(req) || req.session
setPortalSession(res, { setPortalSession(res, {
role: 'admin', role: 'admin',
isAdmin: session.isAdmin,
appwriteUserId: session.appwriteUserId, appwriteUserId: session.appwriteUserId,
name: session.adminName || session.name || 'Admin', name: session.adminName || session.name || 'Admin',
email: session.adminEmail || session.email || '', email: session.adminEmail || session.email || '',

View File

@@ -162,6 +162,24 @@ export async function getCustomerByEmail(email) {
return docs[0] || null return docs[0] || null
} }
export async function getEmployeeByUserId(userId) {
if (!userId) return null
const docs = await listDocuments(config.collections.employees, [
Query.equal('userId', userId),
Query.limit(1),
])
return docs[0] || null
}
export async function getEmployeeByEmail(email) {
if (!email) return null
const docs = await listDocuments(config.collections.employees, [
Query.equal('email', email.trim().toLowerCase()),
Query.limit(1),
])
return docs[0] || null
}
export async function getPortalAccessByCustomerId(customerId) { export async function getPortalAccessByCustomerId(customerId) {
const docs = await listDocuments(config.collections.customerPortalAccess, [ const docs = await listDocuments(config.collections.customerPortalAccess, [
Query.equal('customerId', customerId), Query.equal('customerId', customerId),