npm init
npm i sequelize sequelize-cli mysql2
npx sequelize init
mkdir views routes public passport
touch .env
npm i express cookie-parser express-session morgan multer dotenv nunjucks
npm i -D nodemon
require packages, middlewares
dotenv config
configure template engine
configure session and cookie
connect db
configure default router
general error handling router with error rendering template
copy from textbook repo
define current environment
cookie-secret
port number
static associate(db) { // db object contains other models including this
db.User.hasMany(db.Post);
db.User.belongsToMany(db.User, { //user gets followingId as Follwers
foreignKey: 'followingId',
as: 'Followers',
through: 'Follow',
});
db.User.belongsToMany(db.User, { // user gets followerId as Followings
foreignKey: 'followerId',
as: 'Followings',
through: 'Follow',
});
}
configure id, password, host, etc..
$ npx sequelize db:create
const { sequelize } = require('./models');
sequelize.sync({ force: false })
.then((result) => {
console.log(result.options.host, result.options.database, result.options.username);
console.log('DB connection successful')
})
.catch((err) => console.error(err, 'DB connection failed'));
$ npm i passport passport-local passport-kakao bcrypt
// app.js
const passport = require('passport');
const passportConfig = require('./passport');
const app = express();
passportConfig();
app.use(session(/* */));
app.use(passport.initialize()); // store passport info in req
app.use(passport.session()); // store passport info in session
passport.authenticate()
called from router or controllerreq.login()
passport.serializeUser()
req.session
const passport = require('passport');
const local = require('./localStrategy');
const kakao = require('./kakaoStrategy');
const User = require('../models/user');
module.exports = () => {
passport.serializeUser((user, done) => {
done(null, user.id);
}); // store partial user info to session
passport.deserializeUser((id, done) => {
User.findOne({ where: { id } })
.then(user => done(null, user));
.catch(err => done(err));
}); // restore partial user info from session, query full info from db.
local();
kakao();
}
use these middlewares to verify login status
note that passport module adds isAuthenticated()
to req object.
module.exports = isLoggedIn = (req, res, next) => {
if (req.isAuthenticated()) {
next();
} else {
res.status(403).send('login required');
}
}
module.exports = isNotLoggedIn = (req, res, next) => {
if (!req.isAuthenticated()) {
next();
} else {
const message = encodeURIComponent('already logged in');
res.redirect(`/?error=${message}`);
}
}
passport has nothing to do with join process
passport controls login only (at least in below code)
NEVER forget to hashing passwords when creating new user
const hash = await bcrypt.hash(password, 12);
await User.create({email, nick, password:hash,})
login router defines and calls passport.authenticate()
passport.authenticate()
calls localStrategy registered in index.js
returned value of passport.authenticate()
is used inside of callback(authError, user, info)
req.logout()
is now async
router.post('/login', isNotLoggedIn, (req, res, next) => {
passport.authenticate('local' /* Local Strategy Called HERE */, (authError, user, info) => {
if (authError) {
console.error(authError);
return next(authError);
}
if (!user) {
return res.redirect(`/?loginError=${info.message}`);
}
return req.login(user, (loginError) => {
if (loginError) {
console.error(loginError);
return next(loginError);
}
return res.redirect('/');
});
})(req, res, next);
});
router.get('/logout', isLoggedIn, (req, res, next) => {
req.logout(() => {
req.session.destroy(); // remove req.session object
res.redirect('/'); // remove req.user object
});
});
module.exports = router;
책에 있는 내용을 충실히 따라서 구현했다.
카카오 로그인 기능 학습내용을 응용해보기 위해 naver를 혼자서 구현해봤다.
마침 카카오 서버가 터졌네...
npm install passport-naver-v2
const passport = require('passport');
const NaverStrategy = require('passport-naver-v2').Strategy;
const User = require('../models/user');
module.exports = () => {
passport.use(new NaverStrategy({
clientID: process.env.NAVER_ID,
clientSecret: process.env.NAVER_PW,
callbackURL: '/auth/naver/callback'
}, async (accessToken, refreshToken, profile, done) => {
console.log('naver profile', profile);
try {
const exUser = await User.findOne({ where: { snsId: profile.email.split('@')[0], provider: 'naver' } });
if (exUser) {
done(null, exUser);
} else {
const newUser = await User.create({
email: profile.email,
nick: profile.nickname,
snsId: profile.email.split('@')[0],
provider: 'naver',
});
done(null, newUser);
}
} catch (err) {
console.error(err);
done(err);
}
}));
};
router.get('/naver', passport.authenticate('naver',{authType:'reprompt'}));
router.get('/naver/callback', passport.authenticate('naver', {
failureRedirect: '/',
}), (req, res) => {
res.redirect('/');
});
need to focus on how to make new formdata and extract file object from html element
frontend html
<!--- input --->
<div>
<label id="img-label" for="img">사진 업로드</label>
<input id="img" type="file" accept="image/*" />
<button id="twit-btn" type="submit" class="btn">짹짹</button>
</div>
<!--- script --->
<script>
if (document.getElementById('img')) {
document.getElementById('img').addEventListener('change', function (e) {
// ATTENTION HERE!!
const formData = new FormData()
console.log(this, this.files)
formData.append('img', this.files[0]) // adding file stream to formData
axios
.post('/post/img', formData)
.then((res) => {
document.getElementById('img-url').value = res.data.url
document.getElementById('img-preview').src = res.data.url
document.getElementById('img-preview').style.display = 'inline'
// ATTENTION HERE!!
})
.catch((err) => {
console.error(err)
})
})
}
</script>
express router
const upload = multer({
storage: multer.diskStorage({
destination(req, file, cb) {
cb(null, 'uploads/');
},
filename(req, file, cb) {
const ext = path.extname(file.originalname);
cb(null, path.basename(file.originalname, ext) + Date.now() + ext);
}
}),
limits: { fileSize: 5 * 1024 * 1024 }
});
// get multer filestream input from formdata[img]
router.post('img', isLoggedIn, upload.single('img'), (req, res, next) => {
console.log(req.files);
res.json({ url: `/img/${req.file.filename}` });
// returns created img uri with res obj;
});