Fix Appwrite-Session und Collection-IDs für Ticket-System

Nutzt String-Collection-IDs, ticket.webklar.com/v1 als Default und
korrigiert isAdmin sowie Session-Handling für Same-Origin-Login.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Webklar Deploy
2026-05-22 17:58:11 +00:00
parent 5fbb2fb4b5
commit 4f4de7f290
3 changed files with 109 additions and 62 deletions

9
package-lock.json generated
View File

@@ -56,7 +56,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.28.5",
@@ -1097,7 +1096,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.2.2" "csstype": "^3.2.2"
@@ -1174,7 +1172,6 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -1785,7 +1782,6 @@
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@@ -1797,7 +1793,6 @@
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@@ -1953,8 +1948,7 @@
"version": "0.182.0", "version": "0.182.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz",
"integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/tr46": { "node_modules/tr46": {
"version": "0.0.3", "version": "0.0.3",
@@ -2002,7 +1996,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",

View File

@@ -1,24 +1,61 @@
import { createContext, useContext, useState, useEffect } from 'react' import { createContext, useContext, useState, useEffect } from 'react'
import { account, databases, DATABASE_ID, COLLECTIONS, ID, Query } from '../lib/appwrite' import { account, databases, DATABASE_ID, COLLECTIONS, ID, Query, hasAppwriteSession } from '../lib/appwrite'
const AuthContext = createContext() const AuthContext = createContext()
// Demo mode when Appwrite is not configured // Demo mode when Appwrite is not configured
const DEMO_MODE = !import.meta.env.VITE_APPWRITE_PROJECT_ID const DEMO_MODE = !(import.meta.env.VITE_APPWRITE_PROJECT_ID || '').trim()
const PROJECT_ID = (import.meta.env.VITE_APPWRITE_PROJECT_ID || '').trim()
function clearStaleAppwriteSessions() {
if (typeof window === 'undefined' || !window.localStorage) return
try {
const raw = window.localStorage.getItem('cookieFallback')
if (!raw) return
const parsed = JSON.parse(raw)
if (typeof parsed !== 'object' || parsed === null) return
for (const key of Object.keys(parsed)) {
if (key.startsWith('a_session_') && key !== `a_session_${PROJECT_ID}`) {
delete parsed[key]
}
}
window.localStorage.setItem('cookieFallback', JSON.stringify(parsed))
} catch {
window.localStorage.removeItem('cookieFallback')
}
}
function clearAllLocalAppwriteSessions() {
if (typeof window === 'undefined') return
try {
window.localStorage.removeItem('cookieFallback')
} catch { /* ignore */ }
}
async function createSessionWithCleanup(email, password) {
try {
await account.deleteSessions()
} catch { /* alte Sitzung ggf. nur serverseitig */ }
clearAllLocalAppwriteSessions()
if (account.createEmailPasswordSession) {
await account.createEmailPasswordSession(email, password)
} else {
await account.createEmailSession(email, password)
}
}
// Hilfsfunktion: Fügt User automatisch zur employees Collection hinzu // Hilfsfunktion: Fügt User automatisch zur employees Collection hinzu
async function ensureEmployeeExists(user) { async function ensureEmployeeExists(user) {
if (!user || DEMO_MODE) return if (!user || DEMO_MODE) return
try { try {
// Prüfe ob User bereits in employees Collection existiert
const response = await databases.listDocuments( const response = await databases.listDocuments(
DATABASE_ID, DATABASE_ID,
COLLECTIONS.EMPLOYEES, COLLECTIONS.EMPLOYEES,
[Query.equal('userId', user.$id)] [Query.equal('userId', user.$id)]
) )
// Wenn User noch nicht existiert, füge ihn hinzu
if (response.documents.length === 0) { if (response.documents.length === 0) {
await databases.createDocument( await databases.createDocument(
DATABASE_ID, DATABASE_ID,
@@ -28,13 +65,12 @@ async function ensureEmployeeExists(user) {
userId: user.$id, userId: user.$id,
displayName: user.name || user.email, displayName: user.name || user.email,
email: user.email, email: user.email,
shortcode: '' // Kürzel wird später vom Admin hinzugefügt shortcode: ''
} }
) )
console.log('✅ User automatisch zur Mitarbeiter-Liste hinzugefügt') console.log('✅ User automatisch zur Mitarbeiter-Liste hinzugefügt')
} }
} catch (error) { } catch (error) {
// Fehler ignorieren wenn Collection nicht existiert oder Permissions fehlen
if (error.code !== 404) { if (error.code !== 404) {
console.warn('Could not add user to employees collection:', error.message) console.warn('Could not add user to employees collection:', error.message)
} }
@@ -51,7 +87,6 @@ export function AuthProvider({ children }) {
async function checkUser() { async function checkUser() {
if (DEMO_MODE) { if (DEMO_MODE) {
// Check localStorage for demo session
const demoUser = localStorage.getItem('demo_user') const demoUser = localStorage.getItem('demo_user')
if (demoUser) { if (demoUser) {
setUser(JSON.parse(demoUser)) setUser(JSON.parse(demoUser))
@@ -59,18 +94,24 @@ export function AuthProvider({ children }) {
setLoading(false) setLoading(false)
return return
} }
if (!hasAppwriteSession()) {
setUser(null)
setLoading(false)
return
}
try { try {
const session = await account.get() const session = await account.get()
setUser(session) setUser(session)
// Automatisch zur employees Collection hinzufügen
await ensureEmployeeExists(session) await ensureEmployeeExists(session)
} catch (error) { } catch (error) {
// Kein Fehler loggen beim initialen Check - das ist normal wenn nicht eingeloggt
// Nur loggen wenn es ein unerwarteter Fehler ist (nicht 401)
if (error.code !== 401 && error.code !== 404) { if (error.code !== 401 && error.code !== 404) {
console.error('Unexpected error checking user:', error) console.error('Unexpected error checking user:', error)
} }
if (error.code === 401) {
clearAllLocalAppwriteSessions()
}
setUser(null) setUser(null)
} finally { } finally {
setLoading(false) setLoading(false)
@@ -79,45 +120,45 @@ export function AuthProvider({ children }) {
async function login(email, password) { async function login(email, password) {
if (DEMO_MODE) { if (DEMO_MODE) {
// Demo login - accept any credentials
const demoUser = { $id: 'demo', email, name: email.split('@')[0] } const demoUser = { $id: 'demo', email, name: email.split('@')[0] }
localStorage.setItem('demo_user', JSON.stringify(demoUser)) localStorage.setItem('demo_user', JSON.stringify(demoUser))
setUser(demoUser) setUser(demoUser)
return { success: true } return { success: true }
} }
try { try {
// Appwrite 1.5.7 / SDK 13.0 - versuche beide Methoden für Kompatibilität const normalizedEmail = email.trim()
try { const normalizedPassword = password.trim()
await account.createEmailSession(email, password) clearStaleAppwriteSessions()
} catch (e) {
// Fallback für ältere API await createSessionWithCleanup(normalizedEmail, normalizedPassword)
if (account.createEmailPasswordSession) {
await account.createEmailPasswordSession(email, password)
} else {
throw e
}
}
// User-Daten laden und automatisch zur employees Collection hinzufügen
const session = await account.get() const session = await account.get()
setUser(session) setUser(session)
await ensureEmployeeExists(session) await ensureEmployeeExists(session)
return { success: true } return { success: true }
} catch (error) { } catch (error) {
console.error('Login error:', error) console.error('Login error:', error)
let errorMessage = error.message || 'Login fehlgeschlagen' let errorMessage = error.message || 'Login fehlgeschlagen'
// Bessere Fehlermeldungen if (error.code === 429 || errorMessage.includes('Rate limit')) {
if (error.code === 401 || errorMessage.includes('Invalid credentials')) { errorMessage = 'Zu viele Login-Versuche. Bitte 1530 Minuten warten und es erneut versuchen.'
} else if (errorMessage.includes('missing scopes') && errorMessage.includes('account')) {
errorMessage = 'Session konnte nicht gespeichert werden. Bitte Seite neu laden und erneut versuchen.'
} else if (
error?.type === 'user_session_already_exists' ||
errorMessage.includes('session is active')
) {
errorMessage = 'Es gibt noch eine alte Sitzung. Bitte Seite neu laden und erneut anmelden.'
} else if (error.code === 401 || errorMessage.includes('Invalid credentials')) {
errorMessage = 'Ungültige Email oder Passwort' errorMessage = 'Ungültige Email oder Passwort'
} else if (errorMessage.includes('User not found')) { } else if (errorMessage.includes('User not found')) {
errorMessage = 'Benutzer nicht gefunden. Bitte registriere dich zuerst.' errorMessage = 'Benutzer nicht gefunden. Bitte registriere dich zuerst.'
} else if (errorMessage.includes('Email/Password')) { } else if (errorMessage.includes('Email/Password')) {
errorMessage = 'Email/Password Authentifizierung ist nicht aktiviert. Bitte aktiviere sie in deinem Appwrite Dashboard unter Auth → Providers.' errorMessage = 'Email/Password Authentifizierung ist nicht aktiviert. Bitte aktiviere sie in deinem Appwrite Dashboard unter Auth → Providers.'
} }
return { success: false, error: errorMessage } return { success: false, error: errorMessage }
} }
} }
@@ -128,28 +169,25 @@ export function AuthProvider({ children }) {
setUser(null) setUser(null)
return return
} }
try { try {
await account.deleteSession('current') await account.deleteSessions()
setUser(null)
} catch (error) { } catch (error) {
console.error('Logout error:', error) console.error('Logout error:', error)
} finally {
clearAllLocalAppwriteSessions()
setUser(null)
} }
} }
// Hilfsfunktion um zu prüfen ob Benutzer Admin ist const isAdmin = Boolean(user?.labels?.includes('admin'))
const isAdmin = () => {
if (!user) return false
// Prüfe ob Benutzer das "admin" Label hat
return user.labels?.includes('admin') || false
}
const value = { const value = {
user, user,
loading, loading,
login, login,
logout, logout,
isAdmin: isAdmin() isAdmin
} }
return ( return (

View File

@@ -1,6 +1,5 @@
import { Client, Account, Databases, Storage, ID, Query } from 'appwrite' import { Client, Account, Databases, Storage, ID, Query } from 'appwrite'
// Debug: Zeige geladene Umgebungsvariablen (nur in Development)
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.log('🔧 Appwrite Konfiguration:') console.log('🔧 Appwrite Konfiguration:')
console.log('Endpoint:', import.meta.env.VITE_APPWRITE_ENDPOINT || 'NICHT GESETZT') console.log('Endpoint:', import.meta.env.VITE_APPWRITE_ENDPOINT || 'NICHT GESETZT')
@@ -8,8 +7,8 @@ if (import.meta.env.DEV) {
console.log('Database ID:', import.meta.env.VITE_APPWRITE_DATABASE_ID || 'NICHT GESETZT') console.log('Database ID:', import.meta.env.VITE_APPWRITE_DATABASE_ID || 'NICHT GESETZT')
} }
const endpoint = import.meta.env.VITE_APPWRITE_ENDPOINT || 'https://appwrite.webklar.com/v1' const endpoint = import.meta.env.VITE_APPWRITE_ENDPOINT || 'https://ticket.webklar.com/v1'
const projectId = import.meta.env.VITE_APPWRITE_PROJECT_ID || '' const projectId = (import.meta.env.VITE_APPWRITE_PROJECT_ID || '').trim()
if (!projectId) { if (!projectId) {
console.error('❌ FEHLER: VITE_APPWRITE_PROJECT_ID ist nicht gesetzt!') console.error('❌ FEHLER: VITE_APPWRITE_PROJECT_ID ist nicht gesetzt!')
@@ -26,18 +25,35 @@ export const storage = new Storage(client)
export const DATABASE_ID = import.meta.env.VITE_APPWRITE_DATABASE_ID || 'woms-database' export const DATABASE_ID = import.meta.env.VITE_APPWRITE_DATABASE_ID || 'woms-database'
// Collection IDs - Verwende die tatsächlichen Collection IDs aus Appwrite!
export const COLLECTIONS = { export const COLLECTIONS = {
WORKORDERS: '6943bf7d001901baa60c', // Collection ID für workorders WORKORDERS: 'workorders',
CONFIG: 'config', // Collection ID für Admin-Konfiguration (wird erstellt) CONFIG: 'config',
CUSTOMERS: '694bd1fb002b2e583d13', // Collection ID für customers CUSTOMERS: 'customers',
EMPLOYEES: '695280510031c6c6153b', // Collection ID für employees EMPLOYEES: 'employees',
WORKSHEETS: '6952dbcf0032a92e1168', // Collection ID für worksheets WORKSHEETS: 'worksheets',
USERS: 'users', USERS: 'users',
ATTACHMENTS: 'attachments' ATTACHMENTS: 'attachments'
} }
export const BUCKET_ID = 'woms-attachments' export const BUCKET_ID = import.meta.env.VITE_APPWRITE_BUCKET_ID || 'woms-attachments'
/** Prüft ob vermutlich eine Session existiert (cookieFallback oder Same-Origin-Cookie). */
export function hasAppwriteSession() {
if (typeof window === 'undefined' || !projectId) return false
try {
const apiHost = new URL(endpoint).hostname
if (apiHost === window.location.hostname) {
return true
}
} catch { /* ignore */ }
try {
const raw = window.localStorage.getItem('cookieFallback')
if (!raw) return false
const parsed = JSON.parse(raw)
return Boolean(parsed?.[`a_session_${projectId}`])
} catch {
return false
}
}
// Helper functions
export { ID, Query } export { ID, Query }