Skip to content

Commit

Permalink
temp
Browse files Browse the repository at this point in the history
  • Loading branch information
sanjaysah101 committed Sep 13, 2024
1 parent 6574be0 commit edf688c
Show file tree
Hide file tree
Showing 14 changed files with 86 additions and 35 deletions.
1 change: 1 addition & 0 deletions src/api/v1/auth/auth.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export const success = {
OTP_VERIFIED: 'OTP verified',
TOKEN_VERIFIED: 'Access token is verified',
FORGET_PASSWORD_EMAIL_SENT: 'Forget password email sent',
PASSWORD_RESET_SUCCESSFULLY: 'Password reset successfully',
};
14 changes: 13 additions & 1 deletion src/api/v1/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ export class AuthController {
public static async forgetPassword(req: Request, res: Response, next: NextFunction) {
try {
await AuthService.forgetPassword(req.body.email);

sendResponse({
response: res,
message: success.FORGET_PASSWORD_EMAIL_SENT,
Expand All @@ -124,4 +123,17 @@ export class AuthController {
next(error);
}
}

public static async resetPassword(req: Request, res: Response, next: NextFunction) {
try {
await AuthService.resetPassword(req.body);
sendResponse({
response: res,
message: success.PASSWORD_RESET_SUCCESSFULLY,
statusCode: STATUS_CODES.OK,
});
} catch (error) {
next(error);
}
}
}
5 changes: 1 addition & 4 deletions src/api/v1/auth/auth.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ router.post('/auth/forget-password', AuthController.forgetPassword);

router.get('/auth/google', signInWithGoogle);
router.get('/auth/google/callback', signInWithGoogleCallback, AuthController.signInWithGoogleCallback);

// router.post('/auth/send-reset-password-email', AuthController.sendResetPasswordEmail);
// router.post('/auth/reset-password', AuthController.resetPassword);
// router.post('/auth/verify-email', AuthController.verifyEmail);
router.post('/auth/reset-password', AuthController.resetPassword);

export { router as authRoutes };
24 changes: 22 additions & 2 deletions src/api/v1/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import { ErrorTypeEnum } from '@/constants';
import { comparePassword, generateAccessToken, generateRefreshToken, validateObjectId } from '@/utils';
import { UserDAL } from '@/api/v1/user/user.dal';
import { UserService } from '@/api/v1/user/user.service';
import { CreateUser, Email, validateEmail } from '@/api/v1/user/user.validation';
import {
CreateUser,
Email,
ResetPassword,
validateEmail,
validateResetPasswordSchema,
} from '@/api/v1/user/user.validation';
import { AuthDAL } from './auth.dal';
import { loginSchema, Login, AuthToken, Auth } from './auth.validation';
import { OtpService } from '@/api/v1/otp/otp.service';
import { GoogleUser } from '@/types/passport-google';
import { TokenAction, TokenService } from '@/api/v1/token';
import { notificationService } from '@/services';

export class AuthService {
public static async signUp(userData: CreateUser) {
Expand Down Expand Up @@ -83,10 +91,22 @@ export class AuthService {

public static async forgetPassword(email: Email) {
validateEmail(email);

await OtpService.sendOtp({ email, otpType: 'sendForgetPasswordOTP' });
}

public static async resetPassword(resetPassword: ResetPassword) {
const { password, token } = validateResetPasswordSchema(resetPassword);

const { userId } = await new TokenService().verifyActionToken(token, TokenAction.resetPassword);

const user = await UserService.updateUser(userId, { password });

notificationService.sendEmail({
to: user.email,
eventType: 'sendPasswordChangeConfirmation',
});
}

static async generateAccessAndRefreshToken(userId: string) {
validateObjectId(userId);

Expand Down
2 changes: 2 additions & 0 deletions src/api/v1/token/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './token.service';
export * from './token.validation';
13 changes: 11 additions & 2 deletions src/api/v1/token/token.dal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { TokenModel } from './token.model';
import { CreateToken, Token } from './token.validation';

interface ITokenDal {
createToken(payload: Token): Promise<CreateToken>;
saveToken(payload: Token): Promise<CreateToken>;
getToken(token: string): Promise<Token | null>;
getTokensByUserId(userId: string): Promise<Token[] | null>;
updateToken(tokenId: string, payload: Token): Promise<Token | null>;
deleteToken(tokenId: string): Promise<Token | null>;
replaceTokenForUser(createTokenSchema: CreateToken): Promise<Token | null>;
}

export class TokenDAL implements ITokenDal {
async createToken(payload: CreateToken): Promise<Token> {
async saveToken(payload: CreateToken): Promise<Token> {
return await TokenModel.create(payload);
}

Expand All @@ -29,6 +30,14 @@ export class TokenDAL implements ITokenDal {
async deleteToken(tokenId: string): Promise<Token | null> {
return await TokenModel.findByIdAndDelete(tokenId);
}

async replaceTokenForUser(tokenSchema: CreateToken): Promise<Token | null> {
return await TokenModel.findOneAndUpdate(
{ userId: tokenSchema.userId, action: tokenSchema.action },
{ ...tokenSchema, $inc: { requestAttempts: 1 } },
{ upsert: true, new: true },
);
}
}

export default TokenDAL;
8 changes: 4 additions & 4 deletions src/api/v1/token/token.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generateTokenForAction, verifyToken } from '@/utils';
import { generateTokenForAction, verifyJWTToken } from '@/utils';
import { TokenDAL } from './token.dal';
import { CreateToken, Token, TokenAction } from './token.validation';
import { ErrorTypeEnum, FIVE_MINUTES_IN_MS } from '@/constants';
Expand All @@ -21,12 +21,12 @@ export class TokenService {
expiryTime: new Date(Date.now() + FIVE_MINUTES_IN_MS),
};

await this.tokenDAL.createToken(tokenPayload);
await this.tokenDAL.replaceTokenForUser(tokenPayload);
return token;
}

async verifyActionToken(token: string, action: TokenAction) {
const verifiedToken = await verifyToken<Token>(token, 'action');
async verifyActionToken(token: string, action: TokenAction): Promise<Token> {
const verifiedToken = await verifyJWTToken<Token>(token, 'action');

// Check if the token is valid for the intended action
// e.g. if the token is for changing subscription, then the action must be changeSubscription
Expand Down
4 changes: 4 additions & 0 deletions src/api/v1/user/user.dal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import mongoose from 'mongoose';
import { UserModel } from './user.model';
import { CreateUser, UpdateUser, User, UserRolePermission } from './user.validation';
import { Login } from '../auth/auth.validation';
import { hashPassword } from '../../../utils';

export class UserDAL {
static async createUser(userData: CreateUser): Promise<User> {
Expand Down Expand Up @@ -51,6 +52,9 @@ export class UserDAL {
}

static async updateUser(userId: string, userData: UpdateUser): Promise<User | null> {
if (userData.password !== null && userData.password !== undefined) {
userData.password = await hashPassword(userData.password);
}
return await UserModel.findByIdAndUpdate(userId, userData, { new: true });
}

Expand Down
14 changes: 14 additions & 0 deletions src/api/v1/user/user.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,31 @@ export const updateUserSchema = userSchema
});

export const getUserSchema = userSchema.omit({ password: true, confirmPassword: true, isDeleted: true });
export const resetPasswordSchema = userSchema
.pick({ password: true, confirmPassword: true })
.extend({
token: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Confirm password does not match password',
path: ['confirmPassword'],
});

export type User = z.infer<typeof userSchema>;
export type CreateUser = z.infer<typeof createUserSchema>;
export type UpdateUser = z.infer<typeof updateUserSchema>;
export type GetUser = z.infer<typeof getUserSchema>;
export type Email = z.infer<typeof validEmail>;
export type ResetPassword = z.infer<typeof resetPasswordSchema>;

export const validateCreateUser = (data: CreateUser) => {
createUserSchema.parse(data);
};

export const validateResetPasswordSchema = (data: ResetPassword): { password: string; token: string } => {
return resetPasswordSchema.parse(data);
};

export interface UserRolePermission {
_id: string;
username: string;
Expand Down
5 changes: 5 additions & 0 deletions src/constants/errorTypes.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const errorType = [
'OTP_EXPIRED',
'INITIAL_SETUP_FAILED',
'NOT_ENOUGH_PERMISSION',
'TOO_MANY_REQUESTS',
] as const;

export const ErrorTypeEnum = z.enum(errorType);
Expand Down Expand Up @@ -171,6 +172,10 @@ export const errorMap = {
message: 'User does not have enough permission to perform this action',
},
},
[ErrorTypeEnum.enum.TOO_MANY_REQUESTS]: {
httpStatusCode: STATUS_CODES.TOO_MANY_REQUESTS,
body: { code: 'too_many_requests', message: 'Too many requests' },
},
};

export type ErrorTypeEnum = z.infer<typeof ErrorTypeEnum>;
Expand Down
1 change: 1 addition & 0 deletions src/constants/statusCode.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum STATUS_CODES {
METHOD_NOT_ALLOWED = 405,
CONFLICT = 409,
UNPROCESSABLE_ENTITY = 422,
TOO_MANY_REQUESTS = 429,
INTERNAL_SERVER_ERROR = 500,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
Expand Down
7 changes: 4 additions & 3 deletions src/middlewares/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextFunction, Response, Request } from 'express';
import { extractTokenFromBearerString, verifyToken } from '@/utils/jwt.util';
import { extractTokenFromBearerString, verifyJWTToken } from '@/utils/jwt.util';
import { ErrorTypeEnum } from '@/constants';
import { Auth, JwtAccessToken, JwtRefreshToken } from '@/api/v1/auth/auth.validation';
import { AuthService } from '@/api/v1/auth/auth.service';
Expand All @@ -13,11 +13,12 @@ const parseUser = async (req: Request, _: Response, next: NextFunction, tokenTyp
const token = extractTokenFromBearerString(authHeader);
let user: Auth;

// TODO: instead of first verifying jwt token and then calling auth service to get user, call auth service directly and add logic to verify jwt token in auth service
if (tokenType === 'refresh') {
const { id }: JwtRefreshToken = await verifyToken<JwtRefreshToken>(token, tokenType);
const { id }: JwtRefreshToken = await verifyJWTToken<JwtRefreshToken>(token, tokenType);
user = await AuthService.verifyToken(id);
} else {
const { userId }: JwtAccessToken = await verifyToken<JwtAccessToken>(token, tokenType);
const { userId }: JwtAccessToken = await verifyJWTToken<JwtAccessToken>(token, tokenType);
user = await AuthService.verifyToken(userId);
}

Expand Down
21 changes: 3 additions & 18 deletions src/services/notification.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,6 @@ const emailVerificationOTPPayload = z.object({
otp: otpValidator,
});

const emailVerificationMagicLinkPayload = z.object({
magicLink: z.string().url(),
});

const emailChangeConfirmationPayload = z.object({
newEmail: emailValidator,
});

const passwordResetOTPPayload = z.object({
otp: otpValidator,
});
Expand All @@ -56,21 +48,14 @@ const emailNotification = z.discriminatedUnion('eventType', [
}),
z.object({
to: emailValidator,
eventType: z.literal('sendEmailVerificationMagicLink'),
subject: z.string().optional(),
payload: emailVerificationMagicLinkPayload,
}),
z.object({
to: emailValidator,
eventType: z.literal('sendEmailChangeConfirmation'),
eventType: z.literal('sendForgetPasswordOTP'),
subject: z.string().optional(),
payload: emailChangeConfirmationPayload,
payload: passwordResetOTPPayload,
}),
z.object({
to: emailValidator,
eventType: z.literal('sendForgetPasswordOTP'),
eventType: z.literal('sendPasswordChangeConfirmation'),
subject: z.string().optional(),
payload: passwordResetOTPPayload,
}),
// ... Add other event types and their corresponding payloads ...
]);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/jwt.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const generateTokenForAction = (payload: JwtActionToken) => {
return jwt.sign(validPayload, JWT_TOKEN_FOR_ACTION_SECRET, { expiresIn: '5m' });
};

export const verifyToken = async <T>(token: string, tokenType: 'access' | 'refresh' | 'action'): Promise<T> => {
export const verifyJWTToken = async <T>(token: string, tokenType: 'access' | 'refresh' | 'action'): Promise<T> => {
try {
const secret = tokenSecrets[tokenType];

Expand Down

0 comments on commit edf688c

Please sign in to comment.