Skip to content

Commit 702e884

Browse files
committed
🛂 Skip 401 for root config OIDC, return empty instead
1 parent eadd60c commit 702e884

3 files changed

Lines changed: 32 additions & 8 deletions

File tree

services/app.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ function getAuthMiddleware(authConfig, oidcSettings) {
170170
const initialAuthConfig = loadAuthConfig();
171171
const oidcSettings = loadOidcSettings(initialAuthConfig);
172172
const protectConfig = getAuthMiddleware(initialAuthConfig, oidcSettings);
173+
const bootstrapAuth = oidcSettings
174+
? createOidcMiddleware(oidcSettings, { permissive: true })
175+
: protectConfig;
173176

174177
/* True when any auth method is configured. Used to keep zero-auth deployments
175178
open (their original behaviour) while closing the gate for everyone else. */
@@ -279,7 +282,7 @@ const app = express()
279282
}))
280283
// Middleware to serve any .yml files in USER_DATA_DIR with optional protection
281284
// Note: returns stripped version if auth configured but not yet authenticated
282-
.get('/*.yml', protectConfig, (req, res) => {
285+
.get('/*.yml', bootstrapAuth, (req, res) => {
283286
const ymlFile = req.path.split('/').pop();
284287
const filePath = path.resolve(rootDir, process.env.USER_DATA_DIR || 'user-data', ymlFile);
285288
if (authIsConfigured) {
@@ -295,6 +298,10 @@ const app = express()
295298
printWarning(`Failed to read or parse ${ymlFile}`, e);
296299
return safeEnd(res, errBody('Could not read config'), 500);
297300
}
301+
// Not authenticated, not main conf.yml
302+
if (!req.auth && !guestAccessOn) {
303+
return res.status(401).json({ success: false, message: 'Unauthorized' });
304+
}
298305
}
299306
res.sendFile(filePath, (err) => {
300307
if (err) safeEnd(res, errBody(`Could not read ${ymlFile}`), 404);

services/auth-oidc.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,13 @@ function deriveIsAdmin(claims, settings) {
103103
return false;
104104
}
105105

106-
/* Connect middleware factory. Verifies Bearer id_token; sets req.auth on success. */
107-
function createOidcMiddleware(settings) {
106+
/* Connect middleware factory. Verifies Bearer id_token; sets req.auth on success
107+
* If `permissive: true`, falls through on verification failure instead of 401 */
108+
function createOidcMiddleware(settings, { permissive = false } = {}) {
108109
return async (req, res, next) => {
109110
const header = req.headers.authorization || '';
110111
const match = header.match(/^Bearer\s+(.+)$/i);
111-
if (!match) return next(); // Permissive: no token attached, let downstream gates decide
112+
if (!match) return next(); // No token attached, let downstream gates decide
112113
const token = match[1].trim();
113114
if (!token) return next();
114115

@@ -127,6 +128,7 @@ function createOidcMiddleware(settings) {
127128
return next();
128129
} catch (e) {
129130
console.warn('[auth-oidc] token verification failed:', e.message || e); // eslint-disable-line no-console
131+
if (permissive) return next();
130132
return res.status(401).json({
131133
success: false,
132134
message: 'Unauthorized - Invalid or expired token',

tests/server/conf-strip.test.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,31 @@ describe('OIDC strip behaviour for /conf.yml', () => {
6060
expect(res.headers['vary']).toContain('Authorization');
6161
});
6262

63-
it('does not strip non-root yml files', async () => {
63+
it('requires valid auth for non-root yml files', async () => {
6464
const res = await request(app).get('/sub.yml');
65-
expect(res.status).toBe(200);
66-
expect(res.text).toContain('Sub');
65+
expect(res.status).toBe(401);
66+
});
67+
68+
it('also rejects sub-yml requests with invalid Bearer', async () => {
69+
const res = await request(app)
70+
.get('/sub.yml')
71+
.set('Authorization', 'Bearer not-a-real-token');
72+
expect(res.status).toBe(401);
6773
});
6874

69-
it('rejects invalid Bearer tokens with 401 from the OIDC middleware', async () => {
75+
it('falls through to bootstrap on conf.yml when Bearer fails to verify', async () => {
7076
const res = await request(app)
7177
.get('/conf.yml')
7278
.set('Authorization', 'Bearer not-a-real-token');
79+
expect(res.status).toBe(200);
80+
const body = yamlLoad(res.text);
81+
expect(body._bootstrap.authenticated).toBe(false);
82+
});
83+
84+
it('strict middleware still rejects invalid Bearer on protected API routes', async () => {
85+
const res = await request(app)
86+
.get('/status-check')
87+
.set('Authorization', 'Bearer not-a-real-token');
7388
expect(res.status).toBe(401);
7489
});
7590
});

0 commit comments

Comments
 (0)