Mitarbeiter-Zugang: Review-Punkte (fail closed, Kunden-Konto bleibt, Probelauf robuster)

- removeEmployeeEntry: nur 404 heisst "kein Konto", jeder andere Fehler bricht ab
- Mitarbeiter loeschen (Admin-Panel): Konto ohne Mitarbeiter-Label (Kunde mit
  altem employees-Eintrag) bleibt bestehen, nur der Eintrag geht
- Mitarbeiter bearbeiten gibt nie automatisch das Label (der Eintrag kann von
  einem Kunden stammen); nur Neuanlage bekommt "mitarbeiter"
- --rechte: --offen-lassen <ids>, unlesbare Listen als Fehler statt Abbruch
  (--anwenden verweigert dann), --sicherung <datei> + docker-cp-Hinweis,
  Bucket-Einstellung transformations wird mitgeschickt
- getEmployeeByEmail entfernt (unbenutzt)
- Tests: write/users-unverified, zweiter Lauf idempotent, >100 Dokumente,
  Loeschen mit Kunden- und Mitarbeiter-Konto

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-03 11:00:48 +00:00
1 parent 2659385205
commit 10b393687c
6 files changed
+139 -40

No files matched your search

+31 -9
View File
@@ -92,7 +92,13 @@ export async function grantStaffLabel(userId, request = appwriteAdminRequest) {
export async function removeEmployeeEntry(employeeId, request = appwriteAdminRequest) {
const path = `/databases/${config.appwrite.databaseId}/collections/${config.collections.employees}/documents/${employeeId}`
const employee = await request(path)
const user = employee.userId ? await request(`/users/${employee.userId}`).catch(() => null) : null
// Nur "Konto gibt es nicht" (404) zaehlt als kein Mitarbeiter; jeder andere Fehler bricht ab (fail closed)
const user = employee.userId
? await request(`/users/${employee.userId}`).catch((err) => {
if (err.status === 404) return null
throw err
})
: null
if (isStaffAccount(user)) throw new Error(`${employee.displayName || employeeId} hat das Mitarbeiter-Label - Eintrag bleibt.`)
await request(path, { method: 'DELETE' })
return { employeeId, name: employee.displayName || '', userId: employee.userId || '' }
@@ -100,18 +106,31 @@ export async function removeEmployeeEntry(employeeId, request = appwriteAdminReq
/**
* Alle Stellen mit offenen Rechten: Collections und Buckets, bei Dokument- bzw. Datei-Sicherheit auch einzelne
* Dokumente und Dateien. Jede Aenderung traegt before/after, damit sie sich zuruecknehmen laesst.
* Dokumente und Dateien. Jede Aenderung traegt before/after, damit sie sich zuruecknehmen laesst. keep = IDs von
* Collections/Buckets, die bewusst offen bleiben (samt ihrer Dokumente/Dateien). Eine Liste, die sich nicht lesen
* laesst (z. B. fehlender Key-Scope), landet in errors statt den ganzen Lauf abzubrechen.
*/
export async function planPermissionChanges(request = appwriteAdminRequest) {
export async function planPermissionChanges(request = appwriteAdminRequest, { keep = [] } = {}) {
const changes = []
const errors = []
const kept = new Set(keep)
const add = (change, before) => {
const after = staffOnlyPermissions(before)
if (!samePermissions(before, after)) changes.push({ ...change, before, after })
}
const tryList = async (path, key) => {
try {
return await listAll(request, path, key)
} catch (err) {
errors.push({ path, error: err.message })
return []
}
}
for (const database of await listAll(request, '/databases', 'databases')) {
for (const database of await tryList('/databases', 'databases')) {
const collectionsPath = `/databases/${database.$id}/collections`
for (const collection of await listAll(request, collectionsPath, 'collections')) {
for (const collection of await tryList(collectionsPath, 'collections')) {
if (kept.has(collection.$id)) continue
add(
{
kind: 'collection',
@@ -123,13 +142,14 @@ export async function planPermissionChanges(request = appwriteAdminRequest) {
collection.$permissions || []
)
if (!collection.documentSecurity) continue
for (const document of await listAll(request, `${collectionsPath}/${collection.$id}/documents`, 'documents')) {
for (const document of await tryList(`${collectionsPath}/${collection.$id}/documents`, 'documents')) {
add({ kind: 'document', databaseId: database.$id, collectionId: collection.$id, id: document.$id }, document.$permissions || [])
}
}
}
for (const bucket of await listAll(request, '/storage/buckets', 'buckets')) {
for (const bucket of await tryList('/storage/buckets', 'buckets')) {
if (kept.has(bucket.$id)) continue
add(
{
kind: 'bucket',
@@ -144,16 +164,18 @@ export async function planPermissionChanges(request = appwriteAdminRequest) {
compression: bucket.compression,
encryption: bucket.encryption,
antivirus: bucket.antivirus,
// neuere Appwrite-Versionen: Bild-Transformationen; mitschicken, sonst setzt das PUT den Standard
...(bucket.transformations === undefined ? {} : { transformations: bucket.transformations }),
},
},
bucket.$permissions || []
)
if (!bucket.fileSecurity) continue
for (const file of await listAll(request, `/storage/buckets/${bucket.$id}/files`, 'files')) {
for (const file of await tryList(`/storage/buckets/${bucket.$id}/files`, 'files')) {
add({ kind: 'file', bucketId: bucket.$id, id: file.$id }, file.$permissions || [])
}
}
return changes
return { changes, errors }
}
function writeChange(change, permissions, request) {