diff --git a/backend/.gitignore b/backend/.gitignore index 25c8fdbab..8f5e467c8 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,3 @@ node_modules -package-lock.json \ No newline at end of file +package-lock.json +.env \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 8de5c4ce0..3d9bb40be 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,9 +12,12 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^5.1.1", "cors": "^2.8.5", + "dotenv": "^16.4.5", "express": "^4.17.3", - "mongoose": "^8.0.0", - "nodemon": "^3.0.1" + "express-list-endpoints": "^7.1.0", + "mongoose": "^8.4.0", + "nodemon": "^3.1.1" } } diff --git a/backend/server.js b/backend/server.js index dfe86fb8e..d6be56a82 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,14 +1,46 @@ import cors from "cors"; +import crypto from "crypto"; +import bcrypt from "bcrypt"; import express from "express"; import mongoose from "mongoose"; +import expressListEndpoints from "express-list-endpoints"; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; +// const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/auth"; +const mongoUrl = "mongodb://localhost/auth"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; +const User = mongoose.model("User", { + name: { + type: String, + unique: true, + }, + email: { + type: String, + unique: true, + }, + password: { + type: String, + required: true, + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), + }, +}); + +// Middleware function +const authenticateUser = async (req, res, next) => { + const user = await User.findOne({ accessToken: req.header("Authorization") }); + if (user) { + req.user = user; + next(); + } else { + res.status(401).json({ loggedOut: true }); + } +}; + // Defines the port the app will run on. Defaults to 8080, but can be overridden -// when starting the server. Example command to overwrite PORT env variable value: -// PORT=9000 npm start const port = process.env.PORT || 8080; const app = express(); @@ -16,9 +48,65 @@ const app = express(); app.use(cors()); app.use(express.json()); -// Start defining your routes here +// Documentation endpoint app.get("/", (req, res) => { - res.send("Hello Technigo!"); + const endpoints = expressListEndpoints(app); + const documentation = { + Welcome: "This is the Authentication API!", + Endpoints: { + "/": "Get API documentation", + "/users": { + POST: "Create a new user", + }, + "/sessions": { + POST: "Authenticate a returning user", + }, + "/secrets": { + GET: "Get secret content (requires authentication)", + }, + }, + }; + res.json(documentation); +}); + +app.post("/users", async (req, res) => { + try { + const { name, email, password } = req.body; + // DO NOT STORE PLAINTEXT PASSWORD + const salt = bcrypt.genSaltSync(); + const user = new User({ + name, + email, + password: bcrypt.hashSync(password, salt), + }); + await user.save(); // Await the save operation + res.status(201).json({ + success: true, + message: "User created", + id: user._id, + accessToken: user.accessToken, + }); + } catch (error) { + res.status(400).json({ success: false, message: "Could not create user", errors: error }); + } +}); + +app.post("/sessions", async (req, res) => { + const user = await User.findOne({ email: req.body.email }); + if (user && bcrypt.compareSync(req.body.password, user.password)) { + res.status(200).json({ + success: true, + message: "User authenticated", + id: user._id, + accessToken: user.accessToken, + }); + } else { + res.status(401).json({ success: false, message: "Invalid email or password" }); + } +}); + +app.get("/secrets", authenticateUser, (req, res) => { + res.json({ secret: "This is a super secret!" }); }); // Start the server diff --git a/frontend/index.html b/frontend/index.html index 0c589eccd..2bb7b743e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,10 +4,12 @@ - Vite + React + Auth Vite + React +
+ diff --git a/frontend/package.json b/frontend/package.json index e9c95b79f..005b6796c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,8 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "zustand": "^4.5.2" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/frontend/public/keys-wall.jpg b/frontend/public/keys-wall.jpg new file mode 100644 index 000000000..49d3bc544 Binary files /dev/null and b/frontend/public/keys-wall.jpg differ diff --git a/frontend/public/solo.jpg b/frontend/public/solo.jpg new file mode 100644 index 000000000..700f32088 Binary files /dev/null and b/frontend/public/solo.jpg differ diff --git a/frontend/public/technigo-logo.svg b/frontend/public/technigo-logo.svg new file mode 100644 index 000000000..93f0f4e20 --- /dev/null +++ b/frontend/public/technigo-logo.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 000000000..f8f7c3179 --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,6 @@ +.app-container { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1091d4310..a51e8296c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,14 @@ +import { Footer } from "./Components/Footer"; +import { HomePage } from "./Components/HomePage"; +import { Header } from "./Components/Header"; +import "./App.css" + export const App = () => { - return
Find me in src/app.jsx!
; + return ( +
+
+ +
+ ) }; diff --git a/frontend/src/Components/Buttons.css b/frontend/src/Components/Buttons.css new file mode 100644 index 000000000..9b1041a91 --- /dev/null +++ b/frontend/src/Components/Buttons.css @@ -0,0 +1,14 @@ +.button-container button { + padding: 8px 16px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + margin: 5px 0; + width: 100%; +} + +.button-container button:hover { + background-color: #0056b3; +} \ No newline at end of file diff --git a/frontend/src/Components/Buttons.jsx b/frontend/src/Components/Buttons.jsx new file mode 100644 index 000000000..6c233c6e4 --- /dev/null +++ b/frontend/src/Components/Buttons.jsx @@ -0,0 +1,31 @@ +import "./Buttons.css"; + +export const BtnSignIn = ({ onClick }) => { + return ( +
+ +
+ ); +}; + +export const BtnLogin = ({ onClick }) => { + return ( +
+ +
+ ); +}; + +export const BtnSubmit = ({ onClick }) => { + return ( +
+ +
+ ); +}; diff --git a/frontend/src/Components/Footer.css b/frontend/src/Components/Footer.css new file mode 100644 index 000000000..741bd82e0 --- /dev/null +++ b/frontend/src/Components/Footer.css @@ -0,0 +1,22 @@ +.footer-container { + position: fixed; + bottom: 0; + margin-top: auto; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 5px; + background-color: #303030; + color: rgb(255, 255, 255); + width: 100%; +} + +.footer-container p { + margin: 10px; + font-size: 0.85rem; +} + +.logo { + height: 30px; +} \ No newline at end of file diff --git a/frontend/src/Components/Footer.jsx b/frontend/src/Components/Footer.jsx new file mode 100644 index 000000000..fee0a869c --- /dev/null +++ b/frontend/src/Components/Footer.jsx @@ -0,0 +1,10 @@ +import "./Footer.css" + +export const Footer = () => { + return ( + + ) +} \ No newline at end of file diff --git a/frontend/src/Components/Header.css b/frontend/src/Components/Header.css new file mode 100644 index 000000000..4440358d6 --- /dev/null +++ b/frontend/src/Components/Header.css @@ -0,0 +1,32 @@ +.header-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: white; + font-size: 1.5rem; + width: 100%; + margin-bottom: 20px; +} + +.header-container h1 { + text-align: center; + font-size: 2rem; + backdrop-filter: blur(2px); + margin: 20px; + padding: 0px 15px; + border-radius: 15px; +} + +/* TABLET SCREENS min 745px ------------------------------------------------------------ */ + +@media (min-width: 745px) { + .header-container { + min-height: 300px; + } + + .header-container h1 { + font-size: 4rem; + backdrop-filter: blur(5px); + } +} diff --git a/frontend/src/Components/Header.jsx b/frontend/src/Components/Header.jsx new file mode 100644 index 000000000..40b6bd5cc --- /dev/null +++ b/frontend/src/Components/Header.jsx @@ -0,0 +1,9 @@ +import "./Header.css"; + +export const Header = () => { + return ( +
+

Arnau's Authentication Project

+
+ ); +}; \ No newline at end of file diff --git a/frontend/src/Components/HomePage.css b/frontend/src/Components/HomePage.css new file mode 100644 index 000000000..7040f5cd1 --- /dev/null +++ b/frontend/src/Components/HomePage.css @@ -0,0 +1,49 @@ +.home-page-container { + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 95%; + color: white; + background-color: rgba(0, 0, 0, 0.443); + box-shadow: 2px 2px 20px 10px rgba(0, 0, 0, 0.15); + padding: 10px; + backdrop-filter: blur(4px); + border-radius: 12px; + top: 0; + left: 0; + border: 2px solid white; + padding-bottom: 45px; +} + +.nav { + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; +} + +.button-container button { + padding: 8px 16px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + margin: 5px 0; + width: 100%; +} + +.button-container button:hover { + background-color: #0056b3; +} + +/* TABLET SCREENS min 745px ------------------------------------------------------------ */ + +@media (min-width:745px){ + .home-page-container { + width: 500px; + } +} \ No newline at end of file diff --git a/frontend/src/Components/HomePage.jsx b/frontend/src/Components/HomePage.jsx new file mode 100644 index 000000000..92be4b1b4 --- /dev/null +++ b/frontend/src/Components/HomePage.jsx @@ -0,0 +1,35 @@ +import { BtnLogin, BtnSignIn } from "./Buttons"; +import { LoginForm } from "./LoginForm"; +import { SignInForm } from "./SignInForm"; +import { useStore } from '../stores/storeData'; +import "./HomePage.css"; + +export const HomePage = () => { + const { showSignInForm, showLoginForm, setShowSignInForm, setShowLoginForm, hideForms } = useStore(); + + const handleSignInClick = () => { + hideForms(); // Hide both forms initially + setShowSignInForm(true); // Show SignInForm + }; + + const handleLoginClick = () => { + hideForms(); // Hide both forms initially + setShowLoginForm(true); // Show LoginForm + }; + + return ( +
+

Welcome!

+ +
+ ); +}; + + diff --git a/frontend/src/Components/LoginForm.css b/frontend/src/Components/LoginForm.css new file mode 100644 index 000000000..3c17df054 --- /dev/null +++ b/frontend/src/Components/LoginForm.css @@ -0,0 +1,74 @@ +.login-container { + z-index: 10; + position: absolute; + background-color: rgba(255, 255, 255, 0.837); + box-shadow: 2px 2px 20px 10px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 10px; + width: 100%; + backdrop-filter: blur(2px); + border-radius: 6px; + top: 0; + left: 0; + border: 2px solid white; +} + +.login-container > div { + position: relative; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.input-field { + padding: 0 5px; + margin: 5px; + display: flex; + border-radius: 6px; + background-color: white; + border: 1px solid rgb(219, 218, 218); + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.input-field i{ + position: absolute; + right: 10px; + color: grey; +} + +.input-field > input { + padding: 8px 16px; + padding-left: 1px; + border: none; + outline: none; + cursor: pointer; + width: 100%; + height: 100%; + margin: 3px 0; +} + +small { + font-size: 0.7rem; + color: black; + align-self: flex-start; +} + +.buttons { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; +} + + /*TABLET SCREENS min 745px ------------------------------------------------------------ */ + +@media (min-width: 745px) { +} diff --git a/frontend/src/Components/LoginForm.jsx b/frontend/src/Components/LoginForm.jsx new file mode 100644 index 000000000..37c2491da --- /dev/null +++ b/frontend/src/Components/LoginForm.jsx @@ -0,0 +1,84 @@ +import { useState } from 'react'; +import './LoginForm.css'; +import { BtnLogin } from './Buttons'; +import { useStore } from '../stores/storeData' + +export const LoginForm = () => { + const { hideForms } = useStore(); + const [formData, setFormData] = useState({ + name: '', + email: '', + password: '', + }); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + // Handle form submission logic here + console.log('Form data submitted:', formData); + }; + + return ( +
+
+
+ + +
+
+ + +
+
+ + +
+
+ We'll 'never' share your email with anyone else. +
+
+ +
+ +
+
+
+ ); +}; diff --git a/frontend/src/Components/SignInForm.css b/frontend/src/Components/SignInForm.css new file mode 100644 index 000000000..fb3d0c076 --- /dev/null +++ b/frontend/src/Components/SignInForm.css @@ -0,0 +1,69 @@ +.sign-in-container { + z-index: 10; + position: absolute; + background-color: rgba(255, 255, 255, 0.837); + box-shadow: 2px 2px 20px 10px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 10px; + width: 100%; + height: 190px; + backdrop-filter: blur(2px); + border-radius: 6px; + top: 0; + left: 0; + border: 2px solid white; +} + +.sign-in-container > div { + position: relative; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.input-field { + padding: 0 5px; + margin: 5px; + display: flex; + border-radius: 6px; + background-color: white; + border: 1px solid rgb(219, 218, 218); + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.input-field i{ + position: absolute; + right: 10px; + color: grey; +} + +.sign-in-container input { + padding: 8px 16px; + padding-left: 1px; + border: none; + outline: none; + cursor: pointer; + width: 100%; + height: 100%; + margin: 3px 0; +} + +#buttons { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; +} + +/* TABLET SCREENS min 745px ------------------------------------------------------------ */ + +@media (min-width: 745px) { +} diff --git a/frontend/src/Components/SignInForm.jsx b/frontend/src/Components/SignInForm.jsx new file mode 100644 index 000000000..6777fa2f9 --- /dev/null +++ b/frontend/src/Components/SignInForm.jsx @@ -0,0 +1,66 @@ +import { useState } from "react"; +import { BtnSignIn } from "./Buttons"; +import { useStore } from "../stores/storeData"; +import "./SignInForm.css"; + +export const SignInForm = () => { + const [formData, setFormData] = useState({ + email: "", + password: "", + }); + + const { hideForms } = useStore(); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value, + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + // Handle form submission logic here + console.log("Form data submitted:", formData); + }; + + return ( +
+
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+
+ ); +}; diff --git a/frontend/src/index.css b/frontend/src/index.css index 3e560a674..151b33cc2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,3 +1,9 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + :root { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", @@ -10,4 +16,20 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} + +body { + margin: 0; + width: 100%; + background-image: url("/keys-wall.jpg"); + background-attachment: fixed; + background-position: center; + background-size: 350%; +} + +/* TABLET SCREENS min 745px ------------------------------------------------------------ */ + +@media (min-width:745px){ + body { + } } \ No newline at end of file diff --git a/frontend/src/stores/storeData.jsx b/frontend/src/stores/storeData.jsx new file mode 100644 index 000000000..6211445ed --- /dev/null +++ b/frontend/src/stores/storeData.jsx @@ -0,0 +1,11 @@ +import { create } from "zustand"; + +export const useStore = create((set) => ({ + showSignInForm: false, + showLoginForm: false, + setShowSignInForm: (value) => + set({ showSignInForm: value, showLoginForm: !value }), + setShowLoginForm: (value) => + set({ showLoginForm: value, showSignInForm: !value }), + hideForms: () => set({ showSignInForm: false, showLoginForm: false }), +})); diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index ed9e83391..000000000 --- a/netlify.toml +++ /dev/null @@ -1,6 +0,0 @@ -# This file tells netlify where the code for this project is and -# how it should build the JavaScript assets to deploy from. -[build] - base = "frontend/" - publish = "dist" - command = "npm run build"