Pārlūkot izejas kodu

Bugfix: Refactor registration process: integrate class-validator and class-transformer for input validation, enhance error handling, and update user model structure.

emrecevik106 1 mēnesi atpakaļ
vecāks
revīzija
1d5e784cbe

+ 11 - 8
package.json

@@ -15,24 +15,27 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
+    "class-transformer": "0.5.1",
+    "class-validator": "0.15.1",
     "cors": "2.8.6",
     "dotenv": "17.4.2",
     "express": "5.2.1",
     "ioredis": "5.10.1",
     "jsonwebtoken": "9.0.3",
-    "mongoose": "9.5.0"
+    "mongoose": "9.5.0",
+    "reflect-metadata": "0.2.2"
   },
   "devDependencies": {
     "@eslint/js": "10.0.1",
-    "@types/bcrypt": "^6.0.0",
-    "@types/cors": "^2.8.19",
-    "@types/express": "^4.17.21",
-    "@types/jsonwebtoken": "^9.0.10",
+    "@types/bcrypt": "6.0.0",
+    "@types/cors": "2.8.19",
+    "@types/express": "4.17.21",
+    "@types/jsonwebtoken": "9.0.10",
     "@typescript-eslint/eslint-plugin": "5.0.0",
     "@typescript-eslint/parser": "5.0.0",
     "eslint": "8.0.1",
     "nodemon": "3.1.14",
-    "ts-node": "^10.9.2",
-    "typescript": "^5.5.4"
+    "ts-node": "10.9.2",
+    "typescript": "5.5.4"
   }
-}
+}

+ 42 - 78
src/actions/auth/register/index.ts

@@ -1,118 +1,82 @@
-import crypto from "crypto";
 import {
-    User
+    plainToInstance 
+} from "class-transformer";
+import {
+    validate 
+} from "class-validator";
+import {
+    User 
 } from "../../../models/User";
 import {
-    RegisterResult,
-    RegisterInput
+    RegisterInput, RegisterResult 
 } from "./types";
 
