From aabf94941a4b402e852ece5469a6278f7328ff83 Mon Sep 17 00:00:00 2001 From: aldemirm Date: Mon, 14 Sep 2026 11:11:14 +0200 Subject: [PATCH] - password validation - Adding and refactoring tests --- .../oliver/kidio/backend/plugins/Routing.kt | 335 +++++++++--------- 1 file changed, 169 insertions(+), 166 deletions(-) diff --git a/src/main/kotlin/com/oliver/kidio/backend/plugins/Routing.kt b/src/main/kotlin/com/oliver/kidio/backend/plugins/Routing.kt index 1960858..3623301 100644 --- a/src/main/kotlin/com/oliver/kidio/backend/plugins/Routing.kt +++ b/src/main/kotlin/com/oliver/kidio/backend/plugins/Routing.kt @@ -33,187 +33,190 @@ fun isValidPassword(password: String): Boolean { } fun Application.configureRouting(repository: KidioRepository, jwtService : JwtService) { -fun Application.configureRouting( - repository: KidioRepository, - jwtService: JwtService, -) { - routing { - post("/api/v1/auth/register") { - val registerRequest = call.receive() - if (registerRequest.email.isBlank() || registerRequest.passwordPlain.isBlank()) { - call.respond(HttpStatusCode.BadRequest, "Bitte füllen Sie alle Felder aus") - return@post - } - if (!isValidPassword(registerRequest.passwordPlain)) { - call.respond( - HttpStatusCode.BadRequest, - "Das Passwort muss mindestens 8 Zeichen lang sein, mindestens einen Großbuchstaben und ein Sonderzeichen enthalten und darf keine Emojis enthalten." - ) - return@post - } - if (registerRequest.parentPin.length != 4) { - call.respond(HttpStatusCode.BadRequest, "Die Eltern-PIN muss genau 4 Ziffern lang sein") - return@post - } - val existingUser = repository.findByEmail(registerRequest.email) - if (existingUser != null) { - call.respond(HttpStatusCode.Conflict, "Ein Benutzer mit dieser E-Mail-Adresse existiert bereits") - return@post - } - - // Backend generiert die eindeutige User-ID - val userId = UUID.randomUUID().toString() - val passwordHash = BCrypt.hashpw(registerRequest.passwordPlain, BCrypt.gensalt()) - - // datenbank insert hinzufügen - repository.registerUser( - User( - userId, - registerRequest.email, - passwordHash, - registerRequest.familyName, - registerRequest.childAge, - registerRequest.parentPin, - ), - ) - - val token = jwtService.generateToken(userId) - - call.respond( - HttpStatusCode.Created, - mapOf( - "token" to token, - "userId" to userId, - "message" to "Registrierung erfolgreich", - ), - ) - } - // Health-Check - get("/api/v1/health") { - call.respond(mapOf("status" to "OK", "message" to "Kidio Backend läuft!")) - } - - post("/api/v1/auth/login") { - val loginRequest = call.receive() - if (loginRequest.email.isBlank() || loginRequest.passwordPlain.isBlank()) { - call.respond(HttpStatusCode.BadRequest, "Bitte füllen Sie alle Felder aus") - return@post - } - val getUser = repository.findByEmail(loginRequest.email) - if (getUser == null || !repository.verifyPassword(loginRequest.passwordPlain, getUser.passwordHash)) { - call.respond(HttpStatusCode.Unauthorized, "Ungültige Anmeldedaten") - return@post - } - val token = jwtService.generateToken(getUser.id) - call.respond( - HttpStatusCode.OK, - AuthResponse( - token, - userId = getUser.id, - familyName = getUser.familyName, - childAge = getUser.childAge, - ), - ) - return@post - - - - - } - - authenticate("auth-jwt") { - - // 1. Alle verfügbaren Aufgaben abrufen - get("/api/v1/tasks") { - val tasks = repository.getAllTasks() - call.respond(tasks) - } - - // 2. Antwort auf eine Aufgabe überprüfen - post("/api/v1/tasks/verify") { - val request = call.receive() - val principal = call.principal() - val userId = principal?.userId - if (userId == null) { - call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiges oder abgelaufenes Token")) + fun Application.configureRouting( + repository: KidioRepository, + jwtService: JwtService, + ) { + routing { + post("/api/v1/auth/register") { + val registerRequest = call.receive() + if (registerRequest.email.isBlank() || registerRequest.passwordPlain.isBlank()) { + call.respond(HttpStatusCode.BadRequest, "Bitte füllen Sie alle Felder aus") return@post } - val taskInfo = repository.getTaskAnswerAndReward(request.taskId) - if (taskInfo == null) { + if (!isValidPassword(registerRequest.passwordPlain)) { call.respond( - TaskVerifyResponse( - isCorrect = false, - earnedMinutes = 0, - message = "Aufgabe nicht gefunden!", - ), + HttpStatusCode.BadRequest, + "Das Passwort muss mindestens 8 Zeichen lang sein, mindestens einen Großbuchstaben und ein Sonderzeichen enthalten und darf keine Emojis enthalten." ) return@post } + if (registerRequest.parentPin.length != 4) { + call.respond(HttpStatusCode.BadRequest, "Die Eltern-PIN muss genau 4 Ziffern lang sein") + return@post + } + val existingUser = repository.findByEmail(registerRequest.email) + if (existingUser != null) { + call.respond(HttpStatusCode.Conflict, "Ein Benutzer mit dieser E-Mail-Adresse existiert bereits") + return@post + } - val (correctAnswer, reward) = taskInfo - val isCorrect = request.selectedAnswer.trim() == correctAnswer.trim() + // Backend generiert die eindeutige User-ID + val userId = UUID.randomUUID().toString() + val passwordHash = BCrypt.hashpw(registerRequest.passwordPlain, BCrypt.gensalt()) + + // datenbank insert hinzufügen + repository.registerUser( + User( + userId, + registerRequest.email, + passwordHash, + registerRequest.familyName, + registerRequest.childAge, + registerRequest.parentPin, + ), + ) + + val token = jwtService.generateToken(userId) + + call.respond( + HttpStatusCode.Created, + mapOf( + "token" to token, + "userId" to userId, + "message" to "Registrierung erfolgreich", + ), + ) + } + // Health-Check + get("/api/v1/health") { + call.respond(mapOf("status" to "OK", "message" to "Kidio Backend läuft!")) + } + + post("/api/v1/auth/login") { + val loginRequest = call.receive() + if (loginRequest.email.isBlank() || loginRequest.passwordPlain.isBlank()) { + call.respond(HttpStatusCode.BadRequest, "Bitte füllen Sie alle Felder aus") + return@post + } + val getUser = repository.findByEmail(loginRequest.email) + if (getUser == null || !repository.verifyPassword(loginRequest.passwordPlain, getUser.passwordHash)) { + call.respond(HttpStatusCode.Unauthorized, "Ungültige Anmeldedaten") + return@post + } + val token = jwtService.generateToken(getUser.id) + call.respond( + HttpStatusCode.OK, + AuthResponse( + token, + userId = getUser.id, + familyName = getUser.familyName, + childAge = getUser.childAge, + ), + ) + return@post + + + } + + authenticate("auth-jwt") { + + // 1. Alle verfügbaren Aufgaben abrufen + get("/api/v1/tasks") { + val tasks = repository.getAllTasks() + call.respond(tasks) + } + + // 2. Antwort auf eine Aufgabe überprüfen + post("/api/v1/tasks/verify") { + val request = call.receive() + val principal = call.principal() + val userId = principal?.userId + if (userId == null) { + call.respond( + HttpStatusCode.Unauthorized, + mapOf("error" to "Ungültiges oder abgelaufenes Token") + ) + return@post + } + val taskInfo = repository.getTaskAnswerAndReward(request.taskId) + if (taskInfo == null) { + call.respond( + TaskVerifyResponse( + isCorrect = false, + earnedMinutes = 0, + message = "Aufgabe nicht gefunden!", + ), + ) + return@post + } + + val (correctAnswer, reward) = taskInfo + val isCorrect = request.selectedAnswer.trim() == correctAnswer.trim() + + if (isCorrect) { + val currentScreentime = repository.getScreentime(userId) + val newSeconds = currentScreentime + (reward * 60) + repository.updateScreentime(userId, newSeconds) + + call.respond( + TaskVerifyResponse( + isCorrect = true, + earnedMinutes = reward, + message = "Super gemacht! Du hast $reward Minuten gewonnen.", + ), + ) + } else { + call.respond( + TaskVerifyResponse( + isCorrect = false, + earnedMinutes = 0, + message = "Schade, das war leider falsch. Versuche es nochmal!", + ), + ) + } + } + get("/api/v1/screentime") { + val principal = call.principal() + val userid = + principal?.userId + ?: run { + call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiger Token")) + return@get + } + val remainingSeconds = repository.getScreentime(userid) + call.respond( + HttpStatusCode.OK, + SyncResponse( + remainingSeconds = remainingSeconds, + isBlocked = remainingSeconds <= 0, + ), + ) + } + + // 3. Bildschirmzeit synchronisieren + post("/api/v1/screentime/sync") { + val request = call.receive() + + // Wir holen die echte, fälschungssichere userId aus dem Login-Token: + val principal = call.principal() + val userId = + principal?.userId ?: request.userId // Fallback auf request.userId, falls token noch fehlt + + val currentSeconds = repository.getScreentime(userId) + var newSeconds = currentSeconds - request.usedSecondsSinceLastSync + if (newSeconds < 0) newSeconds = 0 - if (isCorrect) { - val currentScreentime = repository.getScreentime(userId) - val newSeconds = currentScreentime + (reward * 60) repository.updateScreentime(userId, newSeconds) call.respond( - TaskVerifyResponse( - isCorrect = true, - earnedMinutes = reward, - message = "Super gemacht! Du hast $reward Minuten gewonnen.", - ), - ) - } else { - call.respond( - TaskVerifyResponse( - isCorrect = false, - earnedMinutes = 0, - message = "Schade, das war leider falsch. Versuche es nochmal!", + SyncResponse( + remainingSeconds = newSeconds, + isBlocked = newSeconds <= 0, ), ) } } - get("/api/v1/screentime") { - val principal = call.principal() - val userid = - principal?.userId - ?: run { - call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Ungültiger Token")) - return@get - } - val remainingSeconds = repository.getScreentime(userid) - call.respond( - HttpStatusCode.OK, - SyncResponse( - remainingSeconds = remainingSeconds, - isBlocked = remainingSeconds <= 0, - ), - ) - } - - // 3. Bildschirmzeit synchronisieren - post("/api/v1/screentime/sync") { - val request = call.receive() - - // Wir holen die echte, fälschungssichere userId aus dem Login-Token: - val principal = call.principal() - val userId = principal?.userId ?: request.userId // Fallback auf request.userId, falls token noch fehlt - - val currentSeconds = repository.getScreentime(userId) - var newSeconds = currentSeconds - request.usedSecondsSinceLastSync - if (newSeconds < 0) newSeconds = 0 - - repository.updateScreentime(userId, newSeconds) - - call.respond( - SyncResponse( - remainingSeconds = newSeconds, - isBlocked = newSeconds <= 0, - ), - ) - } } } }