RealworldAuthenticationUseCase.kt

package com.example.realworldkotlinspringbootjdbc.usecase.shared

import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.example.realworldkotlinspringbootjdbc.domain.RegisteredUser
import com.example.realworldkotlinspringbootjdbc.domain.UserRepository
import com.example.realworldkotlinspringbootjdbc.domain.user.Email
import com.example.realworldkotlinspringbootjdbc.domain.user.UserId
import com.example.realworldkotlinspringbootjdbc.util.MyError
import com.example.realworldkotlinspringbootjdbc.util.MySessionJwt
import org.springframework.stereotype.Component

/**
 * セッションJWT認証
 */
interface RealworldAuthenticationUseCase {
    /**
     * 実行
     *
     * - セッションJWTに登録済みユーザーIDとEmailが埋まっている
     * - セッション情報のEmail情報が、DBにある情報と異なる場合はJWTをデコードできてもセッションが古いとみなし、エラーを返す
     *
     * @param token JWT文字列
     * @return エラー or 登録済みユーザー
     */
    fun execute(token: String): Either<Unauthorized, RegisteredUser> = throw NotImplementedError()

    sealed interface Unauthorized {
        /**
         * Decodeに失敗
         */
        data class FailedDecodeToken(override val cause: MyError, val token: String) :
            Unauthorized,
            MyError.MyErrorWithMyError

        /**
         * 検索したUserが存在しなかった
         */
        data class NotFound(override val cause: MyError, val id: UserId) : Unauthorized, MyError.MyErrorWithMyError

        /**
         * Emailが合わなかった
         */
        data class NotMatchEmail(val oldEmail: Email, val newEmail: Email) : Unauthorized, MyError.Basic
    }
}

@Component
class RealworldAuthenticationUseCaseImpl(
    val userRepository: UserRepository,
    val mySessionJwt: MySessionJwt
) : RealworldAuthenticationUseCase {
    override fun execute(token: String): Either<RealworldAuthenticationUseCase.Unauthorized, RegisteredUser> {
        /**
         * JWTをセッション情報へデコード
         * Error -> 早期リターン
         */
        val decodedSession = mySessionJwt.decode(token).fold(
            { return RealworldAuthenticationUseCase.Unauthorized.FailedDecodeToken(it, token).left() },
            { it }
        )

        /**
         * セッション情報のUserIdから登録済みユーザーを取得
         * Error -> 早期リターン
         */
        val registeredUser = userRepository.findByUserId(decodedSession.userId).fold(
            {
                return when (it) {
                    is UserRepository.FindByUserIdError.NotFound -> RealworldAuthenticationUseCase.Unauthorized.NotFound(
                        it,
                        decodedSession.userId
                    ).left()
                }
            },
            { it }
        )

        /**
         * セッション情報のEmailと現在のEmailを比較
         * 不一致 -> エラー
         * 一致 -> 登録済みユーザー
         */
        return if (decodedSession.email.value == registeredUser.email.value) {
            registeredUser.right()
        } else {
            RealworldAuthenticationUseCase.Unauthorized.NotMatchEmail(decodedSession.email, registeredUser.email).left()
        }
    }
}