Compare commits
No commits in common. "de337902061cbd9097869cf55e12dbc151991843" and "ad90c4c7d77895dad2436ec64e738fbcd1629731" have entirely different histories.
de33790206
...
ad90c4c7d7
Binary file not shown.
Binary file not shown.
|
|
@ -19,26 +19,20 @@ dependencies {
|
||||||
implementation(ktorLibs.server.config.yaml)
|
implementation(ktorLibs.server.config.yaml)
|
||||||
implementation(ktorLibs.server.core)
|
implementation(ktorLibs.server.core)
|
||||||
implementation(ktorLibs.server.netty)
|
implementation(ktorLibs.server.netty)
|
||||||
|
|
||||||
// JWT Authentication
|
|
||||||
implementation("io.ktor:ktor-server-auth")
|
|
||||||
implementation("io.ktor:ktor-server-auth-jwt")
|
|
||||||
|
|
||||||
implementation(libs.logback.classic)
|
implementation(libs.logback.classic)
|
||||||
|
|
||||||
// JSON-Unterstützung und Serialization
|
// JSON-Unterstützung und Serialization
|
||||||
implementation("io.ktor:ktor-server-content-negotiation-jvm")
|
implementation("io.ktor:ktor-server-content-negotiation-jvm")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
|
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
|
||||||
|
//postgres
|
||||||
// postgres
|
|
||||||
implementation("org.postgresql:postgresql:42.7.3")
|
implementation("org.postgresql:postgresql:42.7.3")
|
||||||
implementation("org.jetbrains.exposed:exposed-core:0.50.0")
|
implementation("org.jetbrains.exposed:exposed-core:0.50.0")
|
||||||
implementation("org.jetbrains.exposed:exposed-dao:0.50.0")
|
implementation("org.jetbrains.exposed:exposed-dao:0.50.0")
|
||||||
implementation("org.jetbrains.exposed:exposed-jdbc:0.50.0")
|
implementation("org.jetbrains.exposed:exposed-jdbc:0.50.0")
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
testImplementation(ktorLibs.server.testHost)
|
testImplementation(ktorLibs.server.testHost)
|
||||||
|
|
||||||
// redis
|
//redis
|
||||||
implementation("redis.clients:jedis:5.1.2")
|
implementation("redis.clients:jedis:5.1.2")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -24,14 +24,6 @@ object ScreentimeTable : Table("user_screentime") {
|
||||||
override val primaryKey = PrimaryKey(userId)
|
override val primaryKey = PrimaryKey(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
object UserTable : Table("users") {
|
|
||||||
val id = varchar("id", 36)
|
|
||||||
val username = varchar("username", 50).index(isUnique = true)
|
|
||||||
val passwordHash = varchar("password_hash", 255)
|
|
||||||
|
|
||||||
override val primaryKey = PrimaryKey(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
object DatabaseFactory {
|
object DatabaseFactory {
|
||||||
fun init(config: ApplicationConfig) {
|
fun init(config: ApplicationConfig) {
|
||||||
val driverClassName = config.propertyOrNull("storage.driverClassName")?.getString() ?: "org.postgresql.Driver"
|
val driverClassName = config.propertyOrNull("storage.driverClassName")?.getString() ?: "org.postgresql.Driver"
|
||||||
|
|
@ -48,7 +40,7 @@ object DatabaseFactory {
|
||||||
?: config.propertyOrNull("storage.database")?.getString()
|
?: config.propertyOrNull("storage.database")?.getString()
|
||||||
?: "kidio_db"
|
?: "kidio_db"
|
||||||
|
|
||||||
val dbUsername = System.getenv("DB_USER")
|
val username = System.getenv("DB_USER")
|
||||||
?: config.propertyOrNull("storage.username")?.getString()
|
?: config.propertyOrNull("storage.username")?.getString()
|
||||||
?: "postgres"
|
?: "postgres"
|
||||||
|
|
||||||
|
|
@ -61,12 +53,12 @@ object DatabaseFactory {
|
||||||
val database = Database.connect(
|
val database = Database.connect(
|
||||||
url = jdbcURL,
|
url = jdbcURL,
|
||||||
driver = driverClassName,
|
driver = driverClassName,
|
||||||
user = dbUsername,
|
user = username,
|
||||||
password = password
|
password = password
|
||||||
)
|
)
|
||||||
|
|
||||||
transaction(database) {
|
transaction(database) {
|
||||||
SchemaUtils.create(TasksTable, ScreentimeTable, UserTable)
|
SchemaUtils.create(TasksTable, ScreentimeTable)
|
||||||
|
|
||||||
// Standard aufgaben, sollte es keine mehr geben
|
// Standard aufgaben, sollte es keine mehr geben
|
||||||
if (TasksTable.selectAll().empty()) {
|
if (TasksTable.selectAll().empty()) {
|
||||||
|
|
@ -94,16 +86,6 @@ object DatabaseFactory {
|
||||||
it[parentPin] = "1234"
|
it[parentPin] = "1234"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a standard user if the Users table is empty.
|
|
||||||
if (UserTable.selectAll().empty()) {
|
|
||||||
UserTable.insert {
|
|
||||||
// ID automatische generierung
|
|
||||||
it[id] = "default_user"
|
|
||||||
it[username] = "default_user"
|
|
||||||
it[passwordHash] = "1234"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,18 +33,6 @@ data class SyncRequest(
|
||||||
val usedSecondsSinceLastSync: Int
|
val usedSecondsSinceLastSync: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class LoginRequest(
|
|
||||||
val userId: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class User(
|
|
||||||
val id: String,
|
|
||||||
val username: String,
|
|
||||||
val passwordHash: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SyncResponse(
|
data class SyncResponse(
|
||||||
val remainingSeconds: Int,
|
val remainingSeconds: Int,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
package com.oliver.kidio.backend.domain.repository
|
package com.oliver.kidio.backend.domain.repository
|
||||||
|
|
||||||
import com.oliver.kidio.backend.Task
|
import com.oliver.kidio.backend.Task
|
||||||
import com.oliver.kidio.backend.User
|
|
||||||
import com.oliver.kidio.backend.data.database.DatabaseFactory
|
import com.oliver.kidio.backend.data.database.DatabaseFactory
|
||||||
import com.oliver.kidio.backend.data.database.RedisFactory
|
import com.oliver.kidio.backend.data.database.RedisFactory
|
||||||
import com.oliver.kidio.backend.data.database.ScreentimeTable
|
import com.oliver.kidio.backend.data.database.ScreentimeTable
|
||||||
import com.oliver.kidio.backend.data.database.TasksTable
|
import com.oliver.kidio.backend.data.database.TasksTable
|
||||||
import com.oliver.kidio.backend.data.database.UserTable
|
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
interface KidioRepository {
|
interface KidioRepository {
|
||||||
|
|
@ -14,9 +12,6 @@ interface KidioRepository {
|
||||||
suspend fun getTaskAnswerAndReward(taskId: String): Pair<String, Int>?
|
suspend fun getTaskAnswerAndReward(taskId: String): Pair<String, Int>?
|
||||||
suspend fun getScreentime(userId: String): Int
|
suspend fun getScreentime(userId: String): Int
|
||||||
suspend fun updateScreentime(userId: String, seconds: Int)
|
suspend fun updateScreentime(userId: String, seconds: Int)
|
||||||
|
|
||||||
suspend fun findByUsername(username: String): User?
|
|
||||||
suspend fun verifyPassword(password: String, passwordHash: String): Boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExposedKidioRepository : KidioRepository {
|
class ExposedKidioRepository : KidioRepository {
|
||||||
|
|
@ -39,7 +34,7 @@ class ExposedKidioRepository : KidioRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getScreentime(userId: String): Int {
|
override suspend fun getScreentime(userId: String): Int {
|
||||||
val redisKey = "screentime:$userId"
|
var redisKey = "screentime:$userId"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
RedisFactory.getResource().use { jedis ->
|
RedisFactory.getResource().use { jedis ->
|
||||||
|
|
@ -81,23 +76,6 @@ class ExposedKidioRepository : KidioRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByUsername(username: String): User? = DatabaseFactory.dbQuery {
|
|
||||||
UserTable.selectAll()
|
|
||||||
.where { UserTable.username eq username }
|
|
||||||
.map {
|
|
||||||
User(
|
|
||||||
id = it[UserTable.id],
|
|
||||||
username = it[UserTable.username],
|
|
||||||
passwordHash = it[UserTable.passwordHash]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.singleOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun verifyPassword(password: String, passwordHash: String): Boolean {
|
|
||||||
return password == passwordHash
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class InMemoryKidioRepository : KidioRepository {
|
class InMemoryKidioRepository : KidioRepository {
|
||||||
|
|
@ -113,10 +91,6 @@ class InMemoryKidioRepository : KidioRepository {
|
||||||
"default_user" to 1800
|
"default_user" to 1800
|
||||||
)
|
)
|
||||||
|
|
||||||
private val users = mutableListOf(
|
|
||||||
User(id = "1", username = "max", passwordHash = "geheim123")
|
|
||||||
)
|
|
||||||
|
|
||||||
override suspend fun getAllTasks(): List<Task> = tasks
|
override suspend fun getAllTasks(): List<Task> = tasks
|
||||||
|
|
||||||
override suspend fun getTaskAnswerAndReward(taskId: String): Pair<String, Int>? = answers[taskId]
|
override suspend fun getTaskAnswerAndReward(taskId: String): Pair<String, Int>? = answers[taskId]
|
||||||
|
|
@ -126,12 +100,4 @@ class InMemoryKidioRepository : KidioRepository {
|
||||||
override suspend fun updateScreentime(userId: String, seconds: Int) {
|
override suspend fun updateScreentime(userId: String, seconds: Int) {
|
||||||
screentimes[userId] = seconds
|
screentimes[userId] = seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findByUsername(username: String): User? {
|
|
||||||
return users.find { it.username == username }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun verifyPassword(password: String, passwordHash: String): Boolean {
|
|
||||||
return password == passwordHash
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@ package com.oliver.kidio.backend.plugins
|
||||||
|
|
||||||
import com.oliver.kidio.backend.*
|
import com.oliver.kidio.backend.*
|
||||||
import com.oliver.kidio.backend.domain.repository.KidioRepository
|
import com.oliver.kidio.backend.domain.repository.KidioRepository
|
||||||
import com.oliver.kidio.backend.security.JwtService
|
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.auth.authenticate
|
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
fun Application.configureRouting(repository: KidioRepository, jwtService : JwtService) {
|
fun Application.configureRouting(repository: KidioRepository) {
|
||||||
routing {
|
routing {
|
||||||
|
|
||||||
// Health-Check
|
// Health-Check
|
||||||
|
|
@ -17,15 +15,6 @@ fun Application.configureRouting(repository: KidioRepository, jwtService : JwtSe
|
||||||
call.respond(mapOf("status" to "OK", "message" to "Kidio Backend läuft!"))
|
call.respond(mapOf("status" to "OK", "message" to "Kidio Backend läuft!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
post ("/api/v1/auth/login" ) {
|
|
||||||
val loginRequest = call.receive<LoginRequest>()
|
|
||||||
|
|
||||||
val token = jwtService.generateToken(userId = loginRequest.userId)
|
|
||||||
call.respond(mapOf("token" to token))
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticate("auth-jwt") {
|
|
||||||
|
|
||||||
// 1. Alle verfügbaren Aufgaben abrufen
|
// 1. Alle verfügbaren Aufgaben abrufen
|
||||||
get("/api/v1/tasks") {
|
get("/api/v1/tasks") {
|
||||||
val tasks = repository.getAllTasks()
|
val tasks = repository.getAllTasks()
|
||||||
|
|
@ -93,6 +82,4 @@ fun Application.configureRouting(repository: KidioRepository, jwtService : JwtSe
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package com.oliver.kidio.backend.plugins
|
|
||||||
|
|
||||||
import com.oliver.kidio.backend.security.JwtService
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.auth.*
|
|
||||||
import io.ktor.server.auth.jwt.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Repräsentiert den authentifizierten Benutzer im Request-Kontext.
|
|
||||||
*/
|
|
||||||
data class UserPrincipal(val userId: String) : Principal
|
|
||||||
|
|
||||||
fun Application.configureSecurity(jwtService: JwtService) {
|
|
||||||
install(Authentication) {
|
|
||||||
jwt("auth-jwt") {
|
|
||||||
realm = "Kidio Backend"
|
|
||||||
verifier(jwtService.verifier)
|
|
||||||
|
|
||||||
validate { credential ->
|
|
||||||
val userId = credential.payload.getClaim("userId").asString()
|
|
||||||
if (!userId.isNullOrEmpty()) {
|
|
||||||
UserPrincipal(userId)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package com.oliver.kidio.backend.security
|
|
||||||
|
|
||||||
import com.auth0.jwt.JWT
|
|
||||||
import com.auth0.jwt.JWTVerifier
|
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class JwtService(
|
|
||||||
private val secret: String = System.getenv("JWT_SECRET") ?: "super-geheimes-secret-fuer-dev",
|
|
||||||
private val issuer: String = "https://kidio.oliver.com/",
|
|
||||||
private val audience: String = "kidio-users",
|
|
||||||
private val expirationInMs: Long = 36_000_000 // 10 Stunden
|
|
||||||
) {
|
|
||||||
|
|
||||||
val verifier: JWTVerifier = JWT
|
|
||||||
.require(Algorithm.HMAC256(secret))
|
|
||||||
.withAudience(audience)
|
|
||||||
.withIssuer(issuer)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generiert ein JWT-Token für einen bestimmten Benutzer.
|
|
||||||
*/
|
|
||||||
fun generateToken(userId: String): String {
|
|
||||||
return JWT.create()
|
|
||||||
.withAudience(audience)
|
|
||||||
.withIssuer(issuer)
|
|
||||||
.withClaim("userId", userId)
|
|
||||||
.withExpiresAt(Date(System.currentTimeMillis() + expirationInMs))
|
|
||||||
.sign(Algorithm.HMAC256(secret))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,6 @@ import com.oliver.kidio.backend.plugins.configureRouting
|
||||||
import com.oliver.kidio.backend.data.database.DatabaseFactory
|
import com.oliver.kidio.backend.data.database.DatabaseFactory
|
||||||
import com.oliver.kidio.backend.data.database.RedisFactory
|
import com.oliver.kidio.backend.data.database.RedisFactory
|
||||||
import com.oliver.kidio.backend.domain.repository.ExposedKidioRepository
|
import com.oliver.kidio.backend.domain.repository.ExposedKidioRepository
|
||||||
import com.oliver.kidio.backend.plugins.configureSecurity
|
|
||||||
import com.oliver.kidio.backend.security.JwtService
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.plugins.contentnegotiation.*
|
import io.ktor.server.plugins.contentnegotiation.*
|
||||||
|
|
@ -18,13 +16,6 @@ fun Application.module() {
|
||||||
json()
|
json()
|
||||||
}
|
}
|
||||||
|
|
||||||
val jwtService = JwtService()
|
// Ruft die Funktion aus Routing.kt auf mit dem echten ExposedRepository
|
||||||
val repository = ExposedKidioRepository()
|
configureRouting(ExposedKidioRepository())
|
||||||
|
|
||||||
configureSecurity(jwtService)
|
|
||||||
|
|
||||||
configureRouting(
|
|
||||||
repository = repository,
|
|
||||||
jwtService = jwtService
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,5 +10,5 @@ storage:
|
||||||
host: "82.165.11.162"
|
host: "82.165.11.162"
|
||||||
port: "5432"
|
port: "5432"
|
||||||
database: "kidio_db"
|
database: "kidio_db"
|
||||||
username: "kidio_admin"
|
username: "postgres"
|
||||||
password: "learning"
|
password: "learning"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue