made register and login apis for host
This commit is contained in:
@@ -21,7 +21,7 @@ export class CreateHostDto {
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
userPasscode?: string;
|
||||
userPassword?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@@ -49,3 +49,27 @@ export class UpdateHostDto {
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export class GetHostLoginResponseDTO {
|
||||
id: number;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
emailAddress: string;
|
||||
mobileNumber: string | null;
|
||||
isActive: boolean;
|
||||
roleXid: number;
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
|
||||
constructor(user: any, accessToken: string, refreshToken: string) {
|
||||
this.id = user.id;
|
||||
this.firstName = user.firstName;
|
||||
this.lastName = user.lastName;
|
||||
this.emailAddress = user.emailAddress;
|
||||
this.mobileNumber = user.mobileNumber;
|
||||
this.isActive = user.isActive;
|
||||
this.roleXid = user.roleXid;
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
}
|
||||
65
src/modules/host/handlers/createPassword.ts
Normal file
65
src/modules/host/handlers/createPassword.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { HostService } from '../services/host.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const hostService = new HostService(prismaService);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Extract token from headers
|
||||
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
|
||||
if(!token) {
|
||||
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
|
||||
}
|
||||
|
||||
// Authenticate user using the shared authForHost function
|
||||
const userInfo = await verifyHostToken(token);
|
||||
const user_xid = userInfo.id;
|
||||
|
||||
// Parse request body
|
||||
let body: { password?: string; confirmPassword?: string };
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { password, confirmPassword } = body;
|
||||
|
||||
if (!password || !confirmPassword) {
|
||||
throw new ApiError(400, 'Password and confirm password are required');
|
||||
}
|
||||
|
||||
// Validate password match
|
||||
if (password !== confirmPassword) {
|
||||
throw new ApiError(400, 'Password and confirm password do not match');
|
||||
}
|
||||
|
||||
// Validate password length
|
||||
if (password.length < 8) {
|
||||
throw new ApiError(400, 'Password must be at least 8 characters long');
|
||||
}
|
||||
|
||||
await hostService.createPassword(user_xid, password);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Password created successfully',
|
||||
data: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
79
src/modules/host/handlers/loginForHost.ts
Normal file
79
src/modules/host/handlers/loginForHost.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { HostService } from '../services/host.service';
|
||||
import { TokenService } from '../services/token.service';
|
||||
import { GetHostLoginResponseDTO } from '../dto/host.dto';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const hostService = new HostService(prismaService);
|
||||
const tokenService = new TokenService();
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Parse request body
|
||||
let body: { emailAddress?: string; userPassword?: string };
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { emailAddress, userPassword } = body;
|
||||
|
||||
if (!emailAddress || !userPassword) {
|
||||
throw new ApiError(400, 'Email and password are required');
|
||||
}
|
||||
|
||||
const loginForHost = await hostService.loginForHost(emailAddress, userPassword);
|
||||
|
||||
if (!loginForHost) {
|
||||
throw new ApiError(400, 'Failed to login');
|
||||
}
|
||||
|
||||
if (!loginForHost.userPassword) {
|
||||
throw new ApiError(401, 'Invalid credentials');
|
||||
}
|
||||
|
||||
const matchPassword = await bcrypt.compare(
|
||||
userPassword,
|
||||
loginForHost.userPassword
|
||||
);
|
||||
|
||||
if (!matchPassword) {
|
||||
throw new ApiError(401, 'Invalid credentials');
|
||||
}
|
||||
|
||||
const generateTokenForHost = await tokenService.generateAuthToken(
|
||||
loginForHost.id
|
||||
);
|
||||
|
||||
if (!generateTokenForHost) {
|
||||
throw new ApiError(500, 'Failed to generate token');
|
||||
}
|
||||
|
||||
const loginForHostResponse = new GetHostLoginResponseDTO(
|
||||
loginForHost,
|
||||
generateTokenForHost.access.token,
|
||||
generateTokenForHost.refresh.token
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
data: loginForHostResponse,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
75
src/modules/host/handlers/registration.ts
Normal file
75
src/modules/host/handlers/registration.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { HostService } from '../services/host.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { generateOtpHelper } from '../../../common/utils/helper/sendOtp';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const hostService = new HostService(prismaService);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Parse request body
|
||||
let body: { email?: string };
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { email } = body;
|
||||
|
||||
if (!email) {
|
||||
throw new ApiError(400, 'Email is required');
|
||||
}
|
||||
|
||||
const user = await prismaService.user.findUnique({
|
||||
where: { emailAddress: email },
|
||||
select: { emailAddress: true, id: true, userPassword: true },
|
||||
});
|
||||
|
||||
if (user && user.userPassword) {
|
||||
throw new ApiError(404, 'User is already registered. Please login.');
|
||||
}
|
||||
|
||||
let newUser;
|
||||
|
||||
if (user && !user.userPassword) {
|
||||
// ✅ User already exists but without password → reuse record
|
||||
newUser = user;
|
||||
} else {
|
||||
// ✅ No user found → create new one
|
||||
newUser = await hostService.createHostUser(email);
|
||||
}
|
||||
|
||||
const otpResult = await generateOtpHelper(
|
||||
Number(newUser?.id),
|
||||
newUser?.emailAddress,
|
||||
'Register',
|
||||
6,
|
||||
5
|
||||
);
|
||||
|
||||
if (!otpResult || !otpResult.otp) {
|
||||
throw new ApiError(500, 'Failed to send OTP');
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'OTP sent successfully.',
|
||||
data: {},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
52
src/modules/host/handlers/verifyOtp.ts
Normal file
52
src/modules/host/handlers/verifyOtp.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { HostService } from '../services/host.service';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { TokenService } from '../services/token.service';
|
||||
|
||||
const prismaService = new PrismaService();
|
||||
const hostService = new HostService(prismaService);
|
||||
const tokenService = new TokenService();
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
// Parse request body
|
||||
let body: { email?: string; otp?: string };
|
||||
|
||||
try {
|
||||
body = event.body ? JSON.parse(event.body) : {};
|
||||
} catch (error) {
|
||||
throw new ApiError(400, 'Invalid JSON in request body');
|
||||
}
|
||||
|
||||
const { email, otp } = body;
|
||||
|
||||
if (!email || !otp) {
|
||||
throw new ApiError(400, 'Email and OTP are required');
|
||||
}
|
||||
|
||||
await hostService.verifyHostOtp(email, otp);
|
||||
const user = await hostService.getHostByEmail(email);
|
||||
const generateTokenForHost = await tokenService.generateAuthToken(
|
||||
user.id
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'OTP verified successfully',
|
||||
accessToken: generateTokenForHost.access.token,
|
||||
refreshToken: generateTokenForHost.refresh.token,
|
||||
data: null,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../../../common/database/prisma.service';
|
||||
import { CreateHostDto, UpdateHostDto } from '../dto/host.dto';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { User } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class HostService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
constructor(private prisma: PrismaService) { }
|
||||
|
||||
async createHost(data: CreateHostDto) {
|
||||
return this.prisma.user.create({ data });
|
||||
@@ -17,7 +20,9 @@ export class HostService {
|
||||
|
||||
async getHostById(id: number) {
|
||||
const host = await this.prisma.user.findUnique({ where: { id } });
|
||||
if (!host || host.roleXid !== 3) throw new Error('Host not found');
|
||||
if (!host || host.roleXid !== 4) {
|
||||
throw new ApiError(404, 'Host not found');
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
@@ -31,4 +36,111 @@ export class HostService {
|
||||
async deleteHost(id: number) {
|
||||
return this.prisma.user.delete({ where: { id } });
|
||||
}
|
||||
|
||||
async getHostByEmail(email: string): Promise<User> {
|
||||
return this.prisma.user.findUnique({ where: { emailAddress: email } });
|
||||
}
|
||||
|
||||
async verifyHostOtp(email: string, otp: string): Promise<boolean> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { emailAddress: email },
|
||||
select: {
|
||||
id: true,
|
||||
emailAddress: true,
|
||||
UserOtp: {
|
||||
where: { isActive: true, isVerified: false },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(404, 'User not found.');
|
||||
}
|
||||
|
||||
const userOtp = user.UserOtp[0];
|
||||
|
||||
if (!userOtp) {
|
||||
throw new ApiError(400, 'No OTP found.');
|
||||
}
|
||||
|
||||
if (new Date() > userOtp.expiresOn) {
|
||||
throw new ApiError(400, 'OTP has expired.');
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(otp, userOtp.otpCode);
|
||||
|
||||
if (!isMatch) {
|
||||
throw new ApiError(400, 'Invalid OTP.');
|
||||
}
|
||||
|
||||
await this.prisma.userOtp.update({
|
||||
where: { id: userOtp.id },
|
||||
data: {
|
||||
isVerified: true,
|
||||
verifiedOn: new Date(),
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async loginForHost(emailAddress: string, userPassword: string) {
|
||||
const existingUser = await this.prisma.user.findUnique({
|
||||
where: { emailAddress: emailAddress },
|
||||
});
|
||||
|
||||
if (!existingUser) {
|
||||
throw new ApiError(404, 'User not found');
|
||||
}
|
||||
|
||||
if (existingUser.roleXid !== 4) {
|
||||
throw new ApiError(403, 'Access denied. Not a host user.');
|
||||
}
|
||||
|
||||
const matchPassword = await bcrypt.compare(userPassword, existingUser.userPassword);
|
||||
if (!matchPassword) {
|
||||
throw new ApiError(401, 'Invalid credentials');
|
||||
}
|
||||
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
async createHostUser(email: string) {
|
||||
const newUser = await this.prisma.user.create({
|
||||
data: { emailAddress: email, roleXid: 4 },
|
||||
});
|
||||
return newUser;
|
||||
}
|
||||
|
||||
async createPassword(user_xid: number, password: string): Promise<boolean> {
|
||||
// Find user by id
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: user_xid },
|
||||
select: { id: true, emailAddress: true, userPassword: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError(404, 'User not found');
|
||||
}
|
||||
|
||||
// Check if password already exists
|
||||
if (user.userPassword) {
|
||||
throw new ApiError(400, 'Password already exists. Use update password instead.');
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
const saltRounds = parseInt(process.env.SALT_ROUNDS || '10', 10);
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
// Update user with hashed password
|
||||
await this.prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { userPassword: hashedPassword },
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
159
src/modules/host/services/token.service.ts
Normal file
159
src/modules/host/services/token.service.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import jwt, { JwtPayload } from "jsonwebtoken";
|
||||
import moment from "moment";
|
||||
import config from "../../../config/config";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export class TokenService {
|
||||
private generateToken(
|
||||
user_xid: number,
|
||||
expiresIn: Date,
|
||||
type: string,
|
||||
secret: string
|
||||
): { token: string; expires: Date } {
|
||||
const token = jwt.sign(
|
||||
{
|
||||
sub: user_xid,
|
||||
iat: moment().unix(),
|
||||
exp: moment(expiresIn).unix(),
|
||||
type,
|
||||
},
|
||||
secret
|
||||
);
|
||||
|
||||
return { token, expires: expiresIn };
|
||||
}
|
||||
|
||||
async generateAuthToken(
|
||||
user_xid: number,
|
||||
): Promise<{
|
||||
access: { token: string; expires: Date };
|
||||
refresh: { token: string; expires: Date };
|
||||
}> {
|
||||
const accessTokenExpires = moment()
|
||||
.add(config.jwt.accessExpirationMinutes, "minutes")
|
||||
.toDate();
|
||||
|
||||
const refreshTokenExpires = moment()
|
||||
.add(config.jwt.refreshExpirationDays, "days")
|
||||
.toDate();
|
||||
|
||||
const accessToken = this.generateToken(
|
||||
user_xid,
|
||||
accessTokenExpires,
|
||||
"access",
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
const refreshToken = this.generateToken(
|
||||
user_xid,
|
||||
refreshTokenExpires,
|
||||
"refresh",
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
await prisma.token.create({
|
||||
data: {
|
||||
token: refreshToken.token,
|
||||
expiringAt: refreshToken.expires,
|
||||
tokenType: "refresh",
|
||||
isBlackListed: false,
|
||||
|
||||
user: {
|
||||
connect: { id: user_xid },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
access: accessToken,
|
||||
refresh: refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async generateAuthTokenAdmin(
|
||||
user_xid: number
|
||||
): Promise<{
|
||||
access: { token: string; expires: Date };
|
||||
refresh: { token: string; expires: Date };
|
||||
}> {
|
||||
const accessTokenExpires = moment()
|
||||
.add(config.jwt.accessExpirationMinutes, "minutes")
|
||||
.toDate();
|
||||
|
||||
const refreshTokenExpires = moment()
|
||||
.add(config.jwt.refreshExpirationDays, "days")
|
||||
.toDate();
|
||||
|
||||
const accessToken = this.generateToken(
|
||||
user_xid,
|
||||
accessTokenExpires,
|
||||
"access",
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
const refreshToken = this.generateToken(
|
||||
user_xid,
|
||||
refreshTokenExpires,
|
||||
"refresh",
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
await prisma.token.create({
|
||||
data: {
|
||||
token: refreshToken.token,
|
||||
expiringAt: refreshToken.expires,
|
||||
tokenType: "refresh",
|
||||
isBlackListed: false,
|
||||
user: {
|
||||
connect: { id: user_xid },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
access: accessToken,
|
||||
refresh: refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
async revokeToken(user_xid: number, deviceId: string): Promise<boolean> {
|
||||
const existingToken = await prisma.token.findFirst({
|
||||
where: {
|
||||
id: user_xid,
|
||||
deviceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingToken) return false;
|
||||
|
||||
await prisma.token.delete({ where: { id: existingToken.id } });
|
||||
return true;
|
||||
}
|
||||
|
||||
async isTokenBlackListed(token: string): Promise<boolean> {
|
||||
const existing = await prisma.token.findUnique({
|
||||
where: { token },
|
||||
});
|
||||
return existing ? true : false;
|
||||
}
|
||||
|
||||
async verifyRefreshToken(
|
||||
token: string
|
||||
): Promise<string | JwtPayload | null> {
|
||||
try {
|
||||
return jwt.verify(token, config.jwt.secret);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async decodeToken(token: string): Promise<string | JwtPayload | null> {
|
||||
try {
|
||||
return jwt.decode(token);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user