wiki-security-passportjs/server/social.coffee

392 lines
12 KiB
CoffeeScript
Raw Normal View History

2016-03-16 14:13:37 +00:00
###
* Federated Wiki : Security Plugin : Social
*
* Copyright Ward Cunningham and other contributors
* Licensed under the MIT license.
* https://github.com/fedwiki/wiki-security-social/blob/master/LICENSE.txt
###
#### Requires ####
fs = require 'fs'
path = require 'path'
https = require 'https'
qs = require 'qs'
url = require 'url'
_ = require('lodash')
2016-03-16 14:13:37 +00:00
passport = require 'passport'
# Export a function that generates security handler
# when called with options object.
module.exports = exports = (log, loga, argv) ->
security = {}
#### Private stuff ####
owner = ''
2016-03-31 04:47:41 +00:00
ownerName = ''
2016-04-07 10:00:09 +00:00
user = {}
wikiName = argv.url
wikiHost = argv.wiki_domain
2016-03-16 14:13:37 +00:00
admin = argv.admin
2016-03-31 04:47:41 +00:00
statusDir = argv.status
2016-03-16 14:13:37 +00:00
2016-03-31 04:47:41 +00:00
console.log "statusDir: ", statusDir
idFile = argv.id
2016-03-31 04:47:41 +00:00
usingPersona = false
2016-03-16 14:13:37 +00:00
if argv.security_useHttps
useHttps = true
callbackProtocol = "https:"
else
useHttps = false
callbackProtocol = url.parse(argv.url).protocol
if wikiHost
callbackHost = wikiHost
2016-05-28 08:51:15 +00:00
if url.parse(argv.url).port
callbackHost = callbackHost + ":" + url.parse(argv.url).port
else
callbackHost = url.parse(argv.url).host
2016-03-16 14:13:37 +00:00
ids = []
2016-03-16 14:13:37 +00:00
2016-03-31 04:47:41 +00:00
# Mozilla Persona service closes on
personaEnd = new Date('2016-11-30')
2016-03-16 14:13:37 +00:00
#### Public stuff ####
# Attempt to figure out if the wiki is claimed or not,
# if it is return the owner.
security.retrieveOwner = (cb) ->
fs.exists idFile, (exists) ->
2016-03-16 14:13:37 +00:00
if exists
fs.readFile(idFile, (err, data) ->
2016-03-16 14:13:37 +00:00
if err then return cb err
owner = JSON.parse(data)
console.log 'retrieveOwner owner: ', owner
if _.has(owner, 'persona')
usingPersona = true
2016-03-16 14:13:37 +00:00
cb())
else
owner = ''
cb()
2016-03-16 14:13:37 +00:00
security.getOwner = getOwner = ->
if !owner.name?
ownerName = ''
2016-03-16 14:13:37 +00:00
else
ownerName = owner.name
2016-03-16 14:13:37 +00:00
ownerName
security.setOwner = setOwner = (id, cb) ->
2016-04-07 10:00:09 +00:00
fs.exists idFile, (exists) ->
2016-03-16 14:13:37 +00:00
if !exists
fs.writeFile(idFile, JSON.stringify(id), (err) ->
2016-03-16 14:13:37 +00:00
if err then return cb err
2016-04-07 10:00:09 +00:00
console.log "Claiming wiki #{wikiName} for #{id}"
2016-03-31 04:47:41 +00:00
owner = id
ownerName = owner.name
2016-03-16 14:13:37 +00:00
cb())
else
2016-04-07 10:00:09 +00:00
cb('Already Claimed')
2016-03-16 14:13:37 +00:00
security.getUser = (req) ->
2016-03-31 04:47:41 +00:00
if req.session.passport
if req.session.passport.user
return req.session.passport.user
else
return ''
2016-03-16 14:13:37 +00:00
else
return ''
2016-03-31 04:47:41 +00:00
security.isAuthorized = isAuthorized = (req) ->
if owner is ''
console.log 'isAuthorized: site not claimed'
2016-03-16 14:13:37 +00:00
return true
else
2016-04-07 10:00:09 +00:00
try
idProvider = req.session.passport.user.provider
if _.isEqual(owner[idProvider].id, req.session.passport.user.id)
return true
else
return false
catch error
return false
2016-04-07 10:00:09 +00:00
2016-03-16 14:13:37 +00:00
security.isAdmin = (req) ->
try
if admin
idProvider = req.session.passport.user.provider
if _.isEqual(admin[idProvider].id, req.session.passport.user.id)
return true
else
return false
catch error
return false
2016-03-16 14:13:37 +00:00
2016-03-16 14:13:37 +00:00
security.login = (updateOwner) ->
2016-03-31 04:47:41 +00:00
console.log "Login...."
2016-03-16 14:13:37 +00:00
security.logout = () ->
(req, res) ->
console.log "Logout...."
security.defineRoutes = (app, cors, updateOwner) ->
passport.serializeUser = (user, req, done) ->
done(null, user)
passport.deserializeUser = (obj, req, done) ->
done(null, obj)
# Github Strategy
2016-03-16 14:13:37 +00:00
if argv.github_clientID? and argv.github_clientSecret?
ids.push('github')
2016-03-16 14:13:37 +00:00
GithubStrategy = require('passport-github').Strategy
passport.use(new GithubStrategy({
clientID: argv.github_clientID
clientSecret: argv.github_clientSecret
scope: 'user:emails'
# callbackURL is optional, and if it exists must match that given in
# the OAuth application settings - so we don't specify it.
2016-03-16 14:13:37 +00:00
}, (accessToken, refreshToken, profile, cb) ->
user = {
provider: 'github',
id: profile.id
username: profile.username
displayName: profile.displayName
email: profile.emails[0].value
}
cb(null, user)))
2016-03-16 14:13:37 +00:00
# Twitter Strategy
2016-03-16 14:13:37 +00:00
if argv.twitter_consumerKey? and argv.twitter_consumerSecret?
ids.push('twitter')
2016-03-16 14:13:37 +00:00
TwitterStrategy = require('passport-twitter').Strategy
passport.use(new TwitterStrategy({
consumerKey: argv.twitter_consumerKey
consumerSecret: argv.twitter_consumerSecret
callbackURL: callbackProtocol + '//' + callbackHost + '/auth/twitter/callback'
2016-03-16 14:13:37 +00:00
}, (accessToken, refreshToken, profile, cb) ->
2016-03-31 04:47:41 +00:00
user = {
provider: 'twitter',
id: profile.id,
username: profile.username,
displayName: profile.displayName
2016-03-31 04:47:41 +00:00
}
cb(null, user)))
2016-03-16 14:13:37 +00:00
# Google Strategy
2016-03-16 14:13:37 +00:00
if argv.google_clientID? and argv.google_clientSecret?
ids.push('google')
2016-03-16 14:13:37 +00:00
GoogleStrategy = require('passport-google-oauth20').Strategy
passport.use(new GoogleStrategy({
clientID: argv.google_clientID
clientSecret: argv.google_clientSecret
callbackURL: callbackProtocol + '//' + callbackHost + '/auth/google/callback'
2016-03-16 14:13:37 +00:00
}, (accessToken, refreshToken, profile, cb) ->
user = {
provider: "google"
id: profile.id
displayName: profile.displayName
emails: profile.emails
}
2016-03-16 14:13:37 +00:00
cb(null, profile)))
2016-05-25 07:36:55 +00:00
# Persona Strategy
PersonaStrategy = require('persona-pass').Strategy
passport.use(new PersonaStrategy({
audience: callbackProtocol + '//' + callbackHost
}, (email, cb) ->
user = {
persona: { email: email }
2016-05-25 07:36:55 +00:00
}
cb(null, user)))
2016-03-16 14:13:37 +00:00
app.use(passport.initialize())
app.use(passport.session())
# Github
app.get('/auth/github', passport.authenticate('github', {scope: 'user:email'}), (req, res) -> )
2016-03-16 14:13:37 +00:00
app.get('/auth/github/callback',
passport.authenticate('github', { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
2016-03-16 14:13:37 +00:00
# Twitter
app.get('/auth/twitter', passport.authenticate('twitter'), (req, res) -> )
app.get('/auth/twitter/callback',
2016-03-31 04:47:41 +00:00
passport.authenticate('twitter', { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
# Google
2016-03-16 14:13:37 +00:00
app.get('/auth/google', passport.authenticate('google', { scope: [
'https://www.googleapis.com/auth/plus.profile.emails.read'
]}))
app.get('/auth/google/callback',
passport.authenticate('google', { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
2016-05-25 07:36:55 +00:00
# Persona
app.post('/auth/browserid',
passport.authenticate('persona', { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
app.get '/auth/client-settings.json', (req, res) ->
# the client needs some information to configure itself
settings = {
useHttps: useHttps
2016-05-25 07:36:55 +00:00
usingPersona: usingPersona
}
if wikiHost
settings.wikiHost = wikiHost
res.json settings
2016-03-16 14:13:37 +00:00
app.get '/auth/loginDialog', (req, res) ->
referer = req.headers.referer
console.log "logging into: ", url.parse(referer).hostname
2016-03-16 14:13:37 +00:00
schemeButtons = []
_(ids).forEach (scheme) ->
switch scheme
when "twitter" then schemeButtons.push({button: "<a href='/auth/twitter' class='scheme-button twitter-button'><span>Twitter</span></a>"})
when "github" then schemeButtons.push({button: "<a href='/auth/github' class='scheme-button github-button'><span>Github</span></a>"})
when "google" then schemeButtons.push({button: "<a href='/auth/google' class='scheme-button google-button'><span>Google</span></a>"})
2016-04-12 16:46:51 +00:00
2016-03-16 14:13:37 +00:00
info = {
2016-05-28 08:51:15 +00:00
wikiName: if useHttps
url.parse(referer).hostname
else
url.parse(referer).host
wikiHostName: if wikiHost
"part of " + req.hostname + " wiki farm"
2016-04-12 16:46:51 +00:00
else
"a federated wiki site"
title: "Federated Wiki: Site Owner Sign-on"
loginText: "Sign in to"
schemes: schemeButtons
2016-03-16 14:13:37 +00:00
}
res.render(path.join(__dirname, '..', 'views', 'securityDialog.html'), info)
2016-05-25 07:36:55 +00:00
app.get '/auth/personaLogin', (req, res) ->
referer = req.headers.referer
console.log "logging into: ", url.parse(referer).hostname
schemeButtons = []
if Date.now() < personaEnd
schemeButtons.push({
button: "<a href='#' id='browserid' class='scheme-button persona-button'><span>Persona</span></a>
<script>
$('#browserid').click(function(){
navigator.id.get(function(assertion) {
if (assertion) {
$('input').val(assertion);
$('form').submit();
} else {
location.reload();
}
});
});
</script>"})
info = {
2016-05-28 08:51:15 +00:00
wikiName: if useHttps
url.parse(referer).hostname
else
url.parse(referer).host
2016-05-25 07:36:55 +00:00
wikiHostName: if wikiHost
"part of " + req.hostname + " wiki farm"
else
"a federated wiki site"
title: "Federated Wiki: Site Owner Sign-on"
loginText: "Sign in to"
message: "Mozilla Persona closes on 30th November 2016. Wiki owners should add an alternative identity as soon as they are able."
schemes: schemeButtons
}
else
info = {
2016-05-28 08:51:15 +00:00
wikiName: if useHttps
url.parse(referer).hostname
else
url.parse(referer).host
2016-05-25 07:36:55 +00:00
wikiHostName: if wikiHost
"part of " + req.hostname + " wiki farm"
else
"a federated wiki site"
title: "Federated Wiki: Site Owner Sign-on"
message: "Mozilla Persona has now closed. Wiki owners will need to contact the Wiki Farm owner to re-claim their wiki."
}
res.render(path.join(__dirname, '..', 'views', 'personaDialog.html'), info)
2016-04-07 10:00:09 +00:00
app.get '/auth/loginDone', (req, res) ->
2016-03-31 04:47:41 +00:00
info = {
title: if owner
"Wiki Site Owner Sign-on"
else
"Sign-on to claim Wiki site"
owner: getOwner
authMessage: "You are now logged in..."
}
res.render(path.join(__dirname, '..', 'views', 'done.html'), info)
2016-04-07 10:00:09 +00:00
app.get '/auth/claim-wiki', (req, res) ->
if owner
console.log 'Claim Request Ignored: Wiki already has owner'
res.sendStatus(403)
else
user = req.session.passport.user
console.log "Claim: user = ", user
2016-04-07 10:00:09 +00:00
id = switch user.provider
when "twitter" then {
name: user.displayName
twitter: {
id: user.id
username: user.username
}
}
when "github" then {
name: user.displayName
github: {
id: user.id
username: user.username
email: user.email
}
}
when "google" then {
name: user.displayName
google: {
id: user.id
emails: user.emails
}
}
setOwner id, (err) ->
2016-04-07 10:00:09 +00:00
if err
console.log 'Failed to claim wiki ', req.hostname, ' for ', JSON.stringify(id)
2016-04-07 10:00:09 +00:00
res.sendStatus(500)
updateOwner getOwner()
2016-04-07 10:00:09 +00:00
res.json({
ownerName: id.name
})
2016-03-31 04:47:41 +00:00
app.get '/logout', (req, res) ->
console.log 'Logout...'
req.logout()
res.send("OK")
2016-03-16 14:13:37 +00:00
security