Skript: 2FA für Mitarbeiter anzeigen, einschalten, abschalten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0555a2274d
commit
acc86c267b
4 files changed
+161
No files matched your search
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Mitarbeiter-2FA (E-Mail-Code) anzeigen, einschalten, abschalten.
|
||||
*
|
||||
* Usage (im Container, mit dem Server-Key):
|
||||
* docker exec -it -w /app webklar-kundenbereich-server node scripts/mitarbeiter-2fa.mjs --status
|
||||
* ... --enable <userId>|alle [--yes]
|
||||
* ... --disable <userId> (Notfall: bei einer Person kommt keine Mail an)
|
||||
*/
|
||||
import 'dotenv/config'
|
||||
import { createInterface } from 'node:readline/promises'
|
||||
import { disableStaffMfa, enableStaffMfa, listStaffMfaStatus } from '../server/services/staffMfaAdmin.js'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const flag = (name) => {
|
||||
const i = args.indexOf(name)
|
||||
return i === -1 ? null : args[i + 1] || ''
|
||||
}
|
||||
|
||||
function printTable(rows) {
|
||||
for (const r of rows) {
|
||||
console.log(
|
||||
`${r.userId.padEnd(22)} ${r.name.padEnd(20)} ${r.email.padEnd(32)} E-Mail bestätigt: ${r.emailVerification ? 'ja ' : 'nein'} 2FA: ${r.mfa ? 'an' : 'aus'}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const rows = await listStaffMfaStatus()
|
||||
|
||||
if (args.includes('--status')) {
|
||||
printTable(rows)
|
||||
return
|
||||
}
|
||||
|
||||
const enable = flag('--enable')
|
||||
if (enable !== null) {
|
||||
const targets = enable === 'alle' ? rows.filter((r) => !r.mfa) : rows.filter((r) => r.userId === enable)
|
||||
if (!targets.length) throw new Error(enable === 'alle' ? 'Alle Mitarbeiter haben 2FA schon an.' : `Kein Mitarbeiter mit userId ${enable}`)
|
||||
console.log('Der Code geht an diese Adressen – sie müssen Post empfangen:')
|
||||
printTable(targets)
|
||||
if (!(await confirm('2FA für diese Konten einschalten?'))) return console.log('Abgebrochen.')
|
||||
for (const r of targets) {
|
||||
await enableStaffMfa(r.userId)
|
||||
console.log(`2FA an: ${r.name} <${r.email}>`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const disable = flag('--disable')
|
||||
if (disable) {
|
||||
const target = rows.find((r) => r.userId === disable)
|
||||
if (!target) throw new Error(`Kein Mitarbeiter mit userId ${disable}`)
|
||||
await disableStaffMfa(disable)
|
||||
console.log(`2FA aus: ${target.name} <${target.email}>`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Usage: --status | --enable <userId>|alle [--yes] | --disable <userId>')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err.message)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -132,6 +132,17 @@ export async function updateAppwriteUserLabels(userId, labels) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateAppwriteUserMfa(userId, mfa) {
|
||||
return adminFetch(`/users/${userId}/mfa`, { method: 'PATCH', body: { mfa: Boolean(mfa) } })
|
||||
}
|
||||
|
||||
export async function updateAppwriteUserEmailVerification(userId, emailVerification) {
|
||||
return adminFetch(`/users/${userId}/verification`, {
|
||||
method: 'PATCH',
|
||||
body: { emailVerification: Boolean(emailVerification) },
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteAppwriteUser(userId) {
|
||||
return adminFetch(`/users/${userId}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { config } from '../config.js'
|
||||
import {
|
||||
getUserById,
|
||||
listDocuments,
|
||||
Query,
|
||||
updateAppwriteUserEmailVerification,
|
||||
updateAppwriteUserMfa,
|
||||
} from './appwriteAdmin.js'
|
||||
|
||||
/**
|
||||
* Mitarbeiter-2FA verwalten (scripts/mitarbeiter-2fa.mjs). Appwrite verlangt den E-Mail-Code nur, wenn
|
||||
* die Adresse bestaetigt ist - deshalb beim Einschalten erst bestaetigen, dann 2FA an.
|
||||
*/
|
||||
export async function listStaffMfaStatus() {
|
||||
const employees = await listDocuments(config.collections.employees, [Query.limit(100)])
|
||||
const rows = []
|
||||
for (const employee of employees) {
|
||||
if (!employee.userId) continue
|
||||
const user = await getUserById(employee.userId)
|
||||
rows.push({
|
||||
userId: employee.userId,
|
||||
name: employee.displayName || user.name || '',
|
||||
email: user.email || employee.email || '',
|
||||
emailVerification: Boolean(user.emailVerification),
|
||||
mfa: Boolean(user.mfa),
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
export async function enableStaffMfa(userId) {
|
||||
await updateAppwriteUserEmailVerification(userId, true)
|
||||
await updateAppwriteUserMfa(userId, true)
|
||||
}
|
||||
|
||||
export async function disableStaffMfa(userId) {
|
||||
await updateAppwriteUserMfa(userId, false)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test, after } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { startAppwriteMock } from './support/appwriteMock.js'
|
||||
import { startFakeStripe } from './support/fakeStripe.js'
|
||||
import { useTestEnv } from './support/testEnv.js'
|
||||
|
||||
// Einrichtungs-Skript mitarbeiter-2fa.mjs: Status, einschalten (erst E-Mail bestaetigen, dann 2FA), abschalten
|
||||
const aw = await startAppwriteMock()
|
||||
const st = await startFakeStripe()
|
||||
useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url })
|
||||
const { listStaffMfaStatus, enableStaffMfa, disableStaffMfa } = await import('../server/services/staffMfaAdmin.js')
|
||||
after(() => Promise.all([aw.close(), st.close()]))
|
||||
|
||||
aw.addUser({ $id: 'u1', email: 'kenso@webklar.com', name: 'Kenso' })
|
||||
aw.addUser({ $id: 'u2', email: 'justin@webklar.com', name: 'Justin', emailVerification: true, mfa: true })
|
||||
aw.seed('employees', { $id: 'e1', userId: 'u1', displayName: 'Kenso', email: 'kenso@webklar.com' })
|
||||
aw.seed('employees', { $id: 'e2', userId: 'u2', displayName: 'Justin', email: 'justin@webklar.com' })
|
||||
aw.seed('employees', { $id: 'e3', userId: '', displayName: 'Ohne Login', email: 'alt@webklar.com' })
|
||||
|
||||
test('Status: nur Mitarbeiter mit Appwrite-Konto', async () => {
|
||||
assert.deepEqual(await listStaffMfaStatus(), [
|
||||
{ userId: 'u1', name: 'Kenso', email: 'kenso@webklar.com', emailVerification: false, mfa: false },
|
||||
{ userId: 'u2', name: 'Justin', email: 'justin@webklar.com', emailVerification: true, mfa: true },
|
||||
])
|
||||
})
|
||||
|
||||
test('Einschalten: erst E-Mail bestaetigen, dann 2FA', async () => {
|
||||
await enableStaffMfa('u1')
|
||||
assert.deepEqual(aw.userCalls(), ['verification:u1', 'mfa:u1'])
|
||||
assert.equal(aw.user('u1').mfa, true)
|
||||
assert.equal(aw.user('u1').emailVerification, true)
|
||||
})
|
||||
|
||||
test('Abschalten: nur 2FA aus, E-Mail bleibt bestaetigt', async () => {
|
||||
await disableStaffMfa('u2')
|
||||
assert.equal(aw.user('u2').mfa, false)
|
||||
assert.equal(aw.user('u2').emailVerification, true)
|
||||
assert.equal(aw.userCalls().at(-1), 'mfa:u2')
|
||||
})
|
||||
Reference in new issue
Block a user