This year, Codeminer42 held its first Hackathon, an internal programming event that energized the miners’ weekend and brought a lot of knowledge, learning, networking, and of course, prizes! But what exactly is a Hackathon, and what happened during this event? Well, follow along as we share all the details about everything that went down!
About the Event
A hackathon is basically a programming competition where participants are given a specific challenge that involves various programming concepts, ranging from basic to complex. Participants have a limited amount of time to develop a solution to the proposed problem, which typically focuses on creating a feasible application that could be replicated in the real world.
The challenge is designed to ensure that all developers can participate, regardless of their level of expertise, as networking between teams and exchanging knowledge are core goals of the event, fostering collaboration and idea-sharing.
Hackathon 2024 was created to encourage miners to break out of their routines and explore new technologies! To achieve this, we organized an interactive event to engage our developers remotely. It took place on Saturday (Nov. 23) and lasted 8 hours. Participants were paired into teams of two and only received access to the challenge on the day of the event.
Although the event was remote, all participants received a T-shirt delivered to their homes and competed for amazing prizes. The winning duo received a 13" MacBook Air M2, while the second-placed team took home an Echo Show 8. All participants needed to join the event was a computer with internet access and plenty of determination to win.
Since miners already have experience working with their daily tech stack, the Hackathon restricted the use of those familiar tools, pushing participants out of their comfort zones to explore new programming languages. The available options were Python, Go, Kotlin or Rust for the backend, and Flutter or Svelte for the frontend.
The Challenge
The challenge miners faced was to develop a functional password manager that included essential features to ensure the integrity and security of stored passwords while also providing a great user experience with a friendly, practical, and intuitive interface. The application also needed to offer modern security features such as 2FA (Two-Factor Authentication) and end-to-end encryption. Check out the challenge requirements and evaluation criteria below:
Requirements
Password Storage
- Users must be able to save their passwords.
- Ensuring the security of this data is essential, so a strong and intelligent authentication system is required.
Retrieve Stored Passwords
- The system must allow users to retrieve previously saved passwords.
Password Generation
- The system should include a password generator that allows users to configure criteria, such as length and the use of special characters.
Copy to Clipboard
- Users should be able to copy stored or generated passwords to the clipboard with a single click.
Two-Factor Authentication (2FA)
- The application must have an option for users to enable 2FA.
- Authentication can be done using temporary codes (TOTP).
- The application does not need to account for recovery codes, only the generation of temporary codes.
Evaluation Criteria
Meeting the Requirements:
- Were the mandatory features implemented?
- How well were the requirements met?
- Use of the chosen technology’s differentiators
- Does the solution make good use of the advantages and capabilities of the chosen stack?
- Were any innovations or optimizations specific to the chosen technology applied?
- Creativity, Originality, and UX
- Does the project present something creative or innovative that sets it apart?
- Is the interface intuitive, pleasant, and does it offer a good UX?
Demo
- Was the application well "sold" during the demo?
- Were design and architecture decisions clear?
- Code Quality
- How easy is it to understand how the application works?
- How complex would it be to modify one of the features?
More detailed information about the challenge and the rules can be found here.
Alright, now that you know a bit more about the event and the challenge, let’s talk a little about how the projects were executed and the participant’s experience throughout the event. The idea here is not to necessarily delve into the code itself, but rather to discuss how the team planned the project, how development progressed during the day, the challenges faced, and the reasoning behind the decisions made throughout this experience, during the Hackathon. Let’s go!
Execution
To write this section and share more about the developed application, we invited the winning team to share the technical details of their project and talk about their experience participating in the Hackathon! Let’s get to know a little more about how this journey went from start to finish!
Planning
According to the winning team, the preparation started long before receiving the challenge. Already knowing the languages they would use, which were chosen by the team: Python for the backend and Svelte for the frontend, the team prepared by studying the documentation of the technologies, and each member developed a simple project to put their knowledge into practice. Additionally, they prepared a Docker project that would serve as the foundation for the challenge, ensuring they wouldn’t waste time setting up the project.
On the day of the challenge, before they started coding, they spent at least 1 hour—valuable time, but essential to organize their ideas and establish how they would design the application, including all the flows, endpoints, models, and database structure.
During this preparation time, the team members analyzed similar tools already available in the market, such as 1Password and BitWarden, and discussed how these tools implement their business rules to ensure the application itself is ‘Zero Knowledge‘, meaning that not even the application’s maintainer can know the passwords saved in their database. They also studied how the passwords were encoded and encrypted in different layers of the application to ensure information security and prevent password leaks—even among the developers working on the project.
Flows
The team established 4 flows that they considered essential for the application to function as they expected.
Account creation
In the account creation step, the user must provide an email
and a password
. As soon as this information reaches the account creation endpoint, the user’s password, named master_password
, is encrypted and saved in the database as a hash.
At this moment, the application takes care of generating a secret_key
, which is a uuid, that will be used by the user to access the application and retrieve their saved passwords.
Just like the master_password
, the secret_key
is also encrypted and saved in the database. However, the secret_key
(uuid) is returned to the user at the time of account creation, along with a message asking the user to save the key for future access on new devices.
It is important to highlight that from this moment on, both passwords are encrypted, meaning it is not possible to recover the original password from the hash in an ‘easy’ way.
In other words, even though the account password and the user’s secret key are stored in the database, the application can only submit a value and compare if the generated hash is the same as the one stored in the database, however, it is not capable of retrieving the original password, and this is exactly how the login process will be performed in the application.
Login
To log in, the user must provide the three pieces of information related to their account: the login
, the master_password
, and the secret_key
.
Considering that the secret_key
returned to the user at the time of creation is a long and complex string, the application, after displaying the information for the user to save it, stores the key in the browser’s local storage and populates the form during the user’s first login.
This way, during their first access, the user only needs to provide the login
and the master_password
to access the application, as the secret_key
will be filled in automatically and the input on the form will be hidden.
It is important to remember that this behavior of saving the secret_key
in local storage occurs every time the user is authenticated. This way, once the user is logged in, they can securely register their own passwords. Let’s see how this works.
Password Creation
To register a new password, still on the frontend, the application encrypts the user’s password based on the secret_key
(uuid) and sends the encrypted password to the backend, which in turn re-encrypts the password based on the user’s master_password
(the hash saved on the database) and saves it in the database.
Initially, the flow was designed to handle only the password
field as it is the core of the application. However, during development, the team decided to add other relevant fields for the password, such as a title, a URL, a field for 2FA, and the login associated with the password.
Retrieving a Password
The process of retrieving a password is basically the reverse path, in which the application decrypts the password saved in the database based on the user’s master_password
(hash) and sends the password (still encrypted by the secret_key
(uuid) on frontend) to the frontend.
In other words, what arrives at the frontend is an encrypted password, which is decrypted based on the user’s secret_key
, accessible through the local storage.
By managing passwords this way, the application ensures that the data saved in the database is secure since the secret_key
is not exposed in the database and each user has their own key to recover their passwords.
At this point, you may wonder how the secret_key
is saved in local storage during a second login, since it is a HASH in the database. Well, the answer is simpler than it seems. In a second login, the user must provide the secret_key
to authenticate. That is, the application encrypts the secret_key
and validates it alongside the other provided credentials. If the authentication is successful, the value entered by the user is saved in local storage.
Routes
The next step the team developed was to establish the routes that would be used in the application, both the frontend routes and the API routes that would be used for communication between the client and the server.
Due to the limited time, the team focused on solving the proposed problem, and as a result, routes for editing, displaying, and deleting both passwords and accounts were cut from the application’s scope.
Models
With the flows and routes defined, the next step was to define the models that would be used in the application. Aiming to keep the application as simple as possible, the team established only two models: the User
, which would represent the user’s account, and the Password
, which represents the passwords saved by the user.
Views
Finally, before starting to code, the team created a simple design for how the main screen of the application would look.
The team decided to keep all the application’s behavior on a single screen, meaning there was no separation in viewing the details of a specific password. All the user’s data would be accessible on one screen.
The buttons present in the table would be responsible for executing various actions, such as revealing the password, hiding the password, or generating a 2FA token.
One additional feature the team considered was that only one password could be displayed at a time, in an attempt to prevent all passwords from being exposed at once, making it easier for someone to take a screenshot and copy everything.
Development
With all the project details defined, the team began implementing the code to bring the plan to life. As mentioned earlier, the technologies used for the backend were Python, and for the frontend, Svelte. For the database, the team chose Postgres, and to facilitate development, they decided to use the SQLAlchemy ORM. Additionally, to save time, the team had already prepared a Docker project to host both the backend and the database of the application.
Code Struct
Although they were somewhat familiar with the languages they were working with, the lack of a framework to help structure the files raised a red flag for the developers. They chose to use the MVC pattern (Model, View, Controller), which they were accustomed to working with on a daily basis, to keep the files organized during the development journey.
Since the challenge was remote, the team also opted to use the LiveShare feature available in VSCode. This way, they could track the code’s progress in real-time and share doubts and solutions more quickly.
Additionally, by using this feature, they wouldn’t need to upload the updated code to the repository and download the latest version of the branch to keep the code up to date, as both were working on the same code.
Libraries
The use of some libraries was also essential for the fast development of the application, such as Flask and Werkzeug for the API and request management, Flask-JWT for authentication, Flask-Bcrypt and cryptography for encryption and data security, SQLAlchemy for the ORM, and SvelteKit for the frontend. On the frontend, the team chose to use TailwindCSS to style the application.
Unforeseen Events
During development, the team faced some unforeseen issues, starting with the database.
When creating the first version of the database table, the team realized that some naming conventions needed to be adjusted to keep the application attributes more consistently named. Since the database was running on only one of the machines, the team had to recreate the database several times before arriving at the final version. This process was a bit time-consuming, as it required stopping all services, deleting the database, recreating it, and restarting the services.
In the end, the database schema was defined as follows:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
master_password_hash TEXT NOT NULL,
secret_key_hash TEXT NOT NULL,
);
CREATE TABLE passwords (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
title VARCHAR(255) NOT NULL,
login VARCHAR(255),
encrypted_password BYTEA,
url VARCHAR(255),
encrypted_otp_secret BYTEA,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
);
Another issue the team faced was related to authentication. When implementing the library in the project, it was confusing until they realized that the ‘subject’ of the JWT had to necessarily be a string. However, these were minor bugs that were resolved.
During these changes, the team also decided to encrypt the OTP secret, which is the secret key used to generate the 2FA token and is stored in the database. This detail had not been initially planned, but as development progressed, it became essential since it ensures that even the authentication tokens cannot be exposed.
On the Frontend side, the team spent considerable time developing the application’s routes. Initially, the team had implemented a SPA (Single Page Application), but their development plan required more routes. After some research, the team found SvelteKit, which allowed them to implement routes as desired, requiring them to recreate the project from scratch, even though not much had been implemented yet.
It is also important to note that, even with a plan to follow, some changes during the implementation were inevitable, whether due to time constraints, new discoveries while exploring the features of the chosen technologies, or bugs that arose during development. This type of situation is common in real projects, where the initial planning may not be enough to cover all possible scenarios. Therefore, the team needs to be prepared to handle unforeseen events and make adjustments throughout the development process.
Racing Against Time
With the main functionalities implemented, the team had to rush to finish implementing the remaining features. With just over 30 minutes left to deliver the final version of the code, the team studied the evaluated points, and even though they knew they wouldn’t be able to deliver all the evaluation criteria, they decided to focus on delivering a functional application.
At this point, they considered it more important to deliver a secure application, with encrypted passwords and working authentication, and had to let go of some features they considered less important, such as the ‘Copy to Clipboard’ function and the custom password generator with parameters.
During these last minutes of the challenge, the team was able to implement a random password generator, but since it was executed client-side, it ended up generating the same password every time. Even though it would affect the evaluation, they didn’t have time to fix it.
In addition, the team also noticed some inconsistencies in the project based on the decisions that had been made, though they would not necessarily become an issue, as the application did not implement features that would bring these inconsistencies to light. This was about changing the user’s account password.
Since all the passwords saved by the user are encrypted using the hash of the master_password
, changing the account password would result in all the user’s saved passwords being lost because the application did not have a feature to re-encrypt the saved passwords in the database.
There was also an edge case that could be considered a problem or not, which occurred after creating the user account. If the user wanted to create an account and then log into the application with different credentials, it would create an inconsistency.
The current behavior of the application was to save the secret_key
in local storage, a detail not transparent enough to the user, potentially confusing if they tried to access the application with another set of credentials after creating an account.
This way, what was intended as an improvement to the user experience ended up being a problem, as the application was not prepared to handle this scenario.
All those issues were only identified in the final moments after the application was almost fully implemented.
In the final minutes, the team performed a final test to ensure that the entire application was working. The test was done manually by going through all the flows: user creation, login, password creation, password recovery, 2FA token generation, logout, and login on a new device. With the successful tests, the team submitted the code and finished the challenge.
Demo
With the code submitted and the challenge concluded for all the teams, the miners prepared for the application presentation round. The team prepared a script for the presentation, where each member was responsible for presenting a part of the application.
Each team had approximately 10 minutes to demonstrate their project and explain the technical decisions made during the implementation.
Next, let’s see some screenshots of the application developed by the winning team.
Experience
With the challenge completed, the demonstrations made, and the code submitted, the judges evaluated all the projects and determined the winning teams.
Hackathon 2024 was undoubtedly a day full of action that brought a lot of fun and learning for the participants. All participants had the opportunity to learn new technologies, explore new languages and frameworks, and most importantly, work as a team on a real-world challenge.
Teams
We would like to extend a special thanks to all the miners who dedicated themselves to participating and making this event a success!
The participating teams were:
- Kim Emmanuel e Fernando Costa
- Carlos Pohlod e Moises Carvalho (🥉 3rd Place)
- Daniel.Brasil e Tony Araújo
- Marcelo Alexandre e Igor Damasceno (🥈 2nd Place)
- Fabio Leandro (Sammy) e Joao Vogler
- Antonio Barbosa (Edy) e Ian Lourenco
- Douglas Marques e Paulo Souza
- Samuel Flores e William Spada
- Alexandre Saura e Lucas Geron (🏆 1st Place)
- Gabriel Quaresma e João Mangilli
We want to work with you. Check out our "What We Do" section!