Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend auth #14

Merged
merged 30 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1f9c029
Enable connection to Mongo
Qrtn Jan 9, 2021
1132904
Merge branch 'main' of github.com:hack4impact-uiuc/memberdb-tool into…
Qrtn Jan 9, 2021
e72171e
Install passport and express-session
Qrtn Jan 9, 2021
165d5c2
Change most member attributes to not required
Qrtn Jan 9, 2021
c205887
Add auth routes
Qrtn Jan 9, 2021
a6b98fe
Add passport setup with cookie sessions
Qrtn Jan 9, 2021
82161ad
Merge branch 'main' of github.com:hack4impact-uiuc/memberdb-tool into…
Qrtn Jan 12, 2021
ccfcf7b
Add options for express-session
Qrtn Jan 12, 2021
e823022
Add options for express-session
Qrtn Jan 12, 2021
0d5b7e4
Move passport-setup to root
Qrtn Jan 12, 2021
7799472
Add middleware for auth
Qrtn Jan 12, 2021
c903f28
Change auth routes to use result instead of data
Qrtn Jan 12, 2021
ea19c9f
Add protected route as an example
Qrtn Jan 12, 2021
ab65429
Remove home test and replace with dummy test
Qrtn Jan 12, 2021
7a91549
Merge branch 'main' of github.com:hack4impact-uiuc/memberdb-tool into…
Qrtn Jan 12, 2021
08b8433
Remove dummy test
Qrtn Jan 13, 2021
f904019
Remove console.log
Qrtn Jan 13, 2021
fc9e9d6
Remove extra || null
Qrtn Jan 13, 2021
bb9c7d9
Merge branch 'main' of github.com:hack4impact-uiuc/memberdb-tool into…
Qrtn Jan 13, 2021
288fb43
Add back home.test.js and new env vars to api.yaml
Qrtn Jan 13, 2021
e7138b2
Add example dev.env
Qrtn Jan 13, 2021
af71365
Add redirectURI endpoint to allow for changing Vercel subdomains
Qrtn Jan 13, 2021
e70b9ec
Add redirect URLs as parameters for /login
Qrtn Jan 13, 2021
ebd34ed
Format auth.js
Qrtn Jan 13, 2021
8dc2b4d
Remove FRONTEND_URI from example env file
Qrtn Jan 13, 2021
d4f710f
Enable failure flash
Qrtn Jan 13, 2021
85b5317
Merge branch 'main' of github.com:hack4impact-uiuc/memberdb-tool into…
Qrtn Jan 13, 2021
4b5a8a1
Add back .env files
Qrtn Jan 13, 2021
3814432
Add vercel env vars
Qrtn Jan 13, 2021
aa2969b
Remove OAUTH_CALLBACK_URI from production env
Qrtn Jan 13, 2021
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
5 changes: 4 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
"debug": "~2.6.9",
"dotenv": "^8.2.0",
"express": "~4.16.1",
"express-session": "^1.17.1",
"helmet": "^3.21.1",
"http-errors": "~1.6.3",
"if-env": "^1.0.4",
"isomorphic-unfetch": "^3.0.0",
"mongodb": "^3.3.2",
"mongoose": "^5.7.5",
"morgan": "~1.9.1"
"morgan": "~1.9.1",
"passport": "^0.4.1",
"passport-google-oauth20": "^2.0.0"
},
"devDependencies": {
"babel-eslint": "^10.0.3",
Expand Down
36 changes: 36 additions & 0 deletions api/src/api/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const express = require('express');
const router = express.Router();
const passport = require('passport');

router.get('/user', (req, res) => {
const user = req.user || null;
mattwalo32 marked this conversation as resolved.
Show resolved Hide resolved

res.json({
result: user,
success: true,
});
});

router.get(
'/login',
passport.authenticate('google', { scope: ['profile', 'email'] }),
);

router.get(
'/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
// Successful authentication, redirect home.
res.redirect('/');
},
);

router.post('/logout', (req, res) => {
req.logout();
res.json({
message: 'Logged out',
success: true,
});
});

module.exports = router;
14 changes: 14 additions & 0 deletions api/src/api/home.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const express = require('express');
const router = express.Router();
const { errorWrap } = require('../middleware');
const { requireRegistered } = require('../middleware/auth');

const Home = require('../models/home');

Expand All @@ -16,4 +17,17 @@ router.get(
}),
);

router.get(
'/registeredOnly',
mattwalo32 marked this conversation as resolved.
Show resolved Hide resolved
[requireRegistered],
errorWrap(async (req, res) => {
const home = await Home.findOne();
res.status(200).json({
message: `Successfully returned home text`,
success: true,
result: home.text,
});
}),
);

module.exports = router;
2 changes: 2 additions & 0 deletions api/src/api/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const home = require('./home');
const auth = require('./auth');

module.exports = {
home,
auth,
};
19 changes: 18 additions & 1 deletion api/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ const helmet = require('helmet');
const logger = require('morgan');
const mongoose = require('mongoose');
const path = require('path');
const routes = require('./routes');
const session = require('express-session');
const bodyParser = require('body-parser');
const passport = require('passport');
const routes = require('./routes');

