multiauth WIP

This commit is contained in:
Jan Prochazka
2024-08-07 13:58:44 +02:00
parent 591945dc93
commit 42c71c1204
6 changed files with 95 additions and 24 deletions

View File

@@ -9,6 +9,8 @@ const jwt = require('jsonwebtoken');
const logger = getLogger('authProvider'); const logger = getLogger('authProvider');
class AuthProviderBase { class AuthProviderBase {
amoid = 'none';
async login(login, password, options = undefined) { async login(login, password, options = undefined) {
return {}; return {};
} }
@@ -51,9 +53,17 @@ class AuthProviderBase {
getSingleConnectionId(req) { getSingleConnectionId(req) {
return null; return null;
} }
toJson() {
return {
amoid: this.amoid,
};
}
} }
class OAuthProvider extends AuthProviderBase { class OAuthProvider extends AuthProviderBase {
amoid = 'oauth';
shouldAuthorizeApi() { shouldAuthorizeApi() {
return true; return true;
} }
@@ -120,6 +130,8 @@ class OAuthProvider extends AuthProviderBase {
} }
class ADProvider extends AuthProviderBase { class ADProvider extends AuthProviderBase {
amoid = 'ad';
async login(login, password) { async login(login, password) {
const adConfig = { const adConfig = {
url: process.env.AD_URL, url: process.env.AD_URL,
@@ -157,6 +169,8 @@ class ADProvider extends AuthProviderBase {
} }
class LoginsProvider extends AuthProviderBase { class LoginsProvider extends AuthProviderBase {
amoid = 'logins';
async login(login, password) { async login(login, password) {
if (password == process.env[`LOGIN_PASSWORD_${login}`]) { if (password == process.env[`LOGIN_PASSWORD_${login}`]) {
return { return {
@@ -176,6 +190,8 @@ class LoginsProvider extends AuthProviderBase {
} }
class DenyAllProvider extends AuthProviderBase { class DenyAllProvider extends AuthProviderBase {
amoid = 'deny';
shouldAuthorizeApi() { shouldAuthorizeApi() {
return true; return true;
} }
@@ -233,19 +249,37 @@ function createEnvAuthProvider() {
} }
} }
let authProvider = createEnvAuthProvider(); let defaultAuthProvider = createEnvAuthProvider();
let authProviders = [defaultAuthProvider];
function getAuthProvider() { function getAuthProviders() {
return authProvider; return authProviders;
} }
function setAuthProvider(value) { function getAuthProviderById(amoid) {
authProvider = value; return authProviders.find(x => x.amoid == amoid);
}
function getDefaultAuthProvider() {
return defaultAuthProvider;
}
function getAuthProviderFromReq(req) {
const authProviderId = req?.auth?.amoid || req?.user?.amoid;
return getAuthProviderById(authProviderId) ?? getDefaultAuthProvider();
}
function setAuthProviders(value, defaultProvider = null) {
authProviders = value;
defaultAuthProvider = defaultProvider || value[0];
} }
module.exports = { module.exports = {
AuthProviderBase, AuthProviderBase,
detectEnvAuthProvider, detectEnvAuthProvider,
getAuthProvider, getAuthProviders,
setAuthProvider, getDefaultAuthProvider,
setAuthProviders,
getAuthProviderById,
getAuthProviderFromReq,
}; };

View File

@@ -5,7 +5,7 @@ const { getLogger } = require('dbgate-tools');
const AD = require('activedirectory2').promiseWrapper; const AD = require('activedirectory2').promiseWrapper;
const crypto = require('crypto'); const crypto = require('crypto');
const { getTokenSecret, getTokenLifetime } = require('../auth/authCommon'); const { getTokenSecret, getTokenLifetime } = require('../auth/authCommon');
const { getAuthProvider } = require('../auth/authProvider'); const { getAuthProviderFromReq, getAuthProviders, getDefaultAuthProvider, getAuthProviderById } = require('../auth/authProvider');
const storage = require('./storage'); const storage = require('./storage');
const logger = getLogger('auth'); const logger = getLogger('auth');
@@ -28,6 +28,7 @@ function authMiddleware(req, res, next) {
'/auth/login', '/auth/login',
'/stream', '/stream',
'storage/get-connections-for-login-page', 'storage/get-connections-for-login-page',
'auth/get-providers',
'/connections/dblogin', '/connections/dblogin',
'/connections/dblogin-auth', '/connections/dblogin-auth',
'/connections/dblogin-auth-token', '/connections/dblogin-auth-token',
@@ -37,7 +38,7 @@ function authMiddleware(req, res, next) {
const isAdminPage = req.headers['x-is-admin-page'] == 'true'; const isAdminPage = req.headers['x-is-admin-page'] == 'true';
if (!isAdminPage && !getAuthProvider().shouldAuthorizeApi()) { if (!isAdminPage && !getAuthProviderFromReq(req).shouldAuthorizeApi()) {
return next(); return next();
} }
let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x)); let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x));
@@ -68,11 +69,11 @@ function authMiddleware(req, res, next) {
module.exports = { module.exports = {
oauthToken_meta: true, oauthToken_meta: true,
async oauthToken(params) { async oauthToken(params) {
return getAuthProvider().oauthToken(params); return getDefaultAuthProvider().oauthToken(params);
}, },
login_meta: true, login_meta: true,
async login(params) { async login(params) {
const { login, password, isAdminPage } = params; const { amoid, login, password, isAdminPage } = params;
if (isAdminPage) { if (isAdminPage) {
if (process.env.ADMIN_PASSWORD && process.env.ADMIN_PASSWORD == password) { if (process.env.ADMIN_PASSWORD && process.env.ADMIN_PASSWORD == password) {
@@ -94,7 +95,15 @@ module.exports = {
return { error: 'Login failed' }; return { error: 'Login failed' };
} }
return getAuthProvider().login(login, password); return getAuthProviderById(amoid).login(login, password);
},
getProviders_meta: true,
getProviders() {
return {
providers: getAuthProviders().map(x => x.toJson()),
default: getDefaultAuthProvider()?.amoid,
};
}, },
authMiddleware, authMiddleware,

View File

@@ -11,7 +11,7 @@ const AsyncLock = require('async-lock');
const currentVersion = require('../currentVersion'); const currentVersion = require('../currentVersion');
const platformInfo = require('../utility/platformInfo'); const platformInfo = require('../utility/platformInfo');
const connections = require('../controllers/connections'); const connections = require('../controllers/connections');
const { getAuthProvider } = require('../auth/authProvider'); const { getAuthProviderFromReq } = require('../auth/authProvider');
const lock = new AsyncLock(); const lock = new AsyncLock();
@@ -28,7 +28,7 @@ module.exports = {
get_meta: true, get_meta: true,
async get(_params, req) { async get(_params, req) {
const authProvider = getAuthProvider(); const authProvider = getAuthProviderFromReq(req);
const login = authProvider.getCurrentLogin(req); const login = authProvider.getCurrentLogin(req);
const permissions = authProvider.getCurrentPermissions(req); const permissions = authProvider.getCurrentPermissions(req);
const isLoginForm = authProvider.isLoginForm(); const isLoginForm = authProvider.isLoginForm();

View File

@@ -34,7 +34,7 @@ const platformInfo = require('./utility/platformInfo');
const getExpressPath = require('./utility/getExpressPath'); const getExpressPath = require('./utility/getExpressPath');
const _ = require('lodash'); const _ = require('lodash');
const { getLogger } = require('dbgate-tools'); const { getLogger } = require('dbgate-tools');
const { getAuthProvider } = require('./auth/authProvider'); const { getDefaultAuthProvider } = require('./auth/authProvider');
const logger = getLogger('main'); const logger = getLogger('main');
@@ -48,7 +48,7 @@ function start() {
if (process.env.BASIC_AUTH) { if (process.env.BASIC_AUTH) {
async function authorizer(username, password, cb) { async function authorizer(username, password, cb) {
try { try {
const resp = await getAuthProvider().login(username, password); const resp = await getDefaultAuthProvider().login(username, password);
if (resp.accessToken) { if (resp.accessToken) {
cb(null, true); cb(null, true);
} else { } else {

View File

@@ -1,6 +1,6 @@
const { compilePermissions, testPermission } = require('dbgate-tools'); const { compilePermissions, testPermission } = require('dbgate-tools');
const _ = require('lodash'); const _ = require('lodash');
const { getAuthProvider } = require('../auth/authProvider'); const { getAuthProviderFromReq } = require('../auth/authProvider');
const cachedPermissions = {}; const cachedPermissions = {};
@@ -10,7 +10,7 @@ function hasPermission(tested, req) {
return true; return true;
} }
const permissions = getAuthProvider().getCurrentPermissions(req); const permissions = getAuthProviderFromReq(req).getCurrentPermissions(req);
if (!cachedPermissions[permissions]) { if (!cachedPermissions[permissions]) {
cachedPermissions[permissions] = compilePermissions(permissions); cachedPermissions[permissions] = compilePermissions(permissions);

View File

@@ -19,19 +19,33 @@
const config = useConfig(); const config = useConfig();
let availableConnections = null; let availableConnections = null;
let availableProviders = [];
let isTesting = false; let isTesting = false;
const testIdRef = createRef(0); const testIdRef = createRef(0);
let sqlConnectResult; let sqlConnectResult;
const values = writable({ databaseServer: null }); let serversLoadedForAmoId = null;
const values = writable({ amoid: null, databaseServer: null });
$: selectedConnection = availableConnections?.find(x => x.conid == $values.databaseServer); $: selectedConnection = availableConnections?.find(x => x.conid == $values.databaseServer);
async function loadAvailableServers() { async function loadAvailableServers(amoid) {
availableConnections = await apiCall('storage/get-connections-for-login-page'); if (amoid) {
availableConnections = await apiCall('storage/get-connections-for-login-page', { amoid });
if (availableConnections?.length > 0) { if (availableConnections?.length > 0) {
values.set({ databaseServer: availableConnections[0].conid }); values.update(x => ({ ...x, databaseServer: availableConnections[0].conid }));
} }
serversLoadedForAmoId = amoid;
} else {
availableConnections = null;
}
}
async function loadAvailableAuthProviders() {
const resp = await apiCall('auth/get-providers');
availableProviders = resp.providers;
values.update(x => ({ ...x, amoid: resp.default }));
} }
onMount(() => { onMount(() => {
@@ -39,9 +53,13 @@
if (removed) removed.remove(); if (removed) removed.remove();
if (!isAdminPage) { if (!isAdminPage) {
loadAvailableServers(); loadAvailableAuthProviders();
} }
}); });
$: if ($values.amoid != serversLoadedForAmoId) {
loadAvailableServers($values.amoid);
}
</script> </script>
<div class="root theme-light theme-type-light"> <div class="root theme-light theme-type-light">
@@ -53,6 +71,15 @@
<div class="box"> <div class="box">
<div class="heading">Log In</div> <div class="heading">Log In</div>
<FormProviderCore {values}> <FormProviderCore {values}>
{#if !isAdminPage}
<FormSelectField
label="Authentization method"
name="amoid"
isNative
options={availableProviders.map(mtd => ({ value: mtd.amoid, label: mtd.name }))}
/>
{/if}
{#if !isAdminPage && availableConnections} {#if !isAdminPage && availableConnections}
<FormSelectField <FormSelectField
label="Database server" label="Database server"
@@ -150,6 +177,7 @@
on:click={async e => { on:click={async e => {
enableApi(); enableApi();
const resp = await apiCall('auth/login', { const resp = await apiCall('auth/login', {
amoid: $values.amoid,
isAdminPage, isAdminPage,
...e.detail, ...e.detail,
}); });