diff --git a/server/routes/auth.js b/server/routes/auth.js index aef0ce4..31a5f0a 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -2,6 +2,7 @@ import { Router } from 'express' import { config } from '../config.js' import { getCustomerByAppwriteUserId, + getCustomerByEmail, getPortalAccessByCustomerId, updateDocument, } from '../services/appwriteAdmin.js' @@ -28,10 +29,15 @@ function sanitizeCustomer(customer) { } } -async function validatePortalAccess(appwriteUserId) { - const customer = await getCustomerByAppwriteUserId(appwriteUserId) +async function validatePortalAccess(appwriteUserId, email) { + let customer = await getCustomerByAppwriteUserId(appwriteUserId) + if (!customer && email) { + customer = await getCustomerByEmail(email) + } if (!customer) { - const error = new Error('Kein Kundenkonto für diesen Login gefunden.') + const error = new Error( + `Kein Kundenkonto für diesen Login gefunden. Im Ticketsystem customers.appwriteUserId auf "${appwriteUserId}" setzen (E-Mail: ${email}).` + ) error.status = 403 throw error } @@ -59,23 +65,6 @@ async function validatePortalAccess(appwriteUserId) { return { customer, portalAccess } } -const DEBUG_LOG = (location, message, data, hypothesisId) => { - // #region agent log - fetch('http://127.0.0.1:7281/ingest/30e8e71c-b377-4e72-84f9-593826c6d234', { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-Debug-Session-Id': '80bbfc' }, - body: JSON.stringify({ - sessionId: '80bbfc', - location, - message, - data, - hypothesisId, - timestamp: Date.now(), - }), - }).catch(() => {}) - // #endregion -} - router.post('/login', async (req, res) => { const { email, password } = req.body || {} if (!email || !password) { @@ -84,12 +73,7 @@ router.post('/login', async (req, res) => { try { const user = await loginWithAppwrite(email.trim(), password) - DEBUG_LOG('auth.js:login', 'appwrite user ok', { userId: user.$id }, 'H3') - const { customer, portalAccess } = await validatePortalAccess(user.$id) - DEBUG_LOG('auth.js:login', 'portal validation ok', { - customerId: customer.$id, - portalAccessEnabled: Boolean(customer.portalAccessEnabled), - }, 'H4') + const { customer, portalAccess } = await validatePortalAccess(user.$id, email.trim()) setPortalSession(res, { customerId: customer.$id, @@ -109,10 +93,12 @@ router.post('/login', async (req, res) => { return res.json({ success: true, customer: sanitizeCustomer(customer) }) } catch (err) { const status = err.status || 500 - DEBUG_LOG('auth.js:login', 'login failed', { - status, - message: err?.message?.slice(0, 120), - }, status === 403 ? 'H4' : status === 401 ? 'H1' : 'H5') + if (err?.message?.includes('not authorized')) { + return res.status(500).json({ + error: + 'Server-Konfiguration: APPWRITE_API_KEY benötigt databases.read für woms-database (customers, customerPortalAccess).', + }) + } return res.status(status).json({ error: err.message || 'Anmeldung fehlgeschlagen' }) } }) diff --git a/server/services/appwriteAdmin.js b/server/services/appwriteAdmin.js index 8c024d0..1088a6b 100644 --- a/server/services/appwriteAdmin.js +++ b/server/services/appwriteAdmin.js @@ -95,6 +95,15 @@ export async function getCustomerByAppwriteUserId(appwriteUserId) { return docs[0] || null } +export async function getCustomerByEmail(email) { + if (!email) return null + const docs = await listDocuments(config.collections.customers, [ + Query.equal('email', email.trim()), + Query.limit(1), + ]) + return docs[0] || null +} + export async function getPortalAccessByCustomerId(customerId) { const docs = await listDocuments(config.collections.customerPortalAccess, [ Query.equal('customerId', customerId), @@ -133,7 +142,6 @@ export async function upsertWebsiteProjectByRepo(repoFullName, data) { }) } -/** @deprecated Nur für Kompatibilität – nutzt native fetch */ export function createAdminClient() { return { usesNativeFetch: true, databaseId: WOMS_DATABASE_ID } } diff --git a/server/services/appwriteClient.js b/server/services/appwriteClient.js index 933fbe8..93fe9fc 100644 --- a/server/services/appwriteClient.js +++ b/server/services/appwriteClient.js @@ -56,7 +56,7 @@ async function appwriteFetch(path, { method = 'GET', body } = {}) { /** * Appwrite Auth per native fetch (Node 26 + node-appwrite-Agent ist inkompatibel). - * Session.secret wird serverseitig oft nicht zurückgegeben – userId aus Session nutzen. + * session.secret fehlt serverseitig oft – userId aus Session + Users-API. */ export async function loginWithAppwrite(email, password) { let session @@ -66,7 +66,6 @@ export async function loginWithAppwrite(email, password) { body: { email, password }, }) DEBUG_LOG('appwriteClient.js:session', 'createEmailPasswordSession ok', { - hasSecret: Boolean(session?.secret), hasUserId: Boolean(session?.userId), sessionId: session?.$id || null, }, 'H6') @@ -81,9 +80,6 @@ export async function loginWithAppwrite(email, password) { } if (!session?.userId) { - DEBUG_LOG('appwriteClient.js:session', 'no userId in session', { - sessionKeys: session ? Object.keys(session).filter((k) => !k.startsWith('provider')) : [], - }, 'H6') const error = new Error('Appwrite-Session ohne userId.') error.status = 500 throw error @@ -94,9 +90,17 @@ export async function loginWithAppwrite(email, password) { user = await getUserById(session.userId) DEBUG_LOG('appwriteClient.js:getUser', 'users.get ok', { userId: user?.$id || null }, 'H6') } catch (err) { - DEBUG_LOG('appwriteClient.js:getUser', 'users.get fail, fallback', { - message: err?.message?.slice(0, 80), - }, 'H6') + DEBUG_LOG('appwriteClient.js:getUser', 'users.get fail', { + message: err?.message?.slice(0, 120), + code: err?.code, + }, 'H7') + if (err?.message?.includes('not authorized')) { + const error = new Error( + 'Server-API-Key: Scope users.read erforderlich (Appwrite Console).' + ) + error.status = 500 + throw error + } user = { $id: session.userId, email, name: '' } } @@ -104,7 +108,7 @@ export async function loginWithAppwrite(email, password) { try { await deleteUserSession(session.userId, session.$id) } catch { - // Portal nutzt eigene Session; Appwrite-Session optional aufräumen + // Portal nutzt eigene Cookie-Session } }