Files
TrisolarisServer/src/main/kotlin/com/android/trisolarisserver/controller/Auth.kt
androidlover5842 bf87d329d4
Some checks failed
build-and-deploy / build-deploy (push) Failing after 22s
Allow updating AppUser name via auth/me
2026-01-26 22:30:48 +05:30

164 lines
6.3 KiB
Kotlin

package com.android.trisolarisserver.controller
import com.android.trisolarisserver.controller.dto.PropertyUserResponse
import com.android.trisolarisserver.controller.dto.UserResponse
import com.android.trisolarisserver.repo.AppUserRepo
import com.android.trisolarisserver.repo.PropertyUserRepo
import com.android.trisolarisserver.security.MyPrincipal
import com.google.firebase.auth.FirebaseAuth
import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import java.util.UUID
@RestController
@RequestMapping("/auth")
class Auth(
private val appUserRepo: AppUserRepo,
private val propertyUserRepo: PropertyUserRepo
) {
private val logger = LoggerFactory.getLogger(Auth::class.java)
@PostMapping("/verify")
fun verify(
@AuthenticationPrincipal principal: MyPrincipal?,
request: HttpServletRequest
): ResponseEntity<AuthResponse> {
logger.info("Auth verify hit, principalPresent={}", principal != null)
val resolved = principal?.let { ResolveResult(it) } ?: resolvePrincipalFromHeader(request)
return resolved.toResponseEntity()
}
@GetMapping("/me")
fun me(
@AuthenticationPrincipal principal: MyPrincipal?,
request: HttpServletRequest
): ResponseEntity<AuthResponse> {
val resolved = principal?.let { ResolveResult(it) } ?: resolvePrincipalFromHeader(request)
return resolved.toResponseEntity()
}
@PutMapping("/me")
fun updateMe(
@AuthenticationPrincipal principal: MyPrincipal?,
request: HttpServletRequest,
@RequestBody body: UpdateMeRequest
): ResponseEntity<AuthResponse> {
val resolved = principal?.let { ResolveResult(it) } ?: resolvePrincipalFromHeader(request)
if (resolved.principal == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(AuthResponse(status = "UNAUTHORIZED"))
}
val user = appUserRepo.findById(resolved.principal.userId).orElseThrow {
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
}
if (!body.name.isNullOrBlank()) {
user.name = body.name.trim()
}
appUserRepo.save(user)
return ResponseEntity.ok(buildAuthResponse(resolved.principal))
}
private fun buildAuthResponse(principal: MyPrincipal): AuthResponse {
val user = appUserRepo.findById(principal.userId).orElseThrow {
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
}
val memberships = propertyUserRepo.findByIdUserId(principal.userId).map {
PropertyUserResponse(
userId = it.id.userId!!,
propertyId = it.id.propertyId!!,
roles = it.roles.map { role -> role.name }.toSet()
)
}
val status = when {
user.superAdmin -> "SUPER_ADMIN"
memberships.isEmpty() -> "NO_PROPERTIES"
else -> "OK"
}
return AuthResponse(
status = status,
user = UserResponse(
id = user.id!!,
firebaseUid = user.firebaseUid,
phoneE164 = user.phoneE164,
name = user.name,
disabled = user.disabled,
superAdmin = user.superAdmin
),
properties = memberships
)
}
private fun resolvePrincipalFromHeader(request: HttpServletRequest): ResolveResult {
val header = request.getHeader("Authorization") ?: throw ResponseStatusException(
HttpStatus.UNAUTHORIZED,
"Missing Authorization token"
)
if (!header.startsWith("Bearer ")) {
logger.warn("Auth verify invalid Authorization header")
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Authorization header")
}
val token = header.removePrefix("Bearer ").trim()
val decoded = try {
FirebaseAuth.getInstance().verifyIdToken(token)
} catch (ex: Exception) {
logger.warn("Auth verify failed: {}", ex.message)
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token")
}
val user = appUserRepo.findByFirebaseUid(decoded.uid) ?: run {
val phone = decoded.claims["phone_number"] as? String
val name = decoded.claims["name"] as? String
val makeSuperAdmin = appUserRepo.count() == 0L
val created = appUserRepo.save(
com.android.trisolarisserver.models.property.AppUser(
firebaseUid = decoded.uid,
phoneE164 = phone,
name = name,
superAdmin = makeSuperAdmin
)
)
logger.warn("Auth verify auto-created user uid={}, userId={}", decoded.uid, created.id)
created
}
logger.warn("Auth verify resolved uid={}, userId={}", decoded.uid, user.id)
return ResolveResult(
MyPrincipal(
userId = user.id ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User id missing"),
firebaseUid = decoded.uid
)
)
}
private fun ResolveResult.toResponseEntity(): ResponseEntity<AuthResponse> {
return if (principal == null) {
ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(AuthResponse(status = "UNAUTHORIZED"))
} else {
ResponseEntity.ok(buildAuthResponse(principal))
}
}
}
data class AuthResponse(
val status: String,
val user: UserResponse? = null,
val properties: List<PropertyUserResponse> = emptyList()
)
data class UpdateMeRequest(
val name: String? = null
)
private data class ResolveResult(
val principal: MyPrincipal?
)