Compare commits

...

8 Commits

Author SHA1 Message Date
e0d64f91d1 Merge pull request '0002343-endpoint-tessere' (#1) from 0002343-endpoint-tessere into dev
Reviewed-on: #1
2025-02-21 16:36:09 +00:00
af2b7cea6d modifica tipi ID
da integer a long
2025-02-21 17:32:39 +01:00
488cab4ac7 Merge remote-tracking branch 'upstream/dev' into 0002343-endpoint-tessere 2025-02-18 16:07:53 +01:00
cd597a52c2 implementazione metodo di verifica JWT
secret in file di configurazione application.config
2025-02-18 16:06:15 +01:00
8e417f02f2 /tessere
uso del metodo authenticate per la verifica della validità del JWT
2025-02-18 16:02:41 +01:00
728eca6dd6 Merge remote-tracking branch 'upstream/dev' into 0002343-endpoint-tessere 2025-02-18 12:55:00 +01:00
b8ee25b762 modifiche uso JWT
utilizzo dipendenze come da documentazione Ktor per JWT
recupero secret da file di configurazione (e non da database)
2025-02-18 12:50:02 +01:00
e3fc1f73b7 prima implementazione
mapping e repository
2025-02-18 09:22:59 +01:00
12 changed files with 211 additions and 70 deletions

View File

@ -35,9 +35,8 @@ dependencies {
implementation("org.jetbrains.exposed:exposed-dao:$exposed_version")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
implementation ("org.jetbrains.exposed:exposed-java-time:$exposed_version")
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")
implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation("io.ktor:ktor-server-auth-jwt:$ktor_version")
testImplementation("io.ktor:ktor-server-test-host-jvm")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
}

View File

@ -1,5 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
}
rootProject.name = "Backend_API"
rootProject.name = "Backend_API_DSU"

View File

@ -1,7 +1,6 @@
package eu.maiora
import eu.maiora.plugins.configureDatabases
//import eu.maiora.model.LogScriptRepositoryImpl
import eu.maiora.plugins.*
import io.ktor.http.*
import io.ktor.server.application.*
@ -18,14 +17,15 @@ fun main() {
fun Application.module() {
val config = ApplicationConfig("application.conf")
val dbUrl = config.property("ktor.database.url").getString();
val username = config.property("ktor.database.username").getString();
val password = config.property("ktor.database.password").getString();
//val repository = LogScriptRepositoryImpl()
val dbUrl = config.property("ktor.database.url").getString()
val username = config.property("ktor.database.username").getString()
val password = config.property("ktor.database.password").getString()
val secret = config.property("ktor.jwt.secret").getString()
configureDatabases(dbUrl, username, password)
//configureRouting(dbUrl, username, password, repository)
configureSecurity(secret)
configureRouting(dbUrl, username, password)
configureSerialization()
install(CallLogging)
install(CORS){
anyHost()

View File

@ -3,6 +3,7 @@ package eu.maiora.db
import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate.Param
import eu.maiora.model.Accounts
import eu.maiora.model.Parametri
import eu.maiora.model.Tessere
import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
@ -27,6 +28,17 @@ object ParametriTable : IdTable<Int>("parametri"){
override val primaryKey = PrimaryKey(id)
}
object TessereTable : IdTable<Long>("view_tessere_api"){
override val id = long("id").entityId()
val idUtente = long("id_utente")
val codiceFiscale = varchar("codice_fiscale", 255)
val numero = varchar("numero", 255)
val saldo = double("saldo")
val punti = integer("punti")
override val primaryKey = PrimaryKey(id)
}
class AccountsDAO(id: EntityID<Int>) :IntEntity(id) {
companion object : IntEntityClass<AccountsDAO>(AccountsTable)
@ -41,6 +53,16 @@ class ParametriDAO(id: EntityID<Int>) :IntEntity(id) {
var valore by ParametriTable.valore
}
class TessereDao(id: EntityID<Long>) :LongEntity(id) {
companion object : LongEntityClass<TessereDao>(TessereTable)
var idUtente by TessereTable.idUtente
var codiceFiscale by TessereTable.codiceFiscale
var numero by TessereTable.numero
var saldo by TessereTable.saldo
var punti by TessereTable.punti
}
fun accountsDaoToModel(dao: AccountsDAO) = Accounts(
dao.id.value,
@ -54,6 +76,15 @@ fun parametriDaoToModel(dao: ParametriDAO) = Parametri(
dao.valore
)
fun tessereDaoToModel(dao: TessereDao) = Tessere(
dao.id.value,
dao.idUtente,
dao.codiceFiscale,
dao.numero,
dao.saldo,
dao.punti
)
suspend fun <T> suspendTransaction(block: Transaction.() -> T): T =
newSuspendedTransaction(Dispatchers.IO, statement = block)

View File

@ -0,0 +1,13 @@
package eu.maiora.model
import kotlinx.serialization.Serializable
@Serializable
data class Tessere(
val id: Long,
val idUtente : Long,
val codiceFiscale : String,
val numero : String,
val saldo : Double,
val punti : Int
)

View File

@ -0,0 +1,5 @@
package eu.maiora.model
interface TessereRepository {
suspend fun tesseraByCodiceFiscale(cf : String): Tessere?
}

View File

@ -0,0 +1,13 @@
package eu.maiora.model
import eu.maiora.db.*
class TessereRepositoryImpl : TessereRepository {
override suspend fun tesseraByCodiceFiscale(cf: String): Tessere? = suspendTransaction {
// Cerca tessere in base al codice fiscale
TessereDao.find { TessereTable.codiceFiscale eq cf }
.singleOrNull() // Restituisce un singolo risultato o null se non trovato
?.let { tessereDaoToModel(it) } // Converte il DAO in un oggetto Tessere
}
}

View File

@ -6,7 +6,9 @@ package eu.maiora.plugins
//import eu.maiora.routes.logScriptRouting
import eu.maiora.model.AccountsRepositoryImpl
import eu.maiora.model.ParametriRepositoryImpl
import eu.maiora.model.TessereRepositoryImpl
import eu.maiora.routes.auth
import eu.maiora.routes.tessere
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
@ -18,9 +20,7 @@ fun Application.configureRouting(dbUrl : String, username : String, password : S
call.respondText("Hello World!")
}
auth(AccountsRepositoryImpl(), ParametriRepositoryImpl())
//analizzaURLRoute()
//eseguiScriptSQLRoute(dbUrl, username, password)
//logScriptRouting(repository)
auth(AccountsRepositoryImpl())
tessere(TessereRepositoryImpl())
}
}

View File

@ -0,0 +1,35 @@
package eu.maiora.plugins
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.response.*
fun Application.configureSecurity(secret: String) {
install(Authentication) {
jwt ("auth-jwt"){
verifier(
JWT
.require(Algorithm.HMAC256(secret))
.build())
validate { credential ->
val expiresAt = credential.payload.expiresAt?.time ?: 0
val now = System.currentTimeMillis()
// Verifica se il token ? scaduto
if (expiresAt >= now) {
JWTPrincipal(credential.payload)
}
else {
null
}
}
challenge { defaultScheme, realm ->
call.respond(HttpStatusCode.Unauthorized, "Token non valido o scaduto")
}
}
}
}

View File

@ -1,13 +1,11 @@
package eu.maiora.routes
import com.fasterxml.jackson.databind.ser.Serializers.Base
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import eu.maiora.model.AccountsRepositoryImpl
import eu.maiora.model.ParametriRepositoryImpl
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.security.Keys
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.config.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
@ -17,70 +15,77 @@ import org.slf4j.LoggerFactory
import java.util.*
fun Route.auth(accountsRepository: AccountsRepositoryImpl, parametriRepository: ParametriRepositoryImpl){
post("/auth"){
// Riceve il body della richiesta e lo deserializza in ReceivedResponse
val receivedResponse = try {
call.receive<ReceivedResponse>()
} catch (e: Exception) {
return@post call.respondText("Body mancante " + e.stackTraceToString(), status = HttpStatusCode.BadRequest)
}
val logger = LoggerFactory.getLogger("AuthRoute")
logger.info("param: " +
receivedResponse.param);
// Decodifica la stringa da Base64 a oggetto Credentials
val decodedBytes = Base64.getDecoder().decode(receivedResponse.param)
val decodedString = String(decodedBytes)
val credentials = Json.decodeFromString<Credentials>(decodedString)
//verifica credenziali (recupero account dal database)
val account = accountsRepository.accountByUsername(credentials.username)
//se le credenziali sono valide, creare il JWT
if (account != null) {
val passwordPlain = String(
Base64.getDecoder().decode(
StringBuffer(
String(
Base64.getDecoder().decode(account.password.toByteArray())
)
).reverse().toString().toByteArray()
fun Route.auth(accountsRepository: AccountsRepositoryImpl) {
route("/auth") {
post() {
// Riceve il body della richiesta e lo deserializza in ReceivedResponse
val receivedResponse = try {
call.receive<ReceivedResponse>()
} catch (e: Exception) {
return@post call.respondText(
"Body mancante " + e.stackTraceToString(),
status = HttpStatusCode.BadRequest
)
)
if(passwordPlain.equals(credentials.password)){
val parametro = parametriRepository.parametroByChiave("jwt_secret")
if (parametro != null) {
}
val logger = LoggerFactory.getLogger("AuthRoute")
logger.info(
"param: " +
receivedResponse.param
);
// Decodifica la stringa da Base64 a oggetto Credentials
val decodedBytes = Base64.getDecoder().decode(receivedResponse.param)
val decodedString = String(decodedBytes)
val credentials = try {
Json.decodeFromString<Credentials>(decodedString)
} catch (e: Exception) {
return@post call.respondText(
"Errore nel param. Verificare la codifica. \n" + e.stackTraceToString(),
status = HttpStatusCode.BadRequest
)
}
//verifica credenziali (recupero account dal database)
val account = accountsRepository.accountByUsername(credentials.username)
//se le credenziali sono valide, creare il JWT
if (account != null) {
val passwordPlain = String(
Base64.getDecoder().decode(
StringBuffer(
String(
Base64.getDecoder().decode(account.password.toByteArray())
)
).reverse().toString().toByteArray()
)
)
if (passwordPlain.equals(credentials.password)) {
val config = ApplicationConfig("application.conf")
val secret = config.property("ktor.jwt.secret").getString()
val key = Keys.hmacShaKeyFor(parametro.valore.toByteArray())
val now = System.currentTimeMillis()
val expiration = now + 86400000 // Scadenza tra 1 giorno (24 ore)
val token = Jwts.builder()
.setSubject(account.username) // Soggetto del JWT
.setIssuedAt(Date(now)) // Data di emissione
.setExpiration(Date(expiration)) // Data di scadenza
.signWith(key, SignatureAlgorithm.HS256) // Firma con una chiave segreta
.compact()
val token = JWT.create()
.withSubject(account.username)
.withIssuedAt(Date(now))
.withExpiresAt(Date(expiration))
.sign(Algorithm.HMAC256(secret))
// Risponde con la stringa decodificata
call.respond(HttpStatusCode.OK, token)
}
}
else {
} else {
call.respond(HttpStatusCode.Unauthorized)
}
} else {
call.respond(HttpStatusCode.Unauthorized)
}
}
else {
call.respond(HttpStatusCode.Unauthorized)
}
}
}
}
@Serializable

View File

@ -0,0 +1,35 @@
package eu.maiora.routes
import eu.maiora.model.TessereRepositoryImpl
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.tessere(tessereRepository: TessereRepositoryImpl){
route("/api/tessere"){
authenticate("auth-jwt") {
get("{cf}"){
// Ottieni il codice fiscale dal percorso
val cf = call.parameters["cf"]
if (cf == null) {
call.respondText("Codice fiscale non valido", status = HttpStatusCode.BadRequest)
return@get
}
// Cerca la tessera per codice fiscale
val tessera = tessereRepository.tesseraByCodiceFiscale(cf)
if (tessera != null) {
call.respond(tessera)
} else {
call.respondText("Tessera non trovata", status = HttpStatusCode.NotFound)
}
}
}
}
}

View File

@ -13,4 +13,9 @@ ktor {
;username = "EP_DONORIONE"
;password = "ep_donorione"
}
}
jwt {
# secret per JWT generato partendo dalla stringa '?Backend_API*06022025!' codificato in Base64
secret = "P0JhY2tlbmRfQVBJKjA2MDIyMDI1IQ=="
}
}