From e28d822d7bda93501af00534ff3b83b92980e742 Mon Sep 17 00:00:00 2001 From: hosead6168 Date: Sun, 22 Aug 2021 20:43:42 -0500 Subject: [PATCH] feat(*): added user login and register --- controllers/user.js | 101 +++++++++ models/user.js | 31 +++ package-lock.json | 343 +++++++++++++++++++++++++++++ package.json | 2 + routes/user.js | 10 + server.js | 7 +- src/API/UserAPI.jsx | 38 ++++ src/App.js | 4 + src/GlobalState.js | 23 +- src/Pages/About/._About.jsx | Bin 4096 -> 0 bytes src/Pages/About/About.jsx | 6 + src/Pages/Articles/ArticleCard.jsx | 24 +- src/Pages/Auth/login.css | 116 ++++++++++ src/Pages/Auth/login.jsx | 76 +++++++ src/Pages/Auth/register.jsx | 90 ++++++++ utils/auth.js | 23 ++ utils/authAdmin.js | 18 ++ 17 files changed, 905 insertions(+), 7 deletions(-) create mode 100644 controllers/user.js create mode 100644 models/user.js create mode 100644 routes/user.js create mode 100644 src/API/UserAPI.jsx delete mode 100755 src/Pages/About/._About.jsx create mode 100644 src/Pages/Auth/login.css create mode 100644 src/Pages/Auth/login.jsx create mode 100644 src/Pages/Auth/register.jsx create mode 100644 utils/auth.js create mode 100644 utils/authAdmin.js diff --git a/controllers/user.js b/controllers/user.js new file mode 100644 index 00000000..5c078fe5 --- /dev/null +++ b/controllers/user.js @@ -0,0 +1,101 @@ +const Users = require('../models/user'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); + +const userCtrl = { + register, + refreshToken, + login +} + +async function register(req, res) { + try { + const { name, email, password } = req.body; + console.log(req.body) + console.log(name) + console.log(email) + console.log(password) + const user = await Users.findOne({ email }) + if (user) return res.status(400).json({ msg: "The email already exists" }) + + if (password.length < 6) + return res.status(400).json({ msg: "Password is at least 6 characters long" }) + + //Password Encryption + const passwordHash = await bcrypt.hash(password, 10) + + //Create new user instance + const newUser = new Users({ + name, email, password: passwordHash + }) + // Save mongodb + await newUser.save() + + //Create jsonwebtoken for authentication + const accesstoken = createAccessToken({ id: newUser._id }) + const refreshtoken = createRefreshToken({ id: newUser._id }) + + res.cookie('refreshtoken', refreshtoken, { + httpOnly: true, + path: '/api/user/refresh_token' + }) + // res.json({ password, passwordHash }) + res.json({ accesstoken }) + // res.json({ msg: "Register Successful" }) + + } catch (err) { + return res.status(500).json({ msg: err.message }) + } +} + +function refreshToken(req, res) { + try { + const rf_token = req.cookies.refreshtoken; + if (!rf_token) return res.status(400).json({ msg: "Please Login or Register" }) + + jwt.verify(rf_token, process.env.REFRESH_TOKEN_SECRET, (err, user) => { + if (err) return res.status(400).json({ msg: "Please Login or Register" }) + + const accesstoken = createAccessToken({ id: user.id }) + + res.json({ accesstoken }) + }) + res.json({ rf_token }) + } catch (err) { + return res.status(500).json({ msg: err.message }) + } +} + +async function login(req, res) { + try { + const { email, password } = req.body + + const user = await Users.findOne({ email }) + if (!user) return res.status(400).json({ msh: "User does not exist." }) + + const isMatch = await bcrypt.compare(password, user.password) + if (!isMatch) return res.status(400).json({ msh: "Invalid password" }) + + const accesstoken = createAccessToken({ id: user._id }) + const refreshtoken = createRefreshToken({ id: user._id }) + + res.cookie('refreshtoken', refreshtoken, { + httpOnly: true, + path: '/api/user/refresh_token' + }) + res.json({ accesstoken }) + // res.json({ msg: "Login successful" }) + + } catch (err) { + return res.status(500).json({ msg: err.message }) + } +} + +const createAccessToken = (user) => { + return jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '1d' }) +} +const createRefreshToken = (user) => { + return jwt.sign(user, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '7d' }) +} + +module.exports = userCtrl \ No newline at end of file diff --git a/models/user.js b/models/user.js new file mode 100644 index 00000000..4fed22af --- /dev/null +++ b/models/user.js @@ -0,0 +1,31 @@ +const mongoose = require('mongoose'); + +const userSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + trim: true + }, + email: { + type: String, + required: true, + unique: true + }, + password: { + type: String, + required: true, + trim: true + }, + role: { + type: Number, + default: 0 + }, + cart: { + type: Array, + default: [] + } +}, { + timestamps: true +}) + +module.exports = mongoose.model('Users', userSchema) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 902b4acf..6bae8af6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@testing-library/user-event": "^12.8.3", "aos": "^2.3.4", "axios": "^0.21.1", + "bcrypt": "^5.0.1", "body-parser": "^1.19.0", "bootstrap": "^5.1.0", "cloudinary": "^1.26.3", @@ -26,6 +27,7 @@ "framer-motion": "^4.1.17", "jest-axe": "^5.0.1", "jsdom": "^16.7.0", + "jsonwebtoken": "^8.5.1", "markdown": "^0.5.0", "marked": "^3.0.0", "mdreact": "^0.1.5", @@ -2950,6 +2952,61 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "dependencies": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@material-ui/core": { "version": "4.12.3", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz", @@ -5638,6 +5695,19 @@ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" }, + "node_modules/bcrypt": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -5942,6 +6012,11 @@ "isarray": "^1.0.0" } }, + "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": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -7894,6 +7969,17 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8185,6 +8271,14 @@ "safer-buffer": "^2.1.0" } }, + "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==", + "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", @@ -14144,6 +14238,40 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "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": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -14257,6 +14385,25 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "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==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -14450,6 +14597,36 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -14460,6 +14637,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "node_modules/lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -15893,6 +16075,11 @@ "tslib": "^2.0.3" } }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, "node_modules/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -27657,6 +27844,47 @@ "chalk": "^4.0.0" } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + } + } + }, "@material-ui/core": { "version": "4.12.3", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.3.tgz", @@ -29732,6 +29960,15 @@ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" }, + "bcrypt": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -29991,6 +30228,11 @@ "isarray": "^1.0.0" } }, + "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": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -31521,6 +31763,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -31769,6 +32016,14 @@ "safer-buffer": "^2.1.0" } }, + "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==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -36379,6 +36634,35 @@ "universalify": "^2.0.0" } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "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": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -36484,6 +36768,25 @@ "object.assign": "^4.1.2" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", @@ -36643,6 +36946,36 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -36653,6 +36986,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -37640,6 +37978,11 @@ "tslib": "^2.0.3" } }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", diff --git a/package.json b/package.json index d991156b..4f5ef6b7 100755 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^12.8.3", "aos": "^2.3.4", "axios": "^0.21.1", + "bcrypt": "^5.0.1", "body-parser": "^1.19.0", "bootstrap": "^5.1.0", "cloudinary": "^1.26.3", @@ -22,6 +23,7 @@ "framer-motion": "^4.1.17", "jest-axe": "^5.0.1", "jsdom": "^16.7.0", + "jsonwebtoken": "^8.5.1", "markdown": "^0.5.0", "marked": "^3.0.0", "mdreact": "^0.1.5", diff --git a/routes/user.js b/routes/user.js new file mode 100644 index 00000000..e597635b --- /dev/null +++ b/routes/user.js @@ -0,0 +1,10 @@ +const router = require('express').Router(); +const userCtrl = require('../controllers/user') + +router.post('/register', userCtrl.register); + +router.post('/login', userCtrl.login); + +router.get('/refresh_token', userCtrl.refreshToken); + +module.exports = router; \ No newline at end of file diff --git a/server.js b/server.js index cb2912af..4100c8b2 100755 --- a/server.js +++ b/server.js @@ -7,8 +7,9 @@ const fileUpload = require('express-fileupload'); const cors = require('cors'); const bodyParser = require("body-parser"); const mongoose = require('mongoose'); -const articleRouter = require('./routes/articles') -const uploadRouter = require('./routes/upload') +const articleRouter = require('./routes/articles'); +const uploadRouter = require('./routes/upload'); +const userRouter = require('./routes/user.js'); const app = express(); app.use(logger('dev')); @@ -30,6 +31,8 @@ app.use(express.static(path.join(__dirname, 'build'))); // Put API routes here, before the "catch all" route app.use('/api', articleRouter); app.use('/api', uploadRouter); +app.use('/api/user', userRouter); + const URI = process.env.MONGODB_URL mongoose.connect(URI, { diff --git a/src/API/UserAPI.jsx b/src/API/UserAPI.jsx new file mode 100644 index 00000000..f1832517 --- /dev/null +++ b/src/API/UserAPI.jsx @@ -0,0 +1,38 @@ +import { useState, useEffect } from "react"; +import axios from "axios"; + +function UserAPI(token) { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [isAdmin, setIsAdmin] = useState(false); + const [history, setHistory] = useState([]); + + useEffect(() => { + if (token) { + const getUser = async () => { + try { + const res = await axios.get("/api/user/infor", { + headers: { Authorization: token }, + }); + console.log(res.data) + console.log('yo') + setIsLoggedIn(true); + res.data.role === 1 ? setIsAdmin(true) : setIsAdmin(false); + + } catch (err) { + alert(err.response.data.msg); + } + }; + getUser(); + } + }, [token]); + + + + return { + isLoggedIn: [isLoggedIn, setIsLoggedIn], + isAdmin: [isAdmin, setIsAdmin], + history: [history, setHistory], + }; +} + +export default UserAPI; diff --git a/src/App.js b/src/App.js index 06a8fdf0..bb38b7e7 100755 --- a/src/App.js +++ b/src/App.js @@ -21,6 +21,8 @@ import { DataProvider } from './GlobalState'; import Client from './Pages/Client/Client'; import ArticleItem from './Pages/Articles/Article/Article'; import ProjectItem from './Pages/Projects/Project/Project'; +import Login from './Pages/Auth/login'; +import Register from './Pages/Auth/register'; export default class App extends Component { @@ -130,6 +132,8 @@ export default class App extends Component { + + diff --git a/src/GlobalState.js b/src/GlobalState.js index 054ec23c..f7d34c95 100755 --- a/src/GlobalState.js +++ b/src/GlobalState.js @@ -1,5 +1,7 @@ -import React, { createContext, useState } from 'react'; +import React, { createContext, useState, useEffect } from "react"; import ArticlesAPI from './API/ArticlesAPI'; +import UserAPI from "./API/UserAPI"; +import axios from "axios"; export const GlobalState = createContext() @@ -7,10 +9,27 @@ export const GlobalState = createContext() export const DataProvider = ({ children }) => { const [token, setToken] = useState(false) + useEffect(() => { + const firstLogin = localStorage.getItem("firstLogin"); + if (firstLogin) { + const refreshToken = async () => { + const res = await axios.get("api/user/refresh_token"); + setToken(res.data.accesstoken); + + setTimeout(() => { + refreshToken(); + }, 10 * 60 * 1000); + }; + + refreshToken(); + } + }, []); const state = { token: [token, setToken], articlesAPI: ArticlesAPI(), + userAPI: UserAPI(token), + } return ( @@ -19,3 +38,5 @@ export const DataProvider = ({ children }) => { ) } + + diff --git a/src/Pages/About/._About.jsx b/src/Pages/About/._About.jsx deleted file mode 100755 index 0d4fb1754a83ee9152cfeb5253ab107cdb912302..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDJkFz{^v(m+1nBL)UWIUt(=a103v0xDsI z=wMg?WDB5a0m{L|rIPb=^%4sTa#Hnj5{pYpi&Ill5=&B*1A;+%7B}c7fas#2HW?5- z3PwXEOnfZB%IXRUIIjLzS3Q0MMdD+0eFjUu&rcnJ4_lgXI-2eXo DZX_ud diff --git a/src/Pages/About/About.jsx b/src/Pages/About/About.jsx index 3bf95a5b..5a34a475 100755 --- a/src/Pages/About/About.jsx +++ b/src/Pages/About/About.jsx @@ -71,6 +71,12 @@ const About = () => { {/* */}
+
+
+

2021

+

I secured my first position as a Full-Stack Java Software Engineer at State Farm in March of 2021. Working in a full agile software development process including mining technical business features and utilizing Java Spring Boot with Maven to create a REST micro-service applications.

+
+

2020

diff --git a/src/Pages/Articles/ArticleCard.jsx b/src/Pages/Articles/ArticleCard.jsx index d7c09beb..da36893c 100755 --- a/src/Pages/Articles/ArticleCard.jsx +++ b/src/Pages/Articles/ArticleCard.jsx @@ -1,18 +1,31 @@ -import React from 'react'; +import React, {useContext} from 'react'; import { Link } from 'react-router-dom'; import './Articles.css' import BtnRender from './BtnRender'; // import ReactHtmlParser from 'react-html-parser'; import VisibilityIcon from '@material-ui/icons/Visibility'; import moment from 'moment-timezone' - +import { GlobalState } from '../../GlobalState'; + + + // + // + // + // + // + // + // + // + // // Main Article Cards const ArticleItem = (props) => { + const state = useContext(GlobalState); + const [isLoggedIn] = state.userAPI.isLoggedIn + const [isAdmin] = state.userAPI.isAdmin const { title, createdAt, description, images, _id } = props.article; const timeFormater = moment.utc(createdAt).format('MM/DD/YYYY') - return (
{/* { {/*
{ReactHtmlParser(sanitizedHtml)}
*/}
- + {isAdmin && isLoggedIn ? + : + null + }
diff --git a/src/Pages/Auth/login.css b/src/Pages/Auth/login.css new file mode 100644 index 00000000..2ee76e48 --- /dev/null +++ b/src/Pages/Auth/login.css @@ -0,0 +1,116 @@ +.login-page { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border-radius: 5px; + padding: 50px; + /* margin: 50px auto; */ + margin: auto; + width: 100%; + text-align: center; + height: auto; +} + +.login-page h2 { + letter-spacing: 2px; + font-size: 2.5rem; + color: rgb(0, 0, 0); + margin: 50px auto; +} + +.login-page form { + width: 30%; +} + +.login-title { + font-family: "Oregon"; +} + +.login-page form input, +.login-page form button { + width: 100%; + height: 40px; + margin: 10px 0; + padding: 0 5px; + outline: rgb(3, 165, 206); + border: 1 solid rgb(3, 165, 206); + margin: auto; +} + +.login-page form input::placeholder { + padding-left: 10px; + font-size: 0.75rem; +} + +.login-page form .row { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0; + margin-top: 20px; +} + +.login-page form button { + width: 100%; + height: 50px; + background: rgb(0, 0, 0); + color: white; + text-transform: uppercase; + letter-spacing: 2px; + margin-bottom: 5px; +} + +.login-page form a { + color: black; + letter-spacing: 1.3px; + text-transform: uppercase; +} + +#login-page-container .footer-top, +#register-page-container .footer-top { + position: absolute; + /* bottom: 0; */ + left: 0; + width: 100%; +} + +#login-page-container label { + float: left; + padding: 0; + font-weight: bold; + font-size: 0.9rem; +} + +#login-page-container .brand { + margin-left: 20px; + margin-top: -6%; + margin-bottom: -6%; +} + +@media only screen and (max-width: 1475px) { + #login-page-container .brand { + width: 100%; + margin-left: 0%; + } + .login-page form { + width: 40%; + } + .login-page h2 { + font-size: 2rem; + margin: 30px auto; + } +} +@media only screen and (max-width: 975px) { + #login-page-container .brand { + width: 100%; + margin-left: 0%; + } + .login-page form { + width: 60%; + } + .login-page h2 { + font-size: 2rem; + margin: 30px auto; + } +} diff --git a/src/Pages/Auth/login.jsx b/src/Pages/Auth/login.jsx new file mode 100644 index 00000000..45080465 --- /dev/null +++ b/src/Pages/Auth/login.jsx @@ -0,0 +1,76 @@ +import React, { useState } from "react"; + +import axios from "axios"; +import { Link } from "react-router-dom"; + +import Footer from "../../Components/Footer/Footer"; +import Logo from "../../icons/logo.png"; +import "./login.css"; + +const Login = () => { + const [user, setUser] = useState({ + email: "", + password: "", + }); + + const onChangeInput = (e) => { + const { name, value } = e.target; + setUser({ ...user, [name]: value }); + }; + + const loginSubmit = async (e) => { + e.preventDefault(); + try { + await axios.post("/api/user/login", { ...user }); + localStorage.setItem("firstLogin", true); + window.location.href = "/"; + } catch (err) { + alert(err.response.data.msg); + } + }; + + return ( +
+
+ + brand-name + +
+

Sign in to your account

+

+ + + + + +
+ +
+ Not yet signed up? Sign up here! +
+
+
+
+
+
+ ); +}; + +export default Login; diff --git a/src/Pages/Auth/register.jsx b/src/Pages/Auth/register.jsx new file mode 100644 index 00000000..6220d260 --- /dev/null +++ b/src/Pages/Auth/register.jsx @@ -0,0 +1,90 @@ +import React, { useState } from "react"; + +import axios from "axios"; +import { Link } from "react-router-dom"; + +import Footer from "../../Components/Footer/Footer"; +import Logo from "../../icons/logo.png"; + +import "./login.css"; + +const Register = () => { + const [user, setUser] = useState({ + name: "", + email: "", + password: "", + }); + + const onChangeInput = (e) => { + const { name, value } = e.target; + setUser({ ...user, [name]: value }); + }; + + const registerSubmit = async (e) => { + e.preventDefault(); + try { + await axios.post("/api/user/register", { ...user }); + + localStorage.setItem("firstLogin", true); + + window.location.href = "/"; + } catch (err) { + alert(err.response.data.msg); + } + }; + + const {name, email, password} = user + return ( +
+
+ + brand-name + +
+

Registration

+ + + + + + + +
+ +
+ Already have an account? Log in here! +
+
+
+
+
+ ); +}; + +export default Register; diff --git a/utils/auth.js b/utils/auth.js new file mode 100644 index 00000000..a089aca3 --- /dev/null +++ b/utils/auth.js @@ -0,0 +1,23 @@ +const jwt = require("jsonwebtoken"); + +const auth = (req, res, next) => { + try { + const token = req.header("Authorization"); + if (!token) + return res.status(400).json({ msg: "Invalid Authentication - no token" }); + + jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => { + if (err) + return res + .status(400) + .json({ msg: "Invalid Authentication - invalid token" }); + + req.user = user; + next(); + }); + } catch (err) { + return res.status(500).json({ msg: err.message }); + } +}; + +module.exports = auth; \ No newline at end of file diff --git a/utils/authAdmin.js b/utils/authAdmin.js new file mode 100644 index 00000000..73915030 --- /dev/null +++ b/utils/authAdmin.js @@ -0,0 +1,18 @@ +const Users = require("../models/user"); + +const authAdmin = async (req, res, next) => { + try { + const user = await Users.findOne({ + _id: req.user.id + }) + if (user.role === 0) + return res.status(400).json({ msg: "Admin resources access denied" }) + next() + } catch (err) { + return res.status(500).json({ msg: err.message }) + + } +} + + +module.exports = authAdmin \ No newline at end of file