diff --git a/docs/concepts-accounts.md b/docs/concepts-accounts.md
index 148f3f2..120be02 100644
--- a/docs/concepts-accounts.md
+++ b/docs/concepts-accounts.md
@@ -46,7 +46,7 @@ on, just like anyone else's.
A **group** is just a named list of accounts, used to control access. This
app has a handful of built-in groups that grant admin powers (e.g. only
-people in the `app_sso_admin` group can see the Users/Groups/Directory/Executive
+people in the `app_sso_admin` group can see the Users/Groups/Directory/Overview
pages at all), but you can also make your own groups for any app you
connect — say, a group listing everyone who should be allowed into your
photo server. Once a group exists, add or remove members from the
diff --git a/docs/index.md b/docs/index.md
index ec9389c..e6ffcfc 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -22,7 +22,7 @@ one command).
## Screenshots
-
+
diff --git a/nodejs/routes/api_metrics.js b/nodejs/routes/api_metrics.js
index 4ff6f9d..7929bdb 100644
--- a/nodejs/routes/api_metrics.js
+++ b/nodejs/routes/api_metrics.js
@@ -3,8 +3,8 @@ const router = require('express').Router();
const permission = require('../utils/permission');
const metrics = require('../utils/metrics');
-// /api/metrics/executive
-router.get('/executive', async (req, res, next) => {
+// /api/metrics/overview
+router.get('/overview', async (req, res, next) => {
try {
await permission.byGroup(req.user, ['app_sso_admin']);
diff --git a/nodejs/routes/index.js b/nodejs/routes/index.js
index 05bac0f..6e4c094 100755
--- a/nodejs/routes/index.js
+++ b/nodejs/routes/index.js
@@ -48,13 +48,14 @@ router.get('/tos', async function(req, res, next) {
// Admin dashboard (stats + recent/inactive users) and Notifications
// (broadcast + history) merged into one page.
-router.get('/executive', function(req, res) {
- res.render('executive', {...values});
+router.get('/overview', function(req, res) {
+ res.render('overview', {...values});
});
-router.get('/admin', (req, res) => res.redirect(301, '/executive'));
-router.get('/notifications', (req, res) => res.redirect(301, '/executive'));
-router.get('/dashboard', (req, res) => res.redirect(301, '/executive'));
+router.get('/admin', (req, res) => res.redirect(301, '/overview'));
+router.get('/notifications', (req, res) => res.redirect(301, '/overview'));
+router.get('/dashboard', (req, res) => res.redirect(301, '/overview'));
+router.get('/executive', (req, res) => res.redirect(301, '/overview'));
router.get('/directory', function(req, res) {
res.render('directory', {...values});
diff --git a/nodejs/routes/user.js b/nodejs/routes/user.js
index 100db7c..0227a56 100755
--- a/nodejs/routes/user.js
+++ b/nodejs/routes/user.js
@@ -78,11 +78,12 @@ router.get('/me', async function(req, res, next){
// The shared client framework gates the UI on a single effective-rights
// flag (the OIDC-client apps send the same key). Here "admin" means
- // membership in app_sso_admin; group-level gating still reads memberOf.
+ // membership in app_sso_admin or the cross-app app_super_admin group;
+ // group-level gating still reads memberOf.
const groups = (user.memberOf || []).map(function(dn){
return String(dn).split(',')[0].replace(/^cn=/i, '');
});
- user.isAdmin = groups.includes('app_sso_admin');
+ user.isAdmin = groups.includes('app_sso_admin') || groups.includes(permission.SUPER_ADMIN_GROUP);
return res.json(user);
}catch(error){
diff --git a/nodejs/utils/permission.js b/nodejs/utils/permission.js
index 869d023..a2fa2ca 100644
--- a/nodejs/utils/permission.js
+++ b/nodejs/utils/permission.js
@@ -2,7 +2,16 @@
const {Group} = require('../models/group_ldap');
+const SUPER_ADMIN_GROUP = 'app_super_admin';
+
let byGroup = async function(user, groups, ownerOf){
+ try{
+ let superAdmin = await Group.get(SUPER_ADMIN_GROUP);
+ if(superAdmin.member.includes(user.dn)) return true
+ }catch(error){
+ // group not found, continue checking
+ }
+
for(let group of groups){
try{
group = await Group.get(group);
@@ -28,4 +37,4 @@ let byGroup = async function(user, groups, ownerOf){
throw error;
}
-module.exports = {byGroup};
+module.exports = {byGroup, SUPER_ADMIN_GROUP};
diff --git a/nodejs/utils/ui.js b/nodejs/utils/ui.js
index de8ebc2..545e6ef 100644
--- a/nodejs/utils/ui.js
+++ b/nodejs/utils/ui.js
@@ -38,9 +38,9 @@ module.exports = {
// app-base.js, which reveals .group-required- for each group the user is
// in (plus the synthetic `admin` group when user/me reports isAdmin).
nav: [
- {href: '/users', icon: 'fa-solid fa-users', label: 'Users', groups: ['app_sso_admin']},
- {href: '/groups', icon: 'fa-solid fa-users-viewfinder', label: 'Groups', groups: ['app_sso_admin']},
- {href: '/directory', icon: 'fa-solid fa-server', label: 'Directory', groups: ['app_sso_admin', 'app_sso_directory_admin']},
- {href: '/executive', icon: 'fa-solid fa-gauge-high', label: 'Executive', groups: ['app_sso_admin']},
+ {href: '/users', icon: 'fa-solid fa-users', label: 'Users', groups: ['app_sso_admin', 'admin']},
+ {href: '/groups', icon: 'fa-solid fa-users-viewfinder', label: 'Groups', groups: ['app_sso_admin', 'admin']},
+ {href: '/directory', icon: 'fa-solid fa-server', label: 'Directory', groups: ['app_sso_admin', 'app_sso_directory_admin', 'admin']},
+ {href: '/overview', icon: 'fa-solid fa-gauge-high', label: 'Overview', groups: ['app_sso_admin', 'admin']},
],
};
diff --git a/nodejs/views/directory.ejs b/nodejs/views/directory.ejs
index 6d49cb2..f95eeab 100644
--- a/nodejs/views/directory.ejs
+++ b/nodejs/views/directory.ejs
@@ -73,7 +73,7 @@