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 { 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 { val resolved = principal?.let { ResolveResult(it) } ?: resolvePrincipalFromHeader(request) return resolved.toResponseEntity() } @PutMapping("/me") fun updateMe( @AuthenticationPrincipal principal: MyPrincipal?, request: HttpServletRequest, @RequestBody body: UpdateMeRequest ): ResponseEntity { 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 { 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 = emptyList() ) data class UpdateMeRequest( val name: String? = null ) private data class ResolveResult( val principal: MyPrincipal? )