mirror of
https://github.com/Lissy93/dashy.git
synced 2025-07-26 23:24:38 +02:00
🔀 Merge pull request #397 from leocov-dev/FEATURE/keycloak-user-data
✨ Adds Keycloak group and role based visibility Credit to @leocov-dev Closes #342
This commit is contained in:
commit
d4d8ea5a5f
@ -143,9 +143,25 @@ appConfig:
|
|||||||
realm: 'alicia-homelab'
|
realm: 'alicia-homelab'
|
||||||
clientId: 'dashy'
|
clientId: 'dashy'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 4. Add groups and roles (Optional)
|
||||||
|
Keycloak allows you to assign users roles and groups. You can use these values to configure who can access various sections in Dashy.
|
||||||
|
Keycloak server administration and configuration is a deep topic; please refer to the [server admin guide](https://www.keycloak.org/docs/latest/server_admin/index.html#assigning-permissions-and-access-using-roles-and-groups) to see details about creating and assigning roles and groups.
|
||||||
|
Once you have groups or roles assigned to users you can configure access under each sections `displayData.showForKeycloakUser` and `displayData.hideForKeycloakUser`.
|
||||||
|
Both show and hide configurations accept a list of `groups` and `roles` that limit access. If a users data matches one or more items in these lists they will be allowed or excluded as defined.
|
||||||
|
```yaml
|
||||||
|
sections:
|
||||||
|
- name: DeveloperResources
|
||||||
|
displayData:
|
||||||
|
showForKeycloakUsers:
|
||||||
|
roles: ['canViewDevResources']
|
||||||
|
hideForKeycloakUsers:
|
||||||
|
groups: ['ProductTeam']
|
||||||
|
```
|
||||||
|
|
||||||
Your app is now secured :) When you load Dashy, it will redirect to your Keycloak login page, and any user without valid credentials will be prevented from accessing your dashboard.
|
Your app is now secured :) When you load Dashy, it will redirect to your Keycloak login page, and any user without valid credentials will be prevented from accessing your dashboard.
|
||||||
|
|
||||||
From within the Keycloak console, you can then configure things like user permissions, time outs, password policies, access, etc. You can also backup your full Keycloak config, and it is recommended to do this, along with your Dashy config. You can spin up both Dashy and Keycloak simultaneously and restore both applications configs using a `docker-compose.yml` file, and this is recommended.
|
From within the Keycloak console, you can then configure things like time-outs, password policies, etc. You can also backup your full Keycloak config, and it is recommended to do this, along with your Dashy config. You can spin up both Dashy and Keycloak simultaneously and restore both applications configs using a `docker-compose.yml` file, and this is recommended.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -228,6 +228,8 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
|
|||||||
**`hideForUsers`** | `string[]` | _Optional_ | Current section will be visible to all users, except for those specified in this list
|
**`hideForUsers`** | `string[]` | _Optional_ | Current section will be visible to all users, except for those specified in this list
|
||||||
**`showForUsers`** | `string[]` | _Optional_ | Current section will be hidden from all users, except for those specified in this list
|
**`showForUsers`** | `string[]` | _Optional_ | Current section will be hidden from all users, except for those specified in this list
|
||||||
**`hideForGuests`** | `boolean` | _Optional_ | Current section will be visible for logged in users, but not for guests (see `appConfig.enableGuestAccess`). Defaults to `false`
|
**`hideForGuests`** | `boolean` | _Optional_ | Current section will be visible for logged in users, but not for guests (see `appConfig.enableGuestAccess`). Defaults to `false`
|
||||||
|
**`hideForKeycloakUsers`** | `object` | _Optional_ | Current section will be visible to all keycloak users, except for those configured via these groups and roles. See `hideForKeycloakUsers`
|
||||||
|
**`showForKeycloakUsers`** | `object` | _Optional_ | Current section will be hidden from all keyclaok users, except for those configured via these groups and roles. See `showForKeycloakUsers`
|
||||||
|
|
||||||
**[⬆️ Back to Top](#configuring)**
|
**[⬆️ Back to Top](#configuring)**
|
||||||
|
|
||||||
@ -239,6 +241,15 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
|
|||||||
|
|
||||||
**[⬆️ Back to Top](#configuring)**
|
**[⬆️ Back to Top](#configuring)**
|
||||||
|
|
||||||
|
### `section.displayData.hideForKeycloakUsers` and `section.displayData.showForKeycloakUsers`
|
||||||
|
|
||||||
|
**Field** | **Type** | **Required**| **Description**
|
||||||
|
--- |------------| --- | ---
|
||||||
|
**`groups`** | `string[]` | _Optional_ | Current Section will be hidden or shown based on the user having any of the groups in this list
|
||||||
|
**`roles`** | `string[]` | _Optional_ | Current Section will be hidden or shown based on the user having any of the roles in this list
|
||||||
|
|
||||||
|
**[⬆️ Back to Top](#configuring)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
@ -285,6 +285,7 @@ Styleguides:
|
|||||||
│ ├── InitServiceWorker.js # Initializes and manages service worker, if enabled
|
│ ├── InitServiceWorker.js # Initializes and manages service worker, if enabled
|
||||||
│ ├── Search.js # Helper functions for searching/ filtering items in all views
|
│ ├── Search.js # Helper functions for searching/ filtering items in all views
|
||||||
│ ├── JsonToYaml.js # Function that parses and converts raw JSON into valid YAML
|
│ ├── JsonToYaml.js # Function that parses and converts raw JSON into valid YAML
|
||||||
|
│ ├── KeycloakAuth.js # Singleton class to manage Keycloak authentication
|
||||||
│ ├── languages.js # Handles fetching, switching and validating languages
|
│ ├── languages.js # Handles fetching, switching and validating languages
|
||||||
│ ╰── ThemeHelper.js # Function that handles the fetching and setting of user themes
|
│ ╰── ThemeHelper.js # Function that handles the fetching and setting of user themes
|
||||||
╰── views # Directory of available pages, corresponding to available routes
|
╰── views # Directory of available pages, corresponding to available routes
|
||||||
|
@ -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
src/main.js
20
src/main.js
@ -2,7 +2,6 @@
|
|||||||
// Import core framework and essential utils
|
// Import core framework and essential utils
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import VueI18n from 'vue-i18n'; // i18n for localization
|
import VueI18n from 'vue-i18n'; // i18n for localization
|
||||||
import Keycloak from 'keycloak-js';
|
|
||||||
|
|
||||||
// Import component Vue plugins, used throughout the app
|
// Import component Vue plugins, used throughout the app
|
||||||
import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component
|
import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component
|
||||||
@ -21,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, getKeycloakConfig } 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);
|
||||||
@ -63,18 +62,7 @@ const mount = () => new Vue({
|
|||||||
if (!isKeycloakEnabled()) {
|
if (!isKeycloakEnabled()) {
|
||||||
mount();
|
mount();
|
||||||
} else { // Keycloak is enabled, redirect to KC login page
|
} else { // Keycloak is enabled, redirect to KC login page
|
||||||
const { serverUrl, realm, clientId } = getKeycloakConfig();
|
initKeycloakAuth()
|
||||||
const initOptions = {
|
.then(() => mount())
|
||||||
url: `${serverUrl}/auth`, realm, clientId, onLoad: 'login-required',
|
.catch(() => window.location.reload());
|
||||||
};
|
|
||||||
const keycloak = Keycloak(initOptions);
|
|
||||||
keycloak.init({ onLoad: initOptions.onLoad }).then((auth) => {
|
|
||||||
if (!auth) {
|
|
||||||
// Not authenticated, reload to Keycloak login page
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
// Yay - user successfully authenticated with Keycloak, render the app!
|
|
||||||
mount();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import sha256 from 'crypto-js/sha256';
|
|||||||
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 = () => {
|
||||||
@ -19,26 +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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* 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();
|
||||||
@ -65,7 +46,6 @@ const generateUserToken = (user) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the user is currently authenticated
|
* Checks if the user is currently authenticated
|
||||||
* @param {Array[Object]} users An array of user objects pulled from the config
|
|
||||||
* @returns {Boolean} Will return true if the user is logged in, else false
|
* @returns {Boolean} Will return true if the user is logged in, else false
|
||||||
*/
|
*/
|
||||||
export const isLoggedIn = () => {
|
export const isLoggedIn = () => {
|
||||||
@ -95,7 +75,7 @@ export const isAuthEnabled = () => {
|
|||||||
/* Returns true if guest access is enabled */
|
/* Returns true if guest access is enabled */
|
||||||
export const isGuestAccessEnabled = () => {
|
export const isGuestAccessEnabled = () => {
|
||||||
const appConfig = getAppConfig();
|
const appConfig = getAppConfig();
|
||||||
if (appConfig.auth && typeof appConfig.auth === 'object') {
|
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled()) {
|
||||||
return appConfig.auth.enableGuestAccess || false;
|
return appConfig.auth.enableGuestAccess || false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -108,6 +88,7 @@ export const isGuestAccessEnabled = () => {
|
|||||||
* @param {String} username The username entered by the user
|
* @param {String} username The username entered by the user
|
||||||
* @param {String} pass The password entered by the user
|
* @param {String} pass The password entered by the user
|
||||||
* @param {String[]} users An array of valid user objects
|
* @param {String[]} users An array of valid user objects
|
||||||
|
* @param {Object} messages A static message template object
|
||||||
* @returns {Object} An object containing a boolean result and a message
|
* @returns {Object} An object containing a boolean result and a message
|
||||||
*/
|
*/
|
||||||
export const checkCredentials = (username, pass, users, messages) => {
|
export const checkCredentials = (username, pass, users, messages) => {
|
||||||
@ -146,7 +127,7 @@ export const login = (username, pass, timeout) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removed the browsers cookie, causing user to be logged out
|
* Removed the browsers' cookie, causing user to be logged out
|
||||||
*/
|
*/
|
||||||
export const logout = () => {
|
export const logout = () => {
|
||||||
document.cookie = 'authenticationToken=null';
|
document.cookie = 'authenticationToken=null';
|
||||||
@ -164,7 +145,7 @@ export const getCurrentUser = () => {
|
|||||||
if (!username) return false; // No username
|
if (!username) return false; // No username
|
||||||
let foundUserObject = false; // Value to return
|
let foundUserObject = false; // Value to return
|
||||||
getUsers().forEach((user) => {
|
getUsers().forEach((user) => {
|
||||||
// If current logged in user found, then return that user
|
// If current logged-in user found, then return that user
|
||||||
if (user.user === username) foundUserObject = user;
|
if (user.user === username) foundUserObject = user;
|
||||||
});
|
});
|
||||||
return foundUserObject;
|
return foundUserObject;
|
||||||
@ -186,7 +167,6 @@ export const isLoggedInAsGuest = () => {
|
|||||||
* But if auth is configured, then will verify user is correctly
|
* But if auth is configured, then will verify user is correctly
|
||||||
* logged in and then check weather they are of type admin, and
|
* logged in and then check weather they are of type admin, and
|
||||||
* return false if any conditions fail
|
* return false if any conditions fail
|
||||||
* @param {String[]} - Array of users
|
|
||||||
* @returns {Boolean} - True if admin privileges
|
* @returns {Boolean} - True if admin privileges
|
||||||
*/
|
*/
|
||||||
export const isUserAdmin = () => {
|
export const isUserAdmin = () => {
|
||||||
@ -212,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
|
||||||
|
@ -6,24 +6,32 @@
|
|||||||
|
|
||||||
// Import helper functions from auth, to get current user, and check if guest
|
// Import helper functions from auth, to get current user, and check if guest
|
||||||
import { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth';
|
import { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth';
|
||||||
|
import { localStorageKeys } from '@/utils/defaults';
|
||||||
|
|
||||||
/* Helper function, checks if a given username appears in a user array */
|
/* Helper function, checks if a given testValue is found in the visibility list */
|
||||||
const determineVisibility = (visibilityList, cUsername) => {
|
const determineVisibility = (visibilityList, testValue) => {
|
||||||
let isFound = false;
|
let isFound = false;
|
||||||
visibilityList.forEach((userInList) => {
|
visibilityList.forEach((visibilityItem) => {
|
||||||
if (userInList.toLowerCase() === cUsername) isFound = true;
|
if (visibilityItem.toLowerCase() === testValue.toLowerCase()) isFound = true;
|
||||||
});
|
});
|
||||||
return isFound;
|
return isFound;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Helper function, determines if two arrays have any intersecting elements
|
||||||
|
(one or more items that are the same) */
|
||||||
|
const determineIntersection = (source = [], target = []) => {
|
||||||
|
const intersections = source.filter(item => target.indexOf(item) !== -1);
|
||||||
|
return intersections.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
/* Returns false if this section should not be rendered for the current user/ guest */
|
/* Returns false if this section should not be rendered for the current user/ guest */
|
||||||
const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
|
const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
|
||||||
// Checks if user explicitly has access to a certain section
|
// Checks if user explicitly has access to a certain section
|
||||||
const checkVisiblity = () => {
|
const checkVisibility = () => {
|
||||||
if (!currentUser) return true;
|
if (!currentUser) return true;
|
||||||
const hideFor = displayData.hideForUsers || [];
|
const hideForUsers = displayData.hideForUsers || [];
|
||||||
const cUsername = currentUser.user.toLowerCase();
|
const cUsername = currentUser.user.toLowerCase();
|
||||||
return !determineVisibility(hideFor, cUsername);
|
return !determineVisibility(hideForUsers, cUsername);
|
||||||
};
|
};
|
||||||
// Checks if user is explicitly prevented from viewing a certain section
|
// Checks if user is explicitly prevented from viewing a certain section
|
||||||
const checkHiddenability = () => {
|
const checkHiddenability = () => {
|
||||||
@ -33,12 +41,36 @@ const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
|
|||||||
if (showForUsers.length < 1) return true;
|
if (showForUsers.length < 1) return true;
|
||||||
return determineVisibility(showForUsers, cUsername);
|
return determineVisibility(showForUsers, cUsername);
|
||||||
};
|
};
|
||||||
|
const checkKeycloakVisibility = () => {
|
||||||
|
if (!displayData.hideForKeycloakUsers) return true;
|
||||||
|
|
||||||
|
const { groups, roles } = JSON.parse(localStorage.getItem(localStorageKeys.KEYCLOAK_INFO) || '{}');
|
||||||
|
const hideForGroups = displayData.hideForKeycloakUsers.groups || [];
|
||||||
|
const hideForRoles = displayData.hideForKeycloakUsers.roles || [];
|
||||||
|
|
||||||
|
return !(determineIntersection(hideForRoles, roles)
|
||||||
|
|| determineIntersection(hideForGroups, groups));
|
||||||
|
};
|
||||||
|
const checkKeycloakHiddenability = () => {
|
||||||
|
if (!displayData.showForKeycloakUsers) return true;
|
||||||
|
|
||||||
|
const { groups, roles } = JSON.parse(localStorage.getItem(localStorageKeys.KEYCLOAK_INFO) || '{}');
|
||||||
|
const showForGroups = displayData.showForKeycloakUsers.groups || [];
|
||||||
|
const showForRoles = displayData.showForKeycloakUsers.roles || [];
|
||||||
|
|
||||||
|
return determineIntersection(showForRoles, roles)
|
||||||
|
|| determineIntersection(showForGroups, groups);
|
||||||
|
};
|
||||||
// Checks if the current user is a guest, and if section allows for guests
|
// Checks if the current user is a guest, and if section allows for guests
|
||||||
const checkIfHideForGuest = () => {
|
const checkIfHideForGuest = () => {
|
||||||
const hideForGuest = displayData.hideForGuests;
|
const hideForGuest = displayData.hideForGuests;
|
||||||
return !(hideForGuest && isGuest);
|
return !(hideForGuest && isGuest);
|
||||||
};
|
};
|
||||||
return checkVisiblity() && checkHiddenability() && checkIfHideForGuest();
|
return checkVisibility()
|
||||||
|
&& checkHiddenability()
|
||||||
|
&& checkIfHideForGuest()
|
||||||
|
&& checkKeycloakVisibility()
|
||||||
|
&& checkKeycloakHiddenability();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Putting it all together, the function to export */
|
/* Putting it all together, the function to export */
|
||||||
|
@ -613,6 +613,60 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false,
|
"default": false,
|
||||||
"description": "If set to true, section will be visible for logged in users, but not for guests"
|
"description": "If set to true, section will be visible for logged in users, but not for guests"
|
||||||
|
},
|
||||||
|
"showForKeycloakUsers": {
|
||||||
|
"title": "Show for select Keycloak groups or roles",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Configure the Keycloak groups or roles that will have access to this section",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"groups": {
|
||||||
|
"title": "Show for Groups",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Section will be hidden from all users except those with one or more of these groups",
|
||||||
|
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the group that will be able to view this section"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"title": "Show for Roles",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Section will be hidden from all users except those with one or more of these roles",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the role that will be able to view this section"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hideForKeycloakUsers": {
|
||||||
|
"title": "Hide for select Keycloak groups or roles",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Configure the Keycloak groups or roles that will not have access to this section",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"groups": {
|
||||||
|
"title": "Hide for Groups",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Section will be hidden from users with any of these groups",
|
||||||
|
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the group that will not be able to view this section"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"title": "Hide for Roles",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Section will be hidden from users with any of roles",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the role that will not be able to view this section"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
92
src/utils/KeycloakAuth.js
Normal file
92
src/utils/KeycloakAuth.js
Normal file
@ -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;
|
||||||
|
};
|
@ -124,6 +124,7 @@ module.exports = {
|
|||||||
USERNAME: 'username',
|
USERNAME: 'username',
|
||||||
MOST_USED: 'mostUsed',
|
MOST_USED: 'mostUsed',
|
||||||
LAST_USED: 'lastUsed',
|
LAST_USED: 'lastUsed',
|
||||||
|
KEYCLOAK_INFO: 'keycloakInfo',
|
||||||
},
|
},
|
||||||
/* Key names for cookie identifiers */
|
/* Key names for cookie identifiers */
|
||||||
cookieKeys: {
|
cookieKeys: {
|
||||||
@ -278,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…
x
Reference in New Issue
Block a user