Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 64 additions & 60 deletions src/models/AuthCode.ts
Original file line number Diff line number Diff line change
@@ -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<AuthCodeDocument> = new Schema<AuthCodeDocument>(
/**
* (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<AuthCodeDocument> =
mongoose.model<AuthCodeDocument>(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<AuthCodeDocument> = new Schema<AuthCodeDocument>(
/**
* (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<AuthCodeDocument> =
mongoose.model<AuthCodeDocument>(Model.AUTH_CODE, authCodeSchema);

export default AuthCode;
153 changes: 82 additions & 71 deletions src/utils/AuthUtils.ts
Original file line number Diff line number Diff line change
@@ -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<T extends Record<string, unknown>>(
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<T extends Record<string, unknown>>(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<T extends Record<string, unknown>>(
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<T extends Record<string, unknown>>(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;