import { config } from '../config.js' function appwriteHeaders() { return { 'Content-Type': 'application/json', 'X-Appwrite-Project': config.appwrite.projectId, } } async function appwriteFetch(path, { method = 'GET', body, headers = {}, onResponse } = {}) { const url = `${config.appwrite.endpoint}${path}` const response = await fetch(url, { method, headers: { ...appwriteHeaders(), ...headers }, body: body ? JSON.stringify(body) : undefined, }) onResponse?.(response) let data = null const text = await response.text() if (text) { try { data = JSON.parse(text) } catch { data = { message: text } } } if (!response.ok) { const error = new Error(data?.message || `Appwrite ${response.status}`) error.status = response.status error.code = data?.code error.type = data?.type if (response.status === 429 || data?.type === 'general_rate_limit_exceeded') { error.message = 'Zu viele Anmeldeversuche. Bitte warte einige Minuten, bevor du es erneut versuchst.' error.status = 429 } throw error } return data } function readSessionCookie(response) { const name = `a_session_${config.appwrite.projectId}=` const cookies = response.headers.getSetCookie?.() || [] const cookie = cookies.find((c) => c.startsWith(name)) return cookie ? decodeURIComponent(cookie.slice(name.length).split(';')[0]) : '' } /** * Appwrite-Aufruf im Namen einer Sitzung aus loginWithAppwrite (z. B. Sitzung eines abgelehnten * Mitarbeiter-Logins beenden). Deutsche Sprache, damit Appwrite deutsche Texte liefert. */ export function appwriteSessionFetch(path, { method = 'GET', body, sessionCookie } = {}) { return appwriteFetch(path, { method, body, headers: { Cookie: `a_session_${config.appwrite.projectId}=${encodeURIComponent(sessionCookie || '')}`, 'X-Appwrite-Locale': 'de', }, }) } /** * Login via Appwrite Auth REST. userId kommt aus der Session – kein users.read nötig. */ let appwriteLoginBlockedUntil = 0 const APPWRITE_LOGIN_COOLDOWN_MS = 5 * 60 * 1000 const APPWRITE_RATE_LIMIT_COOLDOWN_MS = 15 * 60 * 1000 export function getLoginCooldownRemainingSec() { const left = appwriteLoginBlockedUntil - Date.now() return left > 0 ? Math.ceil(left / 1000) : 0 } export function clearLoginCooldown() { appwriteLoginBlockedUntil = 0 } export async function loginWithAppwrite(email, password) { const now = Date.now() if (now < appwriteLoginBlockedUntil) { const waitSec = Math.ceil((appwriteLoginBlockedUntil - now) / 1000) const error = new Error( `Zu viele Anmeldeversuche. Bitte warte noch ${waitSec} Sekunden.` ) error.status = 429 throw error } let session let sessionCookie = '' try { session = await appwriteFetch('/account/sessions/email', { method: 'POST', body: { email, password }, onResponse: (response) => { sessionCookie = readSessionCookie(response) }, }) } catch (err) { if (err.status === 429) { appwriteLoginBlockedUntil = Date.now() + APPWRITE_RATE_LIMIT_COOLDOWN_MS } const error = new Error(err.message || 'Anmeldung fehlgeschlagen') error.status = err.status || 401 throw error } if (!session?.userId) { const error = new Error('Appwrite-Session ohne userId.') error.status = 500 throw error } // sessionCookie: lehnt der Login einen Mitarbeiter ab, beendet der Server damit diese Sitzung wieder const user = { $id: session.userId, email, name: '', sessionCookie } clearLoginCooldown() return user } /** * Prueft ein Passwort ueber eine Appwrite-Session (Passwort aendern im Kundenbereich). Anders als * loginWithAppwrite ohne die globale Login-Sperre: tippt ein Kunde sein aktuelles Passwort falsch, * darf das die Anmeldung der anderen nicht blockieren. Die Session loescht der Aufrufer wieder. * @returns {Promise<{ userId: string, sessionId: string } | null>} null = Passwort falsch */ export async function verifyAppwritePassword(email, password) { try { const session = await appwriteFetch('/account/sessions/email', { method: 'POST', body: { email, password }, }) return { userId: session?.userId || '', sessionId: session?.$id || '' } } catch (err) { // 400: kuerzer als 8 Zeichen (kann nie stimmen), 401: falsche Zugangsdaten; gesperrt u. a. weiterreichen if (err.status === 400 || (err.status === 401 && err.type === 'user_invalid_credentials')) return null throw err } }