4 - Auth
- đŻ Worth: 7%
- đ Due: April 21, 2024 @ 23:59
- đ« Penalty: Late submissions lose 10% per day to a maximum of 3 days. Nothing is accepted after 3 days and a grade of 0% will be given.
đŻ Objectives
- Implement a registration system using emails and passwords for user account creation.
- Utilize sessions and cookies to manage user login and logout flows.
- Construct user interface elements (login and registration forms) that interact with authentication logic.
- Protect specific app routes by requiring user authentication.
- Enhance the app by adding user profile management features or an admin role with elevated permissions.
đš Setup
- Fork (do not download as a zip) the starter repository from GitHub. Make sure you use the link from Moodle to fork the proper repository.
- Make sure Docker Desktop is open.
- Start the development container in VS Code by using the
Dev Containers: Open Folder in Container...
command from the Command Palette (Ctrl or â + SHIFT + P) and select the cloned directory. - Run
npm install
in the terminal to install all necessary dependencies. - Run
npx playwright install --with-deps firefox
to install the necessary browser for the automated tests. Even if you normally use something other than Firefox, Iâve found the tests to be more reliable with Firefox so please donât change this. - Verify that the database was set up properly by running:
psql -d TodoDB
to connect to the database server.\dt
to see the list of tables. There should beusers,
todos, and
subtodos`.
- Start the server
npm run server
in a debug terminal so youâre able to use breakpoints in your code. - Open the browser and navigate to
http://localhost:3000
to see the application running.
đ Context
To complete this assignment, you should be familiar with the following concepts and theories:
- Cookies: How to set and read cookies in a web application.
- Sessions: How to manage sessions in a web application.
- Testing with Playwright: How to run automated tests using Playwright and debug using the UI mode.
In this assignment, you will be implementing user authentication in the Todo application. This will involve creating a User
model class, a UserController
class, and an AuthController
class. The User
model will represent a user in the system, the UserController
will handle user-related HTTP requests, and the AuthController
will handle authentication-related HTTP requests such as logging in and logging out of the system.
Auth Classes
In the 4.X exercises, we learned about cookies/sessions and how to manage them using relatively simple mechanisms. In this assignment, like in previous assignments, the mechanisms are more sophisticated and abstracted away from you. You will be using the following classes to manage cookies and sessions:
Cookie.ts
: Represents a cookie in the system. It has aname
,value
, andoptions
object that can be used to set the cookieâspath
,expires
,maxAge
, etc.Session.ts
: Represents a session in the system. It has asessionId
and adata
object that can be used to store session data.SessionManager.ts
: Manages sessions in the system. It has asessions
object that maps session IDs to session objects. It has methods to create a new session, get a session by ID, and delete expired sessions.Request.ts
: Has been modified to include acookies
array and asession
object. Thecookies
array is populated with cookies from the request headers, and thesession
object is populated with any session data from previous requests.Request.ts constructor(req: IncomingMessage) {this.req = req;this.cookies = this.getCookies();this.session = this.getSession();}getCookies()
: Parses theCookie
header from the request and returns an array ofCookie
objects.findCookie()
: Finds a cookie in thecookies
array by name.getSession()
: Finds the session ID in theCookie
header and returns the session object from theSessionManager
if one exists. If one does not exist, a new session is created.
Response.ts
: Has been modified to include acookies
array. Thecookies
array is used to store cookies that will be sent in the response headers.Response.ts constructor (public req: Request,public res: ServerResponse,public cookies: Cookie[] = [],) {}public setCookie(cookie: Cookie) { ... }setCookie()
: Sets a cookie in the response. Every time this method is called, theSet-Cookie
header is updated with the new cookie (and all cookies that were added before it, if any), and the new cookie is added to thecookies
array.
đŠ Letâs Go
Part 1: User Model (20%)
Goal: Implement the User
model class which will represent a user in the system.
-
Create
- The
User
class should have acreate
method that will insert a new user into the database. - The database table for users has already been created for you. You can find the schema in
init.sql
. - If the email already exists, throw a
DuplicateEmailError
.
- The
-
Login
- The
User
model should have alogin
method that will check if the email and password match a row in the users database table. - If the email and password match, the method should return a new
User
object. Similar to reading a single Todo. - If the email and password do not match, we throw an
InvalidCredentialsError
.
- The
-
Testing
- Find the tests for the
User
model inuser.model.test.ts
. - Implement the logic required to pass the tests for the
create
andlogin
methods. - The commented out tests are for the extra feature in part 5. You can ignore them for now.
- Find the tests for the
Part 2: Registration (20%)
Goal: Implement the UserController
and AuthController
classes which will handle user-related HTTP requests.
Controller
- Just like creating a Todo, the
UserController
should have acreateUser
method that will handle the POST request to/users
. - Upon form submission, this controller method should validate that no fields are blank/missing, that the passwords match, and that there isnât already a user with the given email. If there are any errors, redirect back to the registration form with an error message.
View
- The
AuthController
should have agetRegistrationForm
method that will render a registration form view when making a GET request to/register
. - The form should have fields for
email
,password
, andconfirmPassword
. - The form should have a submit button that will send a POST request to
/users
. - Have an area to display any error messages that are passed in the query params.
Part 3: Login/Logout (20%)
Goal: Implement the AuthController
class which will handle authentication-related HTTP requests.
Controller
-
The
AuthController
should have alogin
method that will log the user in when making a POST request to/login
by calling thelogin
method on theUser
model. -
If the login is successful, the server should set a session parameter for the user and redirect them to the
/todos
page, along with a session cookie to remember that the user is logged in. -
If the login is unsuccessful, redirect back to the login form with an error message using the same query param technique outlined in the tip above.
-
The
AuthController
should have alogout
method that will log the user out of the system when making a GET request to/logout
. -
This should clear the session and redirect the user to the homepage.
View
- The
AuthController
should have aloginForm
method that will render a login form view when making a GET request to/login
. - The form should have fields for
email
andpassword
. - The form should have a submit button that will send a POST request to
/login
. - Upon submission, the server should validate that no fields are blank/missing, and that the email and password match a user in the database. If there are any errors, redirect back to the login form with an error message.
- The form should also have a checkbox for âRemember Meâ. If this checkbox is checked when the form is submitted, the server should set a cookie to remember the userâs email. When the user logs out and visits the login page again, the email field should be pre-filled with the value of this cookie.
Part 4: Todo Authentication (20%)
Goal: Ensure that only authenticated users can access the Todo-related routes.
-
Youâll need to update the Todos table/model to accept a
user_id
/userId
parameter, respectively, for thetodos.model.test.ts
to pass. -
In each Todo controller method, you should check if the user is authenticated before proceeding. If the user is not authenticated, redirect them to the login page. Now that we have sessions, we can check if the user is authenticated by checking if the session has a
userId
parameter. If it does, the user is authenticated. If it doesnât, they are not authenticated. -
Since a Todo can only be created by an authenticated user, you should also update the
createTodo
method in theTodoController
to set theuserId
parameter of the Todo to theuserId
parameter of the session. Also, ensure that a Todo can only be updated/deleted by the user who created it. To achieve this, notice thetodos
database table now has auserId
column to signify that each todo belongs to a user, and theTodoProps
interface has been updated to reflect this.
Part 5: Extra Feature (20%)
Choose ONE of the following features to implement. You will find commented tests for each of these features at the bottom of browser.spec.ts
. You can choose to implement more than one if youâd like, but you will only receive marks for your best one.
In each test file, there are commented tests for the extra features. You can uncomment these tests to run them accordingly.
Feature 1: User Profile
- Add a profile page where the user can update their email and/or password. This is similar to the Todo edit view you made for the last assignment.
- Allow the user to choose dark/light mode on the profile page using a checkbox. This should be remembered using a cookie so that the next time a user logs in, the website will be in the mode they chose.
- Allow the user to upload a profile picture that will appear in the website header. Weâre not going to be storing images in the database. Instead, you can store the URL to an image on the internet in the database. For example, if thereâs an image at
https://example.com/image.png
, you can store that URL in the database and then use it in thesrc
attribute of animg
tag in your Handlebars template.
Feature 2: Admin Priveleges
- Add a list page that allows an admin user to view all users in the system. Only admin users should be able to access this route. This is similar to the Todo list view you made for the last assignment.
- An admin is a user who has a value of
true
in theis_admin
column of theusers
table. You can set this value totrue
in the database manually usingpsql
to make a user an admin. - From this list, the admin should be able to make other users admins using a checkbox toggle. This should ultimately update the
is_admin
column in the database. - From this list, the admin should be able to delete users from the system.
đĄ Tests & Tips
- The tests will give you an idea about what Iâll be looking for, but I havenât written tests for every single case. You should be testing your code manually as well. Here are some non-exhaustive examples of things you should be testing:
- 400 Bad Request: If a user tries to create an entity with a blank field, for example, you should return a 400 Bad Request status code and an error message.
- 404 Not Found: If a user tries to perform an action on an entity that doesnât exist, you should return a 404 Not Found status code.
- 401 Unauthorized: If an unauthenticated user tries to perform an action on an entity that they need to be authenticated for, you should return a 401 Unauthorized status code.
- 403 Forbidden: If an authenticated user tries to perform an action on an entity that they didnât create, you should return a 401 Unauthorized status code.
- Ensure the database is being affected how you think it should be by pausing on a breakpoint in your code and running a select statement on the database using
psql
. In fact, I advise having three terminals open:psql
npm run server
(debug terminal)npm run test:ui
(debug terminal)
- Follow the steps outlined above and run the tests according to which feature youâre currently working on. Remeber to not run all the tests, but instead, run the tests for the feature youâre currently working on. For non-Playwright tests, stick a
test.only
in the test youâre working on to run only that test. For Playwright tests, click the arrow next to the test youâre working on to run only that test.npm run test -- user.model
to run only the tests for theUser
model.npm run test -- todo.model
to run only the tests for theTodo
model.npm run test -- user.http
to run only the tests for theUser
controller.npm run test -- todo.http
to run only the tests for theTodo
controller.npm run test:ui
to run the Playwright tests.
- Does the test time out before youâre done debugging? Increase the timeout time inside
playwright.config.ts
. - Does the debugger take you through weird code that you didnât write? Make better use of the âContinueâ button. If there are 2 breakpoints you want to hit, for example one in the test and on in the controller, you can set them both and then click âContinueâ to hit the second one instead of trying to step over every single line of code.
- Need to know whatâs going inside Handlebars? Use
{{ log foo }}
to log the value offoo
to the console. This is a Handlebars helper that will log the value offoo
to the console when the template is rendered.
đ„ Submission
To submit your assignment, follow these steps:
-
Commit your changes to the local repository, for example:
Terminal window git add .git commit -m "Completed Authentication implementation." -
Push your commits to the remote repository:
Terminal window git push -
Submit your assignment on Gradescope.
- Go to gradescope.ca (not .com!), log in, and click the link for this assignment.
- Select the correct repository and branch from the dropdown menus.
- Click Upload.