diff --git a/src/models/AuthCode.ts b/src/models/AuthCode.ts index 3c566a9..215ba86 100644 --- a/src/models/AuthCode.ts +++ b/src/models/AuthCode.ts @@ -1,60 +1,64 @@ -import mongoose, { Document, Schema } from 'mongoose'; - -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. - * - * There should a MAXIMUM of 1 AuthCode document per phone number. For - * example, if they submit their phone number twice at the login screen, - * and they receive 2 text messages (and different codes), then only the - * latter one will be associated with this phone number. - */ - phoneNumber: string; - - /** - * 6-digit OTP code that is generated randomly. This code is - * automatically generated upon creation of an AuthCode document. - - */ - value: number; -} - -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 } - }, - { 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. - * */ - -const AuthCode: mongoose.Model = - mongoose.model(Model.AUTH_CODE, authCodeSchema); - -export default AuthCode; +import { string } from 'mathjs'; +import mongoose, { Document, Schema } from 'mongoose'; + +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. + * + * There should a MAXIMUM of 1 AuthCode document per phone number. For + * example, if they submit their phone number twice at the login screen, + * and they receive 2 text messages (and different codes), then only the + * latter one will be associated with this phone number. + */ + phoneNumber: string; + + /** + * 6-digit OTP code that is generated randomly. This code is + * automatically generated upon creation of an AuthCode document. + + */ + value: number; +} + +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: String } + }, + { 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 * 2 }); + +const AuthCode: mongoose.Model = + mongoose.model(Model.AUTH_CODE, authCodeSchema); + +export default AuthCode; diff --git a/src/utils/AuthUtils.ts b/src/utils/AuthUtils.ts index f42d3b2..ce8e1b5 100644 --- a/src/utils/AuthUtils.ts +++ b/src/utils/AuthUtils.ts @@ -1,71 +1,82 @@ -import jwt, { SignOptions } from 'jsonwebtoken'; - -import { APP } from './constants'; - -/** - * Returns a random 6 digit number used for OTP logins. The minimum value that - * the first digit could be is 1, so this will never return a number with - * all 0's. - */ -const generateOTP = (): number => { - /** - * (1.02) TODO: - * - Implement this function. - * - Make sure tall the tests pass. - * - Delete this comment. - */ - return 123456; -}; - -/** - * Returns the signed JWT token using the stored JWT secret and expiration. - * - * @param payload - Payload to encode in the token. Must be an object. - * @param expiresIn - In milliseconds. If empty, this will not expire. - */ -function signToken>( - payload: T, - expiresIn?: SignOptions['expiresIn'] -): string { - return jwt.sign(payload, APP.JWT_SECRET, expiresIn ? { expiresIn } : {}); -} - -/** - * Returns true if the token is both a valid JWT token and if it has not yet - * expired. Returns false otherwise. - */ -const verifyToken = (token: string, options?: jwt.VerifyOptions): boolean => { - try { - return !!jwt.verify(token, APP.JWT_SECRET, options); - } catch { - return false; - } -}; - -/** - * Returns the decoded information stored inside the JWT token. We first - * verify the token to ensure that it is not expired, then decode it. - * - * If the token isn't valid, return null. - * - * @example - * const token = signToken({ id: 1 }); - * decodeToken(token) // Returns { id: 1 }. - */ -function decodeToken>(token: string): T { - // If the token isn't verfied, return null! - if (!verifyToken(token)) { - return null; - } - - return jwt.decode(token) as T; -} - -const AuthUtils = { - decodeToken, - generateOTP, - signToken, - verifyToken -}; - -export default AuthUtils; +import jwt, { SignOptions } from 'jsonwebtoken'; +import { floor, random } from 'mathjs'; + +import { APP } from './constants'; + +/** + * Returns a random 6 digit number used for OTP logins. The minimum value that + * the first digit could be is 1, so this will never return a number with + * all 0's. + */ +const generateOTP = (): number => { + /** + * (1.02) TODO: + * - Implement this function. + * - Make sure tall the tests pass. + * - Delete this comment. + */ + let OTP = ''; + + for (let digit = 0; digit < 6; digit++) { + if (digit === 0) { + OTP += String(Math.floor(Math.random() * 8 + 1)); + } else { + OTP += String(Math.floor(Math.random() * 9)); + } + } + return +OTP; + // the + in front of OTP converts it into a number +}; + +/** + * Returns the signed JWT token using the stored JWT secret and expiration. + * + * @param payload - Payload to encode in the token. Must be an object. + * @param expiresIn - In milliseconds. If empty, this will not expire. + */ +function signToken>( + payload: T, + expiresIn?: SignOptions['expiresIn'] +): string { + return jwt.sign(payload, APP.JWT_SECRET, expiresIn ? { expiresIn } : {}); +} + +/** + * Returns true if the token is both a valid JWT token and if it has not yet + * expired. Returns false otherwise. + */ +const verifyToken = (token: string, options?: jwt.VerifyOptions): boolean => { + try { + return !!jwt.verify(token, APP.JWT_SECRET, options); + } catch { + return false; + } +}; + +/** + * Returns the decoded information stored inside the JWT token. We first + * verify the token to ensure that it is not expired, then decode it. + * + * If the token isn't valid, return null. + * + * @example + * const token = signToken({ id: 1 }); + * decodeToken(token) // Returns { id: 1 }. + */ +function decodeToken>(token: string): T { + // If the token isn't verfied, return null! + if (!verifyToken(token)) { + return null; + } + + return jwt.decode(token) as T; +} + +const AuthUtils = { + decodeToken, + generateOTP, + signToken, + verifyToken +}; + +export default AuthUtils;