Drei Wege hinein, je Instanz zuschaltbar: generischer OIDC-Anbieter (Entra ID beziehungsweise authentik), Passkeys und wahlweise E-Mail mit Passwort. Entscheidungen: - Passkeys laufen direkt über SimpleWebAuthn 14, nicht über den experimentellen WebAuthn-Provider von Auth.js, der noch auf Fassung 9 festgelegt ist. - Eigener Prisma-Adapter, weil der mitgelieferte fest auf prisma.user und prisma.account zugreift, unsere Modelle aber Benutzer und Konto heißen. - Sitzungen als JWT, weil Credentials-Provider das verlangen. Damit eine Sperre trotzdem sofort wirkt, liest der session-Callback Rolle und Status bei jeder Anfrage frisch aus der Datenbank. Die Tabellen sitzung und verifizierungs_token entfallen deshalb wieder. - allowDangerousEmailAccountLinking ist bewusst an: je Instanz gibt es genau einen vertrauenswürdigen Anbieter, der die Adresse selbst prüft. Anmeldung, Passwort und Passkey antworten bei Fehlschlag immer gleich, damit sich nicht abfragen lässt, wer hier ein Konto hat. Die WebAuthn-Herausforderung wird nach einem Versuch verworfen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
PASSWORT_HOECHSTLAENGE,
|
|
PASSWORT_MINDESTLAENGE,
|
|
passwortIstGut,
|
|
passwortMaengel,
|
|
} from './passwort'
|
|
|
|
describe('passwortMaengel', () => {
|
|
it('nimmt ein ausreichend langes Passwort an', () => {
|
|
expect(passwortMaengel('richtiges pferd batterie klammer')).toEqual([])
|
|
})
|
|
|
|
it('bemaengelt zu kurze Passwoerter', () => {
|
|
expect(passwortMaengel('kurz')).toContain(
|
|
`Mindestens ${PASSWORT_MINDESTLAENGE} Zeichen.`,
|
|
)
|
|
})
|
|
|
|
it('setzt die Grenze bei genau der Mindestlaenge', () => {
|
|
expect(
|
|
passwortMaengel('a'.repeat(PASSWORT_MINDESTLAENGE - 1)).length,
|
|
).toBeGreaterThan(0)
|
|
// Genau die Mindestlaenge, aber ein einziges wiederholtes Zeichen.
|
|
expect(passwortMaengel('a'.repeat(PASSWORT_MINDESTLAENGE))).toEqual([
|
|
'Darf nicht aus einem einzigen wiederholten Zeichen bestehen.',
|
|
])
|
|
expect(passwortMaengel('abcdefghijkl')).toEqual([])
|
|
})
|
|
|
|
it('weist ueberlange Eingaben ab, damit das Hashen begrenzt bleibt', () => {
|
|
expect(passwortMaengel('a'.repeat(PASSWORT_HOECHSTLAENGE + 1))).toContain(
|
|
`Höchstens ${PASSWORT_HOECHSTLAENGE} Zeichen.`,
|
|
)
|
|
})
|
|
|
|
it('weist reine Leerzeichen ab', () => {
|
|
expect(passwortMaengel(' '.repeat(20))).toContain(
|
|
'Darf nicht nur aus Leerzeichen bestehen.',
|
|
)
|
|
})
|
|
|
|
it('weist ein einzelnes wiederholtes Zeichen ab', () => {
|
|
expect(passwortMaengel('xxxxxxxxxxxxxxxx')).toContain(
|
|
'Darf nicht aus einem einzigen wiederholten Zeichen bestehen.',
|
|
)
|
|
})
|
|
|
|
it('meldet mehrere Maengel gleichzeitig', () => {
|
|
expect(passwortMaengel('aaa').length).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('passwortIstGut', () => {
|
|
it('fasst das Ergebnis zusammen', () => {
|
|
expect(passwortIstGut('richtiges pferd batterie')).toBe(true)
|
|
expect(passwortIstGut('kurz')).toBe(false)
|
|
})
|
|
})
|