Jonathan Wexler/吉川 邦夫 翔泳社 2019年09月25日頃
Lesson25
認証処理のまとめ
- npm install express ejs express-ejs-layouts connect-flash cookie-parser express-session express-validator passport passport-local-mongoose method-override
"use strict";
const express = require("express"),
layouts = require("express-ejs-layouts"),
app = express(),
router = express.Router(),
usersController = require("./controllers/usersController.js"),
mongoose = require("mongoose"),
methodOverride = require("method-override"),
passport = require("passport"),
cookieParser = require("cookie-parser"),
expressSession = require("express-session"),
connectFlash = require("connect-flash"),
User = require("./models/user");
mongoose.connect(
"mongodb://localhost:27017/lesson25",
{ useNewUrlParser: true }
);
app.set("port", process.env.PORT || 3000);
app.set("view engine", "ejs");
router.use(
methodOverride("_method", {
methods: ["POST", "GET"]
})
);
router.use(layouts);
router.use(express.static("public"));
router.use(
express.urlencoded({
extended: false
})
);
router.use(express.json());
router.use(cookieParser("secretLesson25"));
router.use(
expressSession({
secret: "secretLesson25",
cookie: {
maxAge: 4000000
},
resave: false,
saveUninitialized: false
})
);
router.use(connectFlash());
router.use(passport.initialize());
router.use(passport.session());
passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
router.use((req, res, next) => {
res.locals.loggedIn = req.isAuthenticated();
res.locals.currentUser = req.user;
res.locals.flashMessages = req.flash();
next();
});
router.get("/", (req, res) => {
res.render("index");
});
router.get("/users", usersController.index, usersController.indexView);
router.get("/users/new", usersController.new);
router.post(
"/users/create",
usersController.validate,
usersController.create,
usersController.redirectView
);
router.get("/users/login", usersController.login);
router.post("/users/login", usersController.authenticate);
router.get("/users/logout", usersController.logout, usersController.redirectView);
app.use("/", router);
app.listen(app.get("port"), () => {
console.log(`Server running at http://localhost:${app.get("port")}`);
});
<div class="data-form">
<form class="form-signin" action="/users/create" method="POST">
<h2 class="form-signin-heading">Create a new user:</h2>
<label for="name">Name</label>
<input type="text" name="name" id="name" class="form-control" placeholder="name" autofocus>
<label for="inputPassword">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
<label for="inputEmail">Email address</label>
<input type="email" name="email" id="inputEmail" class="form-control" placeholder="Email address" required>
<label for="inputZipCode">Zip Code</label>
<input type="text" name="zipCode" id="inputZipCode" pattern="[0-9]{5}" class="form-control" placeholder="Zip Code" required>
<br /><button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
<form class="form-signin" action="/users/login" method="POST">
<h2 class="form-signin-heading">Login:</h2>
<label for="inputEmail" class="sr-only">Email</label>
<input type="text" name="email" id="inputEmail" class="form-control" placeholder="Email" autofocus required>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
</form>
<h2 class="center">Users Table</h2>
<div class="center">
<a class="button" href="/users/new">Create User</a>
</div>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Zip Code</th>
</tr>
</thead>
<tbody>
<% users.forEach(user => { %>
<tr>
<td>
<%= user.name %>
</td>
<td>
<%= user.email %>
</td>
<td>
<%= user.zipCode %>
</td>
</tr>
<% }); %>
</tbody>
</table>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</head>
<body>
<div class="login">
<a href="/">TOP</a>
<% if (loggedIn) { %>
<p>Logged in as
<a href="<%=`/users/${currentUser._id}`%>">
<%= currentUser.name %></a>
<span class="log-out">
<a href="/users/logout">Log out</a>
</span>
</p>
<%} else {%>
<a href="/users/login">Log In</a>
<% } %>
</div>
<div class="flashes">
<% if (flashMessages) { %>
<% if (flashMessages.success) { %>
<div class="flash success">
<%= flashMessages.success %>
</div>
<% } else if (flashMessages.error) { %>
<div class="flash error">
<%= flashMessages.error %>
</div>
<% } %>
<% } %>
</div>
<%- body %>
</body>
</html>
<div class="col-sm-6">
<dl>
<li><a href="/users">/users</a></li>
<li><a href="/users/new">/users/new</a></li>
</dl>
</div>
"use strict";
const User = require("../models/user"),
passport = require("passport"),
{ body, validationResult } = require('express-validator'),
getUserParams = body => {
return {
name: body.name,
email: body.email,
password: body.password,
zipCode: body.zipCode
};
};
module.exports = {
index: (req, res, next) => {
User.find()
.then(users => {
res.locals.users = users;
next();
})
.catch(error => {
console.log(`Error fetching users: ${error.message}`);
next(error);
});
},
indexView: (req, res) => {
res.render("users/index");
},
new: (req, res) => {
res.render("users/new");
},
create: (req, res, next) => {
if (req.skip) return next();
let newUser = new User(getUserParams(req.body));
User.register(newUser, req.body.password, (e, user) => {
if (user) {
req.flash("success", `${user.name}'s account created successfully!`);
res.locals.redirect = "/users";
next();
} else {
req.flash("error", `Failed to create user account because: ${e.message}.`);
res.locals.redirect = "/users/new";
next();
}
});
},
redirectView: (req, res, next) => {
let redirectPath = res.locals.redirect;
if (redirectPath !== undefined) res.redirect(redirectPath);
else next();
},
login: (req, res) => {
res.render("users/login");
},
validate: (
body("email").trim().isEmail().notEmpty(),
body("zipCode").trim().isInt().isLength({
min: 5,
max: 5
}).notEmpty(),
body("password").trim().notEmpty(),
(req, res, next) => {
const error = validationResult(req);
if (!error.isEmpty()) {
let messages = error.array().map(e => e.msg);
req.skip = true;
req.flash("error", messages.join(" and "));
res.locals.redirect = "/users/new";
next();
} else {
next();
}
}
),
authenticate: passport.authenticate("local", {
failureRedirect: "/users/login",
failureFlash: "Failed to login.",
successRedirect: "/",
}),
logout: (req, res, next) => {
req.logout(()=>{
req.flash("success", "You have been logged out!");
res.locals.redirect = "/";
next();
});
}
};
"use strict";
const mongoose = require("mongoose"),
{ Schema } = require("mongoose"),
passportLocalMongoose = require("passport-local-mongoose");
const userSchema = new Schema(
{
name: {
type: String,
trim: true
},
email: {
type: String,
required: true,
lowercase: true,
unique: true
},
zipCode: {
type: Number,
min: [1000, "Zip code too short"],
max: 99999
},
password: {
type: String,
required: true
},
},
{
timestamps: true
}
);
userSchema.plugin(passportLocalMongoose, {
usernameField: "email"
});
module.exports = mongoose.model("User", userSchema);