Browse Source

Added encryption/decryption login

master
Rohan Sircar 4 years ago
parent
commit
844cca761a
  1. 17
      build.sbt
  2. 1
      project/plugin.sbt
  3. 2
      src/main/resources/application.conf
  4. 10
      src/main/resources/styles/chat.css
  5. 16
      src/main/scala/wow/doge/chatto/ApplicationController.scala
  6. 5
      src/main/scala/wow/doge/chatto/control/MessageBox.scala
  7. 1
      src/main/scala/wow/doge/chatto/control/UserBox.scala
  8. 1
      src/main/scala/wow/doge/chatto/control/UserBox2.scala
  9. 192
      src/main/scala/wow/doge/chatto/controller/ChatController.scala
  10. 11
      src/main/scala/wow/doge/chatto/controller/LoginController.scala
  11. 7
      src/main/scala/wow/doge/chatto/controller/MainViewController.scala
  12. 58
      src/main/scala/wow/doge/chatto/model/Message.scala
  13. 30
      src/main/scala/wow/doge/chatto/model/MessageCipher.scala
  14. 11
      src/main/scala/wow/doge/chatto/service/EncryptionService.scala
  15. 135
      src/main/scala/wow/doge/chatto/service/EncryptionServiceImpl.scala
  16. 88
      src/main/scala/wow/doge/chatto/service/UserService.scala

17
build.sbt

