implementation jti auth

master
aliMohtarami 4 weeks ago
parent 05f97c8300
commit 65e3afd64f
  1. 4
      migrations/20250104074851-create-user.js
  2. 110
      package-lock.json
  3. 2
      package.json
  4. 4
      src/shop/shop.module.ts
  5. 5
      src/users/entities/user.entity.ts
  6. 20
      src/users/guard/auth.guard.ts
  7. 4
      src/users/guard/auth.module.ts
  8. 26
      src/users/guard/jwt.strategy.ts
  9. 6
      src/users/guard/role.guard.ts
  10. 4
      src/users/users.controller.ts
  11. 33
      src/users/users.module.ts
  12. 64
      src/users/users.service.ts

@ -46,10 +46,6 @@ module.exports = {
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, type: Sequelize.DATE,
allowNull: false, allowNull: false,

110
package-lock.json generated

@ -21,6 +21,7 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"ioredis": "^5.4.2",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"pg": "^8.13.1", "pg": "^8.13.1",
@ -30,6 +31,7 @@
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"sequelize-typescript": "^2.1.6", "sequelize-typescript": "^2.1.6",
"umzug": "^3.8.2", "umzug": "^3.8.2",
"uuid": "^11.0.5",
"zarinpal-checkout": "^0.3.0" "zarinpal-checkout": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
@ -936,6 +938,12 @@
"dev": true, "dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -1857,6 +1865,19 @@
"sequelize-typescript": "^2.0.0" "sequelize-typescript": "^2.0.0"
} }
}, },
"node_modules/@nestjs/sequelize/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@nestjs/testing": { "node_modules/@nestjs/testing": {
"version": "10.4.15", "version": "10.4.15",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.15.tgz", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.15.tgz",
@ -3980,6 +4001,15 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/co": { "node_modules/co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -4367,6 +4397,15 @@
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -6103,6 +6142,30 @@
"node": ">=12.0.0" "node": ">=12.0.0"
} }
}, },
"node_modules/ioredis": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.2.tgz",
"integrity": "sha512-0SZXGNGZ+WzISQ67QDyZ2x0+wVxjjUndtD8oSeik/4ajifeiRufed8fCb8QW8VMyi4MXcS+UO1k/0NGhvq1PAg==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -7409,12 +7472,24 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.includes": { "node_modules/lodash.includes": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT"
},
"node_modules/lodash.isboolean": { "node_modules/lodash.isboolean": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
@ -8805,6 +8880,27 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"license": "MIT",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/reflect-metadata": { "node_modules/reflect-metadata": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
@ -9681,6 +9777,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT"
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -10619,16 +10721,16 @@
} }
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "9.0.1", "version": "11.0.5",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
"funding": [ "funding": [
"https://github.com/sponsors/broofa", "https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan" "https://github.com/sponsors/ctavan"
], ],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"uuid": "dist/bin/uuid" "uuid": "dist/esm/bin/uuid"
} }
}, },
"node_modules/v8-compile-cache-lib": { "node_modules/v8-compile-cache-lib": {

@ -32,6 +32,7 @@
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"ioredis": "^5.4.2",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"pg": "^8.13.1", "pg": "^8.13.1",
@ -41,6 +42,7 @@
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"sequelize-typescript": "^2.1.6", "sequelize-typescript": "^2.1.6",
"umzug": "^3.8.2", "umzug": "^3.8.2",
"uuid": "^11.0.5",
"zarinpal-checkout": "^0.3.0" "zarinpal-checkout": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {

@ -11,6 +11,8 @@ import { Wallet } from "./entities/wallet.entity";
import { Transaction } from "./entities/transaction.entity"; import { Transaction } from "./entities/transaction.entity";
import { Payment } from "./entities/payment.entity"; import { Payment } from "./entities/payment.entity";
import { Invoice } from "./entities/invoice.entity"; import { Invoice } from "./entities/invoice.entity";
import { RedisService } from "src/redis/redis.service";
import { JwtStrategy } from "src/users/guard/jwt.strategy";
@Module({ @Module({
imports: [ imports: [
@ -21,6 +23,6 @@ import { Invoice } from "./entities/invoice.entity";
}), }),
], ],
controllers: [ShopController], controllers: [ShopController],
providers: [ShopService, RoleGuard, JwtAuthGuard], providers: [ShopService, RoleGuard, JwtAuthGuard,RedisService,JwtStrategy],
}) })
export class ShopModule {} export class ShopModule {}

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

@ -1,13 +1,16 @@
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { CanActivate, ExecutionContext, UnauthorizedException } from "@nestjs/common"; import { CanActivate, ExecutionContext, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt"; import { JwtService } from "@nestjs/jwt";
import { Observable } from "rxjs"; import { JwtStrategy } from './jwt.strategy';
@Injectable() @Injectable()
export class JwtAuthGuard implements CanActivate { export class JwtAuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {} constructor(
private readonly jwtService: JwtService,
private readonly jwtStrategy: JwtStrategy
) {}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
const token = request.headers["authorization"]?.split(" ")[1]; const token = request.headers["authorization"]?.split(" ")[1];
@ -16,10 +19,15 @@ export class JwtAuthGuard implements CanActivate {
} }
try { try {
const decoded = this.jwtService.verify(token,{secret:process.env.JWT_SECRET}); const decoded = this.jwtService.verify(token, {
request.user = decoded; secret: process.env.JWT_SECRET,
return true; });
await this.jwtStrategy.validate(decoded);
request.user = decoded;
return true;
} catch (error) { } catch (error) {
console.log(error)
throw new UnauthorizedException("Invalid or expired token"); throw new UnauthorizedException("Invalid or expired token");
} }
} }

