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 '
2016-04-29 20:29:58 +00:00
url = require ' url '
2016-08-23 10:45:51 +00:00
_ = require ' lodash '
2016-08-23 09:17:24 +00:00
glob = require ' glob '
2016-04-29 20:29:58 +00:00
2016-08-23 10:45:51 +00:00
passport = require ( ' passport ' )
2016-03-16 14:13:37 +00:00
# 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
2016-04-29 20:29:58 +00:00
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-06-24 10:33:17 +00:00
idFile = argv . id
2016-03-31 04:47:41 +00:00
usingPersona = false
2016-03-16 14:13:37 +00:00
2016-04-29 20:29:58 +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
2016-04-29 20:29:58 +00:00
else
callbackHost = url . parse ( argv . url ) . host
2022-11-23 03:28:45 +00:00
if argv . oauth2_CallbackPort ?
callbackHost = callbackHost + " : " + argv . oauth2_CallbackPort
2016-03-16 14:13:37 +00:00
2016-04-29 20:29:58 +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-08-29 14:44:58 +00:00
watchForOwnerChange = ->
# we watch for owner changes, so we can update the information held here
fs . watch ( idFile , (eventType, filename) ->
# re-read the owner file
fs . readFile ( idFile , (err, data) ->
if err
console . log ' Error reading ' , idFile , err
return
owner = JSON . parse ( data )
usingPersona = false
if _ . isEmpty ( _ . intersection ( _ . keys ( owner ) , ids ) )
if _ . has ( owner , ' persona ' )
usingPersona = true
ownerName = owner . name
)
)
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) ->
2016-06-24 10:33:17 +00:00
fs . exists idFile , (exists) ->
2016-03-16 14:13:37 +00:00
if exists
2016-06-24 10:33:17 +00:00
fs . readFile ( idFile , (err, data) ->
2016-03-16 14:13:37 +00:00
if err then return cb err
2016-06-24 10:33:17 +00:00
owner = JSON . parse ( data )
2016-08-29 14:44:58 +00:00
# we only enable persona if it is the only owner information.
if _ . isEmpty ( _ . intersection ( _ . keys ( owner ) , ids ) )
if _ . has ( owner , ' persona ' )
usingPersona = true
watchForOwnerChange ( )
2016-03-16 14:13:37 +00:00
cb ( ) )
else
2016-06-24 10:33:17 +00:00
owner = ' '
cb ( )
2016-03-16 14:13:37 +00:00
security.getOwner = getOwner = ->
2016-06-24 10:33:17 +00:00
if ! owner . name ?
ownerName = ' '
2016-03-16 14:13:37 +00:00
else
2016-06-24 10:33:17 +00:00
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
2016-04-12 16:13:46 +00:00
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
2016-04-12 16:13:46 +00:00
ownerName = owner . name
2016-08-29 14:44:58 +00:00
watchForOwnerChange ( )
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
2016-08-23 09:17:24 +00:00
security.getUser = 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) ->
2016-06-24 10:33:17 +00:00
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
2016-08-23 09:17:24 +00:00
idProvider = _ . head ( _ . keys ( req . session . passport . user ) )
2016-11-30 10:29:38 +00:00
console . log ' idProvider: ' , idProvider
2016-07-07 09:50:14 +00:00
switch idProvider
2021-10-18 19:13:18 +00:00
when ' github ' , ' google ' , ' twitter ' , ' oauth2 '
2016-08-23 09:17:24 +00:00
if _ . isEqual ( owner [ idProvider ] . id , req . session . passport . user [ idProvider ] . id )
2016-07-07 09:50:14 +00:00
return true
else
return false
when ' persona '
2016-08-23 09:17:24 +00:00
if _ . isEqual ( owner [ idProvider ] . email , req . session . passport . user [ idProvider ] . email )
2016-07-07 09:50:14 +00:00
return true
else
return false
else
return false
2016-06-24 10:33:17 +00:00
catch error
return false
2016-04-07 10:00:09 +00:00
2016-03-16 14:13:37 +00:00
security.isAdmin = (req) ->
2016-08-09 13:00:55 +00:00
return false if admin is undefined
2016-06-24 10:33:17 +00:00
try
2016-09-05 09:21:46 +00:00
return false if req . session . passport . user is undefined
2016-08-09 13:00:55 +00:00
catch
2016-04-29 20:29:58 +00:00
return false
2016-03-16 14:13:37 +00:00
2016-08-23 09:17:24 +00:00
idProvider = _ . head ( _ . keys ( req . session . passport . user ) )
2016-08-09 13:00:55 +00:00
if admin [ idProvider ] is undefined
console . log ' admin not defined for ' , idProvider
return false
switch idProvider
2021-10-18 19:13:18 +00:00
when " github " , " google " , " twitter " , ' oauth2 '
2016-08-23 09:17:24 +00:00
if _ . isEqual ( admin [ idProvider ] , req . session . passport . user [ idProvider ] . id )
2016-08-09 13:00:55 +00:00
return true
else
return false
when " persona "
2016-08-23 09:17:24 +00:00
if _ . isEqual ( admin [ idProvider ] , req . session . passport . user [ idProvider ] . email )
2016-08-09 13:00:55 +00:00
return true
else
return false
else
return false
2016-06-24 10:33:17 +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 )
2021-10-18 19:13:18 +00:00
# OAuth Strategy
if argv . oauth2_clientID ? and argv . oauth2_clientSecret ?
ids . push ( ' oauth2 ' )
OAuth2Strategy = require ( ' passport-oauth2 ' ) . Strategy
oauth2StrategyName = callbackHost + ' OAuth '
2021-10-23 14:56:08 +00:00
if argv . oauth2_UserInfoURL ?
OAuth2Strategy::userProfile = (accesstoken, done) ->
console . log " hello "
console . log accesstoken
@ _oauth2 . _request " GET " , argv . oauth2_UserInfoURL , null , null , accesstoken , (err, data) ->
if err
return done err
try
data = JSON . parse data
catch e
return done e
done ( null , data )
2021-10-18 19:13:18 +00:00
passport . use ( oauth2StrategyName , new OAuth2Strategy ( {
clientID: argv . oauth2_clientID
clientSecret: argv . oauth2_clientSecret
authorizationURL: argv . oauth2_AuthorizationURL
2021-10-23 14:56:08 +00:00
tokenURL: argv . oauth2_TokenURL ,
# not all providers have a way of specifying the callback URL
2021-11-04 10:12:59 +00:00
callbackURL: callbackProtocol + ' // ' + callbackHost + ' /auth/oauth2/callback ' ,
2021-10-23 14:56:08 +00:00
userInfoURL: argv . oauth2_UserInfoURL
2021-10-18 19:13:18 +00:00
} , (accessToken, refreshToken, params, profile, cb) ->
2021-11-02 18:56:21 +00:00
extractUserInfo = (uiParam, uiDef) ->
uiPath = ' '
if typeof uiParam == ' undefined ' then ( uiPath = uiDef ) else ( uiPath = uiParam )
console . log ( ' extractUI ' , uiParam , uiDef , uiPath )
sParts = uiPath . split ( ' . ' )
sFrom = sParts . shift ( )
switch sFrom
when " params "
obj = params
when " profile "
obj = profile
else
console . error ( ' *** source of user info not recognised ' , uiPath )
obj = { }
while ( sParts . length )
obj = obj [ sParts . shift ( ) ]
return obj
2021-10-23 14:56:08 +00:00
console . log ( " accessToken " , accessToken )
console . log ( " refreshToken " , refreshToken )
2021-10-18 19:13:18 +00:00
console . log ( " params " , params )
console . log ( " profile " , profile )
2021-10-23 14:56:08 +00:00
if argv . oauth2_UsernameField ?
username_query = argv . oauth2_UsernameField
else
username_query = ' params.user_id '
2021-11-02 18:56:21 +00:00
try
user.oauth2 = {
id: extractUserInfo ( argv . oauth2_IdField , ' params.user_id ' )
username: extractUserInfo ( argv . oauth2_UsernameField , ' params.user_id ' )
displayName: extractUserInfo ( argv . oauth2_DisplayNameField , ' params.user_id ' )
}
catch e
console . error ( ' *** Error extracting user info: ' , e )
2021-10-23 14:56:08 +00:00
console . log user . oauth2
2021-10-18 19:13:18 +00:00
cb ( null , user ) ) )
2016-04-29 20:29:58 +00:00
# Github Strategy
2016-03-16 14:13:37 +00:00
if argv . github_clientID ? and argv . github_clientSecret ?
2016-04-29 20:29:58 +00:00
ids . push ( ' github ' )
2021-11-11 10:14:23 +00:00
GithubStrategy = require ( ' passport-github2 ' ) . Strategy
2016-03-16 14:13:37 +00:00
2016-08-23 10:45:51 +00:00
githubStrategyName = callbackHost + ' Github '
passport . use ( githubStrategyName , new GithubStrategy ( {
2016-04-29 20:29:58 +00:00
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) ->
2016-08-23 09:17:24 +00:00
user.github = {
2016-04-29 20:29:58 +00:00
id: profile . id
username: profile . username
displayName: profile . displayName
2016-07-07 09:50:14 +00:00
emails: profile . emails
2016-04-29 20:29:58 +00:00
}
cb ( null , user ) ) )
2016-03-16 14:13:37 +00:00
2016-04-29 20:29:58 +00:00
# Twitter Strategy
2016-03-16 14:13:37 +00:00
if argv . twitter_consumerKey ? and argv . twitter_consumerSecret ?
2016-04-29 20:29:58 +00:00
ids . push ( ' twitter ' )
2016-03-16 14:13:37 +00:00
TwitterStrategy = require ( ' passport-twitter ' ) . Strategy
2016-08-23 10:45:51 +00:00
twitterStrategyName = callbackHost + ' Twitter '
passport . use ( twitterStrategyName , new TwitterStrategy ( {
2016-04-29 20:29:58 +00:00
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-08-23 09:17:24 +00:00
user.twitter = {
id: profile . id
username: profile . username
2016-04-29 20:29:58 +00:00
displayName: profile . displayName
2016-03-31 04:47:41 +00:00
}
cb ( null , user ) ) )
2016-03-16 14:13:37 +00:00
2016-04-29 20:29:58 +00:00
# Google Strategy
2016-03-16 14:13:37 +00:00
if argv . google_clientID ? and argv . google_clientSecret ?
2016-04-29 20:29:58 +00:00
ids . push ( ' google ' )
2016-03-16 14:13:37 +00:00
GoogleStrategy = require ( ' passport-google-oauth20 ' ) . Strategy
2016-08-23 10:45:51 +00:00
googleStrategyName = callbackHost + ' Google '
passport . use ( googleStrategyName , new GoogleStrategy ( {
2016-04-29 20:29:58 +00:00
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) ->
2016-08-23 09:17:24 +00:00
user.google = {
2016-04-29 20:29:58 +00:00
id: profile . id
displayName: profile . displayName
emails: profile . emails
}
2016-09-03 06:12:20 +00:00
cb ( null , user ) ) )
2016-04-29 20:29:58 +00:00
2016-05-25 07:36:55 +00:00
# Persona Strategy
PersonaStrategy = require ( ' persona-pass ' ) . Strategy
2016-07-07 09:50:14 +00:00
personaAudience = callbackProtocol + ' // ' + callbackHost
2016-08-23 10:45:51 +00:00
personaStrategyName = callbackHost + ' Persona '
passport . use ( personaStrategyName , new PersonaStrategy ( {
2016-07-07 09:50:14 +00:00
audience: personaAudience
2016-05-25 07:36:55 +00:00
} , (email, cb) ->
user = {
2016-08-23 09:17:24 +00:00
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 ( ) )
2021-10-18 19:13:18 +00:00
# OAuth2
app . get ( ' /auth/oauth2 ' , passport . authenticate ( oauth2StrategyName ) , (req, res) -> )
app . get ( ' /auth/oauth2/callback ' ,
passport . authenticate ( oauth2StrategyName , { successRedirect: ' /auth/loginDone ' , failureRedirect: ' /auth/loginDialog ' } ) )
2016-04-29 20:29:58 +00:00
# Github
2016-08-23 11:20:57 +00:00
app . get ( ' /auth/github ' , passport . authenticate ( githubStrategyName , { scope: ' user:email ' } ) , (req, res) -> )
app . get ( ' /auth/github/callback ' ,
passport . authenticate ( githubStrategyName , { successRedirect: ' /auth/loginDone ' , failureRedirect: ' /auth/loginDialog ' } ) )
2016-03-16 14:13:37 +00:00
# Twitter
2016-08-23 11:20:57 +00:00
app . get ( ' /auth/twitter ' , passport . authenticate ( twitterStrategyName ) , (req, res) -> )
app . get ( ' /auth/twitter/callback ' ,
passport . authenticate ( twitterStrategyName , { successRedirect: ' /auth/loginDone ' , failureRedirect: ' /auth/loginDialog ' } ) )
2016-03-31 04:47:41 +00:00
2016-04-29 20:29:58 +00:00
# Google
2016-08-23 11:20:57 +00:00
app . get ( ' /auth/google ' , passport . authenticate ( googleStrategyName , { scope: [
2020-02-05 15:00:33 +00:00
' profile ' , ' email '
2016-08-23 11:20:57 +00:00
] } ) )
2017-05-11 18:58:32 +00:00
# see https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters for details of prompt...
2016-08-23 11:20:57 +00:00
app . get ( ' /auth/google/callback ' ,
2017-05-11 18:45:40 +00:00
passport . authenticate ( googleStrategyName , { prompt: ' select_account ' , successRedirect: ' /auth/loginDone ' , failureRedirect: ' /auth/loginDialog ' } ) )
2016-04-29 20:29:58 +00:00
2016-05-25 07:36:55 +00:00
# Persona
app . post ( ' /auth/browserid ' ,
2016-08-23 10:45:51 +00:00
passport . authenticate ( personaStrategyName , { successRedirect: ' /auth/loginDone ' , failureRedirect: ' /auth/loginDialog ' } ) )
2016-05-25 07:36:55 +00:00
2016-04-29 20:29:58 +00:00
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
2016-04-29 20:29:58 +00:00
}
if wikiHost
settings.wikiHost = wikiHost
2018-09-23 17:37:43 +00:00
if isAuthorized ( req ) and owner isnt ' '
settings.isOwner = true
else
settings.isOwner = false
2016-04-29 20:29:58 +00:00
res . json settings
2016-03-16 14:13:37 +00:00
app . get ' /auth/loginDialog ' , (req, res) ->
2017-11-12 12:23:31 +00:00
cookies = req . cookies
2016-04-29 20:29:58 +00:00
schemeButtons = [ ]
_ ( ids ) . forEach (scheme) ->
switch scheme
2021-10-18 19:13:18 +00:00
when " oauth2 " then schemeButtons . push ( { button: " <a href= ' /auth/oauth2 ' class= ' scheme-button oauth2-button ' ><span>OAuth2</span></a> " } )
2016-04-29 20:29:58 +00:00
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> " } )
2018-12-18 20:30:14 +00:00
when " google "
schemeButtons . push ( { button: " <a href= ' # ' id= ' google ' class= ' scheme-button google-button ' ><span>Google</span></a>
< script >
googleButton = document . getElementById ( ' google ' ) ;
googleButton.onclick = function ( event ) {
window . resizeBy ( 0 , + 300 ) ;
window . location = ' /auth/google ' ;
}
< / script > " })
2016-04-12 16:46:51 +00:00
2016-03-16 14:13:37 +00:00
info = {
2017-11-12 12:23:31 +00:00
wikiName: cookies [ ' wikiName ' ]
2016-04-29 20:29:58 +00:00
wikiHostName: if wikiHost
" part of " + req . hostname + " wiki farm "
2016-04-12 16:46:51 +00:00
else
2016-04-29 20:29:58 +00:00
" 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) ->
2017-11-12 12:23:31 +00:00
cookies = req . cookies
2016-05-25 07:36:55 +00:00
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 = {
2017-11-12 12:23:31 +00:00
wikiName: cookies [ ' wikiName ' ]
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 = {
2017-11-12 12:23:31 +00:00
wikiName: cookies [ ' wikiName ' ]
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) ->
2017-11-12 12:23:31 +00:00
cookies = req . cookies
2016-08-29 14:44:58 +00:00
2016-03-31 04:47:41 +00:00
info = {
2017-11-12 12:23:31 +00:00
wikiName: cookies [ ' wikiName ' ]
2016-07-26 11:17:22 +00:00
wikiHostName: if wikiHost
" part of " + req . hostname + " wiki farm "
else
" a federated wiki site "
2016-03-31 04:47:41 +00:00
title: if owner
" Wiki Site Owner Sign-on "
else
" Sign-on to claim Wiki site "
owner: getOwner
2016-07-26 11:17:22 +00:00
authMessage: " You are now logged in<br>If this window hasn ' t closed, you can close it. "
2016-03-31 04:47:41 +00:00
}
res . render ( path . join ( __dirname , ' .. ' , ' views ' , ' done.html ' ) , info )
2018-09-09 21:14:54 +00:00
2018-08-27 04:15:49 +00:00
# if configured, enforce restricted access to json
2018-09-09 21:14:54 +00:00
# see http://ward.asia.wiki.org/login-to-view.html
2018-08-13 06:56:22 +00:00
if argv . restricted ?
2018-09-08 21:18:54 +00:00
2018-09-09 21:14:54 +00:00
allowedToView = (req) ->
allowed = [ ]
if argv . allowed_domains ?
if Array . isArray ( argv . allowed_domains )
allowed = argv . allowed_domains
else
# accommodate copy bug to be fixed soon
# https://github.com/fedwiki/wiki/blob/4c6eee69e78c1ba3f3fc8d61f4450f70afb78f10/farm.coffee#L98-L103
for k , v of argv . allowed_domains
allowed . push v
# emails = [ { value: 'ward.cunningham@gmail.com', type: 'account' } ]
emails = req . session ? . passport ? . user ? . google ? . emails
return false unless emails
for entry in emails
2018-09-08 21:18:54 +00:00
have = entry . value . split ( ' @ ' ) [ 1 ]
for want in allowed
return true if want == have
false
2018-08-27 04:15:49 +00:00
app . all ' * ' , (req, res, next) ->
2018-09-16 19:08:17 +00:00
# todo: think about assets??
2018-09-09 21:14:54 +00:00
return next ( ) unless /\.(json|html)$/ . test req . url
2018-09-16 19:08:17 +00:00
# prepare to examine remote server's forwarded session
res . header ' Access-Control-Allow-Origin ' , req . get ( ' Origin ' ) || ' * '
res . header ' Access-Control-Allow-Credentials ' , ' true '
2018-09-09 21:14:54 +00:00
return next ( ) if isAuthorized ( req ) || allowedToView ( req )
return res . redirect ( " /view/ #{ m [ 1 ] } " ) if m = req . url . match / \ / ( . * ) \ . html /
return res . json ( [ ] ) if req . url == ' /system/sitemap.json '
2018-09-16 19:08:17 +00:00
# not happy, explain why these pages can't be viewed
2018-09-09 21:14:54 +00:00
problem = " This is a restricted wiki requires users to login to view pages. You do not have to be the site owner but you do need to login with a participating email address. "
details = " [ #{ argv . details || ' http://ward.asia.wiki.org/login-to-view.html ' } details] "
res . status ( 200 ) . json (
{
" title " : " Login Required " ,
" story " : [
{
" type " : " paragraph " ,
" id " : " 55d44b367ed64875 " ,
" text " : " #{ problem } #{ details } "
}
]
}
)
2018-08-27 04:15:49 +00:00
2018-08-13 06:56:22 +00:00
2016-07-26 11:17:22 +00:00
app . get ' /auth/addAuthDialog ' , (req, res) ->
2016-08-23 09:17:24 +00:00
# only makes sense to add alternative authentication scheme if
# this the user is authenticated
2016-11-25 17:34:24 +00:00
user = getUser ( req )
if user
2017-11-12 12:23:31 +00:00
cookies = req . cookies
2016-08-23 09:17:24 +00:00
2016-08-29 14:44:58 +00:00
currentSchemes = _ . keys ( user )
2016-08-23 09:17:24 +00:00
altSchemes = _ . difference ( ids , currentSchemes )
schemeButtons = [ ]
_ ( altSchemes ) . 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> " } )
info = {
2017-11-12 12:23:31 +00:00
wikiName: cookies [ ' wikiName ' ]
2016-08-23 09:17:24 +00:00
wikiHostName: if wikiHost
" part of " + req . hostname + " wiki farm "
else
" a federated wiki site "
title: " Federated Wiki: Add Alternative Authentication Scheme "
schemes: schemeButtons
}
res . render ( path . join ( __dirname , ' .. ' , ' views ' , ' addAlternativeDialog.html ' ) , info )
else
# user is not authenticated
res . sendStatus ( 403 )
authorized = (req, res, next) ->
if isAuthorized ( req )
next ( )
else
2016-08-29 14:44:58 +00:00
console . log ' rejecting - not authorized ' , req . path
2016-08-23 09:17:24 +00:00
res . sendStatus ( 403 )
app . get ' /auth/addAltAuth ' , authorized , (req, res) ->
# add alternative authorentication scheme - only makes sense if user owns this site
res . status ( 202 ) . end ( )
user = req . session . passport . user
2016-08-29 14:44:58 +00:00
idProviders = _ . keys ( user )
2016-11-21 12:36:43 +00:00
userIds = { }
2016-08-29 14:44:58 +00:00
idProviders . forEach (idProvider) ->
id = switch idProvider
2021-10-18 19:13:18 +00:00
when " oauth2 " then {
name: user . oauth2 . displayName
oauth2: {
id: user . oauth2 . id
username: user . oauth2 . username
}
}
2016-08-29 14:44:58 +00:00
when " twitter " then {
name: user . twitter . displayName
twitter: {
id: user . twitter . id
username: user . twitter . username
}
}
when " github " then {
name: user . github . displayName
github: {
id: user . github . id
username: user . github . username
email: user . github . emails
}
}
when " google " then {
name: user . google . displayName
google: {
id: user . google . id
emails: user . google . emails
}
}
# only needed until persona closes
when " persona " then {
name: user . persona . email
. substr ( 0 , user . persona . email . indexOf ( ' @ ' ) )
. split ( ' . ' )
. join ( ' ' )
. toLowerCase ( )
. replace ( /(^| )(\w)/g , (x) ->
return x . toUpperCase ( ) )
persona: {
email: user . persona . email
}
}
2016-11-21 12:36:43 +00:00
userIds = _ . merge ( userIds , id )
2016-08-23 09:17:24 +00:00
wikiDir = path . resolve ( argv . data , ' .. ' )
statusDir = argv . status . split ( path . sep ) . slice ( - 1 ) [ 0 ]
idFileName = path . parse ( idFile ) . base
pattern = ' */ ' + statusDir + ' / ' + idFileName
glob ( pattern , { cwd: wikiDir } , (err, files) ->
_ . forEach files , (file) ->
# are we the owner?
fs . readFile ( path . join ( wikiDir , file ) , ' utf8 ' , (err, data) ->
if err
console . log ' Error reading ' , file , err
return
siteOwner = JSON . parse ( data )
2016-08-23 11:20:57 +00:00
if _ . intersectionWith ( _ . entries ( siteOwner ) , _ . entries ( user ) , _ . isEqual ) . length > 0
2016-08-29 14:44:58 +00:00
updateOwner = _ . merge ( user , siteOwner )
2016-11-21 12:36:43 +00:00
fs . writeFile ( path . join ( wikiDir , file ) , JSON . stringify ( userIds ) , (err) ->
2016-08-29 14:44:58 +00:00
if err
console . log ' Error writing ' , file , err
# if the write works the change will be picked up by fs.watch() in watchForOwnerChange
# so there is nothing more to do here.
)
)
2016-08-23 09:17:24 +00:00
)
2016-07-26 11:17:22 +00:00
2016-04-07 10:00:09 +00:00
app . get ' /auth/claim-wiki ' , (req, res) ->
if owner
2016-08-29 14:44:58 +00:00
console . log ' Claim Request Ignored: Wiki already has owner - ' , wikiName
2016-04-07 10:00:09 +00:00
res . sendStatus ( 403 )
else
user = req . session . passport . user
2016-08-23 09:17:24 +00:00
# there can be more than one id provider - initially only if we logged in with persona
idProviders = _ . keys ( user )
id = { }
idProviders . forEach (idProvider) ->
id = switch idProvider
2021-10-18 19:13:18 +00:00
when " oauth2 " then {
name: user . oauth2 . displayName
oauth2: {
id: user . oauth2 . id
username: user . oauth2 . username
}
}
2016-08-23 09:17:24 +00:00
when " twitter " then {
name: user . twitter . displayName
twitter: {
id: user . twitter . id
username: user . twitter . username
}
2016-04-07 10:00:09 +00:00
}
2016-08-23 09:17:24 +00:00
when " github " then {
name: user . github . displayName
github: {
id: user . github . id
username: user . github . username
email: user . github . emails
}
2016-04-29 20:29:58 +00:00
}
2016-08-23 09:17:24 +00:00
when " google " then {
2018-09-09 21:14:54 +00:00
name: user . google . displayName || ( user . google . emails [ 0 ] ? . value ? . split ( ' @ ' ) [ 0 ] ) || ' unknown '
2016-08-23 09:17:24 +00:00
google: {
id: user . google . id
emails: user . google . emails
}
}
# only needed until persona closes
when " persona " then {
name: user . persona . email
. substr ( 0 , user . persona . email . indexOf ( ' @ ' ) )
. split ( ' . ' )
. join ( ' ' )
. toLowerCase ( )
. replace ( /(^| )(\w)/g , (x) ->
return x . toUpperCase ( ) )
persona: {
email: user . persona . email
}
2016-04-29 20:29:58 +00:00
}
2016-04-07 10:00:09 +00:00
2016-08-23 09:17:24 +00:00
if _ . isEmpty ( id )
console . log ' Unable to claim wiki ' , req . hostname , ' no valid id provided '
res . sendStatus ( 500 )
else
setOwner id , (err) ->
if err
console . log ' Failed to claim wiki ' , req . hostname , ' for ' , id
res . sendStatus ( 500 )
updateOwner getOwner ( )
res . json ( {
ownerName: id . name
} )
2016-04-07 10:00:09 +00:00
2020-02-14 19:54:22 +00:00
app . get ' /auth/diag ' , (req, res) ->
# some diagnostic feedback to the user, for when something strange happens
user = ' User is unknown '
try
user = req . session . passport . user
date = new Date ( ) . toString ( )
wikiName = new URL ( argv . url ) . hostname
console . log ' SOCIAL *** ' , date , ' *** ' , wikiName , ' *** ' , JSON . stringify ( user )
res . json date
2016-04-07 10:00:09 +00:00
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