Allow updating AppUser name via auth/me
Some checks failed
build-and-deploy / build-deploy (push) Failing after 22s
Some checks failed
build-and-deploy / build-deploy (push) Failing after 22s
This commit is contained in:
@@ -3,8 +3,6 @@ package com.android.trisolarisserver.controller
|
|||||||
import com.android.trisolarisserver.controller.dto.PropertyUserResponse
|
import com.android.trisolarisserver.controller.dto.PropertyUserResponse
|
||||||
import com.android.trisolarisserver.controller.dto.UserResponse
|
import com.android.trisolarisserver.controller.dto.UserResponse
|
||||||
import com.android.trisolarisserver.repo.AppUserRepo
|
import com.android.trisolarisserver.repo.AppUserRepo
|
||||||
import com.android.trisolarisserver.repo.OrganizationRepo
|
|
||||||
import com.android.trisolarisserver.repo.PendingUserRepo
|
|
||||||
import com.android.trisolarisserver.repo.PropertyUserRepo
|
import com.android.trisolarisserver.repo.PropertyUserRepo
|
||||||
import com.android.trisolarisserver.security.MyPrincipal
|
import com.android.trisolarisserver.security.MyPrincipal
|
||||||
import com.google.firebase.auth.FirebaseAuth
|
import com.google.firebase.auth.FirebaseAuth
|
||||||
@@ -13,7 +11,9 @@ import org.slf4j.LoggerFactory
|
|||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
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.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
@@ -24,9 +24,7 @@ import java.util.UUID
|
|||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
class Auth(
|
class Auth(
|
||||||
private val appUserRepo: AppUserRepo,
|
private val appUserRepo: AppUserRepo,
|
||||||
private val propertyUserRepo: PropertyUserRepo,
|
private val propertyUserRepo: PropertyUserRepo
|
||||||
private val organizationRepo: OrganizationRepo,
|
|
||||||
private val pendingUserRepo: PendingUserRepo
|
|
||||||
) {
|
) {
|
||||||
private val logger = LoggerFactory.getLogger(Auth::class.java)
|
private val logger = LoggerFactory.getLogger(Auth::class.java)
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ class Auth(
|
|||||||
request: HttpServletRequest
|
request: HttpServletRequest
|
||||||
): ResponseEntity<AuthResponse> {
|
): ResponseEntity<AuthResponse> {
|
||||||
logger.info("Auth verify hit, principalPresent={}", principal != null)
|
logger.info("Auth verify hit, principalPresent={}", principal != null)
|
||||||
val resolved = principal?.let { ResolveResult(it, null) } ?: resolvePrincipalFromHeader(request)
|
val resolved = principal?.let { ResolveResult(it) } ?: resolvePrincipalFromHeader(request)
|
||||||
return resolved.toResponseEntity()
|
return resolved.toResponseEntity()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,10 +43,31 @@ class Auth(
|
|||||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||||
request: HttpServletRequest
|
request: HttpServletRequest
|
||||||
): ResponseEntity<AuthResponse> {
|
): ResponseEntity<AuthResponse> {
|
||||||
val resolved = principal?.let { ResolveResult(it, null) } ?: resolvePrincipalFromHeader(request)
|
val resolved = principal?.let { ResolveResult(it) } ?: resolvePrincipalFromHeader(request)
|
||||||
return resolved.toResponseEntity()
|
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 {
|
private fun buildAuthResponse(principal: MyPrincipal): AuthResponse {
|
||||||
val user = appUserRepo.findById(principal.userId).orElseThrow {
|
val user = appUserRepo.findById(principal.userId).orElseThrow {
|
||||||
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
|
ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
|
||||||
@@ -60,15 +79,20 @@ class Auth(
|
|||||||
roles = it.roles.map { role -> role.name }.toSet()
|
roles = it.roles.map { role -> role.name }.toSet()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val status = when {
|
||||||
|
user.superAdmin -> "SUPER_ADMIN"
|
||||||
|
memberships.isEmpty() -> "NO_PROPERTIES"
|
||||||
|
else -> "OK"
|
||||||
|
}
|
||||||
return AuthResponse(
|
return AuthResponse(
|
||||||
status = "OK",
|
status = status,
|
||||||
user = UserResponse(
|
user = UserResponse(
|
||||||
id = user.id!!,
|
id = user.id!!,
|
||||||
orgId = user.org.id!!,
|
|
||||||
firebaseUid = user.firebaseUid,
|
firebaseUid = user.firebaseUid,
|
||||||
phoneE164 = user.phoneE164,
|
phoneE164 = user.phoneE164,
|
||||||
name = user.name,
|
name = user.name,
|
||||||
disabled = user.disabled
|
disabled = user.disabled,
|
||||||
|
superAdmin = user.superAdmin
|
||||||
),
|
),
|
||||||
properties = memberships
|
properties = memberships
|
||||||
)
|
)
|
||||||
@@ -90,62 +114,34 @@ class Auth(
|
|||||||
logger.warn("Auth verify failed: {}", ex.message)
|
logger.warn("Auth verify failed: {}", ex.message)
|
||||||
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token")
|
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token")
|
||||||
}
|
}
|
||||||
val user = appUserRepo.findByFirebaseUid(decoded.uid)
|
val user = appUserRepo.findByFirebaseUid(decoded.uid) ?: run {
|
||||||
if (user == null) {
|
val phone = decoded.claims["phone_number"] as? String
|
||||||
val orgs = organizationRepo.findAll()
|
val name = decoded.claims["name"] as? String
|
||||||
if (orgs.isEmpty()) {
|
val makeSuperAdmin = appUserRepo.count() == 0L
|
||||||
logger.warn("Auth verify user not found for uid={}, orgCount=0", decoded.uid)
|
val created = appUserRepo.save(
|
||||||
val phone = decoded.claims["phone_number"] as? String
|
com.android.trisolarisserver.models.property.AppUser(
|
||||||
val name = decoded.claims["name"] as? String
|
firebaseUid = decoded.uid,
|
||||||
val existing = pendingUserRepo.findByFirebaseUid(decoded.uid)
|
phoneE164 = phone,
|
||||||
val pending = existing ?: pendingUserRepo.save(
|
name = name,
|
||||||
com.android.trisolarisserver.models.property.PendingUser(
|
superAdmin = makeSuperAdmin
|
||||||
firebaseUid = decoded.uid,
|
|
||||||
phoneE164 = phone,
|
|
||||||
name = name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return ResolveResult(null, pending.id)
|
)
|
||||||
}
|
logger.warn("Auth verify auto-created user uid={}, userId={}", decoded.uid, created.id)
|
||||||
if (orgs.size == 1) {
|
created
|
||||||
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)
|
|
||||||
return ResolveResult(
|
|
||||||
MyPrincipal(
|
|
||||||
userId = created.id
|
|
||||||
?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User id missing"),
|
|
||||||
firebaseUid = decoded.uid
|
|
||||||
),
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
logger.warn("Auth verify user not found for uid={}, orgCount={}", decoded.uid, orgs.size)
|
|
||||||
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
|
|
||||||
}
|
}
|
||||||
logger.warn("Auth verify resolved uid={}, userId={}", decoded.uid, user.id)
|
logger.warn("Auth verify resolved uid={}, userId={}", decoded.uid, user.id)
|
||||||
return ResolveResult(
|
return ResolveResult(
|
||||||
MyPrincipal(
|
MyPrincipal(
|
||||||
userId = user.id ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User id missing"),
|
userId = user.id ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User id missing"),
|
||||||
firebaseUid = decoded.uid
|
firebaseUid = decoded.uid
|
||||||
),
|
)
|
||||||
null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ResolveResult.toResponseEntity(): ResponseEntity<AuthResponse> {
|
private fun ResolveResult.toResponseEntity(): ResponseEntity<AuthResponse> {
|
||||||
return if (principal == null) {
|
return if (principal == null) {
|
||||||
ResponseEntity.status(HttpStatus.ACCEPTED)
|
ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||||
.body(AuthResponse(status = "NEEDS_ORG", pendingUserId = pendingUserId))
|
.body(AuthResponse(status = "UNAUTHORIZED"))
|
||||||
} else {
|
} else {
|
||||||
ResponseEntity.ok(buildAuthResponse(principal))
|
ResponseEntity.ok(buildAuthResponse(principal))
|
||||||
}
|
}
|
||||||
@@ -155,11 +151,13 @@ class Auth(
|
|||||||
data class AuthResponse(
|
data class AuthResponse(
|
||||||
val status: String,
|
val status: String,
|
||||||
val user: UserResponse? = null,
|
val user: UserResponse? = null,
|
||||||
val properties: List<PropertyUserResponse> = emptyList(),
|
val properties: List<PropertyUserResponse> = emptyList()
|
||||||
val pendingUserId: UUID? = null
|
)
|
||||||
|
|
||||||
|
data class UpdateMeRequest(
|
||||||
|
val name: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class ResolveResult(
|
private data class ResolveResult(
|
||||||
val principal: MyPrincipal?,
|
val principal: MyPrincipal?
|
||||||
val pendingUserId: UUID?
|
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user