mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 22:36:01 +00:00
368 lines
8.7 KiB
JavaScript
368 lines
8.7 KiB
JavaScript
const { getTokenSecret, getTokenLifetime } = require('./authCommon');
|
|
const _ = require('lodash');
|
|
const axios = require('axios');
|
|
const { getLogger, getPredefinedPermissions } = require('dbgate-tools');
|
|
|
|
const AD = require('activedirectory2').promiseWrapper;
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
const logger = getLogger('authProvider');
|
|
|
|
class AuthProviderBase {
|
|
amoid = 'none';
|
|
skipInList = false;
|
|
|
|
async login(login, password, options = undefined, req = undefined) {
|
|
return {
|
|
accessToken: jwt.sign(
|
|
{
|
|
amoid: this.amoid,
|
|
},
|
|
getTokenSecret(),
|
|
{ expiresIn: getTokenLifetime() }
|
|
),
|
|
};
|
|
}
|
|
|
|
oauthToken(params, req) {
|
|
return {};
|
|
}
|
|
|
|
getCurrentLogin(req) {
|
|
const login = req?.user?.login ?? req?.auth?.user ?? null;
|
|
return login;
|
|
}
|
|
|
|
isUserLoggedIn(req) {
|
|
return !!req?.user || !!req?.auth;
|
|
}
|
|
|
|
async getCurrentPermissions(req) {
|
|
const login = this.getCurrentLogin(req);
|
|
const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
|
|
return permissions || process.env.PERMISSIONS;
|
|
}
|
|
|
|
async checkCurrentConnectionPermission(req, conid) {
|
|
return true;
|
|
}
|
|
|
|
async getCurrentDatabasePermissions(req) {
|
|
return [];
|
|
}
|
|
|
|
async getCurrentTablePermissions(req) {
|
|
return [];
|
|
}
|
|
|
|
async getCurrentFilePermissions(req) {
|
|
return [];
|
|
}
|
|
|
|
getLoginPageConnections() {
|
|
return null;
|
|
}
|
|
|
|
getSingleConnectionId(req) {
|
|
return null;
|
|
}
|
|
|
|
toJson() {
|
|
return {
|
|
amoid: this.amoid,
|
|
workflowType: 'anonymous',
|
|
name: 'Anonymous',
|
|
};
|
|
}
|
|
|
|
async redirect({ state }) {
|
|
return {
|
|
status: 'error',
|
|
};
|
|
}
|
|
|
|
async getLogoutUrl() {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class OAuthProvider extends AuthProviderBase {
|
|
amoid = 'oauth';
|
|
|
|
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, id_token } = resp.data;
|
|
|
|
let payload = jwt.decode(access_token);
|
|
|
|
// Fallback to id_token in case the access_token is not a JWT
|
|
// https://www.oauth.com/oauth2-servers/access-tokens/
|
|
// https://github.com/dbgate/dbgate/issues/727
|
|
if (!payload && id_token) {
|
|
payload = jwt.decode(id_token);
|
|
}
|
|
|
|
logger.info({ payload }, 'DBGM-00002 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' };
|
|
}
|
|
|
|
async getLogoutUrl() {
|
|
return process.env.OAUTH_LOGOUT;
|
|
}
|
|
|
|
toJson() {
|
|
return {
|
|
...super.toJson(),
|
|
workflowType: 'redirect',
|
|
name: 'OAuth 2.0',
|
|
};
|
|
}
|
|
|
|
redirect({ state, redirectUri }) {
|
|
const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : '';
|
|
return {
|
|
status: 'ok',
|
|
uri: `${process.env.OAUTH_AUTH}?client_id=${
|
|
process.env.OAUTH_CLIENT_ID
|
|
}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(
|
|
state
|
|
)}${scopeParam}`,
|
|
};
|
|
}
|
|
}
|
|
|
|
class ADProvider extends AuthProviderBase {
|
|
amoid = 'ad';
|
|
|
|
async login(login, password, options = undefined) {
|
|
const adConfig = {
|
|
url: process.env.AD_URL,
|
|
baseDN: process.env.AD_BASEDN,
|
|
username: process.env.AD_USERNAME,
|
|
password: process.env.AD_PASSWORD,
|
|
};
|
|
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(
|
|
{
|
|
amoid: this.amoid,
|
|
login,
|
|
},
|
|
getTokenSecret(),
|
|
{ expiresIn: getTokenLifetime() }
|
|
),
|
|
};
|
|
} catch (e) {
|
|
return { error: 'Login failed' };
|
|
}
|
|
}
|
|
|
|
toJson() {
|
|
return {
|
|
...super.toJson(),
|
|
workflowType: 'credentials',
|
|
name: 'Active Directory',
|
|
};
|
|
}
|
|
}
|
|
|
|
class LoginsProvider extends AuthProviderBase {
|
|
amoid = 'logins';
|
|
|
|
async login(login, password, options = undefined) {
|
|
if (login && password && process.env['LOGIN'] == login && process.env['PASSWORD'] == password) {
|
|
return {
|
|
accessToken: jwt.sign(
|
|
{
|
|
amoid: this.amoid,
|
|
login,
|
|
},
|
|
getTokenSecret(),
|
|
{ expiresIn: getTokenLifetime() }
|
|
),
|
|
};
|
|
}
|
|
|
|
if (password && password == process.env[`LOGIN_PASSWORD_${login}`]) {
|
|
return {
|
|
accessToken: jwt.sign(
|
|
{
|
|
amoid: this.amoid,
|
|
login,
|
|
},
|
|
getTokenSecret(),
|
|
{ expiresIn: getTokenLifetime() }
|
|
),
|
|
};
|
|
}
|
|
|
|
return { error: 'Invalid credentials' };
|
|
}
|
|
|
|
toJson() {
|
|
return {
|
|
...super.toJson(),
|
|
workflowType: 'credentials',
|
|
name: 'Login & Password',
|
|
};
|
|
}
|
|
}
|
|
|
|
class DenyAllProvider extends AuthProviderBase {
|
|
amoid = 'deny';
|
|
|
|
async login(login, password, options = undefined) {
|
|
return { error: 'Login not allowed' };
|
|
}
|
|
|
|
toJson() {
|
|
return {
|
|
...super.toJson(),
|
|
workflowType: 'credentials',
|
|
name: 'Deny all',
|
|
};
|
|
}
|
|
}
|
|
|
|
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 detectEnvAuthProviderCore() {
|
|
if (process.env.AUTH_PROVIDER) {
|
|
return process.env.AUTH_PROVIDER;
|
|
}
|
|
if (process.env.STORAGE_DATABASE) {
|
|
return 'denyall';
|
|
}
|
|
if (process.env.OAUTH_AUTH) {
|
|
return 'oauth';
|
|
}
|
|
if (process.env.AD_URL) {
|
|
return 'ad';
|
|
}
|
|
if (hasEnvLogins()) {
|
|
return 'logins';
|
|
}
|
|
return 'none';
|
|
}
|
|
|
|
function detectEnvAuthProvider() {
|
|
const authProvider = detectEnvAuthProviderCore();
|
|
if (process.env.BASIC_AUTH && authProvider != 'logins' && authProvider != 'ad') {
|
|
throw new Error(`BASIC_AUTH is not supported with ${authProvider} auth provider`);
|
|
}
|
|
return authProvider;
|
|
}
|
|
|
|
function createEnvAuthProvider() {
|
|
const authProvider = detectEnvAuthProvider();
|
|
switch (authProvider) {
|
|
case 'oauth':
|
|
return new OAuthProvider();
|
|
case 'ad':
|
|
return new ADProvider();
|
|
case 'logins':
|
|
return new LoginsProvider();
|
|
case 'denyall':
|
|
return new DenyAllProvider();
|
|
default:
|
|
return new AuthProviderBase();
|
|
}
|
|
}
|
|
|
|
let defaultAuthProvider = createEnvAuthProvider();
|
|
let authProviders = [defaultAuthProvider];
|
|
|
|
function getAuthProviders() {
|
|
return authProviders;
|
|
}
|
|
|
|
function getAuthProviderById(amoid) {
|
|
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 = {
|
|
AuthProviderBase,
|
|
detectEnvAuthProvider,
|
|
getAuthProviders,
|
|
getDefaultAuthProvider,
|
|
setAuthProviders,
|
|
getAuthProviderById,
|
|
getAuthProviderFromReq,
|
|
};
|