diff --git a/package.json b/package.json index 7a5ee63..bfb210a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "connect-mongo": "^2.0.0", "cors": "^2.8.4", "dotenv": "^4.0.0", - "eslint-plugin-import": "^2.7.0", "express": "^4.16.2", "express-session": "^1.15.6", "graphql": "^0.11.7", @@ -19,8 +18,9 @@ "material-ui-icons": "^1.0.0-beta.17", "multer": "^1.3.0", "nodemailer": "^4.2.0", - "passwordless": "^1.1.2", - "passwordless-mongostore": "^0.1.4", + "passport": "^0.4.0", + "passport-windowslive": "^1.0.2", + "qs": "^6.5.1", "react": "^16.0.0", "react-apollo": "^1.4.16", "react-dom": "^16.0.0", @@ -39,7 +39,8 @@ "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject", "test-without-watch": "cross-env CI=true npm test", - "lint": "prettier 'src/**/*.js' 'server/*.js' 'src/components/**/*.js' '*.js' --write --config .prettierrc && eslint --fix 'src/**/*.js' 'server/*.js' 'src/components/**/*.js'", + "lint": + "prettier 'src/**/*.js' 'server/*.js' 'src/components/**/*.js' '*.js' --write --config .prettierrc && eslint --fix 'src/**/*.js' 'server/*.js' 'src/components/**/*.js'", "precommit": "lint-staged && npm run test-without-watch" }, "devDependencies": { @@ -50,6 +51,7 @@ "enzyme-adapter-react-16": "^1.0.1", "eslint": "^4.8.0", "eslint-config-standard": "^10.2.1", + "eslint-plugin-import": "^2.7.0", "eslint-plugin-node": "^5.2.0", "eslint-plugin-promise": "^3.5.0", "eslint-plugin-react": "^7.4.0", diff --git a/server/.env.sample b/server/.env.sample index ad8dfdb..6a9ce68 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -3,4 +3,7 @@ MAIL_PORT=2525 MAIL_USERNAME=yourusername MAIL_PASSWORD=yourpassword DB_HOST=mongodb://username:password@host:port/collection -SECRET=supersecret \ No newline at end of file +SECRET=supersecret +MICROSOFT_APP_ID= +MICROSOFT_APP_SECRET= +MICROSOFT_CALLBACK_URL=http://localhost:8080/auth/microsoft/callback \ No newline at end of file diff --git a/server/app.js b/server/app.js index 8b34a7b..4f9d8a5 100644 --- a/server/app.js +++ b/server/app.js @@ -13,8 +13,8 @@ const cors = require('cors') // Cors origin policy const mail = require('./email') const multer = require('multer') const session = require('express-session') +const passport = require('passport') const MongoStore = require('connect-mongo')(session) -const passwordless = require('passwordless') const multerOptions = { storage: multer.memoryStorage() @@ -66,13 +66,52 @@ app.use( store: new MongoStore({ url: process.env.DB_HOST }) }) ) +app.use(passport.initialize()) +app.use(passport.session()) -require('./auth')(app) +require('./auth')(passport) + +app.get( + '/auth/microsoft', + passport.authenticate('windowslive', { + failureRedirect: 'http://localhost:3000/login' + }) +) + +app.get( + '/auth/microsoft/callback', + passport.authenticate('windowslive', { + failureRedirect: encodeURI( + 'http://localhost:3000/login?error=Use valid franciscan email' + ) + }), + function (req, res) { + // Successful authentication, redirect home. + res.redirect('http://localhost:3000') + } +) /* FOR AUTHENTICATION I WILL GO FOR THIS APPROACHs app.use('*', ssoAuthenticationMiddleware) */ +app.get('/logout', (req, res) => { + req.logout() + res.redirect('http://localhost:3000/login') +}) + +app.use('/check_auth', (req, res) => { + if (req.isAuthenticated()) { + res.json({ + ok: true + }) + } else { + res.status('401').json({ + ok: false + }) + } +}) + // Graphiql GUI for API Testing... app.use( '/graphiql', @@ -99,6 +138,13 @@ app.post( } ) +function ensureAuthenticated (req, res, next) { + if (req.isAuthenticated()) { + return next() + } + res.sendStatus(401) +} + // Making WP API Available by using remote gql server strategy introspectSchema(fetcher) .then(schema => { @@ -108,7 +154,7 @@ introspectSchema(fetcher) }) app.use( '/graphql', - passwordless.restricted(), + ensureAuthenticated, bodyParser.json(), graphqlExpress({ schema: gqlschema }) ) diff --git a/server/auth.js b/server/auth.js index be71538..de1e675 100644 --- a/server/auth.js +++ b/server/auth.js @@ -1,65 +1,30 @@ -const passwordless = require('passwordless') -const MongoStore = require('passwordless-mongostore') -const { transport } = require('./email') -const template = require('./template') +const MicrosoftStrategy = require('passport-windowslive').Strategy -module.exports = app => { - var pathToMongoDb = process.env.DB_HOST - passwordless.init(new MongoStore(pathToMongoDb)) - - passwordless.addDelivery( - async (tokenToSend, uidToSend, recipient, callback) => { - const host = 'localhost:8080' - const link = `http://${host}/logged_in?token=${tokenToSend}&uid=${encodeURIComponent( - uidToSend - )}` - const body = `Hi! Access Your account here: Click Me or goto ${link}` - transport.sendMail( - { - html: template('Login', 'Click', body), - from: 'test@test.com', - to: recipient, - subject: 'Token for ' + host - }, - function (err, message) { - if (err) { - console.log(err) +module.exports = passport => { + passport.use( + new MicrosoftStrategy( + { + clientID: process.env.MICROSOFT_APP_ID, + clientSecret: process.env.MICROSOFT_APP_SECRET, + callbackURL: process.env.MICROSOFT_CALLBACK_URL, + scope: ['wl.signin', 'wl.basic', 'wl.emails'] + }, + function (accessToken, refreshToken, profile, done) { + for (let email of profile.emails) { + if (email.value.endsWith('franciscan.edu')) { + return done(null, profile.id) } - callback(err) } - ) - } - ) - - app.use(passwordless.sessionSupport()) - - app.get('/logged_in', passwordless.acceptToken(), function (req, res) { - res.redirect('http://localhost:3000') - }) - - app.post( - '/sendtoken', - passwordless.requestToken(function (user, delivery, callback, req) { - if (user.endsWith('franciscan.edu')) { - callback(null, user) - } else { - callback(null, null) + return done(null, false) } - }), - function (req, res) { - // success! - res.json({ - ok: true - }) - } + ) ) - app.get('/logout', passwordless.logout(), (req, res) => { - res.redirect('http://localhost:3000/login') + passport.serializeUser(function (id, done) { + done(null, id) }) - app.get('/check_auth', (req, res) => { - if (req.user) res.json({ ok: true }) - else res.json({ ok: false }) + passport.deserializeUser(function (id, done) { + return done(null, id) }) } diff --git a/src/views/Login.js b/src/views/Login.js index d5633ec..83c291c 100644 --- a/src/views/Login.js +++ b/src/views/Login.js @@ -1,11 +1,10 @@ import React, { Component } from 'react' -import Card, { CardActions, CardContent } from 'material-ui/Card' +import Card, { CardContent } from 'material-ui/Card' import Button from 'material-ui/Button' -import Typography from 'material-ui/Typography' -import TextField from 'material-ui/TextField' import Grid from 'material-ui/Grid' import { withStyles, MuiThemeProvider } from 'material-ui/styles' import theme from '../components/Layout/fusTheme' +import qs from 'qs' const styles = theme => ({ heightvh: { @@ -17,7 +16,18 @@ const styles = theme => ({ }) class Login extends Component { - state = { email: '', err: '', txt: 'Submit' } + state = { err: '' } + componentDidMount () { + if (this.props.location.search) { + const parsed = qs.parse(this.props.location.search) + if (parsed['?error']) { + this.setState({ + err: parsed['?error'] + }) + } + } + } + componentWillMount () { // eslint-disable-next-line fetch('http://localhost:8080/check_auth', { @@ -39,75 +49,28 @@ class Login extends Component { > -
- - - Login - - - - - - -
+ + +
+ {this.state.err && ( +

{this.state.err}

+ )} +
) } - handleSubmit = e => { - e.preventDefault() - this.setState({ - err: '', - txt: 'Submitting' - }) - // eslint-disable-next-line - fetch('http://localhost:8080/sendtoken', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - user: this.state.email - }) - }) - .then(res => res.status) - .then(res => { - if (res === 200) { - this.setState({ - txt: 'Email has been sent' - }) - } else if (res === 401) { - this.setState({ - err: 'Use a valid franciscan email', - txt: 'Submit' - }) - } - }) - .catch(e => { - this.setState({ - err: 'This email is not sent', - txt: 'Submit' - }) - }) - } - handleChange = e => { - this.setState({ - [e.target.name]: e.target.value - }) + handleLogin = () => { + window.location.href = 'http://localhost:8080/auth/microsoft/callback' } } diff --git a/yarn.lock b/yarn.lock index 94e4097..1b09524 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5861,6 +5861,10 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +oauth@0.9.x: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + object-assign@4.1.1, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6113,6 +6117,47 @@ parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" +passport-microsoft@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/passport-microsoft/-/passport-microsoft-0.0.5.tgz#34e916b4ea42fadbc0132aec05459acbe464ce15" + dependencies: + passport-oauth2 "1.2.0" + pkginfo "0.2.x" + +passport-oauth2@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.2.0.tgz#49613a3eca85c7a1e65bf1019e2b6b80a10c8ac2" + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + +passport-oauth2@1.x.x: + version "1.4.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad" + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + +passport-windowslive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/passport-windowslive/-/passport-windowslive-1.0.2.tgz#383cfee6589ffb5ecc2ad19c3a41ef691462a705" + dependencies: + passport-oauth2 "1.x.x" + +passport@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.0.tgz#c5095691347bd5ad3b5e180238c3914d16f05811" + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + passwordless-mongostore@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/passwordless-mongostore/-/passwordless-mongostore-0.1.4.tgz#ee8200cb412a7ca18c5cea42715601ccdb053361" @@ -6191,6 +6236,10 @@ pause-stream@0.0.11: dependencies: through "~2.3" +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + pbkdf2@^3.0.3: version "3.0.14" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" @@ -6260,6 +6309,10 @@ pkg-up@^1.0.0: dependencies: find-up "^1.0.0" +pkginfo@0.2.x: + version "0.2.3" + resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.2.3.tgz#7239c42a5ef6c30b8f328439d9b9ff71042490f8" + pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" @@ -6696,7 +6749,7 @@ q@^1.1.2: version "1.5.0" resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" -qs@6.5.1, qs@~6.5.1: +qs@6.5.1, qs@^6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -8261,6 +8314,10 @@ uid-safe@~2.1.5: dependencies: random-bytes "~1.0.0" +uid2@0.0.x: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + undefsafe@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f" @@ -8409,7 +8466,7 @@ utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@1.x.x: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"