Skip to content

Commit 4aa34f6

Browse files
committed
WIP: Attempt at adding header auth. Ignore Settings Lissy93#981
1 parent 4813d49 commit 4aa34f6

File tree

6 files changed

+141
-10
lines changed

6 files changed

+141
-10
lines changed

server.js

+10
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const rebuild = require('./services/rebuild-app'); // A script to programmatical
2727
const systemInfo = require('./services/system-info'); // Basic system info, for resource widget
2828
const sslServer = require('./services/ssl-server'); // TLS-enabled web server
2929
const corsProxy = require('./services/cors-proxy'); // Enables API requests to CORS-blocked services
30+
const getUser = require('./services/get-user'); // Enables server side user lookup
3031

3132
/* Helper functions, and default config */
3233
const printMessage = require('./services/print-message'); // Function to print welcome msg on start
@@ -124,6 +125,15 @@ const app = express()
124125
res.end(JSON.stringify({ success: false, message: e }));
125126
}
126127
})
128+
// GET endpoint to return user info
129+
.use(ENDPOINTS.getUser, (req, res) => {
130+
try {
131+
const user = getUser(req);
132+
res.end(JSON.stringify(user));
133+
} catch (e) {
134+
res.end(JSON.stringify({ success: false, message: e }));
135+
}
136+
})
127137
// GET fallback endpoint
128138
.get('*', (req, res) => res.sendFile(path.join(__dirname, 'dist', 'index.html')));
129139

services/get-user.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = (req) => {
2+
const userHeader = "Remote-User";
3+
console.log("Running Server Side", req.headers[userHeader.toLowerCase()]); // eslint-disable-line no-console
4+
return { "success": true, "user": req.headers[userHeader.toLowerCase()] };
5+
};

src/main.js

+17-10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import ErrorReporting from '@/utils/ErrorReporting'; // Error reporting initial
2121
import clickOutside from '@/directives/ClickOutside'; // Directive for closing popups, modals, etc
2222
import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults';
2323
import { initKeycloakAuth, isKeycloakEnabled } from '@/utils/KeycloakAuth';
24+
import { initHeaderAuth, isHeaderAuthEnabled } from '@/utils/HeaderAuth';
2425
import Keys from '@/utils/StoreMutations';
2526

2627
// Initialize global Vue components
@@ -54,18 +55,24 @@ ErrorReporting(Vue, router);
5455
// Render function
5556
const render = (awesome) => awesome(Dashy);
5657

57-
store.dispatch(Keys.INITIALIZE_CONFIG).then((thing) => console.log('main', thing));
58-
5958
// Mount the app, with router, store i18n and render func
6059
const mount = () => new Vue({
6160
store, router, render, i18n,
6261
}).$mount('#app');
6362

64-
// If Keycloak not enabled, then proceed straight to the app
65-
if (!isKeycloakEnabled()) {
66-
mount();
67-
} else { // Keycloak is enabled, redirect to KC login page
68-
initKeycloakAuth()
69-
.then(() => mount())
70-
.catch(() => window.location.reload());
71-
}
63+
store.dispatch(Keys.INITIALIZE_CONFIG).then((thing) => {
64+
console.log('main', thing);
65+
66+
// Keycloak is enabled, redirect to KC login page
67+
if (isKeycloakEnabled()) {
68+
initKeycloakAuth()
69+
.then(() => mount())
70+
.catch(() => window.location.reload());
71+
} else if (isHeaderAuthEnabled()) {
72+
initHeaderAuth()
73+
.then(() => mount())
74+
.catch(() => window.location.reload());
75+
} else { // If Keycloak not enabled, then proceed straight to the app
76+
mount();
77+
}
78+
});

src/utils/ConfigSchema.json

