diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 5616990..3261b57 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -17,6 +17,10 @@ import di.viewModelModule import org.koin.core.context.GlobalContext.startKoin import ui.App +/** + * Main entry point of the application. + * Initializes Koin for dependency injection and sets up the main application window. + */ fun main() = application { startKoin { diff --git a/src/main/kotlin/core/AppState.kt b/src/main/kotlin/core/AppState.kt index dd7de5f..042c7e7 100644 --- a/src/main/kotlin/core/AppState.kt +++ b/src/main/kotlin/core/AppState.kt @@ -10,54 +10,99 @@ import ui.screens.LoginScreen import ui.screens.LoginSplashScreen import ui.screens.RegisterScreen +/** + * AppState class manages the state of the application, including the current user, + * master password, and the initial screen to display. + */ class AppState { private var currentUser by mutableStateOf(null) private var userExists by mutableStateOf(false) private var masterPassword by mutableStateOf(null) + /** + * Gets the authenticated user. + */ val getAuthenticatedUser: User? get() = currentUser + /** + * Gets the username of the current user if they exist, otherwise returns "system". + */ val userName: String get() = currentUser?.userName.takeIf { userExists } ?: "system" + /** + * Updates the current user. + * @param user The user to set as the current user. + */ fun updateCurrentUser(user: User?) { currentUser = user } + /** + * Sets whether a user exists. + * @param exists Boolean indicating if a user exists. + */ fun userExists(exists: Boolean) { userExists = exists } + /** + * Clears the current user. + */ fun clearCurrentUser() { currentUser = null } + /** + * Determines the initial screen to display based on user existence and authentication status. + * @return The initial screen to display. + */ fun initialScreen(): Screen = when { userExists && isAuthenticated -> LoginSplashScreen() userExists -> LoginScreen() else -> RegisterScreen() } + /** + * Initializes the master password. + * @param password The master password to set. + */ fun initializeMasterPassword(password: CharArray) { clearMasterPassword() masterPassword = password.copyOf() } + /** + * Fetches the master password. + * @return A copy of the master password. + */ fun fetchMasterPassword(): CharArray? { return masterPassword?.copyOf() } + /** + * Clears the master password. + */ fun clearMasterPassword() { masterPassword?.fill('\u0000') masterPassword = null } + /** + * Checks if the master password is present. + * @return True if the master password is present, false otherwise. + */ fun isMasterPasswordPresent(): Boolean { return masterPassword != null } + /** + * Encrypts a string using the master password. + * @param text The string to encrypt. + * @return The encrypted string. + */ fun encryptString(text: String?): String { return text?.let { MasterPasswordManager.encryptString( @@ -69,6 +114,11 @@ class AppState { } ?: "" } + /** + * Decrypts a string using the master password. + * @param text The string to decrypt. + * @return The decrypted string. + */ fun decryptPassword(text: String?): String { return text?.let { MasterPasswordManager.decryptString( @@ -80,7 +130,10 @@ class AppState { } ?: "" } - + /** + * Checks if the user is authenticated. + * @return True if the user is authenticated, false otherwise. + */ private val isAuthenticated: Boolean get() = currentUser != null diff --git a/src/main/kotlin/core/Config.kt b/src/main/kotlin/core/Config.kt index 1f27e0d..fbe2586 100644 --- a/src/main/kotlin/core/Config.kt +++ b/src/main/kotlin/core/Config.kt @@ -6,19 +6,31 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule import core.configs.DatabaseConfig import core.configs.JwtConfig +/** + * Data class representing the configuration settings for the application. + * + * @property database The database configuration settings. + * @property jwt The JWT configuration settings. + */ data class Config( val database: DatabaseConfig, val jwt: JwtConfig ) +/** + * Loads the configuration settings from the `application.yaml` file. + * + * @return The loaded configuration settings. + * @throws IllegalArgumentException if the `application.yaml` resource is not found. + */ fun loadConfigs(): Config { val mapper = ObjectMapper(YAMLFactory()).apply { registerModule(KotlinModule.Builder().build()) } val resourceStream = Config::class.java.classLoader - .getResourceAsStream("application.yaml") - ?: throw IllegalArgumentException("Resource not found: application.yaml") + .getResourceAsStream("application.yaml") + ?: throw IllegalArgumentException("Resource not found: application.yaml") return mapper.readValue(resourceStream, Config::class.java) } \ No newline at end of file diff --git a/src/main/kotlin/core/DatabaseFactory.kt b/src/main/kotlin/core/DatabaseFactory.kt index 25a4101..003be0a 100644 --- a/src/main/kotlin/core/DatabaseFactory.kt +++ b/src/main/kotlin/core/DatabaseFactory.kt @@ -7,8 +7,17 @@ import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.DatabaseConfig import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger +/** + * The `DatabaseFactory` object is responsible for creating and configuring the database connection. + */ object DatabaseFactory { + /** + * Creates and configures a database connection using the provided configuration. + * + * @param config The configuration object containing database settings. + * @return The configured `Database` instance. + */ fun create(config: Config): Database { val dbConfig = config.database diff --git a/src/main/kotlin/core/form/FormField.kt b/src/main/kotlin/core/form/FormField.kt index b513422..65a9aaa 100644 --- a/src/main/kotlin/core/form/FormField.kt +++ b/src/main/kotlin/core/form/FormField.kt @@ -4,6 +4,9 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import core.form.validation.Validator +/** + * Represents a form field with a value, an error state, and a validator. + */ data class FormField( var value: MutableState = mutableStateOf(""), var error: MutableState = mutableStateOf(null), diff --git a/src/main/kotlin/core/form/validation/CommonRules.kt b/src/main/kotlin/core/form/validation/CommonRules.kt index 578ff92..a17eaee 100644 --- a/src/main/kotlin/core/form/validation/CommonRules.kt +++ b/src/main/kotlin/core/form/validation/CommonRules.kt @@ -3,6 +3,9 @@ package core.form.validation import core.form.FormField import java.util.* +/** + * Contains common validation rules for form fields. + */ val emailRule: ValidationRule = ValidationRule( condition = { email -> email.matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$")) diff --git a/src/main/kotlin/core/form/validation/FormValidator.kt b/src/main/kotlin/core/form/validation/FormValidator.kt index deb0f14..747539d 100644 --- a/src/main/kotlin/core/form/validation/FormValidator.kt +++ b/src/main/kotlin/core/form/validation/FormValidator.kt @@ -5,19 +5,36 @@ import androidx.compose.runtime.mutableStateOf import core.form.FormField import core.form.FormFieldName +/** + * A class responsible for managing and validating form fields. + */ class FormValidator { private val fields = mutableMapOf() private val _isValid = mutableStateOf(true) + /** + * A read-only state representing the overall validity of the form. + */ val isValid: State get() = _isValid - + /** + * Adds a form field to the validator. + * + * @param name The name of the form field. + * @param field The form field to be added. + * @return The current instance of FormValidator. + */ fun addField(name: FormFieldName, field: FormField): FormValidator { fields[name] = field return this } + /** + * Validates a specific form field by its name. + * + * @param name The name of the form field to validate. + */ fun validateField(name: FormFieldName) { fields[name]?.let { field -> val (isValid, errorMessage) = field.validator.validate(field.value.value) @@ -26,8 +43,19 @@ class FormValidator { } } + /** + * Retrieves a form field by its name. + * + * @param name The name of the form field. + * @return The form field if found, otherwise null. + */ fun getField(name: FormFieldName): FormField? = fields[name] + /** + * Checks if all form fields are valid. + * + * @return True if all form fields are valid, otherwise false. + */ fun isValid(): Boolean { return fields.values.all { field -> validateField(field) @@ -35,17 +63,30 @@ class FormValidator { } } + /** + * Validates all form fields. + * + * @return The current instance of FormValidator. + */ fun validateAllFields(): FormValidator { fields.keys.forEach { name -> validateField(name) } return this } + /** + * Updates the overall validity state of the form. + */ private fun updateFormValidity() { _isValid.value = fields.values.all { field -> field.error.value == null } } + /** + * Validates a form field. + * + * @param field The form field to validate. + */ private fun validateField(field: FormField) { val (isValid, errorMessage) = field.validator.validate(field.value.value) field.error.value = if (isValid) null else errorMessage diff --git a/src/main/kotlin/core/form/validation/ValidationRule.kt b/src/main/kotlin/core/form/validation/ValidationRule.kt index 0d7ffe4..71bbdf5 100644 --- a/src/main/kotlin/core/form/validation/ValidationRule.kt +++ b/src/main/kotlin/core/form/validation/ValidationRule.kt @@ -1,6 +1,12 @@ package core.form.validation +/** + * Represents a validation rule for form input. + * + * @property condition A lambda function that takes a String and returns a Boolean indicating if the condition is met. + * @property errorMessage The error message to be displayed if the condition is not met. + */ data class ValidationRule( val condition: (String) -> Boolean, val errorMessage: String -) +) \ No newline at end of file diff --git a/src/main/kotlin/core/form/validation/Validator.kt b/src/main/kotlin/core/form/validation/Validator.kt index fa20d36..792687b 100644 --- a/src/main/kotlin/core/form/validation/Validator.kt +++ b/src/main/kotlin/core/form/validation/Validator.kt @@ -1,6 +1,10 @@ package core.form.validation +/** + * Validator class for managing and applying validation rules to input strings. + */ class Validator { + private val rules = mutableListOf() fun addRule(rule: ValidationRule): Validator { diff --git a/src/main/kotlin/core/models/Result.kt b/src/main/kotlin/core/models/Result.kt index b229224..b615677 100644 --- a/src/main/kotlin/core/models/Result.kt +++ b/src/main/kotlin/core/models/Result.kt @@ -1,6 +1,21 @@ package core.models +/** + * A sealed class representing a result, which can be either a success or an error. + */ sealed class Result { + /** + * Represents a successful result containing data. + * + * @param T The type of the data. + * @property data The data of the successful result. + */ data class Success(val data: T) : Result() + + /** + * Represents an error result containing a message. + * + * @property message The error message. + */ data class Error(val message: String) : Result() } \ No newline at end of file diff --git a/src/main/kotlin/core/models/UiModel.kt b/src/main/kotlin/core/models/UiModel.kt index 74f5e6e..780f06b 100644 --- a/src/main/kotlin/core/models/UiModel.kt +++ b/src/main/kotlin/core/models/UiModel.kt @@ -3,12 +3,26 @@ package core.models import androidx.compose.ui.graphics.Color import java.util.* +/** + * Enum class representing different types of notifications with associated colors. + * + * @property color The color associated with the notification type. + */ enum class NotificationType(val color: Color) { ERROR(Color.Red), WARNING(Color(0xFFFFA000)), SUCCESS(Color.Green) } +/** + * Data class representing the display information for a credential. + * + * @property id The unique identifier of the credential. + * @property title The title of the credential. + * @property description The description of the credential. + * @property favorite Indicates if the credential is marked as favorite. + * @property isSelected Indicates if the credential is currently selected. + */ data class CredentialDisplay( val id: UUID, val title: String, @@ -17,18 +31,31 @@ data class CredentialDisplay( val isSelected: Boolean ) +/** + * Enum class representing different sorting options for credentials. + * + * @property value The string representation of the sorting option. + */ enum class CredentialSort(val value: String) { NAME("Name"), FAVORITE("Favorite"), CREATED("Created"), } +/** + * Enum class representing different default menu items. + * + * @property value The string representation of the menu item. + */ enum class DefaultMenuItem(val value: String) { PASSWORDS("Passwords"), CREDIT_CARD("Credit Cards"), NOTES("Notes") } +/** + * Enum class representing different types of forms. + */ enum class FormType { CREATION, MODIFIATION diff --git a/src/main/kotlin/core/models/UiState.kt b/src/main/kotlin/core/models/UiState.kt index b5d5abb..fbca192 100644 --- a/src/main/kotlin/core/models/UiState.kt +++ b/src/main/kotlin/core/models/UiState.kt @@ -1,8 +1,35 @@ package core.models +/** + * Represents the state of the UI. + * + * @param T The type of data associated with the state. + */ sealed class UiState { + + /** + * Represents the idle state of the UI. + */ data object Idle : UiState() + + /** + * Represents the loading state of the UI. + */ data object Loading : UiState() + + /** + * Represents the success state of the UI with associated data. + * + * @param T The type of data. + * @property data The data associated with the success state. + */ data class Success(val data: T) : UiState() + + /** + * Represents the error state of the UI with an error message. + * + * @property message The error message. + */ data class Error(val message: String) : UiState() + } \ No newline at end of file diff --git a/src/main/kotlin/core/security/JwtService.kt b/src/main/kotlin/core/security/JwtService.kt index 69ec477..c5627c7 100644 --- a/src/main/kotlin/core/security/JwtService.kt +++ b/src/main/kotlin/core/security/JwtService.kt @@ -7,6 +7,11 @@ import core.configs.JwtConfig import repository.user.User import java.util.* +/** + * Service for handling JWT operations such as token generation and validation. + * + * @param config The configuration object containing JWT settings. + */ class JwtService(config: Config) { companion object { @@ -16,23 +21,35 @@ class JwtService(config: Config) { private val jwtConfig: JwtConfig = config.jwt + /** + * Generates a JWT token for the given user. + * + * @param user The user for whom the token is generated. + * @return The generated JWT token as a String. + */ fun generateToken(user: User): String { return JWT.create() - .withIssuer(jwtConfig.issuer) - .withSubject(user.id.toString()) - .withClaim(USERNAME, user.userName) - .withClaim(EMAIL, user.email) - .withIssuedAt(Date(System.currentTimeMillis())) - .withExpiresAt(Date(System.currentTimeMillis() + jwtConfig.expiration)) - .sign(Algorithm.HMAC512(jwtConfig.secret)) + .withIssuer(jwtConfig.issuer) + .withSubject(user.id.toString()) + .withClaim(USERNAME, user.userName) + .withClaim(EMAIL, user.email) + .withIssuedAt(Date(System.currentTimeMillis())) + .withExpiresAt(Date(System.currentTimeMillis() + jwtConfig.expiration)) + .sign(Algorithm.HMAC512(jwtConfig.secret)) } + /** + * Validates the given JWT token and returns the user ID if valid. + * + * @param token The JWT token to validate. + * @return The user ID as a UUID if the token is valid, or null if invalid. + */ fun validateToken(token: String): UUID? { return runCatching { val algorithm = Algorithm.HMAC512(jwtConfig.secret) val verifier = JWT.require(algorithm) - .withIssuer(jwtConfig.issuer) - .build() + .withIssuer(jwtConfig.issuer) + .build() verifier.verify(token).subject?.let { UUID.fromString(it) } }.getOrNull() diff --git a/src/main/kotlin/core/security/MasterPasswordManager.kt b/src/main/kotlin/core/security/MasterPasswordManager.kt index 923936b..470fbb8 100644 --- a/src/main/kotlin/core/security/MasterPasswordManager.kt +++ b/src/main/kotlin/core/security/MasterPasswordManager.kt @@ -10,12 +10,34 @@ import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.SecretKeySpec +/** + * Object responsible for managing master passwords, including encryption and decryption. + */ object MasterPasswordManager { + /** + * Converts a plain string to a secure character array. + * + * @param value The plain string to convert. + * @return The secure character array. + */ fun convertToSecureString(value: String): CharArray = value.toCharArray() + /** + * Converts a secure character array back to a plain string. + * + * @param secureString The secure character array to convert. + * @return The plain string. + */ fun convertToUnsecureString(secureString: CharArray): String = String(secureString) + /** + * Encrypts a plain text string using the provided key. + * + * @param plainText The plain text string to encrypt. + * @param key The encryption key. + * @return The encrypted string, encoded in Base64. + */ fun encryptString(plainText: String, key: ByteArray): String { val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") val iv = ByteArray(cipher.blockSize).apply { SecureRandom().nextBytes(this) } @@ -24,6 +46,13 @@ object MasterPasswordManager { return Base64.getEncoder().encodeToString(iv + encrypted) } + /** + * Decrypts an encrypted string using the provided key. + * + * @param encryptedText The encrypted string, encoded in Base64. + * @param key The decryption key. + * @return The decrypted plain text string. + */ fun decryptString(encryptedText: String, key: ByteArray): String { val combined = Base64.getDecoder().decode(encryptedText) val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") @@ -33,6 +62,12 @@ object MasterPasswordManager { return String(cipher.doFinal(encrypted), StandardCharsets.UTF_8) } + /** + * Generates a key from a secret key string using PBKDF2 with HMAC SHA-256. + * + * @param secretKey The secret key string. + * @return The generated key as a byte array. + */ fun getKey(secretKey: String): ByteArray { val salt = secretKey.toByteArray(StandardCharsets.UTF_8) val spec: KeySpec = PBEKeySpec(secretKey.toCharArray(), salt, 10000, 256) diff --git a/src/main/kotlin/core/security/TokenManager.kt b/src/main/kotlin/core/security/TokenManager.kt index ac26d0b..8470cf0 100644 --- a/src/main/kotlin/core/security/TokenManager.kt +++ b/src/main/kotlin/core/security/TokenManager.kt @@ -5,8 +5,15 @@ import core.security.TokenManager.Constants.SYSTEM_PROPERTY import core.security.TokenManager.Constants.TOKEN_FILE_NAME import java.nio.file.Paths +/** + * TokenManager is responsible for managing the security token. + * It provides functionalities to save, load, and clear the token. + */ object TokenManager { + /** + * Constants used by the TokenManager. + */ object Constants { const val SYSTEM_PROPERTY = "user.home" const val APP_FOLDER = ".secvault" @@ -15,6 +22,11 @@ object TokenManager { private val tokenFile = Paths.get(System.getProperty(SYSTEM_PROPERTY), APP_FOLDER, TOKEN_FILE_NAME).toFile() + /** + * Saves the given token to the token file. + * + * @param token The token to be saved. + */ fun saveToken(token: String) { tokenFile.parentFile.exists().let { if (!it) { @@ -25,10 +37,20 @@ object TokenManager { tokenFile.writeText(token) } + /** + * Loads the token from the token file. + * + * @return The token if it exists, otherwise null. + */ fun loadToken(): String? { return if (tokenFile.exists()) tokenFile.readText() else null } + /** + * Clears the token by deleting the token file. + * + * @return True if the token file was deleted, otherwise false. + */ fun clearToken(): Boolean { if (tokenFile.exists()) { return tokenFile.delete() diff --git a/src/main/kotlin/core/security/TwoFactorAuthenticationService.kt b/src/main/kotlin/core/security/TwoFactorAuthenticationService.kt index 98399c1..a76bdd7 100644 --- a/src/main/kotlin/core/security/TwoFactorAuthenticationService.kt +++ b/src/main/kotlin/core/security/TwoFactorAuthenticationService.kt @@ -20,15 +20,32 @@ import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import javax.imageio.ImageIO +/** + * Service for handling two-factor authentication (2FA) operations. + * + * @param config The configuration object containing JWT settings. + */ class TwoFactorAuthenticationService(config: Config) { private val jwtConfig: JwtConfig = config.jwt + /** + * Generates a new TOTP secret key. + * + * @return A new TOTPSecret object. + */ fun generateSecretKey(): TOTPSecret { val randomSecretProvider = RandomSecretProvider() return randomSecretProvider.generateSecret() } + /** + * Verifies the provided TOTP code against the secret key. + * + * @param secretKey The base32 encoded secret key. + * @param code The TOTP code to verify. + * @return A Result object containing a boolean indicating success or failure. + */ fun verifySecret(secretKey: String, code: String): Result { val totpCode = TOTP(code) val totpSecret = TOTPSecret.fromBase32EncodedString(secretKey) @@ -39,6 +56,13 @@ class TwoFactorAuthenticationService(config: Config) { ?: Result.Error("Invalid TOTP secret") } + /** + * Generates a QR code image for the provided secret key and email. + * + * @param secretKey The base32 encoded secret key. + * @param email The email address associated with the TOTP account. + * @return A Result object containing a BitmapPainter for the QR code image. + */ fun generateQRCodeImage(secretKey: String, email: String): Result { val topSecret = TOTPSecret.fromBase32EncodedString(secretKey) val otpAuthUri = DefaultTOTPService().generateTOTPUrl( diff --git a/src/main/kotlin/di/AppModule.kt b/src/main/kotlin/di/AppModule.kt index c10ad6a..a494976 100644 --- a/src/main/kotlin/di/AppModule.kt +++ b/src/main/kotlin/di/AppModule.kt @@ -23,6 +23,9 @@ import repository.user.UserRepository import repository.user.impl.UserRepositoryImpl import viewmodel.* +/** + * Koin module for application-level dependencies. + */ val appModule = module { single { loadConfigs() } withOptions { @@ -51,6 +54,9 @@ val appModule = module { } +/** + * Koin module for repository dependencies. + */ val repositoryModule = module { single { UserRepositoryImpl(get(), get { parametersOf(UserRepository::class.java) }) } @@ -61,6 +67,9 @@ val repositoryModule = module { } +/** + * Koin module for ViewModel dependencies. + */ val viewModelModule = module { factory { LoginScreenModel(get()) } @@ -75,6 +84,12 @@ val viewModelModule = module { } +/** + * Remembers and provides a logger for the given class. + * + * @param clazz The class for which the logger is to be provided. + * @return The logger for the given class. + */ @Composable fun rememberLogger(clazz: Class<*>): Logger { return remember { @@ -82,5 +97,4 @@ fun rememberLogger(clazz: Class<*>): Logger { parametersOf(clazz) } } -} - +} \ No newline at end of file diff --git a/src/main/kotlin/repository/AuditableTable.kt b/src/main/kotlin/repository/AuditableTable.kt index bf629f5..5b352a8 100644 --- a/src/main/kotlin/repository/AuditableTable.kt +++ b/src/main/kotlin/repository/AuditableTable.kt @@ -6,10 +6,34 @@ import org.jetbrains.exposed.sql.javatime.datetime import repository.common.expression.CurrentDateTime import java.time.LocalDateTime +/** + * Abstract table class for auditable entities. + * + * @param name The name of the table. + */ abstract class AuditableTable(name: String) : UUIDTable(name) { + /** + * The date and time when the entity was created. + */ val creationDateTime: Column = datetime("creation_date_time").defaultExpression(CurrentDateTime) + + /** + * The user who created the entity. + */ val createdBy: Column = varchar("created_by", 255) + + /** + * The date and time when the entity was last updated. + */ val lastUpdateDateTime: Column = datetime("last_update_date_time").defaultExpression(CurrentDateTime) + + /** + * The user who last updated the entity. + */ val lastUpdatedBy: Column = varchar("last_updated_by", 255) + + /** + * The version of the entity. + */ val version: Column = integer("version").default(0) -} \ No newline at end of file +} diff --git a/src/main/kotlin/repository/common/errors/DatabaseError.kt b/src/main/kotlin/repository/common/errors/DatabaseError.kt index 1aa74f5..c56e701 100644 --- a/src/main/kotlin/repository/common/errors/DatabaseError.kt +++ b/src/main/kotlin/repository/common/errors/DatabaseError.kt @@ -1,6 +1,7 @@ package repository.common.errors sealed class DatabaseError { + abstract fun extractMessage(): String data object UniqueConstraintViolation : DatabaseError() { diff --git a/src/main/kotlin/ui/App.kt b/src/main/kotlin/ui/App.kt index 09ad93e..b9dc66f 100644 --- a/src/main/kotlin/ui/App.kt +++ b/src/main/kotlin/ui/App.kt @@ -10,6 +10,10 @@ import org.koin.java.KoinJavaComponent.getKoin import ui.theme.PasswordColors import java.lang.invoke.MethodHandles +/** + * Main composable function for the application. + * It sets up the application state, logger, and the theme. + */ @Composable fun App() { val appState = getKoin().get()