tg login
This commit is contained in:
1
gradle.properties
Normal file
1
gradle.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
kotlin.code.style=official
|
||||||
@@ -1,23 +1,67 @@
|
|||||||
package space.luminic.finance.api
|
package space.luminic.finance.api
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils.sha256
|
||||||
|
import org.apache.commons.codec.digest.HmacUtils.hmacSha256
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
import space.luminic.finance.dtos.UserDTO
|
import space.luminic.finance.dtos.UserDTO
|
||||||
import space.luminic.finance.dtos.UserDTO.AuthUserDTO
|
import space.luminic.finance.dtos.UserDTO.AuthUserDTO
|
||||||
import space.luminic.finance.dtos.UserDTO.RegisterUserDTO
|
import space.luminic.finance.dtos.UserDTO.RegisterUserDTO
|
||||||
import space.luminic.finance.mappers.UserMapper.toDto
|
import space.luminic.finance.mappers.UserMapper.toDto
|
||||||
|
import space.luminic.finance.mappers.UserMapper.toTelegramMap
|
||||||
import space.luminic.finance.services.AuthService
|
import space.luminic.finance.services.AuthService
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.time.Instant
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
class AuthController(
|
class AuthController(
|
||||||
private val authService: AuthService
|
private val authService: AuthService,
|
||||||
|
@Value("\${telegram.bot.token}") private val botToken: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
|
fun verifyTelegramAuth(data: Map<String, String>, botToken: String): Boolean {
|
||||||
|
val hash = data["hash"] ?: return false
|
||||||
|
|
||||||
|
val dataCheckString = data
|
||||||
|
.filterKeys { it != "hash" }
|
||||||
|
.toSortedMap()
|
||||||
|
.map { "${it.key}=${it.value}" }
|
||||||
|
.joinToString("\n")
|
||||||
|
|
||||||
|
val secretKey = sha256(botToken)
|
||||||
|
val hmacHex = hmacSha256(secretKey, dataCheckString)
|
||||||
|
|
||||||
|
if (hmacHex != hash) return false
|
||||||
|
|
||||||
|
val authDate = data["auth_date"]?.toLongOrNull() ?: return false
|
||||||
|
val now = Instant.now().epochSecond
|
||||||
|
|
||||||
|
// Опционально — запрет старых ответов (например, старше 1 часа)
|
||||||
|
val maxAgeSeconds = 3600
|
||||||
|
if (now - authDate > maxAgeSeconds) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sha256(input: String): ByteArray =
|
||||||
|
MessageDigest.getInstance("SHA-256").digest(input.toByteArray())
|
||||||
|
|
||||||
|
private fun hmacSha256(secret: ByteArray, message: String): String {
|
||||||
|
val key = SecretKeySpec(secret, "HmacSHA256")
|
||||||
|
val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
mac.init(key)
|
||||||
|
val hashBytes = mac.doFinal(message.toByteArray())
|
||||||
|
return hashBytes.joinToString("") { "%02x".format(it) }
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/test")
|
@GetMapping("/test")
|
||||||
fun test(): String {
|
fun test(): String {
|
||||||
val authentication = SecurityContextHolder.getContext().authentication
|
val authentication = SecurityContextHolder.getContext().authentication
|
||||||
@@ -36,13 +80,16 @@ class AuthController(
|
|||||||
return authService.register(request.username, request.password, request.firstName).toDto()
|
return authService.register(request.username, request.password, request.firstName).toDto()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/tgLogin")
|
@PostMapping("/tg-login")
|
||||||
fun tgLogin(@RequestHeader("X-Tg-Id") tgId: String): Map<String, String> {
|
fun tgLogin(@RequestBody tgUser: UserDTO.TelegramAuthDTO): String {
|
||||||
val token = authService.tgLogin(tgId)
|
val ok = verifyTelegramAuth(tgUser.toTelegramMap(), botToken)
|
||||||
return mapOf("token" to token)
|
if (!ok) throw IllegalArgumentException("Invalid Telegram login")
|
||||||
|
return authService.tgAuth(tgUser)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
fun getMe(): UserDTO {
|
fun getMe(): UserDTO {
|
||||||
logger.info("Get Me")
|
logger.info("Get Me")
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package space.luminic.finance.dtos
|
package space.luminic.finance.dtos
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
data class UserDTO (
|
data class UserDTO (
|
||||||
var id: Int,
|
var id: Int,
|
||||||
val username: String,
|
val username: String,
|
||||||
var firstName: String,
|
var firstName: String,
|
||||||
var tgId: String? = null,
|
var tgId: Long? = null,
|
||||||
var tgUserName: String? = null,
|
var tgUserName: String? = null,
|
||||||
|
var photoUrl: String? = null,
|
||||||
var roles: List<String>
|
var roles: List<String>
|
||||||
|
|
||||||
) {
|
) {
|
||||||
@@ -22,6 +25,17 @@ data class UserDTO (
|
|||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class TelegramAuthDTO(
|
||||||
|
val id: Long,
|
||||||
|
val first_name: String?,
|
||||||
|
val last_name: String?,
|
||||||
|
val username: String?,
|
||||||
|
val photo_url: String?,
|
||||||
|
val auth_date: Long,
|
||||||
|
val hash: String
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package space.luminic.finance.mappers
|
package space.luminic.finance.mappers
|
||||||
|
|
||||||
import space.luminic.finance.dtos.UserDTO
|
import space.luminic.finance.dtos.UserDTO
|
||||||
|
import space.luminic.finance.dtos.UserDTO.TelegramAuthDTO
|
||||||
import space.luminic.finance.models.User
|
import space.luminic.finance.models.User
|
||||||
|
|
||||||
object UserMapper {
|
object UserMapper {
|
||||||
@@ -11,7 +12,18 @@ object UserMapper {
|
|||||||
firstName = this.firstName,
|
firstName = this.firstName,
|
||||||
tgId = this.tgId,
|
tgId = this.tgId,
|
||||||
tgUserName = this.tgUserName,
|
tgUserName = this.tgUserName,
|
||||||
|
photoUrl = this.photoUrl,
|
||||||
roles = this.roles
|
roles = this.roles
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun TelegramAuthDTO.toTelegramMap(): Map<String, String> =
|
||||||
|
mapOf(
|
||||||
|
"id" to id.toString(),
|
||||||
|
"first_name" to (first_name ?: ""),
|
||||||
|
"last_name" to (last_name ?: ""),
|
||||||
|
"username" to (username ?: ""),
|
||||||
|
"photo_url" to (photo_url ?: ""),
|
||||||
|
"auth_date" to auth_date.toString(),
|
||||||
|
"hash" to hash
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,9 @@ data class User(
|
|||||||
var id: Int? = null,
|
var id: Int? = null,
|
||||||
val username: String,
|
val username: String,
|
||||||
var firstName: String,
|
var firstName: String,
|
||||||
var tgId: String? = null,
|
var tgId: Long? = null,
|
||||||
var tgUserName: String? = null,
|
var tgUserName: String? = null,
|
||||||
|
val photoUrl: String? = null,
|
||||||
var password: String? = null,
|
var password: String? = null,
|
||||||
var isActive: Boolean = true,
|
var isActive: Boolean = true,
|
||||||
var regDate: LocalDate = LocalDate.now(),
|
var regDate: LocalDate = LocalDate.now(),
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ interface UserRepo {
|
|||||||
fun findById(id: Int): User?
|
fun findById(id: Int): User?
|
||||||
fun findByUsername(username: String): User?
|
fun findByUsername(username: String): User?
|
||||||
fun findParticipantsBySpace(spaceId: Int): Set<User>
|
fun findParticipantsBySpace(spaceId: Int): Set<User>
|
||||||
fun findByTgId(tgId: String): User?
|
fun findByTgId(tgId: Long): User?
|
||||||
fun save(user: User): User
|
fun create(user: User): User
|
||||||
fun update(user: User): User
|
fun update(user: User): User
|
||||||
fun deleteById(id: Long)
|
fun deleteById(id: Long)
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import org.springframework.jdbc.core.RowMapper
|
|||||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import space.luminic.finance.models.User
|
import space.luminic.finance.models.User
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class UserRepoImpl(
|
class UserRepoImpl(
|
||||||
private val jdbcTemplate: NamedParameterJdbcTemplate
|
private val jdbcTemplate: NamedParameterJdbcTemplate
|
||||||
@@ -14,8 +15,9 @@ class UserRepoImpl(
|
|||||||
id = rs.getInt("id"),
|
id = rs.getInt("id"),
|
||||||
username = rs.getString("username"),
|
username = rs.getString("username"),
|
||||||
firstName = rs.getString("first_name"),
|
firstName = rs.getString("first_name"),
|
||||||
tgId = rs.getString("tg_id"),
|
tgId = rs.getLong("tg_id"),
|
||||||
tgUserName = rs.getString("tg_user_name"),
|
tgUserName = rs.getString("tg_user_name"),
|
||||||
|
photoUrl = rs.getString("photo_url"),
|
||||||
password = rs.getString("password"),
|
password = rs.getString("password"),
|
||||||
isActive = rs.getBoolean("is_active"),
|
isActive = rs.getBoolean("is_active"),
|
||||||
regDate = rs.getDate("reg_date").toLocalDate(),
|
regDate = rs.getDate("reg_date").toLocalDate(),
|
||||||
@@ -41,11 +43,12 @@ class UserRepoImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun findParticipantsBySpace(spaceId: Int): Set<User> {
|
override fun findParticipantsBySpace(spaceId: Int): Set<User> {
|
||||||
val sql = "select * from finance.users u join finance.spaces_participants sp on sp.participants_id = u.id where sp.space_id = :spaceId"
|
val sql =
|
||||||
|
"select * from finance.users u join finance.spaces_participants sp on sp.participants_id = u.id where sp.space_id = :spaceId"
|
||||||
return jdbcTemplate.query(sql, mapOf("spaceId" to spaceId), userRowMapper()).toSet()
|
return jdbcTemplate.query(sql, mapOf("spaceId" to spaceId), userRowMapper()).toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findByTgId(tgId: String): User? {
|
override fun findByTgId(tgId: Long): User? {
|
||||||
val sql = """
|
val sql = """
|
||||||
select * from finance.users u where tg_id = :tgId
|
select * from finance.users u where tg_id = :tgId
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@@ -53,13 +56,15 @@ class UserRepoImpl(
|
|||||||
return jdbcTemplate.queryForObject(sql, params, userRowMapper())
|
return jdbcTemplate.queryForObject(sql, params, userRowMapper())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun save(user: User): User {
|
override fun create(user: User): User {
|
||||||
val sql = "insert into finance.users(username, first_name, tg_id, tg_user_name, password, is_active, reg_date) values (:username, :firstname, :tg_id, :tg_user_name, :password, :isActive, :regDate) returning ID"
|
val sql =
|
||||||
|
"insert into finance.users(username, first_name, tg_id, tg_user_name, photo_url, password, is_active, reg_date) values (:username, :firstname, :tg_id, :tg_user_name, :photo_url :password, :isActive, :regDate) returning ID"
|
||||||
val params = mapOf(
|
val params = mapOf(
|
||||||
"username" to user.username,
|
"username" to user.username,
|
||||||
"firstname" to user.firstName,
|
"firstname" to user.firstName,
|
||||||
"tg_id" to user.tgId,
|
"tg_id" to user.tgId,
|
||||||
"tg_user_name" to user.tgUserName,
|
"tg_user_name" to user.tgUserName,
|
||||||
|
"photo_url" to user.photoUrl,
|
||||||
"password" to user.password,
|
"password" to user.password,
|
||||||
"isActive" to user.isActive,
|
"isActive" to user.isActive,
|
||||||
"regDate" to user.regDate,
|
"regDate" to user.regDate,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException
|
|||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import space.luminic.finance.configs.AuthException
|
import space.luminic.finance.configs.AuthException
|
||||||
|
import space.luminic.finance.dtos.UserDTO
|
||||||
|
import space.luminic.finance.models.NotFoundException
|
||||||
import space.luminic.finance.models.Token
|
import space.luminic.finance.models.Token
|
||||||
import space.luminic.finance.models.User
|
import space.luminic.finance.models.User
|
||||||
import space.luminic.finance.repos.UserRepo
|
import space.luminic.finance.repos.UserRepo
|
||||||
@@ -61,10 +63,12 @@ class AuthService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tgLogin(tgId: String): String {
|
fun tgAuth(tgUser: UserDTO.TelegramAuthDTO): String {
|
||||||
val user =
|
val user: User = try {
|
||||||
userRepo.findByTgId(tgId) ?: throw UsernameNotFoundException("Пользователь не найден")
|
tgLogin(tgUser.id)
|
||||||
|
} catch (e: NotFoundException) {
|
||||||
|
registerTg(tgUser)
|
||||||
|
}
|
||||||
val token = jwtUtil.generateToken(user.username)
|
val token = jwtUtil.generateToken(user.username)
|
||||||
val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10)
|
val expireAt = Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 10)
|
||||||
tokenService.saveToken(
|
tokenService.saveToken(
|
||||||
@@ -73,7 +77,22 @@ class AuthService(
|
|||||||
expiresAt = expireAt.toInstant()
|
expiresAt = expireAt.toInstant()
|
||||||
)
|
)
|
||||||
return token
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerTg(tgUser: UserDTO.TelegramAuthDTO): User {
|
||||||
|
val user = User(
|
||||||
|
username = tgUser.username ?: UUID.randomUUID().toString().split('-')[0],
|
||||||
|
firstName = tgUser.first_name ?: UUID.randomUUID().toString().split('-')[0],
|
||||||
|
tgId = tgUser.id,
|
||||||
|
tgUserName = tgUser.username,
|
||||||
|
photoUrl = tgUser.photo_url,
|
||||||
|
roles = mutableListOf("USER")
|
||||||
|
)
|
||||||
|
return userRepo.create(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tgLogin(tgId: Long): User {
|
||||||
|
return userRepo.findByTgId(tgId) ?: throw NotFoundException("User with provided TG id $tgId not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun register(username: String, password: String, firstName: String): User {
|
fun register(username: String, password: String, firstName: String): User {
|
||||||
@@ -85,7 +104,7 @@ class AuthService(
|
|||||||
firstName = firstName,
|
firstName = firstName,
|
||||||
roles = mutableListOf("USER")
|
roles = mutableListOf("USER")
|
||||||
)
|
)
|
||||||
newUser = userRepo.save(newUser)
|
newUser = userRepo.create(newUser)
|
||||||
return newUser
|
return newUser
|
||||||
} else throw IllegalArgumentException("Пользователь уже зарегистрирован")
|
} else throw IllegalArgumentException("Пользователь уже зарегистрирован")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class UserService(val userRepo: UserRepo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getUserByTelegramId(telegramId: Long): User {
|
fun getUserByTelegramId(telegramId: Long): User {
|
||||||
return userRepo.findByTgId(telegramId.toString())?: throw NotFoundException("User with telegramId: $telegramId not found")
|
return userRepo.findByTgId(telegramId)?: throw NotFoundException("User with telegramId: $telegramId not found")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ logging.level.org.mongodb.driver.protocol.command = INFO
|
|||||||
#management.endpoint.metrics.access=read_only
|
#management.endpoint.metrics.access=read_only
|
||||||
|
|
||||||
|
|
||||||
telegram.bot.token = 6662300972:AAFXjk_h0AUCy4bORC12UcdXbYnh2QSVKAY
|
telegram.bot.token = 7999296388:AAGXPE5r0yt3ZFehBoUh8FGm5FBbs9pYIks
|
||||||
nlp.address=https://nlp.luminic.space
|
nlp.address=https://nlp.luminic.space
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
src/main/resources/db/migration/V22__.sql
Normal file
3
src/main/resources/db/migration/V22__.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
alter table finance.users
|
||||||
|
add column photo_url varchar null,
|
||||||
|
alter column tg_id set data type numeric;
|
||||||
Reference in New Issue
Block a user