diff --git a/.vscode/settings.json b/.vscode/settings.json index 88ee663..7df7064 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,7 @@ { - "cSpell.words": ["zarinpal"] + "cSpell.words": [ + "uuidv", + "zarinpal" + ], + "CodeGPT.apiKey": "CodeGPT Plus Beta" } diff --git a/package-lock.json b/package-lock.json index c178949..349a862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "google-auth-library": "^9.15.1", "ioredis": "^5.4.2", "passport": "^0.7.0", "passport-jwt": "^4.0.1", @@ -3425,7 +3426,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -3462,6 +3462,15 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", "license": "MIT" }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5242,6 +5251,12 @@ "type": "^2.7.2" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -5717,6 +5732,70 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/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/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5840,6 +5919,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -5865,6 +5982,40 @@ "dev": true, "license": "MIT" }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6290,7 +6441,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7280,6 +7430,15 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", diff --git a/package.json b/package.json index 653e5ab..9d373d6 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "google-auth-library": "^9.15.1", "ioredis": "^5.4.2", "passport": "^0.7.0", "passport-jwt": "^4.0.1", diff --git a/src/shop/shop.service.ts b/src/shop/shop.service.ts index b25a4e3..aa3081d 100644 --- a/src/shop/shop.service.ts +++ b/src/shop/shop.service.ts @@ -29,7 +29,7 @@ export class ShopService { const sandboxMode = true; return ZarinpalCheckout.create(merchantId, sandboxMode); } - ///////////////////////////////////////////products////////////////////////////////////////////// + /////////////////////////////////////////products////////////////////////////////////////////// // create a new product async create(createProductDto: CreateProductDto): Promise { try { @@ -203,7 +203,7 @@ export class ShopService { throw new HttpException("An unexpected error occurred while deleting the product.", HttpStatus.INTERNAL_SERVER_ERROR); } } - ////////////////////////////////////////////cart///////////////////////////////////////////////// + //////////////////////////////////////////cart///////////////////////////////////////////////// //create and add item to a cart async createAndAddItemToCart(addToCartDto: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> { const { userId, productId, quantity } = addToCartDto; diff --git a/src/users/guard/auth.guard.ts b/src/users/guard/auth.guard.ts index 2566e1c..54e4c1a 100644 --- a/src/users/guard/auth.guard.ts +++ b/src/users/guard/auth.guard.ts @@ -27,7 +27,6 @@ export class JwtAuthGuard implements CanActivate { request.user = decoded; return true; } catch (error) { - console.log(error) throw new UnauthorizedException("Invalid or expired token"); } } diff --git a/src/users/guard/jwt.strategy.ts b/src/users/guard/jwt.strategy.ts index 3cf9b78..266029b 100644 --- a/src/users/guard/jwt.strategy.ts +++ b/src/users/guard/jwt.strategy.ts @@ -16,7 +16,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) { const { jti, id } = payload; const response = await this.redisService.isWhitelisted(id, jti) - console.log(response) if (!response) { throw new UnauthorizedException('Token is not whitelisted'); } diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index da56e63..4af0f75 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,45 +1,50 @@ -import { Controller, Post, Body, UseGuards, Get, Request, Put, Param, HttpException, HttpStatus, Delete, Query } from "@nestjs/common"; +import { Controller, Get, Query, Post, Body, UseGuards, Request, Put, Param, HttpException, HttpStatus, Delete } from "@nestjs/common"; import { UsersService } from "./users.service"; -import { User } from "./entities/user.entity"; import { CreateUserDto } from "./dto/create-user.dto"; import { LoginUserDto } from "./dto/login-user.dto"; -import { JwtAuthGuard } from "src/users/guard/auth.guard"; import { UpdateUserDto } from "./dto/update-user.dto"; -import { RoleGuard } from "src/users/guard/role.guard"; +import { JwtAuthGuard } from "./guard/auth.guard"; +import { RoleGuard } from "./guard/role.guard"; +import { User } from "./entities/user.entity"; @Controller("user") export class UsersController { constructor(private readonly usersService: UsersService) {} - //register + // Register @Post("register") async register(@Body() createUserDto: CreateUserDto) { return this.usersService.register(createUserDto); } - //login + + // Login @Post("login") async login(@Body() loginUserDto: LoginUserDto) { return this.usersService.login(loginUserDto); } - //get access token + + // Get new access token @Post("new-token") async newAccessToken(@Body("token") token: string) { return this.usersService.newAccessToken(token); } - //logout user + + // Logout user @UseGuards(JwtAuthGuard) @Get("logout") async logout(@Request() req) { const user = req.user; return this.usersService.logout(user); } - //retrieve a user information + + // Retrieve user information @UseGuards(JwtAuthGuard) @Get() async getProfile(@Request() req): Promise { const userId = req.user.id; return this.usersService.getProfile(userId); } - //edit user profile + + // Edit user profile @UseGuards(JwtAuthGuard) @Put() async editProfile(@Request() req, @Body() updateUserDto: UpdateUserDto) { @@ -47,8 +52,10 @@ export class UsersController { const { refreshToken, ...sanitizedDto } = updateUserDto; return this.usersService.editProfile(userId, sanitizedDto); } - /////////////////////////////////////admin access endpoints///////////////////////////////////////////////////////// - //get users list (admin) + + ///////////////////////////////////// Admin access endpoints ///////////////////////////////////////// + + // Get users list (admin) @UseGuards(RoleGuard) @Get('users-list') async findAll( @@ -57,13 +64,15 @@ export class UsersController { ){ return this.usersService.findAll(page, limit); } - // get a specific user info (admin) + + // Get specific user info (admin) @UseGuards(RoleGuard) @Get("userinfo/:id") async findSpecificUserInfoByUser(@Param("id") id) { return this.usersService.findSpecificUserInfoByUser(id); } - // delete a specific user (admin) + + // Delete specific user (admin) @UseGuards(RoleGuard) @Delete(":id?") async deleteUser(@Param("id") id) { @@ -72,4 +81,4 @@ export class UsersController { } return this.usersService.deleteUser(id); } -} +} \ No newline at end of file diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 11a930b..8b309e9 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus, Injectable, UnauthorizedException, BadRequestException, NotFoundException, UseGuards } from "@nestjs/common"; +import { HttpException, HttpStatus, Injectable, UnauthorizedException, BadRequestException, NotFoundException } from "@nestjs/common"; import { InjectModel } from "@nestjs/sequelize"; import { User } from "./entities/user.entity"; import * as bcrypt from "bcrypt"; @@ -9,6 +9,7 @@ import { LoginUserDto } from "./dto/login-user.dto"; import { UpdateUserDto } from "./dto/update-user.dto"; import { v4 as uuidv4 } from "uuid"; import { RedisService } from "src/redis/redis.service"; +import { Op } from "sequelize"; @Injectable() export class UsersService { @@ -17,45 +18,35 @@ export class UsersService { private readonly jwtService: JwtService, private readonly configService: ConfigService, private readonly redisService: RedisService, - ) {} - + ) { + } // Register method async register(createUserDto: CreateUserDto) { try { createUserDto.password = await bcrypt.hash(createUserDto.password, parseInt(process.env.BCRYPT_SALT_ROUNDS || "10", 10)); - const emailExists = await this.userModel.findOne({ - where: { email: createUserDto.email }, - }); - if (emailExists) { - throw new BadRequestException("Email is already registered."); - } - - const phoneExists = await this.userModel.findOne({ - where: { phoneNumber: createUserDto.phoneNumber }, + const existingUser = await this.userModel.findOne({ + where: { + [Op.or]: [{ email: createUserDto.email }, { phoneNumber: createUserDto.phoneNumber }, { username: createUserDto.username }], + }, }); - if (phoneExists) { - throw new BadRequestException("Phone number is already registered."); - } - const usernameExists = await this.userModel.findOne({ - where: { username: createUserDto.username }, - }); - if (usernameExists) { - throw new BadRequestException("Username is already registered."); + if (existingUser) { + if (existingUser.email === createUserDto.email) { + throw new BadRequestException("Email is already registered."); + } + if (existingUser.phoneNumber === createUserDto.phoneNumber) { + throw new BadRequestException("Phone number is already registered."); + } + if (existingUser.username === createUserDto.username) { + throw new BadRequestException("Username is already registered."); + } } await this.userModel.create(createUserDto); const user = await this.userModel.findOne({ where: { email: createUserDto.email }, }); - const refreshToken = this.jwtService.sign( - { id: user.id }, - { - secret: this.configService.get("JWT_REFRESH_SECRET"), - expiresIn: this.configService.get("JWT_REFRESH_EXPIRES") || "7d", - }, - ); return { message: "User registered successfully." }; } catch (error) { if (error instanceof HttpException) { @@ -123,7 +114,7 @@ export class UsersService { if (!user) { throw new HttpException("User not found.", HttpStatus.NOT_FOUND); } - + const jti = uuidv4(); const accessToken = this.jwtService.sign( { id: user.id, role: user.role, jti },