Skip to content

Commit c8f1074

Browse files
committed
Added initial example code
1 parent 411da75 commit c8f1074

File tree

18 files changed

+421
-40
lines changed

18 files changed

+421
-40
lines changed

api/src/controllers/auth.controller.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const { check, validationResult } = require("express-validator");
44

55
const User = require("../models/User");
66
const handleValidationErrors = require("../helpers/handleValidationErrors");
7+
const jwtMiddleware = require("../helpers/jwtMiddleware");
78

89
const router = AsyncRouter();
910

@@ -30,16 +31,31 @@ router.post(
3031
return res.status(400).send("Passwords do not match");
3132

3233
const user = await User.signUp(req.body.email, req.body.password);
33-
res.status(201).send(user);
34+
res.status(201).send(user.sanitize());
3435
}
3536
);
3637

3738
router.post(
3839
"/login",
3940
[...loginValidators, handleValidationErrors],
4041
async (req, res) => {
41-
42+
const user = await User.findOne({email: req.body.email});
43+
44+
if(!user || !user.comparePassword(req.body.password))
45+
return res.status(400).send("Invalid login information");
46+
47+
const token = jwt.sign({
48+
_id: user._id,
49+
}, "CHANGEME!");
50+
51+
res.send({token});
4252
}
4353
);
4454

55+
router.get("/profile", [jwtMiddleware], async (req, res) => {
56+
const user = await User.findOne({_id: req.user._id});
57+
58+
res.send(user);
59+
});
60+
4561
module.exports = router;

api/src/helpers/jwtMiddleware.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const jwt = require("jsonwebtoken");
2+
const User = require("../models/User");
3+
4+
const jwtMiddleware = async (req, res, next) => {
5+
const authorization = req.header("Authorization") || "";
6+
const [type, token] = authorization.split(" ");
7+
8+
try {
9+
if(type === "Bearer" && jwt.verify(token, "CHANGEME!")) {
10+
const payload = jwt.decode(token, "CHANGEME!");
11+
const user = await User.findOne({_id: payload._id});
12+
req.user = user;
13+
next();
14+
} else {
15+
res.status(401).send("Unauthorized");
16+
}
17+
} catch(err) {
18+
res.status(401).send("Unauthorized");
19+
}
20+
}
21+
22+
module.exports = jwtMiddleware;

