proxyauth: user OpenID instead of basic auth
This commit is contained in:
@@ -303,7 +303,7 @@ server {
|
||||
proxy_set_header Content-Length "";
|
||||
}
|
||||
|
||||
location ~ ^/(login|logout)$ {
|
||||
location ~ ^/(login|logout|callback)$ {
|
||||
proxy_pass http://127.0.0.1:3001;
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ server {
|
||||
if ($http_user_agent ~* "container") {
|
||||
return 401;
|
||||
}
|
||||
return 302 /login?redirect=$request_uri;
|
||||
return 302 "https://<%= proxyAuth.oidcEndpoint %>/openid/auth?client_id=<%= proxyAuth.oidcClientId %>&scope=openid profile email&response_type=code&redirect_uri=https://<%= vhost %>/callback";
|
||||
}
|
||||
|
||||
location <%= proxyAuth.location %> {
|
||||
|
||||
24
src/oidc.js
24
src/oidc.js
@@ -4,6 +4,8 @@ exports = module.exports = {
|
||||
start,
|
||||
stop,
|
||||
revokeByUserId,
|
||||
getUserByAuthCode,
|
||||
consumeAuthCode,
|
||||
clients: {
|
||||
add: clientsAdd,
|
||||
get: clientsGet,
|
||||
@@ -197,6 +199,28 @@ async function revokeByUserId(userId) {
|
||||
revokeObjects('AccessToken');
|
||||
}
|
||||
|
||||
async function consumeAuthCode(authCode) {
|
||||
assert.strictEqual(typeof authCode, 'string');
|
||||
|
||||
const authData = DATA_STORE['AuthorizationCode'][authCode];
|
||||
if (!authData || !authData.payload) return;
|
||||
|
||||
DATA_STORE['AuthorizationCode'][authCode].consumed = true;
|
||||
|
||||
save('AuthorizationCode');
|
||||
}
|
||||
|
||||
async function getUserByAuthCode(authCode) {
|
||||
assert.strictEqual(typeof authCode, 'string');
|
||||
|
||||
load('AuthorizationCode');
|
||||
const authData = DATA_STORE['AuthorizationCode'][authCode];
|
||||
|
||||
if (!authData || !authData.payload || !authData.payload.accountId) return null;
|
||||
|
||||
return await users.get(authData.payload.accountId);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Generic oidc node module data store model
|
||||
// -----------------------------
|
||||
|
||||
@@ -22,6 +22,7 @@ const apps = require('./apps.js'),
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess,
|
||||
jwt = require('jsonwebtoken'),
|
||||
middleware = require('./middleware'),
|
||||
oidc = require('./oidc.js'),
|
||||
path = require('path'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance'),
|
||||
@@ -152,6 +153,19 @@ function auth(req, res, next) {
|
||||
next(new HttpSuccess(200, {}));
|
||||
}
|
||||
|
||||
async function callback(req, res, next) {
|
||||
if (!req.query.code) return next(new HttpError(400, 'missing query argument "code"'));
|
||||
|
||||
debug(`callback: with code ${req.query.code}`);
|
||||
|
||||
req.user = await oidc.getUserByAuthCode(req.query.code);
|
||||
|
||||
// this is one-time use
|
||||
await oidc.consumeAuthCode(req.query.code);
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
// endpoint called by login page, username and password posted as JSON body
|
||||
async function passwordAuth(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
@@ -226,6 +240,7 @@ function initializeAuthwallExpressSync() {
|
||||
.use(router)
|
||||
.use(middleware.lastMile());
|
||||
|
||||
router.get ('/callback', callback, authorize);
|
||||
router.get ('/login', loginPage);
|
||||
router.get ('/auth', jwtVerify, authorizationHeader, auth); // called by nginx before accessing protected page
|
||||
router.post('/login', json, passwordAuth, authorize);
|
||||
|
||||
@@ -544,6 +544,11 @@ async function writeAppLocationNginxConfig(app, location, certificatePath) {
|
||||
};
|
||||
data.ip = app.containerIp;
|
||||
data.port = app.manifest.httpPort;
|
||||
|
||||
if (data.proxyAuth.enabled) {
|
||||
data.proxyAuth.oidcClientId = app.id;
|
||||
data.proxyAuth.oidcEndpoint = (await dashboard.getLocation()).fqdn;
|
||||
}
|
||||
} else if (type === Location.TYPE_SECONDARY) {
|
||||
data.ip = app.containerIp;
|
||||
const secondaryDomain = app.secondaryDomains.find(sd => sd.fqdn === fqdn);
|
||||
|
||||
@@ -1737,6 +1737,25 @@ async function setupProxyAuth(app, options) {
|
||||
|
||||
const env = [ { name: 'CLOUDRON_PROXY_AUTH', value: '1' } ];
|
||||
await addonConfigs.set(app.id, 'proxyauth', env);
|
||||
|
||||
debug('Creating OpenID client for proxyAuth');
|
||||
|
||||
// openid client_id is appId for now
|
||||
const [error, result] = await safe(oidc.clients.get(app.id));
|
||||
if (error) throw error;
|
||||
|
||||
// ensure we keep the secret
|
||||
const data = {
|
||||
secret: result ? result.secret : hat(4 * 128),
|
||||
loginRedirectUri: `https://${app.fqdn}/callback`,
|
||||
logoutRedirectUri: '',
|
||||
tokenSignatureAlgorithm: 'RS256',
|
||||
name: '',
|
||||
appId: app.id
|
||||
};
|
||||
|
||||
if (result) await oidc.clients.update(app.id, data);
|
||||
else await oidc.clients.add(app.id, data);
|
||||
}
|
||||
|
||||
async function teardownProxyAuth(app, options) {
|
||||
@@ -1744,6 +1763,11 @@ async function teardownProxyAuth(app, options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
await addonConfigs.unset(app.id, 'proxyauth');
|
||||
|
||||
debug('Deleting OpenID client for proxyAuth');
|
||||
|
||||
const [error] = await safe(oidc.clients.del(app.id));
|
||||
if (error && error.reason !== BoxError.NOT_FOUND) throw error;
|
||||
}
|
||||
|
||||
async function setupDocker(app, options) {
|
||||
|
||||
Reference in New Issue
Block a user