forked from maiora/backend-api
Compare commits
8 Commits
31c528c73f
...
e0d64f91d1
Author | SHA1 | Date | |
---|---|---|---|
e0d64f91d1 | |||
af2b7cea6d | |||
488cab4ac7 | |||
cd597a52c2 | |||
8e417f02f2 | |||
728eca6dd6 | |||
b8ee25b762 | |||
e3fc1f73b7 |
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
13
src/main/kotlin/eu/maiora/model/Tessere.kt
Normal file
13
src/main/kotlin/eu/maiora/model/Tessere.kt
Normal 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
|
||||||
|
)
|
5
src/main/kotlin/eu/maiora/model/TessereRepository.kt
Normal file
5
src/main/kotlin/eu/maiora/model/TessereRepository.kt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package eu.maiora.model
|
||||||
|
|
||||||
|
interface TessereRepository {
|
||||||
|
suspend fun tesseraByCodiceFiscale(cf : String): Tessere?
|
||||||
|
}
|
13
src/main/kotlin/eu/maiora/model/TessereRepositoryImpl.kt
Normal file
13
src/main/kotlin/eu/maiora/model/TessereRepositoryImpl.kt
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
src/main/kotlin/eu/maiora/plugins/Security.kt
Normal file
35
src/main/kotlin/eu/maiora/plugins/Security.kt
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
35
src/main/kotlin/eu/maiora/routes/Tessere.kt
Normal file
35
src/main/kotlin/eu/maiora/routes/Tessere.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user