From 5fa0de01a6a894220d51b54f54375c28ce4913f6 Mon Sep 17 00:00:00 2001
From: Sarthak Shrivastava <sarthaks@vt.edu>
Date: Fri, 24 Nov 2023 22:50:13 -0500
Subject: [PATCH] work in progress auth, login, routing but it builds :)

---
 .../components/{useToken.js => useToken.jsx}  |  17 ++-
 .../components/user/AccountInformation.css    |  38 ++++++
 .../components/user/AccountInformation.jsx    | 123 ++++++++++++++++++
 .../src/components/user/DeleteUser.jsx        |  15 +--
 .../src/components/user/Login.css             |  44 +++++++
 .../src/components/user/Login.jsx             |  43 ++++--
 pom.xml                                       |  17 ++-
 .../accessingdatamysql/MainController.java    |  24 +++-
 .../auth/AuthController.java                  |  25 +++-
 .../example/accessingdatamysql/auth/JWT.java  |  74 +++++++++++
 10 files changed, 390 insertions(+), 30 deletions(-)
 rename inventory-manager/src/components/{useToken.js => useToken.jsx} (56%)
 create mode 100644 inventory-manager/src/components/user/AccountInformation.css
 create mode 100644 inventory-manager/src/components/user/AccountInformation.jsx
 create mode 100644 inventory-manager/src/components/user/Login.css
 create mode 100644 src/main/java/com/example/accessingdatamysql/auth/JWT.java

diff --git a/inventory-manager/src/components/useToken.js b/inventory-manager/src/components/useToken.jsx
similarity index 56%
rename from inventory-manager/src/components/useToken.js
rename to inventory-manager/src/components/useToken.jsx
index b15cd6a..5c0106a 100644
--- a/inventory-manager/src/components/useToken.js
+++ b/inventory-manager/src/components/useToken.jsx
@@ -1,13 +1,24 @@
 import { useState } from 'react';
