mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
Merge branch 'oauth'
This commit is contained in:
commit
c2b6b08105
@ -16,6 +16,7 @@
|
|||||||
"start:app:debug:ssh": "cd app && cross-env DEBUG=ssh yarn start",
|
"start:app:debug:ssh": "cd app && cross-env DEBUG=ssh yarn start",
|
||||||
"start:api:portal": "yarn workspace dbgate-api start:portal",
|
"start:api:portal": "yarn workspace dbgate-api start:portal",
|
||||||
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
|
"start:api:singledb": "yarn workspace dbgate-api start:singledb",
|
||||||
|
"start:api:auth": "yarn workspace dbgate-api start:auth",
|
||||||
"start:web": "yarn workspace dbgate-web dev",
|
"start:web": "yarn workspace dbgate-web dev",
|
||||||
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
||||||
"start:tools": "yarn workspace dbgate-tools start",
|
"start:tools": "yarn workspace dbgate-tools start",
|
||||||
|
1
packages/api/env/auth/.gitignore
vendored
Normal file
1
packages/api/env/auth/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.env
|
@ -17,6 +17,7 @@
|
|||||||
"dbgate"
|
"dbgate"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"activedirectory2": "^2.1.0",
|
||||||
"async-lock": "^1.2.4",
|
"async-lock": "^1.2.4",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
@ -42,6 +43,7 @@
|
|||||||
"is-electron": "^2.2.1",
|
"is-electron": "^2.2.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json-stable-stringify": "^1.0.1",
|
"json-stable-stringify": "^1.0.1",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
"line-reader": "^0.4.0",
|
"line-reader": "^0.4.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
@ -57,6 +59,7 @@
|
|||||||
"start": "env-cmd node src/index.js --listen-api",
|
"start": "env-cmd node src/index.js --listen-api",
|
||||||
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
|
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
|
||||||
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
|
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
|
||||||
|
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
||||||
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
"start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api",
|
||||||
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
"start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api",
|
||||||
"ts": "tsc",
|
"ts": "tsc",
|
||||||
|
142
packages/api/src/controllers/auth.js
Normal file
142
packages/api/src/controllers/auth.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const getExpressPath = require('../utility/getExpressPath');
|
||||||
|
const uuidv1 = require('uuid/v1');
|
||||||
|
const { getLogins } = require('../utility/hasPermission');
|
||||||
|
const AD = require('activedirectory2').promiseWrapper;
|
||||||
|
|
||||||
|
const tokenSecret = uuidv1();
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
function unauthorizedResponse(req, res, text) {
|
||||||
|
// if (req.path == getExpressPath('/config/get-settings')) {
|
||||||
|
// return res.json({});
|
||||||
|
// }
|
||||||
|
// if (req.path == getExpressPath('/connections/list')) {
|
||||||
|
// return res.json([]);
|
||||||
|
// }
|
||||||
|
return res.sendStatus(401).send(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function authMiddleware(req, res, next) {
|
||||||
|
const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', '/auth/login', '/stream'];
|
||||||
|
|
||||||
|
if (!shouldAuthorizeApi()) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x));
|
||||||
|
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
if (!authHeader) {
|
||||||
|
if (skipAuth) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return unauthorizedResponse(req, res, 'missing authorization header');
|
||||||
|
}
|
||||||
|
const token = authHeader.split(' ')[1];
|
||||||
|
try {
|
||||||
|
const decoded = jwt.verify(token, tokenSecret);
|
||||||
|
req.user = decoded;
|
||||||
|
return next();
|
||||||
|
} catch (err) {
|
||||||
|
if (skipAuth) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Sending invalid token error', err.message);
|
||||||
|
|
||||||
|
return unauthorizedResponse(req, res, 'invalid token');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
oauthToken_meta: true,
|
||||||
|
async oauthToken(params) {
|
||||||
|
const { redirectUri, code } = params;
|
||||||
|
|
||||||
|
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}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const { access_token, refresh_token } = resp.data;
|
||||||
|
|
||||||
|
const payload = jwt.decode(access_token);
|
||||||
|
|
||||||
|
console.log('User payload returned from OAUTH:', payload);
|
||||||
|
|
||||||
|
const login = 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` };
|
||||||
|
}
|
||||||
|
if (access_token) {
|
||||||
|
return {
|
||||||
|
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { error: 'Token not found' };
|
||||||
|
},
|
||||||
|
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) {
|
||||||
|
console.log('Failed active directory authentization', err.message);
|
||||||
|
return {
|
||||||
|
error: err.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const logins = getLogins();
|
||||||
|
if (!logins) {
|
||||||
|
return { error: 'Logins not configured' };
|
||||||
|
}
|
||||||
|
if (logins.find(x => x.login == login)?.password == password) {
|
||||||
|
return {
|
||||||
|
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { error: 'Invalid credentials' };
|
||||||
|
},
|
||||||
|
|
||||||
|
authMiddleware,
|
||||||
|
shouldAuthorizeApi,
|
||||||
|
};
|
@ -28,7 +28,7 @@ module.exports = {
|
|||||||
get_meta: true,
|
get_meta: true,
|
||||||
async get(_params, req) {
|
async get(_params, req) {
|
||||||
const logins = getLogins();
|
const logins = getLogins();
|
||||||
const login = logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null;
|
const login = req.user ? req.user.login : logins ? logins.find(x => x.login == (req.auth && req.auth.user)) : null;
|
||||||
const permissions = login ? login.permissions : process.env.PERMISSIONS;
|
const permissions = login ? login.permissions : process.env.PERMISSIONS;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -40,6 +40,9 @@ module.exports = {
|
|||||||
isDocker: platformInfo.isDocker,
|
isDocker: platformInfo.isDocker,
|
||||||
permissions,
|
permissions,
|
||||||
login,
|
login,
|
||||||
|
oauth: process.env.OAUTH_AUTH,
|
||||||
|
oauthLogout: process.env.OAUTH_LOGOUT,
|
||||||
|
isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH),
|
||||||
...currentVersion,
|
...currentVersion,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -20,6 +20,7 @@ const jsldata = require('./controllers/jsldata');
|
|||||||
const config = require('./controllers/config');
|
const config = require('./controllers/config');
|
||||||
const archive = require('./controllers/archive');
|
const archive = require('./controllers/archive');
|
||||||
const apps = require('./controllers/apps');
|
const apps = require('./controllers/apps');
|
||||||
|
const auth = require('./controllers/auth');
|
||||||
const uploads = require('./controllers/uploads');
|
const uploads = require('./controllers/uploads');
|
||||||
const plugins = require('./controllers/plugins');
|
const plugins = require('./controllers/plugins');
|
||||||
const files = require('./controllers/files');
|
const files = require('./controllers/files');
|
||||||
@ -41,7 +42,7 @@ function start() {
|
|||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
const logins = getLogins();
|
const logins = getLogins();
|
||||||
if (logins) {
|
if (logins && process.env.BASIC_AUTH) {
|
||||||
app.use(
|
app.use(
|
||||||
basicAuth({
|
basicAuth({
|
||||||
users: _.fromPairs(logins.map(x => [x.login, x.password])),
|
users: _.fromPairs(logins.map(x => [x.login, x.password])),
|
||||||
@ -53,6 +54,10 @@ function start() {
|
|||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
|
if (auth.shouldAuthorizeApi()) {
|
||||||
|
app.use(auth.authMiddleware);
|
||||||
|
}
|
||||||
|
|
||||||
app.get(getExpressPath('/stream'), async function (req, res) {
|
app.get(getExpressPath('/stream'), async function (req, res) {
|
||||||
res.set({
|
res.set({
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
@ -157,6 +162,7 @@ function useAllControllers(app, electron) {
|
|||||||
useController(app, electron, '/scheduler', scheduler);
|
useController(app, electron, '/scheduler', scheduler);
|
||||||
useController(app, electron, '/query-history', queryHistory);
|
useController(app, electron, '/query-history', queryHistory);
|
||||||
useController(app, electron, '/apps', apps);
|
useController(app, electron, '/apps', apps);
|
||||||
|
useController(app, electron, '/auth', auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setElectronSender(electronSender) {
|
function setElectronSender(electronSender) {
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
import getElectron from './utility/getElectron';
|
import getElectron from './utility/getElectron';
|
||||||
import AppStartInfo from './widgets/AppStartInfo.svelte';
|
import AppStartInfo from './widgets/AppStartInfo.svelte';
|
||||||
import SettingsListener from './utility/SettingsListener.svelte';
|
import SettingsListener from './utility/SettingsListener.svelte';
|
||||||
|
import { handleAuthOnStartup, handleOauthCallback } from './clientAuth';
|
||||||
|
|
||||||
let loadedApi = false;
|
let loadedApi = false;
|
||||||
let loadedPlugins = false;
|
let loadedPlugins = false;
|
||||||
@ -33,9 +34,11 @@
|
|||||||
try {
|
try {
|
||||||
// console.log('************** LOADING API');
|
// console.log('************** LOADING API');
|
||||||
|
|
||||||
|
const config = await getConfig();
|
||||||
|
await handleAuthOnStartup(config);
|
||||||
|
|
||||||
const connections = await apiCall('connections/list');
|
const connections = await apiCall('connections/list');
|
||||||
const settings = await getSettings();
|
const settings = await getSettings();
|
||||||
const config = await getConfig();
|
|
||||||
const apps = await getUsedApps();
|
const apps = await getUsedApps();
|
||||||
loadedApi = settings && connections && config && apps;
|
loadedApi = settings && connections && config && apps;
|
||||||
|
|
||||||
|
115
packages/web/src/LoginPage.svelte
Normal file
115
packages/web/src/LoginPage.svelte
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { internalRedirectTo } from './clientAuth';
|
||||||
|
import FormButton from './forms/FormButton.svelte';
|
||||||
|
import FormPasswordField from './forms/FormPasswordField.svelte';
|
||||||
|
import FormProvider from './forms/FormProvider.svelte';
|
||||||
|
import FormSubmit from './forms/FormSubmit.svelte';
|
||||||
|
import FormTextField from './forms/FormTextField.svelte';
|
||||||
|
import { apiCall, enableApi } from './utility/api';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const removed = document.getElementById('starting_dbgate_zero');
|
||||||
|
if (removed) removed.remove();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root theme-light theme-type-light">
|
||||||
|
<div class="text">DbGate</div>
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="logo">
|
||||||
|
<img class="img" src="logo192.png" />
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="heading">Log In</div>
|
||||||
|
<FormProvider>
|
||||||
|
<FormTextField label="Username" name="login" autocomplete="username" />
|
||||||
|
<FormPasswordField label="Password" name="password" autocomplete="current-password" />
|
||||||
|
|
||||||
|
<div class="submit">
|
||||||
|
<FormSubmit
|
||||||
|
value="Log In"
|
||||||
|
on:click={async e => {
|
||||||
|
enableApi();
|
||||||
|
const resp = await apiCall('auth/login', e.detail);
|
||||||
|
if (resp.error) {
|
||||||
|
internalRedirectTo(`/?page=not-logged&error=${encodeURIComponent(resp.error)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { accessToken } = resp;
|
||||||
|
if (accessToken) {
|
||||||
|
localStorage.setItem('accessToken', accessToken);
|
||||||
|
internalRedirectTo('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internalRedirectTo(`/?page=not-logged`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
font-size: 30pt;
|
||||||
|
font-family: monospace;
|
||||||
|
color: var(--theme-bg-2);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.submit {
|
||||||
|
margin: var(--dim-large-form-margin);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit :global(input) {
|
||||||
|
flex: 1;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
color: var(--theme-font-1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--theme-bg-1);
|
||||||
|
align-items: baseline;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 600px;
|
||||||
|
max-width: 80vw;
|
||||||
|
/* max-width: 600px;
|
||||||
|
width: 40vw; */
|
||||||
|
border: 1px solid var(--theme-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--theme-bg-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
margin-top: 20vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
text-align: center;
|
||||||
|
margin: 1em;
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
</style>
|
52
packages/web/src/NotLoggedPage.svelte
Normal file
52
packages/web/src/NotLoggedPage.svelte
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import FormStyledButton from './buttons/FormStyledButton.svelte';
|
||||||
|
import { doLogout, redirectToLogin } from './clientAuth';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const removed = document.getElementById('starting_dbgate_zero');
|
||||||
|
if (removed) removed.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const error = params.get('error');
|
||||||
|
|
||||||
|
function handleLogin() {
|
||||||
|
redirectToLogin(undefined, true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root theme-light theme-type-light">
|
||||||
|
<div class="title">Sorry, you are not authorized to run DbGate</div>
|
||||||
|
{#if error}
|
||||||
|
<div class="error">{error}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="button">
|
||||||
|
<FormStyledButton value="Log In" on:click={handleLogin} />
|
||||||
|
<FormStyledButton value="Log Out" on:click={doLogout} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
color: var(--theme-font-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: x-large;
|
||||||
|
margin-top: 20vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
margin-top: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
107
packages/web/src/clientAuth.ts
Normal file
107
packages/web/src/clientAuth.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { apiCall, enableApi } from './utility/api';
|
||||||
|
import { getConfig } from './utility/metadataLoaders';
|
||||||
|
|
||||||
|
export function isOauthCallback() {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const sentCode = params.get('code');
|
||||||
|
const sentState = params.get('state');
|
||||||
|
|
||||||
|
return (
|
||||||
|
sentCode && sentState && sentState.startsWith('dbg-oauth:') && sentState == sessionStorage.getItem('oauthState')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleOauthCallback() {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const sentCode = params.get('code');
|
||||||
|
|
||||||
|
if (isOauthCallback()) {
|
||||||
|
sessionStorage.removeItem('oauthState');
|
||||||
|
apiCall('auth/oauth-token', {
|
||||||
|
code: sentCode,
|
||||||
|
redirectUri: location.origin + location.pathname,
|
||||||
|
}).then(authResp => {
|
||||||
|
const { accessToken, error, errorMessage } = authResp;
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
console.log('Settings access token from OAUTH');
|
||||||
|
localStorage.setItem('accessToken', accessToken);
|
||||||
|
internalRedirectTo('/');
|
||||||
|
} else {
|
||||||
|
console.log('Error when processing OAUTH callback', error || errorMessage);
|
||||||
|
internalRedirectTo(`/?page=not-logged&error=${error || errorMessage}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleAuthOnStartup(config) {
|
||||||
|
if (config.oauth) {
|
||||||
|
console.log('OAUTH callback URL:', location.origin + location.pathname);
|
||||||
|
}
|
||||||
|
if (config.oauth || config.isLoginForm) {
|
||||||
|
if (localStorage.getItem('accessToken')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectToLogin(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function redirectToLogin(config = null, force = false) {
|
||||||
|
if (!config) {
|
||||||
|
enableApi();
|
||||||
|
config = await getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isLoginForm) {
|
||||||
|
if (!force) {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
if (params.get('page') == 'login' || params.get('page') == 'not-logged') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internalRedirectTo('/?page=login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.oauth) {
|
||||||
|
const state = `dbg-oauth:${Math.random().toString().substr(2)}`;
|
||||||
|
sessionStorage.setItem('oauthState', state);
|
||||||
|
console.log('Redirecting to OAUTH provider');
|
||||||
|
location.replace(
|
||||||
|
`${config.oauth}?client_id=dbgate&response_type=code&redirect_uri=${encodeURIComponent(
|
||||||
|
location.origin + location.pathname
|
||||||
|
)}&state=${encodeURIComponent(state)}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function internalRedirectTo(path) {
|
||||||
|
const index = location.pathname.lastIndexOf('/');
|
||||||
|
const newPath = index >= 0 ? location.pathname.substring(0, index) + path : path;
|
||||||
|
location.replace(newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function doLogout() {
|
||||||
|
enableApi();
|
||||||
|
const config = await getConfig();
|
||||||
|
if (config.oauth) {
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
if (config.oauthLogout) {
|
||||||
|
window.location.href = config.oauthLogout;
|
||||||
|
} else {
|
||||||
|
internalRedirectTo('/?page=not-logged');
|
||||||
|
}
|
||||||
|
} else if (config.isLoginForm) {
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
internalRedirectTo('/?page=not-logged');
|
||||||
|
} else {
|
||||||
|
window.location.href = 'config/logout';
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,7 @@ import runCommand from './runCommand';
|
|||||||
import { openWebLink } from '../utility/exportFileTools';
|
import { openWebLink } from '../utility/exportFileTools';
|
||||||
import { getSettings } from '../utility/metadataLoaders';
|
import { getSettings } from '../utility/metadataLoaders';
|
||||||
import { isMac } from '../utility/common';
|
import { isMac } from '../utility/common';
|
||||||
|
import { doLogout, internalRedirectTo } from '../clientAuth';
|
||||||
|
|
||||||
// function themeCommand(theme: ThemeDefinition) {
|
// function themeCommand(theme: ThemeDefinition) {
|
||||||
// return {
|
// return {
|
||||||
@ -548,9 +549,7 @@ registerCommand({
|
|||||||
category: 'App',
|
category: 'App',
|
||||||
name: 'Logout',
|
name: 'Logout',
|
||||||
testEnabled: () => getCurrentConfig()?.login != null,
|
testEnabled: () => getCurrentConfig()?.login != null,
|
||||||
onClick: () => {
|
onClick: doLogout,
|
||||||
window.location.href = 'config/logout';
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function registerFileCommands({
|
export function registerFileCommands({
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
export let value;
|
export let value;
|
||||||
export let focused = false;
|
export let focused = false;
|
||||||
export let domEditor = undefined;
|
export let domEditor = undefined;
|
||||||
|
export let autocomplete = 'new-password';
|
||||||
|
|
||||||
if (focused) onMount(() => domEditor.focus());
|
if (focused) onMount(() => domEditor.focus());
|
||||||
</script>
|
</script>
|
||||||
@ -17,5 +18,5 @@
|
|||||||
on:click
|
on:click
|
||||||
bind:this={domEditor}
|
bind:this={domEditor}
|
||||||
on:keydown
|
on:keydown
|
||||||
autocomplete="new-password"
|
{autocomplete}
|
||||||
/>
|
/>
|
||||||
|
@ -3,14 +3,41 @@ import './utility/connectionsPinger';
|
|||||||
import './utility/changeCurrentDbByTab';
|
import './utility/changeCurrentDbByTab';
|
||||||
import './commands/stdCommands';
|
import './commands/stdCommands';
|
||||||
import localStorageGarbageCollector from './utility/localStorageGarbageCollector';
|
import localStorageGarbageCollector from './utility/localStorageGarbageCollector';
|
||||||
|
import { handleOauthCallback } from './clientAuth';
|
||||||
|
import LoginPage from './LoginPage.svelte';
|
||||||
|
import NotLoggedPage from './NotLoggedPage.svelte';
|
||||||
|
|
||||||
|
const isOauthCallback = handleOauthCallback();
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const page = params.get('page');
|
||||||
|
|
||||||
localStorageGarbageCollector();
|
localStorageGarbageCollector();
|
||||||
|
|
||||||
const app = new App({
|
function createApp() {
|
||||||
target: document.body,
|
if (isOauthCallback) {
|
||||||
props: {},
|
return null;
|
||||||
});
|
}
|
||||||
|
|
||||||
// const app = null;
|
switch (page) {
|
||||||
|
case 'login':
|
||||||
|
return new LoginPage({
|
||||||
|
target: document.body,
|
||||||
|
props: {},
|
||||||
|
});
|
||||||
|
case 'not-logged':
|
||||||
|
return new NotLoggedPage({
|
||||||
|
target: document.body,
|
||||||
|
props: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new App({
|
||||||
|
target: document.body,
|
||||||
|
props: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = createApp();
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
@ -4,10 +4,21 @@ import { writable } from 'svelte/store';
|
|||||||
import getElectron from './getElectron';
|
import getElectron from './getElectron';
|
||||||
// import socket from './socket';
|
// import socket from './socket';
|
||||||
import { showSnackbarError } from '../utility/snackbar';
|
import { showSnackbarError } from '../utility/snackbar';
|
||||||
|
import { isOauthCallback, redirectToLogin } from '../clientAuth';
|
||||||
|
|
||||||
let eventSource;
|
let eventSource;
|
||||||
let apiLogging = false;
|
let apiLogging = false;
|
||||||
// let cacheCleanerRegistered;
|
// let cacheCleanerRegistered;
|
||||||
|
let apiDisabled = false;
|
||||||
|
const disabledOnOauth = isOauthCallback();
|
||||||
|
|
||||||
|
export function disableApi() {
|
||||||
|
apiDisabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enableApi() {
|
||||||
|
apiDisabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
function wantEventSource() {
|
function wantEventSource() {
|
||||||
if (!eventSource) {
|
if (!eventSource) {
|
||||||
@ -17,9 +28,9 @@ function wantEventSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processApiResponse(route, args, resp) {
|
function processApiResponse(route, args, resp) {
|
||||||
if (apiLogging) {
|
// if (apiLogging) {
|
||||||
console.log('<<< API RESPONSE', route, args, resp);
|
// console.log('<<< API RESPONSE', route, args, resp);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (resp?.apiErrorMessage) {
|
if (resp?.apiErrorMessage) {
|
||||||
showSnackbarError('API error:' + resp?.apiErrorMessage);
|
showSnackbarError('API error:' + resp?.apiErrorMessage);
|
||||||
@ -35,6 +46,14 @@ export async function apiCall(route: string, args: {} = undefined) {
|
|||||||
if (apiLogging) {
|
if (apiLogging) {
|
||||||
console.log('>>> API CALL', route, args);
|
console.log('>>> API CALL', route, args);
|
||||||
}
|
}
|
||||||
|
if (apiDisabled) {
|
||||||
|
console.log('API disabled!!', route);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (disabledOnOauth && route != 'auth/oauth-token') {
|
||||||
|
console.log('API disabled because oauth callback!!', route);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
if (electron) {
|
if (electron) {
|
||||||
@ -51,6 +70,18 @@ export async function apiCall(route: string, args: {} = undefined) {
|
|||||||
body: JSON.stringify(args),
|
body: JSON.stringify(args),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (resp.status == 401 && !apiDisabled) {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
|
||||||
|
disableApi();
|
||||||
|
console.log('Disabling API', route);
|
||||||
|
if (params.get('page') != 'login' && params.get('page') != 'not-logged') {
|
||||||
|
// unauthorized
|
||||||
|
redirectToLogin();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const json = await resp.json();
|
const json = await resp.json();
|
||||||
return processApiResponse(route, args, json);
|
return processApiResponse(route, args, json);
|
||||||
}
|
}
|
||||||
|
@ -15,5 +15,10 @@ export default function resolveApi() {
|
|||||||
export function resolveApiHeaders() {
|
export function resolveApiHeaders() {
|
||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
|
|
||||||
return {};
|
const res = {};
|
||||||
|
const accessToken = localStorage.getItem('accessToken');
|
||||||
|
if (accessToken) {
|
||||||
|
res['Authorization'] = `Bearer ${accessToken}`;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
78
yarn.lock
78
yarn.lock
@ -1701,6 +1701,11 @@ abbrev@1:
|
|||||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||||
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
||||||
|
|
||||||
|
abstract-logging@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839"
|
||||||
|
integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==
|
||||||
|
|
||||||
accepts@~1.3.8:
|
accepts@~1.3.8:
|
||||||
version "1.3.8"
|
version "1.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
||||||
@ -1765,6 +1770,16 @@ acorn@^8.2.4, acorn@^8.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
|
||||||
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
|
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
|
||||||
|
|
||||||
|
activedirectory2@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/activedirectory2/-/activedirectory2-2.1.0.tgz#4293f72ade8ff36e9199cf5fa5cae3818bdb947a"
|
||||||
|
integrity sha512-HaccG+/mf5NpHL1mAcLzXed4+gGlO6l7mkBi8vNIo6sTJvLoJjHgvJg12F4cy5CNcRqvPS48++s5tfdSiafn4Q==
|
||||||
|
dependencies:
|
||||||
|
abstract-logging "^2.0.0"
|
||||||
|
async "^3.1.0"
|
||||||
|
ldapjs "^2.1.0"
|
||||||
|
merge-options "^2.0.0"
|
||||||
|
|
||||||
adler-32@~1.2.0:
|
adler-32@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.2.0.tgz#6a3e6bf0a63900ba15652808cb15c6813d1a5f25"
|
resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.2.0.tgz#6a3e6bf0a63900ba15652808cb15c6813d1a5f25"
|
||||||
@ -2058,7 +2073,7 @@ async@^2.6.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.14"
|
lodash "^4.17.14"
|
||||||
|
|
||||||
async@^3.2.0, async@^3.2.3:
|
async@^3.1.0, async@^3.2.0, async@^3.2.3:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
|
||||||
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
|
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
|
||||||
@ -2225,6 +2240,13 @@ babel-preset-jest@^28.1.3:
|
|||||||
babel-plugin-jest-hoist "^28.1.3"
|
babel-plugin-jest-hoist "^28.1.3"
|
||||||
babel-preset-current-node-syntax "^1.0.0"
|
babel-preset-current-node-syntax "^1.0.0"
|
||||||
|
|
||||||
|
backoff@^2.5.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f"
|
||||||
|
integrity sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==
|
||||||
|
dependencies:
|
||||||
|
precond "0.2"
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
@ -5416,6 +5438,11 @@ is-plain-obj@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
|
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
|
||||||
integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==
|
integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==
|
||||||
|
|
||||||
|
is-plain-obj@^2.0.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
|
||||||
|
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
|
||||||
|
|
||||||
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
||||||
@ -7044,6 +7071,27 @@ kleur@^3.0.0, kleur@^3.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||||
|
|
||||||
|
ldap-filter@^0.3.3:
|
||||||
|
version "0.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ldap-filter/-/ldap-filter-0.3.3.tgz#2b14c68a2a9d4104dbdbc910a1ca85fd189e9797"
|
||||||
|
integrity sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg==
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
|
ldapjs@^2.1.0:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ldapjs/-/ldapjs-2.3.3.tgz#06c317d3cbb5ac42fbba741e1a8b130ffcf997ab"
|
||||||
|
integrity sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==
|
||||||
|
dependencies:
|
||||||
|
abstract-logging "^2.0.0"
|
||||||
|
asn1 "^0.2.4"
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
backoff "^2.5.0"
|
||||||
|
ldap-filter "^0.3.3"
|
||||||
|
once "^1.4.0"
|
||||||
|
vasync "^2.2.0"
|
||||||
|
verror "^1.8.1"
|
||||||
|
|
||||||
leaflet@^1.8.0:
|
leaflet@^1.8.0:
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.8.0.tgz#4615db4a22a304e8e692cae9270b983b38a2055e"
|
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.8.0.tgz#4615db4a22a304e8e692cae9270b983b38a2055e"
|
||||||
@ -7398,6 +7446,13 @@ merge-descriptors@1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||||
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
|
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
|
||||||
|
|
||||||
|
merge-options@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-2.0.0.tgz#36ca5038badfc3974dbde5e58ba89d3df80882c3"
|
||||||
|
integrity sha512-S7xYIeWHl2ZUKF7SDeBhGg6rfv5bKxVBdk95s/I7wVF8d+hjLSztJ/B271cnUiF6CAFduEQ5Zn3HYwAjT16DlQ==
|
||||||
|
dependencies:
|
||||||
|
is-plain-obj "^2.0.0"
|
||||||
|
|
||||||
merge-stream@^2.0.0:
|
merge-stream@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
@ -8623,6 +8678,11 @@ prebuild-install@^7.0.1, prebuild-install@^7.1.0, prebuild-install@^7.1.1:
|
|||||||
tar-fs "^2.0.0"
|
tar-fs "^2.0.0"
|
||||||
tunnel-agent "^0.6.0"
|
tunnel-agent "^0.6.0"
|
||||||
|
|
||||||
|
precond@0.2:
|
||||||
|
version "0.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac"
|
||||||
|
integrity sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==
|
||||||
|
|
||||||
prelude-ls@~1.1.2:
|
prelude-ls@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||||
@ -10864,6 +10924,13 @@ vary@^1, vary@~1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||||
|
|
||||||
|
vasync@^2.2.0:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vasync/-/vasync-2.2.1.tgz#d881379ff3685e4affa8e775cf0fd369262a201b"
|
||||||
|
integrity sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==
|
||||||
|
dependencies:
|
||||||
|
verror "1.10.0"
|
||||||
|
|
||||||
verror@1.10.0:
|
verror@1.10.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||||
@ -10873,6 +10940,15 @@ verror@1.10.0:
|
|||||||
core-util-is "1.0.2"
|
core-util-is "1.0.2"
|
||||||
extsprintf "^1.2.0"
|
extsprintf "^1.2.0"
|
||||||
|
|
||||||
|
verror@^1.8.1:
|
||||||
|
version "1.10.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb"
|
||||||
|
integrity sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
core-util-is "1.0.2"
|
||||||
|
extsprintf "^1.2.0"
|
||||||
|
|
||||||
vm-browserify@^1.0.1:
|
vm-browserify@^1.0.1:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||||
|
Loading…
Reference in New Issue
Block a user