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

@@ -2,6 +2,7 @@ import { importSpielplan } from '../utils/spielplan-import.js'
import { importLeagueTables } from '../utils/spielklassen-tables-import.js'
import { publishImportedSpielplan } from '../utils/spielplan-publish.js'
import { info as loggerInfo, error as loggerError } from '../utils/logger.js'
import { cleanupPasswordResetLogs } from '../utils/password-reset-log.js'
const TIME_ZONE = 'Europe/Berlin'
const RUN_HOUR = 7
@@ -65,11 +66,22 @@ function nextRunAt(now = new Date()) {
return candidate
}
async function runImport(reason) {
async function runDailyJobs(reason, skipSpielplanImport = false) {
if (running) return
running = true
try {
try {
const cleanup = await cleanupPasswordResetLogs()
loggerInfo(`[password-reset-log] ${reason}: Bereinigung abgeschlossen`, cleanup)
} catch (error) {
loggerError('[password-reset-log] Bereinigung fehlgeschlagen:', { error })
}
if (skipSpielplanImport) {
return
}
const spielplan = await importSpielplan()
loggerInfo(`[spielplan-import] ${reason}: ${spielplan.matchCount} Spiele importiert`, { range: `${spielplan.source.season.dateStart} - ${spielplan.source.season.dateEnd}` })
@@ -96,13 +108,13 @@ async function runImport(reason) {
}
}
function scheduleNext() {
function scheduleNext(skipSpielplanImport = false) {
const runAt = nextRunAt()
const delay = Math.min(Math.max(runAt.getTime() - Date.now(), 1_000), MAX_TIMEOUT)
timer = setTimeout(async () => {
await runImport('taeglicher Lauf')
scheduleNext()
await runDailyJobs('taeglicher Lauf', skipSpielplanImport)
scheduleNext(skipSpielplanImport)
}, delay)
timer.unref?.()
@@ -110,15 +122,15 @@ function scheduleNext() {
}
export default defineNitroPlugin((nitroApp) => {
if (process.env.SPIELPLAN_IMPORT_DISABLED === 'true') {
loggerInfo('[spielplan-import] Scheduler deaktiviert')
return
const skipSpielplanImport = process.env.SPIELPLAN_IMPORT_DISABLED === 'true'
if (skipSpielplanImport) {
loggerInfo('[spielplan-import] Import deaktiviert; Passwort-Reset-Log-Bereinigung bleibt aktiv')
}
scheduleNext()
scheduleNext(skipSpielplanImport)
if (process.env.SPIELPLAN_IMPORT_RUN_ON_START === 'true') {
runImport('Startlauf')
runDailyJobs('Startlauf', skipSpielplanImport)
}
nitroApp.hooks.hookOnce('close', () => {