+31
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,37 @@
450450
}
451451
}
452452
},
453+
"enableHeaderAuth": {
454+
"title": "Enable HeaderAuth?",
455+
"type": "boolean",
456+
"default": false,
457+
"description": "If set to true, enable Header Authentication. See appConfig.auth.headerAuth"
458+
},
459+
"headerAuth": {
460+
"type": "object",
461+
"description": "Configuration for headerAuth",
462+
"additionalProperties": false,
463+
"required": [
464+
"proxyWhitelist"
465+
],
466+
"properties": {
467+
"userHeader": {
468+
"title": "User Header",
469+
"type": "string",
470+
"description": "Header name which contains username",
471+
"default": "REMOTE_USER"
472+
},
473+
"proxyWhitelist": {
474+
"title": "Upstream Proxy Auth Trust",
475+
"type": "array",
476+
"description": "Upstream proxy servers to expect authenticated requests from",
477+
"items": {
478+
"type": "string",
479+
"description": "IPs of upstream proxies that will be trusted"
480+
}
481+
}
482+
}
483+
},
453484
"enableKeycloak": {
454485
"title": "Enable Keycloak?",
455486
"type": "boolean",

src/utils/HeaderAuth.js

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import axios from 'axios';
2+
import sha256 from 'crypto-js/sha256';
3+
import ConfigAccumulator from '@/utils/ConfigAccumalator';
4+
import { cookieKeys, localStorageKeys, serviceEndpoints } from '@/utils/defaults';
5+
import { InfoHandler, ErrorHandler, InfoKeys } from '@/utils/ErrorHandler';
6+
import { logout, getUserState } from '@/utils/Auth';
7+
8+
const getAppConfig = () => {
9+
const Accumulator = new ConfigAccumulator();
10+
const config = Accumulator.config();
11+
return config.appConfig || {};
12+
};
13+
14+
class HeaderAuth {
15+
constructor() {
16+
const { auth } = getAppConfig();
17+
const {
18+
userHeader, proxyWhitelist,
19+
} = auth.headerAuth;
20+
this.userHeader = userHeader;
21+
this.proxyWhitelist = proxyWhitelist;
22+
this.users = auth.users;
23+
}
24+
25+
/* eslint-disable class-methods-use-this */
26+
login() {
27+
return new Promise((resolve, reject) => {
28+
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
29+
axios.get(`${baseUrl}${serviceEndpoints.getUser}`).then((response) => {
30+
if (!response.data || response.data.errorMsg) {
31+
reject(response.data.errorMsg || 'Error');
32+
} else {
33+
try {
34+
this.users.forEach((user) => {
35+
if (user.user.toLowerCase() === response.data.user.toLowerCase()) { // User found
36+
const strAndUpper = (input) => input.toString().toUpperCase();
37+
const sha = strAndUpper(sha256(strAndUpper(user.user) + strAndUpper(user.hash)));
38+
document.cookie = `${cookieKeys.AUTH_TOKEN}=${sha};`;
39+
localStorage.setItem(localStorageKeys.USERNAME, user.user);
40+
InfoHandler(`Succesfully signed in as ${response.data.user}`, InfoKeys.AUTH);
41+
console.log('I think we\'re good', getUserState());
42+
resolve(response.data.user);
43+
}
44+
});
45+
} catch (e) {
46+
reject(e);
47+
}
48+
}
49+
});
50+
});
51+
}
52+
53+
logout() {
54+
logout();
55+
}
56+
}
57+
58+
export const isHeaderAuthEnabled = () => {
59+
const { auth } = getAppConfig();
60+
if (!auth) return false;
61+
return auth.enableHeaderAuth || false;
62+
};
63+
64+
let headerAuth;
65+
66+
export const initHeaderAuth = () => {
67+
headerAuth = new HeaderAuth();
68+
return headerAuth.login();
69+
};
70+
71+
// TODO: Find where this is implemented
72+
export const getHeaderAuth = () => {
73+
if (!headerAuth) {
74+
ErrorHandler("HeaderAuth not initialized, can't get instance of class");
75+
}
76+
return headerAuth;
77+
};

src/utils/defaults.js

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module.exports = {
4444
rebuild: '/config-manager/rebuild',
4545
systemInfo: '/system-info',
4646
corsProxy: '/cors-proxy',
47+
getUser: '/get-user',
4748
},
4849
/* List of built-in themes, to be displayed within the theme-switcher dropdown */
4950
builtInThemes: [

0 commit comments

Comments
 (0)