From 21a56dce50f6d91b7eac0df911cf0eba75fc9893 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Sat, 1 Aug 2026 18:42:51 -0400 Subject: [PATCH] v1.16.1: fix 401 on /conf and /vault for logged-in admins (#137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both view routes did server-side auth via req.user, but this app's auth-token is a header set by client JS (localStorage), not a cookie — so req.user is undefined on a browser navigation. permission.byGroup(undefined,...) throws status 401, and the middleware.auth gate on /vault threw Auth.errors.login() (401) for the same reason. Both routes now render the shell unconditionally (like /users, /directory) and gate client-side. conf.ejs already called app.auth.forceLogin; vault.ejs now derives isAdmin + personal namespace from /api/user/me after forceLogin instead of server-rendering them. /api/conf and /api/vault still enforce app_sso_admin + OpenBao scope server-side — only the view-route gating moved client-side where the session lives. Also removed a dead duplicate /conf route. Co-authored-by: Claude --- CHANGELOG.md | 18 +++++++++++++++ nodejs/package.json | 2 +- nodejs/routes/index.js | 41 ++++++++++++++------------------- nodejs/views/vault.ejs | 51 +++++++++++++++++++++++------------------- 4 files changed, 64 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44bb1f3..08b14d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to this project are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`. +## [1.16.1] - 2026-08-01 + +Fix: the Configuration (`/conf`) and Vault (`/vault`) pages returned **401** for +a logged-in admin. Both view routes did server-side auth using `req.user`, but +this app's auth-token is a header set by client-side JS (localStorage), not a +cookie — so `req.user` is undefined on a plain browser navigation. +`permission.byGroup(undefined, …)` throws status 401, and the `middleware.auth` +gate on `/vault` threw `Auth.errors.login()` (401) for the same reason. + +Both routes now render the shell unconditionally (like `/users`, `/directory`, +`/overview`) and gate client-side: `conf.ejs` already called +`app.auth.forceLogin(['admin','app_sso_admin'])`; `vault.ejs` now derives +`isAdmin` + the personal namespace from `/api/user/me` after `forceLogin()` +instead of server-rendering them. The `/api/conf` and `/api/vault` endpoints +still enforce `app_sso_admin` + the OpenBao scope server-side, so protection is +unchanged — only the view-route gating moved client-side where the session +actually lives. Also removed a dead duplicate `/conf` route definition. + ## [1.16.0] - 2026-08-01 OpenBao becomes the central secrets store for the theta42 stack, and the SSO diff --git a/nodejs/package.json b/nodejs/package.json index db6617d..1bba5cd 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "t42-sso-manager", - "version": "1.16.0", + "version": "1.16.1", "description": "A very simple LDAP management and SSO system", "author": [ { diff --git a/nodejs/routes/index.js b/nodejs/routes/index.js index fbb2050..0b8f4f7 100755 --- a/nodejs/routes/index.js +++ b/nodejs/routes/index.js @@ -11,8 +11,6 @@ const {Tos} = require('../models/tos'); const conf = require('@simpleworkjs/conf'); const buildInfo = require('../utils/build_info'); const { mountStaticModules } = require('@simpleworkjs/app-stack'); -const middleware = require('../middleware/auth'); -const permission = require('../utils/permission'); const values ={ title: conf.environment !== 'production' ? `dev` : '', @@ -66,13 +64,15 @@ 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('/conf', async function(req, res, next) { - try { - await permission.byGroup(req.user, ['app_sso_admin']); - res.render('conf', {...values}); - } catch(err) { - next(err); - } +router.get('/conf', function(req, res) { + // Admin-only Configuration page. The view renders the shell for anyone + // (like /users, /directory, etc.); the client gates access with + // app.auth.forceLogin(['admin','app_sso_admin']) and the /api/conf endpoint + // enforces app_sso_admin server-side. The previous server-side + // permission.byGroup(req.user,…) 401'd on a browser navigation because this + // app's auth-token is a header set by client JS (localStorage), not a + // cookie — so req.user is undefined on a plain page load. + res.render('conf', {...values}); }); router.get('/directory', function(req, res) { @@ -87,21 +87,18 @@ router.get('/plugins', function(req, res, next) { res.redirect('/directory'); }); -router.get('/vault', middleware.auth, async function(req, res, next) { +router.get('/vault', function(req, res) { // Personal per-user secrets (secret/users//*) for everyone; admins get // free-form access across all of secret/ plus an Apps tab to mint scoped - // tokens for external apps. The /api/vault proxy enforces the same scoping - // server-side (scopeGuard + the token's own OpenBao policy). - let isAdmin = false; - try { - await permission.byGroup(req.user, ['app_sso_admin']); - isAdmin = true; - } catch (e) { /* non-admin: personal namespace only */ } + // tokens for external apps. The view renders the shell for any logged-in + // user; the client gates login via app.auth.forceLogin() and derives the + // admin/namespace scope from /api/user/me. The /api/vault proxy enforces the + // same scoping server-side (scopeGuard + the token's own OpenBao policy), so + // the client-derived scope is only cosmetic. vaultAddr is the only + // server-rendered value (it's a non-user-specific env var); uid + isAdmin + // are resolved client-side to avoid the header-vs-navigation auth mismatch. res.render('vault', { ...values, - vaultUid: req.user.uid, - vaultIsAdmin: isAdmin, - vaultBase: isAdmin ? '' : `users/${req.user.uid}/`, vaultAddr: process.env.VAULT_ADDR || 'http://openbao:8200', }); }); @@ -137,10 +134,6 @@ router.get('/users', async function(req, res, next) { res.render('users', {...values}); }); -router.get('/conf', async function(req, res, next) { - res.render('conf', {...values}); -}); - router.get('/login', async function(req, res, next) { res.render('login', {...values, redirect: req.query.redirect}); }); diff --git a/nodejs/views/vault.ejs b/nodejs/views/vault.ejs index aa58c3e..e3ae199 100644 --- a/nodejs/views/vault.ejs +++ b/nodejs/views/vault.ejs @@ -2,15 +2,10 @@
-

- <% if (vaultIsAdmin) { %> Vault Secrets (admin — all of secret/) - <% } else { %> My Secrets (personal namespace)<% } %> -

+

My Secrets (personal namespace)

@@ -52,8 +47,7 @@
- - <% if (vaultIsAdmin) { %> +
@@ -89,7 +83,6 @@ curl "$VAULT_ADDR/v1/secret/data/apps//conf"
- <% } %> @@ -103,10 +96,8 @@ curl "$VAULT_ADDR/v1/secret/data/apps//conf"