-const isValidEmail = (mail: string): boolean => {
-    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-    return emailRegex.test(mail);
-};
-
-const isValidPhone = (phone: string): boolean => {
-    const phoneRegex = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/;
-    return phoneRegex.test(phone);
-};
-
-const isStrongPassword = (password: string): { valid: boolean; message: string } => {
-    if (password.length < 8) return {
-        valid: false, message: "Şifre en az 8 karakter olmalı"
-    };
-
-    if (!/[A-Z]/.test(password)) return {
-        valid: false, message: "Şifre en az 1 büyük harf içermeli"
-    };
-
-    if (!/[a-z]/.test(password)) return {
-        valid: false, message: "Şifre en az 1 küçük harf içermeli"
-    };
-
-    if (!/[0-9]/.test(password)) return {
-        valid: false, message: "Şifre en az 1 sayı içermeli"
-    };
-
-    if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) return {
-        valid: false, message: "Şifre en az 1 özel karakter içermeli"
-    };
-
-    return {
-        valid: true,
-        message: ""
-    };
-};
+/* const isStrongPassword = (password: string): { valid: boolean; message: string } => {
+    if (password.length < 8) return { message: "Password must be at least 8 characters", valid: false };
+    if (!/[A-Z]/.test(password)) return { message: "Password must contain at least 1 uppercase letter", valid: false };
+    if (!/[a-z]/.test(password)) return { message: "Password must contain at least 1 lowercase letter", valid: false };
+    if (!/[0-9]/.test(password)) return { message: "Password must contain at least 1 number", valid: false };
+    if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) return { message: "Password must contain at least 1 special character", valid: false };
+    return { message: "", valid: true };
+}; */
 
 export const register = async (input: RegisterInput): Promise<RegisterResult> => {
-    const {
-        companyName, 
-        phoneNumber, 
-        firstName,
-        lastName, 
-        password,
-        mail, 
-    } = input;
+    const dto = plainToInstance(RegisterInput, input);
+    const errors = await validate(dto);
 
-    if (!isValidEmail(mail)) {
+    if (errors.length > 0) {
+        const message = Object.values(errors[0].constraints!)[0];
         return {
-            success: false, 
-            message: "Geçersiz email formatı", 
-            statusCode: 400 
+            code: 400,
+            message, 
         };
     }
 
-    if (!isValidPhone(phoneNumber)) {
-        return {
-            success: false, message: "Geçersiz telefon numarası formatı", statusCode: 400
-        };
-    }
-
-    const passwordCheck = isStrongPassword(password);
-
-    if (!passwordCheck.valid) {
-        return {
-            success: false, 
-            message: passwordCheck.message,
-            statusCode: 400
-        };
-    }
+    const {
+        companyName,
+        phoneNumber,
+        firstName, 
+        lastName, 
+        password, 
+        mail 
+    } = input;
 
     const existingUser = await User.findOne({
-        mail
+        mail 
     });
 
     if (existingUser) {
         return {
-            success: false, message: "Bu email zaten kayıtlı", statusCode: 409
+            message: "Email already in use", 
+            code: 409 
         };
     }
 
     const existingPhone = await User.findOne({
-        phoneNumber
+        phoneNumber 
     });
-
+    
     if (existingPhone) {
         return {
-            success: false, message: "Bu telefon numarası zaten kayıtlı", statusCode: 409
+            message: "Phone number already in use", 
+            code: 409 
         };
     }
-    const hashedPassword = crypto.createHash("md5").update(password).digest("hex");
 
     await User.create({
         fullName: `${firstName} ${lastName}`,
-        password: hashedPassword,
         phoneNumber,
         companyName,
         firstName,
         lastName,
-        mail,
+        password,
+        mail
     });
 
     return {
-        success: true,
-        message: "Kayıt başarılı",
-        statusCode: 201,
+        message: "Registration successful",
+        code: 201 
     };
 };
+
 export default register;

+ 32 - 15
src/actions/auth/register/types.ts

@@ -1,18 +1,35 @@
-export interface RegisterInput {
-  firstName: string;
-  lastName: string;
-  companyName: string;
-  mail: string;
-  phoneNumber: string;
-  password: string;
+import {
+    IsNotEmpty,
+    IsString,
+    IsEmail, 
+    Matches 
+} from "class-validator";
+
+export class RegisterInput {
+    @IsNotEmpty({ message: "First name is required" })
+    @IsString()
+        firstName!: string;
+
+    @IsNotEmpty({ message: "Last name is required" })
+    @IsString()
+        lastName!: string;
+
+    @IsNotEmpty({ message: "Company name is required" })
+    @IsString()
+        companyName!: string;
+
+    @IsEmail({}, { message: "Invalid email format" })
+        mail!: string;
+
+    @IsNotEmpty({ message: "Phone number is required" })
+    @Matches(/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/, { message: "Invalid phone number format" })
+        phoneNumber!: string;
+
+    @IsNotEmpty({ message: "Password is required" })
+        password!: string;
 }
 
 export interface RegisterResult {
-  success: boolean;
-  message: string;
-  statusCode: number;
-}
-/* export interface RegisterResult {
-  message: string;
-  code: number;
-} */
+    message: string;
+    code: number;
+}

+ 37 - 42
src/controllers/authController.ts

@@ -1,41 +1,43 @@
 import {
-    Request, Response
+    Request, Response 
 } from "express";
 import {
-    register as _register
+    register as _register 
 } from "../actions/auth/register";
 import {
-    login as _login
+    login as _login 
 } from "../actions/auth/login";
 
 export const register = async (req: Request, res: Response): Promise<void> => {
     try {
         const {
-            firstName, lastName, companyName, mail, phoneNumber, password
+            phoneNumber,
+            companyName,
+            firstName, 
+            lastName,
+            password,
+            mail
         } = req.body;
 
-        if (!firstName || !lastName || !companyName || !mail || !phoneNumber || !password) {
-            res.status(400).json({
-                statusCode: 400,
-                message: "Tüm alanlar zorunludur",
-            });
-            return;
-        }
-
         const result = await _register({
-            firstName, lastName, companyName, mail, phoneNumber, password
+            phoneNumber,
+            companyName,
+            firstName, 
+            lastName,
+            password,
+            mail
         });
 
-        res.status(result.statusCode).json({
-            code: result.statusCode,
+        res.status(result.code).json({
             message: result.message,
+            code: result.code
         });
 
     } catch (error) {
         console.error("Register error:", error);
         res.status(500).json({
-            statusCode: 500,
-            message: "Sunucu hatası",
+            message: "Internal server error",
+            code: 500, 
         });
     }
 };
@@ -43,42 +45,35 @@ export const register = async (req: Request, res: Response): Promise<void> => {
 export const login = async (req: Request, res: Response): Promise<void> => {
     try {
         const {
-            mail,
-            password
+            mail, password 
         } = req.body;
 
         if (!mail || !password) {
-            res.status(400)
-                .json({
-                    statusCode: 400,
-                    message: "Mail ve şifre zorunludur",
-                });
+            res.status(400).json({
+                statusCode: 400, message: "Mail ve şifre zorunludur" 
+            });
             return;
         }
 
         const result = await _login({
-            mail,
-            password
+            mail, password 
         });
 
-        res.status(result.statusCode)
-            .json({
-                statusCode: result.statusCode,
-                message: result.message,
-                ...(result.code && {
-                    code: result.code
-                }),
-                ...(result.payload && {
-                    payload: result.payload
-                }),
-            });
+        res.status(result.statusCode).json({
+            statusCode: result.statusCode,
+            message: result.message,
+            ...(result.code && {
+                code: result.code 
+            }),
+            ...(result.payload && {
+                payload: result.payload 
+            }),
+        });
 
     } catch (error) {
         console.error("Login error:", error);
-        res.status(500)
-            .json({
-                statusCode: 500,
-                message: "Sunucu hatası",
-            });
+        res.status(500).json({
+            statusCode: 500, message: "Sunucu hatası" 
+        });
     }
 };

+ 1 - 0
src/index.ts

@@ -1,5 +1,6 @@
 import express from "express";
 import cors from "cors";
+import "reflect-metadata";
 import dotenv from "dotenv";
 import {
     connectDB 

+ 46 - 26
src/models/User.ts

@@ -3,57 +3,77 @@ import mongoose, {
 } from "mongoose";
 
 export interface IUser extends Document {
-  fullName: string;
-  firstName: string;
-  lastName: string;
-  companyName: string;
-  mail: string;
-  phoneNumber: string;
-  password: string;
-  isActive: boolean;
-  isMailVerified: boolean;
-  isPhoneVerified: boolean;
-  isDeleted: boolean;
-  refreshToken?: string;
-  deleteAccountDate?: Date;
-  createdAt: Date;
-  updatedAt: Date;
+    deleteAccountDate?: Date;
+    isPhoneVerified: boolean;
+    isMailVerified: boolean;
+    refreshToken?: string;
+    companyName: string;
+    phoneNumber: string;
+    isDeleted: boolean;
+    firstName: string;
+    isActive: boolean;
+    fullName: string;
+    lastName: string;
+    password: string;
+    createdAt: Date;
+    updatedAt: Date;
+    mail: string;
 }
 
 const UserSchema = new Schema<IUser>(
     {
         fullName: {
-            type: String, required: true, trim: true 
+            required: true,
+            type: String, 
+            trim: true 
         },
         firstName: {
-            type: String, required: true, trim: true 
+            required: true,
+            type: String, 
+            trim: true 
         },
         lastName: {
-            type: String, required: true, trim: true 
+            required: true, 
+            type: String, 
+            trim: true 
         },
         companyName: {
-            type: String, required: true, trim: true 
+            required: true, 
+            type: String, 
+            trim: true 
         },
         mail: {
-            type: String, required: true, unique: true, lowercase: true, trim: true 
+            lowercase: true, 
+            required: true, 
+            type: String, 
+            unique: true, 
+            trim: true 
         },
         phoneNumber: {
-            type: String, required: true, unique: true, trim: true 
+            required: true, 
+            type: String, 
+            unique: true, 
+            trim: true 
         },
         password: {
-            type: String, required: true 
+            required: true, 
+            type: String
         },
         isActive: {
-            type: Boolean, default: true 
+            type: Boolean, 
+            default: true 
         },
         isMailVerified: {
-            type: Boolean, default: false 
+            type: Boolean, 
+            default: false 
         },
         isPhoneVerified: {
-            type: Boolean, default: false 
+            type: Boolean, 
+            default: false 
         },
         isDeleted: {
-            type: Boolean, default: false 
+            type: Boolean, 
+            default: false 
         },
         refreshToken: {
             type: String 

+ 3 - 1
tsconfig.json

@@ -103,6 +103,8 @@
 
     /* Completeness */
     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
-    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
+    "skipLibCheck": true,
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true                        /* Skip type checking all .d.ts files. */
   }
 }

+ 34 - 0
yarn.lock

@@ -232,6 +232,11 @@
     "@types/node" "*"
     "@types/send" "<1"
 
+"@types/validator@^13.15.3":
+  version "13.15.10"
+  resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.15.10.tgz#742b77ec34d58554b94a76a14cef30d59e3c16b9"
+  integrity sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==
+
 "@types/webidl-conversions@*":
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz#1306dbfa53768bcbcfc95a1c8cde367975581859"
@@ -500,6 +505,20 @@ chokidar@^3.5.2:
   optionalDependencies:
     fsevents "~2.3.2"
 
+class-transformer@^0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336"
+  integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==
+
+class-validator@^0.15.1:
+  version "0.15.1"
+  resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.15.1.tgz#002600c101bcebb16e7240870cb50535340c9600"
+  integrity sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==
+  dependencies:
+    "@types/validator" "^13.15.3"
+    libphonenumber-js "^1.11.1"
+    validator "^13.15.22"
+
 cluster-key-slot@^1.1.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
@@ -1212,6 +1231,11 @@ levn@^0.4.1:
     prelude-ls "^1.2.1"
     type-check "~0.4.0"
 
+libphonenumber-js@^1.11.1:
+  version "1.12.42"
+  resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.12.42.tgz#794db17340b0a04f8d193b65dc7998e3e6e1088c"
+  integrity sha512-oKQFPTibqQwZZkChCDVMFVJXMZdyJNqDWZWYNn8BgyAaK/6yFJEowxCY0RVFirRyWP63hMRuKlkSEd9qlvbWXg==
+
 lodash.defaults@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
@@ -1548,6 +1572,11 @@ redis-parser@^3.0.0:
   dependencies:
     redis-errors "^1.0.0"
 
+reflect-metadata@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b"
+  integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==
+
 regexpp@^3.1.0, regexpp@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
@@ -1865,6 +1894,11 @@ v8-compile-cache@^2.0.3:
   resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128"
   integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==
 
+validator@^13.15.22:
+  version "13.15.35"
+  resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.35.tgz#81cf455c51f15b69d8d340be5914f3fab00dbf7f"
+  integrity sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==
+
 vary@^1, vary@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"