From 5f2164cb12def29f695995a2cc8a648d5ccf2adb Mon Sep 17 00:00:00 2001
From: 3wc <3wc.github@doesthisthing.work>
Date: Mon, 18 Oct 2021 21:13:18 +0200
Subject: [PATCH 1/5] Add generic OAuth support
---
docs/config-oauth2.md | 15 +++++++++++++
docs/configuration.md | 1 +
server/social.coffee | 50 +++++++++++++++++++++++++++++++++++++++++--
3 files changed, 64 insertions(+), 2 deletions(-)
create mode 100644 docs/config-oauth2.md
diff --git a/docs/config-oauth2.md b/docs/config-oauth2.md
new file mode 100644
index 0000000..8313124
--- /dev/null
+++ b/docs/config-oauth2.md
@@ -0,0 +1,15 @@
+
+
+```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",
+ "wikiDomains": {
+ "example.wiki": {}
+ }
+}
+```
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/server/social.coffee b/server/social.coffee
index 1cd9fcb..a4dad83 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,32 @@ 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'
+
+ passport.use(oauth2StrategyName, new OAuth2Strategy({
+ clientID: argv.oauth2_clientID
+ clientSecret: argv.oauth2_clientSecret
+ authorizationURL: argv.oauth2_AuthorizationURL
+ tokenURL: argv.oauth2_TokenURL
+ # userURL: argv.oauth2_UserURL
+ # 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.
+ }, (accessToken, refreshToken, params, profile, cb) ->
+ console.log("params", params)
+ console.log("profile", profile)
+ user.oauth2 = {
+ id: params.user_id,
+ username: params.user_id,
+ displayName: params.user_id,
+ }
+ cb(null, user)))
+
# Github Strategy
if argv.github_clientID? and argv.github_clientSecret?
ids.push('github')
@@ -275,6 +301,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 +348,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 +536,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 +619,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: {
From f4f44afa35c91bc4325a3fa26c942acb523e4a33 Mon Sep 17 00:00:00 2001
From: 3wc <3wc.github@doesthisthing.work>
Date: Fri, 22 Oct 2021 00:29:07 +0200
Subject: [PATCH 2/5] Add passport-oauth2 to package.json
---
package.json | 1 +
1 file changed, 1 insertion(+)
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",
From 001def2feaa99199c25a40363952e4b0ff90bfd1 Mon Sep 17 00:00:00 2001
From: 3wc <3wc.github@doesthisthing.work>
Date: Sat, 23 Oct 2021 16:56:08 +0200
Subject: [PATCH 3/5] Custom callback and user profile URLs for OAuth2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
For parsing `oauth2_UsernameField` values like
`profile.preferred_username`, this makes use of `eval()` which is
generally Evilâ„¢, but I'm assuming that anyone with permission to edit
config.json likely has permission to make changes to the fedwiki source
code already anyway, so it's fragile rather than increasing a security
attack surface. An alternative would be using a small function to look
up properties of the `params` / `profile` objects using the same
dotted-path notation.
---
docs/config-oauth2.md | 51 ++++++++++++++++++++++++++++++++++++++++---
server/social.coffee | 35 ++++++++++++++++++++++-------
2 files changed, 75 insertions(+), 11 deletions(-)
diff --git a/docs/config-oauth2.md b/docs/config-oauth2.md
index 8313124..b1710fd 100644
--- a/docs/config-oauth2.md
+++ b/docs/config-oauth2.md
@@ -1,4 +1,36 @@
+## 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_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
{
@@ -8,8 +40,21 @@
"oauth2_clientSecret": "CLIENT SECRET",
"oauth2_AuthorizationURL": "https://auth.example.com/oauth2/authorize",
"oauth2_TokenURL": "https://auth.example.com/oauth2/token",
- "wikiDomains": {
- "example.wiki": {}
- }
+}
+```
+
+#### 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_CallbackURL": "http://localhost:3000/auth/oauth2/callback",
+ "oauth2_UsernameField": "profile.preferred_username"
}
```
diff --git a/server/social.coffee b/server/social.coffee
index a4dad83..97ffce0 100644
--- a/server/social.coffee
+++ b/server/social.coffee
@@ -201,23 +201,42 @@ module.exports = exports = (log, loga, argv) ->
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
- # userURL: argv.oauth2_UserURL
- # 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.
+ tokenURL: argv.oauth2_TokenURL,
+ # not all providers have a way of specifying the callback URL
+ callbackURL: argv.oauth2_CallbackURL,
+ userInfoURL: argv.oauth2_UserInfoURL
}, (accessToken, refreshToken, params, profile, cb) ->
+ 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'
user.oauth2 = {
- id: params.user_id,
- username: params.user_id,
- displayName: params.user_id,
+ id: eval username_query,
+ username: eval username_query
+ displayName: eval username_query
}
+ console.log user.oauth2
cb(null, user)))
# Github Strategy
From 7eba6ba411a0313855652725dd383774c5c1eee7 Mon Sep 17 00:00:00 2001
From: Paul Rodwell
Date: Tue, 2 Nov 2021 18:56:21 +0000
Subject: [PATCH 4/5] replacing eval() with function using property accessors
---
docs/config-oauth2.md | 2 +-
server/social.coffee | 34 +++++++++++++++++++++++++++++-----
2 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/docs/config-oauth2.md b/docs/config-oauth2.md
index b1710fd..595cd2a 100644
--- a/docs/config-oauth2.md
+++ b/docs/config-oauth2.md
@@ -21,7 +21,7 @@ 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_UsernameField` -- starting with
+* `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.
diff --git a/server/social.coffee b/server/social.coffee
index 97ffce0..2b56754 100644
--- a/server/social.coffee
+++ b/server/social.coffee
@@ -223,6 +223,26 @@ module.exports = exports = (log, loga, argv) ->
callbackURL: argv.oauth2_CallbackURL,
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)
@@ -231,11 +251,15 @@ module.exports = exports = (log, loga, argv) ->
username_query = argv.oauth2_UsernameField
else
username_query = 'params.user_id'
- user.oauth2 = {
- id: eval username_query,
- username: eval username_query
- displayName: eval username_query
- }
+
+ 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)))
From 88b0e2b825c240c63c9c629b84e9fae947cddf1e Mon Sep 17 00:00:00 2001
From: Paul Rodwell
Date: Thu, 4 Nov 2021 10:12:59 +0000
Subject: [PATCH 5/5] callbackURL has fix location, rather than being a
parameter.
---
docs/config-oauth2.md | 1 -
server/social.coffee | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/docs/config-oauth2.md b/docs/config-oauth2.md
index 595cd2a..9bb94c8 100644
--- a/docs/config-oauth2.md
+++ b/docs/config-oauth2.md
@@ -54,7 +54,6 @@ Sometimes, you'll be able to look up the URLs by visiting your provider's
"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_CallbackURL": "http://localhost:3000/auth/oauth2/callback",
"oauth2_UsernameField": "profile.preferred_username"
}
```
diff --git a/server/social.coffee b/server/social.coffee
index 2b56754..0ce6eb4 100644
--- a/server/social.coffee
+++ b/server/social.coffee
@@ -220,7 +220,7 @@ module.exports = exports = (log, loga, argv) ->
authorizationURL: argv.oauth2_AuthorizationURL
tokenURL: argv.oauth2_TokenURL,
# not all providers have a way of specifying the callback URL
- callbackURL: argv.oauth2_CallbackURL,
+ callbackURL: callbackProtocol + '//' + callbackHost + '/auth/oauth2/callback',
userInfoURL: argv.oauth2_UserInfoURL
}, (accessToken, refreshToken, params, profile, cb) ->