Added encryption/decryption login

This commit is contained in:
Rohan Sircar 2020-08-22 20:42:48 +05:30
parent 0681105778
commit 844cca761a
16 changed files with 411 additions and 180 deletions

View File

@ -23,6 +23,14 @@ val osName = System.getProperty("os.name") match {
fork := true 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( libraryDependencies ++= Seq(
"base", "base",
"controls", "controls",
@ -40,6 +48,7 @@ libraryDependencies += "com.sfxcode.sapphire" %% "sapphire-extension" % "1.0.6"
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.1.1",
"org.scalafx" %% "scalafx" % "12.0.2-R18", "org.scalafx" %% "scalafx" % "12.0.2-R18",
"org.scalafx" %% "scalafx-extras" % "0.3.4", "org.scalafx" %% "scalafx-extras" % "0.3.4",
"com.softwaremill.sttp.client" %% "json4s" % "2.1.1", "com.softwaremill.sttp.client" %% "json4s" % "2.1.1",
@ -57,7 +66,11 @@ libraryDependencies ++= Seq(
) )
libraryDependencies += "org.asynchttpclient" % "async-http-client" % "2.12.1" libraryDependencies += "org.asynchttpclient" % "async-http-client" % "2.12.1"
libraryDependencies += "com.softwaremill.macwire" %% "macros" % "2.3.3" 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" libraryDependencies += "org.scalafx" %% "scalafxml-core-sfx8" % "0.5"
// https://mvnrepository.com/artifact/com.jfoenix/jfoenix // https://mvnrepository.com/artifact/com.jfoenix/jfoenix
@ -90,6 +103,6 @@ javaFxTitle := "chatto-sapphire"
javaFxCategory := "Aplication" javaFxCategory := "Aplication"
javaFxNativeBundles := "image" javaFxNativeBundles := "deb"
javaFxVerbose := true javaFxVerbose := true

View File

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

View File

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

View File

@ -87,7 +87,15 @@
-fx-background-color: transparent; -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-text-fill: white;
/* -fx-background-color: LIGHTGREEN; */ /* -fx-background-color: LIGHTGREEN; */
-fx-background-color: #82ccdd; -fx-background-color: #82ccdd;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,18 +2,13 @@ package wow.doge.chatto.controller
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control.MenuBar 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.controller.ViewController
import com.sfxcode.sapphire.core.scene.{ContentDidChangeEvent, ContentManager} import com.sfxcode.sapphire.core.scene.ContentManager
import com.typesafe.scalalogging.LazyLogging import com.typesafe.scalalogging.LazyLogging
import wow.doge.chatto.messagebuble.BubbledMDFXNode
import scalafx.scene.layout.GridPane
import scalafx.Includes._ import scalafx.Includes._
import javafx.scene.layout.HBox import javafx.scene.layout.HBox
import javafx.scene.layout.Priority import javafx.scene.layout.Priority
import wow.doge.chatto.control.UserBox
class MainViewController extends ViewController with LazyLogging { class MainViewController extends ViewController with LazyLogging {

View 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
}

View 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")
)
}

View File

@ -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]
}

View File

@ -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()
}

View File

@ -1,93 +1,71 @@
package wow.doge.chatto.service 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 sttp.client.json4s._
import org.json4s._ import org.json4s._
import sttp.client._ import sttp.client._
import scala.concurrent.Future
import sttp.client.asynchttpclient.WebSocketHandler
import javax.inject.Inject 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 wow.doge.chatto.types.AppTypes.HttpBackend
import com.typesafe.scalalogging.LazyLogging import com.typesafe.scalalogging.LazyLogging
import org.scalafx.extras._
import wow.doge.chatto.ApplicationController
import wow.doge.chatto.UserCredentials import wow.doge.chatto.UserCredentials
import javax.inject._ import javax.inject._
import wow.doge.chatto.AppDataHandler import wow.doge.chatto.AppDataHandler
import org.json4s.jackson.JsonMethods._
import org.json4s.ext.JavaTimeSerializers import org.json4s.ext.JavaTimeSerializers
import java.time.ZonedDateTime 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 implicit backend: HttpBackend
) extends LazyLogging { ) extends LazyLogging {
private implicit lazy val serialization = org.json4s.jackson.Serialization private implicit lazy val serialization = org.json4s.jackson.Serialization
private implicit lazy val formats = private implicit lazy val formats =
DefaultFormats ++ JavaTimeSerializers.all DefaultFormats ++ JavaTimeSerializers.all + MessageCipher.rename
private val domain = "http://localhost:8080" private val domain = "http://localhost:8080"
private lazy val baseUrl = uri"$domain/api/chat" 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) => { private lazy val tokenBasicRequest = (token: String) => {
basicRequest.header("X-AUTH-TOKEN", token) 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" private def endpoint(uri: String) = uri"$baseUrl/$uri"
def getUsers(credentials: UserCredentials) = async { def getUsers(credentials: UserCredentials) =
// logger.debug(s"${appDataHandler.appData}")
// println(
// write[ActiveUser](
// ActiveUser("hmm what is it", true, Some(ZonedDateTime.now()))
// )
// )
await {
// authBasicRequest(credentials)
tokenBasicRequest(credentials.token) tokenBasicRequest(credentials.token)
.get(uri"http://localhost:8080/api/chat/get/users") .get(uri"http://localhost:8080/api/chat/get/users")
.response(asJson[List[String]]) .response(asJson[List[String]])
.send() .send()
}
}
def getMessages(credentials: UserCredentials) = async { def getEncryptedMessages(credentials: UserCredentials, user: String) =
// logger.debug(s"${appDataHandler.appData}") Request
await { .messagesPaginated(credentials, user)
// authBasicRequest(credentials) .send()
tokenBasicRequest(credentials.token)
.get(uri"http://localhost:8080/api/chat/get/users") def getMessages(credentials: UserCredentials, user: String) =
.response(asJson[List[String]]) Request
.messagesPaginated(credentials, user)
.mapResponseRight(
_.map(_.toMessage("password", encryptionService.decrypt)).sequence
)
.send() .send()
}
}
def getActiveUsers(credentials: UserCredentials) = def getActiveUsers(credentials: UserCredentials) =
// authBasicRequest(credentials)
tokenBasicRequest(credentials.token) tokenBasicRequest(credentials.token)
.get(uri"http://localhost:8080/api/chat/get/active-users") .get(uri"http://localhost:8080/api/chat/get/active-users")
.response(asJson[List[ActiveUser]]) .response(asJson[List[ActiveUser]])
.send() .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( final case class HttpBinResponse(