docker-compose.yml reicht ENC_KEY_V2 immer an den Behaelter durch - mit leerem Wert, wenn in der .env keiner steht. src/lib/env.ts erkannte Schluessel aber am Variablennamen statt am Inhalt, fand also ein leeres ENC_KEY_V2 und brach ab mit "Umgebungsvariable ENC_KEY_V2 fehlt". Die Meldung war doppelt irrefuehrend: Sie nannte einen Schluessel, den niemand wollte, und sie erschien an einer Stelle, die damit nichts zu tun hat - beim Einrichten des zweiten Faktors, als Serverfehler ohne Text. Betroffen war jede Stelle, die verschluesselte Felder schreibt oder liest, also praktisch alles ausser der Anmeldung selbst. Die kam durch, weil sie ohne Verschluesselung auskommt - der Fehler trat deshalb erst auf der naechsten Seite auf, und der Zusammenhang war nicht zu sehen. Leere Schluesselvariablen werden jetzt uebersprungen. Sieben Tests decken die Auswertung ab, darunter genau diese Konstellation. Die Meldung beim Schluesselwechsel nennt jetzt ausserdem, welche Versionen vorhanden sind, und die richtige Reihenfolge: erst den neuen Schluessel eintragen, dann ENC_KEY_AKTIV umstellen. Dazu scripts/anmeldung-pruefen.mjs. Das Anmeldeformular sagt bewusst nicht, woran eine Anmeldung scheiterte - sonst liesse 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, zusammen mit dem Zustand des Zugangs, dem Stand der Sperre und der AUTH_URL. Etappe: 8 Phase: Haertung
106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
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/)
|
|
})
|
|
})
|