Mitarbeiter nur noch per Label: Kunden-Konten bekommen keinen Team-Zugang
Kunden haben Konten im selben Appwrite-Projekt wie das Ticketsystem. Bisher galt als Mitarbeiter, wer einen employees-Eintrag hatte - den legte das Ticketsystem beim Login jedes Kontos automatisch an, und jedes angemeldete Konto konnte ihn auch selbst anlegen. Damit kam ein Kunde ueber /api/team an Chats, Kundenansicht, Angebote und Finanzen. - staffLabels.js: Mitarbeiter = Label "mitarbeiter" oder "admin" (setzt nur der Server-Key) - Team-Eingang (requireStaffJwt) und Portal-Login-Sperre nur noch per Label; kein Abgleich ueber employees-Eintrag oder Mail mehr - Portal-Login: Konto-Abruf gestoert -> 503 fuer alle (fail closed) - Mitarbeiter anlegen/bearbeiten setzt das Label "mitarbeiter" - scripts/mitarbeiter-zugang.mjs: --status (markiert employees-Eintraege von Kunden), --label, --eintrag-loeschen (nur Eintrag, nie das Konto), --rechte (Probelauf, --anwenden mit Sicherung, --zurueck): any/guests/users -> label:mitarbeiter + label:admin in Collections, Buckets und bei Dokument-/ Datei-Sicherheit auch einzelnen Dokumenten und Dateien Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
be7f9b1aa3
commit
2659385205
12 files changed
+602
-65
No files matched your search
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Zugang zum Ticketsystem nur fuer Mitarbeiter (Label "mitarbeiter" oder "admin").
|
||||
*
|
||||
* Usage (im Container, mit dem Server-Key):
|
||||
* docker exec -it -w /app webklar-kundenbereich-server node scripts/mitarbeiter-zugang.mjs --status
|
||||
* ... --label <userId>[,<userId>...] [--yes] Label "mitarbeiter" vergeben (nur ausdrueckliche Konten)
|
||||
* ... --eintrag-loeschen <employeeId> [--yes] employees-Eintrag eines Kunden loeschen (Konto bleibt)
|
||||
* ... --rechte Probelauf: welche Rechte sich aendern
|
||||
* ... --rechte --anwenden [--yes] Rechte umstellen, Sicherung als JSON (Pfad wird ausgegeben)
|
||||
* ... --rechte --zurueck <sicherung.json> [--yes] Rechte aus einer Sicherung wiederherstellen
|
||||
*/
|
||||
import 'dotenv/config'
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { createInterface } from 'node:readline/promises'
|
||||
import {
|
||||
applyPermissionChanges,
|
||||
grantStaffLabel,
|
||||
planPermissionChanges,
|
||||
removeEmployeeEntry,
|
||||
staffAccessStatus,
|
||||
} from '../server/services/staffAccessAdmin.js'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const flag = (name) => {
|
||||
const i = args.indexOf(name)
|
||||
return i === -1 ? null : args[i + 1] || ''
|
||||
}
|
||||
|
||||
async function confirm(question) {
|
||||
if (args.includes('--yes')) return true
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
||||
const answer = await rl.question(`${question} (Ja/Nein) `)
|
||||
rl.close()
|
||||
return /^j(a)?$/i.test(answer.trim())
|
||||
}
|
||||
|
||||
function where(change) {
|
||||
if (change.kind === 'collection') return `Collection ${change.databaseId}/${change.id} (${change.name})`
|
||||
if (change.kind === 'document') return `Dokument ${change.collectionId}/${change.id}`
|
||||
if (change.kind === 'bucket') return `Bucket ${change.id} (${change.name})`
|
||||
return `Datei ${change.bucketId}/${change.id}`
|
||||
}
|
||||
|
||||
function printChanges(changes) {
|
||||
for (const change of changes) {
|
||||
console.log(where(change))
|
||||
console.log(` vorher: ${change.before.join(' ') || '(keine)'}`)
|
||||
console.log(` nachher: ${change.after.join(' ') || '(keine)'}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function status() {
|
||||
const { rows, labelOnly } = await staffAccessStatus()
|
||||
console.log('employees-Einträge:')
|
||||
for (const r of rows) {
|
||||
const state = r.staff ? 'Zugang: ja ' : 'Zugang: NEIN'
|
||||
const note = r.customer ? ` ← KUNDE (${r.customer}), Eintrag löschen` : r.login ? '' : ' (kein Konto)'
|
||||
console.log(` ${r.employeeId.padEnd(22)} ${r.userId.padEnd(38)} ${r.name.padEnd(20)} ${r.email.padEnd(32)} ${state} [${r.labels.join(',')}]${note}`)
|
||||
}
|
||||
if (labelOnly.length) {
|
||||
console.log('Konten mit Mitarbeiter-Label ohne employees-Eintrag:')
|
||||
for (const u of labelOnly) console.log(` ${u.userId.padEnd(38)} ${u.name.padEnd(20)} ${u.email.padEnd(32)} [${u.labels.join(',')}]`)
|
||||
}
|
||||
}
|
||||
|
||||
async function label(ids) {
|
||||
const userIds = ids.split(',').map((id) => id.trim()).filter(Boolean)
|
||||
if (!userIds.length) throw new Error('Bitte userIds angeben (kommagetrennt).')
|
||||
console.log(`Label "mitarbeiter" für: ${userIds.join(', ')}`)
|
||||
if (!(await confirm('Diese Konten bekommen Zugang zum Ticketsystem. Fortfahren?'))) return console.log('Abgebrochen.')
|
||||
for (const userId of userIds) {
|
||||
const r = await grantStaffLabel(userId)
|
||||
console.log(`Zugang: ${r.name} <${r.email}> [${r.labels.join(',')}]`)
|
||||
}
|
||||
}
|
||||
|
||||
async function removeEntry(employeeId) {
|
||||
if (!(await confirm(`employees-Eintrag ${employeeId} löschen (das Konto bleibt)?`))) return console.log('Abgebrochen.')
|
||||
const r = await removeEmployeeEntry(employeeId)
|
||||
console.log(`Eintrag gelöscht: ${r.name} (Konto ${r.userId} bleibt)`)
|
||||
}
|
||||
|
||||
function report(results) {
|
||||
const failed = results.filter((r) => !r.ok)
|
||||
for (const r of failed) console.log(`FEHLER ${where(r.change)}: ${r.error}`)
|
||||
console.log(`${results.length - failed.length} von ${results.length} Stellen geschrieben.`)
|
||||
if (failed.length) process.exitCode = 1
|
||||
}
|
||||
|
||||
async function permissions() {
|
||||
const restore = flag('--zurueck')
|
||||
if (restore) {
|
||||
const changes = JSON.parse(readFileSync(restore, 'utf8'))
|
||||
printChanges(changes.map((c) => ({ ...c, before: c.after, after: c.before })))
|
||||
if (!(await confirm(`${changes.length} Stellen auf den Stand der Sicherung zurücksetzen?`))) return console.log('Abgebrochen.')
|
||||
return report(await applyPermissionChanges(changes, { direction: 'before' }))
|
||||
}
|
||||
|
||||
const changes = await planPermissionChanges()
|
||||
if (!changes.length) return console.log('Keine offenen Rechte (any/guests/users) gefunden.')
|
||||
printChanges(changes)
|
||||
if (!args.includes('--anwenden')) return console.log(`Probelauf: ${changes.length} Stellen. Mit --anwenden umstellen.`)
|
||||
|
||||
const backup = join(tmpdir(), `mitarbeiter-rechte-${new Date().toISOString().replace(/[:.]/g, '-')}.json`)
|
||||
writeFileSync(backup, JSON.stringify(changes, null, 2))
|
||||
console.log(`Sicherung: ${backup}`)
|
||||
if (!(await confirm(`${changes.length} Stellen nur noch für Mitarbeiter freigeben?`))) return console.log('Abgebrochen.')
|
||||
report(await applyPermissionChanges(changes))
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (args.includes('--status')) return status()
|
||||
const ids = flag('--label')
|
||||
if (ids !== null) return label(ids)
|
||||
const employeeId = flag('--eintrag-loeschen')
|
||||
if (employeeId) return removeEntry(employeeId)
|
||||
if (args.includes('--rechte')) return permissions()
|
||||
console.log('Usage: --status | --label <userId>[,...] | --eintrag-loeschen <employeeId> | --rechte [--anwenden | --zurueck <datei>] [--yes]')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err.message)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in new issue
Block a user