const app = express();
const { errorHandler } = require('./middleware');
Expand Down Expand Up @@ -40,6 +42,21 @@ app.use(logger('dev'));
app.use(bodyParser.json({ limit: '2.1mb' }));
app.use(bodyParser.urlencoded({ limit: '2.1mb', extended: false }));

// Session support, needed for authentication
app.use(
session({
secret: process.env.SESSION_SECRET,
cookie: {},
resave: false,
saveUninitialized: false,
}),
);

// Passport setup
require('./passport-setup');
app.use(passport.initialize());
app.use(passport.session());

app.use('/', routes);

app.get('/', (req, res) => res.json('API working!'));
Expand Down
41 changes: 41 additions & 0 deletions api/src/middleware/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const { levelEnum } = require('../models/member');

const requireLevel = (levels) => (req, res, next) => {
console.log(req.user);
if (!req.user || !levels.includes(req.user.level)) {
return res.status(401).json({
success: false,
message: 'Unauthorized',
});
} else {
next();
}
};

const requireRegistered = requireLevel(Object.values(levelEnum));

const requireMember = requireLevel([
levelEnum.ADMIN,
levelEnum.DIRECTOR,
levelEnum.LEAD,
levelEnum.MEMBER,
]);

const requireLead = requireLevel([
levelEnum.ADMIN,
levelEnum.DIRECTOR,
levelEnum.LEAD,
]);

const requireDirector = requireLevel([levelEnum.ADMIN, levelEnum.DIRECTOR]);

const requireAdmin = requireLevel([levelEnum.ADMIN]);

module.exports = {
requireLevel,
requireRegistered,
requireMember,
requireLead,
requireDirector,
requireAdmin,
};
2 changes: 2 additions & 0 deletions api/src/middleware/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const errorHandler = require('./errorHandler');
const errorWrap = require('./errorWrap');
const auth = require('./auth');

module.exports = {
errorHandler,
errorWrap,
auth,
};
34 changes: 17 additions & 17 deletions api/src/models/member.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,50 +53,50 @@ const classStandingEnum = {
};

const Member = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
firstName: { type: String, required: false },
lastName: { type: String, required: false },
oauthID: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
phone: { type: String, required: true },
netID: { type: String, required: true },
UIN: { type: String, required: true },
major: { type: String, required: true },
birthdate: { type: Date, required: true },
email: { type: String, required: false, unique: true },
phone: { type: String, required: false },
netID: { type: String, required: false },
UIN: { type: String, required: false },
major: { type: String, required: false },
birthdate: { type: Date, required: false },
github: { type: String, required: false },
snapchat: { type: String, required: false },
instagram: { type: String, required: false },
areDuesPaid: { type: Boolean, required: true },
areDuesPaid: { type: Boolean, required: false },

gradYear: { type: Number, required: true },
gradYear: { type: Number, required: false },
gradSemester: {
type: String,
enum: Object.values(semesterEnum),
required: true,
required: false,
},

classStanding: {
type: String,
enum: Object.values(classStandingEnum),
required: true,
required: false,
},

generationYear: { type: Number, required: true },
generationYear: { type: Number, required: false },
generationSemester: {
type: String,
enum: Object.values(semesterEnum),
required: true,
required: false,
},

location: {
type: String,
enum: Object.values(locationEnum),
required: true,
required: false,
},

role: {
type: String,
enum: Object.values(roleEnum),
required: true,
required: false,
},

level: {
Expand All @@ -108,7 +108,7 @@ const Member = new mongoose.Schema({
status: {
type: String,
enum: Object.values(statusEnum),
required: true,
required: false,
},
});

Expand Down
52 changes: 52 additions & 0 deletions api/src/passport-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const passport = require('passport');
const Member = require('./models/member');

// Defines the default level a user gets assigned with upon first sign-in
const DEFAULT_LEVEL = process.env.DEFAULT_LEVEL || Member.levelEnum.TBD;

passport.serializeUser((user, done) => {
done(null, user._id);
});

passport.deserializeUser((id, done) => {
// Find in DB and return user
Member.findById(id, (err, user) => {
if (err) {
console.error('err');
done(err);
}
done(null, user);
});
});

passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.OAUTH_CALLBACK_URI,
},
async (accessToken, refreshToken, profile, cb) => {
// find the user in the database based on their oauth id
const user = await Member.findOne({ oauthID: profile.id });

console.log(profile);
mattwalo32 marked this conversation as resolved.
Show resolved Hide resolved

if (user) {
// user exists
return cb(null, user);
} else {
const newUser = await new Member({
firstName: profile.name.givenName,
lastName: profile.name.familyName,
oauthID: profile.id,
email: profile.emails[0].value,
level: DEFAULT_LEVEL,
}).save();

cb(null, newUser);
}
},
),
);
3 changes: 2 additions & 1 deletion api/src/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const express = require('express');

const router = express.Router();
const { home } = require('../api');
const { home, auth } = require('../api');

router.use('/api/home', home);
router.use('/api/auth', auth);

module.exports = router;
3 changes: 3 additions & 0 deletions api/test/dummy.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
describe('Dummy test', () => {
test('Dummy test', () => {});
mattwalo32 marked this conversation as resolved.
Show resolved Hide resolved
});
26 changes: 0 additions & 26 deletions api/test/home.test.js

This file was deleted.

Loading