@ -23,6 +23,14 @@ val osName = System.getProperty("os.name") match {
fork := true
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.2
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := scalafixSemanticdb.revision // use Scalafix compatible version
)
)
libraryDependencies ++= Seq(
"base",
"controls",
@ -40,6 +48,7 @@ libraryDependencies += "com.sfxcode.sapphire" %% "sapphire-extension" % "1.0.6"
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.1.1",
"org.scalafx" %% "scalafx" % "12.0.2-R18",
"org.scalafx" %% "scalafx-extras" % "0.3.4",
"com.softwaremill.sttp.client" %% "json4s" % "2.1.1",
@ -57,7 +66,11 @@ libraryDependencies ++= Seq(
)
libraryDependencies += "org.asynchttpclient" % "async-http-client" % "2.12.1"
libraryDependencies += "com.softwaremill.macwire" %% "macros" % "2.3.3"
scalacOptions ++= Seq("-Ymacro-annotations", "-deprecation")
scalacOptions ++= Seq(
"-Ymacro-annotations",
"-deprecation",
"-Ywarn-unused:imports"
)
libraryDependencies += "org.scalafx" %% "scalafxml-core-sfx8" % "0.5"
// https://mvnrepository.com/artifact/com.jfoenix/jfoenix
@ -90,6 +103,6 @@ javaFxTitle := "chatto-sapphire"
javaFxCategory := "Aplication"
javaFxNativeBundles := "image"
javaFxNativeBundles := "deb"
javaFxVerbose := true

1
project/plugin.sbt

@ -4,3 +4,4 @@ addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0")
addSbtPlugin("com.quadstingray" % "sbt-javafx" % "1.5.2")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.17")

2
src/main/resources/application.conf

@ -1,5 +1,5 @@
sapphire.core.fxml.basePath="/fxml/"
application.name = "Application"
application.name = "Chatto"
project.name = "chatto-sapphire"
project.version = "0.1.0-SNAPSHOT"
stage.default {

10
src/main/resources/styles/chat.css

@ -87,7 +87,15 @@
-fx-background-color: transparent;
}
.chat-message-box {
.chat-message-sender-box {
-fx-text-fill: white;
-fx-background-color: LIGHTGREEN;
/* -fx-background-color: #82ccdd; */
-fx-background-radius: 30px;
-fx-padding: 20px;
}
.chat-message-receiver-box {
-fx-text-fill: white;
/* -fx-background-color: LIGHTGREEN; */
-fx-background-color: #82ccdd;

16
src/main/scala/wow/doge/chatto/ApplicationController.scala

@ -3,30 +3,17 @@ package wow.doge.chatto
import javax.enterprise.context.ApplicationScoped
import javax.enterprise.inject.Produces
import javax.inject.Named
import com.typesafe.config.ConfigFactory
import com.sfxcode.sapphire.core.controller.DefaultWindowController
// import org.asynchttpclient.Dsl._
import wow.doge.chatto.controller.MainViewController
import sttp.client.asynchttpclient.future.AsyncHttpClientFutureBackend
import sttp.client._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.async.Async.{async, await}
import sttp.client.json4s._
import org.json4s._
// import org.json4s.native.JsonMethods._
import org.json4s.jackson.JsonMethods._
import org.json4s.JsonDSL._
import scala.util.Success
import scala.util.Failure
import com.softwaremill.quicklens._
import org.scalafx.extras._
import wow.doge.chatto.service.UserService
import javax.inject._
import javafx.application.Platform
import com.sfxcode.sapphire.core.controller.SceneControllerDidChangeEvent
import javax.enterprise.event.Observes
import com.sfxcode.sapphire.core.controller.SceneControllerWillChangeEvent
@Named
@ApplicationScoped
class ApplicationController extends DefaultWindowController {
@ -55,6 +42,9 @@ class ApplicationController extends DefaultWindowController {
@Produces
def httpBackend = backend
// @Produces
// def encryptionService: EncryptionService = EncryptionServiceImpl()
def replacePrimarySceneContent(): Unit = {
// Styling
reloadStyles()

5
src/main/scala/wow/doge/chatto/control/MessageBox.scala

@ -1,11 +1,8 @@
package wow.doge.chatto.control
import javafx.scene.layout.HBox
import javafx.scene.control.Label
import scalafx.Includes._
import wow.doge.chatto.controller.ChatData
import com.sfxcode.sapphire.core.value.FXBean
import wow.doge.chatto.controller.Message
import wow.doge.chatto.model.Message
import com.sandec.mdfx.MDFXNode
import javafx.geometry.Pos
import javafx.scene.layout.Priority

1
src/main/scala/wow/doge/chatto/control/UserBox.scala

@ -5,7 +5,6 @@ import javafx.fxml.FXML
import javafx.scene.control.RadioButton
import javafx.scene.control.Label
import javafx.fxml.FXMLLoader
import scalafx.Includes._
class UserBox() extends VBox() {
@FXML private var _userRadioButton: RadioButton = _

1
src/main/scala/wow/doge/chatto/control/UserBox2.scala

@ -4,7 +4,6 @@ import javafx.scene.layout.HBox
import javafx.scene.control.Label
import scalafx.Includes._
import wow.doge.chatto.controller.ChatData
import com.sfxcode.sapphire.core.value.FXBean
class UserBox2(val username: String, val chatData: ChatData) extends HBox() {
val usernameLabel = new Label(username) {

192
src/main/scala/wow/doge/chatto/controller/ChatController.scala

@ -3,53 +3,30 @@ package wow.doge.chatto.controller
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.control.Button
import javafx.scene.layout.FlowPane
import javafx.scene.control.TextArea
import javafx.scene.control.ListView
import javafx.scene.layout.HBox
import javafx.scene.layout.VBox
import scalafx.Includes._
import wow.doge.chatto.control.UserBox
import javafx.application.Platform
import javax.inject.Inject
import org.scalafx.extras._
import wow.doge.chatto.messagebuble.BubbledMDFXNode
import wow.doge.chatto.service.UserService
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
import com.typesafe.scalalogging.LazyLogging
import com.sfxcode.sapphire.core.value.FXBean
import wow.doge.chatto.AppDataHandler
import com.jfoenix.controls.JFXListView
import scala.async.Async.{async, await}
import javafx.scene.paint.Color
import scalafx.collections.ObservableBuffer
import javafx.beans.property.SimpleListProperty
import wow.doge.chatto.control.UserBox2
import javafx.beans.value.ChangeListener
import com.sfxcode.sapphire.core.value.KeyBindings
import com.sfxcode.sapphire.core.value.FXBeanAdapter
import scalafx.collections.ObservableMap
import com.sfxcode.sapphire.core.value.BeanConversions
import javafx.util.converter.DateStringConverter
import javafx.beans.binding.Bindings
import wow.doge.chatto.service.ActiveUser
import scala.collection.mutable
import scala.collection.concurrent.TrieMap
import wow.doge.chatto.messagebuble.BubbleSpec
import javafx.scene.layout.Background
import javafx.scene.layout.BackgroundFill
import javafx.geometry.Pos
import scalafx.beans.property.ReadOnlyBufferProperty
import scalafx.beans.property.ReadOnlyBufferWrapper
import javafx.beans.property.ReadOnlyListProperty
import scalafx.beans.property.BufferProperty
import javafx.collections.FXCollections
import com.sandec.mdfx.MDFXNode
import javafx.scene.layout.BorderPane
import javafx.scene.layout.Priority
import net.synedra.validatorfx.Validator
import wow.doge.chatto.control.JFXSmoothScroll
import javafx.scene.control.ContextMenu
import javafx.scene.control.MenuItem
import javafx.scene.input.Clipboard
@ -57,16 +34,21 @@ import javafx.scene.input.ClipboardContent
import scalafx.scene.input.KeyCodeCombination
import scalafx.scene.input.KeyCode
import scalafx.scene.input.KeyCombination
import javafx.scene.input.DataFormat
import wow.doge.chatto.control.MessageBox
import javafx.scene.control.SelectionMode
import scalafx.beans.property.BooleanProperty
import javafx.scene.control.ListCell
import java.time.Instant
import com.github.marlonlom.utilities.timeago.TimeAgo
import wow.doge.chatto.service.EncryptionService
import wow.doge.chatto.model.MessageCipher
import wow.doge.chatto.model.MessageType
import scala.util.Try
import java.time.ZonedDateTime
import wow.doge.chatto.model.Message
class ChatController @Inject() (
userService: UserService,
encryptionService: EncryptionService,
appDataHandler: AppDataHandler
) extends AbstractViewController
with LazyLogging {
@ -86,19 +68,19 @@ class ChatController @Inject() (
@FXML private var isOnlineLabel: Label = _
@FXML private var selectedUserBox: HBox = _
private val usersBuffer = ObservableBuffer.empty[ActiveUser]
private val usersListProperty = BufferProperty(usersBuffer)
private lazy val usersBuffer = ObservableBuffer.empty[ActiveUser]
private lazy val usersListProperty = BufferProperty(usersBuffer)
/**
* Readonly property wrapping an unmodifiable list.
* Synchronized with the internal users list property.
* Attemping to modify the internal list will throw an exception
*/
val usersListROProp: ReadOnlyListProperty[ActiveUser] = BufferProperty(
lazy val usersListROProp: ReadOnlyListProperty[ActiveUser] = BufferProperty(
FXCollections.unmodifiableObservableList(usersListProperty())
)
private val chatDataStore = TrieMap.empty[String, ChatDataProperty]
private lazy val chatDataStore = TrieMap.empty[String, ChatDataProperty]
private lazy val chatDataAdapter = FXBeanAdapter[ChatData](this)
@ -108,7 +90,9 @@ class ChatController @Inject() (
chatMainPane.hgrow = Priority.ALWAYS
chatListView.selectionModel().selectionMode = SelectionMode.MULTIPLE
chatListView.setCellFactory(_ => new ChatListCell())
chatListView.setCellFactory(_ =>
new ChatListCell(appDataHandler.appData.credentials.username)
)
chatDataAdapter.set(FXBean(ChatData.empty))
@ -149,6 +133,8 @@ class ChatController @Inject() (
}
}
usersListView.items <== usersListProperty
usersListView
.selectionModel()
.selectedItemProperty()
@ -164,10 +150,45 @@ class ChatController @Inject() (
}
chatDataAdapter.set(chatDataBean)
maybeCDP.foreach(cdp => {
cdp.messages().clear()
lastActiveLabel.text <== cdp.lastActive
isOnlineLabel.text <== cdp.isActive.asString()
chatListView.items <== cdp.messages
// chatListView.items <== cdp.messages
// logger.debug(s"1 ${cdp.messages}")
})
async {
val maybeMessages = await {
offFXAndWait {
userService
.getMessages(appDataHandler.appData.credentials, nv.userName)
.map(_.body)
}
}
logger.debug(maybeMessages.toString)
onFX {
// maybeMessages.foreach(
// _.map(m =>
// maybeCDP.foreach(cdp => {
// cdp.messages() ++= m
// // logger.debug(
// // s"2 ${chatDataStore.get(nv.userName).map(_.messages())}"
// // )
// chatListView.items <== cdp.messages
// })
// )
// )
for {
tryMessages <- maybeMessages
messages <- tryMessages.toEither
cdp <- maybeCDP.toRight("CDP is null")
_ <- Right {
cdp.messages ++= messages
chatListView.items <== cdp.messages
}
} yield ()
}
}
})
})
@ -185,12 +206,6 @@ class ChatController @Inject() (
chatListMenu.items += copyMessageMenuItem
chatListView.contextMenu = chatListMenu
usersListView.items <== usersListProperty
val validator = new Validator()
submitButton.disable <== validator.containsErrorsProperty()
submitButton.onAction = (e) => {
if (!chatInput.text().equals("") &&
!chatInput.text().equals(" ") &&
@ -203,11 +218,19 @@ class ChatController @Inject() (
// cdp.username(),
// chatInput.text()
// )
cdp.messages += Message.empty.copy(message = chatInput.text())
cdp.messages += Message(
fromUser = appDataHandler.appData.credentials.username,
toUser = chatDataAdapter.get.bean.userName,
chatInput.text(),
Instant.now()
)
// Message.empty.copy(message = chatInput.text()) +=: cdp.messages()
})
}
}
val validator = new Validator()
validator
.createCheck()
.withMethod(c => {
@ -245,17 +268,28 @@ class ChatController @Inject() (
maybeActiveUsers.foreach(users => usersBuffer ++= users)
}
chatDataStore
.map { case (key, value) => value }
.foreach(cdp => {
cdp.messages ++= Seq(
Message.empty.copy(message = "hi"),
Message.empty.copy(message = "hello"),
Message.empty.copy(message = "bye")
)
})
// chatDataStore
// .map { case (key, value) => value }
// .foreach(cdp => {
// cdp.messages ++= Seq(
// Message.empty.copy(message = "hi"),
// Message.empty.copy(message = "hello"),
// Message.empty.copy(message = "bye")
// )
// })
// simulate update
val maybeCDP = for {
usersMap <- maybeActiveUsers.map(_.groupBy(_.userName))
user <- usersMap.get("user1").toRight("")
cdp <- chatDataStore.get("user1").toRight("")
} yield (cdp)
maybeCDP.foreach(cdp => {
cdp.isActive() = true
})
}
}
def func() = {
val x = offFXAndWait {
2 + 3
@ -272,6 +306,9 @@ class ChatController @Inject() (
chatDataAdapter.set(FXBean(ChatData.empty))
usersListView.items().clear()
chatListView.items().clear()
// chatDataStore.foreach {
// case (_, cdp) => cdp.messages.clear()
// }
chatDataStore.clear()
usersBuffer.clear()
chatInput.clear()
@ -291,55 +328,42 @@ final case class ChatData(
.getOrElse("User has not logged in yet")
lazy val onlineString = activeUser.online.toString()
}
object ChatData {
final object ChatData {
def empty = {
ChatData("empty", ActiveUser.empty, ObservableBuffer.empty[Message])
}
}
class ChatDataProperty(chatData: ChatData) {
final class ChatDataProperty(chatData: ChatData) {
val bean = FXBean(chatData)
val username = bean.getStringProperty("userName")
val isActive = bean.getBooleanProperty("activeUser.online")
val lastActive = bean.getStringProperty("lastActiveString")
val messages = BufferProperty(chatData.messages)
def updateItem(chatData: ChatData) = {
username() = chatData.userName
isActive() = chatData.activeUser.online
lastActive() = chatData.lastActiveString
messages() ++= chatData.messages
}
}
final case class Message(
final case class EncryptedMessage(
fromUser: String,
toUser: String,
message: String,
messageTime: Instant
)
object Message {
lazy val markdownStyleSheet =
getClass().getResource("/styles/markdown.css").toExternalForm()
def empty = Message("", "", "", Instant.MIN)
def createMdMessageBox(
message: Message
) = {
val mdfxNode = new MDFXNode(message.message);
mdfxNode
.getStylesheets()
.add(markdownStyleSheet)
mdfxNode.setMaxWidth(500)
mdfxNode.vgrow = Priority.ALWAYS
mdfxNode.setAlignment(Pos.CENTER)
mdfxNode.styleClass = Seq("chat-message-box")
val box = new HBox()
box.setAlignment(Pos.CENTER_RIGHT)
// box.maxWidth(500)
box.hgrow = Priority.ALWAYS
box.vgrow = Priority.ALWAYS
box.children ++= Seq(mdfxNode)
box.fillHeight = true
box
}
messageCipher: MessageCipher,
messageTime: ZonedDateTime
) {
def toMessage(
passphrase: String,
decryptionFn: (String, MessageCipher) => Try[String]
): Try[Message] =
decryptionFn(passphrase, this.messageCipher)
.map(ms => Message(this.fromUser, this.toUser, ms, messageTime.toInstant))
}
final class ChatListCell extends ListCell[Message] {
final class ChatListCell(principal: String) extends ListCell[Message] {
private val messageBox = new MessageBox()
override def updateItem(item: Message, empty: Boolean): Unit = {
super.updateItem(item, empty)
@ -349,7 +373,11 @@ final class ChatListCell extends ListCell[Message] {
} else {
// messageBox.setItem(item)
// setGraphic(messageBox)
setGraphic(Message.createMdMessageBox(item))
if (principal.equals(item.fromUser)) {
setGraphic(Message.createMdMessageBox(item, MessageType.Sender))
} else {
setGraphic(Message.createMdMessageBox(item, MessageType.Receiver))
}
}
}
}

11
src/main/scala/wow/doge/chatto/controller/LoginController.scala

@ -1,18 +1,12 @@
package wow.doge.chatto.controller
import com.typesafe.scalalogging.LazyLogging
import com.sfxcode.sapphire.core.controller.ViewController
import javafx.fxml.FXML
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXTextField
import com.jfoenix.controls.JFXPasswordField
import scalafx.Includes._
import scalafx.event.ActionEvent
import com.sfxcode.sapphire.core.value.KeyBindings
import scalafx.scene.layout.VBox
import com.sfxcode.sapphire.core.value.FXBean
import javax.inject.Inject
import com.sfxcode.sapphire.core.value.FXBeanAdapter
import wow.doge.chatto.service.UserService
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
@ -20,18 +14,13 @@ import scala.util.Failure
import javafx.scene.control.Label
import javafx.scene.input.KeyCode
import scala.async.Async.{async, await}
import wow.doge.chatto.AppData
import wow.doge.chatto.UserCredentials
import sttp.client._
import scala.concurrent.Future
import sttp.client.asynchttpclient.WebSocketHandler
import wow.doge.chatto.types.AppTypes.HttpBackend
import wow.doge.chatto.types.AppTypes
import org.scalafx.extras._
import wow.doge.chatto.AppDataHandler
import com.sfxcode.sapphire.core.value.BeanConversions
import javafx.scene.layout.StackPane
import com.jfoenix.controls.JFXSpinner
class LoginController @Inject() (
userService: UserService,

7
src/main/scala/wow/doge/chatto/controller/MainViewController.scala

@ -2,18 +2,13 @@ package wow.doge.chatto.controller
import javafx.fxml.FXML
import javafx.scene.control.MenuBar
import javafx.scene.layout.Pane
import javax.enterprise.event.Observes
import com.sfxcode.sapphire.core.controller.ViewController
import com.sfxcode.sapphire.core.scene.{ContentDidChangeEvent, ContentManager}
import com.sfxcode.sapphire.core.scene.ContentManager
import com.typesafe.scalalogging.LazyLogging
import wow.doge.chatto.messagebuble.BubbledMDFXNode
import scalafx.scene.layout.GridPane
import scalafx.Includes._
import javafx.scene.layout.HBox
import javafx.scene.layout.Priority
import wow.doge.chatto.control.UserBox
class MainViewController extends ViewController with LazyLogging {

58
src/main/scala/wow/doge/chatto/model/Message.scala

@ -0,0 +1,58 @@
package wow.doge.chatto.model
import java.time.Instant
import com.sandec.mdfx.MDFXNode
import javafx.scene.layout.HBox
import scalafx.Includes._
import javafx.scene.layout.Priority
import javafx.geometry.Pos
import wow.doge.chatto.model.MessageType.Sender
import wow.doge.chatto.model.MessageType.Receiver
final case class Message(
fromUser: String,
toUser: String,
message: String,
messageTime: Instant
)
object Message {
lazy val markdownStyleSheet =
getClass().getResource("/styles/markdown.css").toExternalForm()
def empty = Message("", "", "", Instant.MIN)
def createMdMessageBox(
message: Message,
messageType: MessageType
) = {
val mdfxNode = new MDFXNode(message.message);
mdfxNode
.getStylesheets()
.add(markdownStyleSheet)
mdfxNode.setMaxWidth(500)
mdfxNode.vgrow = Priority.ALWAYS
mdfxNode.setAlignment(Pos.CENTER)
mdfxNode.styleClass = messageType match {
case Sender => Seq("chat-message-sender-box")
case Receiver => Seq("chat-message-receiver-box")
}
val box = new HBox()
messageType match {
case Receiver => box.setAlignment(Pos.CENTER_LEFT)
case Sender => box.setAlignment(Pos.CENTER_RIGHT)
}
// box.maxWidth(500)
box.hgrow = Priority.ALWAYS
box.vgrow = Priority.ALWAYS
box.children ++= Seq(mdfxNode)
box.fillHeight = true
box
}
}
sealed trait MessageType
object MessageType {
case object Sender extends MessageType
case object Receiver extends MessageType
}

30
src/main/scala/wow/doge/chatto/model/MessageCipher.scala

@ -0,0 +1,30 @@
package wow.doge.chatto.model
import org.json4s.FieldSerializer
import org.json4s.FieldSerializer._
case class MessageCipher(
iv: String,
v: Int,
iterations: Int,
keySize: Int,
tagSize: Int,
mode: String,
adata: String,
cipher: String,
salt: String,
cipherText: String
)
object MessageCipher {
val rename = FieldSerializer[MessageCipher](
renameTo("iterations", "iter") orElse
renameTo("keySize", "ks") orElse
renameTo("tagSize", "ts") orElse
renameTo("cipherText", "ct"),
renameFrom("iter", "iterations") orElse
renameFrom("ks", "keySize") orElse
renameFrom("ts", "tagSize") orElse
renameFrom("ct", "cipherText")
)
}

11
src/main/scala/wow/doge/chatto/service/EncryptionService.scala

@ -0,0 +1,11 @@
package wow.doge.chatto.service
import wow.doge.chatto.model.MessageCipher
import scala.util.Try
trait EncryptionService {
def encrypt(password: String, plainText: String): String Either MessageCipher
def decrypt(password: String, cipher: MessageCipher): Try[String]
}

135
src/main/scala/wow/doge/chatto/service/EncryptionServiceImpl.scala

@ -0,0 +1,135 @@
package wow.doge.chatto.service
import wow.doge.chatto.model.MessageCipher;
import java.util.Base64;
import javax.crypto.Cipher;;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom
import scala.util.Try
class EncryptionServiceImpl extends EncryptionService {
override def encrypt(
password: String,
plainText: String
): String Either MessageCipher = {
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val secureRandom = new SecureRandom()
val saltLength = 12
val keyLength = 128
val iterationCount = 10000
val tagSize = 128
val encode = (bytes: Array[Byte]) =>
Base64.getEncoder().encodeToString(bytes)
val salt = Array(saltLength.toByte)
secureRandom.nextBytes(salt)
val nonce = Array(12.toByte)
secureRandom.nextBytes(nonce)
// val spec =
// new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength)
// val tmp = factory.generateSecret(spec)
// val secretKey = new SecretKeySpec(tmp.getEncoded(), "AES")
// val cipher = Cipher.getInstance("AES/GCM/NoPadding")
// cipher.init(
// Cipher.ENCRYPT_MODE,
// secretKey,
// new GCMParameterSpec(128, nonce)
// )
// val cipherTextByte = cipher.doFinal(plainText.getBytes)
val messageCipher = for {
factory <- {
Try(SecretKeyFactory.getInstance("PBKDFWithHmacSHA56")).toOption
.toRight("Failed to get skf instance")
}
spec <- {
Try(
new PBEKeySpec(
password.toCharArray(),
salt,
iterationCount,
keyLength
)
).toOption.toRight("Failed to get pbekeyspec")
}
secret <- Try(factory.generateSecret(spec)).toOption
.toRight("Failed to get secret")
secretKey <- Try(new SecretKeySpec(secret.getEncoded(), "AES")).toOption
.toRight("Failed to get secret key")
cipher <- Try(Cipher.getInstance("AES/GCM/NoPadding")).toOption
.toRight("Failed to get cipher instance")
_ <- Right(
cipher.init(
Cipher.ENCRYPT_MODE,
secretKey,
new GCMParameterSpec(128, nonce)
)
)
cipherTextByte <- Try(cipher.doFinal(plainText.getBytes())).toOption
.toRight("Failed to generate cipher")
messageCipher = MessageCipher(
v = 1,
salt = encode(salt),
mode = "gcm",
iterations = iterationCount,
cipher = "aes",
adata = "",
cipherText = encode(cipherTextByte),
iv = encode(nonce),
keySize = keyLength,
tagSize = tagSize
)
} yield (messageCipher)
messageCipher
}
override def decrypt(
password: String,
messageCipher: MessageCipher
): Try[String] = {
val decode = (text: String) => {
Base64.getDecoder().decode(text)
}
Try {
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val spec = new PBEKeySpec(
password.toCharArray(),
decode(messageCipher.salt),
messageCipher.iterations,
messageCipher.keySize
);
val tmp = factory.generateSecret(spec);
val secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(
Cipher.DECRYPT_MODE,
secretKey,
new GCMParameterSpec(128, decode(messageCipher.iv))
);
new String(cipher.doFinal(decode(messageCipher.cipherText)));
}
}
}
object EncryptionServiceImpl {
def apply() = new EncryptionServiceImpl()
}

88
src/main/scala/wow/doge/chatto/service/UserService.scala

@ -1,93 +1,71 @@
package wow.doge.chatto.service
import scala.concurrent.ExecutionContext
import scala.concurrent.ExecutionContext.Implicits.global
import scala.async.Async.{async, await}
import sttp.client.json4s._
import org.json4s._
import sttp.client._
import scala.concurrent.Future
import sttp.client.asynchttpclient.WebSocketHandler
import javax.inject.Inject
import scala.util.Success
import scala.util.Failure
import wow.doge.chatto.AppData
import wow.doge.chatto.types.AppTypes.HttpBackend
import com.typesafe.scalalogging.LazyLogging
import org.scalafx.extras._
import wow.doge.chatto.ApplicationController
import wow.doge.chatto.UserCredentials
import javax.inject._
import wow.doge.chatto.AppDataHandler
import org.json4s.jackson.JsonMethods._
import org.json4s.ext.JavaTimeSerializers
import java.time.ZonedDateTime
import org.json4s.jackson.Serialization._
import wow.doge.chatto.model.MessageCipher
import wow.doge.chatto.controller.EncryptedMessage
import cats.implicits._
class UserService @Inject() (appDataHandler: AppDataHandler)(
class UserService @Inject() (
appDataHandler: AppDataHandler,
encryptionService: EncryptionService
)(
implicit backend: HttpBackend
) extends LazyLogging {
private implicit lazy val serialization = org.json4s.jackson.Serialization
private implicit lazy val formats =
DefaultFormats ++ JavaTimeSerializers.all
DefaultFormats ++ JavaTimeSerializers.all + MessageCipher.rename
private val domain = "http://localhost:8080"
private lazy val baseUrl = uri"$domain/api/chat"
// private lazy val authBasicRequest = (credentials: UserCredentials) =>
// basicRequest.auth
// .basic(credentials.username, credentials.password)
// .header("X-AUTH-TOKEN", credentials.token)
private lazy val tokenBasicRequest = (token: String) => {
basicRequest.header("X-AUTH-TOKEN", token)
}
def func1() = async {
val willBeResponse = func2()
val r = await { willBeResponse }
r.body.map(println)
}
private def endpoint(uri: String) = uri"$baseUrl/$uri"
def func2() =
basicRequest
.get(uri"https://httpbin.org/get")
.response(asJson[HttpBinResponse])
def getUsers(credentials: UserCredentials) =
tokenBasicRequest(credentials.token)
.get(uri"http://localhost:8080/api/chat/get/users")
.response(asJson[List[String]])
.send()
private def endpoint(uri: String) = uri"$baseUrl/$uri"
def getUsers(credentials: UserCredentials) = async {
// logger.debug(s"${appDataHandler.appData}")
// println(
// write[ActiveUser](
// ActiveUser("hmm what is it", true, Some(ZonedDateTime.now()))
// )
// )
await {
// authBasicRequest(credentials)
tokenBasicRequest(credentials.token)
.get(uri"http://localhost:8080/api/chat/get/users")
.response(asJson[List[String]])
.send()
}
}
def getEncryptedMessages(credentials: UserCredentials, user: String) =
Request
.messagesPaginated(credentials, user)
.send()
def getMessages(credentials: UserCredentials) = async {
// logger.debug(s"${appDataHandler.appData}")
await {
// authBasicRequest(credentials)
tokenBasicRequest(credentials.token)
.get(uri"http://localhost:8080/api/chat/get/users")
.response(asJson[List[String]])
.send()
}
}
def getMessages(credentials: UserCredentials, user: String) =
Request
.messagesPaginated(credentials, user)
.mapResponseRight(
_.map(_.toMessage("password", encryptionService.decrypt)).sequence
)
.send()
def getActiveUsers(credentials: UserCredentials) =
// authBasicRequest(credentials)
tokenBasicRequest(credentials.token)
.get(uri"http://localhost:8080/api/chat/get/active-users")
.response(asJson[List[ActiveUser]])
.send()
object Request {
lazy val messagesPaginated = (credentials: UserCredentials, user: String) =>
tokenBasicRequest(credentials.token)
.get(
uri"http://localhost:8080/api/chat/get/messages/$user?page=0&size=9"
)
.response(asJson[List[EncryptedMessage]])
}
}
final case class HttpBinResponse(

Loading…
Cancel
Save