api/src/models/User.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ userSchema.statics.signUp = async function(email, password) {
3535
userSchema.methods.hashPassword = function(plainText) {
3636
this.password = bcrypt.hashSync(plainText, 4);
3737
};
38-
38+
userSchema.methods.sanitize = function() {
39+
return {
40+
...this._doc,
41+
password: undefined,
42+
}
43+
}
44+
userSchema.methods.comparePassword = function(plainText) {
45+
return bcrypt.compareSync(plainText, this.password);
46+
}
3947
const User = mongoose.model("User", userSchema);
4048

4149
module.exports = User;

api/src/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const connectDatabase = async (databaseName="fullstackjs-boilerplate", hostname=
2828
return database;
2929
}
3030

31-
const startServer = (hostname="localhost", port=1337) => {
31+
const startServer = (hostname="0.0.0.0", port=1337) => {
3232
app.listen(port, hostname, async () => {
3333
await connectDatabase();
3434
if(process.env.ENV !== "test")

ui/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6+
"axios": "^0.19.0",
67
"react": "^16.10.1",
78
"react-dom": "^16.10.1",
8-
"react-scripts": "3.1.2"
9+
"react-router-dom": "^5.1.2",
10+
"react-scripts": "3.1.2",
11+
"reactn": "^2.2.4"
912
},
1013
"scripts": {
1114
"start": "BROWSER=none react-scripts start",

ui/src/App.js

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,59 @@
1-
import React from 'react';
2-
import logo from './logo.svg';
3-
import './App.css';
1+
import React, { useGlobal } from "reactn";
2+
3+
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
4+
5+
import "./App.css";
6+
import Home from "./pages/Home";
7+
// import NotFound from './pages/NotFound';
8+
import Profile from "./pages/Profile";
9+
import PrivateRoute from "./components/PrivateRoute";
10+
import Login from "./pages/Login";
11+
import Logout from './components/Logout';
12+
13+
const NavBar = () => {
14+
const { 0: token } = useGlobal("token");
15+
16+
return (
17+
<nav>
18+
<ul>
19+
<li>
20+
<Link type="li" to="/">
21+
Home
22+
</Link>
23+
</li>
24+
{!token && (
25+
<li>
26+
<Link type="li" to="/login">
27+
Login
28+
</Link>
29+
</li>
30+
)}
31+
{token && (
32+
<>
33+
<li>
34+
<Link type="li" to="/profile">
35+
Profile
36+
</Link>
37+
</li>
38+
<li>
39+
<Logout />
40+
</li>
41+
</>
42+
)}
43+
</ul>
44+
</nav>
45+
);
46+
};
447

548
function App() {
649
return (
7-
<div className="App">
8-
<header className="App-header">
9-
<img src={logo} className="App-logo" alt="logo" />
10-
<p>
11-
Edit <code>src/App.js</code> and save to reload.
12-
</p>
13-
<a
14-
className="App-link"
15-
href="https://reactjs.org"
16-
target="_blank"
17-
rel="noopener noreferrer"
18-
>
19-
Learn React
20-
</a>
21-
</header>
22-
</div>
50+
<Router>
51+
<NavBar />
52+
<Route path="/" exact component={Home} />
53+
<Route path="/login" component={Login} />
54+
<PrivateRoute path="/profile" component={Profile} />
55+
{/* <Route component={NotFound} /> */}
56+
</Router>
2357
);
2458
}
2559

ui/src/App.test.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

ui/src/api/client.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import axios from "axios";
2+
3+
const localUrl = `//${window.location.host.split(":")[0]}:1337`;
4+
const client = axios.create({
5+
baseURL: localUrl,
6+
});
7+
8+
export default client;

ui/src/components/LoginForm.jsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React, { useState, useGlobal } from "reactn";
2+
import { Redirect } from "react-router-dom";
3+
import client from "../api/client";
4+
5+
const LoginForm = (props) => {
6+
const [formState, setFormState] = useState({
7+
8+
password: "password123"
9+
});
10+
const [loggedIn, setLoggedIn] = useState(false);
11+
12+
const { 1: setToken } = useGlobal("token");
13+
14+
const handleChange = e => {
15+
setFormState({
16+
...formState,
17+
[e.target.name]: e.target.value
18+
});
19+
};
20+
21+
const handleSubmit = async e => {
22+
e.preventDefault();
23+
24+
const { data } = await client.post(
25+
"/auth/login",
26+
formState
27+
);
28+
setToken(data.token);
29+
setLoggedIn(true);
30+
};
31+
32+
return (
33+
<form onSubmit={handleSubmit}>
34+
{(loggedIn) && (
35+
(props.redirect) && (
36+
<Redirect push to={props.redirect} />
37+
)
38+
)}
39+
<div>
40+
<input
41+
type="email"
42+
name="email"
43+
value={formState.email}
44+
onChange={handleChange}
45+
placeholder="E-mail"
46+
/>
47+
</div>
48+
<div>
49+
<input
50+
type="password"
51+
name="password"
52+
value={formState.password}
53+
onChange={handleChange}
54+
placeholder="Password..."
55+
/>
56+
</div>
57+
<div>
58+
<button>Sign In</button>
59+
</div>
60+
</form>
61+
);
62+
};
63+
64+
export default LoginForm;

ui/src/components/Logout.jsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React, { useGlobal } from "reactn";
2+
import { Redirect } from "react-router-dom";
3+
4+
const Logout = (props) => {
5+
const [token, setToken] = useGlobal("token");
6+
return (
7+
<>
8+
{(!token && props.redirect) && (
9+
<Redirect push to={props.redirect} />
10+
)}
11+
<button onClick={() => setToken(null)}>Logout</button>
12+
</>
13+
);
14+
}
15+
16+
export default Logout;

0 commit comments

Comments
 (0)