Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
232543e2aa |
+47
-1
@@ -26,6 +26,51 @@ Wiederherstellung*.
|
|||||||
|
|
||||||
_Noch nichts._
|
_Noch nichts._
|
||||||
|
|
||||||
|
## [1.0.2] – 2026-08-22
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
|
||||||
|
- **Die Einrichtung des zweiten Faktors brach mit einem Serverfehler ab**
|
||||||
|
(„Application error: a server-side exception has occurred"). Im
|
||||||
|
Serverlog stand `Umgebungsvariable ENC_KEY_V2 fehlt` — obwohl niemand
|
||||||
|
einen zweiten Schlüssel wollte.
|
||||||
|
|
||||||
|
Ursache: `docker-compose.yml` reicht `ENC_KEY_V2` immer an den Behälter
|
||||||
|
durch, mit **leerem Wert**, wenn in der `.env` keiner steht. Die
|
||||||
|
Anwendung erkannte Schlüssel aber am Variablennamen statt am Inhalt,
|
||||||
|
fand also ein leeres `ENC_KEY_V2` und brach ab. Leere
|
||||||
|
Schlüsselvariablen werden jetzt übersprungen.
|
||||||
|
|
||||||
|
Betroffen war jede Stelle, die verschlüsselte Felder schreibt oder
|
||||||
|
liest — also praktisch alles außer der reinen Anmeldung. Die Anmeldung
|
||||||
|
ging durch, weil sie ohne Verschlüsselung auskommt; der Fehler trat
|
||||||
|
deshalb erst auf der nächsten Seite auf.
|
||||||
|
|
||||||
|
- **Die Meldung beim Schlüsselwechsel** nennt jetzt, welche Versionen
|
||||||
|
tatsächlich vorhanden sind, und die richtige Reihenfolge: erst den
|
||||||
|
neuen Schlüssel eintragen, dann `ENC_KEY_AKTIV` umstellen.
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
|
||||||
|
- **`scripts/anmeldung-pruefen.mjs`** — beantwortet die Frage, warum eine
|
||||||
|
Anmeldung nicht klappt. Das Formular sagt bewusst nicht, woran es lag,
|
||||||
|
sonst ließe sich daran ablesen, welche Adressen es gibt. Auf dem Server
|
||||||
|
darf man es wissen: Jeder Versuch wird mit Grund protokolliert, und das
|
||||||
|
Skript liest ihn vor — dazu den Zustand des Zugangs, den Stand der
|
||||||
|
Login-Sperre und die eingestellte `AUTH_URL`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec app node scripts/anmeldung-pruefen.mjs deine@adresse.de
|
||||||
|
```
|
||||||
|
|
||||||
|
Es ändert nichts, es liest nur.
|
||||||
|
|
||||||
|
### Geprüft
|
||||||
|
|
||||||
|
- Sieben neue Tests (`tests/umgebung.test.ts`) decken die
|
||||||
|
Schlüsselauswertung ab, darunter genau die Konstellation aus dem
|
||||||
|
Fehlerbericht: gesetztes `ENC_KEY_V1`, leeres `ENC_KEY_V2`.
|
||||||
|
|
||||||
## [1.0.1] – 2026-08-22
|
## [1.0.1] – 2026-08-22
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
@@ -219,6 +264,7 @@ Was davon wann angegangen wird, steht in [`docs/ROADMAP.md`](docs/ROADMAP.md).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.1...HEAD
|
[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.2...HEAD
|
||||||
|
[1.0.2]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.1...v1.0.2
|
||||||
[1.0.1]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.0...v1.0.1
|
[1.0.1]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.0...v1.0.1
|
||||||
[1.0.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/releases/tag/v1.0.0
|
[1.0.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/releases/tag/v1.0.0
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ services:
|
|||||||
AUTH_URL: ${AUTH_URL:?AUTH_URL fehlt in der .env}
|
AUTH_URL: ${AUTH_URL:?AUTH_URL fehlt in der .env}
|
||||||
AUTH_TRUST_HOST: 'true'
|
AUTH_TRUST_HOST: 'true'
|
||||||
ENC_KEY_V1: ${ENC_KEY_V1:?ENC_KEY_V1 fehlt in der .env - ohne ihn sind die verschluesselten Felder nicht lesbar}
|
ENC_KEY_V1: ${ENC_KEY_V1:?ENC_KEY_V1 fehlt in der .env - ohne ihn sind die verschluesselten Felder nicht lesbar}
|
||||||
|
# Bleibt leer, solange nicht rotiert wird. Die Anwendung ueberspringt
|
||||||
|
# leere Schluesselvariablen - siehe src/lib/env.ts.
|
||||||
ENC_KEY_V2: ${ENC_KEY_V2:-}
|
ENC_KEY_V2: ${ENC_KEY_V2:-}
|
||||||
ENC_KEY_AKTIV: ${ENC_KEY_AKTIV:-1}
|
ENC_KEY_AKTIV: ${ENC_KEY_AKTIV:-1}
|
||||||
INDEX_KEY: ${INDEX_KEY:?INDEX_KEY fehlt in der .env}
|
INDEX_KEY: ${INDEX_KEY:?INDEX_KEY fehlt in der .env}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "betreuungs-cockpit",
|
"name": "betreuungs-cockpit",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Arbeits- und Dokumentationswerkzeug fuer die ehrenamtliche rechtliche Betreuung",
|
"description": "Arbeits- und Dokumentationswerkzeug fuer die ehrenamtliche rechtliche Betreuung",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Warum klappt die Anmeldung nicht?
|
||||||
|
*
|
||||||
|
* node --env-file=.env scripts/anmeldung-pruefen.mjs <e-mail>
|
||||||
|
* docker compose exec app node scripts/anmeldung-pruefen.mjs <e-mail>
|
||||||
|
*
|
||||||
|
* Die Anwendung sagt am Anmeldeformular bewusst nicht, woran es lag - sonst
|
||||||
|
* liesse sich daran ablesen, welche Adressen es gibt. Auf dem Server darf
|
||||||
|
* man es wissen: Jeder Versuch wird mit Grund protokolliert, und dieses
|
||||||
|
* Skript liest ihn vor.
|
||||||
|
*
|
||||||
|
* Es aendert nichts. Es liest nur.
|
||||||
|
*/
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
const email = (process.argv[2] ?? '').trim().toLowerCase()
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
console.error('Aufruf: node scripts/anmeldung-pruefen.mjs <e-mail>')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new PrismaClient()
|
||||||
|
|
||||||
|
function zeit(d) {
|
||||||
|
return d ? new Date(d).toLocaleString('de-DE') : '—'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Klartext zu den Gruenden, die src/auth.ts protokolliert. */
|
||||||
|
const GRUND = {
|
||||||
|
unbekannt: 'Diese E-Mail-Adresse gibt es nicht (Tippfehler? andere Adresse?)',
|
||||||
|
'passwort-falsch': 'Das Passwort stimmt nicht',
|
||||||
|
'code-falsch': 'Der Code aus der Authenticator-App stimmt nicht',
|
||||||
|
gesperrt: 'Abgewiesen, weil zu diesem Zeitpunkt bereits gesperrt war',
|
||||||
|
'konto-inaktiv': 'Der Zugang ist deaktiviert oder im Papierkorb',
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const benutzer = await db.user.findUnique({ where: { email } })
|
||||||
|
|
||||||
|
console.log('\n══ Zugang ' + '═'.repeat(50))
|
||||||
|
|
||||||
|
if (!benutzer) {
|
||||||
|
console.log(` Es gibt KEINEN Zugang mit der Adresse ${email}.`)
|
||||||
|
const alle = await db.user.findMany({
|
||||||
|
select: { email: true, aktiv: true, geloeschtAm: true },
|
||||||
|
orderBy: { erstelltAm: 'asc' },
|
||||||
|
})
|
||||||
|
console.log(
|
||||||
|
alle.length
|
||||||
|
? '\n Vorhandene Adressen:\n' +
|
||||||
|
alle.map((u) => ` ${u.email}${u.geloeschtAm ? ' (im Papierkorb)' : u.aktiv ? '' : ' (deaktiviert)'}`).join('\n')
|
||||||
|
: '\n Es gibt ueberhaupt keinen Zugang. Leg ihn im Browser unter /ersteinrichtung an.',
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const zweiterFaktor = benutzer.totpAktiviertAm
|
||||||
|
? `eingerichtet am ${zeit(benutzer.totpAktiviertAm)}`
|
||||||
|
: benutzer.totpSecretVerschluesselt
|
||||||
|
? 'vorbereitet, aber NICHT bestaetigt - beim Anmelden wird noch kein Code verlangt'
|
||||||
|
: 'noch nicht eingerichtet - beim Anmelden wird noch kein Code verlangt'
|
||||||
|
|
||||||
|
console.log(` Name: ${benutzer.name}`)
|
||||||
|
console.log(` Rolle: ${benutzer.rolle}`)
|
||||||
|
console.log(` Aktiv: ${benutzer.aktiv ? 'ja' : 'NEIN - Anmeldung nicht moeglich'}`)
|
||||||
|
console.log(` Papierkorb: ${benutzer.geloeschtAm ? 'JA seit ' + zeit(benutzer.geloeschtAm) : 'nein'}`)
|
||||||
|
console.log(` Zweiter Faktor: ${zweiterFaktor}`)
|
||||||
|
console.log(` Wiederherstellung: ${benutzer.wiederherstellungscodes.length} Codes uebrig`)
|
||||||
|
console.log(` Passwort gesetzt: ${zeit(benutzer.passwortGeaendertAm)}`)
|
||||||
|
console.log(` Letzte Anmeldung: ${zeit(benutzer.letzteAnmeldung)}`)
|
||||||
|
|
||||||
|
const zugaenge = await db.betreuungsZugang.count({ where: { userId: benutzer.id } })
|
||||||
|
const betreute = await db.betreute.count({ where: { geloeschtAm: null } })
|
||||||
|
console.log(` Betreute Personen: ${zugaenge} zugeordnet, ${betreute} insgesamt angelegt`)
|
||||||
|
|
||||||
|
if (zugaenge === 0 && betreute === 0) {
|
||||||
|
console.log(
|
||||||
|
'\n Hinweis: Nach dem Anmelden landest du zuerst bei der\n' +
|
||||||
|
' Ersteinrichtung - erst zweiter Faktor, dann betreute Person.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Versuche der letzten Tage -------------------------------------------
|
||||||
|
const versuche = await db.loginVersuch.findMany({
|
||||||
|
where: { email },
|
||||||
|
orderBy: { zeitpunkt: 'desc' },
|
||||||
|
take: 15,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n══ Letzte Anmeldeversuche ' + '═'.repeat(35))
|
||||||
|
|
||||||
|
if (versuche.length === 0) {
|
||||||
|
console.log(
|
||||||
|
' Keine. Es ist also NIE ein Versuch beim Server angekommen.\n\n' +
|
||||||
|
' Das heisst: Das Formular hat gar nicht abgeschickt, oder es lief\n' +
|
||||||
|
' auf eine andere Adresse. Pruef AUTH_URL - siehe unten.',
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
for (const v of versuche) {
|
||||||
|
const kennzeichen = v.erfolg ? 'OK ' : 'FEHLER'
|
||||||
|
const grund = v.erfolg ? 'angemeldet' : (GRUND[v.grund] ?? v.grund ?? 'unbekannter Grund')
|
||||||
|
console.log(` ${zeit(v.zeitpunkt).padEnd(20)} ${kennzeichen} ${grund}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sperre ---------------------------------------------------------------
|
||||||
|
const fenster = Number.parseInt(process.env.LOGIN_FENSTER_SEKUNDEN ?? '900', 10)
|
||||||
|
const grenze = Number.parseInt(process.env.LOGIN_MAX_VERSUCHE ?? '5', 10)
|
||||||
|
const seit = new Date(Date.now() - fenster * 1000)
|
||||||
|
|
||||||
|
const offen = await db.loginVersuch.count({
|
||||||
|
where: { email, erfolg: false, zeitpunkt: { gte: seit } },
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n══ Sperre ' + '═'.repeat(51))
|
||||||
|
console.log(` ${offen} von ${grenze} Fehlversuchen im laufenden Fenster (${fenster / 60} Minuten)`)
|
||||||
|
|
||||||
|
if (offen >= grenze) {
|
||||||
|
console.log(
|
||||||
|
'\n GESPERRT. Warte das Fenster ab, oder setz die Zaehler zurueck:\n' +
|
||||||
|
' docker compose exec db psql -U cockpit betreuungs_cockpit \\\n' +
|
||||||
|
' -c "DELETE FROM login_versuch WHERE erfolg = false;"',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Umgebung -------------------------------------------------------------
|
||||||
|
console.log('\n══ Umgebung ' + '═'.repeat(49))
|
||||||
|
console.log(` AUTH_URL: ${process.env.AUTH_URL ?? 'NICHT GESETZT'}`)
|
||||||
|
console.log(
|
||||||
|
'\n Diese Adresse muss EXAKT die sein, die du im Browser aufrufst -\n' +
|
||||||
|
' gleiches Schema (https), gleicher Hostname, kein Schraegstrich am\n' +
|
||||||
|
' Ende. Weicht sie ab, wird das Sitzungs-Cookie fuer die falsche\n' +
|
||||||
|
' Herkunft gesetzt: Die Anmeldung landet ohne Fehlermeldung wieder\n' +
|
||||||
|
' auf dem Formular, und oben steht kein einziger Fehlversuch.\n',
|
||||||
|
)
|
||||||
|
} catch (fehler) {
|
||||||
|
// Ein Prisma-Stacktrace hilft hier niemandem weiter.
|
||||||
|
const meldung = fehler instanceof Error ? fehler.message : String(fehler)
|
||||||
|
|
||||||
|
if (/Can't reach database server|ECONNREFUSED/i.test(meldung)) {
|
||||||
|
// Das Passwort in der Verbindungszeichenkette gehoert nicht auf den Schirm.
|
||||||
|
const url = (process.env.DATABASE_URL ?? 'NICHT GESETZT').replace(/:[^:@/]+@/, ':***@')
|
||||||
|
console.error(
|
||||||
|
[
|
||||||
|
'',
|
||||||
|
'Die Datenbank ist nicht erreichbar.',
|
||||||
|
'',
|
||||||
|
` DATABASE_URL: ${url}`,
|
||||||
|
'',
|
||||||
|
' Im Docker-Betrieb gehoert dieses Skript IN den Behaelter:',
|
||||||
|
' docker compose exec app node scripts/anmeldung-pruefen.mjs <e-mail>',
|
||||||
|
'',
|
||||||
|
' Laeuft die Datenbank ueberhaupt?',
|
||||||
|
' docker compose ps',
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
console.error(`\nFehler: ${meldung}\n`)
|
||||||
|
}
|
||||||
|
process.exitCode = 1
|
||||||
|
} finally {
|
||||||
|
await db.$disconnect()
|
||||||
|
}
|
||||||
+19
-5
@@ -58,14 +58,25 @@ function einmal<T>(fn: () => T): () => T {
|
|||||||
|
|
||||||
const holeVerschluesselungsschluessel = einmal((): Map<number, Buffer> => {
|
const holeVerschluesselungsschluessel = einmal((): Map<number, Buffer> => {
|
||||||
const schluessel = new Map<number, Buffer>()
|
const schluessel = new Map<number, Buffer>()
|
||||||
|
|
||||||
// ENC_KEY_V1, ENC_KEY_V2, ... - alte Versionen bleiben zum Entschluesseln
|
// ENC_KEY_V1, ENC_KEY_V2, ... - alte Versionen bleiben zum Entschluesseln
|
||||||
// stehen, verschluesselt wird immer mit der aktiven Version.
|
// stehen, verschluesselt wird immer mit der aktiven Version.
|
||||||
for (const [name] of Object.entries(process.env)) {
|
for (const [name, wert] of Object.entries(process.env)) {
|
||||||
const treffer = name.match(/^ENC_KEY_V(\d+)$/)
|
const treffer = name.match(/^ENC_KEY_V(\d+)$/)
|
||||||
if (treffer) {
|
if (!treffer) continue
|
||||||
schluessel.set(Number.parseInt(treffer[1]!, 10), schluessel32(name))
|
|
||||||
}
|
// Eine LEERE Variable ist kein Schluessel.
|
||||||
|
//
|
||||||
|
// docker compose reicht ENC_KEY_V2 immer durch - mit leerem Wert, wenn
|
||||||
|
// in der .env keiner steht. Wuerde hier allein der Name zaehlen, waere
|
||||||
|
// jede Installation ohne zweiten Schluessel kaputt, und zwar mit der
|
||||||
|
// irrefuehrenden Meldung "ENC_KEY_V2 fehlt" - obwohl niemand einen
|
||||||
|
// zweiten Schluessel wollte.
|
||||||
|
if (!wert || wert.trim() === '') continue
|
||||||
|
|
||||||
|
schluessel.set(Number.parseInt(treffer[1]!, 10), schluessel32(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schluessel.size === 0) {
|
if (schluessel.size === 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Kein Verschluesselungsschluessel gefunden. Es muss mindestens ENC_KEY_V1 ' +
|
'Kein Verschluesselungsschluessel gefunden. Es muss mindestens ENC_KEY_V1 ' +
|
||||||
@@ -78,8 +89,11 @@ const holeVerschluesselungsschluessel = einmal((): Map<number, Buffer> => {
|
|||||||
const holeAktiveVersion = einmal((): number => {
|
const holeAktiveVersion = einmal((): number => {
|
||||||
const version = zahl('ENC_KEY_AKTIV', 1)
|
const version = zahl('ENC_KEY_AKTIV', 1)
|
||||||
if (!holeVerschluesselungsschluessel().has(version)) {
|
if (!holeVerschluesselungsschluessel().has(version)) {
|
||||||
|
const vorhanden = [...holeVerschluesselungsschluessel().keys()].sort().join(', ')
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`ENC_KEY_AKTIV=${version}, aber ENC_KEY_V${version} ist nicht gesetzt.`,
|
`ENC_KEY_AKTIV=${version}, aber ENC_KEY_V${version} ist nicht gesetzt oder leer. ` +
|
||||||
|
`Vorhanden sind die Versionen: ${vorhanden}. Beim Rotieren zuerst den ` +
|
||||||
|
`neuen Schluessel eintragen, danach ENC_KEY_AKTIV umstellen.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return version
|
return version
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/** Muss zur "version" in der package.json passen. */
|
/** Muss zur "version" in der package.json passen. */
|
||||||
export const VERSION = '1.0.1'
|
export const VERSION = '1.0.2'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kurzer Git-Stand, beim Bauen hineingereicht:
|
* Kurzer Git-Stand, beim Bauen hineingereicht:
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auswertung der Umgebungsvariablen.
|
||||||
|
*
|
||||||
|
* Der Anlass für diese Datei war ein Fehler im Betrieb: docker compose
|
||||||
|
* reicht `ENC_KEY_V2` immer durch — mit leerem Wert, wenn in der .env
|
||||||
|
* keiner steht. Die Schlüsselsammlung erkannte Schlüssel aber am
|
||||||
|
* Variablennamen statt am Inhalt, fand also ein leeres `ENC_KEY_V2` und
|
||||||
|
* brach ab. Die Meldung lautete „ENC_KEY_V2 fehlt", obwohl niemand einen
|
||||||
|
* zweiten Schlüssel wollte.
|
||||||
|
*
|
||||||
|
* Jede Prüfung hier lädt `@/lib/env` frisch, weil die Werte beim ersten
|
||||||
|
* Zugriff gemerkt werden.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ECHT = Buffer.alloc(32, 7).toString('base64')
|
||||||
|
const ECHT_ZWEI = Buffer.alloc(32, 9).toString('base64')
|
||||||
|
|
||||||
|
let vorher: NodeJS.ProcessEnv
|
||||||
|
|
||||||
|
async function frischesEnv() {
|
||||||
|
// Leert die Modulregistrierung, damit der naechste Import das Modul neu
|
||||||
|
// auswertet - die Schluessel werden sonst nur beim ersten Mal gelesen.
|
||||||
|
vi.resetModules()
|
||||||
|
const { env } = await import('@/lib/env')
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vorher = { ...process.env }
|
||||||
|
for (const name of Object.keys(process.env)) {
|
||||||
|
if (/^ENC_KEY_V\d+$/.test(name)) delete process.env[name]
|
||||||
|
}
|
||||||
|
delete process.env.ENC_KEY_AKTIV
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = vorher
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Verschlüsselungsschlüssel aus der Umgebung', () => {
|
||||||
|
it('nimmt einen einzelnen Schlüssel an', async () => {
|
||||||
|
process.env.ENC_KEY_V1 = ECHT
|
||||||
|
|
||||||
|
const env = await frischesEnv()
|
||||||
|
expect([...env.verschluesselungsschluessel.keys()]).toEqual([1])
|
||||||
|
expect(env.aktiveSchluesselversion).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('überspringt eine leere Schlüsselvariable', async () => {
|
||||||
|
// Genau das setzt docker compose, wenn in der .env kein zweiter
|
||||||
|
// Schlüssel steht: ENC_KEY_V2: ${ENC_KEY_V2:-}
|
||||||
|
process.env.ENC_KEY_V1 = ECHT
|
||||||
|
process.env.ENC_KEY_V2 = ''
|
||||||
|
|
||||||
|
const env = await frischesEnv()
|
||||||
|
expect([...env.verschluesselungsschluessel.keys()]).toEqual([1])
|
||||||
|
expect(env.aktiveSchluesselversion).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('überspringt eine Variable, in der nur Leerzeichen stehen', async () => {
|
||||||
|
process.env.ENC_KEY_V1 = ECHT
|
||||||
|
process.env.ENC_KEY_V2 = ' '
|
||||||
|
|
||||||
|
const env = await frischesEnv()
|
||||||
|
expect([...env.verschluesselungsschluessel.keys()]).toEqual([1])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('nimmt beide Schlüssel an, wenn wirklich rotiert wird', async () => {
|
||||||
|
process.env.ENC_KEY_V1 = ECHT
|
||||||
|
process.env.ENC_KEY_V2 = ECHT_ZWEI
|
||||||
|
process.env.ENC_KEY_AKTIV = '2'
|
||||||
|
|
||||||
|
const env = await frischesEnv()
|
||||||
|
expect([...env.verschluesselungsschluessel.keys()].sort()).toEqual([1, 2])
|
||||||
|
expect(env.aktiveSchluesselversion).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sagt beim Rotieren deutlich, wenn der neue Schlüssel fehlt', async () => {
|
||||||
|
process.env.ENC_KEY_V1 = ECHT
|
||||||
|
process.env.ENC_KEY_V2 = ''
|
||||||
|
process.env.ENC_KEY_AKTIV = '2'
|
||||||
|
|
||||||
|
const env = await frischesEnv()
|
||||||
|
expect(() => env.aktiveSchluesselversion).toThrow(/ENC_KEY_AKTIV=2/)
|
||||||
|
// Die Meldung nennt, was da ist, und die richtige Reihenfolge.
|
||||||
|
expect(() => env.aktiveSchluesselversion).toThrow(/Vorhanden sind die Versionen: 1/)
|
||||||
|
expect(() => env.aktiveSchluesselversion).toThrow(/zuerst den neuen Schluessel/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('beschwert sich, wenn gar kein Schlüssel gesetzt ist', async () => {
|
||||||
|
process.env.ENC_KEY_V2 = ''
|
||||||
|
|
||||||
|
const env = await frischesEnv()
|
||||||
|
expect(() => env.verschluesselungsschluessel).toThrow(/mindestens ENC_KEY_V1/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('weist einen Schlüssel falscher Länge ab', async () => {
|
||||||
|
process.env.ENC_KEY_V1 = Buffer.alloc(16, 1).toString('base64')
|
||||||
|
|
||||||
|
const env = await frischesEnv()
|
||||||
|
expect(() => env.verschluesselungsschluessel).toThrow(/32 Bytes/)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user