初步加上帳號服務

This commit is contained in:
Raymond Yang 2023-05-15 17:03:13 +08:00
parent 7c2839cade
commit 169278fc1d
13 changed files with 250 additions and 97 deletions

View File

@ -31,4 +31,6 @@ dependencies {
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
implementation("org.litote.kmongo:kmongo:$kmongo_version")
implementation("io.ktor:ktor-server-auth-jvm:$ktor_version")
implementation("io.ktor:ktor-server-auth-jwt-jvm:$ktor_version")
}

View File

@ -1,12 +1,31 @@
package com.ray650128
import com.ray650128.dto.UserDto
import com.ray650128.model.User
import com.ray650128.plugins.configureRouting
import com.ray650128.plugins.configureSerialization
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(Authentication) {
jwt {
verifier(JwtConfig.verifier)
realm = JwtConfig.myRealm
validate {
val name = it.payload.getClaim("account").asString()
if (name != null) {
UserDto(account = name, password = "")
} else {
null
}
}
}
}
configureRouting()
configureSerialization()
}.start(wait = true)

View File

@ -0,0 +1,35 @@
package com.ray650128
import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.ray650128.dto.UserDto
import java.util.*
object JwtConfig {
val secret = "my-secret"
val issuer = "com.ray650128"
val myRealm = "com.ray650128"
private val validityInMs = 36_000_00 * 24 // 1 day
private val algorithm = Algorithm.HMAC512(secret)
val verifier: JWTVerifier = JWT
.require(algorithm)
.withIssuer(issuer)
.build()
/**
* Produce a token for this combination of name and password
*/
fun generateToken(user: UserDto): String = JWT.create()
.withSubject("Authentication")
.withIssuer(issuer)
.withClaim("account", user.account)
//.withExpiresAt(getExpiration()) // optional
.sign(algorithm)
/**
* Calculate the expiration Date based on current time + the given validity
*/
private fun getExpiration() = Date(System.currentTimeMillis() + validityInMs)
}

View File

@ -1,10 +0,0 @@
package com.ray650128.dto
import kotlinx.serialization.Serializable
@Serializable
data class PersonDto(
val id: String? = null,
val name: String,
val age: Int
)

View File

@ -0,0 +1,15 @@
package com.ray650128.dto
import io.ktor.server.auth.*
import kotlinx.serialization.Serializable
@Serializable
data class UserDto(
val id: String? = null,
val account: String,
val password: String,
val name: String? = null,
var token: String? = null,
var createAt: Long? = null,
var updatedAt: Long? = null
): Principal

View File

@ -1,17 +0,0 @@
package com.ray650128.extension
import com.ray650128.dto.PersonDto
import com.ray650128.model.Person
fun Person.toDto(): PersonDto =
PersonDto(
id = this.id.toString(),
name = this.name,
age = this.age
)
fun PersonDto.toPerson(): Person =
Person(
name = this.name,
age = this.age
)

View File

@ -0,0 +1,25 @@
package com.ray650128.extension
import com.ray650128.dto.UserDto
import com.ray650128.model.User
fun User.toDto(): UserDto =
UserDto(
id = this.id.toString(),
account = this.account,
password = this.password,
name = this.name,
token = this.token,
createAt = this.createAt,
updatedAt = this.updatedAt
)
fun UserDto.toUser(): User =
User(
account = this.account,
password = this.password,
name = this.name,
token = this.token,
createAt = this.createAt,
updatedAt = this.updatedAt
)

View File

@ -0,0 +1,9 @@
package com.ray650128.model
import kotlinx.serialization.Serializable
@Serializable
data class LoginResult(
var account: String,
var token: String
)

View File

@ -1,11 +0,0 @@
package com.ray650128.model
import org.bson.codecs.pojo.annotations.BsonId
import org.litote.kmongo.Id
data class Person(
@BsonId
val id: Id<Person>? = null,
val name: String,
val age: Int
)

View File

@ -0,0 +1,15 @@
package com.ray650128.model
import org.bson.codecs.pojo.annotations.BsonId
import org.litote.kmongo.Id
data class User(
@BsonId
val id: Id<User>? = null,
val account: String,
val password: String,
var name: String? = null,
var token: String? = null,
var createAt: Long? = null,
var updatedAt: Long? = null
)

View File

