Skip to content

Commit

Permalink
Automate workflow for updating default password upon first login
Browse files Browse the repository at this point in the history
  • Loading branch information
haikalpribadi committed Dec 8, 2023
1 parent 2bbbb52 commit fd130dd
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 48 deletions.
2 changes: 2 additions & 0 deletions Studio.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import com.vaticle.typedb.studio.module.project.ProjectDialog
import com.vaticle.typedb.studio.module.type.TypeBrowser
import com.vaticle.typedb.studio.module.type.TypeDialog
import com.vaticle.typedb.studio.module.type.TypeEditor
import com.vaticle.typedb.studio.module.user.UpdateDefaultPasswordDialog
import com.vaticle.typedb.studio.service.Service
import com.vaticle.typedb.studio.service.common.util.Label
import com.vaticle.typedb.studio.service.common.util.Message
Expand Down Expand Up @@ -158,6 +159,7 @@ object Studio {
PreferenceDialog.MayShowDialogs()
ProjectDialog.MayShowDialogs(window)
TypeDialog.MayShowDialogs()
UpdateDefaultPasswordDialog.MayShowDialogs()
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions framework/common/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import kotlin.math.roundToInt

object Util {

const val ELLIPSES = "..."

fun Rect.contains(x: Int, y: Int): Boolean = this.contains(Offset(x.toFloat(), y.toFloat()))

fun toRectDP(rawRectangle: Rect, density: Float) = Rect(
Expand Down Expand Up @@ -73,6 +75,10 @@ object Util {
pop()
}.toAnnotatedString()

fun mayTruncate(string: String, length: Int): String {
return if (string.length <= length) string else string.take(length) + ELLIPSES
}

fun String.hyphenate(): String = this.replace(" ", "-")

// TODO: Investigate usages of this method -- why were they needed to begin with. Most likely is race condition.
Expand Down
3 changes: 2 additions & 1 deletion framework/output/LogOutput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.vaticle.typedb.driver.api.concept.thing.Relation
import com.vaticle.typedb.driver.api.concept.thing.Thing
import com.vaticle.typedb.driver.api.concept.type.Type
import com.vaticle.typedb.driver.api.concept.value.Value
import com.vaticle.typedb.studio.framework.common.Util
import com.vaticle.typedb.studio.framework.common.theme.Color
import com.vaticle.typedb.studio.framework.common.theme.Theme
import com.vaticle.typedb.studio.framework.editor.TextEditor
Expand Down Expand Up @@ -134,7 +135,7 @@ internal class LogOutput constructor(
if (!isCollecting.get()) return@launchAndHandle
val timeSinceLastResponse = System.currentTimeMillis() - lastOutputTime.get()
if (timeSinceLastResponse >= RUNNING_INDICATOR_DELAY.inWholeMilliseconds) {
output(INFO, "...")
output(INFO, Util.ELLIPSES)
duration = RUNNING_INDICATOR_DELAY
} else {
duration = RUNNING_INDICATOR_DELAY - timeSinceLastResponse.milliseconds
Expand Down
5 changes: 4 additions & 1 deletion module/Toolbar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import androidx.compose.ui.graphics.Color
import com.vaticle.typedb.driver.api.TypeDBSession
import com.vaticle.typedb.driver.api.TypeDBTransaction
import com.vaticle.typedb.studio.framework.common.URL
import com.vaticle.typedb.studio.framework.common.Util.mayTruncate
import com.vaticle.typedb.studio.framework.common.theme.Theme
import com.vaticle.typedb.studio.framework.common.theme.Theme.TOOLBAR_BUTTON_SIZE
import com.vaticle.typedb.studio.framework.common.theme.Theme.TOOLBAR_SEPARATOR_HEIGHT
Expand Down Expand Up @@ -61,6 +62,8 @@ import com.vaticle.typedb.studio.service.connection.DriverState.Status.DISCONNEC

object Toolbar {

private const val CONNECTION_NAME_LENGTH_LIMIT = 50

private val isConnected get() = Service.driver.isConnected
private val isScript get() = Service.driver.isScriptMode
private val isInteractive get() = Service.driver.isInteractiveMode
Expand Down Expand Up @@ -524,7 +527,7 @@ object Toolbar {
when (Service.driver.status) {
DISCONNECTED -> ConnectionButton(Label.CONNECT_TO_TYPEDB)
CONNECTING -> ConnectionButton(Label.CONNECTING)
CONNECTED -> ConnectionButton(Service.driver.connectionName!!)
CONNECTED -> ConnectionButton(mayTruncate(Service.driver.connectionName!!, CONNECTION_NAME_LENGTH_LIMIT))
}
}

Expand Down
31 changes: 11 additions & 20 deletions module/connection/ServerDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import com.vaticle.typedb.studio.framework.material.Tooltip
import com.vaticle.typedb.studio.service.Service
import com.vaticle.typedb.studio.service.common.util.Label
import com.vaticle.typedb.studio.service.common.util.Property
import com.vaticle.typedb.studio.service.common.util.Property.Server.TYPEDB
import com.vaticle.typedb.studio.service.common.util.Property.Server.TYPEDB_CORE
import com.vaticle.typedb.studio.service.common.util.Property.Server.TYPEDB_ENTERPRISE
import com.vaticle.typedb.studio.service.common.util.Sentence
import com.vaticle.typedb.studio.service.connection.DriverState.Status.CONNECTED
Expand All @@ -76,41 +76,32 @@ object ServerDialog {
private val state by mutableStateOf(ConnectServerForm())

private class ConnectServerForm : Form.State() {
var server: Property.Server by mutableStateOf(appData.server ?: Property.Server.TYPEDB)
var server: Property.Server by mutableStateOf(appData.server ?: Property.Server.TYPEDB_CORE)
var coreAddress: String by mutableStateOf(appData.coreAddress ?: "")
var enterpriseAddresses: MutableList<String> = mutableStateListOf<String>().also {
appData.enterpriseAddresses?.let { saved -> it.addAll(saved) }
}
var username: String by mutableStateOf(appData.username ?: "")
var password: String by mutableStateOf("")
var tlsEnabled: Boolean by mutableStateOf(appData.tlsEnabled ?: false)
var tlsEnabled: Boolean by mutableStateOf(appData.tlsEnabled ?: true)
var caCertificate: String by mutableStateOf(appData.caCertificate ?: "")

override fun cancel() = Service.driver.connectServerDialog.close()
override fun isValid(): Boolean = when (server) {
TYPEDB -> coreAddress.isNotBlank() && addressFormatIsValid(coreAddress)
TYPEDB_CORE -> coreAddress.isNotBlank() && addressFormatIsValid(coreAddress)
TYPEDB_ENTERPRISE -> !(enterpriseAddresses.isEmpty() || username.isBlank() || password.isBlank())
}

override fun submit() {
when (server) {
TYPEDB -> {
Service.driver.tryConnectToTypeDBAsync(coreAddress) {
Service.driver.connectServerDialog.close()
}
}
TYPEDB_ENTERPRISE -> {
val onSuccess = Service.driver.connectServerDialog::close
when {
caCertificate.isBlank() || !tlsEnabled -> Service.driver.tryConnectToTypeDBEnterpriseAsync(
enterpriseAddresses.toSet(), username, password, tlsEnabled, onSuccess
)
else -> Service.driver.tryConnectToTypeDBEnterpriseAsync(
enterpriseAddresses.toSet(), username, password, caCertificate, onSuccess
)
}
TYPEDB_CORE -> Service.driver.tryConnectToTypeDBCoreAsync(coreAddress) {
Service.driver.connectServerDialog.close()
}
TYPEDB_ENTERPRISE -> Service.driver.tryConnectToTypeDBEnterpriseAsync(
enterpriseAddresses.toSet(), username, password, tlsEnabled, caCertificate
) { Service.driver.connectServerDialog.close() }
}
password = ""
appData.server = server
appData.coreAddress = coreAddress
appData.enterpriseAddresses = enterpriseAddresses
Expand Down Expand Up @@ -158,7 +149,7 @@ object ServerDialog {
PasswordFormField(state)
TLSEnabledFormField(state)
if (state.tlsEnabled) CACertificateFormField(state = state, dialogWindow = window)
} else if (state.server == TYPEDB) {
} else if (state.server == TYPEDB_CORE) {
CoreAddressFormField(state, shouldFocus = Service.driver.isDisconnected)
}
Spacer(Modifier.weight(1f))
Expand Down
103 changes: 103 additions & 0 deletions module/user/UpdateDefaultPasswordDialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (C) 2022 Vaticle
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*
*/

package com.vaticle.typedb.studio.module.user

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vaticle.typedb.studio.framework.material.Dialog
import com.vaticle.typedb.studio.framework.material.Form
import com.vaticle.typedb.studio.service.Service
import com.vaticle.typedb.studio.service.common.util.Label
import com.vaticle.typedb.studio.service.common.util.Sentence.UPDATE_DEFAULT_PASSWORD_FOR_USERNAME
import com.vaticle.typedb.studio.service.common.util.Sentence.UPDATE_DEFAULT_PASSWORD_INSTRUCTION

object UpdateDefaultPasswordDialog {

private val WIDTH = 500.dp
private val HEIGHT = 225.dp

private val state by mutableStateOf(UpdateDefaultPasswordForm())

private class UpdateDefaultPasswordForm : Form.State() {
var oldPassword: String by mutableStateOf("")
var newPassword: String by mutableStateOf("")
var repeatPassword: String by mutableStateOf("")

override fun cancel() = Service.driver.updateDefaultPasswordDialog.cancel()
override fun submit() = Service.driver.updateDefaultPasswordDialog.submit(oldPassword, newPassword)
override fun isValid() = oldPassword.isNotEmpty() && newPassword.isNotEmpty()
&& oldPassword != newPassword && repeatPassword == newPassword
}

@Composable
fun MayShowDialogs() {
if (Service.driver.updateDefaultPasswordDialog.isOpen) UpdateDefaultPassword()
}

@Composable
private fun UpdateDefaultPassword() = Dialog.Layout(
state = Service.driver.connectServerDialog,
title = UPDATE_DEFAULT_PASSWORD_FOR_USERNAME.format(Service.data.connection.username).removeSuffix("."),
width = WIDTH,
height = HEIGHT
) {
Form.Submission(state = state, modifier = Modifier.fillMaxSize(), showButtons = true) {
Form.Text(value = UPDATE_DEFAULT_PASSWORD_INSTRUCTION, softWrap = true)
OldPasswordFormField(state)
NewPasswordFormField(state)
RepeatPasswordFormField(state)
}
}

@Composable
private fun OldPasswordFormField(state: UpdateDefaultPasswordForm) = Form.Field(label = Label.OLD_PASSWORD) {
Form.TextInput(
value = state.oldPassword,
onValueChange = { state.oldPassword = it },
isPassword = true,
modifier = Modifier.fillMaxSize(),
)
}

@Composable
private fun NewPasswordFormField(state: UpdateDefaultPasswordForm) = Form.Field(label = Label.NEW_PASSWORD) {
Form.TextInput(
value = state.newPassword,
onValueChange = { state.newPassword = it },
isPassword = true,
modifier = Modifier.fillMaxSize(),
)
}

@Composable
private fun RepeatPasswordFormField(state: UpdateDefaultPasswordForm) = Form.Field(label = Label.REPEAT_PASSWORD) {
Form.TextInput(
value = state.repeatPassword,
onValueChange = { state.repeatPassword = it },
isPassword = true,
modifier = Modifier.fillMaxSize(),
)
}
}
2 changes: 1 addition & 1 deletion service/Service.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object Service {
notification = NotificationService()
confirmation = ConfirmationService()
pages = PageService()
driver = DriverState(notification, preference)
driver = DriverState(notification, preference, data)
project = ProjectService(preference, data, notification, confirmation, driver, pages)
schema = SchemaService(driver.session, pages, notification, confirmation, status)
}
Expand Down
3 changes: 3 additions & 0 deletions service/common/util/Label.kt
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,12 @@ object Label {
const val MOVE_DIRECTORY = "Move Directory"
const val MS = "ms"
const val NEXT_OCCURRENCE = "Next Occurrence"
const val NEW_PASSWORD = "New password"
const val NO = "No"
const val NONE = "None"
const val NONE_IN_PARENTHESES = "($NONE)"
const val NOT = "Not"
const val OLD_PASSWORD = "Old password"
const val OK = "OK"
const val OPEN = "Open"
const val OPEN_PREFERENCES = "Open Preferences"
Expand Down Expand Up @@ -168,6 +170,7 @@ object Label {
const val PROJECT_IGNORED_PATHS = "Set Ignored Paths"
const val PROJECT_MANAGER = "Project Manager"
const val PROPERTY = "Property"
const val REPEAT_PASSWORD = "Repeat password"
const val QUERY = "Query"
const val QUERY_IS_RUNNING = "Query is Running"
const val QUERY_RESPONSE_TIME = "Query Response Time"
Expand Down
6 changes: 6 additions & 0 deletions service/common/util/Message.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ abstract class Message(codePrefix: String, codeNumber: Int, messagePrefix: Strin
Connection(17, "Failed to fetch schema for database '%s', due to:\n%s")
val CREDENTIALS_EXPIRE_SOON_HOURS =
Connection(18, "Your credentials are expiring within %s hour(s). Use TypeDB Console to update your password.")
val PASSWORD_UPDATED_SUCCESSFULLY =
Connection(19, "Password updated successfully.")
val RECONNECTED_WITH_NEW_PASSWORD_SUCCESSFULLY =
Connection(20, "Reconnected with new password successfully.")
val FAILED_TO_UPDATE_PASSWORD =
Connection(21, "Failed to update your password, due to: \n%s")
}
}

Expand Down
6 changes: 3 additions & 3 deletions service/common/util/Property.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ object Property {
}

enum class Server(val displayName: String) {
TYPEDB("TypeDB"),
TYPEDB_CORE("TypeDB Core"),
TYPEDB_ENTERPRISE("TypeDB Enterprise");

companion object {
fun of(name: String): Server? {
return when (name) {
TYPEDB.displayName -> TYPEDB
TYPEDB_CORE.displayName -> TYPEDB_CORE
TYPEDB_ENTERPRISE.displayName -> TYPEDB_ENTERPRISE
else -> null
}
Expand Down Expand Up @@ -86,7 +86,7 @@ object Property {

fun serverOf(displayName: String): Server {
return when (displayName) {
Server.TYPEDB.displayName -> Server.TYPEDB
Server.TYPEDB_CORE.displayName -> Server.TYPEDB_CORE
Server.TYPEDB_ENTERPRISE.displayName -> Server.TYPEDB_ENTERPRISE
else -> throw IllegalStateException("Unrecognised TypeDB server type")
}
Expand Down
4 changes: 4 additions & 0 deletions service/common/util/Sentence.kt
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,8 @@ object Sentence {
"undefining schema, in addition to matching. " + BUTTON_ENABLED_WHEN_SESSION_OPEN
const val TYPE_BROWSER_ONLY_INTERACTIVE =
"The Type Browser only works in 'interactive' mode."
const val UPDATE_DEFAULT_PASSWORD_FOR_USERNAME =
"Update default password for username '%s'."
const val UPDATE_DEFAULT_PASSWORD_INSTRUCTION =
"Update your initial default password below. The new password must be different, and the repeated password must be identical."
}
Loading

0 comments on commit fd130dd

Please sign in to comment.