104 lines
4.1 KiB
Kotlin
104 lines
4.1 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.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 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 {
|
|
logger.warn("Auth verify user not found for uid={}", decoded.uid)
|
|
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
|
|
}
|
|
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<PropertyUserResponse>
|
|
)
|