@ -1,9 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import { JwtAuthGuard } from './auth.guard'; import { JwtAuthGuard } from './auth.guard';
import { RedisService } from 'src/redis/redis.service';
import { JwtStrategy } from './jwt.strategy';
@Module({ @Module({
providers: [JwtService, JwtAuthGuard], providers: [JwtService, JwtAuthGuard,RedisService,JwtStrategy],
exports: [JwtService, JwtAuthGuard], exports: [JwtService, JwtAuthGuard],
}) })
export class AuthModule {} export class AuthModule {}

@ -0,0 +1,26 @@
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { RedisService } from 'src/redis/redis.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly redisService: RedisService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
});
}
async validate(payload: any): Promise<any> {
const { jti, id } = payload;
const response = await this.redisService.isWhitelisted(id, jti)
console.log(response)
if (!response) {
throw new UnauthorizedException('Token is not whitelisted');
}
return payload;
}
}

@ -1,4 +1,4 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; import { CanActivate, ExecutionContext, HttpException, Injectable, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt"; import { JwtService } from "@nestjs/jwt";
@Injectable() @Injectable()
@ -23,7 +23,9 @@ export class RoleGuard implements CanActivate {
request.user = decoded; request.user = decoded;
return true; return true;
} catch (error) { } catch (error) {
console.log(error); if(error instanceof HttpException){
throw error
}
throw new UnauthorizedException(error.response); throw new UnauthorizedException(error.response);
} }
} }

@ -29,8 +29,8 @@ export class UsersController {
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get("logout") @Get("logout")
async logout(@Request() req) { async logout(@Request() req) {
const userId = req.user.id; const user = req.user;
return this.usersService.logout(userId); return this.usersService.logout(user);
} }
//retrieve a user information //retrieve a user information
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)

@ -1,30 +1,33 @@
import { Module } from '@nestjs/common'; import { Module } from "@nestjs/common";
import { SequelizeModule } from '@nestjs/sequelize'; import { SequelizeModule } from "@nestjs/sequelize";
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from "@nestjs/config";
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from "@nestjs/passport";
import { UsersService } from './users.service'; import { UsersService } from "./users.service";
import { UsersController } from './users.controller'; import { UsersController } from "./users.controller";
import { User } from './entities/user.entity'; import { User } from "./entities/user.entity";
import { JwtAuthGuard } from 'src/users/guard/auth.guard'; import { JwtAuthGuard } from "src/users/guard/auth.guard";
import { JwtStrategy } from "./guard/jwt.strategy";
import { RedisService } from "src/redis/redis.service";
@Module({ @Module({
imports: [SequelizeModule.forFeature([User]), imports: [
PassportModule.register({ defaultStrategy: 'jwt' }), SequelizeModule.forFeature([User]),
PassportModule.register({ defaultStrategy: "jwt" }),
JwtModule.registerAsync({ JwtModule.registerAsync({
imports: [ConfigModule], imports: [ConfigModule],
inject: [ConfigService], inject: [ConfigService],
useFactory: (config: ConfigService) => { useFactory: (config: ConfigService) => {
return { return {
secret: config.get<string>('JWT_SECRET'), secret: config.get<string>("JWT_SECRET"),
signOptions: { signOptions: {
expiresIn: config.get<string | number>('JWT_EXPIRES', '1h'), expiresIn: config.get<string | number>("JWT_EXPIRES", "1h"),
}, },
}; };
}, },
}), }),
], ],
controllers: [UsersController], controllers: [UsersController],
providers: [UsersService,JwtAuthGuard], providers: [UsersService, JwtAuthGuard, RedisService, JwtStrategy],
}) })
export class UsersModule {} export class UsersModule {}