@ -1,38 +1,92 @@
package com.ray650128.plugins
import com.ray650128.dto.PersonDto
import com.ray650128.JwtConfig
import com.ray650128.dto.UserDto
import com.ray650128.extension.toDto
import com.ray650128.extension.toPerson
import com.ray650128.extension.toUser
import com.ray650128.model.ErrorResponse
import com.ray650128.model.Person
import com.ray650128.service.PersonService
import com.ray650128.model.LoginResult
import com.ray650128.model.User
import com.ray650128.service.UserService
import io.ktor.http.*
import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
fun Application.configureRouting() {
val service = PersonService()
val service = UserService()
routing {
get("/") {
call.respondText("Hello World!")
}
route("/person") {
post {
val request = call.receive<PersonDto>()
val person = request.toPerson()
service.create(person)
?.let { userId ->
call.response.headers.append("My-User-Id-Header", userId.toString())
call.respond(HttpStatusCode.Created)
} ?: call.respond(HttpStatusCode.BadRequest, ErrorResponse.BAD_REQUEST_RESPONSE)
route("/api") {
route("/v1") {
post("/register") {
val request = call.receive<UserDto>()
val newToken = JwtConfig.generateToken(request)
val user = request.toUser().apply {
token = newToken
createAt = System.currentTimeMillis()
}
service.create(user)
?.let { userId ->
call.response.headers.append("My-User-Id-Header", userId.toString())
call.respond(HttpStatusCode.Created, LoginResult(request.account, newToken))
} ?: call.respond(HttpStatusCode.BadRequest, ErrorResponse.BAD_REQUEST_RESPONSE)
}
post("/login") {
val request = call.receive<UserDto>()
val user = service.findByLoginInfo(request.account, request.password)
if (user != null) {
var token = user.token
if (token.isNullOrEmpty()) {
token = JwtConfig.generateToken(request)
user.token = token
user.updatedAt = System.currentTimeMillis()
service.updateById(user.id.toString(), user)
}
call.respond(HttpStatusCode.OK, LoginResult(request.account, token!!))
} else {
call.respond(
status = HttpStatusCode.Unauthorized,
message = mapOf("message" to "Account or Password wrong.")
)
}
}
authenticate {
post("/logout") {
val account = call.authentication.principal<UserDto>()?.account ?: run {
call.respond(
status = HttpStatusCode.Unauthorized,
message = mapOf("message" to "token wrong")
)
return@post
}
val user = service.findByAccount(account) ?: run {
call.respond(
status = HttpStatusCode.Unauthorized,
message = mapOf("message" to "token wrong")
)
return@post
}
user.apply {
token = null
updatedAt = System.currentTimeMillis()
}
service.updateById(user.id.toString(), user)
call.respond(HttpStatusCode.OK, "User has logged out")
}
}
}
get {
val peopleList = service.findAll().map(Person::toDto)
val peopleList = service.findAll().map(User::toDto)
call.respond(peopleList)
}
@ -45,15 +99,15 @@ fun Application.configureRouting() {
get("/search") {
val name = call.request.queryParameters["name"].toString()
val foundPeople = service.findByName(name).map(Person::toDto)
val foundPeople = service.findByName(name).map(User::toDto)
call.respond(foundPeople)
}
put("/{id}") {
val id = call.parameters["id"].toString()
val personRequest = call.receive<PersonDto>()
val person = personRequest.toPerson()
val updatedSuccessfully = service.updateById(id, person)
val userRequest = call.receive<UserDto>()
val user = userRequest.toUser()
val updatedSuccessfully = service.updateById(id, user)
if (updatedSuccessfully) {
call.respond(HttpStatusCode.NoContent)
} else {

View File

@ -1,40 +0,0 @@
package com.ray650128.service
import com.ray650128.model.Person
import org.bson.types.ObjectId
import org.litote.kmongo.*
import org.litote.kmongo.id.toId
class PersonService {
private val client = KMongo.createClient("mongodb://www.ray650128.com:27017")
private val database = client.getDatabase("person")
private val personCollection = database.getCollection<Person>()
fun create(person: Person): Id<Person>? {
personCollection.insertOne(person)
return person.id
}
fun findAll(): List<Person> = personCollection.find().toList()
fun findById(id: String): Person? {
val bsonId: Id<Person> = ObjectId(id).toId()
return personCollection.findOne(Person::id eq bsonId)
}
fun findByName(name: String): List<Person> {
val caseSensitiveTypeSafeFilter = Person::name regex name
return personCollection.find(caseSensitiveTypeSafeFilter).toList()
}
fun updateById(id: String, request: Person): Boolean =
findById(id)?.let { person ->
val updateResult = personCollection.replaceOne(person.copy(name = request.name, age = request.age))
updateResult.modifiedCount == 1L
} ?: false
fun deleteById(id: String): Boolean {
val deleteResult = personCollection.deleteOneById(ObjectId(id))
return deleteResult.deletedCount == 1L
}
}

View File

@ -0,0 +1,57 @@
package com.ray650128.service
import com.ray650128.model.User
import org.bson.types.ObjectId
import org.litote.kmongo.*
import org.litote.kmongo.id.toId
class UserService {
private val client = KMongo.createClient("mongodb://www.ray650128.com:27017")
private val database = client.getDatabase("users")
private val userCollection = database.getCollection<User>()
fun create(user: User): Id<User>? {
userCollection.insertOne(user)
return user.id
}
fun findAll(): List<User> = userCollection.find().toList()
fun findById(id: String): User? {
val bsonId: Id<User> = ObjectId(id).toId()
return userCollection.findOne(User::id eq bsonId)
}
fun findByName(name: String): List<User> {
val caseSensitiveTypeSafeFilter = User::name regex name
return userCollection.find(caseSensitiveTypeSafeFilter).toList()
}
fun findByAccount(account: String): User? {
return userCollection.findOne(User::account eq account)
}
fun findByLoginInfo(account: String, password: String): User? {
return userCollection.findOne(
User::account eq account,
User::password eq password
)
}
fun updateById(id: String, request: User): Boolean =
findById(id)?.let { user ->
val updateResult = userCollection.replaceOne(
user.copy(
name = request.name,
token = request.token,
updatedAt = request.updatedAt
)
)
updateResult.modifiedCount == 1L
} ?: false
fun deleteById(id: String): Boolean {
val deleteResult = userCollection.deleteOneById(ObjectId(id))
return deleteResult.deletedCount == 1L
}
}