wiki-security-passportjs 0.4.8 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AUTHORS.txt CHANGED
@@ -4,3 +4,4 @@ Paul Rodwell <paul.rodwell@btinternet.com>
4
4
  Ward Cunningham <ward@c2.com>
5
5
  Marc-Antoine Parent <maparent@acm.org>
6
6
  Ian Goodacre <Ian.Goodacre@entrain.nz>
7
+ 3wc <3wc.github@doesthisthing.work>
package/Gruntfile.js CHANGED
@@ -2,7 +2,6 @@ module.exports = function (grunt) {
2
2
  grunt.loadNpmTasks('grunt-browserify');
3
3
  grunt.loadNpmTasks('grunt-contrib-watch');
4
4
  grunt.loadNpmTasks('grunt-git-authors');
5
- grunt.loadNpmTasks('grunt-retire');
6
5
 
7
6
  grunt.initConfig({
8
7
 
@@ -24,16 +23,10 @@ module.exports = function (grunt) {
24
23
  files: ['client/*.coffee'],
25
24
  tasks: ['build']
26
25
  }
27
- },
28
26
 
29
- retire: {
30
- node: ['.'],
31
- options: {packageOnly: true}
32
27
  }
33
-
34
28
  });
35
29
 
36
- grunt.registerTask('check', ['retire']);
37
30
  grunt.registerTask('build', ['browserify']);
38
31
  grunt.registerTask('default', ['build']);
39
32
 
@@ -272,7 +272,7 @@ window.plugins.security = {setup, claim_wiki, update_footer};
272
272
 
273
273
 
