MainWindow.fxml
code from.PasswordManager.java
.Refer to the guide Setting up and getting started.
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of the main components and how they interact with each other.
Main components of the architecture
Main
(consisting of classes Main
and MainApp
) is in charge of the app launch and shut down.
The bulk of the app's work is done by the following four components:
UI
: The UI of the App.Logic
: The command executor.Model
: Holds the data of the App in memory.Storage
: Reads data from, and writes data to, the hard disk.Commons
represents a collection of classes used by multiple other components.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command :rm -i 1
.
Each of the four main components (also shown in the diagram above),
interface
with the same name as the Component.{Component Name}Manager
class (which follows the corresponding API interface
mentioned in the previous point.For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality using the LogicManager.java
class which follows the Logic
interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
The API of this component is specified in Ui.java
The UI
consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class which captures the commonalities between classes that represent parts of the visible GUI. Other than the MainWindow
, we have created a PasswordPromptDialog
that is used to prompt the user for a password when they try to access the app.
The UI
component uses the JavaFx UI framework. The layout of these UI
parts are defined in matching .fxml
files that are in the src/main/resources/view
folder. For example, the layout of the MainWindow
is specified in MainWindow.fxml
The UI
component,
Logic
component.Model
data so that the UI
can be updated with the modified data.Logic
component, because the UI
relies on the Logic
to execute commands.Model
component, as it displays Person
object residing in the Model
.API : Logic.java
Here's a (partial) class diagram of the Logic
component:
The sequence diagram below illustrates the interactions within the Logic
component, taking execute(":rm -i 1")
API call as an example.
Note: The lifeline for DeleteCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
How the Logic
component works:
Logic
is called upon to execute a command, it is passed to an AddressBookParser
object which in turn creates a parser that matches the command (e.g., DeleteCommandParser
) and uses it to parse the command.Command
object (more precisely, an object of one of its subclasses e.g., DeleteCommand
) which is executed by the LogicManager
.Model
when it is executed (e.g. to remove a person).Model
) to achieve the result.CommandResult
object which is returned back from Logic
.Here are the other classes in Logic
(omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
AddressBookParser
class creates an XYZCommandParser
(XYZ
is a placeholder for the specific command name e.g., AddCommandParser
) which uses the other classes shown above to parse the user command and create a XYZCommand
object (e.g., AddCommand
) which the AddressBookParser
returns back as a Command
object.XYZCommandParser
classes (e.g., AddCommandParser
, DeleteCommandParser
, ...) inherit from the Parser
interface so that they can be treated similarly where possible e.g, during testing.API : Model.java
The Model
component,
Person
objects (which are contained in a UniquePersonList
object).Person
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiable ObservableList<Person>
that can be 'observed' e.g. the UI
can be bound to this list so that the UI
automatically updates when the data in the list change.UserPref
object that represents the user’s preferences. This is exposed to the outside as a ReadOnlyUserPref
objects.Model
represents data entities of the domain, they should make sense on their own without depending on other components)Note: An alternative (arguably, a more OOP) model is given below. It has a Tag
list in the AddressBook
, which Person
references. This allows AddressBook
to only require one Tag
object per unique tag, instead of each Person
needing their own Tag
objects.
API : Storage.java
The Storage
component
AddressBookStorage
and UserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed).Model
component (because the Storage
component's job is to save/retrieve objects that belong to the Model
)Classes used by multiple components are in the seedu.address.commons
package.
This section describes some noteworthy details on how certain features are implemented.
The undo/redo mechanism is facilitated by VersionedAddressBook
. It extends AddressBook
with an undo/redo history, stored internally as an addressBookStateList
and currentStatePointer
. Additionally, it implements the following operations:
VersionedAddressBook#commit()
— Saves the current address book state in its history.VersionedAddressBook#undo()
— Restores the previous address book state from its history.VersionedAddressBook#redo()
— Restores a previously undone address book state from its history.These operations are exposed in the Model
interface as Model#commitAddressBook()
, Model#undoAddressBook()
and Model#redoAddressBook()
respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The VersionedAddressBook
will be initialized with the initial address book state, and the currentStatePointer
pointing to that single address book state.
Step 2. The user executes :rm -i 5
command to remove the 5th person in the address book. The :rm
command calls Model#commitAddressBook()
, causing the modified state of the address book after the :rm -i 5
command executes to be saved in the addressBookStateList
, and the currentStatePointer
is shifted to the newly inserted address book state.
Step 3. The user executes :add -n David
to add a new person. The :add
command also calls Model#commitAddressBook()
, causing another modified address book state to be saved into the addressBookStateList
.
Note: If a command fails its execution, it will not call Model#commitAddressBook()
, so the address book state will not be saved into the addressBookStateList
.
Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the :undo
command. The :undo
command will call Model#undoAddressBook()
, which will shift the currentStatePointer
once to the left, pointing it to the previous address book state, and restores the address book to that state.
Note: If the currentStatePointer
is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The :undo
command uses Model#canUndoAddressBook()
to check if this is the case. If so, it will return an error to the user rather
than attempting to perform the undo.
The following sequence diagram shows how an undo operation goes through the Logic
component:
Note: The lifeline for UndoCommand
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Similarly, how an undo operation goes through the Model
component is shown below:
The :redo
command does the opposite — it calls Model#redoAddressBook()
, which shifts the currentStatePointer
once to the right, pointing to the previously undone state, and restores the address book to that state.
Note: If the currentStatePointer
is at index addressBookStateList.size() - 1
, pointing to the latest address book state, then there are no undone AddressBook states to restore. The :redo
command uses Model#canRedoAddressBook()
to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
Step 5. The user then decides to execute the command :list
. Commands that do not modify the address book, such as :list
, will usually not call Model#commitAddressBook()
, Model#undoAddressBook()
or Model#redoAddressBook()
. Thus, the addressBookStateList
remains unchanged.
Step 6. The user executes :clear
, which calls Model#commitAddressBook()
. Since the currentStatePointer
is not pointing at the end of the addressBookStateList
, all address book states after the currentStatePointer
will be purged. Reason: It no longer makes sense to redo the :add -n David
command. This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
Aspect: How undo & redo executes:
Alternative 1 (current choice): Saves the entire address book.
Alternative 2: Individual command knows how to undo/redo by itself.
:remove
, just save the person being removed).The remove feature allows removal of a person from the address book. The user can remove a person by specifying the index of the person to remove. The user can also remove multiple persons by specifying multiple indexes of the persons to remove.
How the remove feature works:
Format: :remove -i INDEX1, INDEX2...
The DeleteCommand
class has a method DeleteCommand#execute(Model model)
that calls the ModelManager.
The ModelManager
class has a method ModelManager#deletePerson(Person target)
that calls the AddressBook
class.
The AddressBook
class has a method AddressBook#removePerson(Person key)
that removes a person from the UniquePersonList field persons
in the AddressBook
class.
The following class diagram shows the relationships between the classes involved in the remove feature:
The following sequence diagram shows how a remove operation goes through the Logic
component:
Similarly, how a remove operation goes through the Model
component is shown below:
The following activity diagram summarizes what happens when a user executes a new command:
Design considerations:
Aspect: How remove executes:
Alternative 1 (current choice): Removes the person by index.
Alternative 2: Removes the person by name.
The add feature allows a person to be added to the address book. It accepts the parameters name, phone, location, email, remark and tag. The name parameter is compulsory, while the rest are optional. Multiple tags are accepted for one person.
The add feature follows the remove feature in that AddCommand
calls ModelManager
, which calls the AddressBook
class, which adds a person to the UniquePersonList
class. Therefore, the class and activity diagram will be omitted
for conciseness.
The following sequence diagram shows how an add operation goes through the Logic
component:
The parsing process is described in detail in this sequence diagram:
Similar to the remove feature, how an add operation goes through the Model
component is shown below:
The ExportCommand
class is responsible for exporting address book data to a user-specified location in JSON format. It provides flexibility in its usage by allowing a destination to be selected via a file chooser or by setting a predetermined destination file, which is particularly useful for testing purposes. The data to be exported is encrypted, and the ExportCommand
handles decryption, export location selection, and file I/O operations. The following outlines its components and workflow.
The ExportCommand
class facilitates this export functionality and manages file I/O operations in a structured, asynchronous workflow.
Constructor Variants:
ExportCommand()
: The default constructor for regular use, opening a file chooser dialog to select the export destination.ExportCommand(File destinationFile, File sourceFile, String keyPath)
: An overloaded constructor that allows specifying a destination file and encryption key path directly, which is particularly useful for testing.Attributes:
destinationFile
: The file chosen or set as the target for the export.sourceFile
: A temporary file that holds the JSON data to be exported.keyPath
: The path to the decryption key required for decrypting the address book data.Given below is an example usage scenario and how the export process behaves at each step.
Step 1. The user initiates an export by executing :export
. The ExportCommand
will attempt to decrypt the data
before exporting it.
Step 2. The execute(Model model)
method reads encrypted data from the sourceFile
, decrypting it with
EncryptionManager.decrypt()
using the provided keyPath
. The decrypted data is written to a temporary file vbook.json
.
Step 3. If destinationFile
is not set, ExportCommand
invokes chooseExportLocation(Stage stage)
, which displays
a file chooser dialog for the user to select an export location. If the user cancels this dialog, the export process
is aborted with an error message.
Step 4. The performExport(File sourceFile, File destinationFile)
method copies the decrypted data to the specified destinationFile
, using Files.copy()
with StandardCopyOption.REPLACE_EXISTING
to overwrite any existing file. The temporary file is then deleted.
Note: The performExport
method is asynchronous, leveraging CompletableFuture
to manage successful completion or error handling, ensuring smooth performance without blocking the main application thread.
The following sequence diagram explains how the export operation works:
Design Considerations:
Aspect: Export Execution and Destination Selection
Alternative 1 (current choice): Use a file chooser dialog to allow the user to select the export location.
Alternative 2: Set a default export location without user input.
The encryption mechanism is managed by the EncryptionManager
class. This component is responsible for securely encrypting and decrypting sensitive data using the AES (Advanced Encryption Standard) algorithm. The EncryptionManager
performs encryption and decryption with a secret key, which is securely loaded and stored using Java's Key Store API. The implementation details are as follows:
encrypt(String data, String keyPath)
:
vbook.jks
if not provided).decrypt(byte[] data, String keyPath)
:
vbook.jks
if not provided).generateKey(String keyPath)
:
vbook-encryption
.
getKey(String keyPath)
:
generateKey()
to create one.EncryptionManager
is used in the ExportCommand
to decrypt data before exporting it and in JsonAddressBookStorage
to encrypt data before writing it to a file.// Encryption
String jsonData = JsonUtil.toJsonString(new JsonSerializableAddressBook(addressBook));
byte[] encryptedData = EncryptionManager.encrypt(jsonData, this.keyPath);
// Decryption
jsonData = EncryptionManager.decrypt(encryptedData, this.keyPath);
Step 1. The user initially adds a new contact in the address book. The EncryptionManager
uses the AES algorithm and the secret key to encrypt the information before saving it.
Step 2. The encrypted data is stored securely. When needed, the user can request to decrypt the information.
Step 3. The EncryptionManager
decrypts the data using the same AES algorithm and the secret key, ensuring that the information is securely handled at all times.
Note: If an error occurs during encryption or decryption (e.g., if the secret key is invalid or corrupted), the EncryptionManager
will handle the error gracefully and return an appropriate error message.
The following sequence diagram shows how the encryption process works:
Note: The sequence diagram simplifies the encryption and decryption processes to focus on the main interactions between components.
Risk of Local KeyStore Exposure:
Alternative Storage Locations:
JAVA_HOME/lib/security/cacerts
directory is an option, but this depends on the user’s configuration and permissions. Users might not have their JAVA_HOME
path set correctly, which can lead to access issues.Security Through Obscurity:
The password management mechanism is handled by the PasswordManager
class. This component is responsible for securely hashing and verifying user passwords using the PBKDF2 (Password-Based Key Derivation Function 2) algorithm with HMAC-SHA1. The PasswordManager
ensures that passwords are safely stored in a local text file, employing a salting strategy to enhance security. The implementation details are as follows:
readPassword(String path)
:
password.txt
).null
if the file does not exist.
savePassword(String password, String path)
:
password.txt
).isPasswordCorrect(String inputPassword, String path)
:
true
if they match or false
otherwise.
hashPassword(String password, byte[] salt)
:
generateSalt()
:
SecureRandom
for use in password hashing.PasswordManager
is invoked during application startup to check for an existing password file.
// Saving a new password on initial startup
if (PasswordManager.readPassword(null) == null) {
String newPassword = scanner.nextLine();
PasswordManager.savePassword(newPassword, null);
}
// Verifying the password on subsequent starts
String inputPassword = scanner.nextLine();
if (PasswordManager.isPasswordCorrect(inputPassword, null)) {
// Handle correct password
} else {
// Handle wrong password
}
Step 1. On the initial startup of the application, the PasswordManager
checks for the existence of the password.txt
file. If the file is not found, the user is prompted to enter a new password.
Step 2. The entered password is hashed and saved securely, along with a generated salt.
Step 3. On subsequent startups, the user is prompted to input their original password. The PasswordManager
verifies the input against the stored hash and salt, granting access if the password matches.
Note: If an error occurs during password hashing or verification (e.g., if the stored format is incorrect), the PasswordManager
will handle the error gracefully and provide an appropriate error message.
The following activity diagram illustrates how the password management process operates:
Note: The activity diagram simplifies the password management processes to highlight the flow for user authentication.
password.txt
file, they could potentially compromise user accounts. This poses a fundamental risk of local file storage, as the security of the password relies on the protection of the local file system.Target user profile:
Value proposition: manage contacts faster than a typical mouse/GUI driven app
Listed below are user stories for features that have already been implemented in the current version of the application.
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * | first-time user | add contacts to my contact book | store my contacts |
* * * | user | add contacts to my contact book using only partial details | store contacts that I may not have full information about |
* * * | user | see all my contacts | see and manage my contacts |
* * * | user | remove contacts | remove contacts I do not need anymore |
* * | user | edit contact details | correct errors I made when adding a contact |
* * | first-time user | see sample contacts | explore the app's features without adding real data |
* * | first-time user | clear sample data and start fresh | input my real contacts securely |
* * | first-time user | view a tutorial on the app | learn how to use the app quickly |
* * | first-time user | quickly access a CLI command cheat sheet | learn essential commands without slowing down |
* * | new user | secure my contact data with a password | feel confident that my client information is protected |
* * | new user | choose to encrypt the contact data that is stored | ensure my client information cannot be accessed from the storage location |
* * | new and inexperienced user | undo actions like deletions CTRL+Z | recover data quickly if I make a mistake |
* * | new and inexperienced user | redo an action that was undone with undo CTRL+SHIFT+Z | reapply an action if I realise I need it after undoing it |
* * | new and inexperienced user | be prompted with why an invalid command is invalid | receive immediate and specific feedback if I type an invalid command |
* * | returning user | search contacts using partial details (name, email) | find relevant contacts faster |
* * | user whose contacts span multiple projects | tag contacts with a project or organisation name | organise my contacts better |
* * | user | filter contacts by project or organisation | quickly locate clients related to specific tasks |
* * | experienced user | use keyboard shortcuts to bring up the CLI | execute commands faster |
* * | experienced user | use keyboard shortcuts to manage contacts | manage my contacts faster |
* | user | multi-select contacts for deletion | manage my list more efficiently |
* | power user | export my contact list to JSON format | use it in other tools or projects |
Listed below are user stories that represent features that we have not implemented yet, but plan to in the future.
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * | new user | import contacts from a CSV or another format (e.g. Apple's .vcf) | use more data formats to quickly populate my address book |
* * | new user | open up a settings menu | configure keyboard shortcuts |
* | returning user | customise the app's theme | make my user experience more personalised as I use the app more |
* | frequent user | navigate command history with arrow keys | quickly fill the search field and modify and execute previous commands |
* | programmer | configure my shortcuts to be similar to my IDE shortcuts | switch between my IDE and VBook more effectively |
* | frequent user | pin important contacts | have them appear at the top of my list for easy access |
* | long time user | archive old contacts | clean up my address book without having to remove contacts |
(For all use cases below, the System is the VBook
and the Actor is the user, unless specified otherwise)
Use case: UC01 - Add a person
MSS
User requests to add a specific person to VBook.
VBook adds the person.
Use case ends.
Extensions
1a. One or more of the inputted parameters, or the command, is invalid.
1a1. VBook shows an error message.
Use case ends.
1b. The name of the requested person is the same as an existing person in the address book.
1b1. VBook shows an error message.
Use case ends.
Use case: UC02 - Edit a person's details
MSS
User requests to list persons.
VBook shows a list of persons.
User requests to edit a specific person's details in the list.
VBook edits the person's details.
Use case ends.
Extensions
2a. The list is empty.
Use case ends.
3a. The given index is invalid.
3a1. VBook shows an error message.
Use case resumes at step 2.
3b. One or more of the inputted parameters, or the command, is invalid.
3b1. VBook shows an error message.
Use case resumes at step 2.
3c. The name of the requested person is the same as an existing person in the address book.
3c1. VBook shows an error message.
Use case resumes at step 2.
Use case: UC03 - Remove a person
MSS
User requests to list persons.
VBook shows a list of persons.
User requests to remove a specific person in the list.
VBook removes the person.
Use case ends.
Extensions
2a. The list is empty.
Use case ends.
3a. The given index is invalid.
3a1. VBook shows an error message.
Use case resumes at step 2.
Use case: UC04 - Find persons matching criteria
MSS
User requests to find persons in the list that match provided search criteria.
VBook displays a list of persons that match the criteria.
Use case ends.
Extensions
1a. No persons match the search criteria.
1a1. VBook displays a message indicating that no persons were found.
Use case ends.
1b. The command entered is invalid.
1b1. VBook shows an error message.
Use case ends.
Use case: UC05 - Export data
MSS
User requests to export data.
VBook opens the file explorer window.
User chooses the destination and name of the exported data.
VBook exports the data to the destination folder in JSON format.
Use case ends.
Extensions
2a. User closes the file explorer window without selecting a destination.
Use case ends.
3a. The name of the exported data clashes with an existing name in the same file destination.
3a1. The file explorer displays an error message.
Use case returns to step 2.
3b. The user enters an invalid name.
3b1. The file explorer displays an error message.
Use case returns to step 2.
Use case: UC06 - Enter password
Preconditions: User has already set a password previously.
MSS
User starts the app.
VBook displays a window prompting the user to enter a password.
User enters the correct password.
VBook closes the password prompt window and opens the main app window.
Use case ends.
Extensions
2a1. VBook displays an error message.
Use case returns to step 2.
Use case: UC07 - Undo command
Preconditions: At least one command that changes the address book has been entered.
MSS
User requests to undo the last command.
VBook undoes the last change to the data.
Use case ends.
Extensions
1a. There is no previously done add/edit/remove command found.
1a1. VBook displays a message that there are no more commands to undo.
Use case ends.
1b. User has undone more commands than the maximum amount allowed.
1b1. VBook displays a message that there are no more commands to undo.
Use case ends.
Use case: UC08 - Redo command
MSS
User requests to redo the last undone command.
VBook redoes the last undone command.
Use case ends.
Extensions
1a. There is no previously undone command.
1a1. VBook displays a message that there are no more commands to redo.
Use case ends.
1b. User has redone more commands than the maximum amount allowed.
1b1. VBook displays a message that there are no more commands to redo.
Use case ends.
17
or above installed.EncryptionManagerTest.java
, temporary files are created before and deleted after each test for the Encryption and Export tests. Without this cleanup, subsequent runs of ./gradlew test
will fail.java.nio.file.FileSystemException
exception when attempting to delete the files due to the difference in how Windows processes manage files. (StackOverflow issue)*test.key
files and test
folder created, both in the root directory of VBook.jar.Team Size: 4
UI
space. We plan to add a maximum length for the tags to be 50 characters, as the longest English word is 45 characters.0000000
. Hence, we plan to split the location field into three distinct fields: postal code, street name, and block number. Input validation will ensure specificity, restrict ambiguity, and allow only valid special characters relevant to location data.integer
is entered into our index field, the error message displays: Invalid command format!
instead of The person index provided is invalid
. This is likely due to an integer overflow error throwing a different exception than expected. We plan to fix this by adding a check for overflow and returning the appropriate error message afterwards.vbook.json
data manually: Doing so would result in an exception if the program attempts to decrypt the data (for example, on :export
command). However, this exception is not handled gracefully on the Command box and instead, only shown in the console. We plan to fix this by adding a Command Exception on decryption error to inform the user that the data has been tampered with.Given below are instructions to test the app manually.
Note: These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.
Initial launch:
Instructions:
.jar
file and copy it into an empty folder.cd
) to the folder where you saved the VBook.jar
file, and use the following command to run the application:cd path/to/vbook
java -jar VBook.jar
Saving window preferences:
Basic help command:
:help
Adding a new person with all details:
:add -n John Doe -p 91234567 -e johndoe@example.com -l 123, Clementi Rd, #01-01, S123456 -t Friend -r Met at a wedding
Adding a person with only mandatory fields:
:add -n Jane Smith
:list
Editing a person’s details with valid inputs:
:edit 1 -n Jonathan Doe -p 92345678 -e jonathan.doe@example.com -l 456, Orchard Rd, #02-02, S654321 -t Family
Editing only a specific field:
:edit 1 -p 91234567
Successful export to a chosen location:
:export
contacts.json
is created in the selected location, containing the contact data. A confirmation message should display the successful export.Export with existing file name in destination:
contacts.json
is present in the selected export location.:export
contacts.json
for the export.Undo a recent add command:
:add -n John Doe -p 91234567 -e johndoe@example.com
followed by :undo
Undo a recent edit command:
:edit 1 -p 92345678
followed by :undo
Redo a recent undo of an add command:
:add -n John Doe -p 91234567 -e johndoe@example.com
, then :undo
, followed by :redo
:redo
command, with a confirmation message indicating the redo was successful.Redo a recent undo of an edit command:
:edit 1 -p 92345678
, then :undo
, followed by :redo
:redo
command, with a confirmation message.Find by full name:
:find -n John Doe
Find by partial name:
:find -n John
Removing the first person in the list:
:list
command. Multiple persons are in the list.:remove -i 1
Attempting to remove a person with an invalid index (zero):
:remove -i 0
Basic clear command to remove all contacts:
:clear
Clear command when the contact list is already empty:
:clear
Basic quit command to exit the application:
:quit
Quit command immediately after startup:
:quit
Dealing with missing data files
data/vbook.json
file in the home folder of the .jar
file.data/vbook.json
file, close the address book, and then open it again.Initial fresh startup - Cancelling prompt:
password.txt
file in the root directory of the project.password.txt
file. Press "Cancel" or close the password prompt using the cross button.password.txt
file is created.Initial fresh startup - Creating a new password
password.txt
file in the root directory of the project.password.txt
file is created in the root directory containing the salted hash of the entered password.Initial fresh startup
Prerequisites: Ensure there is no data/vbook.json
file or vbook.jks
file in the root directory.
Test case: Start the program and invoke a GUI command (e.g., :add
).
Action: Add a new entry using the GUI command.
Expected outcome:
vbook.jks
file is created in the root directory, containing the encryption key.data/vbook.json
file is created. Attempting to open the file from the file explorer shows unreadable binary data.Subsequent loads with existing vbook.jks
and encrypted vbook.json
Prerequisites: Ensure a valid vbook.jks
file exists in the root directory and an encrypted data/vbook.json
file is present.
Test case: Start the program.
Action: Launch the program.
Expected outcome:
data/vbook.json
file using the vbook.jks
encryption key.data/vbook.json
file, ensuring the file remains binary and unreadable outside the program.This project was challenging to implement, especially with our ambition of making it as keyboard-friendly as possible. Implementing keyboard shortcuts, as well as keyboard-friendly UI was not easy.
While we didn't expand the fields much from AB3, certain features like the Export feature took us a lot of time to debug, especially given known bugs with the Windows system (see failing tests on Windows when run more than once). The password prompt with proper salted hashing and encryption features with standard encryption algorithms also add an additional layer of security.