diff --git a/Sources/pom.xml b/Sources/pom.xml index f4258f4..6107ebe 100644 --- a/Sources/pom.xml +++ b/Sources/pom.xml @@ -68,6 +68,11 @@ ktor-server-content-negotiation-jvm ${ktor_version} + + io.ktor + ktor-server-auth-jwt + ${ktor_version} + io.ktor ktor-server-html-builder-jvm @@ -105,6 +110,23 @@ ktor-server-content-negotiation-jvm ${ktor_version} + + io.ktor + ktor-server-auth-jvm + 2.3.4 + implementation + + + io.ktor + ktor-server-auth-jwt-jvm + 2.3.4 + implementation + + + com.auth0 + java-jwt + 4.4.0 + ${project.basedir}/src/main/kotlin diff --git a/Sources/src/main/kotlin/allin/Application.kt b/Sources/src/main/kotlin/allin/Application.kt index 15afe0f..cde6981 100644 --- a/Sources/src/main/kotlin/allin/Application.kt +++ b/Sources/src/main/kotlin/allin/Application.kt @@ -2,22 +2,47 @@ package allin import allin.routing.BasicRouting import allin.routing.UserRouter +import allin.routing.tokenManager +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm +import com.typesafe.config.ConfigFactory import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import io.ktor.server.config.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.response.* +import io.ktor.server.response.* import io.ktor.server.routing.* +import io.ktor.server.routing.* +import allin.utils.TokenManager fun main() { - embeddedServer(Netty,port=8080,host="0.0.0.0"){ + embeddedServer(Netty, port = 8080, host = "0.0.0.0") { extracted() }.start(wait = true) } private fun Application.extracted() { + val config=HoconApplicationConfig(ConfigFactory.load()) + val tokenManager= TokenManager(config) + authentication { + jwt { + verifier(tokenManager.verifyJWTToken()) + realm=config.property("realm").getString() + validate { jwtCredential -> + if(jwtCredential.payload.getClaim("username").asString().isNotEmpty()) + JWTPrincipal(jwtCredential.payload) + else null + } + } + } install(ContentNegotiation) { json() } diff --git a/Sources/src/main/kotlin/allin/model/User.kt b/Sources/src/main/kotlin/allin/model/User.kt index 503ebda..418c230 100644 --- a/Sources/src/main/kotlin/allin/model/User.kt +++ b/Sources/src/main/kotlin/allin/model/User.kt @@ -3,12 +3,7 @@ package allin.model import kotlinx.serialization.Serializable @Serializable -data class User(val username: String, val email: String, val password: String, var nbCoins: Int = 1000) +data class User(val username: String, val email: String, val password: String, var nbCoins: Int = 1000, var token: String? = null) @Serializable -data class CheckUser(val login: String,val password: String) - -fun isEmailValid(email: String): Boolean { - val emailRegex = Regex("^[A-Za-z0-9+_.-]+@(.+)$") - return !emailRegex.matches(email) -} +data class CheckUser(val login: String,val password: String) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/routing/UserRouter.kt b/Sources/src/main/kotlin/allin/routing/UserRouter.kt index 3cbf1a8..0630642 100644 --- a/Sources/src/main/kotlin/allin/routing/UserRouter.kt +++ b/Sources/src/main/kotlin/allin/routing/UserRouter.kt @@ -2,23 +2,28 @@ package allin.routing import allin.model.CheckUser import allin.model.User -import allin.model.isEmailValid -import io.ktor.client.utils.* +import com.typesafe.config.ConfigFactory import io.ktor.http.* import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import io.ktor.server.config.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* +import allin.utils.RegexChecker +import allin.utils.TokenManager val users = mutableListOf() - +val tokenManager= TokenManager(HoconApplicationConfig(ConfigFactory.load())) +val RegexChecker= RegexChecker() fun Application.UserRouter() { routing { route("/users/register"){ post { val TempUser = call.receive() - if (isEmailValid(TempUser.email)){ + if (RegexChecker.isEmailInvalid(TempUser.email)){ call.respond(HttpStatusCode.Forbidden,"Input a valid mail !") } val user = users.find { it.username == TempUser.username || it.email == TempUser.email } @@ -35,6 +40,7 @@ fun Application.UserRouter() { val checkUser = call.receive() val user = users.find { it.username == checkUser.login || it.email == checkUser.login } if (user != null && user.password == checkUser.password) { + user.token=tokenManager.generateJWTToken(user) call.respond(HttpStatusCode.OK, user) } else { call.respond(HttpStatusCode.NotFound,"Login and/or password incorrect.") @@ -54,7 +60,19 @@ fun Application.UserRouter() { } } } + + authenticate { + get("/users/token") { + val principal = call.principal() + val username = principal!!.payload.getClaim("username").asString() + val user = users.find { it.username == username } + if (user != null) { + call.respond(HttpStatusCode.OK, user) + } else { + call.respond(HttpStatusCode.NotFound, "User not found with the valid token !") + } + } + } + } } -// REGISTER 201 created 400 bad request -// LOGIN 200 OK 404 diff --git a/Sources/src/main/kotlin/allin/utils/RegexChecker.kt b/Sources/src/main/kotlin/allin/utils/RegexChecker.kt new file mode 100644 index 0000000..8a70a90 --- /dev/null +++ b/Sources/src/main/kotlin/allin/utils/RegexChecker.kt @@ -0,0 +1,13 @@ +package allin.utils + +class RegexChecker { + + private val emailRegex="^[A-Za-z0-9+_.-]+@(.+)$" + + + fun isEmailInvalid(email: String): Boolean { + val emailRegex = Regex(emailRegex) + return !emailRegex.matches(email) + } + +} \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/utils/TokenManager.kt b/Sources/src/main/kotlin/allin/utils/TokenManager.kt new file mode 100644 index 0000000..3a9824c --- /dev/null +++ b/Sources/src/main/kotlin/allin/utils/TokenManager.kt @@ -0,0 +1,33 @@ +package allin.utils + +import allin.model.User +import com.auth0.jwt.JWT +import com.auth0.jwt.JWTVerifier +import com.auth0.jwt.algorithms.Algorithm +import io.ktor.server.config.* +import java.util.* + +class TokenManager (val config: HoconApplicationConfig){ + + val audience=config.property("audience").getString() + val secret=config.property("secret").getString() + val issuer=config.property("issuer").getString() + val expirationDate = System.currentTimeMillis() + 60000 + fun generateJWTToken(user : User): String { + + val token = JWT.create() + .withAudience(audience) + .withIssuer(issuer) + .withClaim("username", user.username) + .withExpiresAt(Date(expirationDate)) + .sign(Algorithm.HMAC256(secret)) + return token + } + + fun verifyJWTToken(): JWTVerifier{ + return JWT.require(Algorithm.HMAC256(secret)) + .withAudience(audience) + .withIssuer(issuer) + .build() + } +} \ No newline at end of file diff --git a/Sources/src/main/resources/application.conf b/Sources/src/main/resources/application.conf new file mode 100644 index 0000000..e69de29