dbgate-api
Version:
Allows run DbGate data-manipulation scripts.
351 lines (298 loc) • 8.56 kB
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';
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;
}
getCurrentPermissions(req) {
const login = this.getCurrentLogin(req);
const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
return permissions || process.env.PERMISSIONS;
}
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 }, '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,
};