Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Example of Nonce-based authentication with http4s.
- // Authentication middleware see at https://pastebin.com/G4y4qURn
- // for Cryptobits library add to build.sbt: "org.reactormonk" %% "cryptobits" % 1.2
- import java.security.MessageDigest
- import java.util.UUID
- import cats.Applicative
- import com.typesafe.config.ConfigFactory
- import scala.collection.mutable
- import scala.compat.Platform
- import scala.concurrent.duration._
- import scala.util.Try
- object AuthService {
- sealed trait TokenError
- case object InvalidToken extends TokenError
- case object InvalidUserId extends TokenError
- case object TokenExpired extends TokenError
- }
- class AuthService[F[_]: Applicative](userDao: UserDao[F]) {
- private val config = ConfigFactory.load()
- private val secretKey = config.getString("auth.secret.key")
- private val tokenLifetime = config.getInt("auth.token.lifetime.hours")
- private val sha256 = MessageDigest.getInstance("SHA-256")
- private val crypto = CryptoBits(PrivateKey(secretKey.getBytes))
- private val login2nonceMap = mutable.Map.empty[String, String]
- def signUp(login: String, passwordHex: String, email: String): F[Either[String, String]] = {
- import cats.implicits._
- val validationResult = for {
- _ <- validateUserName(login)
- _ <- validatePassword(passwordHex)
- _ <- validateEmail(email)
- } yield ()
- validationResult flatTraverse (_ => storeUser(login, passwordHex, email))
- }
- def signIn(login: String, signature: String, cnonce: String): F[Option[String]] = {
- import cats.implicits._
- userDao.findUser(login) map { _ flatMap { user =>
- val nonce = login2nonceMap.getOrElse(login, "error")
- val password = user.passwordHash
- val expectedSignature = sha256.digest(s"$nonce$cnonce$password".getBytes).map("%02x" format _).mkString
- user.userId match {
- case Some(id) if signature == expectedSignature =>
- login2nonceMap -= login
- Some(crypto.signToken(id.toString, Platform.currentTime.toString))
- case _ => None
- }
- }}
- }
- def userExists(login: String): F[Boolean] = {
- import cats.implicits._
- userDao.findUser(login) map (_.isDefined)
- }
- def generateNonce(login: String): String = {
- val nonce = UUID.randomUUID().toString.replace("-", "")
- login2nonceMap += (login -> nonce)
- nonce
- }
- def isTokenValid(token: String): Either[TokenError, Long] = {
- import cats.implicits._
- lazy val regex = """([\da-f]*)-(\d*)-(\d*)""".r
- lazy val isActive = token match {
- case regex(_, nonce, _) if (Platform.currentTime milliseconds) - (nonce.toLong milliseconds) < (tokenLifetime hours) => Right(Unit)
- case _ => Left(TokenExpired)
- }
- for {
- userIdStr <- crypto.validateSignedToken(token) toRight InvalidToken
- userId <- Try(userIdStr.toLong).toEither.leftMap(_ => InvalidUserId)
- _ <- isActive
- } yield userId
- }
- private def validateUserName(name: String): Either[String, Unit] = {
- Either.cond(name.matches("[\\w.\\-]{4,64}"), Unit, "Incorrect user name. It must be at least 4 characters long and contain latin characters or digits")
- }
- private def validatePassword(passwordHex: String): Either[String, Unit] = {
- // recommended conditions for a client:
- // password.length >= 8 && password.matches(".*[A-Z].*") && password.matches(".*[a-z].*") && password.matches(".*[\\d].*")
- Either.cond(passwordHex.matches("[\\da-f]{32,}"), Unit, "Incorrect password. Password must be at least 32 chars long HEX encoded string")
- }
- private def validateEmail(email: String): Either[String, Unit] = {
- Either.cond(email.matches(emailRegex), Unit, "Incorrect email address")
- }
- private def storeUser(login: String, passwordHex: String, email: String): F[Either[String, String]] = {
- import cats.implicits._
- val row = UserRow(None, login, passwordHex, email)
- userDao.saveUser(row) map (_.map(newId => crypto.signToken(newId.toString, Platform.currentTime.toString)))
- }
- }
Add Comment
Please, Sign In to add comment