Compare commits

...

3 Commits

Author SHA1 Message Date
Julien Fontanet
6162ecff04 use passport-http 2019-12-06 17:57:08 +01:00
Julien Fontanet
19d859e047 feat(xo-server): support HTTP basic auth
See https://xcp-ng.org/forum/topic/2316/http-authentication
2019-12-06 12:32:06 +01:00
Julien Fontanet
049d62873e feat(xo-server): passport strategies can be unregistered
Now all authentication plugins can be unloaded.
2019-12-06 12:29:28 +01:00
9 changed files with 175 additions and 16 deletions

View File

@@ -19,6 +19,7 @@ export const configurationSchema = {
class AuthGitHubXoPlugin { class AuthGitHubXoPlugin {
constructor(xo) { constructor(xo) {
this._unregisterPassportStrategy = undefined
this._xo = xo this._xo = xo
} }
@@ -29,7 +30,7 @@ class AuthGitHubXoPlugin {
load() { load() {
const { _xo: xo } = this const { _xo: xo } = this
xo.registerPassportStrategy( this._unregisterPassportStrategy = xo.registerPassportStrategy(
new Strategy( new Strategy(
this._conf, this._conf,
async (accessToken, refreshToken, profile, done) => { async (accessToken, refreshToken, profile, done) => {
@@ -42,6 +43,10 @@ class AuthGitHubXoPlugin {
) )
) )
} }
unload() {
this._unregisterPassportStrategy()
}
} }
// =================================================================== // ===================================================================

View File

@@ -31,6 +31,7 @@ export const configurationSchema = {
class AuthGoogleXoPlugin { class AuthGoogleXoPlugin {
constructor({ xo }) { constructor({ xo }) {
this._conf = null this._conf = null
this._unregisterPassportStrategy = undefined
this._xo = xo this._xo = xo
} }
@@ -42,7 +43,7 @@ class AuthGoogleXoPlugin {
const conf = this._conf const conf = this._conf
const xo = this._xo const xo = this._xo
xo.registerPassportStrategy( this._unregisterPassportStrategy = xo.registerPassportStrategy(
new Strategy(conf, async (accessToken, refreshToken, profile, done) => { new Strategy(conf, async (accessToken, refreshToken, profile, done) => {
try { try {
done( done(
@@ -60,6 +61,10 @@ class AuthGoogleXoPlugin {
}) })
) )
} }
unload() {
this._unregisterPassportStrategy()
}
} }
// =================================================================== // ===================================================================

View File

@@ -0,0 +1,3 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)

View File

@@ -0,0 +1,24 @@
/benchmark/
/benchmarks/
*.bench.js
*.bench.js.map
/examples/
example.js
example.js.map
*.example.js
*.example.js.map
/fixture/
/fixtures/
*.fixture.js
*.fixture.js.map
*.fixtures.js
*.fixtures.js.map
/test/
/tests/
*.spec.js
*.spec.js.map
__snapshots__/

View File

@@ -0,0 +1,48 @@
{
"name": "xo-server-auth-http",
"version": "0.0.0",
"license": "AGPL-3.0",
"description": "Basic HTTP authentication plugin for XO-Server",
"keywords": [
"authorization",
"basic",
"http",
"orchestra",
"plugin",
"xen-orchestra",
"xen",
"xo-server"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server-auth-http",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "packages/xo-server-auth-http",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"main": "dist/",
"files": [
"dist/"
],
"engines": {
"node": ">=8.10"
},
"dependencies": {
"passport-http": "^0.3.0"
},
"devDependencies": {
"@babel/cli": "^7.7.4",
"@babel/core": "^7.7.4",
"@babel/preset-env": "^7.7.4",
"cross-env": "^6.0.3",
"rimraf": "^3.0.0"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "rimraf dist/",
"predev": "yarn run prebuild",
"prepublishOnly": "yarn run build"
},
"private": true
}

View File

@@ -0,0 +1,46 @@
import { BasicStrategy } from 'passport-http'
export const configurationSchema = {
type: 'object',
properties: {
realm: {
type: 'string',
},
},
required: ['realm'],
}
class Plugin {
constructor({ xo }) {
this._configuration = undefined
this._unregisterPassportStrategy = undefined
this._xo = xo
}
configure(configuration) {
this._configuration = configuration
}
load() {
const xo = this._xo
this._unregisterPassportStrategy = xo.registerPassportStrategy(
new BasicStrategy(
this._configuration,
async (username, password, done) => {
try {
const { user } = await xo.authenticateUser({ username, password })
done(null, user)
} catch (error) {
done(null, false, { message: error.message })
}
}
)
)
}
unload() {
this._unregisterPassportStrategy()
}
}
export default opts => new Plugin(opts)

View File

@@ -48,6 +48,7 @@ You should try \`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddr
class AuthSamlXoPlugin { class AuthSamlXoPlugin {
constructor({ xo }) { constructor({ xo }) {
this._conf = null this._conf = null
this._unregisterPassportStrategy = undefined
this._usernameField = null this._usernameField = null
this._xo = xo this._xo = xo
} }
@@ -66,7 +67,7 @@ class AuthSamlXoPlugin {
load() { load() {
const xo = this._xo const xo = this._xo
xo.registerPassportStrategy( this._unregisterPassportStrategy = xo.registerPassportStrategy(
new Strategy(this._conf, async (profile, done) => { new Strategy(this._conf, async (profile, done) => {
const name = profile[this._usernameField] const name = profile[this._usernameField]
if (!name) { if (!name) {
@@ -83,6 +84,10 @@ class AuthSamlXoPlugin {
}) })
) )
} }
unload() {
this._unregisterPassportStrategy()
}
} }
// =================================================================== // ===================================================================

View File

@@ -15,7 +15,7 @@ import serveStatic from 'serve-static'
import stoppable from 'stoppable' import stoppable from 'stoppable'
import WebServer from 'http-server-plus' import WebServer from 'http-server-plus'
import WebSocket from 'ws' import WebSocket from 'ws'
import { forOwn, map } from 'lodash' import { forOwn, map, once } from 'lodash'
import { URL } from 'url' import { URL } from 'url'
import { compile as compilePug } from 'pug' import { compile as compilePug } from 'pug'
@@ -124,11 +124,19 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
strategy, strategy,
{ label = strategy.label, name = strategy.name } = {} { label = strategy.label, name = strategy.name } = {}
) => { ) => {
passport.use(name, strategy) if (name in strategies) {
throw new TypeError('duplicate passport strategy ' + name)
}
passport.use(name, strategy)
if (name !== 'local') { if (name !== 'local') {
strategies[name] = label ?? name strategies[name] = label ?? name
} }
return once(() => {
passport.unuse(name)
delete strategies[name]
})
} }
// Registers the sign in form. // Registers the sign in form.
@@ -171,7 +179,9 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
} }
if (authenticator.check(req.body.otp, user.preferences.otp)) { if (authenticator.check(req.body.otp, user.preferences.otp)) {
setToken(req, res, next) setToken(req, res, next).then(() =>
res.redirect(303, req.flash('return-url')[0] || '/')
)
} else { } else {
req.flash('error', 'Invalid code') req.flash('error', 'Invalid code')
res.redirect(303, '/signin-otp') res.redirect(303, '/signin-otp')
@@ -183,7 +193,7 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
parseDuration parseDuration
) )
const SESSION_VALIDITY = ifDef(authCfg.sessionCookieValidity, parseDuration) const SESSION_VALIDITY = ifDef(authCfg.sessionCookieValidity, parseDuration)
const setToken = async (req, res, next) => { const setToken = async (req, res) => {
const { user, isPersistent } = req.session const { user, isPersistent } = req.session
const token = await xo.createAuthenticationToken({ const token = await xo.createAuthenticationToken({
expiresIn: isPersistent ? PERMANENT_VALIDITY : SESSION_VALIDITY, expiresIn: isPersistent ? PERMANENT_VALIDITY : SESSION_VALIDITY,
@@ -200,7 +210,6 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
delete req.session.isPersistent delete req.session.isPersistent
delete req.session.user delete req.session.user
res.redirect(303, req.flash('return-url')[0] || '/')
} }
const SIGNIN_STRATEGY_RE = /^\/signin\/([^/]+)(\/callback)?(:?\?.*)?$/ const SIGNIN_STRATEGY_RE = /^\/signin\/([^/]+)(\/callback)?(:?\?.*)?$/
@@ -220,6 +229,12 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
} }
if (!user) { if (!user) {
if (typeof info === 'string') {
res.statusCode = 401
res.setHeader('WWW-Authenticate', info)
return res.end('unauthorized')
}
req.flash('error', info ? info.message : 'Invalid credentials') req.flash('error', info ? info.message : 'Invalid credentials')
return res.redirect(303, '/signin') return res.redirect(303, '/signin')
} }
@@ -232,16 +247,17 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
return res.redirect(303, '/signin-otp') return res.redirect(303, '/signin-otp')
} }
setToken(req, res, next) await setToken(req, res)
res.redirect(303, req.flash('return-url')[0] || '/')
})(req, res, next) })(req, res, next)
} }
if (req.cookies.token) { if (req.cookies.token) {
next() return next()
} else {
req.flash('return-url', url)
res.redirect(authCfg.defaultSignInPage)
} }
req.flash('return-url', url)
return res.redirect(authCfg.defaultSignInPage)
}) })
// Install the local strategy. // Install the local strategy.

View File

@@ -2,7 +2,7 @@
# yarn lockfile v1 # yarn lockfile v1
"@babel/cli@^7.0.0", "@babel/cli@^7.1.5", "@babel/cli@^7.4.4", "@babel/cli@^7.7.0": "@babel/cli@^7.0.0", "@babel/cli@^7.1.5", "@babel/cli@^7.4.4", "@babel/cli@^7.7.0", "@babel/cli@^7.7.4":
version "7.7.4" version "7.7.4"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.4.tgz#38804334c8db40209f88c69a5c90998e60cca18b" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.4.tgz#38804334c8db40209f88c69a5c90998e60cca18b"
integrity sha512-O7mmzaWdm+VabWQmxuM8hqNrWGGihN83KfhPUzp2lAW4kzIMwBxujXkZbD4fMwKMYY9FXTbDvXsJqU+5XHXi4A== integrity sha512-O7mmzaWdm+VabWQmxuM8hqNrWGGihN83KfhPUzp2lAW4kzIMwBxujXkZbD4fMwKMYY9FXTbDvXsJqU+5XHXi4A==
@@ -25,7 +25,7 @@
dependencies: dependencies:
"@babel/highlight" "^7.0.0" "@babel/highlight" "^7.0.0"
"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.5", "@babel/core@^7.1.6", "@babel/core@^7.4.4", "@babel/core@^7.7.2": "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.5", "@babel/core@^7.1.6", "@babel/core@^7.4.4", "@babel/core@^7.7.2", "@babel/core@^7.7.4":
version "7.7.4" version "7.7.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.4.tgz#37e864532200cb6b50ee9a4045f5f817840166ab" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.4.tgz#37e864532200cb6b50ee9a4045f5f817840166ab"
integrity sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ== integrity sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==
@@ -740,7 +740,7 @@
core-js "^2.6.5" core-js "^2.6.5"
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
"@babel/preset-env@^7.0.0", "@babel/preset-env@^7.1.5", "@babel/preset-env@^7.1.6", "@babel/preset-env@^7.4.4", "@babel/preset-env@^7.7.1": "@babel/preset-env@^7.0.0", "@babel/preset-env@^7.1.5", "@babel/preset-env@^7.1.6", "@babel/preset-env@^7.4.4", "@babel/preset-env@^7.7.1", "@babel/preset-env@^7.7.4":
version "7.7.4" version "7.7.4"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8"
integrity sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g== integrity sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g==
@@ -10928,6 +10928,13 @@ passport-google-oauth20@^2.0.0:
dependencies: dependencies:
passport-oauth2 "1.x.x" passport-oauth2 "1.x.x"
passport-http@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/passport-http/-/passport-http-0.3.0.tgz#8ee53d4380be9c60df2151925029826f77115603"
integrity sha1-juU9Q4C+nGDfIVGSUCmCb3cRVgM=
dependencies:
passport-strategy "1.x.x"
passport-local@^1.0.0: passport-local@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee" resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"