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

Add microsoft auth and remove passwordless auth #65

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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": {
Expand All @@ -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",
Expand Down
5 changes: 4 additions & 1 deletion server/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ MAIL_PORT=2525
MAIL_USERNAME=yourusername
MAIL_PASSWORD=yourpassword
DB_HOST=mongodb://username:password@host:port/collection
SECRET=supersecret
SECRET=supersecret
MICROSOFT_APP_ID=<YOUR APP ID>
MICROSOFT_APP_SECRET=<YOUR SECRET>
MICROSOFT_CALLBACK_URL=http://localhost:8080/auth/microsoft/callback
52 changes: 49 additions & 3 deletions server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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',
Expand All @@ -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 => {
Expand All @@ -108,7 +154,7 @@ introspectSchema(fetcher)
})
app.use(
'/graphql',
passwordless.restricted(),
ensureAuthenticated,
bodyParser.json(),
graphqlExpress({ schema: gqlschema })
)
Expand Down
75 changes: 20 additions & 55 deletions server/auth.js
Original file line number Diff line number Diff line change
@@ -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: <a href="${link}">Click Me</a> or goto ${link}`
transport.sendMail(
{
html: template('Login', 'Click', body),
from: '[email protected]',
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)
})
}
97 changes: 30 additions & 67 deletions src/views/Login.js
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -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', {
Expand All @@ -39,75 +49,28 @@ class Login extends Component {
>
<Grid item xs={8} sm={5} md={4}>
<Card>
<form onSubmit={this.handleSubmit}>
<CardContent>
<Typography type="display1" component="h2">
Login
</Typography>
<TextField
error={!!this.state.err}
name="email"
label={this.state.err ? this.state.err : 'Email'}
value={this.state.email}
onChange={this.handleChange}
fullWidth
margin={'normal'}
type="email"
required
/>
</CardContent>
<CardActions>
<Button type="submit" raised color="primary">
{this.state.txt}
</Button>
</CardActions>
</form>
<CardContent>
<Button
type="submit"
onClick={this.handleLogin}
raised
color="primary"
>
LOGIN USING MICROSOFT
</Button>
<br />
{this.state.err && (
<p style={{ color: 'red' }}>{this.state.err}</p>
)}
</CardContent>
</Card>
</Grid>
</Grid>
</MuiThemeProvider>
)
}
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'
}
}

Expand Down
Loading