Compare commits

..

No commits in common. '65e3afd64faab2eb3c7ae37bfc7cdac9161aa46a' and 'fc6794915185f61396b877bf7ba766b326ad8cff' have entirely different histories.

  1. 4
      migrations/20250104074851-create-user.js
  2. 110
      package-lock.json
  3. 2
      package.json
  4. 32
      src/redis/redis.service.ts
  5. 4
      src/shop/shop.module.ts
  6. 5
      src/users/entities/user.entity.ts
  7. 16
      src/users/guard/auth.guard.ts
  8. 4
      src/users/guard/auth.module.ts
  9. 26
      src/users/guard/jwt.strategy.ts
  10. 6
      src/users/guard/role.guard.ts
  11. 4
      src/users/users.controller.ts
  12. 33
      src/users/users.module.ts
  13. 62
      src/users/users.service.ts

@ -46,6 +46,10 @@ 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,7 +21,6 @@
"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",
@ -31,7 +30,6 @@
"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": {
@ -938,12 +936,6 @@
"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",
@ -1865,19 +1857,6 @@
"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",
@ -4001,15 +3980,6 @@
"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",
@ -4397,15 +4367,6 @@
"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",
@ -6142,30 +6103,6 @@
"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",
@ -7472,24 +7409,12 @@
"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",
@ -8880,27 +8805,6 @@
"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",
@ -9777,12 +9681,6 @@
"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",
@ -10721,16 +10619,16 @@
} }
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "11.0.5", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"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/esm/bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/v8-compile-cache-lib": { "node_modules/v8-compile-cache-lib": {

@ -32,7 +32,6 @@
"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",
@ -42,7 +41,6 @@
"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": {

@ -1,32 +0,0 @@
import { Injectable } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RedisService {
private readonly redis: Redis;
constructor() {
this.redis = new Redis({
host: 'localhost',
});
}
// Add a userId and JTI pair to the whitelist with TTL (Time-To-Live)
async addToWhitelist(userId: string, jti: string, ttl: number): Promise<void> {
const key = `${userId}:${jti}`;
await this.redis.set(key, 'valid', 'EX', ttl);
}
// Check if the userId and JTI pair exists in the whitelist
async isWhitelisted(userId: string, jti: string): Promise<boolean> {
const key = `${userId}:${jti}`;
const value = await this.redis.get(key);
return value === 'valid';
}
// Delete a userId and JTI pair from the whitelist
async deleteFromWhitelist(userId: string, jti: string): Promise<void> {
const key = `${userId}:${jti}`;
await this.redis.del(key);
}
}

@ -11,8 +11,6 @@ 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: [
@ -23,6 +21,6 @@ import { JwtStrategy } from "src/users/guard/jwt.strategy";
}), }),
], ],
controllers: [ShopController], controllers: [ShopController],
providers: [ShopService, RoleGuard, JwtAuthGuard,RedisService,JwtStrategy], providers: [ShopService, RoleGuard, JwtAuthGuard],
}) })
export class ShopModule {} export class ShopModule {}

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

@ -1,11 +1,9 @@
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,RedisService,JwtStrategy], providers: [JwtService, JwtAuthGuard],
exports: [JwtService, JwtAuthGuard], exports: [JwtService, JwtAuthGuard],
}) })
export class AuthModule {} export class AuthModule {}

@ -1,26 +0,0 @@
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, HttpException, Injectable, UnauthorizedException } from "@nestjs/common"; import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt"; import { JwtService } from "@nestjs/jwt";
@Injectable() @Injectable()
@ -23,9 +23,7 @@ export class RoleGuard implements CanActivate {
request.user = decoded; request.user = decoded;
return true; return true;
} catch (error) { } catch (error) {
if(error instanceof HttpException){ console.log(error);
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 user = req.user; const userId = req.user.id;
return this.usersService.logout(user); return this.usersService.logout(userId);
} }
//retrieve a user information //retrieve a user information
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)

@ -1,33 +1,30 @@
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: [ imports: [SequelizeModule.forFeature([User]),
SequelizeModule.forFeature([User]), PassportModule.register({ defaultStrategy: 'jwt' }),
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, RedisService, JwtStrategy], providers: [UsersService,JwtAuthGuard],
}) })
export class UsersModule {} export class UsersModule {}

@ -7,8 +7,6 @@ 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 {
@ -16,7 +14,6 @@ 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
@ -49,6 +46,7 @@ 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 },
{ {
@ -56,6 +54,9 @@ 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) {
@ -72,7 +73,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);
@ -80,9 +81,8 @@ 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, jti }, { 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",
@ -90,13 +90,17 @@ export class UsersService {
); );
const refreshToken = this.jwtService.sign( const refreshToken = this.jwtService.sign(
{ id: user.id, jti }, { id: user.id },
{ {
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) {
@ -124,29 +128,37 @@ export class UsersService {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND); throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
} }
const jti = uuidv4(); if (user.refreshToken !== refreshToken) {
throw new HttpException("Invalid refresh token.", HttpStatus.FORBIDDEN);
}
const accessToken = this.jwtService.sign( const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role, jti }, { 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",
}, },
); );
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(user: any) { async logout(userId: number) {
if (!userId) {
throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST);
}
try { try {
const jti = user.jti; const user = await this.userModel.findOne({ where: { id: userId } });
const userId = user.id; if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
}
await this.redisService.deleteFromWhitelist(userId, jti); await this.userModel.update({ refreshToken: null }, { where: { id: userId } });
return { message: "Logged out successfully." }; return { message: "Logout is successful." };
} catch (error) { } catch (error) {
throw new UnauthorizedException("An error occurred during logout."); throw new HttpException("An error occurred while logging out.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
//get information user method //get information user method
@ -154,7 +166,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);
@ -240,14 +252,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"] },
@ -257,8 +269,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);
} }
@ -280,8 +292,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