diff --git a/packages/api/env/auth/.env b/packages/api/env/auth/.env index 1737c73e6..9d4d60626 100644 --- a/packages/api/env/auth/.env +++ b/packages/api/env/auth/.env @@ -1,4 +1,5 @@ DEVMODE=1 -OAUTH=http://auth.metrostav.vychozi.cz/auth/realms/metrostav/protocol/openid-connect +OAUTH_AUTH=http://auth.metrostav.vychozi.cz/auth/realms/metrostav/protocol/openid-connect/auth +OAUTH_TOKEN=http://auth.metrostav.vychozi.cz/auth/realms/metrostav/protocol/openid-connect/token OAUTH_CLIENT_ID=dbgate OAUTH_CLIENT_SECRET=ffd5634b-b60a-4c3a-bbec-b4144c73ea2a \ No newline at end of file diff --git a/packages/api/package.json b/packages/api/package.json index 35822cea4..e3dc03f09 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -42,6 +42,7 @@ "is-electron": "^2.2.1", "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.1", + "jsonwebtoken": "^8.5.1", "line-reader": "^0.4.0", "lodash": "^4.17.21", "ncp": "^2.0.0", diff --git a/packages/api/src/controllers/auth.js b/packages/api/src/controllers/auth.js index 27a1c3e15..7235ce123 100644 --- a/packages/api/src/controllers/auth.js +++ b/packages/api/src/controllers/auth.js @@ -1,4 +1,39 @@ const axios = require('axios'); +const jwt = require('jsonwebtoken'); +const getExpressPath = require('../utility/getExpressPath'); +const uuidv1 = require('uuid/v1'); + +const tokenSecret = uuidv1(); + +function shouldAuthorizeApi() { + return !!process.env.OAUTH_AUTH; +} + +function authMiddleware(req, res, next) { + const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', '/stream']; + + if (!shouldAuthorizeApi()) { + return next(); + } + if (SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x))) { + return next(); + } + const authHeader = req.headers.authorization; + if (!authHeader) { + return res.send(401, 'missing authorization header'); + } + const token = authHeader.split(' ')[1]; + try { + const decoded = jwt.verify(token, tokenSecret); + req.user = decoded; + return next(); + } catch (err) { + console.log('&&&&&&&&&&&&&&&&&&&&&& IUNVALID TOKEN'); + console.log(token); + console.log(err); + return res.sendStatus(401).send('Invalid Token'); + } +} module.exports = { oauthToken_meta: true, @@ -6,12 +41,25 @@ module.exports = { const { redirectUri, code } = params; const resp = await axios.default.post( - `${process.env.OAUTH}/token`, + `${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}` ); - return resp.data; + const { access_token, refresh_token } = resp.data; + + const payload = jwt.decode(access_token); + + if (access_token) { + return { + accessToken: jwt.sign({ user: 'oauth' }, tokenSecret, { expiresIn: '1m' }), + }; + } + + return { error: 'Token not found' }; }, + + authMiddleware, + shouldAuthorizeApi, }; diff --git a/packages/api/src/controllers/config.js b/packages/api/src/controllers/config.js index a75fd12bb..8b59609dc 100644 --- a/packages/api/src/controllers/config.js +++ b/packages/api/src/controllers/config.js @@ -40,7 +40,7 @@ module.exports = { isDocker: platformInfo.isDocker, permissions, login, - oauth: process.env.OAUTH, + oauth: process.env.OAUTH_AUTH, ...currentVersion, }; }, diff --git a/packages/api/src/main.js b/packages/api/src/main.js index 9ce31bb68..a89670449 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -54,6 +54,10 @@ function start() { app.use(cors()); + if (auth.shouldAuthorizeApi()) { + app.use(auth.authMiddleware); + } + app.get(getExpressPath('/stream'), async function (req, res) { res.set({ 'Cache-Control': 'no-cache', diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index c3000f83d..ca59767c8 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -20,38 +20,11 @@ import getElectron from './utility/getElectron'; import AppStartInfo from './widgets/AppStartInfo.svelte'; import SettingsListener from './utility/SettingsListener.svelte'; + import { handleAuthOnStartup } from './clientAuth'; let loadedApi = false; let loadedPlugins = false; - async function handleAuth(config) { - if (config.oauth) { - const params = new URLSearchParams(location.search); - const sentCode = params.get('code'); - const sentState = params.get('state'); - if ( - sentCode && - sentState && - sentState.startsWith('dbg-oauth:') && - sentState == sessionStorage.getItem('oauthState') - ) { - const accessToken = await apiCall('auth/oauth-token', { - code: sentCode, - redirectUri: location.origin, - }); - console.log('TOKEN', accessToken); - } else { - const state = `dbg-oauth:${Math.random().toString().substr(2)}`; - sessionStorage.setItem('oauthState', state); - location.replace( - `${config.oauth}/auth?client_id=dbgate&response_type=code&redirect_uri=${encodeURIComponent( - location.origin - )}&state=${encodeURIComponent(state)}` - ); - } - } - } - async function loadApi() { // if (shouldWaitForElectronInitialize()) { // setTimeout(loadApi, 100); @@ -61,10 +34,11 @@ try { // console.log('************** LOADING API'); + const config = await getConfig(); + await handleAuthOnStartup(config); + const connections = await apiCall('connections/list'); const settings = await getSettings(); - const config = await getConfig(); - handleAuth(config); const apps = await getUsedApps(); loadedApi = settings && connections && config && apps; diff --git a/packages/web/src/clientAuth.ts b/packages/web/src/clientAuth.ts new file mode 100644 index 000000000..6024006ad --- /dev/null +++ b/packages/web/src/clientAuth.ts @@ -0,0 +1,46 @@ +import { apiCall } from './utility/api'; +import { getConfig } from './utility/metadataLoaders'; + +export async function handleAuthOnStartup(config) { + console.log('********************* handleAuthOnStartup'); + if (config.oauth) { + const params = new URLSearchParams(location.search); + const sentCode = params.get('code'); + const sentState = params.get('state'); + + if ( + sentCode && + sentState && + sentState.startsWith('dbg-oauth:') && + sentState == sessionStorage.getItem('oauthState') + ) { + const authResp = await apiCall('auth/oauth-token', { + code: sentCode, + redirectUri: location.origin, + }); + const { accessToken } = authResp; + console.log('Got new access token:', accessToken); + localStorage.setItem('accessToken', accessToken); + location.replace('/'); + } else { + if (localStorage.getItem('accessToken')) { + return; + } + + redirectToLogin(config); + } + } +} + +export async function redirectToLogin(config = null) { + if (!config) config = await getConfig(); + + 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 + )}&state=${encodeURIComponent(state)}` + ); +} diff --git a/packages/web/src/main.ts b/packages/web/src/main.ts index bfb1df049..2a72600c5 100644 --- a/packages/web/src/main.ts +++ b/packages/web/src/main.ts @@ -4,22 +4,6 @@ import './utility/changeCurrentDbByTab'; import './commands/stdCommands'; import localStorageGarbageCollector from './utility/localStorageGarbageCollector'; -const params = new URLSearchParams(location.search); -console.log('CODE', params.get('code')); -// console.log( -// `http://auth.metrostav.vychozi.cz/auth/realms/metrostav/protocol/openid-connect/auth?client_id=dbgate&response_type=code&redirect_uri=${encodeURIComponent( -// 'http://localhost:5001/oauth-redirect' -// )}&state=1234` -// ); - -console.log(location); - -// location.replace( -// `http://auth.metrostav.vychozi.cz/auth/realms/metrostav/protocol/openid-connect/auth?client_id=dbgate&response_type=code&redirect_uri=${encodeURIComponent( -// 'http://localhost:5001/' -// )}&state=1234` -// ); - localStorageGarbageCollector(); const app = new App({ diff --git a/packages/web/src/utility/api.ts b/packages/web/src/utility/api.ts index c4f6694b1..54edbeaf7 100644 --- a/packages/web/src/utility/api.ts +++ b/packages/web/src/utility/api.ts @@ -4,10 +4,16 @@ import { writable } from 'svelte/store'; import getElectron from './getElectron'; // import socket from './socket'; import { showSnackbarError } from '../utility/snackbar'; +import { redirectToLogin } from '../clientAuth'; let eventSource; let apiLogging = false; // let cacheCleanerRegistered; +// let apiDisabled = false; + +// export function disableApi() { +// apiDisabled = true; +// } function wantEventSource() { if (!eventSource) { @@ -17,9 +23,9 @@ function wantEventSource() { } function processApiResponse(route, args, resp) { - if (apiLogging) { - console.log('<<< API RESPONSE', route, args, resp); - } + // if (apiLogging) { + // console.log('<<< API RESPONSE', route, args, resp); + // } if (resp?.apiErrorMessage) { showSnackbarError('API error:' + resp?.apiErrorMessage); @@ -35,6 +41,10 @@ export async function apiCall(route: string, args: {} = undefined) { if (apiLogging) { console.log('>>> API CALL', route, args); } + if (apiDisabled) { + console.log('Error, API disabled!!'); + return null; + } const electron = getElectron(); if (electron) { @@ -51,6 +61,11 @@ export async function apiCall(route: string, args: {} = undefined) { body: JSON.stringify(args), }); + if (resp.status == 401) { + // unauthorized + redirectToLogin(); + } + const json = await resp.json(); return processApiResponse(route, args, json); } diff --git a/packages/web/src/utility/resolveApi.ts b/packages/web/src/utility/resolveApi.ts index e15506e6b..f2c00b555 100644 --- a/packages/web/src/utility/resolveApi.ts +++ b/packages/web/src/utility/resolveApi.ts @@ -15,5 +15,10 @@ export default function resolveApi() { export function resolveApiHeaders() { const electron = getElectron(); - return {}; + const res = {}; + const accessToken = localStorage.getItem('accessToken'); + if (accessToken) { + res['Authorization'] = `Bearer ${accessToken}`; + } + return res; }