Skip to content
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
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# 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 project is a very simple authentication API built with Node.js and Express. It allows users to sign up, log in, and access protected routes with a valid token.

## 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?
The main challenge was to implement user authentication and authorization. I started by setting up the server with Node.js and Express, and then used the bcrypt library to hash user passwords for secure storage.

If I had more time, I would implement a password reset feature, add email verification for sign up, and refine the error handling.

## 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.
Frontend: https://simple-authentication-app.netlify.app/

Backend: https://project-auth-backend.onrender.com/
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcrypt-nodejs": "^0.0.3",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.17.3",
"mongoose": "^8.0.0",
"nodemon": "^3.0.1"
Expand Down
183 changes: 175 additions & 8 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,194 @@
import express from "express";
import bodyParser from "body-parser";
import cors from "cors";
import mongoose from "mongoose";
import crypto from "crypto";
import bcrypt from "bcrypt-nodejs";
import dotenv from "dotenv";
dotenv.config();

const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo";
// mongoose connection
const mongoUrl = process.env.MONGO_URL
|| "mongodb://localhost/auth";
mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true });
mongoose.Promise = Promise;

// 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
// schema mongoose
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Name is required"],
minlength: 2,
maxlength: 20,
},
email: {
type: String,
required: [true, "Email is required"],
unique: true,
minlength: 5,
},
password: {
type: String,
required: [true, "Password is required"],
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString("hex"),
},
});

const SecretSchema = new mongoose.Schema({
message: {
type: String,
required: [true, "Message is required"],
},
});


// User model
const User = mongoose.model("User", UserSchema, "users");
const Secret = mongoose.model('Secret', SecretSchema, 'secrets');

// Defines the port the app will run on. Defaults to 8080
const port = process.env.PORT || 8080;
const app = express();

// Add middlewares to enable cors and json body parsing
// Middlewares to enable cors and json body parsing
app.use(cors());
app.use(express.json());
app.use(bodyParser.json());

const authenticateUser = async (req, res, next) => {
const user = await User.findOne({ accessToken: req.header("Authorization") });
if (user) {
req.user = user;
next();

// Start defining your routes here
} else if (!user) {
res.status(401).json({ loggedOut: true });
} else if (user) {
res.status(401).json({ accessDenied: true });

} else {
res.status(401).json({ loggedOut: true });
}
}

// Routes
app.get("/", (req, res) => {
res.send("Hello Technigo!");
res.json([
{
// home route
"path": "/",
"methods": ["GET"],
"middlewares": ["anonymous"]
},
{
// signup route
"path": "/signup",
"methods": ["POST"],
"middlewares": ["anonymous"]
},
{
// login route
"path": "/login",
"methods": ["POST"],
"middlewares": ["anonymous"]
},
{
// secrets route
"path": "/secrets",
"methods": ["GET", "POST"],
"middlewares": ["authenticateUser"]
},
{
// logout route
"path": "/logout",
"methods": ["POST"],
"middlewares": ["authenticateUser"]
}


]);
});

app.post("/signup", async (req, res) => {
try {
const { name, email, password } = req.body;
const user = new User({ name, email, password: bcrypt.hashSync(password) });
await user.save();
res.status(201).json({ id: user._id, accessToken: user.accessToken });
}
catch (err) {
console.log(err);
res.status(400).json({ message: "Could not create user", errors: err.errors });
}

});

app.post("/secrets", async (req, res) => {
const { message } = req.body;
const secret = new Secret({ message });

try {
const savedSecret = await secret.save();
res.status(201).json(savedSecret);
} catch (err) {
res.status(400).json({ message: 'Could not save secret to the Database', error: err.errors });
}
});


app.get("/secrets", authenticateUser, (req, res) => {
Secret.find().then((secrets) => {
res.json(secrets);
});
}
);

