ZindagiAssan_Backend / src /modules /auth /auth.service.ts
Talha812's picture
Upload 45 files
2c16c8c verified
import { getSupabaseClient } from "@/infra/supabase/supabase.client";
import {
ForgetPasswordResponseSchema,
LoginRequestSchema,
OAuthTokenResponse,
RefreshSessionRequestSchema,
RegisterUserRequestSchema,
RegisterUserResponseSchema,
ResendOtpRequestSchema,
ResendOtpResponseSchema,
ResetPasswordRequestSchema,
VerifyOtpRequestSchema,
} from "./auth.dto";
import { IAuthRepository } from "./auth.repository";
import { logger } from "@/shared/logger/logger";
import RedisService from "@/infra/redis/redis.service";
import { ApiResponse, ApiResponseType } from "@/shared/http/api-response";
import { nodemailerService } from "@/infra/nodemail/nodemail.service";
import { HttpStatusCode } from "@/shared/http/status-code";
export class AuthService {
private readonly redisService: RedisService;
constructor(private readonly authRepo: IAuthRepository) {
this.redisService = RedisService.getInstance();
}
public async login(dto: LoginRequestSchema): Promise<ApiResponseType<OAuthTokenResponse>> {
const {data, error} = await getSupabaseClient().auth.signInWithPassword({email: dto.email, password: dto.password});
if (!data.session) {
logger.error("Unable to create session");
return ApiResponse.error(
"Unable to create session",
"SESSION_CREATION_FAILED",
error,
HttpStatusCode.UNAUTHORIZED
);
}
return ApiResponse.success(data.weakPassword?.message || '', data.session);
}
public async register(dto: RegisterUserRequestSchema): Promise<ApiResponseType<RegisterUserResponseSchema>> {
try {
const {data, error} = await getSupabaseClient().auth.admin.listUsers();
if (error) {
logger.error(error, "Failed to check user existence");
return ApiResponse.error(error.message, error.name, null, 500);
}
if (data.users.some((u) => u.email === dto.email)) {
logger.info(`User with email ${dto.email} already exists`);
return ApiResponse.error(
"User with email already exists",
"USER_ALREADY_EXISTS",
null,
HttpStatusCode.CONFLICT
);
}
const otpKey = this.getOtpRedisKey(dto.email);
const existingOtpSession = await this.redisService.get(otpKey);
if (existingOtpSession) {
const ttl = await this.redisService.ttl(otpKey);
if (ttl > 30) {
return ApiResponse.error(
"OTP already sent. Please wait before requesting again.",
"OTP_ALREADY_SENT",
null,
HttpStatusCode.TOO_MANY_REQUESTS
);
}
}
const otp = this.generateOtp();
await this.redisService.set(otpKey, { ...dto, otp }, 60);
// In production send OTP via email/SMS here
await nodemailerService.sendEmail({
to: dto.email,
subject: 'Your OTP Code',
text: `Your OTP code is ${otp}. It is valid for 1 minutes.`,
})
return ApiResponse.success(`OTP sent to email ${dto.email}`, { email: dto.email });
} catch (err) {
logger.error(err, "Error in AuthService.register");
const error = err as Error;
return ApiResponse.error(error.message, error.name, null, 500);
}
}
public async resendOtp(dto: ResendOtpRequestSchema): Promise<ApiResponseType<ResendOtpResponseSchema>> {
try {
const { data, error } = await getSupabaseClient()
.from("auth.users")
.select("id")
.eq("email", dto.email)
.maybeSingle();
if (error) {
logger.error(error, "Failed to check user existence");
return ApiResponse.error(error.message, error.name, null, 500);
}
if (data?.id) {
return ApiResponse.error(
"User with email already exists",
"USER_ALREADY_EXISTS",
null,
HttpStatusCode.CONFLICT
);
}
const otpKey = this.getOtpRedisKey(dto.email);
const existingSession = await this.redisService.get(otpKey);
if (existingSession) {
const ttl = await this.redisService.ttl(otpKey);
if (ttl > 30) {
return ApiResponse.error(
"OTP already sent. Please wait before requesting again.",
"OTP_ALREADY_SENT",
null,
HttpStatusCode.TOO_MANY_REQUESTS
);
}
}
const otp = this.generateOtp();
await this.redisService.set(otpKey, { ...existingSession, otp }, 60);
return ApiResponse.success(`OTP resent to email ${dto.email}`, {otpCode: otp});
} catch (err) {
logger.error(err, "Error in AuthService.resendOtp");
const error = err as Error;
return ApiResponse.error(error.message, error.name, null, 500);
}
}
public async verifyEmailOtp(dto: VerifyOtpRequestSchema): Promise<ApiResponseType<OAuthTokenResponse>> {
try {
const otpKey = this.getOtpRedisKey(dto.email);
const session = await this.redisService.get(otpKey);
if (!session || session.otp !== dto.otpCode) {
logger.warn(`OTP session expired or invalid for ${dto.email}`);
return ApiResponse.error(
"OTP session expired or invalid",
"OTP_SESSION_EXPIRED",
null,
HttpStatusCode.UNAUTHORIZED
);
}
const { error } = await getSupabaseClient().auth.admin.createUser({
email: session.email,
password: session.password,
user_metadata: {
fullName: session.fullName,
phone: session.phone,
},
email_confirm: true,
});
if (error) {
logger.error(error, "Unable to create user");
return ApiResponse.error(
error.message,
error.name,
null,
error.status || HttpStatusCode.INTERNAL_SERVER_ERROR
);
}
const { data } = await getSupabaseClient().auth.signInWithPassword({email: session.email, password: session.password});
if (!data.session) {
logger.error("Unable to create session");
return ApiResponse.error(
"Unable to create session",
"SESSION_CREATION_FAILED",
null,
HttpStatusCode.UNAUTHORIZED
);
}
await this.redisService.del(otpKey);
return ApiResponse.success("OTP verified successfully", data.session);
} catch (err) {
logger.error(err, "Error in AuthService.verifySmsOtp");
const error = err as Error;
return ApiResponse.error(error.message, error.name, null, 500);
}
}
public async forgetPassword(email: string): Promise<ApiResponseType<ForgetPasswordResponseSchema>> {
try {
const { data, error } = await getSupabaseClient().auth.admin.listUsers();
if (error) {
logger.error(error, "Failed to list users");
return ApiResponse.error(error.message, error.name, null, 500);
}
const user = data.users.find((u) => u.email === email);
if (!user) {
return ApiResponse.error(
"No user found with that email",
"USER_NOT_FOUND",
null,
HttpStatusCode.NOT_FOUND
);
}
const otpKey = this.getForgetPasswordRedisKey(email);
const existingOtp = await this.redisService.get(otpKey);
if (existingOtp) {
const ttl = await this.redisService.ttl(otpKey);
if (ttl > 30) {
return ApiResponse.error(
"OTP already sent. Please wait before requesting again.",
"OTP_ALREADY_SENT",
null,
HttpStatusCode.TOO_MANY_REQUESTS
);
}
}
const otp = this.generateOtp();
await this.redisService.set(otpKey, { email, otp }, 60);
await nodemailerService.sendEmail({
to: email,
subject: "Reset your password",
text: `Your password reset OTP is ${otp}. It is valid for 1 minute.`,
});
return ApiResponse.success(`OTP sent to ${email}`, { email });
} catch (err) {
logger.error(err, "Error in forgetPasswordRequest");
const error = err as Error;
return ApiResponse.error(error.message, error.name, null, 500);
}
}
public async resetPassword(resetPasswordDto: ResetPasswordRequestSchema): Promise<ApiResponseType<{}>> {
try {
const otpKey = this.getForgetPasswordRedisKey(resetPasswordDto.email);
const session = await this.redisService.get(otpKey);
if (!session || session.otp !== resetPasswordDto.otpCode) {
logger.warn(`Invalid or expired OTP for ${resetPasswordDto.email}`);
return ApiResponse.error(
"OTP invalid or expired",
"OTP_INVALID",
null,
HttpStatusCode.UNAUTHORIZED
);
}
const { data, error } = await getSupabaseClient().auth.admin.listUsers();
if (error) {
logger.error(error, "Failed to list users");
return ApiResponse.error(error.message, error.name, null, 500);
}
const user = data.users.find((u) => u.email === resetPasswordDto.email);
if (!user) {
return ApiResponse.error(
"User not found",
"USER_NOT_FOUND",
null,
HttpStatusCode.NOT_FOUND
);
}
const { error: updateError } = await getSupabaseClient().auth.admin.updateUserById(user.id, {
password: resetPasswordDto.newPassword,
});
if (updateError) {
logger.error(updateError, "Failed to update user password");
return ApiResponse.error(updateError.message, updateError.name, null, 500);
}
await this.redisService.del(otpKey);
return ApiResponse.success("Password updated successfully", {});
} catch (err) {
logger.error(err, "Error in verifyForgetPasswordOtp");
const error = err as Error;
return ApiResponse.error(error.message, error.name, null, 500);
}
}
public async refreshSession(sessionDto: RefreshSessionRequestSchema): Promise<ApiResponseType<OAuthTokenResponse>> {
try {
const { data, error } = await getSupabaseClient().auth.setSession({
refresh_token: sessionDto.refreshToken,
access_token: sessionDto.accessToken,
});
if (error || !data.session) {
console.error("Refresh token error:", error);
return ApiResponse.error(
"Unable to refresh session",
"REFRESH_FAILED",
error,
HttpStatusCode.UNAUTHORIZED
);
}
return ApiResponse.success("Session refreshed successfully", data.session);
} catch (err) {
console.error("Unexpected error in refreshSession:", err);
const error = err as Error;
return ApiResponse.error(error.message, error.name, null, 500);
}
}
private generateOtp(): string {
return Math.floor(100000 + Math.random() * 900000).toString();
}
private getOtpRedisKey(email: string): string {
return `register_otp:${email}`;
}
private getForgetPasswordRedisKey(email: string): string {
return `forget_password_otp:${email}`;
}
}