Add refresh token endpoint for admin and user

master
nicekid1 1 month ago
parent 3591ae9139
commit b043c75f92
  1. 44
      migrations/20250104074851-create-user.js
  2. 13
      migrations/20250104094702-create-admin.js
  3. 10
      src/admin/admin.controller.ts
  4. 48
      src/admin/admin.service.ts
  5. 5
      src/admin/entities/admin.entity.ts
  6. 7
      src/users/entities/user.entity.ts
  7. 10
      src/users/users.controller.ts
  8. 75
      src/users/users.service.ts

@ -1,63 +1,69 @@
'use strict'; 'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = { module.exports = {
async up(queryInterface, Sequelize) { up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Users',{cascade:true}) await queryInterface.dropTable('Users', { cascade: true });
await queryInterface.createTable('Users', { await queryInterface.createTable('Users', {
id: { id: {
allowNull: false, type: Sequelize.INTEGER,
autoIncrement: true, autoIncrement: true,
primaryKey: true, primaryKey: true,
type: Sequelize.INTEGER allowNull: false,
}, },
email: { email: {
type: Sequelize.STRING, type: Sequelize.STRING,
unique: true, unique: true,
allowNull: false allowNull: false,
}, },
password: { password: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false allowNull: false,
}, },
role: { role: {
type: Sequelize.STRING, type: Sequelize.STRING,
defaultValue: 'user' allowNull: false,
defaultValue: 'user',
}, },
firstName: { firstName: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false allowNull: true,
}, },
lastName: { lastName: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false allowNull: true,
}, },
username: { username: {
type: Sequelize.STRING, type: Sequelize.STRING,
unique: true, unique: true,
allowNull: false allowNull: false,
}, },
phoneNumber: { phoneNumber: {
type: Sequelize.STRING, type: Sequelize.STRING,
unique: true, unique: true,
allowNull: false allowNull: false,
}, },
gender: { gender: {
type: Sequelize.ENUM("male", "female"), type: Sequelize.ENUM('male', 'female'),
allowNull: false allowNull: false,
},
refreshToken: {
type: Sequelize.STRING,
allowNull: true,
}, },
createdAt: { createdAt: {
type: Sequelize.DATE,
allowNull: false, allowNull: false,
type: Sequelize.DATE defaultValue: Sequelize.fn('NOW'),
}, },
updatedAt: { updatedAt: {
type: Sequelize.DATE,
allowNull: false, allowNull: false,
type: Sequelize.DATE defaultValue: Sequelize.fn('NOW'),
} },
}); });
}, },
async down(queryInterface, Sequelize) { down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Users'); await queryInterface.dropTable('Users');
} },
}; };

