mirror of https://github.com/lissy93/dashy
❇️ added keycloak logout button
This commit is contained in:
parent
da98ea5cf2
commit
dd30099ad8
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- If auth configured, show status text -->
|
<!-- If auth configured, show status text -->
|
||||||
<span class="user-type-note">{{ makeText() }}</span>
|
<span class="user-type-note">{{ makeUserGreeting() }}</span>
|
||||||
<div class="display-options">
|
<div class="display-options">
|
||||||
<!-- If user logged in, show logout button -->
|
<!-- If user logged in, show logout button -->
|
||||||
<IconLogout
|
<IconLogout
|
||||||
|
@ -17,6 +17,13 @@
|
||||||
v-tooltip="tooltip($t('settings.sign-in-tooltip'))"
|
v-tooltip="tooltip($t('settings.sign-in-tooltip'))"
|
||||||
class="layout-icon" tabindex="-2"
|
class="layout-icon" tabindex="-2"
|
||||||
/>
|
/>
|
||||||
|
<!-- If user logged in via keycloak, show keycloak logout button -->
|
||||||
|
<IconLogout
|
||||||
|
v-if="userType == userStateEnum.keycloakEnabled"
|
||||||
|
@click="keycloakLogout()"
|
||||||
|
v-tooltip="tooltip($t('settings.sign-out-tooltip'))"
|
||||||
|
class="layout-icon" tabindex="-2"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -24,6 +31,7 @@
|
||||||
<script>
|
<script>
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import { logout as registerLogout } from '@/utils/Auth';
|
import { logout as registerLogout } from '@/utils/Auth';
|
||||||
|
import { getKeycloakAuth } from '@/utils/KeycloakAuth';
|
||||||
import { localStorageKeys, userStateEnum } from '@/utils/defaults';
|
import { localStorageKeys, userStateEnum } from '@/utils/defaults';
|
||||||
import IconLogout from '@/assets/interface-icons/user-logout.svg';
|
import IconLogout from '@/assets/interface-icons/user-logout.svg';
|
||||||
|
|
||||||
|
@ -48,14 +56,22 @@ export default {
|
||||||
router.push({ path: '/login' });
|
router.push({ path: '/login' });
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
|
keycloakLogout() {
|
||||||
|
const keycloak = getKeycloakAuth();
|
||||||
|
this.$toasted.show(this.$t('login.logout-message'));
|
||||||
|
setTimeout(() => {
|
||||||
|
keycloak.logout();
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
goToLogin() {
|
goToLogin() {
|
||||||
router.push({ path: '/login' });
|
router.push({ path: '/login' });
|
||||||
},
|
},
|
||||||
tooltip(content) {
|
tooltip(content) {
|
||||||
return { content, trigger: 'hover focus', delay: 250 };
|
return { content, trigger: 'hover focus', delay: 250 };
|
||||||
},
|
},
|
||||||
makeText() {
|
makeUserGreeting() {
|
||||||
if (this.userType === userStateEnum.loggedIn) {
|
if (this.userType === userStateEnum.loggedIn
|
||||||
|
|| this.userType === userStateEnum.keycloakEnabled) {
|
||||||
const username = localStorage[localStorageKeys.USERNAME];
|
const username = localStorage[localStorageKeys.USERNAME];
|
||||||
return username ? this.$t('settings.sign-in-welcome', { username }) : '';
|
return username ? this.$t('settings.sign-in-welcome', { username }) : '';
|
||||||
}
|
}
|
||||||
|
@ -73,7 +89,6 @@ export default {
|
||||||
|
|
||||||
span.user-type-note {
|
span.user-type-note {
|
||||||
color: var(--settings-text-color);
|
color: var(--settings-text-color);
|
||||||
text-transform: capitalize;
|
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<LayoutSelector :displayLayout="displayLayout" />
|
<LayoutSelector :displayLayout="displayLayout" />
|
||||||
<ItemSizeSelector :iconSize="iconSize" />
|
<ItemSizeSelector :iconSize="iconSize" />
|
||||||
<ConfigLauncher />
|
<ConfigLauncher />
|
||||||
<AuthButtons v-if="userState != 'noone'" :userType="userState" />
|
<AuthButtons v-if="userState !== 0" :userType="userState" />
|
||||||
</div>
|
</div>
|
||||||
<div :class="`show-hide-container ${settingsVisible? 'hide-btn' : 'show-btn'}`">
|
<div :class="`show-hide-container ${settingsVisible? 'hide-btn' : 'show-btn'}`">
|
||||||
<button @click="toggleSettingsVisibility()"
|
<button @click="toggleSettingsVisibility()"
|
||||||
|
@ -80,7 +80,7 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Determines which button should display, based on the user type
|
* Determines which button should display, based on the user type
|
||||||
* 0 = Auth not configured, don't show anything
|
* 0 = Auth not configured, don't show anything
|
||||||
* 1 = Auth condifured, and user logged in, show logout button
|
* 1 = Auth configured, and user logged in, show logout button
|
||||||
* 2 = Auth configured, guest access enabled, and not logged in, show login
|
* 2 = Auth configured, guest access enabled, and not logged in, show login
|
||||||
* Note that if auth is enabled, but not guest access, and user not logged in,
|
* Note that if auth is enabled, but not guest access, and user not logged in,
|
||||||
* then they will never be able to view the homepage, so no button needed
|
* then they will never be able to view the homepage, so no button needed
|
||||||
|
|
|
@ -20,7 +20,7 @@ import clickOutside from '@/utils/ClickOutside'; // Directive for closing p
|
||||||
import { messages } from '@/utils/languages'; // Language texts
|
import { messages } from '@/utils/languages'; // Language texts
|
||||||
import ErrorReporting from '@/utils/ErrorReporting'; // Error reporting initializer (off)
|
import ErrorReporting from '@/utils/ErrorReporting'; // Error reporting initializer (off)
|
||||||
import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults';
|
import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults';
|
||||||
import { isKeycloakEnabled, cleanupKeycloakInfo, initKeycloak } from '@/utils/Auth'; // Keycloak auth config
|
import { initKeycloakAuth, isKeycloakEnabled } from '@/utils/KeycloakAuth';
|
||||||
|
|
||||||
// Initialize global Vue components
|
// Initialize global Vue components
|
||||||
Vue.use(VueI18n);
|
Vue.use(VueI18n);
|
||||||
|
@ -58,13 +58,11 @@ const mount = () => new Vue({
|
||||||
store, router, render, i18n,
|
store, router, render, i18n,
|
||||||
}).$mount('#app');
|
}).$mount('#app');
|
||||||
|
|
||||||
// every page reload removes keycloak user data
|
|
||||||
cleanupKeycloakInfo();
|
|
||||||
// If Keycloak not enabled, then proceed straight to the app
|
// If Keycloak not enabled, then proceed straight to the app
|
||||||
if (!isKeycloakEnabled()) {
|
if (!isKeycloakEnabled()) {
|
||||||
mount();
|
mount();
|
||||||
} else { // Keycloak is enabled, redirect to KC login page
|
} else { // Keycloak is enabled, redirect to KC login page
|
||||||
initKeycloak()
|
initKeycloakAuth()
|
||||||
.then(() => mount())
|
.then(() => mount())
|
||||||
.catch(() => window.location.reload());
|
.catch(() => window.location.reload());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import sha256 from 'crypto-js/sha256';
|
import sha256 from 'crypto-js/sha256';
|
||||||
import Keycloak from 'keycloak-js';
|
|
||||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||||
import ErrorHandler from '@/utils/ErrorHandler';
|
import ErrorHandler from '@/utils/ErrorHandler';
|
||||||
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
|
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
|
||||||
|
import { isKeycloakEnabled } from '@/utils/KeycloakAuth';
|
||||||
|
|
||||||
/* Uses config accumulator to get and return app config */
|
/* Uses config accumulator to get and return app config */
|
||||||
const getAppConfig = () => {
|
const getAppConfig = () => {
|
||||||
|
@ -20,82 +20,6 @@ const printWarning = () => {
|
||||||
ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.');
|
ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.');
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Returns true if keycloak is enabled */
|
|
||||||
export const isKeycloakEnabled = () => {
|
|
||||||
const appConfig = getAppConfig();
|
|
||||||
if (!appConfig.auth) return false;
|
|
||||||
return appConfig.auth.enableKeycloak || false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Returns the users keycloak config */
|
|
||||||
export const getKeycloakConfig = () => {
|
|
||||||
const appConfig = getAppConfig();
|
|
||||||
if (!isKeycloakEnabled()) return false;
|
|
||||||
const { keycloak } = appConfig.auth;
|
|
||||||
const { serverUrl, realm, clientId } = keycloak;
|
|
||||||
if (!serverUrl || !realm || !clientId) {
|
|
||||||
ErrorHandler('Keycloak config missing- please ensure you specify: serverUrl, realm, clientId');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return keycloak;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper that persists keycloak user data in browser local storage
|
|
||||||
* @param {Keycloak.KeycloakInstance} keycloak The username of user
|
|
||||||
*/
|
|
||||||
const storeKeycloakInfo = (keycloak) => {
|
|
||||||
if (keycloak.tokenParsed && typeof keycloak.tokenParsed === 'object') {
|
|
||||||
const {
|
|
||||||
groups,
|
|
||||||
realm_access: realmAccess,
|
|
||||||
resource_access: resourceAccess,
|
|
||||||
azp: clientId,
|
|
||||||
} = keycloak.tokenParsed;
|
|
||||||
|
|
||||||
const realmRoles = realmAccess.roles || [];
|
|
||||||
|
|
||||||
let clientRoles = [];
|
|
||||||
if (Object.hasOwn(resourceAccess, clientId)) {
|
|
||||||
clientRoles = resourceAccess[clientId].roles || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const roles = [...realmRoles, ...clientRoles];
|
|
||||||
|
|
||||||
const info = {
|
|
||||||
groups,
|
|
||||||
roles,
|
|
||||||
};
|
|
||||||
|
|
||||||
localStorage.setItem(localStorageKeys.KEYCLOAK_INFO, JSON.stringify(info));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* remove keycloak local storage */
|
|
||||||
export const cleanupKeycloakInfo = () => localStorage.removeItem(localStorageKeys.KEYCLOAK_INFO);
|
|
||||||
|
|
||||||
/* starts the keycloak login process and gathers user data */
|
|
||||||
export const initKeycloak = () => {
|
|
||||||
const { serverUrl, realm, clientId } = getKeycloakConfig();
|
|
||||||
const initOptions = {
|
|
||||||
url: `${serverUrl}/auth`, realm, clientId, onLoad: 'login-required',
|
|
||||||
};
|
|
||||||
const keycloak = Keycloak(initOptions);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
keycloak.init({ onLoad: initOptions.onLoad })
|
|
||||||
.then((auth) => {
|
|
||||||
if (auth) {
|
|
||||||
storeKeycloakInfo(keycloak);
|
|
||||||
return resolve();
|
|
||||||
} else {
|
|
||||||
return reject(new Error('Not authenticated'));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((reason) => reject(reason));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Returns array of users from appConfig.auth, if available, else an empty array */
|
/* Returns array of users from appConfig.auth, if available, else an empty array */
|
||||||
const getUsers = () => {
|
const getUsers = () => {
|
||||||
const appConfig = getAppConfig();
|
const appConfig = getAppConfig();
|
||||||
|
@ -268,7 +192,13 @@ export const isUserAdmin = () => {
|
||||||
* then they will never be able to view the homepage, so no button needed
|
* then they will never be able to view the homepage, so no button needed
|
||||||
*/
|
*/
|
||||||
export const getUserState = () => {
|
export const getUserState = () => {
|
||||||
const { notConfigured, loggedIn, guestAccess } = userStateEnum; // Numeric enum options
|
const {
|
||||||
|
notConfigured,
|
||||||
|
loggedIn,
|
||||||
|
guestAccess,
|
||||||
|
keycloakEnabled,
|
||||||
|
} = userStateEnum; // Numeric enum options
|
||||||
|
if (isKeycloakEnabled()) return keycloakEnabled; // Keycloak auth configured
|
||||||
if (!isAuthEnabled()) return notConfigured; // No auth enabled
|
if (!isAuthEnabled()) return notConfigured; // No auth enabled
|
||||||
if (isLoggedIn()) return loggedIn; // User is logged in
|
if (isLoggedIn()) return loggedIn; // User is logged in
|
||||||
if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing
|
if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
import Keycloak from 'keycloak-js';
|
||||||
|
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||||
|
import { localStorageKeys } from '@/utils/defaults';
|
||||||
|
import ErrorHandler from '@/utils/ErrorHandler';
|
||||||
|
|
||||||
|
const getAppConfig = () => {
|
||||||
|
const Accumulator = new ConfigAccumulator();
|
||||||
|
const config = Accumulator.config();
|
||||||
|
return config.appConfig || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class KeycloakAuth {
|
||||||
|
constructor() {
|
||||||
|
const { auth } = getAppConfig();
|
||||||
|
const { serverUrl, realm, clientId } = auth.keycloak;
|
||||||
|
const initOptions = {
|
||||||
|
url: `${serverUrl}/auth`, realm, clientId, onLoad: 'login-required',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.keycloakClient = Keycloak(initOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.keycloakClient.init({ onLoad: 'login-required' })
|
||||||
|
.then((auth) => {
|
||||||
|
if (auth) {
|
||||||
|
this.storeKeycloakInfo();
|
||||||
|
return resolve();
|
||||||
|
} else {
|
||||||
|
return reject(new Error('Not authenticated'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((reason) => reject(reason));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
localStorage.removeItem(localStorageKeys.USERNAME);
|
||||||
|
localStorage.removeItem(localStorageKeys.KEYCLOAK_INFO);
|
||||||
|
this.keycloakClient.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
storeKeycloakInfo() {
|
||||||
|
if (this.keycloakClient.tokenParsed && typeof this.keycloakClient.tokenParsed === 'object') {
|
||||||
|
const {
|
||||||
|
groups,
|
||||||
|
realm_access: realmAccess,
|
||||||
|
resource_access: resourceAccess,
|
||||||
|
azp: clientId,
|
||||||
|
preferred_username: preferredUsername,
|
||||||
|
} = this.keycloakClient.tokenParsed;
|
||||||
|
|
||||||
|
const realmRoles = realmAccess.roles || [];
|
||||||
|
|
||||||
|
let clientRoles = [];
|
||||||
|
if (Object.hasOwn(resourceAccess, clientId)) {
|
||||||
|
clientRoles = resourceAccess[clientId].roles || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles = [...realmRoles, ...clientRoles];
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
groups,
|
||||||
|
roles,
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem(localStorageKeys.KEYCLOAK_INFO, JSON.stringify(info));
|
||||||
|
localStorage.setItem(localStorageKeys.USERNAME, preferredUsername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isKeycloakEnabled = () => {
|
||||||
|
const { auth } = getAppConfig();
|
||||||
|
if (!auth) return false;
|
||||||
|
return auth.enableKeycloak || false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let keycloak;
|
||||||
|
|
||||||
|
export const initKeycloakAuth = () => {
|
||||||
|
keycloak = new KeycloakAuth();
|
||||||
|
return keycloak.login();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getKeycloakAuth = () => {
|
||||||
|
if (!keycloak) {
|
||||||
|
ErrorHandler("Keycloak not initialized, can't get instance of class");
|
||||||
|
}
|
||||||
|
return keycloak;
|
||||||
|
};
|
|
@ -279,6 +279,7 @@ module.exports = {
|
||||||
loggedIn: 1,
|
loggedIn: 1,
|
||||||
guestAccess: 2,
|
guestAccess: 2,
|
||||||
notLoggedIn: 3,
|
notLoggedIn: 3,
|
||||||
|
keycloakEnabled: 4,
|
||||||
},
|
},
|
||||||
/* Progressive Web App settings, used by Vue Config */
|
/* Progressive Web App settings, used by Vue Config */
|
||||||
pwa: {
|
pwa: {
|
||||||
|
|
Loading…
Reference in New Issue