feat(auth): implement Android refresh token handling and session management
Some checks failed
Code Analysis and Production Deploy / analyze (push) Failing after 5m7s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Has been skipped

- Added support for generating Android access tokens and managing refresh sessions in the auth endpoints.
- Implemented new tests for login, logout, and refresh functionalities specific to Android clients.
- Enhanced password reset logging with normalization and masking of email addresses.
- Created a new diagnostics endpoint for password reset attempts, including filtering and summarizing logs.
- Introduced a new utility for managing password reset logs with retention policies.
- Added tests for password reset log utilities to ensure proper functionality and privacy compliance.
- Updated WebAuthn configuration tests to validate origin handling for production and allowed origins.
This commit is contained in:
Torsten Schulz (local)
2026-05-27 19:34:32 +02:00
parent 755442fb70
commit 58fd7fa5c6
32 changed files with 1477 additions and 180 deletions

View File

@@ -0,0 +1,74 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const filesystem = vi.hoisted(() => ({
mkdir: vi.fn(),
appendFile: vi.fn(),
readFile: vi.fn(),
writeFile: vi.fn()
}))
vi.mock('fs/promises', () => ({
default: filesystem
}))
import {
cleanupPasswordResetLogs,
fingerprintResetEmail,
maskResetEmail,
normalizeResetEmail,
writePasswordResetLog
} from '../server/utils/password-reset-log.js'
describe('Password reset diagnostic log privacy helpers', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('normalisiert E-Mail-Adressen für Lookup und Korrelation', () => {
expect(normalizeResetEmail(' User@Example.com ')).toBe('user@example.com')
expect(fingerprintResetEmail(' User@Example.com ')).toBe(fingerprintResetEmail('user@example.com'))
})
it('maskiert die E-Mail-Adresse für Diagnoseausgaben', () => {
const masked = maskResetEmail('ag2608@googlemail.com')
expect(masked).toBe('ag***@go***.com')
expect(masked).not.toContain('ag2608')
expect(masked).not.toContain('googlemail')
})
it('entfernt Diagnoseeinträge nach 72 Stunden', async () => {
const now = Date.parse('2026-05-27T12:00:00.000Z')
filesystem.readFile.mockResolvedValue([
JSON.stringify({ ts: '2026-05-24T11:59:59.000Z', requestId: 'alt' }),
JSON.stringify({ ts: '2026-05-24T12:00:00.000Z', requestId: 'neu' }),
''
].join('\n'))
const result = await cleanupPasswordResetLogs(now)
expect(result).toEqual({ retained: 1, removed: 1 })
expect(filesystem.writeFile).toHaveBeenCalledWith(
expect.any(String),
`${JSON.stringify({ ts: '2026-05-24T12:00:00.000Z', requestId: 'neu' })}\n`,
'utf8'
)
})
it('schreibt bereinigte Fehlerdetails ohne E-Mail oder Credentials', async () => {
await writePasswordResetLog({
requestId: 'r1',
email: 'ag2608@googlemail.com',
step: 'mail_send',
status: 'failed',
error: Object.assign(new Error('Versand an ag2608@googlemail.com fehlgeschlagen password=geheim'), { code: 'EAUTH' })
})
const payload = filesystem.appendFile.mock.calls[0][1]
expect(payload).toContain('"errorCode":"EAUTH"')
expect(payload).toContain('ag***@go***.com')
expect(payload).toContain('password=[redacted]')
expect(payload).not.toContain('ag2608@googlemail.com')
expect(payload).not.toContain('geheim')
})
})