Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project Auth #347

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# Project Auth API

Replace this readme with your own information about your project.

Start by briefly describing the assignment in a sentence or two. Keep it short and to the point.
This assignment was to build an API with authentication to implement a registration flow and a frontend with forms to register, sign in, and view some content once logged in.

## The problem

Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next?
Struggled a lot with where to salt the password, then found Technigo's example of how to solve it. This was a hard project and it helped a lot to break it down into lots of little steps. So first I just created the React components with no back end. Then I set up the API endpoints but didn't put any code in them yet. After that I made the login form send a POST request to the API but didn't try to code the login check yet.

## View it live

Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
https://project-auth-jcs.netlify.app/
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"express": "^4.17.3",
"mongoose": "^8.0.0",
Expand Down
67 changes: 67 additions & 0 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import cors from "cors";
import express from "express";
import mongoose from "mongoose";
// install bcrypt with npm install. https://nordvpn.com/sv/blog/what-is-bcrypt/
import bcrypt from "bcrypt";
import crypto from "crypto";

const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo";
mongoose.connect(mongoUrl);
mongoose.Promise = Promise;

const User = mongoose.model("User", {
username: {
type: String,
unique: true,
required: true,
},
password: {
type: String,
required: true,
},
token: {
type: String,
default: () => crypto.randomBytes(128).toString("hex"),
},
});

// 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
Expand All @@ -21,6 +40,54 @@ app.get("/", (req, res) => {
res.send("Hello Technigo!");
});

// here is the route for post sign up.
app.post("/signup", async (req, res) => {
// https://heynode.com/blog/2020-04/salt-and-hash-passwords-bcrypt/
const salt = bcrypt.genSaltSync(10);

// here we are defining what kind of data we are going to get.
User.create({
username: req.body.username,
password: bcrypt.hashSync(req.body.password, salt),
})
.then((user) => {
res.status(201).json({ token: user.token });
})
.catch((error) => {
res.status(400).json({ message: "Could not create user", error });
});
});

// created a login endpoint here. POST/login is the endpoint. POST/sign up is also an endpoint. GET is also an endpoint. The endpoints like log in and user data gets stored in the database (mongoDB)
app.post("/login", async (req, res) => {
const { username, password } = req.body;
// here we are creating a token ... like you get a ticket to a festival, and the wristband is the token
return User.findOne({ username: req.body.username }).then((user) => {
if (user && bcrypt.compareSync(req.body.password, user.password)) {
res.json({ token: user.token });
} else {
res.status(401).json({ message: "Could not log in" });
return;
}
});
});

// here we are checking the token, if it is correct we get the message, if not we get a 401 error
app.get("/private", async (req, res) => {
if (!req.headers.authorization) {
res.status(401).json({ message: "Not authorized" });
}

const user = await User.findOne({
token: req.headers.authorization.split(" ")[1],
});
if (!user) {
res.status(401).json({ message: "Not authorized" });
return;
}
res.json({ message: "This is a secret message" });
});

// Start the server
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
Expand Down
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
Expand Down
22 changes: 19 additions & 3 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
export const App = () => {
return <div>Find me in src/app.jsx!</div>;
};
// Import code from movie-project
// here i import the componets
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { LoginPage } from "./LoginPage";
import { SignupPage } from "./SignupPage";
import { PrivatePage } from "./PrivatePage";

// https://reactrouter.com/
// here setting up routes for the app
export const App = () => (
<Router>
<Routes>
<Route path="/" element={<LoginPage />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/private" element={<PrivatePage />} />
</Routes>
</Router>
);
52 changes: 52 additions & 0 deletions frontend/src/LoginPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// here we import the useNavigate hook from react-router-dom and use it to navigate to the /private route when the form is submitted.
import { Link, useNavigate } from "react-router-dom";

export const LoginPage = () => {
// getting the function out
const navigate = useNavigate();
const handleSubmit = (event) => {
// prevent dedefault stops the page from reloading
event.preventDefault();

// fetch is for sending requests to the server. Post is for sending data to the server.
fetch("https://project-auth-jcs.onrender.com/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
// here we are defining what data we are going to send to the server
body: JSON.stringify({
username: event.target.username.value,
password: event.target.password.value,
}),
})
// here converting the response from json, which is a string, to an object that we can work with
.then((res) => res.json())
.then((res) => {
console.log(res);
// here we are saving the token in the local storage, so the user can stay logged in
localStorage.setItem("token", res.token);
navigate("/private");
});
};
return (
<>
{/* here we have to first register an event handler for the form submission */}
<form onSubmit={handleSubmit}>
<h1>Login</h1>
<label>
Username:
<input type="text" name="username" />
</label>
<label>
Password:
<input type="password" name="password" />
</label>
<button type="submit">Login</button>
</form>
<p>
<Link to="/signup">Sign up</Link>
</p>
</>
);
};
42 changes: 42 additions & 0 deletions frontend/src/PrivatePage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

export const PrivatePage = () => {
const navigate = useNavigate();

const [data, setData] = useState(null);
const [status, setStatus] = useState(null);

useEffect(() => {
fetch("https://project-auth-jcs.onrender.com/private", {
headers: {
// this is how we get the token back from the local storage, to prove that the user is still logged in.
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((res) => {
setStatus(res.status);
return res.json();
})
.then((data) => {
setData(data.message);
});
}, []);

// here we create the log out function that removes the token from the local storage
const logout = (event) => {
event.preventDefault();
localStorage.removeItem("token");
navigate("/");
};

return (
<div>
{/* data is the msg from the server */}
{data}
{/* the && is how to write if in jsx, in js we would typ "if (status === 200) <button onClick ... and so on. " */}
{/* here we are hiding the log out button if the status is not 200. 200 means user is logged in. 401 is unauthorized, (not logged in) */}
{status === 200 && <button onClick={logout}>Log out</button>}
</div>
);
};
57 changes: 57 additions & 0 deletions frontend/src/SignupPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { Link } from "react-router-dom";

export const SignupPage = () => {
const [error, setError] = useState(false);
const navigate = useNavigate();
const handleSubmit = (event) => {
// prevent dedefault stops the page from reloading
event.preventDefault();

// fetch is for sending requests to the server. Post is for sending data to the server.
fetch("https://project-auth-jcs.onrender.com/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
// here we are defining what data we are going to send to the server
body: JSON.stringify({
username: event.target.username.value,
password: event.target.password.value,
}),
})
// here converting the response from json, which is a string, to an object that we can work with
.then((res) => res.json())
.then((res) => {
if (res.error) {
setError(true);
return;
}
console.log(res);
// here we are saving the token in the local storage, so the user can stay logged in
localStorage.setItem("token", res.token);
navigate("/private");
});
};
return (
<>
<form onSubmit={handleSubmit}>
<h1>Sign up</h1>
{error && <p>Something went wrong</p>}
<label>
Username:
<input type="text" name="username" />
</label>
<label>
Password:
<input type="password" name="password" />
</label>
<button type="submit">Sign up</button>
</form>
<p>
<Link to="/">Log in</Link>
</p>
</>
);
};
6 changes: 0 additions & 6 deletions netlify.toml

This file was deleted.