// login route
app.post("/login", async (req, res) => {
const { email, password } = req.body;

try {
const user = await User.findOne({ email });

if (!user) {
return res.status(400).json({ message: "Invalid email or password" });
}

bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) {
return res.status(500).json({ message: "Something went wrong", error: err });
}

if (!isMatch) {
return res.status(400).json({ message: "Invalid email or password" });
}

// User is authenticated
res.json({ userId: user._id, accessToken: user.accessToken });
});

} catch (err) {
res.status(500).json({ message: "Something went wrong", error: err });
}
});

// logout route
app.post("/logout", authenticateUser, async (req, res) => {
try {
req.user.accessToken = crypto.randomBytes(128).toString("hex");
await req.user.save();
res.status(200).json({ loggedOut: true });
}
catch (err) {
res.status(400).json({ message: "Could not log out", error: err });
}
}
);

// Start the server
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});

11 changes: 7 additions & 4 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"start": "vite preview",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0"

},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.1",
"styled-components": "^6.1.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
Expand Down
124 changes: 121 additions & 3 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,121 @@
export const App = () => {
return <div>Find me in src/app.jsx!</div>;
};
import { useState } from 'react';
import styled from 'styled-components';

const API_URL = 'https://project-auth-backend.onrender.com';

const Container = styled.div`

display: grid;
grid-template-columns: 1fr;
gap: 10px;
align-items: center;
justify-content: center;
max-width: 50%;
margin: 0 auto;
column-gap: 10px;
`;

const Loading = () => <div>Loading...</div>; // Define a loading component



const App = () => {
const [user, setUser] = useState({ name: '', email: '', password: '' });
const [token, setToken] = useState('');
const [loading, setLoading] = useState(false);

const handleChange = (event) => {
setUser({ ...user, [event.target.name]: event.target.value });
}


const handleSignup = async () => {
const response = await fetch(`${API_URL}/signup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
});
const data = await response.json();
setToken(data.accessToken);
}

const handleLogin = async () => {
try {
setLoading(true);
const response = await fetch(`${API_URL}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: user.email, password: user.password }),
});
const data = await response.json();
setToken(data.accessToken);
} finally {
setLoading(false);
}
}

const handleLogout = async () => {
await fetch(`${API_URL}/logout`, {
method: 'POST',
headers: { Authorization: token },
});
setToken('');
setSecrets(null);
}

const [newSecret, setNewSecret] = useState('');

const handlePostSecret = async () => {
await fetch(`${API_URL}/secrets`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: token },
body: JSON.stringify({ message: newSecret }),
});
setNewSecret('');
handleFetchSecrets();
};

const [secrets, setSecrets] = useState(null);

const handleFetchSecrets = async () => {
const response = await fetch(`${API_URL}/secrets`, {
headers: { Authorization: token },
});
const data = await response.json();
// console.log(data);
setSecrets(data);

};

return (
<Container>
<h1>Simple Authentication App</h1>
<input name="name" value={user.name} onChange={handleChange} placeholder="Name" />
<input name="email" value={user.email} onChange={handleChange} placeholder="Email" />
<input name="password" value={user.password} onChange={handleChange} placeholder="Password" type="password" />
<button onClick={handleSignup}>Sign Up</button>
<button onClick={handleLogin}>Log In</button>
<button onClick={handleLogout}>Log Out</button>
{loading ? (
<Loading />
) : (
<>
{token && <input value={newSecret} onChange={(event) => setNewSecret(event.target.value)} placeholder="Enter your secret here" />}
{token && <button onClick={handlePostSecret}>Post Secret</button>}

{token && <button onClick={handleFetchSecrets}>Fetch Secrets</button>}
{secrets && secrets.map((secret, index) => (
<div key={index}>
{secret.message}

</div>
))}
</>
)}
</Container>
);
}



export default App;
2 changes: 1 addition & 1 deletion frontend/src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App.jsx";
import App from "./App.jsx";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
Expand Down
Loading