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 {
constructor(xo) {
this._unregisterPassportStrategy = undefined
this._xo = xo
}
@@ -29,7 +30,7 @@ class AuthGitHubXoPlugin {
load() {
const { _xo: xo } = this
xo.registerPassportStrategy(
this._unregisterPassportStrategy = xo.registerPassportStrategy(
new Strategy(
this._conf,
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 {
constructor({ xo }) {
this._conf = null
this._unregisterPassportStrategy = undefined
this._xo = xo
}
@@ -42,7 +43,7 @@ class AuthGoogleXoPlugin {
const conf = this._conf
const xo = this._xo
xo.registerPassportStrategy(
this._unregisterPassportStrategy = xo.registerPassportStrategy(
new Strategy(conf, async (accessToken, refreshToken, profile, done) => {
try {
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 {
constructor({ xo }) {
this._conf = null
this._unregisterPassportStrategy = undefined
this._usernameField = null
this._xo = xo
}
@@ -66,7 +67,7 @@ class AuthSamlXoPlugin {
load() {
const xo = this._xo
xo.registerPassportStrategy(
this._unregisterPassportStrategy = xo.registerPassportStrategy(
new Strategy(this._conf, async (profile, done) => {
const name = profile[this._usernameField]
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 WebServer from 'http-server-plus'
import WebSocket from 'ws'
import { forOwn, map } from 'lodash'
import { forOwn, map, once } from 'lodash'
import { URL } from 'url'
import { compile as compilePug } from 'pug'
@@ -124,11 +124,19 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
strategy,
{ 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') {
strategies[name] = label ?? name
}
return once(() => {
passport.unuse(name)
delete strategies[name]
})
}
// 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)) {
setToken(req, res, next)
setToken(req, res, next).then(() =>
res.redirect(303, req.flash('return-url')[0] || '/')
)
} else {
req.flash('error', 'Invalid code')
res.redirect(303, '/signin-otp')
@@ -183,7 +193,7 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
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 token = await xo.createAuthenticationToken({
expiresIn: isPersistent ? PERMANENT_VALIDITY : SESSION_VALIDITY,
@@ -200,7 +210,6 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
delete req.session.isPersistent
delete req.session.user
res.redirect(303, req.flash('return-url')[0] || '/')
}
const SIGNIN_STRATEGY_RE = /^\/signin\/([^/]+)(\/callback)?(:?\?.*)?$/
@@ -220,6 +229,12 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
}
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')
return res.redirect(303, '/signin')
}
@@ -232,16 +247,17 @@ async function setUpPassport(express, xo, { authentication: authCfg }) {
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)
}
if (req.cookies.token) {
next()
} else {
req.flash('return-url', url)
res.redirect(authCfg.defaultSignInPage)
return next()
}
req.flash('return-url', url)
return res.redirect(authCfg.defaultSignInPage)
})
// Install the local strategy.

View File

@@ -2,7 +2,7 @@
# 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"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.4.tgz#38804334c8db40209f88c69a5c90998e60cca18b"
integrity sha512-O7mmzaWdm+VabWQmxuM8hqNrWGGihN83KfhPUzp2lAW4kzIMwBxujXkZbD4fMwKMYY9FXTbDvXsJqU+5XHXi4A==
@@ -25,7 +25,7 @@
dependencies:
"@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"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.4.tgz#37e864532200cb6b50ee9a4045f5f817840166ab"
integrity sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==
@@ -740,7 +740,7 @@
core-js "^2.6.5"
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"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8"
integrity sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g==
@@ -10928,6 +10928,13 @@ passport-google-oauth20@^2.0.0:
dependencies:
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:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"