Files
applications-tracker/api/src/main/kotlin/com/andr3h3nriqu3s/applications/User.kt
2024-09-11 18:11:44 +01:00

226 lines
7.1 KiB
Kotlin

package com.andr3h3nriqu3s.applications
import java.util.UUID
import kotlin.io.encoding.Base64
import kotlin.random.Random
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
enum class UserLevel(val level: Int) {
NORMAL(1),
ADMIN(255)
}
data class UserDb(
var id: String,
var username: String,
var email: String,
var password: String,
var level: Int
) {
fun toSafe(): User {
return User(this.id, this.username, this.email, this.level)
}
fun checkLevel(level: Int): Boolean {
return (level and this.level) == level
}
fun checkLevelThrow(level: Int) {
if (!this.checkLevel(level)) {
throw NotAuth()
}
}
}
data class UserCreateRequest(val email: String, val password: String, val username: String)
data class LoginUserRequest(val email: String, val password: String)
data class LoggedInUser(val token: String, val user: User)
data class User(val id: String, var username: String, var email: String, var level: Int)
@RestController
@ControllerAdvice
@RequestMapping("/api/user")
@kotlin.io.encoding.ExperimentalEncodingApi
class UserController(
val userService: UserService,
val sessionService: SessionService,
val sessionServiceCreator: SessionServiceCreator,
) {
@PostMapping(path = ["/login"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun login(@RequestBody user: LoginUserRequest): LoggedInUser {
val userdb = userService.login(user.email, user.password)
val session = sessionServiceCreator.createSession(userdb)
return LoggedInUser(session.token, userdb.toSafe())
}
@GetMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun list(@RequestHeader("token") token: String): List<User> {
sessionService.verifyTokenThrow(token).checkLevelThrow(UserLevel.ADMIN.ordinal)
return userService.findUsers().map { elm -> elm.toSafe() }
}
@PostMapping(path = ["/register"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun register(@RequestBody user: UserCreateRequest): LoggedInUser {
var new_user = userService.createUser(user)
val session = sessionServiceCreator.createSession(new_user)
return LoggedInUser(session.token, new_user.toSafe())
}
}
@Service
class UserService(val db: JdbcTemplate) {
fun login(email: String, passwd: String): UserDb {
var user = this.findUserByEmail(email)
if (user == null) {
throw UserNotFound()
}
var arg2SpringSecurity = Argon2PasswordEncoder(16, 32, 1, 60000, 10)
// TODO make this safe
if (!arg2SpringSecurity.matches(passwd, user.password)) {
throw UserNotFound()
}
return user
}
fun findUserByEmail(email: String): UserDb? {
var users =
db
.query("select * from users where email=?", arrayOf(email)) { response, _ ->
UserDb(
response.getString("id"),
response.getString("username"),
response.getString("email"),
response.getString("passwd"),
response.getInt("level")
)
}
.toList()
if (users.size == 0) {
return null
}
return users[0]
}
fun createUser(user: UserCreateRequest): UserDb {
var user_check = findUserByEmail(user.email)
if (user_check != null) {
throw Exception("User already exists")
}
val id = UUID.randomUUID().toString()
var arg2SpringSecurity = Argon2PasswordEncoder(16, 32, 1, 60000, 10)
val hashPassword = arg2SpringSecurity.encode(user.password)
var new_user = UserDb(id, user.username, user.email, hashPassword, UserLevel.NORMAL.ordinal)
db.update(
"insert into users (id, username, email, passwd, level) values (?, ?, ?, ?, ?)",
new_user.id,
user.username,
user.email,
new_user.password,
new_user.level,
)
return new_user
}
fun findUsers(): List<UserDb> =
db.query("select * from users") { response, _ ->
UserDb(
response.getString("id"),
response.getString("username"),
response.getString("email"),
response.getString("passwd"),
response.getInt("level"),
)
}
}
data class Session(val token: String, val user_id: String)
@Service
class SessionService(val db: JdbcTemplate) {
fun verifyToken(token: String): UserDb? {
var users =
db
.query(
"select * from tokens as t inner join users as u on id = user_id where token = ?",
arrayOf(token)
) { response, _ ->
UserDb(
response.getString("id"),
response.getString("username"),
response.getString("email"),
response.getString("passwd"),
response.getInt("level"),
)
}
.toList()
if (users.size == 0) {
return null
}
return users[0]
}
fun verifyTokenThrow(token: String): UserDb {
val new_user = this.verifyToken(token)
if (new_user == null) {
throw NoToken()
}
return new_user
}
}
@Service
@kotlin.io.encoding.ExperimentalEncodingApi
class SessionServiceCreator(val db: JdbcTemplate) {
fun createSession(user: UserDb): Session {
val session = Session(this.generateToken(), user.id)
db.update(
"insert into tokens (token, user_id) values (?,?);",
session.token,
session.user_id
)
return session
}
fun generateToken(): String {
var b = ByteArray(60)
Random.nextBytes(b)
return Base64.encode(b)
}
}