diff --git a/docs/config-oauth2.md b/docs/config-oauth2.md
new file mode 100644
index 0000000..9bb94c8
--- /dev/null
+++ b/docs/config-oauth2.md
@@ -0,0 +1,59 @@
+## Generic OAuth 2
+
+### Login provider set-up
+
+Like the other PassportJS login providers, we'll need a separate "OAuth2 Client"
+(others call it an "app", a "product" etc.) for our Federated Wiki instance.
+
+How to do this varies slightly for each provider.
+
+### `config.json`
+
+In general, you will need to specify:
+* `oauth2_clientID` -- some systems generate this for you, others allow you to
+ specify it
+* `oauth2_clientSecret` -- secure key (keep this secret!)
+* `oauth2_AuthorizationURL` and `oauth2_TokenURL` -- from your login provider's documentation
+
+You will also need to specify a callback URL. For some providers, you can add
+this when making a new "OAuth Client" for your wiki, for others you will need to
+specify it with `oauth2_CallbackURL`.
+
+You might also need to tell Federated Wiki how to look up usernames:
+* `oauth2_UserInfoURL` -- from login provider's documentation
+* `oauth2_IdField`, `oauth2_DisplayNameField`, `oauth2_UsernameField` -- starting with
+ * `params` for information returned in the original token request, or
+ * `profile` for data returned from `oauth2_UserInfoURL`, if you provided it.
+
+Sometimes, you'll be able to look up the URLs by visiting your provider's
+`/.well-known/openid-configuration` URL in a web browser.
+
+### Examples
+
+#### Nextcloud
+
+```JSON
+{
+ "farm": true,
+ "security_type": "passportjs",
+ "oauth2_clientID": "CLIENT ID",
+ "oauth2_clientSecret": "CLIENT SECRET",
+ "oauth2_AuthorizationURL": "https://auth.example.com/oauth2/authorize",
+ "oauth2_TokenURL": "https://auth.example.com/oauth2/token",
+}
+```
+
+#### Keycloak
+
+```JSON
+{
+ "farm": true,
+ "security_type": "passportjs",
+ "oauth2_clientID": "CLIENT ID",
+ "oauth2_clientSecret": "CLIENT SECRET",
+ "oauth2_AuthorizationURL": "https://auth.example.com/auth/realms/Wiki.Cafe/protocol/openid-connect/auth",
+ "oauth2_TokenURL": "https://auth.example.com/auth/realms/Wiki.Cafe/protocol/openid-connect/token",
+ "oauth2_UserInfoURL": "https://auth.example.com/auth/realms/Wiki.Cafe/protocol/openid-connect/userinfo",
+ "oauth2_UsernameField": "profile.preferred_username"
+}
+```
diff --git a/docs/configuration.md b/docs/configuration.md
index 455daf8..7cf9c09 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -17,3 +17,4 @@ See, depending on which identity provider you choose to use:
* [GitHub](./config-github.md)
* [Google](./config-google.md)
* [Twitter](./config-twitter.md)
+* [Generic OAuth](./config-oauth2.md)
diff --git a/package.json b/package.json
index 8f27f32..3b1e257 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"passport": "^0.3.2",
"passport-github": "github:FedWiki/passport-github#70fe99ba7e66422092a19d89f4b1ab1a23eac644",
"passport-google-oauth20": "^2.0.0",
+ "passport-oauth2": "^1.6.1",
"passport-twitter": "^1.0.4",
"persona-pass": "^0.2.1",
"qs": "^6.7.0",
diff --git a/server/social.coffee b/server/social.coffee
index 1cd9fcb..0ce6eb4 100644
--- a/server/social.coffee
+++ b/server/social.coffee
@@ -135,7 +135,7 @@ module.exports = exports = (log, loga, argv) ->
idProvider = _.head(_.keys(req.session.passport.user))
console.log 'idProvider: ', idProvider
switch idProvider
- when 'github', 'google', 'twitter'
+ when 'github', 'google', 'twitter', 'oauth2'
if _.isEqual(owner[idProvider].id, req.session.passport.user[idProvider].id)
return true
else
@@ -165,7 +165,7 @@ module.exports = exports = (log, loga, argv) ->
return false
switch idProvider
- when "github", "google", "twitter"
+ when "github", "google", "twitter", 'oauth2'
if _.isEqual(admin[idProvider], req.session.passport.user[idProvider].id)
return true
else
@@ -194,6 +194,75 @@ module.exports = exports = (log, loga, argv) ->
passport.deserializeUser = (obj, req, done) ->
done(null, obj)
+ # OAuth Strategy
+ if argv.oauth2_clientID? and argv.oauth2_clientSecret?
+ ids.push('oauth2')
+ OAuth2Strategy = require('passport-oauth2').Strategy
+
+ oauth2StrategyName = callbackHost + 'OAuth'
+
+ 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)
+
+ passport.use(oauth2StrategyName, new OAuth2Strategy({
+ clientID: argv.oauth2_clientID
+ clientSecret: argv.oauth2_clientSecret
+ authorizationURL: argv.oauth2_AuthorizationURL
+ tokenURL: argv.oauth2_TokenURL,
+ # not all providers have a way of specifying the callback URL
+ callbackURL: callbackProtocol + '//' + callbackHost + '/auth/oauth2/callback',
+ userInfoURL: argv.oauth2_UserInfoURL
+ }, (accessToken, refreshToken, params, profile, cb) ->
+
+ 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
+
+ console.log("accessToken", accessToken)
+ console.log("refreshToken", refreshToken)
+ console.log("params", params)
+ console.log("profile", profile)
+ if argv.oauth2_UsernameField?
+ username_query = argv.oauth2_UsernameField
+ else
+ username_query = 'params.user_id'
+
+ 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)
+ console.log user.oauth2
+ cb(null, user)))
+
# Github Strategy
if argv.github_clientID? and argv.github_clientSecret?
ids.push('github')
@@ -275,6 +344,11 @@ module.exports = exports = (log, loga, argv) ->
app.use(passport.initialize())
app.use(passport.session())
+ # OAuth2
+ app.get('/auth/oauth2', passport.authenticate(oauth2StrategyName), (req, res) -> )
+ app.get('/auth/oauth2/callback',
+ passport.authenticate(oauth2StrategyName, { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
+
# Github
app.get('/auth/github', passport.authenticate(githubStrategyName, {scope: 'user:email'}), (req, res) -> )
app.get('/auth/github/callback',
@@ -317,6 +391,7 @@ module.exports = exports = (log, loga, argv) ->
schemeButtons = []
_(ids).forEach (scheme) ->
switch scheme
+ when "oauth2" then schemeButtons.push({button: "OAuth2"})
when "twitter" then schemeButtons.push({button: ""})
when "github" then schemeButtons.push({button: "Github"})
when "google"
@@ -504,6 +579,13 @@ module.exports = exports = (log, loga, argv) ->
userIds = {}
idProviders.forEach (idProvider) ->
id = switch idProvider
+ when "oauth2" then {
+ name: user.oauth2.displayName
+ oauth2: {
+ id: user.oauth2.id
+ username: user.oauth2.username
+ }
+ }
when "twitter" then {
name: user.twitter.displayName
twitter: {
@@ -580,6 +662,13 @@ module.exports = exports = (log, loga, argv) ->
id = {}
idProviders.forEach (idProvider) ->
id = switch idProvider
+ when "oauth2" then {
+ name: user.oauth2.displayName
+ oauth2: {
+ id: user.oauth2.id
+ username: user.oauth2.username
+ }
+ }
when "twitter" then {
name: user.twitter.displayName
twitter: {