From fd503c200afbf4369de439febe890ba9d83fb0bf Mon Sep 17 00:00:00 2001 From: GloryU Date: Sat, 30 Oct 2021 02:50:02 -0400 Subject: [PATCH 1/7] Completed tickets #8 and #1. All tests pass :) --- src/models/AuthCode.test.ts | 10 ++---- src/models/AuthCode.ts | 26 +++------------- src/routes/VerifyCodeRoute.test.ts | 9 +----- src/routes/VerifyCodeRoute.ts | 50 +++++++++++++----------------- src/utils/AuthUtils.ts | 18 ++++++----- 5 files changed, 41 insertions(+), 72 deletions(-) diff --git a/src/models/AuthCode.test.ts b/src/models/AuthCode.test.ts index 7cd879e..64113ee 100644 --- a/src/models/AuthCode.test.ts +++ b/src/models/AuthCode.test.ts @@ -1,14 +1,8 @@ import { TEST_USER } from '../../jest.setup'; import AuthCode from './AuthCode'; -/** - * TODO: (1.05) - * - Remove the ".skip" from the following function. - * - Go to your terminal and run the following command: - * npm run test AuthCode - * - Delete this comment. - */ -describe.skip('Model: AuthCode', () => { + +describe('Model: AuthCode', () => { test('Should auto generate an OTP for value.', async () => { // Even though we don't specify a value for the OTP code, it should // automatically generate. diff --git a/src/models/AuthCode.ts b/src/models/AuthCode.ts index 3c566a9..1974ed1 100644 --- a/src/models/AuthCode.ts +++ b/src/models/AuthCode.ts @@ -4,11 +4,7 @@ import AuthUtils from '../utils/AuthUtils'; import { Model } from '../utils/constants'; import { BaseModel } from '../utils/types'; -/** - * (1.01) TODO: - * - Read this interface. - * - Delete this comment once you've done so. - */ + interface IAuthCode extends BaseModel { /** * Phone number in which the OTP code is associated with. @@ -31,28 +27,16 @@ interface IAuthCode extends BaseModel { export type AuthCodeDocument = Document<{}, {}, IAuthCode> & IAuthCode; const authCodeSchema: Schema = new Schema( - /** - * (1.03) TODO: - * - Create the schema for the AuthCodes that we'll save in the database. - * - Delete this comment and the example field. - * - Add comment(s) to explain your work. - */ + { // Here's an example of how to add a field to the schema. - exampleField: { required: true, type: String, unique: false } + phoneNumber: { required: true, type: String, unique: true }, + value: {default: AuthUtils.generateOTP, required: true, type: Number} }, { timestamps: true } ); -/** - * (1.04) TODO: - * - Add a line of code here that will elete every document in the "AuthCode" - * collection after 5 minutes (60 seconds * 5). - * - To be very clear, the only way you're going to figure this out is by - * Googling around for the answer. The solution is one line. - * - Once you find something, add the code to this document and include a link - * to the code you found in a comment. - * */ +authCodeSchema.index({ createdAt: 1}, { expireAfterSeconds: 60*5}); const AuthCode: mongoose.Model = mongoose.model(Model.AUTH_CODE, authCodeSchema); diff --git a/src/routes/VerifyCodeRoute.test.ts b/src/routes/VerifyCodeRoute.test.ts index ed8a12e..f6f3669 100644 --- a/src/routes/VerifyCodeRoute.test.ts +++ b/src/routes/VerifyCodeRoute.test.ts @@ -2,14 +2,7 @@ import { TEST_USER } from '../../jest.setup'; import AuthCode, { AuthCodeDocument } from '../models/AuthCode'; import TestUtils from '../utils/TestUtils'; -/** - * TODO: (8.08) - * - Remove the ".skip" from the following function. - * - Go to your terminal and run the following command: - * npm run test VerifyCode - * - Delete this comment. - */ -describe.skip('POST /verify', () => { +describe('POST /verify', () => { test('If code is not a number, return a 400.', async () => { await TestUtils.agent.post('/verify').send({ code: '123456' }).expect(400); }); diff --git a/src/routes/VerifyCodeRoute.ts b/src/routes/VerifyCodeRoute.ts index 64f258a..4c0f0af 100644 --- a/src/routes/VerifyCodeRoute.ts +++ b/src/routes/VerifyCodeRoute.ts @@ -18,15 +18,8 @@ type VerifyCodeRequest = ApplicationRequest<{}, VerifyCodeBody>; export default class VerifyCodeRoute extends BaseRoute { constructor() { super({ - /** - * TODO: (8.01) - * - Replace null with the correct route type from the RouteMethod enum - * in the constants.ts file. - * - Fill in the path string with the appropriate path to this endpoint. - * - Delete this comment. - */ - method: null, - path: '/' + method: RouteMethod.POST, + path: '/verify' }); } @@ -36,14 +29,13 @@ export default class VerifyCodeRoute extends BaseRoute { * - body.phoneNumber */ middleware() { - /** - * TODO: (8.02) - * - Add another validation in the returned array to verify that the code - * from the body of the request is a 6-digit number. - * - We've left in the code that will verify that the phone number is a - * valid US phone number and checks if its in our database. - */ return [ + body('code') + .isInt() + .withMessage('Invalid input. Expected an integer') + .isLength({max: 6, min: 6}) + .withMessage('The code must be a 6-digit number'), + body('phoneNumber') .isMobilePhone('en-US') .withMessage('The phone number you inputted was not valid.') @@ -70,27 +62,29 @@ export default class VerifyCodeRoute extends BaseRoute { * @throws {RouteError} - If the code does not match what is in DB. */ async content(req: VerifyCodeRequest, res: Response): Promise { - // TODO: (8.03) Get the code and phone number from the request body. + const {code, phoneNumber} = req.body; - // TODO: (8.04) Find the real code associated with the number from our - // database. + const authCode: AuthCodeDocument = await AuthCode.findOne({phoneNumber}); - // TODO: (8.05) Compare the code we received in the request body with the - // one from our database. If they differ, throw a RouteError and them know - // what's wrong. + if (authCode.value !== code) { + throw new RouteError({ + message: 'Invalid code!', + statusCode: 401 + }); + } - // TODO: (8.06) First try to get the user by fetching them from DB. But, if - // they don't already exist, then just create a new user. - const user: UserDocument = null; + let user: UserDocument = await User.findOne({phoneNumber}); + + if (!user) { + user = await User.create({phoneNumber}); + } // Renew's the user's tokens and attaches these new tokens on the // Express response object to send back to the client. const { accessToken, refreshToken } = await user.renewToken(); MiddlewareUtils.attachTokens(res, { accessToken, refreshToken }); - // TODO: (8.07) In the case that the user properly authenticates with the - // code, we no longer want to store the authentication code - // (it's short-lived), so we delete it! + await AuthCode.deleteOne({phoneNumber}); return true; } diff --git a/src/utils/AuthUtils.ts b/src/utils/AuthUtils.ts index f42d3b2..e5b5050 100644 --- a/src/utils/AuthUtils.ts +++ b/src/utils/AuthUtils.ts @@ -8,13 +8,17 @@ import { APP } from './constants'; * all 0's. */ const generateOTP = (): number => { - /** - * (1.02) TODO: - * - Implement this function. - * - Make sure tall the tests pass. - * - Delete this comment. - */ - return 123456; + // Generate a number between 0 and 1. + const randomFraction: number = Math.random(); + + // Multiply that number by 900000 + const sixDigit: number = randomFraction * 900000; + + // Get rid of decimal + const sixDigitNumber: number = Math.floor(sixDigit); + + // return 6-digit number + return sixDigitNumber + 100000; }; /** From af36a11a61e2c6e479830164ff1a3fe7339b4de5 Mon Sep 17 00:00:00 2001 From: GloryU Date: Sat, 30 Oct 2021 03:06:58 -0400 Subject: [PATCH 2/7] Cosmetic changes to tickets #8 and #1 --- src/models/AuthCode.test.ts | 1 - src/models/AuthCode.ts | 8 +++----- src/routes/VerifyCodeRoute.ts | 14 +++++++------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/models/AuthCode.test.ts b/src/models/AuthCode.test.ts index 64113ee..9718874 100644 --- a/src/models/AuthCode.test.ts +++ b/src/models/AuthCode.test.ts @@ -1,7 +1,6 @@ import { TEST_USER } from '../../jest.setup'; import AuthCode from './AuthCode'; - describe('Model: AuthCode', () => { test('Should auto generate an OTP for value.', async () => { // Even though we don't specify a value for the OTP code, it should diff --git a/src/models/AuthCode.ts b/src/models/AuthCode.ts index 1974ed1..d46f7fa 100644 --- a/src/models/AuthCode.ts +++ b/src/models/AuthCode.ts @@ -4,7 +4,6 @@ import AuthUtils from '../utils/AuthUtils'; import { Model } from '../utils/constants'; import { BaseModel } from '../utils/types'; - interface IAuthCode extends BaseModel { /** * Phone number in which the OTP code is associated with. @@ -28,15 +27,14 @@ export type AuthCodeDocument = Document<{}, {}, IAuthCode> & IAuthCode; const authCodeSchema: Schema = new Schema( - { - // Here's an example of how to add a field to the schema. + { // Here's an example of how to add a field to the schema. phoneNumber: { required: true, type: String, unique: true }, - value: {default: AuthUtils.generateOTP, required: true, type: Number} + value: { default: AuthUtils.generateOTP, required: true, type: Number } }, { timestamps: true } ); -authCodeSchema.index({ createdAt: 1}, { expireAfterSeconds: 60*5}); +authCodeSchema.index({ createdAt: 1 }, { expireAfterSeconds: 60*5 }); const AuthCode: mongoose.Model = mongoose.model(Model.AUTH_CODE, authCodeSchema); diff --git a/src/routes/VerifyCodeRoute.ts b/src/routes/VerifyCodeRoute.ts index 4c0f0af..f9f2f10 100644 --- a/src/routes/VerifyCodeRoute.ts +++ b/src/routes/VerifyCodeRoute.ts @@ -33,7 +33,7 @@ export default class VerifyCodeRoute extends BaseRoute { body('code') .isInt() .withMessage('Invalid input. Expected an integer') - .isLength({max: 6, min: 6}) + .isLength({ max: 6, min: 6 }) .withMessage('The code must be a 6-digit number'), body('phoneNumber') @@ -62,9 +62,9 @@ export default class VerifyCodeRoute extends BaseRoute { * @throws {RouteError} - If the code does not match what is in DB. */ async content(req: VerifyCodeRequest, res: Response): Promise { - const {code, phoneNumber} = req.body; + const { code, phoneNumber } = req.body; - const authCode: AuthCodeDocument = await AuthCode.findOne({phoneNumber}); + const authCode: AuthCodeDocument = await AuthCode.findOne({ phoneNumber }); if (authCode.value !== code) { throw new RouteError({ @@ -73,10 +73,10 @@ export default class VerifyCodeRoute extends BaseRoute { }); } - let user: UserDocument = await User.findOne({phoneNumber}); - + let user: UserDocument = await User.findOne({ phoneNumber }); + if (!user) { - user = await User.create({phoneNumber}); + user = await User.create({ phoneNumber }); } // Renew's the user's tokens and attaches these new tokens on the @@ -84,7 +84,7 @@ export default class VerifyCodeRoute extends BaseRoute { const { accessToken, refreshToken } = await user.renewToken(); MiddlewareUtils.attachTokens(res, { accessToken, refreshToken }); - await AuthCode.deleteOne({phoneNumber}); + await AuthCode.deleteOne({ phoneNumber }); return true; } From 7a6a575e646bb5520bd32129426e8f9c6f127579 Mon Sep 17 00:00:00 2001 From: GloryU Date: Sat, 30 Oct 2021 03:16:55 -0400 Subject: [PATCH 3/7] Formatting changes --- src/models/AuthCode.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/AuthCode.ts b/src/models/AuthCode.ts index d46f7fa..0404dba 100644 --- a/src/models/AuthCode.ts +++ b/src/models/AuthCode.ts @@ -26,7 +26,6 @@ interface IAuthCode extends BaseModel { export type AuthCodeDocument = Document<{}, {}, IAuthCode> & IAuthCode; const authCodeSchema: Schema = new Schema( - { // Here's an example of how to add a field to the schema. phoneNumber: { required: true, type: String, unique: true }, value: { default: AuthUtils.generateOTP, required: true, type: Number } @@ -34,7 +33,7 @@ const authCodeSchema: Schema = new Schema( { timestamps: true } ); -authCodeSchema.index({ createdAt: 1 }, { expireAfterSeconds: 60*5 }); +authCodeSchema.index({ createdAt: 1 }, { expireAfterSeconds: 60 * 5 }); const AuthCode: mongoose.Model = mongoose.model(Model.AUTH_CODE, authCodeSchema); From 9aa7e0db4ab0c30714d20e820fb57df82d7393cc Mon Sep 17 00:00:00 2001 From: GloryU Date: Sat, 30 Oct 2021 03:22:58 -0400 Subject: [PATCH 4/7] more cosmetic changes --- src/models/AuthCode.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/AuthCode.ts b/src/models/AuthCode.ts index 0404dba..6e4a74b 100644 --- a/src/models/AuthCode.ts +++ b/src/models/AuthCode.ts @@ -26,7 +26,8 @@ interface IAuthCode extends BaseModel { export type AuthCodeDocument = Document<{}, {}, IAuthCode> & IAuthCode; const authCodeSchema: Schema = new Schema( - { // Here's an example of how to add a field to the schema. + { + // Here's an example of how to add a field to the schema. phoneNumber: { required: true, type: String, unique: true }, value: { default: AuthUtils.generateOTP, required: true, type: Number } }, From d634626193366c40ebbea1d23aaa272493db407e Mon Sep 17 00:00:00 2001 From: GloryU Date: Sat, 30 Oct 2021 03:36:29 -0400 Subject: [PATCH 5/7] Modified lint to avoid unnecessary errors --- .eslintrc.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8ee479f..c98172d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,6 +23,12 @@ module.exports = { 'max-lines-per-function': ['error', { max: 75, skipComments: true }], 'no-underscore-dangle': 0, 'react/jsx-props-no-spreading': 0, - 'react/prop-types': 0 + 'react/prop-types': 0, + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto' + } + ] } }; From 5e4f6b0048f39c13d937120f8dddb3f15c228d26 Mon Sep 17 00:00:00 2001 From: GloryU Date: Sat, 30 Oct 2021 03:40:54 -0400 Subject: [PATCH 6/7] Completed ticket #9. Tests passed! --- src/routes/LogoutRoute.test.ts | 9 +-------- src/routes/LogoutRoute.ts | 20 ++++++-------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/src/routes/LogoutRoute.test.ts b/src/routes/LogoutRoute.test.ts index 48688b4..0846ca7 100644 --- a/src/routes/LogoutRoute.test.ts +++ b/src/routes/LogoutRoute.test.ts @@ -1,14 +1,7 @@ import { TEST_AUTH_COOKIE } from '../../jest.setup'; import TestUtils from '../utils/TestUtils'; -/** - * TODO: (9.03) - * - Remove the ".skip" from the following function. - * - Go to your terminal and run the following command: - * npm run test Logout - * - Delete this comment. - */ -describe.skip('POST /logout', () => { +describe('POST /logout', () => { test('If the user is not authenticated, should return a 401.', async () => { await TestUtils.agent.post('/logout').expect(401); }); diff --git a/src/routes/LogoutRoute.ts b/src/routes/LogoutRoute.ts index 4546792..1f23cdb 100644 --- a/src/routes/LogoutRoute.ts +++ b/src/routes/LogoutRoute.ts @@ -7,17 +7,9 @@ import { RouteMethod } from '../utils/constants'; export default class LogoutRoute extends BaseRoute { constructor() { super({ - /** - * TODO: (9.01) - * - Should the user be authenticated to hit this route? - * - Replace null with the correct route type from the RouteMethod enum - * in the constants.ts file. - * - Fill in the path string with the appropriate path to this endpoint. - * - Delete this comment. - */ - authenticated: false, - method: null, - path: '/' + authenticated: true, + method: RouteMethod.POST, + path: '/logout' }); } @@ -29,9 +21,9 @@ export default class LogoutRoute extends BaseRoute { * Returns true in all cases. */ async content(_: ApplicationRequest, res: Response): Promise { - // TODO: (9.02) Use the res.clearCookie('') function to remove the - // accessToken and refreshToken from their cookies. Return true after! + res.clearCookie('accessToken'); + res.clearCookie('refreshToken'); - return false; + return true; } } From 876538afcb9797174126f0983d771b5f7c53c680 Mon Sep 17 00:00:00 2001 From: Glory U <65326763+RukenaG@users.noreply.github.com> Date: Sun, 31 Oct 2021 12:44:21 -0400 Subject: [PATCH 7/7] Updated eslintrc.js --- .eslintrc.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 59eb2a7..c98172d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,7 +23,6 @@ module.exports = { 'max-lines-per-function': ['error', { max: 75, skipComments: true }], 'no-underscore-dangle': 0, 'react/jsx-props-no-spreading': 0, - 'react/prop-types': 0, 'prettier/prettier': [ 'error', @@ -31,8 +30,5 @@ module.exports = { endOfLine: 'auto' } ] - 'react/prop-types': 0 - - } };