Merge branch 'master' of github.com:Lissy93/dashy into FEATURE/more-widgets
This commit is contained in:
@@ -2,6 +2,7 @@ import sha256 from 'crypto-js/sha256';
|
||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
|
||||
import { isKeycloakEnabled } from '@/utils/KeycloakAuth';
|
||||
|
||||
/* Uses config accumulator to get and return app config */
|
||||
const getAppConfig = () => {
|
||||
@@ -19,26 +20,6 @@ const printWarning = () => {
|
||||
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 */
|
||||
const getUsers = () => {
|
||||
const appConfig = getAppConfig();
|
||||
@@ -65,7 +46,6 @@ const generateUserToken = (user) => {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export const isLoggedIn = () => {
|
||||
@@ -95,7 +75,7 @@ export const isAuthEnabled = () => {
|
||||
/* Returns true if guest access is enabled */
|
||||
export const isGuestAccessEnabled = () => {
|
||||
const appConfig = getAppConfig();
|
||||
if (appConfig.auth && typeof appConfig.auth === 'object') {
|
||||
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled()) {
|
||||
return appConfig.auth.enableGuestAccess || false;
|
||||
}
|
||||
return false;
|
||||
@@ -108,6 +88,7 @@ export const isGuestAccessEnabled = () => {
|
||||
* @param {String} username The username entered by the user
|
||||
* @param {String} pass The password entered by the user
|
||||
* @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
|
||||
*/
|
||||
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 = () => {
|
||||
document.cookie = 'authenticationToken=null';
|
||||
@@ -164,7 +145,7 @@ export const getCurrentUser = () => {
|
||||
if (!username) return false; // No username
|
||||
let foundUserObject = false; // Value to return
|
||||
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;
|
||||
});
|
||||
return foundUserObject;
|
||||
@@ -182,11 +163,10 @@ export const isLoggedInAsGuest = () => {
|
||||
|
||||
/**
|
||||
* Checks if the current user has admin privileges.
|
||||
* If no users are setup, then function will always return true
|
||||
* If no users are set up, then function will always return true
|
||||
* But if auth is configured, then will verify user is correctly
|
||||
* logged in and then check weather they are of type admin, and
|
||||
* return false if any conditions fail
|
||||
* @param {String[]} - Array of users
|
||||
* @returns {Boolean} - True if admin privileges
|
||||
*/
|
||||
export const isUserAdmin = () => {
|
||||
@@ -212,7 +192,13 @@ export const isUserAdmin = () => {
|
||||
* then they will never be able to view the homepage, so no button needed
|
||||
*/
|
||||
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 (isLoggedIn()) return loggedIn; // User is logged in
|
||||
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 { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth';
|
||||
import { localStorageKeys } from '@/utils/defaults';
|
||||
|
||||
/* Helper function, checks if a given username appears in a user array */
|
||||
const determineVisibility = (visibilityList, cUsername) => {
|
||||
/* Helper function, checks if a given testValue is found in the visibility list */
|
||||
const determineVisibility = (visibilityList, testValue) => {
|
||||
let isFound = false;
|
||||
visibilityList.forEach((userInList) => {
|
||||
if (userInList.toLowerCase() === cUsername) isFound = true;
|
||||
visibilityList.forEach((visibilityItem) => {
|
||||
if (visibilityItem.toLowerCase() === testValue.toLowerCase()) isFound = true;
|
||||
});
|
||||
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 */
|
||||
const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
|
||||
// Checks if user explicitly has access to a certain section
|
||||
const checkVisiblity = () => {
|
||||
const checkVisibility = () => {
|
||||
if (!currentUser) return true;
|
||||
const hideFor = displayData.hideForUsers || [];
|
||||
const hideForUsers = displayData.hideForUsers || [];
|
||||
const cUsername = currentUser.user.toLowerCase();
|
||||
return !determineVisibility(hideFor, cUsername);
|
||||
return !determineVisibility(hideForUsers, cUsername);
|
||||
};
|
||||
// Checks if user is explicitly prevented from viewing a certain section
|
||||
const checkHiddenability = () => {
|
||||
@@ -33,12 +41,36 @@ const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
|
||||
if (showForUsers.length < 1) return true;
|
||||
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
|
||||
const checkIfHideForGuest = () => {
|
||||
const hideForGuest = displayData.hideForGuests;
|
||||
return !(hideForGuest && isGuest);
|
||||
};
|
||||
return checkVisiblity() && checkHiddenability() && checkIfHideForGuest();
|
||||
return checkVisibility()
|
||||
&& checkHiddenability()
|
||||
&& checkIfHideForGuest()
|
||||
&& checkKeycloakVisibility()
|
||||
&& checkKeycloakHiddenability();
|
||||
};
|
||||
|
||||
/* Putting it all together, the function to export */
|
||||
|
||||
@@ -84,7 +84,8 @@
|
||||
"parent",
|
||||
"top",
|
||||
"modal",
|
||||
"workspace"
|
||||
"workspace",
|
||||
"clipboard"
|
||||
],
|
||||
"default": "newtab",
|
||||
"description": "The default opening method for items. Only used if no item.target is specified"
|
||||
@@ -613,6 +614,58 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -658,7 +711,8 @@
|
||||
"parent",
|
||||
"top",
|
||||
"modal",
|
||||
"workspace"
|
||||
"workspace",
|
||||
"clipboard"
|
||||
],
|
||||
"default": "newtab",
|
||||
"description": "Where / how the item is opened when it's clicked"
|
||||
@@ -742,4 +796,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
};
|
||||
@@ -8,7 +8,8 @@ export const shouldBeVisible = (routeName) => !hideFurnitureOn.includes(routeNam
|
||||
/* Based on section title, item name and index, return a string value for ID */
|
||||
const makeItemId = (sectionStr, itemStr, index) => {
|
||||
const charSum = sectionStr.split('').map((a) => a.charCodeAt(0)).reduce((x, y) => x + y);
|
||||
const itemTitleStr = itemStr.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase();
|
||||
const newItemStr = itemStr || `unknown_${Math.random()}`;
|
||||
const itemTitleStr = newItemStr.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase();
|
||||
return `${index}_${charSum}_${itemTitleStr}`;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,10 +3,7 @@ module.exports = {
|
||||
pageInfo: {
|
||||
title: 'Dashy',
|
||||
description: '',
|
||||
navLinks: [
|
||||
{ title: 'Home', path: '/' },
|
||||
{ title: 'Source', path: 'https://github.com/Lissy93/dashy' },
|
||||
],
|
||||
navLinks: [],
|
||||
footerText: '',
|
||||
},
|
||||
/* Default appConfig to be used, if user does not specify their own */
|
||||
@@ -124,6 +121,7 @@ module.exports = {
|
||||
USERNAME: 'username',
|
||||
MOST_USED: 'mostUsed',
|
||||
LAST_USED: 'lastUsed',
|
||||
KEYCLOAK_INFO: 'keycloakInfo',
|
||||
},
|
||||
/* Key names for cookie identifiers */
|
||||
cookieKeys: {
|
||||
@@ -284,6 +282,7 @@ module.exports = {
|
||||
loggedIn: 1,
|
||||
guestAccess: 2,
|
||||
notLoggedIn: 3,
|
||||
keycloakEnabled: 4,
|
||||
},
|
||||
/* Progressive Web App settings, used by Vue Config */
|
||||
pwa: {
|
||||
|
||||
Reference in New Issue
Block a user