Added encryption/decryption login
This commit is contained in:
parent
0681105778
commit
844cca761a
17
build.sbt
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
|
||||
|
@ -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")
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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 = _
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
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))
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
Normal file
58
src/main/scala/wow/doge/chatto/model/Message.scala
Normal file
@ -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
Normal file
30
src/main/scala/wow/doge/chatto/model/MessageCipher.scala
Normal file
@ -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")
|
||||
)
|
||||
}
|
@ -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]
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
def func2() =
|
||||
basicRequest
|
||||
.get(uri"https://httpbin.org/get")
|
||||
.response(asJson[HttpBinResponse])
|
||||
.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)
|
||||
def getUsers(credentials: UserCredentials) =
|
||||
tokenBasicRequest(credentials.token)
|
||||
.get(uri"http://localhost:8080/api/chat/get/users")
|
||||
.response(asJson[List[String]])
|
||||
.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]])
|
||||
def getEncryptedMessages(credentials: UserCredentials, user: String) =
|
||||
Request
|
||||
.messagesPaginated(credentials, user)
|
||||
.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…
Reference in New Issue
Block a user