diff --git a/.gitignore b/.gitignore index f63fac1..e6f29d6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ hs_err_pid* /project/project/target/ /project/target -/target \ No newline at end of file +/target + +.bsp diff --git a/app/controllers/authentication/AuthenticatedAction.scala b/app/controllers/authentication/AuthenticatedAction.scala index 0938b50..c765769 100644 --- a/app/controllers/authentication/AuthenticatedAction.scala +++ b/app/controllers/authentication/AuthenticatedAction.scala @@ -2,20 +2,19 @@ package controllers.authentication import dao.AuthTokensDAO import jakarta.inject.Inject -import models.AuthToken +import models.{AuthToken, User} import play.api.libs.json.* import play.api.mvc.Results.Unauthorized import play.api.mvc.{ActionRefiner, Request, Result, WrappedRequest} import scala.concurrent.{ExecutionContext, Future} -class AuthenticatedRequest[A](val token: AuthToken, request: Request[A]) extends WrappedRequest[A](request) +class AuthenticatedRequest[A](val token: AuthToken, val user: User, request: Request[A]) extends WrappedRequest[A](request) class AuthenticatedAction @Inject()(authTokensDao: AuthTokensDAO)(implicit val executionContext: ExecutionContext) extends ActionRefiner[Request, AuthenticatedRequest] { private final val tokenPrefix = "Token" override protected def refine[A](request: Request[A]): Future[Either[Result, AuthenticatedRequest[A]]] = { - val unauthenticatedRequestError = Left(Unauthorized(Json.obj("error" -> "Unauthenticated"))) val authHeader = request.headers.get("Authorization") .flatMap { header => if header.startsWith(tokenPrefix) then Some(header.substring(tokenPrefix.length).trim) @@ -25,11 +24,21 @@ class AuthenticatedAction @Inject()(authTokensDao: AuthTokensDAO)(implicit val e authHeader match { case Some(token) => - authTokensDao.find(token).map { - case Some(authToken) => Right(AuthenticatedRequest[A](authToken, request)) - case None => unauthenticatedRequestError + authTokensDao.find(token).flatMap { + case Some(authToken) => findUser(request, authToken) + case None => Future.successful(unauthenticatedRequestError) } case None => Future.successful(unauthenticatedRequestError) } } + + private val unauthenticatedRequestError = Left(Unauthorized(Json.obj("error" -> "Unauthenticated"))) + + private def findUser[A](request: Request[A], token: AuthToken) = { + authTokensDao.getUser(token).map { + case Some(user) => Right(AuthenticatedRequest(token, user, request)) + case None => unauthenticatedRequestError + } + } + } diff --git a/app/dao/AuthTokensDAO.scala b/app/dao/AuthTokensDAO.scala index b699b82..fef04f5 100644 --- a/app/dao/AuthTokensDAO.scala +++ b/app/dao/AuthTokensDAO.scala @@ -12,12 +12,11 @@ import java.time.{Duration, Instant} import scala.concurrent.{ExecutionContext, Future} import models.AuthToken import schemas.AuthTokens +import utils.Random import scala.language.postfixOps class AuthTokensDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)(implicit val ec: ExecutionContext) extends HasDatabaseConfigProvider[PostgresProfile] { - private lazy val random = SecureRandom() - private lazy val base64 = Base64.getEncoder private final lazy val tokenValidDuration = Duration.ofDays(30) def find(token: String): Future[Option[AuthToken]] = { @@ -26,12 +25,10 @@ class AuthTokensDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProv } def createToken(user: models.User): Future[AuthToken] = { - val tokenRaw = ByteBuffer.allocate(12) - random.nextBytes(tokenRaw.array()) val token = AuthToken( id = None, userId = user.id.get, - token = base64.encodeToString(tokenRaw.array()), + token = Random.randomBase64(12), createdAt = Instant.now, expiresAt = Instant.now.plus(tokenValidDuration) ) @@ -39,6 +36,11 @@ class AuthTokensDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProv db.run((AuthTokens returning AuthTokens.map(_.id)) += token).map { tokenId => token.copy(id = Some(tokenId)) } } + def getUser(token: AuthToken): Future[Option[models.User]] = { + val query = schemas.Users.filter(_.id === token.userId).take(1).result.headOption + db.run(query) + } + def destroy(token: AuthToken): Future[Unit] = { val q = AuthTokens.filter(_.token === token.token).delete db.run(q).map{ _ => () } diff --git a/app/utils/Random.scala b/app/utils/Random.scala new file mode 100644 index 0000000..1e71d2b --- /dev/null +++ b/app/utils/Random.scala @@ -0,0 +1,16 @@ +package utils + +import java.nio.ByteBuffer +import java.security.SecureRandom +import java.util.Base64 + +object Random { + private lazy val random = SecureRandom() + private lazy val base64 = Base64.getEncoder + + def randomBase64(bytes: Int): String = { + val tokenRaw = ByteBuffer.allocate(bytes) + random.nextBytes(tokenRaw.array()) + base64.encodeToString(tokenRaw.array()) + } +}