DEV Community

Paul Kim
Paul Kim

Posted on

Hacktoberfest Week 4

For my fourth and final Hacktoberfest PR, I worked on a project called YABin by Yureien.

Project Repository: https://github.com/Yureien/YABin
Issue: https://github.com/Yureien/YABin/issues/19
My Pull Request: https://github.com/Yureien/YABin/pull/29

YABin

YABin is "Yet Another Pastebin" that seeks to add features not found in other similar applications, such as:

  • Modern and minimal UI (This site's design was inspired by bin).
  • Optional end-to-end encryption (we're using AES-256-GCM) with optional password protection (using PBKDF2).
  • Syntax highlighting (using Prism) that supports 297 languages.
  • API support to create and get pastes from command line.
  • View raw pastes. Normally, encrypted pastebins do not have this. With this site, you can either get the Base64-encoded encrypted paste, or decrypt it on the server side (even with the password) and get the raw paste.
  • Keyboard shortcuts!
  • And of course, being fully open-source and easily self-hostable.
  • NEW Ability to edit pastes after creation, and a dashboard for viewing all your pastes.
  • Comes with a CLI tool to create and read pastes from the command line!
  • It can even be run on edge servers and in serverless environments!

It is a Svelte web application, can leverage any type of database (supported by Prisma). Users can create "pastes" to share with other users, set their time of expiration, they can be encrypted, be destroyed after being read, and users can create accounts.

You can check it out here: https://bin.sohamsen.me/

Issue

The issue asks to implement a page for the user to reset their account password. Breaking this down, the flow would look as such:

  1. The user clicks on a "Forgot password?" link in the login page.
  2. Present user with a form to enter their username or email.
  3. The user clicks submit.
  4. The server finds the email address associated to user in the database (if just the username was provided).
  5. Generate a password reset token, overwriting any existing ones.
  6. Send an email to the user with a link to reset their password i.e. "Click this link to reset your password".
  7. The link takes the user to a page to enter their new password.
  8. Check that the password reset token exists and has not yet expired.
  9. If the provided password is valid, update it.

This entails:

  • The addition of two new routes
  • A password reset token database entity

Setup

The first step was to get the project running. Following the project's README, I had to set up some kind of database; Postgres was recommended. I have not worked with the Prisma ORM before but I quite enjoyed using it. After setting up a Postgres database and user, all I had to do was migrate the schema defined in Prisma into my database and it would set it up accordingly. Prisma is flexible enough to work with many different types of databases, so it didn't have to necessarily be Postgres. It was interesting to look through through the Prisma migrations files in the project, they were incremental SQL statements that collectively formed the instructions on how to completely rebuild the database from scratch (source).

Svelte

Another new thing about this project for me was using Svelte. Svelte is a framework I have only heard recently as a "down to earth" experience that felt like one was coding in the good old days where there was less abstraction in web development. The first thing that struck out at me was the file naming convention - a view was named +page.svelte and server code was placed in +page.server.ts files. I hadn't seen any frameworks that use + signs in their pathnames - I would've thought this would cause issues in certain environments. The front-end syntax resembled Handlebars.js, a similar HTML templating library I learned a while ago.

The "Forgot Password" Page

Here's the "Forgot Password" page I wrote - src/routes/(auth)/forgot-password/+page.svelte and it's accompanying server code - src/routes/(auth)/forgot-password/+page.server.ts. If the MAIL_ENABLED environment variable is set to false, the page will simply redirect the user to the root route (since email is disabled), otherwise the page loads normally. I set up two Svelte functions a load function which is called upon page load and actions which defines i.e. a callback function when a form is submitted. The page asks for a username or email of the account to reset the password for. It then checks if that user exists, then sends an email to the one registered for the user. It pulls the user data with Prisma's findFirst method which was very straightforward to use. It was nice not having to worry about database-specific methods and just use a generic one that'll work for all of them.

Nodemailer

I set up a sendRequestEmail function (source) that calls a generatePasswordResetToken function (source) that upserts a password reset token into this database for this user. It then constructs a URL and sends an email using the nodemailer library which I've used before. The SMTP service I had used before had gone defunct unfortunately and the free SMTP server options had features I could not overcome (i.e. requiring a legitimate business identity and address). I ended up using a spare Gmail address configured with an app password. I used 10minutemail to generate some email addresses and everything worked great. The tokenized URL was sent to users.

Processing a password change

This URL would point to a new reset-password route (source). Upon page load, it will check to see if the URL contains query parameters for the user ID and the reset token that was passed via the email. If one is missing or invalid, the user is blocked from the route. If both are present and verified, the page loads. The user will see two fields - a field to enter their new password and another to confirm. Upon page submission, the server will validate the password (i.e. are of a sufficient length), generate a password hash, delete the reset token, and then redirect the user to the login page (source).

I originally intended for the user to stay on the page with a confirmation message i.e. "Your password has been successfully updated." However, when I delete the reset token from the database, it noticed that it forces a page reload, which is problematic since the token will no longer exist and the route will be invalid. I attempted to suppress this page reload but was unable to find a way to do so. I am still not sure how to overcome this behaviour.

Takeaways

This project was fun to work on. The new technologies (Svelte, Prisma) were fun and straightforward to work with and the project was organized well. I had to refactor some existing code to make it work (source) - I tried to make smart, surgical changes that impacted the existing code as little as possible. I learned that it can be pretty easy to learn and work with new technologies on the fly - they all tend to follow similar features and workflows that can be worked out even without prior knowledge.

Thanks for reading!

Top comments (0)