@ -3,13 +3,12 @@
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Admins', { cascade: true }); await queryInterface.dropTable('Admins', { cascade: true });
await queryInterface.createTable('Admins', { await queryInterface.createTable('Admins', {
id: { id: {
allowNull: false, type: Sequelize.INTEGER,
autoIncrement: true, autoIncrement: true,
primaryKey: true, primaryKey: true,
type: Sequelize.INTEGER allowNull: false,
}, },
email: { email: {
type: Sequelize.STRING, type: Sequelize.STRING,
@ -42,6 +41,10 @@ module.exports = {
allowNull: false, allowNull: false,
unique: true, unique: true,
}, },
refreshToken: {
type: Sequelize.STRING,
allowNull: true,
},
gender: { gender: {
type: Sequelize.ENUM('male', 'female'), type: Sequelize.ENUM('male', 'female'),
allowNull: false, allowNull: false,
@ -49,12 +52,12 @@ module.exports = {
createdAt: { createdAt: {
type: Sequelize.DATE, type: Sequelize.DATE,
allowNull: false, allowNull: false,
defaultValue: Sequelize.NOW, defaultValue: Sequelize.fn('NOW'),
}, },
updatedAt: { updatedAt: {
type: Sequelize.DATE, type: Sequelize.DATE,
allowNull: false, allowNull: false,
defaultValue: Sequelize.NOW, defaultValue: Sequelize.fn('NOW'),
}, },
}); });
}, },

@ -8,14 +8,22 @@ import { RoleGuard } from "src/guard/role.guard";
@Controller("admin") @Controller("admin")
export class AdminController { export class AdminController {
constructor(private readonly adminService: AdminService) {} constructor(private readonly adminService: AdminService) {}
// register as a admin
@Post("register") @Post("register")
async register(@Body() createAdminDto: CreateAdminDto): Promise<{message}> { async register(@Body() createAdminDto: CreateAdminDto): Promise<{message}> {
return this.adminService.register(createAdminDto); return this.adminService.register(createAdminDto);
} }
//login as admin
@Post("login") @Post("login")
async login(@Body() loginAdminDto: LoginAdminDto): Promise<{ token: string }> { async login(@Body() loginAdminDto: LoginAdminDto): Promise<{ accessToken, refreshToken }> {
return this.adminService.login(loginAdminDto); return this.adminService.login(loginAdminDto);
} }
//get a new access token
@Post("new-token")
async newAccessToken(@Body("refreshToken") refreshToken: string) {
return this.adminService.newAccessToken(refreshToken);
}
//edit admin profile
@UseGuards(RoleGuard) @UseGuards(RoleGuard)
@Put() @Put()
async editAdminProfile(@Request() req, @Body() updateAdminDto: UpdateUserDto): Promise<{message}>{ async editAdminProfile(@Request() req, @Body() updateAdminDto: UpdateUserDto): Promise<{message}>{

@ -34,7 +34,7 @@ export class AdminService {
} }
} }
//login method //login method
async login(loginAdminDto: LoginAdminDto): Promise<{ token: string }> { async login(loginAdminDto: LoginAdminDto): Promise<{ accessToken: string; refreshToken: string }> {
try { try {
const admin = await this.adminModel.findOne({ const admin = await this.adminModel.findOne({
where: { email: loginAdminDto.email }, where: { email: loginAdminDto.email },
@ -49,7 +49,7 @@ export class AdminService {
throw new HttpException("Invalid email or password.", HttpStatus.UNAUTHORIZED); throw new HttpException("Invalid email or password.", HttpStatus.UNAUTHORIZED);
} }
const token = this.jwtService.sign( const accessToken = this.jwtService.sign(
{ id: admin.id, role: admin.role }, { id: admin.id, role: admin.role },
{ {
secret: this.configService.get<string>("JWT_SECRET"), secret: this.configService.get<string>("JWT_SECRET"),
@ -57,7 +57,15 @@ export class AdminService {
}, },
); );
return { token }; const refreshToken = this.jwtService.sign(
{ id: admin.id, role: admin.role },
{
secret: this.configService.get<string>("JWT_REFRESH_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
},
);
return { accessToken, refreshToken };
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
@ -65,6 +73,40 @@ export class AdminService {
throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
//getting new access token
async newAccessToken(refreshToken: string) {
if (!refreshToken) {
throw new HttpException({ message: "Refresh token is required." }, HttpStatus.BAD_REQUEST);
}
let decoded;
try {
decoded = this.jwtService.verify(refreshToken, { secret: process.env.JWT_REFRESH_SECRET });
} catch (error) {
throw new HttpException({ message: "Invalid or expired token." }, HttpStatus.UNAUTHORIZED);
}
const user = await this.adminModel.findOne({
where: { id: decoded.id },
});
if (!user) {
throw new HttpException({ message: "User not found." }, HttpStatus.NOT_FOUND);
}
if (user.role !== "admin") {
throw new HttpException({ message: "You are not authorized to get an admin token." }, HttpStatus.FORBIDDEN);
}
const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role },
{
secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h",
},
);
return { accessToken };
}
//edit admin profile method //edit admin profile method
async editAdminProfile(userId: number, updateAdminDto: UpdateUserDto) { async editAdminProfile(userId: number, updateAdminDto: UpdateUserDto) {
try { try {

@ -22,6 +22,11 @@ export class Admin extends Model<Admin> {
@Column({ unique: true }) @Column({ unique: true })
phoneNumber: string; phoneNumber: string;
@Column({
type: DataType.STRING,
allowNull: true,
})
refreshToken: string;
@Column({ @Column({
type: DataType.ENUM("male", "female"), type: DataType.ENUM("male", "female"),

@ -28,6 +28,13 @@ export class User extends Model<User> {
allowNull: false, allowNull: false,
}) })
gender: string; gender: string;
@Column({
type: DataType.STRING,
allowNull: true,
})
refreshToken: string;
} }
export enum Gender { export enum Gender {
Male = 'male', Male = 'male',

@ -12,14 +12,19 @@ export class UsersController {
constructor(private readonly usersService: UsersService) {} constructor(private readonly usersService: UsersService) {}
//register as user //register as user
@Post("register") @Post("register")
async register(@Body() createUserDto: CreateUserDto): Promise<{message}> { async register(@Body() createUserDto: CreateUserDto): Promise<{ message }> {
return this.usersService.register(createUserDto); return this.usersService.register(createUserDto);
} }
//login as user //login as user
@Post("login") @Post("login")
async login(@Body() loginUserDto: LoginUserDto): Promise<{ token }> { async login(@Body() loginUserDto: LoginUserDto): Promise<{ accessToken; refreshToken }> {
return this.usersService.login(loginUserDto); return this.usersService.login(loginUserDto);
} }
//get access token
@Post("new-token")
async newAccessToken(@Body("refreshToken") refreshToken: string) {
return this.usersService.newAccessToken(refreshToken);
}
//retrieve a user information //retrieve a user information
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ -41,6 +46,7 @@ export class UsersController {
async findAll(): Promise<User[]> { async findAll(): Promise<User[]> {
return this.usersService.findAll(); return this.usersService.findAll();
} }
// get a specific user info by admin
@UseGuards(RoleGuard) @UseGuards(RoleGuard)
@Get("users/:id") @Get("users/:id")
async findSpecificUserInfoByUser(@Param("id") id): Promise<User> { async findSpecificUserInfoByUser(@Param("id") id): Promise<User> {

@ -36,7 +36,7 @@ export class UsersService {
} }
} }
// Login method // Login method
async login(loginUserDto: LoginUserDto): Promise<{ token: string }> { async login(loginUserDto: LoginUserDto): Promise<{ accessToken: string; refreshToken: string }> {
try { try {
const user = await this.userModel.findOne({ const user = await this.userModel.findOne({
where: { email: loginUserDto.email }, where: { email: loginUserDto.email },
@ -45,18 +45,34 @@ export class UsersService {
if (!user) { if (!user) {
throw new UnauthorizedException("Invalid email or password."); throw new UnauthorizedException("Invalid email or password.");
} }
const passwordMatch = await bcrypt.compare(loginUserDto.password, user.password); const passwordMatch = await bcrypt.compare(loginUserDto.password, user.password);
if (!passwordMatch) { if (!passwordMatch) {
throw new UnauthorizedException("Invalid email or password."); throw new UnauthorizedException("Invalid email or password.");
} }
const token = this.jwtService.sign(
const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role }, { id: user.id, role: user.role },
{ {
secret: this.configService.get<string>("JWT_SECRET"), secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h", expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h",
}, },
); );
return { token };
const refreshToken = this.jwtService.sign(
{ id: user.id },
{
secret: this.configService.get<string>("JWT_REFRESH_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
},
);
await this.userModel.update(
{ refreshToken }, //
{ where: { id: user.id } },
);
return { accessToken: accessToken, refreshToken: refreshToken };
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
@ -64,6 +80,36 @@ export class UsersService {
throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
// getting access token
async newAccessToken(refreshToken: string) {
if (!refreshToken) {
throw new HttpException({ message: "Refresh token is required." }, HttpStatus.BAD_REQUEST);
}
let decoded;
try {
decoded = this.jwtService.verify(refreshToken, { secret: process.env.JWT_REFRESH_SECRET });
} catch (error) {
throw new HttpException({ message: "Invalid or expired token." }, HttpStatus.UNAUTHORIZED);
}
const user = await this.userModel.findOne({
where: { id: decoded.id },
});
if (!user) {
throw new HttpException({ message: "User not found." }, HttpStatus.NOT_FOUND);
}
const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role },
{
secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h",
},
);
return { accessToken };
}
//get information user method //get information user method
async getProfile(userId: number): Promise<User> { async getProfile(userId: number): Promise<User> {
try { try {
@ -84,7 +130,7 @@ export class UsersService {
try { try {
let user = await this.userModel.findOne({ where: { id: userId } }); let user = await this.userModel.findOne({ where: { id: userId } });
if (!user) { if (!user) {
throw new NotFoundException('User not found.'); throw new NotFoundException("User not found.");
} }
if (updateUserDto.password) { if (updateUserDto.password) {
updateUserDto.password = await bcrypt.hash(updateUserDto.password, 10); updateUserDto.password = await bcrypt.hash(updateUserDto.password, 10);
@ -92,14 +138,11 @@ export class UsersService {
await user.update(updateUserDto); await user.update(updateUserDto);
user = await this.userModel.findOne({ user = await this.userModel.findOne({
where: { id: userId }, where: { id: userId },
attributes: { exclude: ['password'] }, attributes: { exclude: ["password"] },
}); });
return { message: 'User account updated successfully.', user }; return { message: "User account updated successfully.", user };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
'An unexpected error occurred. Please try again later.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
//get users list //get users list
@ -107,10 +150,7 @@ export class UsersService {
try { try {
return await this.userModel.findAll(); return await this.userModel.findAll();
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException("An unexpected error occurred while fetching users.", HttpStatus.INTERNAL_SERVER_ERROR);
'An unexpected error occurred while fetching users.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
//get users list //get users list
@ -118,14 +158,11 @@ export class UsersService {
try { try {
const user = await this.userModel.findByPk(userId); const user = await this.userModel.findByPk(userId);
if (!user) { if (!user) {
throw new HttpException('User not found.', HttpStatus.NOT_FOUND); throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
} }
return user; return user;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException("An unexpected error occurred while fetching user information.", HttpStatus.INTERNAL_SERVER_ERROR);
'An unexpected error occurred while fetching user information.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
} }

Loading…
Cancel
Save