Skip to content
This repository has been archived by the owner on Aug 28, 2021. It is now read-only.

Commit

Permalink
feat(authorization): added github authorization, added storage of pro…
Browse files Browse the repository at this point in the history
…vider profiles on users
  • Loading branch information
didimitrie committed Jul 30, 2019
1 parent 4fd23ca commit e56d60e
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 20 deletions.
26 changes: 16 additions & 10 deletions .env-base
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

# SERVER_NAME: The server name is important, as it will help users differentiate
# between multiple accounts.
SERVER_NAME="Default Server"
SERVER_NAME="Please Change My Name Server"

# CANONICAL_URL: The url address (if you have any) where this server exists. For example,
# a canonical url can be "https://hestia.speckle.works". Leave blank otherwise.
# a canonical url can be "https://hestia.speckle.works".
CANONICAL_URL="http://localhost:3000"

# PUBLIC_STREAMS: Wether all streams created should be public (true) or private (false)
Expand Down Expand Up @@ -82,7 +82,7 @@ SMPT_PASSWORD=""

# Will populate the `from:` field in any emails this server sends. Please note, some
# providers will require to verify your domain first.
EMAIL_SENDER="noreply@yourverifieddomain.com"
EMAIL_SENDER="please_change_this@whatever.com"

#
# Authentication Strategies
Expand All @@ -96,16 +96,22 @@ USE_LOCAL=true

# Auth0
USE_AUTH0=false
AUTH0_CLIENT_ID=XXX
AUTH0_DOMAIN=XXX
AUTH0_CLIENT_SECRET=XXX
AUTH0_CLIENT_ID="CHANGE_ME"
AUTH0_DOMAIN="CHANGE_ME"
AUTH0_CLIENT_SECRET="CHANGE_ME"

# Azure AD
USE_AZUREAD=false
AZUREAD_ORG_NAME="Your Organisation Name"
AZUREAD_CLIENT_ID="XXX"
AZUREAD_IDENTITY_METADATA="XXX"
AZUREAD_CLIENT_SECRET="XXX"
AZUREAD_ORG_NAME="CHANGE_ME"
AZUREAD_CLIENT_ID="CHANGE_ME"
AZUREAD_IDENTITY_METADATA="CHANGE_ME"
AZUREAD_CLIENT_SECRET="CHANGE_ME"

# Github
USE_GITHUB=false
GITHUB_CLIENT_ID="CHANGE_ME"
GITHUB_CLIENT_SECRET="CHANGE_ME"
GITHUB_CALLBACK="CHANGE_ME"

