Persist pending users when no org exists
All checks were successful
build-and-deploy / build-deploy (push) Successful in 27s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 27s
This commit is contained in:
@@ -4,6 +4,7 @@ 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.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
|
||||||
@@ -16,13 +17,15 @@ import org.springframework.web.bind.annotation.RequestMapping
|
|||||||
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
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@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 organizationRepo: OrganizationRepo,
|
||||||
|
private val pendingUserRepo: PendingUserRepo
|
||||||
) {
|
) {
|
||||||
private val logger = LoggerFactory.getLogger(Auth::class.java)
|
private val logger = LoggerFactory.getLogger(Auth::class.java)
|
||||||
|
|
||||||
@@ -32,12 +35,8 @@ class Auth(
|
|||||||
request: HttpServletRequest
|
request: HttpServletRequest
|
||||||
): AuthResponse {
|
): AuthResponse {
|
||||||
logger.info("Auth verify hit, principalPresent={}", principal != null)
|
logger.info("Auth verify hit, principalPresent={}", principal != null)
|
||||||
val resolved = principal ?: resolvePrincipalFromHeader(request)
|
val resolved = principal?.let { ResolveResult(it, null) } ?: resolvePrincipalFromHeader(request)
|
||||||
return if (resolved == null) {
|
return resolved.toResponse()
|
||||||
AuthResponse(status = "NEEDS_ORG")
|
|
||||||
} else {
|
|
||||||
buildAuthResponse(resolved)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
@@ -45,12 +44,8 @@ class Auth(
|
|||||||
@AuthenticationPrincipal principal: MyPrincipal?,
|
@AuthenticationPrincipal principal: MyPrincipal?,
|
||||||
request: HttpServletRequest
|
request: HttpServletRequest
|
||||||
): AuthResponse {
|
): AuthResponse {
|
||||||
val resolved = principal ?: resolvePrincipalFromHeader(request)
|
val resolved = principal?.let { ResolveResult(it, null) } ?: resolvePrincipalFromHeader(request)
|
||||||
return if (resolved == null) {
|
return resolved.toResponse()
|
||||||
AuthResponse(status = "NEEDS_ORG")
|
|
||||||
} else {
|
|
||||||
buildAuthResponse(resolved)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildAuthResponse(principal: MyPrincipal): AuthResponse {
|
private fun buildAuthResponse(principal: MyPrincipal): AuthResponse {
|
||||||
@@ -78,7 +73,7 @@ class Auth(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolvePrincipalFromHeader(request: HttpServletRequest): MyPrincipal? {
|
private fun resolvePrincipalFromHeader(request: HttpServletRequest): ResolveResult {
|
||||||
val header = request.getHeader("Authorization") ?: throw ResponseStatusException(
|
val header = request.getHeader("Authorization") ?: throw ResponseStatusException(
|
||||||
HttpStatus.UNAUTHORIZED,
|
HttpStatus.UNAUTHORIZED,
|
||||||
"Missing Authorization token"
|
"Missing Authorization token"
|
||||||
@@ -99,7 +94,17 @@ class Auth(
|
|||||||
val orgs = organizationRepo.findAll()
|
val orgs = organizationRepo.findAll()
|
||||||
if (orgs.isEmpty()) {
|
if (orgs.isEmpty()) {
|
||||||
logger.warn("Auth verify user not found for uid={}, orgCount=0", decoded.uid)
|
logger.warn("Auth verify user not found for uid={}, orgCount=0", decoded.uid)
|
||||||
return null
|
val phone = decoded.claims["phone_number"] as? String
|
||||||
|
val name = decoded.claims["name"] as? String
|
||||||
|
val existing = pendingUserRepo.findByFirebaseUid(decoded.uid)
|
||||||
|
val pending = existing ?: pendingUserRepo.save(
|
||||||
|
com.android.trisolarisserver.models.property.PendingUser(
|
||||||
|
firebaseUid = decoded.uid,
|
||||||
|
phoneE164 = phone,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return ResolveResult(null, pending.id)
|
||||||
}
|
}
|
||||||
if (orgs.size == 1) {
|
if (orgs.size == 1) {
|
||||||
val org = orgs.first()
|
val org = orgs.first()
|
||||||
@@ -114,24 +119,45 @@ class Auth(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.warn("Auth verify auto-created user uid={}, userId={}, orgId={}", decoded.uid, created.id, org.id)
|
logger.warn("Auth verify auto-created user uid={}, userId={}, orgId={}", decoded.uid, created.id, org.id)
|
||||||
return MyPrincipal(
|
return ResolveResult(
|
||||||
userId = created.id ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User id missing"),
|
MyPrincipal(
|
||||||
|
userId = created.id
|
||||||
|
?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User id missing"),
|
||||||
firebaseUid = decoded.uid
|
firebaseUid = decoded.uid
|
||||||
|
),
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
logger.warn("Auth verify user not found for uid={}, orgCount={}", decoded.uid, orgs.size)
|
logger.warn("Auth verify user not found for uid={}, orgCount={}", decoded.uid, orgs.size)
|
||||||
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found")
|
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 MyPrincipal(
|
return ResolveResult(
|
||||||
|
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.toResponse(): AuthResponse {
|
||||||
|
return if (principal == null) {
|
||||||
|
AuthResponse(status = "NEEDS_ORG", pendingUserId = pendingUserId)
|
||||||
|
} else {
|
||||||
|
buildAuthResponse(principal)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class ResolveResult(
|
||||||
|
val principal: MyPrincipal?,
|
||||||
|
val pendingUserId: UUID?
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.android.trisolarisserver.models.property
|
||||||
|
|
||||||
|
import jakarta.persistence.Column
|
||||||
|
import jakarta.persistence.Entity
|
||||||
|
import jakarta.persistence.GeneratedValue
|
||||||
|
import jakarta.persistence.Id
|
||||||
|
import jakarta.persistence.Table
|
||||||
|
import jakarta.persistence.UniqueConstraint
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(
|
||||||
|
name = "pending_user",
|
||||||
|
uniqueConstraints = [UniqueConstraint(columnNames = ["firebase_uid"])]
|
||||||
|
)
|
||||||
|
class PendingUser(
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
@Column(columnDefinition = "uuid")
|
||||||
|
val id: UUID? = null,
|
||||||
|
|
||||||
|
@Column(name = "firebase_uid", nullable = false)
|
||||||
|
var firebaseUid: String,
|
||||||
|
|
||||||
|
@Column(name = "phone_e164")
|
||||||
|
var phoneE164: String? = null,
|
||||||
|
|
||||||
|
var name: String? = null,
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false, columnDefinition = "timestamptz")
|
||||||
|
val createdAt: OffsetDateTime = OffsetDateTime.now()
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.android.trisolarisserver.repo
|
||||||
|
|
||||||
|
import com.android.trisolarisserver.models.property.PendingUser
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
interface PendingUserRepo : JpaRepository<PendingUser, UUID> {
|
||||||
|
fun findByFirebaseUid(firebaseUid: String): PendingUser?
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user