feature #9: Add routes for login
This commit is contained in:
@@ -5,6 +5,8 @@ val kotlin_version: String by project
|
|||||||
val logback_version: String by project
|
val logback_version: String by project
|
||||||
val koinVersion: String by project
|
val koinVersion: String by project
|
||||||
val postgresjson_version: String by project
|
val postgresjson_version: String by project
|
||||||
|
val jackson_version: String by project
|
||||||
|
val cucumber_version: String by project
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
@@ -39,11 +41,13 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-auth:$ktor_version")
|
implementation("io.ktor:ktor-auth:$ktor_version")
|
||||||
implementation("io.ktor:ktor-auth-jwt:$ktor_version")
|
implementation("io.ktor:ktor-auth-jwt:$ktor_version")
|
||||||
implementation("io.ktor:ktor-gson:$ktor_version")
|
implementation("io.ktor:ktor-gson:$ktor_version")
|
||||||
|
implementation("io.ktor:ktor-auth-jwt:$ktor_version")
|
||||||
implementation("org.koin:koin-ktor:$koinVersion")
|
implementation("org.koin:koin-ktor:$koinVersion")
|
||||||
implementation("io.ktor:ktor-jackson:$ktor_version")
|
implementation("io.ktor:ktor-jackson:$ktor_version")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version")
|
||||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.9")
|
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version")
|
||||||
implementation("net.pearx.kasechange:kasechange-jvm:1.1.0")
|
implementation("net.pearx.kasechange:kasechange-jvm:1.1.0")
|
||||||
|
implementation("com.auth0:java-jwt:3.8.2")
|
||||||
implementation("fr.postgresjson:postgresjson:$postgresjson_version")
|
implementation("fr.postgresjson:postgresjson:$postgresjson_version")
|
||||||
|
|
||||||
testImplementation("io.ktor:ktor-server-tests:$ktor_version")
|
testImplementation("io.ktor:ktor-server-tests:$ktor_version")
|
||||||
@@ -53,6 +57,6 @@ dependencies {
|
|||||||
testImplementation("io.mockk:mockk:1.9")
|
testImplementation("io.mockk:mockk:1.9")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.5.0")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.5.0")
|
||||||
testImplementation("org.amshove.kluent:kluent:1.4")
|
testImplementation("org.amshove.kluent:kluent:1.4")
|
||||||
testImplementation("io.cucumber:cucumber-java8:4.3.1")
|
testImplementation("io.cucumber:cucumber-java8:$cucumber_version")
|
||||||
testImplementation("io.cucumber:cucumber-junit:4.3.1")
|
testImplementation("io.cucumber:cucumber-junit:$cucumber_version")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,5 @@ kotlin_version=1.3.40
|
|||||||
logback_version=1.2.1
|
logback_version=1.2.1
|
||||||
postgresjson_version=0.1
|
postgresjson_version=0.1
|
||||||
koinVersion=2.0.1
|
koinVersion=2.0.1
|
||||||
|
jackson_version=2.9.9
|
||||||
|
cucumber_version=4.3.1
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import com.fasterxml.jackson.datatype.joda.JodaModule
|
|||||||
import fr.dcproject.entity.Article
|
import fr.dcproject.entity.Article
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.entity.Constitution
|
import fr.dcproject.entity.Constitution
|
||||||
|
import fr.dcproject.entity.User
|
||||||
import fr.dcproject.routes.*
|
import fr.dcproject.routes.*
|
||||||
import fr.postgresjson.migration.Migrations
|
import fr.postgresjson.migration.Migrations
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
import io.ktor.auth.Authentication
|
import io.ktor.auth.Authentication
|
||||||
|
import io.ktor.auth.jwt.jwt
|
||||||
import io.ktor.features.AutoHeadResponse
|
import io.ktor.features.AutoHeadResponse
|
||||||
import io.ktor.features.CallLogging
|
import io.ktor.features.CallLogging
|
||||||
import io.ktor.features.ContentNegotiation
|
import io.ktor.features.ContentNegotiation
|
||||||
@@ -93,6 +95,18 @@ fun Application.module() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install(Authentication) {
|
install(Authentication) {
|
||||||
|
/**
|
||||||
|
* Setup the JWT authentication to be used in [Routing].
|
||||||
|
* If the token is valid, the corresponding [User] is fetched from the database.
|
||||||
|
* The [User] can then be accessed in each [ApplicationCall].
|
||||||
|
*/
|
||||||
|
jwt {
|
||||||
|
verifier(JwtConfig.verifier)
|
||||||
|
realm = "dc-project.fr"
|
||||||
|
validate {
|
||||||
|
it.payload.getClaim("id").asInt()?.let { get<User>() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
install(AutoHeadResponse)
|
install(AutoHeadResponse)
|
||||||
@@ -115,6 +129,7 @@ fun Application.module() {
|
|||||||
|
|
||||||
install(Routing) {
|
install(Routing) {
|
||||||
article(get())
|
article(get())
|
||||||
|
auth(get())
|
||||||
citizen(get())
|
citizen(get())
|
||||||
constitution(get())
|
constitution(get())
|
||||||
followArticle(get())
|
followArticle(get())
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package fr.dcproject
|
package fr.dcproject
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT
|
||||||
|
import com.auth0.jwt.JWTVerifier
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import fr.dcproject.entity.User
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
private var config = ConfigFactory.load()
|
private var config = ConfigFactory.load()
|
||||||
@@ -14,3 +19,33 @@ class Config {
|
|||||||
var password: String = config.getString("db.password")
|
var password: String = config.getString("db.password")
|
||||||
val port: Int = config.getInt("db.port")
|
val port: Int = config.getInt("db.port")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object JwtConfig {
|
||||||
|
|
||||||
|
private const val secret = "zAP5MBA4B4Ijz0MZaS48"
|
||||||
|
private const val issuer = "dc-project.fr"
|
||||||
|
private const val validityInMs = 36_000_00 * 10 // 10 hours
|
||||||
|
// TODO change to RSA512
|
||||||
|
private val algorithm = Algorithm.HMAC512(secret)
|
||||||
|
|
||||||
|
val verifier: JWTVerifier = JWT
|
||||||
|
.require(algorithm)
|
||||||
|
.withIssuer(issuer)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a token for this combination of User and Account
|
||||||
|
*/
|
||||||
|
fun makeToken(user: User): String = JWT.create()
|
||||||
|
.withSubject("Authentication")
|
||||||
|
.withIssuer(issuer)
|
||||||
|
.withClaim("id", user.id.toString())
|
||||||
|
.withExpiresAt(getExpiration())
|
||||||
|
.sign(algorithm)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the expiration Date based on current time + the given validity
|
||||||
|
*/
|
||||||
|
private fun getExpiration() = Date(System.currentTimeMillis() + validityInMs)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import fr.dcproject.repository.Citizen as CitizenRepository
|
|||||||
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
||||||
import fr.dcproject.repository.FollowArticle as FollowArticleRepository
|
import fr.dcproject.repository.FollowArticle as FollowArticleRepository
|
||||||
import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository
|
import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository
|
||||||
|
import fr.dcproject.repository.User as UserRepository
|
||||||
|
|
||||||
val config = Config()
|
val config = Config()
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ val Module = module {
|
|||||||
).createRequester() }
|
).createRequester() }
|
||||||
|
|
||||||
// TODO: create generic declaration
|
// TODO: create generic declaration
|
||||||
|
single { UserRepository(get()) }
|
||||||
single { ArticleRepository(get()) }
|
single { ArticleRepository(get()) }
|
||||||
single { CitizenRepository(get()) }
|
single { CitizenRepository(get()) }
|
||||||
single { ConstitutionRepository(get()) }
|
single { ConstitutionRepository(get()) }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
|
|
||||||
import fr.postgresjson.entity.*
|
import fr.postgresjson.entity.*
|
||||||
|
import io.ktor.auth.Principal
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -11,4 +12,5 @@ class User(
|
|||||||
var plainPassword: String?
|
var plainPassword: String?
|
||||||
) : UuidEntity(id),
|
) : UuidEntity(id),
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
EntityUpdatedAt by EntityUpdatedAtImp()
|
EntityUpdatedAt by EntityUpdatedAtImp(),
|
||||||
|
Principal
|
||||||
|
|||||||
19
src/main/kotlin/fr/dcproject/repository/User.kt
Normal file
19
src/main/kotlin/fr/dcproject/repository/User.kt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import io.ktor.auth.UserPasswordCredential
|
||||||
|
import fr.dcproject.entity.User as UserEntity
|
||||||
|
|
||||||
|
class User(override var requester: Requester) : RepositoryI<UserEntity> {
|
||||||
|
override val entityName = UserEntity::class
|
||||||
|
|
||||||
|
fun findByCredentials(credentials: UserPasswordCredential): UserEntity? {
|
||||||
|
return requester
|
||||||
|
.getFunction("check_user")
|
||||||
|
.selectOne(
|
||||||
|
"username" to credentials.name,
|
||||||
|
"plain_password" to credentials.password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/kotlin/fr/dcproject/routes/Auth.kt
Normal file
29
src/main/kotlin/fr/dcproject/routes/Auth.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package fr.dcproject.routes
|
||||||
|
|
||||||
|
import Paths
|
||||||
|
import com.fasterxml.jackson.databind.exc.MismatchedInputException
|
||||||
|
import fr.dcproject.JwtConfig
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.auth.UserPasswordCredential
|
||||||
|
import io.ktor.features.BadRequestException
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.post
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.response.respondText
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
|
import fr.dcproject.repository.User as UserRepository
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
@KtorExperimentalAPI
|
||||||
|
fun Route.auth(repo: UserRepository) {
|
||||||
|
post <Paths.LoginRequest> {
|
||||||
|
try {
|
||||||
|
val credentials = call.receive<UserPasswordCredential>()
|
||||||
|
val user = repo.findByCredentials(credentials) ?: throw BadRequestException("Username not exist or password is wrong")
|
||||||
|
call.respondText(JwtConfig.makeToken(user))
|
||||||
|
} catch (e: MismatchedInputException) {
|
||||||
|
throw BadRequestException("You must be send name and password to the request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import io.ktor.locations.Location
|
|||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
object Paths {
|
object Paths {
|
||||||
|
@Location("/login") class LoginRequest
|
||||||
|
|
||||||
@Location("/articles") class ArticlesRequest(page: Int = 1, limit: Int = 50, val sort: String? = null, val direction: Direction? = null, val search: String? = null) {
|
@Location("/articles") class ArticlesRequest(page: Int = 1, limit: Int = 50, val sort: String? = null, val direction: Direction? = null, val search: String? = null) {
|
||||||
val page: Int = if (page < 1) 1 else page
|
val page: Int = if (page < 1) 1 else page
|
||||||
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
||||||
|
|||||||
Reference in New Issue
Block a user