proxyauth: user OpenID instead of basic auth

This commit is contained in:
Johannes Zellner
2024-04-15 12:35:03 +02:00
parent caf1c37171
commit 21d7438bbe
5 changed files with 70 additions and 2 deletions

View File

@@ -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 %> {

View File

@@ -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
// -----------------------------

View File

@@ -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);

View File

@@ -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);

View File

@@ -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) {