@ -7,6 +7,8 @@ import { ConfigService } from "@nestjs/config";
import { CreateUserDto } from "./dto/create-user.dto"; import { CreateUserDto } from "./dto/create-user.dto";
import { LoginUserDto } from "./dto/login-user.dto"; import { LoginUserDto } from "./dto/login-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto"; import { UpdateUserDto } from "./dto/update-user.dto";
import { v4 as uuidv4 } from "uuid";
import { RedisService } from "src/redis/redis.service";
@Injectable() @Injectable()
export class UsersService { export class UsersService {
@ -14,6 +16,7 @@ export class UsersService {
@InjectModel(User) private readonly userModel: typeof User, @InjectModel(User) private readonly userModel: typeof User,
private readonly jwtService: JwtService, private readonly jwtService: JwtService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly redisService: RedisService,
) {} ) {}
// Register method // Register method
@ -46,7 +49,6 @@ export class UsersService {
const user = await this.userModel.findOne({ const user = await this.userModel.findOne({
where: { email: createUserDto.email }, where: { email: createUserDto.email },
}); });
const refreshToken = this.jwtService.sign( const refreshToken = this.jwtService.sign(
{ id: user.id }, { id: user.id },
{ {
@ -54,9 +56,6 @@ export class UsersService {
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d", expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
}, },
); );
await this.userModel.update({ refreshToken }, { where: { id: user.id } });
return { message: "User registered successfully." }; return { message: "User registered successfully." };
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
@ -73,7 +72,7 @@ export class UsersService {
}); });
if (!user) { if (!user) {
throw new UnauthorizedException("Invalid email , username or password."); throw new UnauthorizedException("Invalid email, username or password.");
} }
const passwordMatch = await bcrypt.compare(loginUserDto.password, user.password); const passwordMatch = await bcrypt.compare(loginUserDto.password, user.password);
@ -81,8 +80,9 @@ export class UsersService {
throw new UnauthorizedException("Invalid email or password."); throw new UnauthorizedException("Invalid email or password.");
} }
const jti = uuidv4();
const accessToken = this.jwtService.sign( const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role }, { id: user.id, role: user.role, jti },
{ {
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",
@ -90,17 +90,13 @@ export class UsersService {
); );
const refreshToken = this.jwtService.sign( const refreshToken = this.jwtService.sign(
{ id: user.id }, { id: user.id, jti },
{ {
secret: this.configService.get<string>("JWT_REFRESH_SECRET"), secret: this.configService.get<string>("JWT_REFRESH_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d", expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
}, },
); );
await this.redisService.addToWhitelist(user.id, jti, 3600);
await this.userModel.update(
{ refreshToken }, //
{ where: { id: user.id } },
);
return { accessToken, refreshToken }; return { accessToken, refreshToken };
} catch (error) { } catch (error) {
@ -127,38 +123,30 @@ export class UsersService {
if (!user) { if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND); throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
} }
if (user.refreshToken !== refreshToken) { const jti = uuidv4();
throw new HttpException("Invalid refresh token.", HttpStatus.FORBIDDEN);
}
const accessToken = this.jwtService.sign( const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role }, { id: user.id, role: user.role, jti },
{ {
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",
}, },
); );
await this.redisService.addToWhitelist(user.id, jti, 3600);
return { accessToken }; return { accessToken };
} }
//logout (delete refresh token from database) //logout (delete refresh token from database)
async logout(userId: number) { async logout(user: any) {
if (!userId) {
throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST);
}
try { try {
const user = await this.userModel.findOne({ where: { id: userId } }); const jti = user.jti;
if (!user) { const userId = user.id;
throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
}
await this.userModel.update({ refreshToken: null }, { where: { id: userId } }); await this.redisService.deleteFromWhitelist(userId, jti);
return { message: "Logout is successful." }; return { message: "Logged out successfully." };
} catch (error) { } catch (error) {
throw new HttpException("An error occurred while logging out.", HttpStatus.INTERNAL_SERVER_ERROR); throw new UnauthorizedException("An error occurred during logout.");
} }
} }
//get information user method //get information user method
@ -166,7 +154,7 @@ export class UsersService {
try { try {
const user = await this.userModel.findOne({ const user = await this.userModel.findOne({
where: { id: userId }, where: { id: userId },
attributes: { exclude: ["password","refreshToken"] }, attributes: { exclude: ["password", "refreshToken"] },
}); });
if (!user) { if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND); throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
@ -252,14 +240,14 @@ export class UsersService {
return users; return users;
} catch (error) { } catch (error) {
if(error instanceof HttpException){ if (error instanceof HttpException) {
throw error throw error;
} }
throw new HttpException("An unexpected error occurred while fetching users.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An unexpected error occurred while fetching users.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
//get users list //get users list
async findSpecificUserInfoByUser(userId: number){ async findSpecificUserInfoByUser(userId: number) {
try { try {
const user = await this.userModel.findByPk(userId, { const user = await this.userModel.findByPk(userId, {
attributes: { exclude: ["refreshToken", "password"] }, attributes: { exclude: ["refreshToken", "password"] },
@ -269,8 +257,8 @@ export class UsersService {
} }
return user; return user;
} catch (error) { } catch (error) {
if(error instanceof HttpException){ if (error instanceof HttpException) {
throw error throw error;
} }
throw new HttpException("An unexpected error occurred while fetching user information.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An unexpected error occurred while fetching user information.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
@ -292,8 +280,8 @@ export class UsersService {
return { message: "User deleted successfully." }; return { message: "User deleted successfully." };
} catch (error) { } catch (error) {
if(error instanceof HttpException){ if (error instanceof HttpException) {
throw error throw error;
} }
throw new HttpException("An error occurred while deleting the user.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An error occurred while deleting the user.", HttpStatus.INTERNAL_SERVER_ERROR);
} }

Loading…
Cancel
Save