274
274
  },{"es6-promise":2,"whatwg-fetch":4}],2:[function(require,module,exports){
275
- (function (process,global){
275
+ (function (process,global){(function (){
276
276
  /*!
277
277
  * @overview es6-promise - a tiny implementation of Promises/A+.
278
278
  * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
@@ -1448,7 +1448,7 @@ return Promise$1;
1448
1448
 
1449
1449
 
1450
1450
 
1451
- }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
1451
+ }).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
1452
1452
  },{"_process":3}],3:[function(require,module,exports){
1453
1453
  // shim for using process in browser
1454
1454
  var process = module.exports = {};
@@ -1640,12 +1640,13 @@ process.umask = function() { return 0; };
1640
1640
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
1641
1641
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
1642
1642
  (factory((global.WHATWGFetch = {})));
1643
- }(this, (function (exports) {
1643
+ }(this, (function (exports) { 'use strict';
1644
+
1645
+ var global =
1646
+ (typeof globalThis !== 'undefined' && globalThis) ||
1647
+ (typeof self !== 'undefined' && self) ||
1648
+ (typeof global !== 'undefined' && global);
1644
1649
 
1645
- var global = (function(self) {
1646
- return self
1647
- // eslint-disable-next-line no-invalid-this
1648
- })(typeof self !== 'undefined' ? self : this);
1649
1650
  var support = {
1650
1651
  searchParams: 'URLSearchParams' in global,
1651
1652
  iterable: 'Symbol' in global && 'iterator' in Symbol,
@@ -1693,7 +1694,7 @@ process.umask = function() { return 0; };
1693
1694
  name = String(name);
1694
1695
  }
1695
1696
  if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') {
1696
- throw new TypeError('Invalid character in header field name')
1697
+ throw new TypeError('Invalid character in header field name: "' + name + '"')
1697
1698
  }
1698
1699
  return name.toLowerCase()
1699
1700
  }
@@ -1920,7 +1921,20 @@ process.umask = function() { return 0; };
1920
1921
 
1921
1922
  this.arrayBuffer = function() {
1922
1923
  if (this._bodyArrayBuffer) {
1923
- return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
1924
+ var isConsumed = consumed(this);
1925
+ if (isConsumed) {
1926
+ return isConsumed
1927
+ }
1928
+ if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
1929
+ return Promise.resolve(
1930
+ this._bodyArrayBuffer.buffer.slice(
1931
+ this._bodyArrayBuffer.byteOffset,
1932
+ this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
1933
+ )
1934
+ )
1935
+ } else {
1936
+ return Promise.resolve(this._bodyArrayBuffer)
1937
+ }
1924
1938
  } else {
1925
1939
  return this.blob().then(readBlobAsArrayBuffer)
1926
1940
  }
@@ -1966,6 +1980,10 @@ process.umask = function() { return 0; };
1966
1980
  }
1967
1981
 
1968
1982
  function Request(input, options) {
1983
+ if (!(this instanceof Request)) {
1984
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.')
1985
+ }
1986
+
1969
1987
  options = options || {};
1970
1988
  var body = options.body;
1971
1989
 
@@ -2044,20 +2062,31 @@ process.umask = function() { return 0; };
2044
2062
  // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
2045
2063
  // https://tools.ietf.org/html/rfc7230#section-3.2
2046
2064
  var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
2047
- preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
2048
- var parts = line.split(':');
2049
- var key = parts.shift().trim();
2050
- if (key) {
2051
- var value = parts.join(':').trim();
2052
- headers.append(key, value);
2053
- }
2054
- });
2065
+ // Avoiding split via regex to work around a common IE11 bug with the core-js 3.6.0 regex polyfill
2066
+ // https://github.com/github/fetch/issues/748
2067
+ // https://github.com/zloirock/core-js/issues/751
2068
+ preProcessedHeaders
2069
+ .split('\r')
2070
+ .map(function(header) {
2071
+ return header.indexOf('\n') === 0 ? header.substr(1, header.length) : header
2072
+ })
2073
+ .forEach(function(line) {
2074
+ var parts = line.split(':');
2075
+ var key = parts.shift().trim();
2076
+ if (key) {
2077
+ var value = parts.join(':').trim();
2078
+ headers.append(key, value);
2079
+ }
2080
+ });
2055
2081
  return headers
2056
2082
  }
2057
2083
 
2058
2084
  Body.call(Request.prototype);
2059
2085
 
2060
2086
  function Response(bodyInit, options) {
2087
+ if (!(this instanceof Response)) {
2088
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.')
2089
+ }
2061
2090
  if (!options) {
2062
2091
  options = {};
2063
2092
  }
@@ -2065,7 +2094,7 @@ process.umask = function() { return 0; };
2065
2094
  this.type = 'default';
2066
2095
  this.status = options.status === undefined ? 200 : options.status;
2067
2096
  this.ok = this.status >= 200 && this.status < 300;
2068
- this.statusText = 'statusText' in options ? options.statusText : '';
2097
+ this.statusText = options.statusText === undefined ? '' : '' + options.statusText;
2069
2098
  this.headers = new Headers(options.headers);
2070
2099
  this.url = options.url || '';
2071
2100
  this._initBody(bodyInit);
@@ -2099,8 +2128,9 @@ process.umask = function() { return 0; };
2099
2128
  };
2100
2129
 
2101
2130
  exports.DOMException = global.DOMException;
2102
-
2103
- if (typeof exports.DOMException !== 'function') {
2131
+ try {
2132
+ new exports.DOMException();
2133
+ } catch (err) {
2104
2134
  exports.DOMException = function(message, name) {
2105
2135
  this.message = message;
2106
2136
  this.name = name;
@@ -2184,9 +2214,15 @@ process.umask = function() { return 0; };
2184
2214
  }
2185
2215
  }
2186
2216
 
2187
- request.headers.forEach(function(value, name) {
2188
- xhr.setRequestHeader(name, value);
2189
- });
2217
+ if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers)) {
2218
+ Object.getOwnPropertyNames(init.headers).forEach(function(name) {
2219
+ xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
2220
+ });
2221
+ } else {
2222
+ request.headers.forEach(function(value, name) {
2223
+ xhr.setRequestHeader(name, value);
2224
+ });
2225
+ }
2190
2226
 
2191
2227
  if (request.signal) {
2192
2228
  request.signal.addEventListener('abort', abortXhr);
@@ -0,0 +1,59 @@
1
+ ## Generic OAuth 2
2
+
3
+ ### Login provider set-up
4
+
5
+ Like the other PassportJS login providers, we'll need a separate "OAuth2 Client"
6
+ (others call it an "app", a "product" etc.) for our Federated Wiki instance.
7
+
8
+ How to do this varies slightly for each provider.
9
+
10
+ ### `config.json`
11
+
12
+ In general, you will need to specify:
13
+ * `oauth2_clientID` -- some systems generate this for you, others allow you to
14
+ specify it
15
+ * `oauth2_clientSecret` -- secure key (keep this secret!)
16
+ * `oauth2_AuthorizationURL` and `oauth2_TokenURL` -- from your login provider's documentation
17
+
18
+ You will also need to specify a callback URL. For some providers, you can add
19
+ this when making a new "OAuth Client" for your wiki, for others you will need to
20
+ specify it with `oauth2_CallbackURL`.
21
+
22
+ You might also need to tell Federated Wiki how to look up usernames:
23
+ * `oauth2_UserInfoURL` -- from login provider's documentation
24
+ * `oauth2_IdField`, `oauth2_DisplayNameField`, `oauth2_UsernameField` -- starting with
25
+ * `params` for information returned in the original token request, or
26
+ * `profile` for data returned from `oauth2_UserInfoURL`, if you provided it.
27
+
28
+ Sometimes, you'll be able to look up the URLs by visiting your provider's
29
+ `/.well-known/openid-configuration` URL in a web browser.
30
+
31
+ ### Examples
32
+
33
+ #### Nextcloud
34
+
35
+ ```JSON
36
+ {
37
+ "farm": true,
38
+ "security_type": "passportjs",
39
+ "oauth2_clientID": "CLIENT ID",
40
+ "oauth2_clientSecret": "CLIENT SECRET",
41
+ "oauth2_AuthorizationURL": "https://auth.example.com/oauth2/authorize",
42
+ "oauth2_TokenURL": "https://auth.example.com/oauth2/token",
43
+ }
44
+ ```
45
+
46
+ #### Keycloak
47
+
48
+ ```JSON
49
+ {
50
+ "farm": true,
51
+ "security_type": "passportjs",
52
+ "oauth2_clientID": "CLIENT ID",
53
+ "oauth2_clientSecret": "CLIENT SECRET",
54
+ "oauth2_AuthorizationURL": "https://auth.example.com/auth/realms/Wiki.Cafe/protocol/openid-connect/auth",
55
+ "oauth2_TokenURL": "https://auth.example.com/auth/realms/Wiki.Cafe/protocol/openid-connect/token",
56
+ "oauth2_UserInfoURL": "https://auth.example.com/auth/realms/Wiki.Cafe/protocol/openid-connect/userinfo",
57
+ "oauth2_UsernameField": "profile.preferred_username"
58
+ }
59
+ ```
@@ -17,3 +17,4 @@ See, depending on which identity provider you choose to use:
17
17
  * [GitHub](./config-github.md)
18
18
  * [Google](./config-google.md)
19
19
  * [Twitter](./config-twitter.md)
20
+ * [Generic OAuth](./config-oauth2.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-security-passportjs",
3
- "version": "0.4.8",
3
+ "version": "0.6.0",
4
4
  "description": "Security plugin for Federated Wiki, using passport.js",
5
5
  "author": "Paul Rodwell <paul.rodwell@btinternet.com> (http://rodwell.me)",
6
6
  "license": "MIT",
@@ -9,9 +9,10 @@
9
9
  "es6-promise": "^4.2.8",
10
10
  "lodash": "^4.17.19",
11
11
  "passport": "^0.3.2",
12
- "passport-github": "github:FedWiki/passport-github#70fe99ba7e66422092a19d89f4b1ab1a23eac644",
12
+ "passport-github2": "^0.1.12",
13
13
  "passport-google-oauth20": "^2.0.0",
14
- "passport-twitter": "^1.0.4",
14
+ "passport-oauth2": "^1.6.1",
15
+ "passport-twitter": "github:paul90/passport-twitter#48b52556f48e4e8f7c55288baaf3ba3076eeba16",
15
16
  "persona-pass": "^0.2.1",
16
17
  "qs": "^6.7.0",
17
18
  "whatwg-fetch": "^3.2.0"
@@ -19,10 +20,9 @@
19
20
  "devDependencies": {
20
21
  "coffeeify": "^3.0.1",
21
22
  "grunt": "^1.2.1",
22
- "grunt-browserify": "^5.2.0",
23
+ "grunt-browserify": "^6.0.0",
23
24
  "grunt-contrib-watch": "^1.0.0",
24
- "grunt-git-authors": "^3.2.0",
25
- "grunt-retire": "^1.0.8"
25
+ "grunt-git-authors": "^3.2.0"
26
26
  },
27
27
  "repository": {
28
28
  "type": "git",
@@ -135,7 +135,7 @@ module.exports = exports = (log, loga, argv) ->
135
135
  idProvider = _.head(_.keys(req.session.passport.user))
136
136
  console.log 'idProvider: ', idProvider
137
137
  switch idProvider
138
- when 'github', 'google', 'twitter'
138
+ when 'github', 'google', 'twitter', 'oauth2'
139
139
  if _.isEqual(owner[idProvider].id, req.session.passport.user[idProvider].id)
140
140
  return true
141
141
  else
@@ -165,7 +165,7 @@ module.exports = exports = (log, loga, argv) ->
165
165
  return false
166
166
 
167
167
  switch idProvider
168
- when "github", "google", "twitter"
168
+ when "github", "google", "twitter", 'oauth2'
169
169
  if _.isEqual(admin[idProvider], req.session.passport.user[idProvider].id)
170
170
  return true
171
171
  else
@@ -194,10 +194,79 @@ module.exports = exports = (log, loga, argv) ->
194
194
  passport.deserializeUser = (obj, req, done) ->
195
195
  done(null, obj)
196
196
 
197
+ # OAuth Strategy
198
+ if argv.oauth2_clientID? and argv.oauth2_clientSecret?
199
+ ids.push('oauth2')
200
+ OAuth2Strategy = require('passport-oauth2').Strategy
201
+
202
+ oauth2StrategyName = callbackHost + 'OAuth'
203
+
204
+ if argv.oauth2_UserInfoURL?
205
+ OAuth2Strategy::userProfile = (accesstoken, done) ->
206
+ console.log "hello"
207
+ console.log accesstoken
208
+ @_oauth2._request "GET", argv.oauth2_UserInfoURL, null, null, accesstoken, (err, data) ->
209
+ if err
210
+ return done err
211
+ try
212
+ data = JSON.parse data
213
+ catch e
214
+ return done e
215
+ done(null, data)
216
+
217
+ passport.use(oauth2StrategyName, new OAuth2Strategy({
218
+ clientID: argv.oauth2_clientID
219
+ clientSecret: argv.oauth2_clientSecret
220
+ authorizationURL: argv.oauth2_AuthorizationURL
221
+ tokenURL: argv.oauth2_TokenURL,
222
+ # not all providers have a way of specifying the callback URL
223
+ callbackURL: callbackProtocol + '//' + callbackHost + '/auth/oauth2/callback',
224
+ userInfoURL: argv.oauth2_UserInfoURL
225
+ }, (accessToken, refreshToken, params, profile, cb) ->
226
+
227
+ extractUserInfo = (uiParam, uiDef) ->
228
+ uiPath = ''
229
+ if typeof uiParam == 'undefined' then (uiPath = uiDef) else (uiPath = uiParam)
230
+ console.log('extractUI', uiParam, uiDef, uiPath)
231
+ sParts = uiPath.split('.')
232
+ sFrom = sParts.shift()
233
+ switch sFrom
234
+ when "params"
235
+ obj = params
236
+ when "profile"
237
+ obj = profile
238
+ else
239
+ console.error('*** source of user info not recognised', uiPath)
240
+ obj = {}
241
+
242
+ while (sParts.length)
243
+ obj = obj[sParts.shift()]
244
+ return obj
245
+
246
+ console.log("accessToken", accessToken)
247
+ console.log("refreshToken", refreshToken)
248
+ console.log("params", params)
249
+ console.log("profile", profile)
250
+ if argv.oauth2_UsernameField?
251
+ username_query = argv.oauth2_UsernameField
252
+ else
253
+ username_query = 'params.user_id'
254
+
255
+ try
256
+ user.oauth2 = {
257
+ id: extractUserInfo(argv.oauth2_IdField, 'params.user_id')
258
+ username: extractUserInfo(argv.oauth2_UsernameField, 'params.user_id')
259
+ displayName: extractUserInfo(argv.oauth2_DisplayNameField, 'params.user_id')
260
+ }
261
+ catch e
262
+ console.error('*** Error extracting user info:', e)
263
+ console.log user.oauth2
264
+ cb(null, user)))
265
+
197
266
  # Github Strategy
198
267
  if argv.github_clientID? and argv.github_clientSecret?
199
268
  ids.push('github')
200
- GithubStrategy = require('passport-github').Strategy
269
+ GithubStrategy = require('passport-github2').Strategy
201
270
 
202
271
  githubStrategyName = callbackHost + 'Github'
203
272
 
@@ -275,6 +344,11 @@ module.exports = exports = (log, loga, argv) ->
275
344
  app.use(passport.initialize())
276
345
  app.use(passport.session())
277
346
 
347
+ # OAuth2
348
+ app.get('/auth/oauth2', passport.authenticate(oauth2StrategyName), (req, res) -> )
349
+ app.get('/auth/oauth2/callback',
350
+ passport.authenticate(oauth2StrategyName, { successRedirect: '/auth/loginDone', failureRedirect: '/auth/loginDialog'}))
351
+
278
352
  # Github
279
353
  app.get('/auth/github', passport.authenticate(githubStrategyName, {scope: 'user:email'}), (req, res) -> )
280
354
  app.get('/auth/github/callback',
@@ -317,6 +391,7 @@ module.exports = exports = (log, loga, argv) ->
317
391
  schemeButtons = []
318
392
  _(ids).forEach (scheme) ->
319
393
  switch scheme
394
+ when "oauth2" then schemeButtons.push({button: "<a href='/auth/oauth2' class='scheme-button oauth2-button'><span>OAuth2</span></a>"})
320
395
  when "twitter" then schemeButtons.push({button: "<a href='/auth/twitter' class='scheme-button twitter-button'><span>Twitter</span></a>"})
321
396
  when "github" then schemeButtons.push({button: "<a href='/auth/github' class='scheme-button github-button'><span>Github</span></a>"})
322
397
  when "google"
@@ -504,6 +579,13 @@ module.exports = exports = (log, loga, argv) ->
504
579
  userIds = {}
505
580
  idProviders.forEach (idProvider) ->
506
581
  id = switch idProvider
582
+ when "oauth2" then {
583
+ name: user.oauth2.displayName
584
+ oauth2: {
585
+ id: user.oauth2.id
586
+ username: user.oauth2.username
587
+ }
588
+ }
507
589
  when "twitter" then {
508
590
  name: user.twitter.displayName
509
591
  twitter: {
@@ -580,6 +662,13 @@ module.exports = exports = (log, loga, argv) ->
580
662
  id = {}
581
663
  idProviders.forEach (idProvider) ->
582
664
  id = switch idProvider
665
+ when "oauth2" then {
666
+ name: user.oauth2.displayName
667
+ oauth2: {
668
+ id: user.oauth2.id
669
+ username: user.oauth2.username
670
+ }
671
+ }
583
672
  when "twitter" then {
584
673
  name: user.twitter.displayName
585
674
  twitter: {