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.OrganizationRepo 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.RequestMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.server.ResponseStatusException import org.springframework.http.HttpStatus @RestController @RequestMapping("/auth") class Auth( private val appUserRepo: AppUserRepo, private val propertyUserRepo: PropertyUserRepo, private val organizationRepo: OrganizationRepo ) { private val logger = LoggerFactory.getLogger(Auth::class.java) @PostMapping("/verify") fun verify( @AuthenticationPrincipal principal: MyPrincipal?, request: HttpServletRequest ): AuthResponse { logger.info("Auth verify hit, principalPresent={}", principal != null) return buildAuthResponse(principal ?: resolvePrincipalFromHeader(request)) } @GetMapping("/me") fun me( @AuthenticationPrincipal principal: MyPrincipal?, request: HttpServletRequest ): AuthResponse { return buildAuthResponse(principal ?: resolvePrincipalFromHeader(request)) } private fun buildAuthResponse(principal: MyPrincipal?): AuthResponse { if (principal == null) { throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing principal") } 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() ) } return AuthResponse( user = UserResponse( id = user.id!!, orgId = user.org.id!!, firebaseUid = user.firebaseUid, phoneE164 = user.phoneE164, name = user.name, disabled = user.disabled ), properties = memberships ) } private fun resolvePrincipalFromHeader(request: HttpServletRequest): MyPrincipal { 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 orgs = organizationRepo.findAll() if (orgs.size != 1) { logger.warn("Auth verify user not found for uid={}, orgCount={}", decoded.uid, orgs.size) throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found") } val org = orgs.first() val phone = decoded.claims["phone_number"] as? String val name = decoded.claims["name"] as? String val created = appUserRepo.save( com.android.trisolarisserver.models.property.AppUser( org = org, firebaseUid = decoded.uid, phoneE164 = phone, name = name ) ) logger.warn("Auth verify auto-created user uid={}, userId={}, orgId={}", decoded.uid, created.id, org.id) created } logger.warn("Auth verify resolved uid={}, userId={}", decoded.uid, user.id) return MyPrincipal( userId = user.id ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User id missing"), firebaseUid = decoded.uid ) } } data class AuthResponse( val user: UserResponse, val properties: List )