# Whitelisted domains
REDIRECT_URLS="https://app.speckle.systems"
Expand Down
14 changes: 10 additions & 4 deletions app/auth/auth0/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ module.exports = {
async ( req, res, next ) => {

if ( !req.user ) {
req.session.errorMessage = 'Auth0 flow failed.'
req.session.errorMessage = 'Auth0 authentication failed.'
res.redirect( '/signin/error' )
}

let email = req.user._json.email
let name = req.user._json.name

if ( !name || !email ) {
req.session.errorMessage = 'Failed to retrieve email or name from the Auth0.'
req.session.errorMessage = 'Failed to retrieve email or name from Auth0.'
return res.redirect( '/signin/error' )
}

Expand All @@ -75,7 +75,13 @@ module.exports = {
}
existingUser.logins.push( { date: Date.now( ) } )
existingUser.markModified( 'logins' )

existingUser.providerProfiles[ 'auth0' ] = req.user._json
existingUser.markModified( 'providerProfiles' )

await existingUser.save( )

req.user = userObj
return next( )
}

Expand All @@ -84,13 +90,13 @@ module.exports = {
let userCount = await User.count( )
let myUser = new User( {
email: email,
company: process.env.AZUREAD_ORG_NAME,
apitoken: null,
role: 'user',
verified: true, // If coming from an AD route, we assume the user's email is verified.
password: cryptoRandomString( { length: 20, type: 'base64' } ), // need a dummy password
} )

myUser.providerProfiles[ 'auth0' ] = req.user._json
myUser.apitoken = 'JWT ' + jwt.sign( { _id: myUser._id }, process.env.SESSION_SECRET, { expiresIn: '2y' } )
let token = 'JWT ' + jwt.sign( { _id: myUser._id, name: myUser.name, email: myUser.email }, process.env.SESSION_SECRET, { expiresIn: '24h' } )

Expand All @@ -102,7 +108,7 @@ module.exports = {
myUser.name = namePieces[ 1 ]
myUser.surname = namePieces[ 0 ]
} else {
myUser.name = "Anonymouss"
myUser.name = "Anonymous"
myUser.surname = name
}

Expand Down
6 changes: 6 additions & 0 deletions app/auth/azure-ad/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
if ( process.env.USE_AZUREAD !== 'true' )
return null

// define and set strategy
let strategy = new OIDCStrategy( {
identityMetadata: process.env.AZUREAD_IDENTITY_METADATA,
clientID: process.env.AZUREAD_CLIENT_ID,
Expand Down Expand Up @@ -69,6 +70,10 @@ module.exports = {

existingUser.logins.push( { date: Date.now( ) } )
existingUser.markModified( 'logins' )

existingUser.providerProfiles[ 'azure' ] = req.user._json
existingUser.markModified( 'providerProfiles' )

await existingUser.save( )

req.user = userObj
Expand All @@ -86,6 +91,7 @@ module.exports = {
password: cryptoRandomString( { length: 20, type: 'base64' } ), // need a dummy password
} )

myUser.providerProfiles[ 'azure' ] = req.user._json
myUser.apitoken = 'JWT ' + jwt.sign( { _id: myUser._id }, process.env.SESSION_SECRET, { expiresIn: '2y' } )
let token = 'JWT ' + jwt.sign( { _id: myUser._id, name: myUser.name, email: myUser.email }, process.env.SESSION_SECRET, { expiresIn: '24h' } )

Expand Down
135 changes: 135 additions & 0 deletions app/auth/github/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use strict'

const passport = require( 'passport' )
const GithubStrategy = require( 'passport-github' )
const cryptoRandomString = require( 'crypto-random-string' )
const jwt = require( 'jsonwebtoken' )

const winston = require( '../../../config/logger' )
const User = require( '../../../models/User' )

module.exports = {
init( app, sessionMiddleware, redirectCheck, handleLogin ) {

if ( process.env.USE_GITHUB !== "true" )
return null

// define and set strategy
let strategy = new GithubStrategy( {
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: process.env.GITHUB_CALLBACK,
scope: [ 'profile', 'email' ]
}, async ( accessToken, refreshToken, profile, done ) => {
return done( null, profile )
} )

passport.use( strategy )

// create signin route
app.get( '/signin/github',
sessionMiddleware,
redirectCheck,
passport.authenticate( 'github', { failureRedirect: '/signin/error' } ),
)

// create signin callback (this url needs to be whitelisted in your github application settings)
app.get( '/signin/github/callback',
sessionMiddleware,
redirectCheck,
passport.authenticate( 'github', { failureRedirect: '/signin/error' } ),
async ( req, res, next ) => {

if ( !req.user ) {
req.session.errorMessage = 'Github authentication failed.'
res.redirect( '/signin/error' )
}

// return res.send( req.user )

let email = req.user._json.email
let name = req.user._json.name

if ( !name || !email ) {
req.session.errorMessage = 'Failed to retrieve email or name from the Auth0.'
return res.redirect( '/signin/error' )
}

try {
let existingUser = await User.findOne( { email: email } )

// If user exists:
if ( existingUser ) {
let userObj = {
name: existingUser.name,
surname: existingUser.surname,
email: existingUser.email,
role: existingUser.role,
token: 'JWT ' + jwt.sign( { _id: existingUser._id, name: existingUser.name, email: existingUser.email }, process.env.SESSION_SECRET, { expiresIn: '24h' } ),
}
existingUser.logins.push( { date: Date.now( ) } )
existingUser.markModified( 'logins' )

existingUser.providerProfiles[ 'github' ] = req.user._json
existingUser.markModified( 'providerProfiles' )

await existingUser.save( )

req.user = userObj
return next( )
}

// If user does not exist:

let userCount = await User.count( )
let myUser = new User( {
email: email,
company: req.user._json.company || null,
apitoken: null,
role: 'user',
verified: true, // If coming from an AD route, we assume the user's email is verified.
password: cryptoRandomString( { length: 20, type: 'base64' } ), // need a dummy password
} )

myUser.providerProfiles[ 'github' ] = req.user._json
myUser.apitoken = 'JWT ' + jwt.sign( { _id: myUser._id }, process.env.SESSION_SECRET, { expiresIn: '2y' } )
let token = 'JWT ' + jwt.sign( { _id: myUser._id, name: myUser.name, email: myUser.email }, process.env.SESSION_SECRET, { expiresIn: '24h' } )

if ( userCount === 0 && process.env.FIRST_USER_ADMIN === 'true' )
myUser.role = 'admin'

let namePieces = name.split( /(?<=^\S+)\s/ )
if ( namePieces.length === 2 ) {
myUser.name = namePieces[ 1 ]
myUser.surname = namePieces[ 0 ]
} else {
myUser.name = "Anonymous"
myUser.surname = name
}

await myUser.save( )

req.user = {
name: myUser.name,
surname: myUser.surname,
email: myUser.email,
role: myUser.role,
verified: myUser.verified,
token: token
}
return next( )
} catch ( err ) {
winston.error( err )
req.session.errorMessage = `Something went wrong. Server said: ${err.message}`
return res.redirect( '/error' )
}
},
handleLogin )

return {
strategyName: 'Github',
signinRoute: '/signin/github',
useForm: false
}
}
}
1 change: 1 addition & 0 deletions app/auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ module.exports = function ( app, express ) {
require( './local' ).init( app, sessionMiddleware, redirectCheck, handleLogin ),
require( './auth0' ).init( app, sessionMiddleware, redirectCheck, handleLogin ),
require( './azure-ad' ).init( app, sessionMiddleware, redirectCheck, handleLogin ),
require( './github' ).init( app, sessionMiddleware, redirectCheck, handleLogin ),
]


Expand Down
3 changes: 2 additions & 1 deletion models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ var userSchema = mongoose.Schema( {
role: { type: String, default: 'user' },
private: { type: Boolean, default: true },
verified: { type: Boolean, default: false },
archived: { type: Boolean, default: false }
archived: { type: Boolean, default: false },
providerProfiles: { type: Object, default: {} },
}, { timestamps: true } )


Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@
"passport-anonymous": "^1.0.1",
"passport-auth0": "^1.1.0",
"passport-azure-ad": "^4.1.0",
"passport-github": "^1.1.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"query-to-mongo": "^0.7.0",
"redis": "^2.8.0",
"request": "^2.88.0",
"shortid": "^2.2.13",
"uuid": "^3.3.2",
"winston": "^3.2.1",
Expand Down
24 changes: 21 additions & 3 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,26 @@ const plugins = require( './plugins' )( )
/// MASTER process /////.
/////////////////////////////////////////////////////////////////////////
if ( cluster.isMaster ) {
console.log( chalk.blue( `
█▀▀ █▀▀█ █▀▀ █▀▀▀ █ █ █ █▀▀
▀▀█ █ █ █▀▀ █ █▀▄ █ █▀▀
▀▀▀ █▀▀▀ ▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀
` ), `
█ https://speckle.works
█ The Open Source Data Platform for AEC.
`,
chalk.red(`
█ Server running at: ${process.env.CANONICAL_URL}
`)
)

logger.level = 'debug'
logger.info( chalk.bgBlue( `Speckle is starting up.` ) )

let osCpus = require( 'os' ).cpus( ).length
let envCpus = process.env.MAX_PROC
Expand Down Expand Up @@ -84,7 +102,7 @@ if ( cluster.isMaster ) {
} )

mongoose.connection.on( 'connected', ( ) => {
logger.debug( chalk.red( 'Connected to mongo.' ) )
logger.debug( 'Connected to mongo.' )
} )


Expand Down Expand Up @@ -138,6 +156,6 @@ if ( cluster.isMaster ) {
var port = process.env.PORT || 3000
var ip = process.env.IP || null
server.listen( port, ip, ( ) => {
logger.debug( chalk.yellow( `Speckle worker process ${process.pid} now running on port ${port}.` ) )
logger.debug( `Speckle worker process ${process.pid} now running on port ${port}.` )
} )
}
2 changes: 1 addition & 1 deletion views/login.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</form>
<hr>
<p class='text-center'>
<a onclick="window.location='/signin/local/register'+window.location.search;" href='#'>register</a>
<a href='/signin/local/register'>register</a>
</p>
</div>
</div>
2 changes: 1 addition & 1 deletion views/register.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
</form>
<hr>
<p class='text-center'>
<a onclick="window.location='/signin/local/login'+window.location.search;" href='#'>login</a>
<a href='/signin/local/login'>login</a>
</p>
</div>
</div>

0 comments on commit e56d60e

Please sign in to comment.