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.exposed:exposed-dao:$exposed_version")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
implementation ("org.jetbrains.exposed:exposed-java-time:$exposed_version") implementation ("org.jetbrains.exposed:exposed-java-time:$exposed_version")
implementation("io.jsonwebtoken:jjwt-api:0.11.5") implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation("io.jsonwebtoken:jjwt-impl:0.11.5") implementation("io.ktor:ktor-server-auth-jwt:$ktor_version")
implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")
testImplementation("io.ktor:ktor-server-test-host-jvm") testImplementation("io.ktor:ktor-server-test-host-jvm")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
} }

View File

@ -1,5 +1,5 @@
plugins { plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" 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 package eu.maiora
import eu.maiora.plugins.configureDatabases import eu.maiora.plugins.configureDatabases
//import eu.maiora.model.LogScriptRepositoryImpl
import eu.maiora.plugins.* import eu.maiora.plugins.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
@ -18,14 +17,15 @@ fun main() {
fun Application.module() { fun Application.module() {
val config = ApplicationConfig("application.conf") val config = ApplicationConfig("application.conf")
val dbUrl = config.property("ktor.database.url").getString(); val dbUrl = config.property("ktor.database.url").getString()
val username = config.property("ktor.database.username").getString(); val username = config.property("ktor.database.username").getString()
val password = config.property("ktor.database.password").getString(); val password = config.property("ktor.database.password").getString()
//val repository = LogScriptRepositoryImpl() val secret = config.property("ktor.jwt.secret").getString()
configureDatabases(dbUrl, username, password) configureDatabases(dbUrl, username, password)
//configureRouting(dbUrl, username, password, repository) configureSecurity(secret)
configureRouting(dbUrl, username, password) configureRouting(dbUrl, username, password)
configureSerialization() configureSerialization()
install(CallLogging) install(CallLogging)
install(CORS){ install(CORS){
anyHost() anyHost()

View File

@ -3,6 +3,7 @@ package eu.maiora.db
import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate.Param import com.fasterxml.jackson.databind.deser.impl.CreatorCandidate.Param
import eu.maiora.model.Accounts import eu.maiora.model.Accounts
import eu.maiora.model.Parametri import eu.maiora.model.Parametri
import eu.maiora.model.Tessere
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.dao.IntEntity import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass import org.jetbrains.exposed.dao.IntEntityClass
@ -27,6 +28,17 @@ object ParametriTable : IdTable<Int>("parametri"){
override val primaryKey = PrimaryKey(id) 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) { class AccountsDAO(id: EntityID<Int>) :IntEntity(id) {
companion object : IntEntityClass<AccountsDAO>(AccountsTable) companion object : IntEntityClass<AccountsDAO>(AccountsTable)
@ -41,6 +53,16 @@ class ParametriDAO(id: EntityID<Int>) :IntEntity(id) {
var valore by ParametriTable.valore 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( fun accountsDaoToModel(dao: AccountsDAO) = Accounts(
dao.id.value, dao.id.value,
@ -54,6 +76,15 @@ fun parametriDaoToModel(dao: ParametriDAO) = Parametri(
dao.valore 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 = suspend fun <T> suspendTransaction(block: Transaction.() -> T): T =
newSuspendedTransaction(Dispatchers.IO, statement = block) 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.routes.logScriptRouting
import eu.maiora.model.AccountsRepositoryImpl import eu.maiora.model.AccountsRepositoryImpl
import eu.maiora.model.ParametriRepositoryImpl import eu.maiora.model.ParametriRepositoryImpl
import eu.maiora.model.TessereRepositoryImpl
import eu.maiora.routes.auth import eu.maiora.routes.auth
import eu.maiora.routes.tessere
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
@ -18,9 +20,7 @@ fun Application.configureRouting(dbUrl : String, username : String, password : S
call.respondText("Hello World!") call.respondText("Hello World!")
} }
auth(AccountsRepositoryImpl(), ParametriRepositoryImpl()) auth(AccountsRepositoryImpl())
//analizzaURLRoute() tessere(TessereRepositoryImpl())
//eseguiScriptSQLRoute(dbUrl, username, password)
//logScriptRouting(repository)
} }
} }

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 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.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.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.config.*
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.*
@ -17,70 +15,77 @@ import org.slf4j.LoggerFactory
import java.util.* import java.util.*
fun Route.auth(accountsRepository: AccountsRepositoryImpl, parametriRepository: ParametriRepositoryImpl){ fun Route.auth(accountsRepository: AccountsRepositoryImpl) {
route("/auth") {
post("/auth"){ post() {
// Riceve il body della richiesta e lo deserializza in ReceivedResponse // Riceve il body della richiesta e lo deserializza in ReceivedResponse
val receivedResponse = try { val receivedResponse = try {
call.receive<ReceivedResponse>() call.receive<ReceivedResponse>()
} catch (e: Exception) { } catch (e: Exception) {
return@post call.respondText("Body mancante " + e.stackTraceToString(), status = HttpStatusCode.BadRequest) return@post call.respondText(
} "Body mancante " + e.stackTraceToString(),
val logger = LoggerFactory.getLogger("AuthRoute") status = HttpStatusCode.BadRequest
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()
) )
) }
if(passwordPlain.equals(credentials.password)){ val logger = LoggerFactory.getLogger("AuthRoute")
val parametro = parametriRepository.parametroByChiave("jwt_secret") logger.info(
if (parametro != null) { "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 now = System.currentTimeMillis()
val expiration = now + 86400000 // Scadenza tra 1 giorno (24 ore) val expiration = now + 86400000 // Scadenza tra 1 giorno (24 ore)
val token = Jwts.builder() val token = JWT.create()
.setSubject(account.username) // Soggetto del JWT .withSubject(account.username)
.setIssuedAt(Date(now)) // Data di emissione .withIssuedAt(Date(now))
.setExpiration(Date(expiration)) // Data di scadenza .withExpiresAt(Date(expiration))
.signWith(key, SignatureAlgorithm.HS256) // Firma con una chiave segreta .sign(Algorithm.HMAC256(secret))
.compact()
// Risponde con la stringa decodificata // Risponde con la stringa decodificata
call.respond(HttpStatusCode.OK, token) call.respond(HttpStatusCode.OK, token)
}
} } else {
else { call.respond(HttpStatusCode.Unauthorized)
}
} else {
call.respond(HttpStatusCode.Unauthorized) call.respond(HttpStatusCode.Unauthorized)
} }
}
else {
call.respond(HttpStatusCode.Unauthorized)
}
}
} }
} }
@Serializable @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" ;username = "EP_DONORIONE"
;password = "ep_donorione" ;password = "ep_donorione"
} }
} jwt {
# secret per JWT generato partendo dalla stringa '?Backend_API*06022025!' codificato in Base64
secret = "P0JhY2tlbmRfQVBJKjA2MDIyMDI1IQ=="
}
}