فهرست منبع

Create route for logging in.

Lee Morgan 1 سال پیش
والد
کامیت
9637ee8e05
6فایلهای تغییر یافته به همراه186 افزوده شده و 3 حذف شده
  1. 1 1
      app.js
  2. 50 1
      controllers/user.js
  3. 18 0
      documentation/bruno/User/Login.bru
  4. 113 0
      package-lock.json
  5. 1 0
      package.json
  6. 3 1
      routes/user.js

+ 1 - 1
app.js

@@ -11,7 +11,7 @@ import otherRoutes from "./routes/other.js";
 import userRoutes from "./routes/user.js";
 
 let mongoString = "mongodb://127.0.0.1/workout";
-if(process.env.COOKIE_SECRET){
+if(process.env.NODE_ENV === "production"){
     mongoString = `mongodb://workout:${process.env.MONGODB_PASS}@127.0.0.1:27017/workout?authSource=admin`;
 }
 

+ 50 - 1
controllers/user.js

@@ -4,6 +4,7 @@ import {HttpError} from "../HttpError.js";
 import validate from "../validation/user.js";
 import bcrypt from "bcrypt";
 import crypto from "crypto";
+import jwt from "jsonwebtoken";
 
 const createRoute = async (req, res, next)=>{
     try{
@@ -14,6 +15,17 @@ const createRoute = async (req, res, next)=>{
     }catch(e){next(e)}
 }
 
+const loginRoute = async (req, res, next)=>{
+    try{
+        const user = await User.findOne({email: req.body.email.toLowerCase()});
+        if(user === null) throw new HttpError(400, "Invalid credentials");
+        await comparePass(req.body.pass, user.password);
+        const token = createToken(user);
+        setCookie(res, "userToken", token);
+        res.json(responseUser(user));
+    }catch(e){next(e)}
+}
+
 /*
  Create a new User object
  @param {Object} - Request body with user data
@@ -42,6 +54,42 @@ const hashPass = async (password)=>{
     return await bcrypt.hash(password, 10);
 }
 
+/*
+ Throw error if password does not match
+ @param {String} password - Password to compare
+ @param {String} hashedPass - Hashed password from database
+ */
+const comparePass = async (password, hashedPass)=>{
+    const result = await bcrypt.compare(password, hashedPass);
+    if(result !== true) throw new HttpError(400, "Invalid credentials");
+}
+
+/*
+ Create an authorization token for the user
+ @param {User} user - User object
+ @return {String} - User auth token
+ */
+const createToken = (user)=>{
+    return jwt.sign({
+        id: user._id,
+        token: user.token
+    }, process.env.JWT_SECRET);
+}
+
+/*
+ Set a cookie
+ @param {Response} res - Response object containing the 'cookie' method
+ @param {String} name - Name of the cookie to set
+ @param {String} value - Value of the cookie
+ */
+const setCookie = (res, name, value)=>{
+    res.cookie(name, value, {
+        httpOnly: true,
+        secure: process.env.NODE_ENV === "production",
+        signed: true
+    });
+}
+
 /*
  Create user object for sending to frontend
  @param {User} - User object
@@ -65,5 +113,6 @@ const createUuid = ()=>{
 }
 
 export {
-    createRoute
+    createRoute,
+    loginRoute
 }

+ 18 - 0
documentation/bruno/User/Login.bru

@@ -0,0 +1,18 @@
+meta {
+  name: Login
+  type: http
+  seq: 2
+}
+
+put {
+  url: http://localhost:8000/user/login
+  body: json
+  auth: none
+}
+
+body:json {
+  {
+    "email": "lee@leemorgan.dev",
+    "pass": "leerobertmorgan"
+  }
+}

+ 113 - 0
package-lock.json

@@ -15,6 +15,7 @@
         "cors": "^2.8.5",
         "esbuild": "^0.25.4",
         "express": "^5.1.0",
+        "jsonwebtoken": "^9.0.2",
         "mongoose": "^8.14.3"
       }
     },
@@ -498,6 +499,12 @@
         "node": ">=16.20.1"
       }
     },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+      "license": "BSD-3-Clause"
+    },
     "node_modules/bytes": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -701,6 +708,15 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -1009,6 +1025,49 @@
       "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
       "license": "MIT"
     },
+    "node_modules/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+      "license": "MIT",
+      "dependencies": {
+        "jws": "^3.2.2",
+        "lodash.includes": "^4.3.0",
+        "lodash.isboolean": "^3.0.3",
+        "lodash.isinteger": "^4.0.4",
+        "lodash.isnumber": "^3.0.3",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.once": "^4.0.0",
+        "ms": "^2.1.1",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/jwa": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
+      "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-equal-constant-time": "^1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "license": "MIT",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "node_modules/kareem": {
       "version": "2.6.3",
       "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
@@ -1018,6 +1077,48 @@
         "node": ">=12.0.0"
       }
     },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+      "license": "MIT"
+    },
     "node_modules/math-intrinsics": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -1381,6 +1482,18 @@
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
       "license": "MIT"
     },
+    "node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/send": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "cors": "^2.8.5",
     "esbuild": "^0.25.4",
     "express": "^5.1.0",
+    "jsonwebtoken": "^9.0.2",
     "mongoose": "^8.14.3"
   }
 }

+ 3 - 1
routes/user.js

@@ -1,7 +1,9 @@
 import {
-    createRoute
+    createRoute,
+    loginRoute
 } from "../controllers/user.js";
 
 export default (app)=>{
     app.post("/user", createRoute);
+    app.put("/user/login", loginRoute);
 }