MySession.kt

package com.example.realworldkotlinspringbootjdbc.util

import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.exceptions.JWTCreationException
import com.auth0.jwt.exceptions.JWTDecodeException
import com.example.realworldkotlinspringbootjdbc.domain.user.Email
import com.example.realworldkotlinspringbootjdbc.domain.user.UserId
import org.springframework.stereotype.Component

data class MySession(
    val userId: UserId,
    val email: Email
)

interface MySessionJwt {
    companion object {
        const val ISSUER = "RealWorld"
        const val USER_ID_KEY = "userId"
        const val EMAIL_KEY = "email"
    }

    fun decode(token: String): Either<DecodeError, MySession> = throw NotImplementedError()
    fun encode(session: MySession): Either<EncodeError, String> = throw NotImplementedError()

    sealed interface DecodeError : MyError {
        data class FailedDecode(override val cause: JWTDecodeException, val token: String) :
            DecodeError,
            MyError.MyErrorWithThrowable

        data class NotMatchIssuer(val token: String, val actual: String, val expected: String) :
            DecodeError,
            MyError.Basic

        data class NothingRequiredClaim(val token: String) : DecodeError, MyError
    }

    sealed interface EncodeError : MyError {
        data class FailedEncode(override val cause: JWTCreationException, val session: MySession) :
            EncodeError,
            MyError.MyErrorWithThrowable
    }
}

@Component
object MySessionJwtImpl : MySessionJwt {
    override fun decode(token: String): Either<MySessionJwt.DecodeError, MySession> {
        val decodedToken = try {
            JWT.decode(token)
        } catch (e: JWTDecodeException) {
            return MySessionJwt.DecodeError.FailedDecode(cause = e, token).left()
        }
        return try {
            val userId = decodedToken.getClaim(MySessionJwt.USER_ID_KEY).asInt()
            val email = decodedToken.getClaim(MySessionJwt.EMAIL_KEY).asString()
            if (decodedToken.issuer == MySessionJwt.ISSUER) {
                val session = MySession(
                    UserId(userId),
                    Email.newWithoutValidation(email)
                )
                session.right()
            } else {
                MySessionJwt.DecodeError.NotMatchIssuer(token, decodedToken.issuer, MySessionJwt.ISSUER).left()
            }
        } catch (e: NullPointerException) {
            MySessionJwt.DecodeError.NothingRequiredClaim(token).left()
        }
    }

    override fun encode(session: MySession): Either<MySessionJwt.EncodeError, String> {
        val secret = "secret"
        return try {
            val token = JWT.create()
                .withIssuer(MySessionJwt.ISSUER)
                .withClaim(MySessionJwt.USER_ID_KEY, session.userId.value)
                .withClaim(MySessionJwt.EMAIL_KEY, session.email.value)
                .sign(Algorithm.HMAC256(secret))
            token.right()
        } catch (e: JWTCreationException) {
            MySessionJwt.EncodeError.FailedEncode(e, session).left()
        }
    }
}