Hi everyone,
Thanks for reading and supporting the thread!
I managed to resolve the issue with the Members section appearing for non-admin users after a page refresh in Umbraco 10.
What I did:
I implemented a custom AngularJS extension to continuously monitor the URL and DOM in the Umbraco backoffice. Here’s a summary of the approach:
- Created a custom Angular module (
custom.memberTabControl
) that:
- Fetches the current user using
authResource
.
- Determines if the user is an admin (based on user ID, groups, userType, or allowed sections).
- Hides the Members section entirely for non-admin users.
- Redirects to the Content section if a non-admin tries to access
/member
directly or via refresh.
- Uses
$rootScope
route/location/state change listeners and MutationObserver
to dynamically handle tab reappearances.
- Implements periodic and continuous checks for added safety.
- Added this module via a
package.manifest
so it loads with the Umbraco backoffice.
(function () {
'use strict';
angular.module('umbraco').requires.push('custom.memberTabControl');
angular.module('custom.memberTabControl', [])
.run(['authResource', '$rootScope', '$timeout', '$interval', '$location',
function (authResource, $rootScope, $timeout, $interval, $location) {
// CONTINUOUS URL MONITORING - Block member access immediately and continuously
let isAuthCheckComplete = false;
let isAdminUser = false;
function immediateBlockMemberAccess() {
const currentUrl = window.location.href.toLowerCase();
if (currentUrl.includes('/umbraco#/member') || currentUrl.includes('/umbraco/#/member')) {
// Only block if we know the user is not an admin
if (isAuthCheckComplete && !isAdminUser) {
console.warn('Non-admin user tried to access member section. Redirecting to content...');
window.location.href = '/umbraco#/content';
return true;
} else if (!isAuthCheckComplete) {
// If auth check is not complete, redirect as a safety measure
console.warn('Potential unauthorized member access detected. Redirecting to content...');
window.location.href = '/umbraco#/content';
return true;
}
}
return false;
}
// Set up continuous URL monitoring
function setupContinuousUrlMonitoring() {
// Monitor URL changes via hash change
window.addEventListener('hashchange', function () {
immediateBlockMemberAccess();
});
// Monitor URL changes via popstate
window.addEventListener('popstate', function () {
immediateBlockMemberAccess();
});
// Periodic URL checking
const urlCheckInterval = setInterval(() => {
immediateBlockMemberAccess();
}, 100); // Check every 100ms
// Clean up after 5 minutes
setTimeout(() => {
clearInterval(urlCheckInterval);
}, 300000);
}
// Start continuous monitoring
setupContinuousUrlMonitoring();
// Run immediate check first
if (immediateBlockMemberAccess()) {
return; // Exit if we already redirected
}
// Also hide any member content immediately (before auth check)
function immediateHideMemberContent() {
const memberSelectors = [
'li[data-element="section-member"]',
'li[data-element="section-members"]',
'a[href*="#/member"]',
'a[href*="member"]',
'.umb-navigation__link[href*="member"]',
'.umb-nav-item[data-element="section-member"]',
'.umb-navigation li:has(a[href*="member"])'
];
memberSelectors.forEach(selector => {
try {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
if (element && !element.closest('.umb-user-profile') && !element.closest('.umb-user')) {
element.style.display = 'none !important';
element.style.visibility = 'hidden';
element.style.opacity = '0';
}
});
} catch (e) {
// Ignore selector errors
}
});
}
// Hide member content immediately
immediateHideMemberContent();
authResource.getCurrentUser().then(function (user) {
console.log("Full user object:", user);
// Multiple ways to detect admin users
const groups = user?.userGroups || [];
const userType = user?.userType || '';
const allowedSections = user?.allowedSections || [];
console.log("User groups:", groups);
console.log("User type:", userType);
console.log("Allowed sections:", allowedSections);
// Check if user is admin through specific methods
let isAdmin = false;
// Method 1: Check if user ID is 0 (super admin) or -1 (system user)
if (user.id === 0 || user.id === -1) {
isAdmin = true;
console.log("Admin detected by user ID:", user.id);
}
// Method 2: Check user groups for specific admin groups only
if (!isAdmin && groups.length > 0) {
isAdmin = groups.some(g => {
const groupName = (typeof g === 'string' ? g : (g.alias || g.name || '')).toLowerCase();
return groupName === 'administrator' || groupName === 'administrators';
});
if (isAdmin) {
console.log("Admin detected by user group");
}
}
// Method 3: Check if user type is specifically admin
if (!isAdmin && userType.toLowerCase() === 'admin') {
isAdmin = true;
console.log("Admin detected by user type:", userType);
}
// Method 4: Check if user has all major sections (content, media, settings, users, members)
if (!isAdmin && allowedSections.length >= 5) {
const requiredSections = ['content', 'media', 'settings', 'users', 'member'];
const hasAllSections = requiredSections.every(section =>
allowedSections.includes(section)
);
if (hasAllSections) {
isAdmin = true;
console.log("Admin detected by having all sections");
}
}
console.log("Final admin status:", isAdmin);
// Set the auth check completion status
isAuthCheckComplete = true;
isAdminUser = isAdmin;
if (isAdmin) {
console.log('Admin user detected – allowing member tab access.');
// For admin users, show the member tab if it was hidden
showMemberTabForAdmin();
return;
}
// Enhanced blocking for non-admins
function preventDirectMemberAccess() {
const currentUrl = window.location.href.toLowerCase();
const currentPath = $location.path().toLowerCase();
if (currentUrl.includes('/umbraco#/member') ||
currentUrl.includes('/umbraco/#/member') ||
currentPath.includes('/member')) {
console.warn('Non-admin user tried to access /member directly. Redirecting...');
$location.path('/content');
return;
}
}
// Call this immediately after auth check
preventDirectMemberAccess();
// Function to show member tab for admin users
function showMemberTabForAdmin() {
const memberSelectors = [
'li[data-element="section-member"]',
'li[data-element="section-members"]',
'a[href*="#/member"]',
'a[href*="member"]',
'.umb-navigation__link[href*="member"]',
'.umb-nav-item[data-element="section-member"]'
];
memberSelectors.forEach(selector => {
try {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
if (element && !element.closest('.umb-user-profile') && !element.closest('.umb-user')) {
element.style.display = '';
element.style.visibility = 'visible';
element.style.opacity = '1';
}
});
} catch (e) {
// Ignore selector errors
}
});
}
// Non-admins: proceed to hide member tab
function hideMemberTab() {
let attempts = 0;
const maxAttempts = 20;
function tryHide() {
// Multiple selectors to find the member tab
const selectors = [
'li[data-element="section-member"]',
'li[data-element="section-members"]',
'a[href*="#/member"]',
'a[href*="member"]',
'.umb-navigation__link[href*="member"]',
'.umb-nav-item[data-element="section-member"]',
'.umb-navigation li:has(a[href*="member"])'
];
let tabFound = false;
selectors.forEach(selector => {
try {
const tabs = document.querySelectorAll(selector);
tabs.forEach(tab => {
// Make sure we're not touching user profile/logout elements
if (tab &&
tab.style.display !== 'none' &&
!tab.closest('.umb-user-profile') &&
!tab.closest('.umb-user') &&
!tab.closest('[data-element="user"]') &&
!tab.closest('.umb-header') &&
!tab.classList.contains('umb-user-profile') &&
!tab.classList.contains('umb-user')) {
// Additional check: make sure this is actually a navigation item
const isNavItem = tab.closest('.umb-navigation') ||
tab.closest('.umb-nav') ||
tab.getAttribute('data-element') === 'section-member' ||
tab.classList.contains('umb-nav-item');
if (isNavItem) {
// Hide the tab completely
tab.style.display = 'none !important';
tab.style.visibility = 'hidden';
tab.style.opacity = '0';
// Also hide parent li if this is a link
if (tab.tagName === 'A' && tab.closest('li')) {
const parentLi = tab.closest('li');
parentLi.style.display = 'none !important';
parentLi.style.visibility = 'hidden';
}
// Remove from DOM entirely
tab.remove();
tabFound = true;
console.log('Removed Members tab for non-admin user using selector:', selector);
}
}
});
} catch (e) {
// Ignore selector errors
}
});
// Additional method: find by text content (more specific)
const navItems = document.querySelectorAll('li[data-element*="section"], .umb-nav-item, .umb-navigation li');
navItems.forEach(element => {
if (element.textContent &&
(element.textContent.toLowerCase().trim() === 'members' ||
element.textContent.toLowerCase().trim() === 'member') &&
(element.getAttribute('data-element') === 'section-member' ||
element.querySelector('a[href*="member"]'))) {
element.remove();
tabFound = true;
console.log('Removed Members tab by specific text content');
}
});
if (!tabFound && attempts < maxAttempts) {
attempts++;
$timeout(tryHide, 300);
}
}
// Start immediately
tryHide();
}
// Run multiple times with different delays
$timeout(hideMemberTab, 100);
$timeout(hideMemberTab, 500);
$timeout(hideMemberTab, 1000);
$timeout(hideMemberTab, 2000);
// Enhanced route change monitoring
$rootScope.$on('$routeChangeStart', (event, next, current) => {
if (next && next.originalPath && next.originalPath.toLowerCase().includes('/member')) {
console.warn('Non-admin user tried to navigate to member route. Blocking...');
event.preventDefault();
$location.path('/content');
}
});
$rootScope.$on('$routeChangeSuccess', () => {
preventDirectMemberAccess();
$timeout(hideMemberTab, 100);
$timeout(hideMemberTab, 500);
});
$rootScope.$on('$locationChangeStart', (event, newUrl, oldUrl) => {
if (newUrl && newUrl.toLowerCase().includes('/member')) {
console.warn('Non-admin user tried to change location to member. Blocking...');
event.preventDefault();
$location.path('/content');
}
});
$rootScope.$on('$locationChangeSuccess', () => {
preventDirectMemberAccess();
$timeout(hideMemberTab, 100);
$timeout(hideMemberTab, 500);
});
// Additional event listeners for better coverage
$rootScope.$on('$stateChangeStart', (event, toState, toParams) => {
if (toState && toState.url && toState.url.toLowerCase().includes('/member')) {
console.warn('Non-admin user tried to change state to member. Blocking...');
event.preventDefault();
$location.path('/content');
}
});
$rootScope.$on('$stateChangeSuccess', () => {
preventDirectMemberAccess();
$timeout(hideMemberTab, 100);
$timeout(hideMemberTab, 500);
});
// Enhanced continuous monitoring for non-admin users
const continuousCheck = $interval(() => {
const currentUrl = window.location.href.toLowerCase();
if (currentUrl.includes('/umbraco#/member') || currentUrl.includes('/umbraco/#/member')) {
console.warn('Non-admin user detected in member section during continuous check. Redirecting...');
$location.path('/content');
}
hideMemberTab();
}, 500); // Check every 500ms
// Clean up after 5 minutes
$timeout(() => $interval.cancel(continuousCheck), 300000);
// More aggressive periodic check
const periodicCheck = $interval(() => {
hideMemberTab();
}, 2000);
$timeout(() => $interval.cancel(periodicCheck), 120000); // Run for 2 minutes
// Watch for DOM changes and hide member tab when it appears
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// Only trigger if the added nodes are in navigation area
let shouldHide = false;
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) { // Element node
const isNavigation = node.closest && (
node.closest('.umb-navigation') ||
node.closest('.umb-nav') ||
node.getAttribute('data-element') === 'section-member'
);
if (isNavigation) {
shouldHide = true;
}
}
});
if (shouldHide) {
$timeout(hideMemberTab, 50);
}
}
});
});
// Start observing
observer.observe(document.body, {
childList: true,
subtree: true
});
// Stop observing after 5 minutes
$timeout(() => observer.disconnect(), 300000);
}).catch(function (err) {
console.error('Error getting user from authResource:', err);
// If auth fails, assume non-admin and block member access
isAuthCheckComplete = true;
isAdminUser = false;
const currentUrl = window.location.href.toLowerCase();
if (currentUrl.includes('/umbraco#/member') || currentUrl.includes('/umbraco/#/member')) {
console.warn('Auth failed and member access attempted. Redirecting...');
window.location.href = '/umbraco#/content';
}
});
}
]);
})();`
//this is mymember-section-hide.js
{
"javascript": [
"~/App_Plugins/MemberSectionHide/member-section-hide.js"
]
}
//its my package.manifest