From c3c9ad1aedf902b374adbb84a25ec2761b5ae58b Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 25 Jul 2024 16:47:31 +0200 Subject: [PATCH 1/6] auth providert refactor WIP --- packages/api/src/auth/authCommon.js | 11 + packages/api/src/auth/authProvider.js | 216 ++++++++++++++++++ packages/api/src/controllers/auth.js | 109 +-------- packages/api/src/controllers/storage.js | 4 + packages/api/src/main.js | 7 +- packages/api/src/utility/hasPermission.js | 79 ++++--- packages/web/src/utility/connectionsPinger.js | 7 +- .../web/src/widgets/WidgetIconPanel.svelte | 5 +- 8 files changed, 289 insertions(+), 149 deletions(-) create mode 100644 packages/api/src/auth/authCommon.js create mode 100644 packages/api/src/auth/authProvider.js diff --git a/packages/api/src/auth/authCommon.js b/packages/api/src/auth/authCommon.js new file mode 100644 index 000000000..1ebeeafdc --- /dev/null +++ b/packages/api/src/auth/authCommon.js @@ -0,0 +1,11 @@ +const crypto = require('crypto'); + +const tokenSecret = crypto.randomUUID(); + +export function getTokenLifetime() { + return process.env.TOKEN_LIFETIME || '1d'; +} + +export function getTokenSecret() { + return tokenSecret; +} diff --git a/packages/api/src/auth/authProvider.js b/packages/api/src/auth/authProvider.js new file mode 100644 index 000000000..81eccb9e7 --- /dev/null +++ b/packages/api/src/auth/authProvider.js @@ -0,0 +1,216 @@ +const { getTokenSecret, getTokenLifetime } = require('./authCommon'); +const _ = require('lodash'); +const axios = require('axios'); +const { getLogger } = require('dbgate-tools'); + +const AD = require('activedirectory2').promiseWrapper; +const jwt = require('jsonwebtoken'); + +const logger = getLogger('authProvider'); + +let envLoginsCache = null; +let envLoginsLoaded = false; + +function getEnvLogins() { + if (envLoginsLoaded) { + return envLoginsCache; + } + + const res = []; + if (process.env.LOGIN && process.env.PASSWORD) { + res.push({ + login: process.env.LOGIN, + password: process.env.PASSWORD, + permissions: process.env.PERMISSIONS, + }); + } + if (process.env.LOGINS) { + const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim())); + for (const login of logins) { + const password = process.env[`LOGIN_PASSWORD_${login}`]; + const permissions = process.env[`LOGIN_PERMISSIONS_${login}`]; + if (password) { + res.push({ + login, + password, + permissions, + }); + } + } + } + + envLoginsCache = res.length > 0 ? res : null; + envLoginsLoaded = true; + return envLoginsCache; +} + +class AuthProviderBase { + async login(login, password) { + return {}; + } + + getBasicAuthLogins() { + return null; + } + + shouldAuthorizeApi() { + return false; + } + + oauthToken(params) { + return {}; + } + + getCurrentLogin(req) {} + + getCurrentPermissions(req) { + return process.env.PERMISSIONS; + } +} + +class OAuthProvider extends AuthProviderBase { + shouldAuthorizeApi() { + return true; + } + + async oauthToken(params) { + const { redirectUri, code } = params; + + const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : ''; + const resp = await axios.default.post( + `${process.env.OAUTH_TOKEN}`, + `grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent( + redirectUri + )}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}${scopeParam}` + ); + + const { access_token, refresh_token } = resp.data; + + const payload = jwt.decode(access_token); + + logger.info({ payload }, 'User payload returned from OAUTH'); + + const login = + process.env.OAUTH_LOGIN_FIELD && payload && payload[process.env.OAUTH_LOGIN_FIELD] + ? payload[process.env.OAUTH_LOGIN_FIELD] + : 'oauth'; + + if ( + process.env.OAUTH_ALLOWED_LOGINS && + !process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim()) + ) { + return { error: `Username ${login} not allowed to log in` }; + } + + const groups = + process.env.OAUTH_GROUP_FIELD && payload && payload[process.env.OAUTH_GROUP_FIELD] + ? payload[process.env.OAUTH_GROUP_FIELD] + : []; + + const allowedGroups = process.env.OAUTH_ALLOWED_GROUPS + ? process.env.OAUTH_ALLOWED_GROUPS.split(',').map(group => group.toLowerCase().trim()) + : []; + + if (process.env.OAUTH_ALLOWED_GROUPS && !groups.some(group => allowedGroups.includes(group.toLowerCase().trim()))) { + return { error: `Username ${login} does not belong to an allowed group` }; + } + + if (access_token) { + return { + accessToken: jwt.sign({ login }, getTokenSecret(), { expiresIn: getTokenLifetime() }), + }; + } + + return { error: 'Token not found' }; + } +} + +class ADProvider extends AuthProviderBase { + async login(login, password) { + const adConfig = { + url: process.env.AD_URL, + baseDN: process.env.AD_BASEDN, + username: process.env.AD_USERNAME, + password: process.env.AD_PASSOWRD, + }; + const ad = new AD(adConfig); + try { + const res = await ad.authenticate(login, password); + if (!res) { + return { error: 'Login failed' }; + } + if ( + process.env.AD_ALLOWED_LOGINS && + !process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim()) + ) { + return { error: `Username ${login} not allowed to log in` }; + } + return { + accessToken: jwt.sign({ login }, getTokenSecret(), { expiresIn: getTokenLifetime() }), + }; + } catch (e) { + return { error: 'Login failed' }; + } + } + + shouldAuthorizeApi() { + return true; + } +} + +class LoginsProvider extends AuthProviderBase { + async login(login, password) { + const logins = getEnvLogins(); + if (!logins) { + return { error: 'Logins not configured' }; + } + const foundLogin = logins.find(x => x.login == login); + if (foundLogin && foundLogin.password && foundLogin.password == password) { + return { + accessToken: jwt.sign({ login }, getTokenSecret(), { expiresIn: getTokenLifetime() }), + }; + } + return { error: 'Invalid credentials' }; + } + + getBasicAuthLogins() { + const logins = getEnvLogins(); + if (logins && process.env.BASIC_AUTH) { + return _.fromPairs(logins.filter(x => x.password).map(x => [x.login, x.password])); + } + } + + shouldAuthorizeApi() { + return !process.env.BASIC_AUTH; + } +} + +export function detectEnvAuthProvider() { + if (process.env.AUTH_PROVIDER) { + return process.env.AUTH_PROVIDER; + } + if (process.env.OAUTH_AUTH) { + return 'oauth'; + } + if (process.env.AD_URL) { + return 'ad'; + } + if (getEnvLogins()) { + return 'logins'; + } + return 'none'; +} + +export function createAuthProvider() { + const authProvider = detectEnvAuthProvider(); + switch (authProvider) { + case 'oauth': + return new OAuthProvider(); + case 'ad': + return new ADProvider(); + case 'logins': + return new LoginsProvider(); + default: + return new AuthProviderBase(); + } +} diff --git a/packages/api/src/controllers/auth.js b/packages/api/src/controllers/auth.js index bf5b84a1f..aa4df5d74 100644 --- a/packages/api/src/controllers/auth.js +++ b/packages/api/src/controllers/auth.js @@ -5,18 +5,14 @@ const { getLogins } = require('../utility/hasPermission'); const { getLogger } = require('dbgate-tools'); const AD = require('activedirectory2').promiseWrapper; const crypto = require('crypto'); +const { getTokenSecret, getTokenLifetime } = require('../auth/authCommon'); +const { createAuthProvider } = require('../auth/authProvider'); +const { create } = require('lodash'); const logger = getLogger('auth'); -const tokenSecret = crypto.randomUUID(); - function shouldAuthorizeApi() { - const logins = getLogins(); - return !!process.env.OAUTH_AUTH || !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH); -} - -function getTokenLifetime() { - return process.env.TOKEN_LIFETIME || '1d'; + return createAuthProvider().shouldAuthorizeApi(); } function unauthorizedResponse(req, res, text) { @@ -46,7 +42,7 @@ function authMiddleware(req, res, next) { } const token = authHeader.split(' ')[1]; try { - const decoded = jwt.verify(token, tokenSecret); + const decoded = jwt.verify(token, getTokenSecret()); req.user = decoded; return next(); } catch (err) { @@ -63,104 +59,13 @@ function authMiddleware(req, res, next) { module.exports = { oauthToken_meta: true, async oauthToken(params) { - const { redirectUri, code } = params; - - const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : ''; - const resp = await axios.default.post( - `${process.env.OAUTH_TOKEN}`, - `grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent( - redirectUri - )}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}${scopeParam}` - ); - - const { access_token, refresh_token } = resp.data; - - const payload = jwt.decode(access_token); - - logger.info({ payload }, 'User payload returned from OAUTH'); - - const login = - process.env.OAUTH_LOGIN_FIELD && payload && payload[process.env.OAUTH_LOGIN_FIELD] - ? payload[process.env.OAUTH_LOGIN_FIELD] - : 'oauth'; - - if ( - process.env.OAUTH_ALLOWED_LOGINS && - !process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim()) - ) { - return { error: `Username ${login} not allowed to log in` }; - } - - const groups = - process.env.OAUTH_GROUP_FIELD && payload && payload[process.env.OAUTH_GROUP_FIELD] - ? payload[process.env.OAUTH_GROUP_FIELD] - : []; - - const allowedGroups = - process.env.OAUTH_ALLOWED_GROUPS - ? process.env.OAUTH_ALLOWED_GROUPS.split(',').map(group => group.toLowerCase().trim()) - : []; - - if ( - process.env.OAUTH_ALLOWED_GROUPS && - !groups.some(group => allowedGroups.includes(group.toLowerCase().trim())) - ) { - return { error: `Username ${login} does not belong to an allowed group` }; - } - - if (access_token) { - return { - accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }), - }; - } - - return { error: 'Token not found' }; + return createAuthProvider().oauthToken(params); }, login_meta: true, async login(params) { const { login, password } = params; - if (process.env.AD_URL) { - const adConfig = { - url: process.env.AD_URL, - baseDN: process.env.AD_BASEDN, - username: process.env.AD_USERNAME, - password: process.env.AD_PASSOWRD, - }; - const ad = new AD(adConfig); - try { - const res = await ad.authenticate(login, password); - if (!res) { - return { error: 'Login failed' }; - } - if ( - process.env.AD_ALLOWED_LOGINS && - !process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim()) - ) { - return { error: `Username ${login} not allowed to log in` }; - } - return { - accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }), - }; - } catch (err) { - logger.error({ err }, 'Failed active directory authentization'); - return { - error: err.message, - }; - } - } - - const logins = getLogins(); - if (!logins) { - return { error: 'Logins not configured' }; - } - const foundLogin = logins.find(x => x.login == login); - if (foundLogin && foundLogin.password && foundLogin.password == password) { - return { - accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }), - }; - } - return { error: 'Invalid credentials' }; + return createAuthProvider().login(login, password); }, authMiddleware, diff --git a/packages/api/src/controllers/storage.js b/packages/api/src/controllers/storage.js index 3013eaf21..f08605ed0 100644 --- a/packages/api/src/controllers/storage.js +++ b/packages/api/src/controllers/storage.js @@ -3,4 +3,8 @@ module.exports = { async connections() { return null; }, + + async getConnection({ conid }) { + return null; + }, }; diff --git a/packages/api/src/main.js b/packages/api/src/main.js index 6bed33e2f..0c295b71a 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -35,6 +35,7 @@ const getExpressPath = require('./utility/getExpressPath'); const { getLogins } = require('./utility/hasPermission'); const _ = require('lodash'); const { getLogger } = require('dbgate-tools'); +const { createAuthProvider } = require('./auth/authProvider'); const logger = getLogger('main'); @@ -45,11 +46,11 @@ function start() { const server = http.createServer(app); - const logins = getLogins(); - if (logins && process.env.BASIC_AUTH) { + const basicAuthLogins = createAuthProvider().getBasicAuthLogins(); + if (basicAuthLogins) { app.use( basicAuth({ - users: _.fromPairs(logins.filter(x => x.password).map(x => [x.login, x.password])), + users: basicAuthLogins, challenge: true, realm: 'DbGate Web App', }) diff --git a/packages/api/src/utility/hasPermission.js b/packages/api/src/utility/hasPermission.js index d8c7d15bc..fc40d3871 100644 --- a/packages/api/src/utility/hasPermission.js +++ b/packages/api/src/utility/hasPermission.js @@ -24,49 +24,48 @@ function hasPermission(tested, req) { return testPermission(tested, userPermissions[key]); } -let loginsCache = null; -let loginsLoaded = false; +// let loginsCache = null; +// let loginsLoaded = false; -function getLogins() { - if (loginsLoaded) { - return loginsCache; - } +// function getLogins() { +// if (loginsLoaded) { +// return loginsCache; +// } - const res = []; - if (process.env.LOGIN && process.env.PASSWORD) { - res.push({ - login: process.env.LOGIN, - password: process.env.PASSWORD, - permissions: process.env.PERMISSIONS, - }); - } - if (process.env.LOGINS) { - const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim())); - for (const login of logins) { - const password = process.env[`LOGIN_PASSWORD_${login}`]; - const permissions = process.env[`LOGIN_PERMISSIONS_${login}`]; - if (password) { - res.push({ - login, - password, - permissions, - }); - } - } - } - else if (process.env.OAUTH_PERMISSIONS) { - const login_permission_keys = Object.keys(process.env).filter((key) => _.startsWith(key, 'LOGIN_PERMISSIONS_')) - for (const permissions_key of login_permission_keys) { - const login = permissions_key.replace('LOGIN_PERMISSIONS_', ''); - const permissions = process.env[permissions_key]; - userPermissions[login] = compilePermissions(permissions); - } - } +// const res = []; +// if (process.env.LOGIN && process.env.PASSWORD) { +// res.push({ +// login: process.env.LOGIN, +// password: process.env.PASSWORD, +// permissions: process.env.PERMISSIONS, +// }); +// } +// if (process.env.LOGINS) { +// const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim())); +// for (const login of logins) { +// const password = process.env[`LOGIN_PASSWORD_${login}`]; +// const permissions = process.env[`LOGIN_PERMISSIONS_${login}`]; +// if (password) { +// res.push({ +// login, +// password, +// permissions, +// }); +// } +// } +// } else if (process.env.OAUTH_PERMISSIONS) { +// const login_permission_keys = Object.keys(process.env).filter(key => _.startsWith(key, 'LOGIN_PERMISSIONS_')); +// for (const permissions_key of login_permission_keys) { +// const login = permissions_key.replace('LOGIN_PERMISSIONS_', ''); +// const permissions = process.env[permissions_key]; +// userPermissions[login] = compilePermissions(permissions); +// } +// } - loginsCache = res.length > 0 ? res : null; - loginsLoaded = true; - return loginsCache; -} +// loginsCache = res.length > 0 ? res : null; +// loginsLoaded = true; +// return loginsCache; +// } function connectionHasPermission(connection, req) { if (!connection) { diff --git a/packages/web/src/utility/connectionsPinger.js b/packages/web/src/utility/connectionsPinger.js index aed248bf5..efa0fd7b6 100644 --- a/packages/web/src/utility/connectionsPinger.js +++ b/packages/web/src/utility/connectionsPinger.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { openedConnections, currentDatabase, openedConnectionsWithTemporary } from '../stores'; +import { openedConnections, currentDatabase, openedConnectionsWithTemporary, getCurrentConfig } from '../stores'; import { apiCall, strmid } from './api'; import { getConnectionList } from './metadataLoaders'; @@ -10,7 +10,10 @@ import { getConnectionList } from './metadataLoaders'; // }; const doServerPing = value => { - apiCall('server-connections/ping', { conidArray: ['__storage', ...value], strmid }); + apiCall('server-connections/ping', { + conidArray: getCurrentConfig().storageDatabase ? ['__storage', ...value] : value, + strmid, + }); }; const doDatabasePing = value => { diff --git a/packages/web/src/widgets/WidgetIconPanel.svelte b/packages/web/src/widgets/WidgetIconPanel.svelte index 44b5f9639..4995e7c36 100644 --- a/packages/web/src/widgets/WidgetIconPanel.svelte +++ b/packages/web/src/widgets/WidgetIconPanel.svelte @@ -8,6 +8,7 @@ visibleWidgetSideBar, visibleHamburgerMenuWidget, lockedDatabaseMode, + getCurrentConfig, } from '../stores'; import mainMenuDefinition from '../../../../app/src/mainMenuDefinition'; import hasPermission from '../utility/hasPermission'; @@ -16,7 +17,7 @@ let domMainMenu; const widgets = [ - { + getCurrentConfig().storageDatabase && { icon: 'icon admin', name: 'admin', title: 'Administration', @@ -103,7 +104,7 @@ {/if} - {#each widgets.filter(x => hasPermission(`widgets/${x.name}`)) as item} + {#each widgets.filter(x => x && hasPermission(`widgets/${x.name}`)) as item}
Date: Fri, 26 Jul 2024 09:15:22 +0200 Subject: [PATCH 2/6] auth provider refactor --- packages/api/src/auth/authCommon.js | 9 +++-- packages/api/src/auth/authProvider.js | 43 ++++++++++++++++++++--- packages/api/src/controllers/auth.js | 1 - packages/api/src/controllers/config.js | 14 ++++---- packages/api/src/main.js | 1 - packages/api/src/utility/hasPermission.js | 37 +++++++++++-------- 6 files changed, 76 insertions(+), 29 deletions(-) diff --git a/packages/api/src/auth/authCommon.js b/packages/api/src/auth/authCommon.js index 1ebeeafdc..824b9baa8 100644 --- a/packages/api/src/auth/authCommon.js +++ b/packages/api/src/auth/authCommon.js @@ -2,10 +2,15 @@ const crypto = require('crypto'); const tokenSecret = crypto.randomUUID(); -export function getTokenLifetime() { +function getTokenLifetime() { return process.env.TOKEN_LIFETIME || '1d'; } -export function getTokenSecret() { +function getTokenSecret() { return tokenSecret; } + +module.exports = { + getTokenLifetime, + getTokenSecret, +}; diff --git a/packages/api/src/auth/authProvider.js b/packages/api/src/auth/authProvider.js index 81eccb9e7..706a5a92e 100644 --- a/packages/api/src/auth/authProvider.js +++ b/packages/api/src/auth/authProvider.js @@ -61,10 +61,19 @@ class AuthProviderBase { return {}; } - getCurrentLogin(req) {} + getCurrentLogin(req) { + const { user } = (req && req.auth) || {}; + return user; + } getCurrentPermissions(req) { - return process.env.PERMISSIONS; + const login = this.getCurrentLogin(req); + const permissions = process.env[`LOGIN_PERMISSIONS_${login}`]; + return permissions || process.env.PERMISSIONS; + } + + isLoginForm() { + return false; } } @@ -123,6 +132,11 @@ class OAuthProvider extends AuthProviderBase { return { error: 'Token not found' }; } + + getCurrentLogin(req) { + const { login } = (req && req.user) || {}; + return login; + } } class ADProvider extends AuthProviderBase { @@ -156,6 +170,10 @@ class ADProvider extends AuthProviderBase { shouldAuthorizeApi() { return true; } + + isLoginForm() { + return true; + } } class LoginsProvider extends AuthProviderBase { @@ -183,9 +201,21 @@ class LoginsProvider extends AuthProviderBase { shouldAuthorizeApi() { return !process.env.BASIC_AUTH; } + + getCurrentPermissions(req) { + const logins = getEnvLogins(); + const loginName = + req && req.user && req.user.login ? req.user.login : req && req.auth && req.auth.user ? req.auth.user : null; + const login = logins && loginName ? logins.find(x => x.login == loginName) : null; + return login ? login.permissions : process.env.PERMISSIONS; + } + + isLoginForm() { + return !process.env.BASIC_AUTH; + } } -export function detectEnvAuthProvider() { +function detectEnvAuthProvider() { if (process.env.AUTH_PROVIDER) { return process.env.AUTH_PROVIDER; } @@ -201,7 +231,7 @@ export function detectEnvAuthProvider() { return 'none'; } -export function createAuthProvider() { +function createAuthProvider() { const authProvider = detectEnvAuthProvider(); switch (authProvider) { case 'oauth': @@ -214,3 +244,8 @@ export function createAuthProvider() { return new AuthProviderBase(); } } + +module.exports = { + detectEnvAuthProvider, + createAuthProvider, +}; diff --git a/packages/api/src/controllers/auth.js b/packages/api/src/controllers/auth.js index aa4df5d74..2b6389216 100644 --- a/packages/api/src/controllers/auth.js +++ b/packages/api/src/controllers/auth.js @@ -1,7 +1,6 @@ const axios = require('axios'); const jwt = require('jsonwebtoken'); const getExpressPath = require('../utility/getExpressPath'); -const { getLogins } = require('../utility/hasPermission'); const { getLogger } = require('dbgate-tools'); const AD = require('activedirectory2').promiseWrapper; const crypto = require('crypto'); diff --git a/packages/api/src/controllers/config.js b/packages/api/src/controllers/config.js index 9960b08bd..6fcb55eb6 100644 --- a/packages/api/src/controllers/config.js +++ b/packages/api/src/controllers/config.js @@ -3,7 +3,7 @@ const os = require('os'); const path = require('path'); const axios = require('axios'); const { datadir, getLogsFilePath } = require('../utility/directories'); -const { hasPermission, getLogins } = require('../utility/hasPermission'); +const { hasPermission } = require('../utility/hasPermission'); const socket = require('../utility/socket'); const _ = require('lodash'); const AsyncLock = require('async-lock'); @@ -11,6 +11,7 @@ const AsyncLock = require('async-lock'); const currentVersion = require('../currentVersion'); const platformInfo = require('../utility/platformInfo'); const connections = require('../controllers/connections'); +const { createAuthProvider } = require('../auth/authProvider'); const lock = new AsyncLock(); @@ -27,11 +28,10 @@ module.exports = { get_meta: true, async get(_params, req) { - const logins = getLogins(); - const loginName = - req && req.user && req.user.login ? req.user.login : req && req.auth && req.auth.user ? req.auth.user : null; - const login = logins && loginName ? logins.find(x => x.login == loginName) : null; - const permissions = login ? login.permissions : process.env.PERMISSIONS; + const authProvider = createAuthProvider(); + const login = authProvider.getCurrentLogin(req); + const permissions = authProvider.getCurrentPermissions(req); + const isLoginForm = authProvider.isLoginForm(); return { runAsPortal: !!connections.portalConnections, @@ -47,7 +47,7 @@ module.exports = { oauthClient: process.env.OAUTH_CLIENT_ID, oauthScope: process.env.OAUTH_SCOPE, oauthLogout: process.env.OAUTH_LOGOUT, - isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH), + isLoginForm, storageDatabase: process.env.STORAGE_DATABASE, logsFilePath: getLogsFilePath(), connectionsFilePath: path.join(datadir(), 'connections.jsonl'), diff --git a/packages/api/src/main.js b/packages/api/src/main.js index 0c295b71a..a95f0d945 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -32,7 +32,6 @@ const onFinished = require('on-finished'); const { rundir } = require('./utility/directories'); const platformInfo = require('./utility/platformInfo'); const getExpressPath = require('./utility/getExpressPath'); -const { getLogins } = require('./utility/hasPermission'); const _ = require('lodash'); const { getLogger } = require('dbgate-tools'); const { createAuthProvider } = require('./auth/authProvider'); diff --git a/packages/api/src/utility/hasPermission.js b/packages/api/src/utility/hasPermission.js index fc40d3871..2dcc36a9d 100644 --- a/packages/api/src/utility/hasPermission.js +++ b/packages/api/src/utility/hasPermission.js @@ -1,27 +1,37 @@ const { compilePermissions, testPermission } = require('dbgate-tools'); const _ = require('lodash'); +const { createAuthProvider } = require('../auth/authProvider'); -const userPermissions = {}; +const cachedPermissions = {}; function hasPermission(tested, req) { if (!req) { // request object not available, allow all return true; } - const { user } = (req && req.auth) || {}; - const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {}; - const key = user || login || ''; - const logins = getLogins(); - if (!userPermissions[key]) { - if (logins) { - const login = logins.find(x => x.login == user); - userPermissions[key] = compilePermissions(login ? login.permissions : null); - } else { - userPermissions[key] = compilePermissions(process.env.PERMISSIONS); - } + const permissions = createAuthProvider().getCurrentPermissions(req); + + if (!cachedPermissions[permissions]) { + cachedPermissions[permissions] = compilePermissions(permissions); } - return testPermission(tested, userPermissions[key]); + + return testPermission(tested, cachedPermissions[permissions]); + + // const { user } = (req && req.auth) || {}; + // const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {}; + // const key = user || login || ''; + // const logins = getLogins(); + + // if (!userPermissions[key]) { + // if (logins) { + // const login = logins.find(x => x.login == user); + // userPermissions[key] = compilePermissions(login ? login.permissions : null); + // } else { + // userPermissions[key] = compilePermissions(process.env.PERMISSIONS); + // } + // } + // return testPermission(tested, userPermissions[key]); } // let loginsCache = null; @@ -86,7 +96,6 @@ function testConnectionPermission(connection, req) { module.exports = { hasPermission, - getLogins, connectionHasPermission, testConnectionPermission, }; From 05329951f90c7b4bfc035264036b87d14cfb6560 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 26 Jul 2024 09:38:05 +0200 Subject: [PATCH 3/6] fix --- packages/api/src/auth/authProvider.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/api/src/auth/authProvider.js b/packages/api/src/auth/authProvider.js index 706a5a92e..342851de1 100644 --- a/packages/api/src/auth/authProvider.js +++ b/packages/api/src/auth/authProvider.js @@ -62,8 +62,8 @@ class AuthProviderBase { } getCurrentLogin(req) { - const { user } = (req && req.auth) || {}; - return user; + const login = req?.user?.login ?? req?.auth?.user ?? null; + return login; } getCurrentPermissions(req) { @@ -132,11 +132,6 @@ class OAuthProvider extends AuthProviderBase { return { error: 'Token not found' }; } - - getCurrentLogin(req) { - const { login } = (req && req.user) || {}; - return login; - } } class ADProvider extends AuthProviderBase { @@ -202,14 +197,6 @@ class LoginsProvider extends AuthProviderBase { return !process.env.BASIC_AUTH; } - getCurrentPermissions(req) { - const logins = getEnvLogins(); - const loginName = - req && req.user && req.user.login ? req.user.login : req && req.auth && req.auth.user ? req.auth.user : null; - const login = logins && loginName ? logins.find(x => x.login == loginName) : null; - return login ? login.permissions : process.env.PERMISSIONS; - } - isLoginForm() { return !process.env.BASIC_AUTH; } From 8db941dc067ad6c43402d57d139f76c226c3cab3 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 26 Jul 2024 09:57:27 +0200 Subject: [PATCH 4/6] AD auth supports basic auth --- packages/api/src/auth/authProvider.js | 15 ++------------- packages/api/src/main.js | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/api/src/auth/authProvider.js b/packages/api/src/auth/authProvider.js index 342851de1..e114b9d10 100644 --- a/packages/api/src/auth/authProvider.js +++ b/packages/api/src/auth/authProvider.js @@ -49,10 +49,6 @@ class AuthProviderBase { return {}; } - getBasicAuthLogins() { - return null; - } - shouldAuthorizeApi() { return false; } @@ -163,11 +159,11 @@ class ADProvider extends AuthProviderBase { } shouldAuthorizeApi() { - return true; + return !process.env.BASIC_AUTH; } isLoginForm() { - return true; + return !process.env.BASIC_AUTH; } } @@ -186,13 +182,6 @@ class LoginsProvider extends AuthProviderBase { return { error: 'Invalid credentials' }; } - getBasicAuthLogins() { - const logins = getEnvLogins(); - if (logins && process.env.BASIC_AUTH) { - return _.fromPairs(logins.filter(x => x.password).map(x => [x.login, x.password])); - } - } - shouldAuthorizeApi() { return !process.env.BASIC_AUTH; } diff --git a/packages/api/src/main.js b/packages/api/src/main.js index a95f0d945..3c0fa85fc 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -45,11 +45,23 @@ function start() { const server = http.createServer(app); - const basicAuthLogins = createAuthProvider().getBasicAuthLogins(); - if (basicAuthLogins) { + if (process.env.BASIC_AUTH) { + async function authorizer(username, password, cb) { + try { + const resp = await createAuthProvider().login(username, password); + if (resp.accessToken) { + cb(null, true); + } else { + cb(null, false); + } + } catch (err) { + cb(err, false); + } + } app.use( basicAuth({ - users: basicAuthLogins, + authorizer, + authorizeAsync: true, challenge: true, realm: 'DbGate Web App', }) From c3fe20b6f93d8aea13a7be504953ee6ee460448e Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 26 Jul 2024 10:12:22 +0200 Subject: [PATCH 5/6] removed LOGINS variable --- packages/api/src/auth/authProvider.js | 57 +++++++-------------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/packages/api/src/auth/authProvider.js b/packages/api/src/auth/authProvider.js index e114b9d10..fa1d71459 100644 --- a/packages/api/src/auth/authProvider.js +++ b/packages/api/src/auth/authProvider.js @@ -8,42 +8,6 @@ const jwt = require('jsonwebtoken'); const logger = getLogger('authProvider'); -let envLoginsCache = null; -let envLoginsLoaded = false; - -function getEnvLogins() { - if (envLoginsLoaded) { - return envLoginsCache; - } - - const res = []; - if (process.env.LOGIN && process.env.PASSWORD) { - res.push({ - login: process.env.LOGIN, - password: process.env.PASSWORD, - permissions: process.env.PERMISSIONS, - }); - } - if (process.env.LOGINS) { - const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim())); - for (const login of logins) { - const password = process.env[`LOGIN_PASSWORD_${login}`]; - const permissions = process.env[`LOGIN_PERMISSIONS_${login}`]; - if (password) { - res.push({ - login, - password, - permissions, - }); - } - } - } - - envLoginsCache = res.length > 0 ? res : null; - envLoginsLoaded = true; - return envLoginsCache; -} - class AuthProviderBase { async login(login, password) { return {}; @@ -169,12 +133,7 @@ class ADProvider extends AuthProviderBase { class LoginsProvider extends AuthProviderBase { async login(login, password) { - const logins = getEnvLogins(); - if (!logins) { - return { error: 'Logins not configured' }; - } - const foundLogin = logins.find(x => x.login == login); - if (foundLogin && foundLogin.password && foundLogin.password == password) { + if (password == process.env[`LOGIN_PASSWORD_${login}`]) { return { accessToken: jwt.sign({ login }, getTokenSecret(), { expiresIn: getTokenLifetime() }), }; @@ -191,6 +150,18 @@ class LoginsProvider extends AuthProviderBase { } } +function hasEnvLogins() { + if (process.env.LOGIN && process.env.PASSWORD) { + return true; + } + for (const key in process.env) { + if (key.startsWith('LOGIN_PASSWORD_')) { + return true; + } + } + return false; +} + function detectEnvAuthProvider() { if (process.env.AUTH_PROVIDER) { return process.env.AUTH_PROVIDER; @@ -201,7 +172,7 @@ function detectEnvAuthProvider() { if (process.env.AD_URL) { return 'ad'; } - if (getEnvLogins()) { + if (hasEnvLogins()) { return 'logins'; } return 'none'; From a4cb65b7b116c32028979106b7347f6ece51fd9e Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 26 Jul 2024 12:30:49 +0200 Subject: [PATCH 6/6] icons --- packages/web/src/icons/FontIcon.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte index f25e90ea1..6bd0312d4 100644 --- a/packages/web/src/icons/FontIcon.svelte +++ b/packages/web/src/icons/FontIcon.svelte @@ -27,6 +27,7 @@ 'icon users': 'mdi mdi-account-multiple', 'icon role': 'mdi mdi-account-group', 'icon admin': 'mdi mdi-security', + 'icon auth': 'mdi mdi-account-key', 'icon version': 'mdi mdi-ticket-confirmation', 'icon pin': 'mdi mdi-pin', 'icon arrange': 'mdi mdi-arrange-send-to-back', @@ -191,6 +192,7 @@ 'img users': 'mdi mdi-account-multiple color-icon-blue', 'img role': 'mdi mdi-account-group color-icon-blue', 'img admin': 'mdi mdi-security color-icon-blue', + 'img auth': 'mdi mdi-account-key color-icon-blue', 'img add': 'mdi mdi-plus-circle color-icon-green', 'img minus': 'mdi mdi-minus-circle color-icon-red',