├── .gitignore ├── README.md ├── views ├── dashboard.pug ├── stories │ ├── index.pug │ ├── add.pug │ ├── edit.pug │ └── show.pug ├── profile.pug ├── index.pug ├── partials │ ├── _footer.pug │ └── _mixins.pug └── layout.pug ├── middlewares └── auth.js ├── routes ├── index.js ├── auth.js └── stories.js ├── package.json ├── helper.js ├── models ├── User.js └── Story.js ├── config └── passport.js ├── public ├── styles │ └── style.css └── img │ ├── public.svg │ └── myProfile.svg └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | keys.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository is an experiment to try passport Google OAuth Authentication. 2 | -------------------------------------------------------------------------------- /views/dashboard.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | include partials/_mixins 3 | block content 4 | .container.mt-3 5 | .row 6 | .col-md-12 7 | +table(stories) 8 | 9 | -------------------------------------------------------------------------------- /views/stories/index.pug: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | include ../partials/_mixins 3 | block content 4 | if stories 5 | .container.mt-3 6 | .row 7 | each story in stories 8 | .col-md-3.mb-3 9 | +storyCard(story) -------------------------------------------------------------------------------- /middlewares/auth.js: -------------------------------------------------------------------------------- 1 | exports.loginAuth = (req, res, next) => { 2 | 3 | if(req.isAuthenticated()){ 4 | return next(); 5 | } 6 | res.redirect('/'); 7 | 8 | }; 9 | 10 | exports.guestCheck = (req, res, next) => { 11 | 12 | // if(req.isAuthenticated()){ 13 | // return res.redirect('/dashboard'); 14 | // } 15 | return next(); 16 | 17 | }; -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const { loginAuth, guestCheck } = require('../middlewares/auth'); 4 | const { Story } = require('../models/Story'); 5 | router.get('/', guestCheck, (req, res) => { 6 | res.render('index'); 7 | }); 8 | 9 | router.get('/dashboard', loginAuth, async (req, res) => { 10 | let stories = await Story.find({ 11 | user: req.user.id 12 | }).populate('user'); 13 | res.render('dashboard',{ 14 | stories 15 | }); 16 | }); 17 | 18 | 19 | module.exports = router; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "googleAuthNode", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node app.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cookie-parser": "^1.4.4", 14 | "express": "^4.16.4", 15 | "express-session": "^1.15.6", 16 | "method-override": "^3.0.0", 17 | "moment": "^2.24.0", 18 | "mongoose": "^5.4.19", 19 | "passport": "^0.4.0", 20 | "passport-google-oauth20": "^2.0.0", 21 | "pug": "^2.0.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /helper.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const moment = require('moment'); 3 | 4 | exports.pre = (data) => { 5 | return `
${JSON.stringify(data)}
`; 6 | }; 7 | 8 | exports.serveSVG = (filename) => { 9 | return fs.readFileSync(`./public/img/${filename}.svg`) 10 | }; 11 | 12 | exports.bodyPreview = (input) => { 13 | if (input) { 14 | let body = input.replace(/<(?:.|\n)*?>/gm, ""); 15 | return body.length > 40 ? body.substring(0, 40) + '...' : body; 16 | } 17 | } 18 | 19 | exports.dateFormat = (date, format) => { 20 | return moment(date).format(format); 21 | }; -------------------------------------------------------------------------------- /views/profile.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | //- != h.pre(stories) 5 | //- != h.pre(user) 6 | .container 7 | .profile.mb-3 8 | .profile__cover 9 | .profile__header.p-3(style=`background-image: url(${user.image})`) 10 | .profile__primary 11 | img.profile__image(src=user.image) 12 | p.lead.text-center.font-weight-bold.mt-3= user.firstName+' '+user.lastName 13 | .card.p-3(style="margin-top:140px") 14 | .row 15 | .col-md-3 16 | .stat 17 | | Total stories = #{stories.length} -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | const userSchema = new mongoose.Schema({ 5 | googleId: { 6 | type: String, 7 | required: true, 8 | unique: true 9 | }, 10 | email: { 11 | type: String, 12 | required: true, 13 | unique: true 14 | }, 15 | firstName: { 16 | type: String, 17 | required: true 18 | }, 19 | lastName: { 20 | type: String, 21 | required: true 22 | }, 23 | image: { 24 | type: String 25 | } 26 | }); 27 | 28 | const User = mongoose.model('user',userSchema); 29 | 30 | exports.User = User; 31 | exports.userSchema = userSchema; -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const passport = require('passport'); 4 | 5 | router.get('/google', passport.authenticate('google',{ 6 | scope: ['profile', 'email'] 7 | })); 8 | 9 | router.get('/google/callback', 10 | passport.authenticate('google', { failureRedirect: '/' }),(req, res) => { 11 | res.redirect('/stories'); 12 | }); 13 | 14 | router.get('/logout',(req, res) => { 15 | req.logout(); 16 | res.redirect('/'); 17 | }); 18 | 19 | router.get('/verify',(req, res) => { 20 | if(req.user){ 21 | console.log(req.user); 22 | } else { 23 | console.log('Not auth'); 24 | } 25 | }) 26 | 27 | 28 | 29 | module.exports = router; -------------------------------------------------------------------------------- /views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | include partials/_mixins 3 | block content 4 | +storiesNavbar 5 | .jumbotron.bg-light.rounded-0 6 | .container 7 | if !user 8 | .display-5 9 | span Sign in to live your life worthy 10 | br 11 | a.btn.btn-sm.btn-danger.mt-3.text-uppercase(href="/auth/google") Sign in with Google 12 | else 13 | .text-success 14 | .display-4 15 | | Welcome #{user.firstName}, 16 | p.lead 17 | | Read the experiences of others to improve your life -------------------------------------------------------------------------------- /models/Story.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | 4 | const storySchema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | required: true, 8 | unique: true 9 | }, 10 | body: { 11 | type: String, 12 | required: true, 13 | unique: true 14 | }, 15 | status: { 16 | type: String, 17 | required: true 18 | }, 19 | allowComments: { 20 | type: Boolean, 21 | required: true 22 | }, 23 | comments: [{ 24 | commentBody:{ 25 | type: String, 26 | }, 27 | commentDate:{ 28 | type: Date, 29 | default: Date.now 30 | }, 31 | commentUser:{ 32 | type: mongoose.Schema.Types.ObjectId, 33 | ref: 'user' 34 | } 35 | }], 36 | user: { 37 | type: mongoose.Schema.Types.ObjectId, 38 | ref: 'user' 39 | }, 40 | date: { 41 | type: Date, 42 | default: Date.now 43 | } 44 | }); 45 | 46 | const Story = mongoose.model('story',storySchema); 47 | 48 | exports.Story = Story; 49 | exports.storySchema = storySchema; -------------------------------------------------------------------------------- /views/stories/add.pug: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | .container-fluid 5 | .row.my-3 6 | .col-md-6.mx-auto 7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 | 19 |
20 | .form-group 21 | label(for="body") 22 | textarea.form-control#body(name="body", cols="30", rows="10") 23 |
24 | 25 | 26 |
27 | 28 |
-------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | const GoogleStrategy = require('passport-google-oauth20').Strategy; 2 | const passport = require('passport'); 3 | const key = require('./keys'); 4 | const { User } = require('../models/User'); 5 | 6 | module.exports = function (passport) { 7 | passport.use(new GoogleStrategy({ 8 | clientID: key.googleClientID, 9 | clientSecret: key.googleClientScret, 10 | callbackURL: "/auth/google/callback", 11 | proxy: true 12 | }, 13 | async (accessToken, refreshToken, profile, done) => { 14 | 15 | const newUser = { 16 | googleId: profile.id, 17 | email: profile.emails[0].value, 18 | firstName: profile.name.givenName, 19 | lastName: profile.name.familyName, 20 | image: profile.photos[0].value 21 | }; 22 | 23 | let user = await User.findOne({ 24 | googleId: newUser.googleId 25 | }); 26 | if (user) { 27 | done(null, user); 28 | } 29 | else { 30 | user = new User(newUser); 31 | await user.save(); 32 | done(null, user) 33 | } 34 | 35 | })); 36 | passport.serializeUser(function (user, done) { 37 | done(null, user.id); 38 | }); 39 | 40 | passport.deserializeUser(function (id, done) { 41 | User.findById(id, function (err, user) { 42 | done(err, user); 43 | }); 44 | }); 45 | }; -------------------------------------------------------------------------------- /views/partials/_footer.pug: -------------------------------------------------------------------------------- 1 | 2 | mixin footer 3 | -------------------------------------------------------------------------------- /views/stories/edit.pug: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | //- != h.pre(story) 5 | .container-fluid 6 | .row.my-3 7 | .col-md-6.mx-auto 8 | form(action=`/stories/${story.id}?_method=PUT` method="post") 9 | input(type="hidden" name="_method" value="PUT") 10 |
11 | 12 | input.form-control#title(type="text", name="title", value=story.title) 13 |
14 |
15 | 16 | select(name="status", class="form-control", id="visibility", value=story.status) 17 | 18 | 19 | 20 |
21 | .form-group 22 | label(for="body") 23 | textarea.form-control#body(name="body", cols="30", rows="10")= story.body 24 |
25 | input(checked=story.allowComments type="checkbox" name="allowComments" class="form-check-input" id="comments") 26 | 27 |
28 | -------------------------------------------------------------------------------- /public/styles/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | min-height:100vh; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main{ 8 | flex: 1; 9 | } 10 | footer{ 11 | font-size: 14px 12 | } 13 | 14 | .text-sm{ 15 | font-size: 12px; 16 | } 17 | 18 | .publicCard p.card-text { 19 | height: 60px; 20 | } 21 | 22 | .profile__header{ 23 | filter: blur(12px) saturate(0.4); 24 | -webkit-filter: blur(12px) saturate(0.4); 25 | background-repeat: no-repeat; 26 | background-size: cover; 27 | overflow: hidden; 28 | min-height: 200px; 29 | position: relative; 30 | transform: scale(1.2) 31 | 32 | } 33 | .profile__header::after{ 34 | position: absolute; 35 | top: 0; 36 | left: 0; 37 | background: rgba(0,0,0,0.5); 38 | width: 100%; 39 | height: 100%; 40 | content: ''; 41 | } 42 | .profile{ 43 | position: relative; 44 | } 45 | .profile__cover{ 46 | position: relative; 47 | overflow: hidden; 48 | } 49 | 50 | .profile__primary{ 51 | position: absolute; 52 | bottom: -14px; 53 | left: 50%; 54 | transform: translate(-50%,50%); 55 | /* width: 100%; */ 56 | } 57 | 58 | .profile__image{ 59 | width: 150px; 60 | border-radius: 50%; 61 | } 62 | 63 | img, .prevent-select{ 64 | user-select: none; 65 | -webkit-user-drag: none; 66 | -moz-user-drag: none; 67 | 68 | } 69 | 70 | 71 | .postedUserImage{ 72 | width: 32px; 73 | border-radius: 50%; 74 | } 75 | 76 | .showUserImage{ 77 | max-width: 150px; 78 | border-radius: 50%; 79 | } 80 | 81 | 82 | 83 | .public-experience svg{ 84 | width: 20px; 85 | margin-right: 8px; 86 | } 87 | .public-experience{ 88 | font-size: 14px; 89 | 90 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const mongoose = require('mongoose'); 4 | const passport = require('passport'); 5 | const session = require('express-session'); 6 | const cookie = require('cookie-parser'); 7 | const path =require('path') ; 8 | const methodOverride = require('method-override'); 9 | 10 | const key = require('./config/keys'); 11 | const helper = require('./helper'); 12 | 13 | const root = require('./routes/index'); 14 | const auth = require('./routes/auth'); 15 | const stories = require('./routes/stories'); 16 | 17 | mongoose.connect(key.mongoURL, { useNewUrlParser: true, useCreateIndex: true }) 18 | .then(() => console.log('Connected to DB')) 19 | .catch((err) => console.log(err)); 20 | 21 | app.use(express.urlencoded({extended: true})); 22 | app.use(express.json()); 23 | 24 | app.use(express.static(path.join(__dirname,'public'))) 25 | 26 | // PASSING PASSPORT 27 | require('./config/passport')(passport); 28 | 29 | app.set('view engine','pug'); 30 | app.set('views',path.join(__dirname,'views')); 31 | 32 | app.use(cookie()); 33 | app.use(session({ 34 | secret: 'secret', 35 | resave: false, 36 | saveUninitialized: false 37 | })); 38 | 39 | app.use(methodOverride('_method')) 40 | 41 | app.use(passport.initialize()); 42 | app.use(passport.session()); 43 | 44 | app.use((req, res, next) => { 45 | res.locals.user = req.user || null; 46 | res.locals.h = helper; 47 | next(); 48 | }); 49 | 50 | app.use('/', root); 51 | app.use('/auth', auth); 52 | app.use('/stories', stories); 53 | 54 | // PORT ADDRESS AND CONNECTION 55 | const PORT = process.env.PORT || 5000; 56 | app.listen(PORT, () => console.log(`Listening to PORT ${PORT}`)); -------------------------------------------------------------------------------- /public/img/public.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /views/stories/show.pug: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | .container.mt-3 5 | .row 6 | .col-md-8.d-flex.flex-column 7 | .card.p-3.w-100.mb-3 8 | span.text-sm.mb-3= h.dateFormat(story.date, 'DD/MM/YYYY') 9 | h4.mb-3= story.title 10 | != story.body 11 | .p-3 12 | if user==null 13 | p.lead.text-center 14 | | You are not Logged in, please  15 | a(href="/auth/google") Login 16 | | to continue 17 | else 18 | form.mb-3(action=`/stories/comment/${story.id}` method="post") 19 | .form-group 20 | label(for="comment") Your comment 21 | textarea#comment.form-control(name="comment") 22 | button.btn.btn-sm.btn-success(type="submit" role="button") Submit 23 | div 24 | if story.comments.length<=0 25 | p.lead.text-center.p-3 26 | | No comments 27 | else 28 | p.lead.text-center 29 | | Comments 30 | each comment in story.comments 31 | .border-top.p-3 32 | .mb-2.font-weight-bold 33 | img.postedUserImage.mr-1(src=comment.commentUser.image) 34 | | #{comment.commentUser.firstName} 35 | small.font-italic 36 | | #{h.dateFormat(story.date,'MMMM Do YYYY')} 37 | p 38 | | #{comment.commentBody} 39 | 40 | .col-md-4 41 | .card.p-3.text-center 42 | p.lead.text-center= story.user.firstName+' '+story.user.lastName 43 | img.d-block.mx-auto.showUserImage(src=story.user.image) 44 | a.text-success(href=`/stories/user/${story.user.id}`) More from this user -------------------------------------------------------------------------------- /views/partials/_mixins.pug: -------------------------------------------------------------------------------- 1 | mixin storiesNavbar 2 | 18 | 19 | 20 | mixin storyCard(story) 21 |
22 |
23 | .d-flex.justify-content-between.align-items-center 24 | 25 | img.postedUserImage.mr-1(src=story.user.image) 26 | | #{story.user.firstName} 27 | 28 | small 29 | | #{h.dateFormat(story.date,'DD/MM/YYYY')} 30 |
#{story.title}
31 |

32 | | #{h.bodyPreview(story.body)} 33 |

34 | if user 35 | if story.user.id === user.id 36 | a.mr-2(href=`/stories/edit/${story.id}` class="btn btn-warning btn-sm") Edit 37 | a(href=`/stories/show/${story.id}` class="btn btn-success btn-sm") Read more 38 |
39 |
40 | 41 | mixin table(stories) 42 | table.table 43 | thead 44 | tr 45 | th title 46 | th status 47 | th date 48 | th actions 49 | tbody 50 | each story in stories 51 | tr 52 | td 53 | a(href=`/stories/show/${story.id}`) #{story.title} 54 | td= story.status 55 | td= h.dateFormat(story.date, 'DD/MM/YYYY') 56 | td.d-flex 57 | a.btn.btn-sm.btn-warning.mr-2(href=`/stories/edit/${story.id}`) Edit 58 | form(action=`/stories/delete/${story.id}?_method=DELETE` method="post") 59 | input(type="hidden" name="_method" value="DELETE") 60 | button.btn.btn-sm.btn-danger(href=`/stories/edit/${story.id}` role="button") Delete 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /views/layout.pug: -------------------------------------------------------------------------------- 1 | include partials/_footer 2 | 3 | html(lang="en") 4 | head 5 | meta(charset="UTF-8") 6 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 7 | meta(http-equiv="X-UA-Compatible", content="ie=edge") 8 | link(rel="stylesheet", href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css") 9 | link(rel="stylesheet", href="/styles/style.css") 10 | title Document 11 | body 12 | 13 | 34 | main 35 | block content 36 | 37 | 38 | +footer 39 | 40 | 41 | 42 | 43 | 44 | script. 45 | CKEDITOR.replace('body',{ 46 | plugins:'wysiwygarea,toolbar,basicstyles,link' 47 | }) 48 | 49 | -------------------------------------------------------------------------------- /public/img/myProfile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /routes/stories.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const {loginAuth, guestCheck} = require('../middlewares/auth'); 4 | const {Story} = require('../models/Story'); 5 | const {User} = require('../models/User'); 6 | 7 | router.get('/',async (req, res) => { 8 | const stories = await Story.find({ 9 | status: 'public', 10 | }).populate('user'); 11 | res.render('stories/index',{ 12 | stories 13 | }); 14 | }); 15 | 16 | router.get('/show/:id',async (req, res) => { 17 | 18 | let story = await Story.findOne({ 19 | _id: req.params.id 20 | }) 21 | .populate('user') 22 | .populate('comments.commentUser'); 23 | 24 | res.render('stories/show',{ 25 | story 26 | }); 27 | }); 28 | 29 | router.put('/:id', async (req, res) => { 30 | let story = await Story.findOneAndUpdate({_id: req.params.id},{ 31 | $set: { 32 | title: req.body.title, 33 | body: req.body.body, 34 | status: req.body.status, 35 | allowComments: (req.body.allowComments) ? true : false 36 | } 37 | },{new: true}); 38 | 39 | res.redirect('/dashboard'); 40 | 41 | }); 42 | 43 | router.post('/comment/:id',async (req, res) => { 44 | let story = await Story.findOne({_id: req.params.id}); 45 | if(story){ 46 | const newComment = { 47 | commentBody: req.body.comment, 48 | commentUser: req.user.id 49 | }; 50 | story.comments.unshift(newComment); 51 | await story.save(); 52 | res.redirect(`/stories/show/${req.params.id}`) 53 | } 54 | }) 55 | 56 | router.get('/add', loginAuth, (req, res) => { 57 | res.render('stories/add'); 58 | }); 59 | 60 | router.get('/user/:id', async (req, res) => { 61 | let stories = await Story.find({ 62 | user: req.params.id 63 | }); 64 | let user = await User.findOne({ 65 | _id: req.params.id 66 | }); 67 | res.render('profile',{stories, user}); 68 | }) 69 | 70 | router.get('/edit/:id', loginAuth,async (req, res) => { 71 | let story = await Story.findOne({ 72 | _id: req.params.id 73 | }); 74 | if(story){ 75 | res.render('stories/edit',{story}); 76 | } 77 | }); 78 | 79 | router.post('/', async (req, res) => { 80 | console.log(req.body); 81 | 82 | const newStory = { 83 | title: req.body.title, 84 | body: req.body.body, 85 | status: req.body.status, 86 | allowComments: (req.body.allowComments) ? true : false, 87 | user: req.user.id 88 | }; 89 | 90 | const story = new Story(newStory); 91 | await story.save(); 92 | 93 | res.redirect(`/stories/show/${story.id}`); 94 | 95 | }); 96 | 97 | router.delete('/delete/:id',async(req, res) => { 98 | let story = await Story.findOneAndDelete({_id: req.params.id}); 99 | if(story){ 100 | console.log(story); 101 | res.redirect('/dashboard'); 102 | 103 | } 104 | }) 105 | 106 | module.exports = router; --------------------------------------------------------------------------------