+import Axios from "axios";
 
 export default function useToken() {
   const getToken = () => {
     const tokenString = sessionStorage.getItem('token');
     if (!tokenString)
         return null;
+    console.log(tokenString);
     const userToken = JSON.parse(tokenString);
-    if (userToken.user != null)
-        return tokenString;
+    console.log(userToken);
+    if (userToken.jwt != null)
+    {
+      Axios.post("http://localhost:8080/auth/verify", {
+        jwt: userToken.jwt
+      }).then((response) => {
+        console.log(response);
+        if (response.user)
+          return response;
+      });
+    }
     return null;
   };
 
@@ -15,7 +26,7 @@ export default function useToken() {
 
   const saveToken = userToken => {
     sessionStorage.setItem('token', JSON.stringify(userToken.data));
-    setToken(userToken.token);
+    setToken(userToken.data);
   };
 
   return {
diff --git a/inventory-manager/src/components/user/AccountInformation.css b/inventory-manager/src/components/user/AccountInformation.css
new file mode 100644
index 0000000..ce3388d
--- /dev/null
+++ b/inventory-manager/src/components/user/AccountInformation.css
@@ -0,0 +1,38 @@
+/* AccountInformation.css */
+
+.account-info-container {
+  max-width: 400px;
+  margin: 0 auto;
+  padding: 20px;
+  border: 1px solid #ccc;
+  border-radius: 5px;
+}
+
+.info-form {
+  display: flex;
+  flex-direction: column;
+}
+
+label {
+  margin-bottom: 8px;
+}
+
+input {
+  padding: 8px;
+  margin-bottom: 16px;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+}
+
+button {
+  padding: 10px;
+  background-color: #007bff;
+  color: #fff;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+button:hover {
+  background-color: #0056b3;
+}
diff --git a/inventory-manager/src/components/user/AccountInformation.jsx b/inventory-manager/src/components/user/AccountInformation.jsx
new file mode 100644
index 0000000..7884328
--- /dev/null
+++ b/inventory-manager/src/components/user/AccountInformation.jsx
@@ -0,0 +1,123 @@
+import React, { useState, useEffect } from "react";
+import Axios from "axios";
+import PropTypes from "prop-types";
+import './AccountInformation.css'; // Import your external CSS file
+
+const AccountInformation = ({ token }) => {
+  const [userInfo, setUserInfo] = useState({
+    fname: "",
+    lname: "",
+    password: "",
+    phoneNumber: "",
+    email: "",
+  });
+
+  useEffect(() => {
+    // Fetch user information when the component mounts
+    getUserInfo();
+  }, []); // Empty dependency array ensures the effect runs once
+
+  const getUserInfo = async () => {
+    try {
+      const response = await Axios.post(
+        "http://localhost:8080/user/user",
+        { jwt: token.jwt }
+      );
+      setUserInfo(response.data);
+    } catch (error) {
+      console.error("Error fetching user information:", error);
+    }
+  };
+
+  const handleUpdate = async () => {
+    try {
+      const response = await Axios.put(
+        "http://localhost:8080/user/update",
+        {
+          fname: userInfo.fname,
+          lname: userInfo.lname,
+          password: userInfo.password,
+          phoneNumber: userInfo.phoneNumber,
+          email: userInfo.email,
+          jwt: token.jwt,
+        }
+      );
+      setUserInfo(response.data);
+      console.log("User information updated successfully");
+    } catch (error) {
+      console.error("Error updating user information:", error);
+    }
+  };
+
+  const handleChange = (e) => {
+    const { name, value } = e.target;
+    setUserInfo((prevUserInfo) => ({
+      ...prevUserInfo,
+      [name]: value,
+    }));
+  };
+
+  return (
+    <div className="account-info-container">
+      <h2>Account Information</h2>
+      <div className="info-form">
+        <label htmlFor="fname">First Name</label>
+        <input
+          type="text"
+          id="fname"
+          name="fname"
+          value={userInfo.fname}
+          onChange={handleChange}
+        />
+
+        <label htmlFor="lname">Last Name</label>
+        <input
+          type="text"
+          id="lname"
+          name="lname"
+          value={userInfo.lname}
+          onChange={handleChange}
+        />
+
+        <label htmlFor="password">Password</label>
+        <input
+          type="password"
+          id="password"
+          name="password"
+          value={userInfo.password}
+          onChange={handleChange}
+        />
+
+        <label htmlFor="phoneNumber">Phone Number</label>
+        <input
+          type="text"
+          id="phoneNumber"
+          name="phoneNumber"
+          value={userInfo.phoneNumber || ""}
+          onChange={handleChange}
+        />
+
+        <label htmlFor="email">Email</label>
+        <input
+          type="email"
+          id="email"
+          name="email"
+          value={userInfo.email}
+          onChange={handleChange}
+        />
+
+        <button type="button" onClick={handleUpdate}>
+          Update Information
+        </button>
+      </div>
+    </div>
+  );
+};
+
+AccountInformation.propTypes = {
+  token: PropTypes.shape({
+    jwt: PropTypes.string.isRequired,
+  }).isRequired,
+};
+
+export default AccountInformation;
diff --git a/inventory-manager/src/components/user/DeleteUser.jsx b/inventory-manager/src/components/user/DeleteUser.jsx
index 75c1de1..3121abb 100644
--- a/inventory-manager/src/components/user/DeleteUser.jsx
+++ b/inventory-manager/src/components/user/DeleteUser.jsx
@@ -4,17 +4,16 @@ import useToken from "../useToken";
 
 export const DeleteUser = (token) => {
   const [email, setEmail] = useState("");
-  console.log(JSON.parse(token.token).user);
+  // console.log(JSON.parse(token.token).user);
+  // console.log(token.token.user);
+  console.log(token);
   const handleSubmit = (e) => {
     e.preventDefault();
-
-    Axios.delete("http://localhost:8080/user/delete", {
-      headers: {},
-      data: {
-        email: JSON.parse(token.token).user
-      }
+    Axios.post("http://localhost:8080/user/delete", {
+      jwt: token.token.jwt
     }).then((response) => {
-      console.log(response);
+      // console.log(response);
+      // if (response.data.email == "")
     });
     };
 
diff --git a/inventory-manager/src/components/user/Login.css b/inventory-manager/src/components/user/Login.css
new file mode 100644
index 0000000..330234b
--- /dev/null
+++ b/inventory-manager/src/components/user/Login.css
@@ -0,0 +1,44 @@
+/* Login.css */
+
+.auth-form-container {
+  max-width: 400px;
+  margin: 0 auto;
+  padding: 20px;
+  border: 1px solid #ccc;
+  border-radius: 5px;
+}
+
+.login-form {
+  display: flex;
+  flex-direction: column;
+}
+
+label {
+  margin-bottom: 8px;
+}
+
+input {
+  padding: 8px;
+  margin-bottom: 16px;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+}
+
+button {
+  padding: 10px;
+  background-color: #007bff;
+  color: #fff;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+button:hover {
+  background-color: #0056b3;
+}
+
+/* Add this style for error messages */
+.error-message {
+  color: red;
+  margin-top: 10px;
+}
\ No newline at end of file
diff --git a/inventory-manager/src/components/user/Login.jsx b/inventory-manager/src/components/user/Login.jsx
index f80c507..440067e 100644
--- a/inventory-manager/src/components/user/Login.jsx
+++ b/inventory-manager/src/components/user/Login.jsx
@@ -1,21 +1,39 @@
 import React, { useState } from "react";
 import Axios from "axios";
 import PropTypes from "prop-types";
-export const Login = ({setToken}) => {
+import './Login.css'; // Import your external CSS file
+
+export const Login = ({ setToken }) => {
   const [email, setEmail] = useState();
   const [pass, setPass] = useState();
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState("");
+
   const handleSubmit = (e) => {
     e.preventDefault();
-    console.log(email);
-    console.log(pass);
+    setLoading(true);
+    setError("");
     Axios.post("http://localhost:8080/auth/login", {
       email: email,
       password: pass,
     }).then((response) => {
-      // console.log(typeof(response));
-      setToken(response);
-      console.log(response.data);
-    });
+      console.log(response);
+      // console.log(response.data.login);
+      if (response.data.login != "bad password")
+      {
+        setToken(response);
+      }
+      else 
+      {
+        setError("Invalid email or password. Please try again.");
+      }
+    }).catch((error) => {
+        setError("An error occurred. Please try again later.");
+        console.error("Login error:", error);
+      })
+      .finally(() => {
+        setLoading(false);
+      });
   };
 
   return (
@@ -40,12 +58,19 @@ export const Login = ({setToken}) => {
           id="password"
           name="password"
         />
-        <button type="submit">Log In</button>
+        {loading ? (
+          <p>Loading...</p>
+        ) : (
+          <button type="submit">Log In</button>
+        )}
+        {error && <p className="error-message">{error}</p>}
       </form>
     </div>
   );
-};
+}; 
+
 Login.propTypes = {
   setToken: PropTypes.func.isRequired
 };
+
 export default Login;
diff --git a/pom.xml b/pom.xml
index 6370c92..a030a6c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,7 +63,22 @@
 			<artifactId>spring-boot-devtools</artifactId>
 			<optional>true</optional>
 		</dependency>
-
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt</artifactId>
+			<version>0.9.1</version>
+		</dependency>
+		<!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
+		<dependency>
+			<groupId>javax.xml.bind</groupId>
+			<artifactId>jaxb-api</artifactId>
+			<version>2.3.1</version>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-configuration-processor</artifactId>
+			<optional>true</optional>
+		</dependency>	
 	</dependencies>
 
 	<build>
diff --git a/src/main/java/com/example/accessingdatamysql/MainController.java b/src/main/java/com/example/accessingdatamysql/MainController.java
index 9e22665..d21ae88 100644
--- a/src/main/java/com/example/accessingdatamysql/MainController.java
+++ b/src/main/java/com/example/accessingdatamysql/MainController.java
@@ -7,6 +7,9 @@ import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.Map;
+import java.util.HashMap;
+import io.jsonwebtoken.Claims;
+import com.example.accessingdatamysql.auth.AuthController;
 import java.util.Optional;
 
 @CrossOrigin
@@ -67,21 +70,28 @@ public class MainController {
         return userRepository.findById(email);
     }
 
-    @DeleteMapping(path = "/delete")
-    public @ResponseBody User deleteUser(@RequestBody Map<String, String> json)
+    @PostMapping(path = "/delete")
+    @ResponseBody 
+    public User deleteUser(@RequestBody Map<String, String> json)
     {
-        User found = null;
-        String email = json.get("email");
+        User found = new User();
+        AuthController au = new AuthController();
+        Map<String, String> res =  au.verify(json); // if the jwt token could not be verified
+        if (res.containsKey("login") && res.get("login").equals("failed"))
+        {
+            found.setEmail("failed");
+            return found;
+        }
+        String email = res.get("user");
         Optional<User> optionalUser = userRepository.findById(email);
-
         if (optionalUser.isPresent())
         {
             System.out.println("in if statement");
             found = optionalUser.get();
             userRepository.deleteById(email);
-
             return found;
         }
-        return null;
+        found.setEmail("not found");
+        return found;
     }
 }
\ No newline at end of file
diff --git a/src/main/java/com/example/accessingdatamysql/auth/AuthController.java b/src/main/java/com/example/accessingdatamysql/auth/AuthController.java
index 657ca54..fe30cec 100644
--- a/src/main/java/com/example/accessingdatamysql/auth/AuthController.java
+++ b/src/main/java/com/example/accessingdatamysql/auth/AuthController.java
@@ -1,6 +1,8 @@
 package com.example.accessingdatamysql.auth;
 import org.springframework.web.bind.annotation.RequestMapping;
+import com.example.accessingdatamysql.auth.JWT;
 
+import io.jsonwebtoken.Claims;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.core.MediaType;
@@ -43,12 +45,31 @@ public class AuthController {
             if (usr.getEmail().equals(json.get("email")) && usr.getPassword().equals(json.get("password")))
             {
                 res.put("user", user.get().getEmail());
+                //give them a token
+                res.put("jwt", JWT.createJWT("id", "issuer", "sarthaks@vt.edu", 99999999));
                 return res;
             }
-            res.put("login", "failed");
+            res.put("login", "bad password");
             return res;
         }
-        res.put("login", "failed");
+        res.put("login", "bad username");
+        return res;
+    }
+
+    @PostMapping(path="/verify")
+    public @ResponseBody Map<String, String> verify(@RequestBody Map<String, String> json)
+    {
+        Map<String, String> res = new HashMap<String, String>();
+        if (json.containsKey("jwt"))
+        {
+            Claims claim = JWT.decodeJWT(json.get("jwt"));
+            if (claim != null)
+                res.put("user", claim.getSubject());
+        }
+        else
+        {
+            res.put("login", "failed");
+        }
         return res;
     }
 }
diff --git a/src/main/java/com/example/accessingdatamysql/auth/JWT.java b/src/main/java/com/example/accessingdatamysql/auth/JWT.java
new file mode 100644
index 0000000..d2093a8
--- /dev/null
+++ b/src/main/java/com/example/accessingdatamysql/auth/JWT.java
@@ -0,0 +1,74 @@
+package com.example.accessingdatamysql.auth;
+
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+import java.security.Key;
+
+import io.jsonwebtoken.*;
+
+import java.util.Date;
+import org.springframework.stereotype.Component;
+
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.Claims;
+
+/*
+    A simple static class that is used to create and decode JWTs.
+ */
+public class JWT{
+
+    
+    // The secret key. This should be in a property file NOT under source
+    // control and not hard coded in real life. We're putting it here for
+    // simplicity.
+    private static String SECRET_KEY = "secret dev key";
+    private static final long DEFAULT_TTL = 99999;
+
+    //Sample method to construct a JWT
+    public static String createJWT(String id, String issuer, String subject, long ttlMillis) {
+
+        //The JWT signature algorithm we will be using to sign the token
+        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+
+        long nowMillis = System.currentTimeMillis();
+        Date now = new Date(nowMillis);
+
+        //We will sign our JWT with our ApiKey secret
+        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
+        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
+
+        //Let's set the JWT Claims
+        JwtBuilder builder = Jwts.builder().setId(id)
+                .setIssuedAt(now)
+                .setSubject(subject)
+                .setIssuer(issuer)
+                .signWith(signatureAlgorithm, signingKey);
+
+        //if it has been specified, let's add the expiration
+        if (ttlMillis >= 0) {
+            long expMillis = nowMillis + ttlMillis + DEFAULT_TTL; // pad with default amount 
+            Date exp = new Date(expMillis);
+            builder.setExpiration(exp);
+        }
+
+        //Builds the JWT and serializes it to a compact, URL-safe string
+        return builder.compact();
+    }
+
+    public static Claims decodeJWT(String jwt) {
+
+        //This line will throw an exception if it is not a signed JWS (as expected)
+        try 
+        {
+            Claims claims = Jwts.parser()
+                    .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY))
+                    .parseClaimsJws(jwt).getBody();
+            return claims;
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+   }
+
+}
\ No newline at end of file
-- 
GitLab