Of all the Computer Science classes I took at UCLA, one stood far above the rest when it came to the practical skills I acquired during the quarter. CS35L, or the Software
Construction Lab, was a class that taught me a myriad of technologies such as scripting with BASH and Python. The final project for this class was my favorite part, and This
required me to build a full stack application with a server and a client. My final product was UCLAOutlet.
This project was a combination of many robust features that are extremely common in many modern e-commerce websites. The core technology stack was Node.js for backend, React with HTTP for the frontend, and MongoDB Atlas for information storage.
Sass was used for additonal frontend styling, and Google SMTP service was used for features such as email receipts and OTP authentication.
The core features of the app are:
The first function we had to build was the ability for our website to store and display products. The structure to do this involved sending an HTTP GET request to our backend, which then grabs a list of all product information from
our cloud MongoDB database (discussed more in the Persistent User Data section.) This would return a list of JSON items which represented a product menu.
Now that we have this data in JSON form, the front end can parse it to display images and products in a list. The product thumbnails are all stored in a folder called uploads, and each image's name is the same as its ID. This means the frontend
can construct a filepath since it knows the product ID's. The uploads folder is publically shared as /uploads, so the path /uploads/$id will grab the image required. You can see this in practice on the home page.
Any one of these products can be clicked on to navigate to its product page, where much more information is visible about it. You are given details, a SKU, size options, and the ability to place an order and add to wishlist. These last two functionalities
will be expanded upon much more in the Persistent User Data and Wishlist functionality sections.
Product Search
As seen in the landing page for the website, we also implemented a search functionality for sorting through
all products meaningfully. The search bar field takes a string value, then passes a request to the server with
that value. The page then displays all SKUs and/or product names that match the requested value.
SECURITY NOTE
To prevent end users from being able to pass any request to the database directly, I opted to not change
the MongoDB request based on user input. Instead, upon receiving a search request, the server first gets all
products from the database. It then uses the info passed in the search request to parse this list. This approach
means the end user can never change the way the server interacts with the product database and thus protects
it from attacks such as NoSQL injections.
Besides just the search bar, we allowed users to sort their products by categories. There are four categories present
in the header of the website, with the clothing category also having subcategories for
kids, men's, and women's.
Clicking on any of these will send a similar request to the product search, but since these are not
user-modifiable, we allow direct access to the database. All product json items have a category
attribute, and all products in the clothing category also have a subcategory attribute. When the user
accesses the category pages, the database searches for all products with a matching attribute and displays them.
Advanced User Authentication
My favorite feature of this project was the implementation of advanced user authentication using
JWTs (JSON Web Tokens). The user authentication process requires multiple steps, and this begins when
you click login from any page on the website.
This page will verify your login by checking your email and password against a database of users.
Users are added to this database via the account creation link found below the login button, which will
be described more in-depth in the Persistent User Data section. If you entire
a valid email-password combination, you will receive a one-time password which you must verify to login.
When an OTP is created, it has three attributes: value, time to live, and userID. The value
is an encryped hash of the generated value that the user receives in their email. The time to live
is the time at which the code expires, which is always exactly ten minutes after creation. UserID
is the identifier used to check if the OTP code belongs to the user who is submitting it. If UserID
and value are both correct when the server receives a request, it will return a JSON Web Token
that is then embedded into the browser header.
SECURITY NOTE
As seen in the Authorization header above, all sensitive info stored in the MongoDB database is encrypted. This includes passwords,
OTPs, and JWTs. Info is encrypted using the NodeJS library bcrypt and salted ten times. This means that even if access to the
cloud database was gained, user data would not be exposed. JWT's also expire after two hours for extra security, requiring the user to log
in again.
Now that we have JWT authentication, the client can make requests to protected routes on the server. These routes will return
a 401 error code if the user attempts to access them when not logged in. These routes usually involve user-specific actions, such as
ordering items or placing them in/viewing your wishlist.
Persistent User Data
As mentioned in the previous section, there are several way an end user can interact with the client to permanently change data in the server.
There are several ways to do this, all of which require a user account. From the user login page seen previously, you can click a link that takes
you to the account creation page.
When you fill out your desired user information here and click register, the client will send a POST request to the server. This first
checks whether the email address already has an existing associated account. If it doesn't, then a user is created with the provided username and password.
With a user account created, there are two other ways a user can create persistent data.