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 installin the terminal to install all necessary dependencies. - Run
npx playwright install --with-deps firefoxto 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 TodoDBto connect to the database server.\dtto see the list of tables. There should beusers,todos, andsubtodos`.
- Start the server
npm run serverin a debug terminal so youâre able to use breakpoints in your code. - Open the browser and navigate to
http://localhost:3000to 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, andoptionsobject that can be used to set the cookieâspath,expires,maxAge, etc.Session.ts: Represents a session in the system. It has asessionIdand adataobject that can be used to store session data.SessionManager.ts: Manages sessions in the system. It has asessionsobject 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 acookiesarray and asessionobject. Thecookiesarray is populated with cookies from the request headers, and thesessionobject 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 theCookieheader from the request and returns an array ofCookieobjects.findCookie(): Finds a cookie in thecookiesarray by name.getSession(): Finds the session ID in theCookieheader and returns the session object from theSessionManagerif one exists. If one does not exist, a new session is created.
Response.ts: Has been modified to include acookiesarray. Thecookiesarray 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-Cookieheader is updated with the new cookie (and all cookies that were added before it, if any), and the new cookie is added to thecookiesarray.
đŠ Letâs Go
Part 1: User Model (20%)
Goal: Implement the User model class which will represent a user in the system.
-
Create
- The
Userclass should have acreatemethod 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
Usermodel should have aloginmethod 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
Userobject. 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
Usermodel inuser.model.test.ts. - Implement the logic required to pass the tests for the
createandloginmethods. - 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
UserControllershould have acreateUsermethod 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
AuthControllershould have agetRegistrationFormmethod 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
AuthControllershould have aloginmethod that will log the user in when making a POST request to/loginby calling theloginmethod on theUsermodel. -
If the login is successful, the server should set a session parameter for the user and redirect them to the
/todospage, 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
AuthControllershould have alogoutmethod 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
AuthControllershould have aloginFormmethod that will render a login form view when making a GET request to/login. - The form should have fields for
emailandpassword. - 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/userIdparameter, respectively, for thetodos.model.test.tsto 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
userIdparameter. 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
createTodomethod in theTodoControllerto set theuserIdparameter of the Todo to theuserIdparameter of the session. Also, ensure that a Todo can only be updated/deleted by the user who created it. To achieve this, notice thetodosdatabase table now has auserIdcolumn to signify that each todo belongs to a user, and theTodoPropsinterface 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 thesrcattribute of animgtag 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
truein theis_admincolumn of theuserstable. You can set this value totruein the database manually usingpsqlto 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_admincolumn 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:psqlnpm 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.onlyin 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.modelto run only the tests for theUsermodel.npm run test -- todo.modelto run only the tests for theTodomodel.npm run test -- user.httpto run only the tests for theUsercontroller.npm run test -- todo.httpto run only the tests for theTodocontroller.npm run test:uito 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 offooto the console. This is a Handlebars helper that will log the value offooto 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.