Last active
January 3, 2021 16:06
-
-
Save jboxman/5ffbfd3bd9ccc591484a468bc5805156 to your computer and use it in GitHub Desktop.
Github OAuth 2.0 authorization grant code SPA integration - deprecated
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const config = require('../../config/envs'); | |
import React from 'react'; | |
import propTypes from 'prop-types'; | |
import { connect } from 'react-redux'; | |
import { bindActionCreators } from 'redux'; | |
import { | |
Input, | |
Menu, | |
Dropdown, | |
Icon, | |
Image, | |
Checkbox | |
} from 'semantic-ui-react'; | |
import loginTab from '../../util/openWindow'; | |
import * as userActions from '../redux/userActions'; | |
import withAuthStatus from './hoc/withAuthStatus'; | |
class AppMenu extends React.Component { | |
constructor() { | |
super(); | |
} | |
handleLogIn(e, {name}) { | |
const msg = loginTab(config.app.oauthUrl); | |
msg.then(user => { | |
this.props.userActions.injectUser(user); | |
}); | |
} | |
handleLogOut(e, {name}) { | |
this.props.userActions.logoutUser(); | |
} | |
render() { | |
const {isAuthenticated, currentUser} = this.props; | |
const trigger = ( | |
<span> | |
<Icon name='user' /> | |
{isAuthenticated ? currentUser.username : ''} | |
</span>); | |
const loginButton = isAuthenticated ? | |
<Menu.Item> | |
<Dropdown simple trigger={trigger}> | |
<Dropdown.Menu> | |
<Dropdown.Divider /> | |
<Dropdown.Item onClick={this.handleLogOut.bind(this)}>Logout</Dropdown.Item> | |
</Dropdown.Menu> | |
</Dropdown> | |
</Menu.Item> | |
: | |
<Menu.Item name="Login" onClick={this.handleLogIn.bind(this)} />; | |
return ( | |
<Menu> | |
<Menu.Menu position='right'> | |
{loginButton} | |
</Menu.Menu> | |
</Menu> | |
) | |
} | |
} | |
AppMenu.PropTypes = { | |
isAuthenticated: propTypes.bool, | |
currentUser: propTypes.object | |
}; | |
const mapStateToProps = state => ({ | |
isAuthenticated: state.currentUser.isAuthenticated, | |
currentUser: state.currentUser.user | |
}); | |
const mapDispatchToProps = dispatch => ({ | |
userActions: bindActionCreators(userActions, dispatch) | |
}); | |
export default connect(mapStateToProps, mapDispatchToProps)(AppMenu); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// http://scottksmith.com/blog/2014/09/18/beer-locker-building-a-restful-api-with-node-username-and-password/ | |
const passport = require('koa-passport'); | |
const GitHubStrategy = require('passport-github2').Strategy; | |
const MockStrategy = require('./mock-strategy').Strategy; | |
const User = require('../backend/entities/users/userModel'); | |
function onSuccess(accessToken, refreshToken, profile, done) { | |
// https://stackoverflow.com/questions/20431049/what-is-function-user-findorcreate-doing-and-when-is-it-called-in-passport | |
User.findOrCreate( | |
{ | |
oauthId: profile.id | |
}, | |
{ | |
oauthId: profile.id, | |
oauthProvider: profile.provider, | |
email: profile.emails[0].value, | |
username: profile.username, | |
avatarUrl: profile._json.avatar_url | |
}, | |
function(error, user, created) { | |
done(null, user.toJSON()); | |
}); | |
} | |
function strategyForEnvironment() { | |
let strategy; | |
switch(process.env.NODE_ENV) { | |
case 'production': | |
strategy = new GitHubStrategy({ | |
clientID: process.env.CLIENT_ID, | |
clientSecret: process.env.CLIENT_SECRET, | |
callbackURL: process.env.CALLBACK_URL | |
}, onSuccess); | |
break; | |
default: | |
strategy = new MockStrategy('github', onSuccess); | |
} | |
return strategy; | |
} | |
passport.serializeUser(function(user, done) { | |
done(null, user.id); | |
}); | |
passport.deserializeUser(async function(id, done) { | |
try { | |
const user = await User.findById(id).exec(); | |
done(null, user.toJSON()); | |
} | |
catch(err) { | |
done(err); | |
} | |
}); | |
passport.use(strategyForEnvironment()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// From https://gist.github.com/gauravtiwari/2ae9f44aee281c759fe5a66d5c2721a2 | |
// By https://gist.github.com/gauravtiwari | |
/* global window */ | |
const popup = (myUrl) => { | |
const windowArea = { | |
width: Math.floor(window.outerWidth * 0.8), | |
height: Math.floor(window.outerHeight * 0.5), | |
}; | |
if (windowArea.width < 1000) { windowArea.width = 1000; } | |
if (windowArea.height < 630) { windowArea.height = 630; } | |
windowArea.left = Math.floor(window.screenX + ((window.outerWidth - windowArea.width) / 2)); | |
windowArea.top = Math.floor(window.screenY + ((window.outerHeight - windowArea.height) / 8)); | |
const sep = (myUrl.indexOf('?') !== -1) ? '&' : '?'; | |
const url = `${myUrl}${sep}`; | |
const windowOpts = `toolbar=0,scrollbars=1,status=1,resizable=1,location=1,menuBar=0, | |
width=${windowArea.width},height=${windowArea.height}, | |
left=${windowArea.left},top=${windowArea.top}`; | |
const authWindow = window.open(url, '_blank', windowOpts); | |
// Create IE + others compatible event handler | |
const eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent'; | |
const eventer = window[eventMethod]; | |
const messageEvent = eventMethod === 'attachEvent' ? 'onmessage' : 'message'; | |
// Listen to message from child window | |
const authPromise = new Promise((resolve, reject) => { | |
eventer(messageEvent, (msg) => { | |
// This doesn't work in Chrome 59 | |
//if (e.origin !== window.SITE_DOMAIN) { | |
// https://stackoverflow.com/questions/25098021/securityerror-blocked-a-frame-with-origin-from-accessing-a-cross-origin-frame | |
if (!~msg.origin.indexOf(`${window.location.protocol}//${window.location.host}`)) { | |
authWindow.close(); | |
reject('Not allowed'); | |
} | |
if (msg.data.payload) { | |
try { | |
resolve(JSON.parse(msg.data.payload)); | |
} | |
catch(e) { | |
resolve(msg.data.payload); | |
} | |
finally { | |
authWindow.close(); | |
} | |
} else { | |
authWindow.close(); | |
reject('Unauthorised'); | |
} | |
}, false); | |
}); | |
return authPromise; | |
}; | |
export default popup; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Login successful</title> | |
</head> | |
<body> | |
<h1>Success</h1> | |
<p>You are authenticated...</p> | |
</body> | |
<script> | |
document.body.onload = function() { | |
var injectedUser = <%- JSON.stringify(user) %>; | |
window.opener.postMessage( | |
{ | |
payload: injectedUser, | |
status: 'success' | |
}, | |
window.opener.location | |
); | |
/* | |
window.opener.postMessage( | |
{ error: 'Login failed' }, | |
window.opener.location | |
); | |
*/ | |
}; | |
</script> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as ActionTypes from './userActionTypes'; | |
export function injectUser(user = {}) { | |
return { | |
type: ActionTypes.INJECT, | |
payload: { | |
user | |
} | |
}; | |
}; | |
export function logoutUser() { | |
return { | |
type: ActionTypes.LOGOUT | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const INJECT = 'INJECT_USER'; | |
export const LOGOUT = 'LOGOUT_USER'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const mongoose = require('mongoose'); | |
const findOrCreate = require('mongoose-findorcreate'); | |
const userSchema = mongoose.Schema({ | |
username: { | |
type: String, | |
required: true | |
}, | |
email: { | |
type: String, | |
required: true | |
}, | |
avatarUrl: { | |
type: String | |
}, | |
oauthId: { | |
type: String, | |
required: true, | |
select: false | |
}, | |
oauthProvider: { | |
type: String, | |
required: true, | |
enum: ['github'], | |
select: false | |
} | |
}); | |
// https://stackoverflow.com/questions/20431049/what-is-function-user-findorcreate-doing-and-when-is-it-called-in-passport | |
userSchema.plugin(findOrCreate); | |
module.exports = mongoose.model('User', userSchema); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as actionTypes from './userActionTypes'; | |
export const STATE_KEY = 'currentUser'; | |
export const initialState = { | |
isAuthenticated: false, | |
user: {} | |
}; | |
export default function userReducer(state = initialState, action = {}) { | |
const {type, payload} = action; | |
switch(type) { | |
case actionTypes.INJECT: | |
return { | |
...state, | |
isAuthenticated: true, | |
user: payload.user | |
}; | |
case actionTypes.LOGOUT: | |
return { | |
...initialState | |
}; | |
default: | |
return state; | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Router = require('koa-router'); | |
const passport = require('koa-passport'); | |
const { | |
isAuthenticated | |
} = require('../../helpers'); | |
const userModel = require('./userModel'); | |
const router = new Router({ | |
prefix: '/users' | |
}); | |
router.get('/auth/github', | |
passport.authenticate('github') | |
); | |
// Custom handler that returns the authenticated user object | |
router.get('/auth/github/callback', function(ctx) { | |
return passport.authenticate('github', async function(err, user, info) { | |
await ctx.logIn(user); | |
await ctx.render('success', {user: JSON.stringify(ctx.state.user)}); | |
})(ctx); | |
}); | |
router.get('/auth/logout', isAuthenticated, function(ctx) { | |
ctx.logOut(); | |
}); | |
module.exports = router; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment