fix: keep is_admin from settings.json for hash_dir users (etherpad#8110) - #123
JohnMcLear wants to merge 2 commits into
Conversation
A user can be listed in settings.json `users` with `is_admin` but no
`hash`, keeping the password in a `hash_dir` hash file. In that case
authenticate() takes the hash_dir branch, which replaced
`settings.users[username]` with a fresh `{username, is_admin, displayname}`
object and derived `is_admin` only from the `.adm` file or the global
`hash_adm` default. The `is_admin: true` the administrator had configured
was silently dropped, so login succeeded (200) but /admin answered 403.
Resolve the admin flag most-specific-first instead:
1. an explicit `.adm` file, including one containing "false", so a
deliberate demotion keeps working;
2. `is_admin` already configured for that user in settings.json;
3. the site-wide `hash_adm` default.
Regression tests cover all three levels plus repeat logins. Verified
end to end against etherpad core: POST /admin-auth/ with correct creds
goes 403 -> 200, wrong creds stay 401, and `.adm` containing "false"
still yields 403.
Refs: ether/etherpad#8110
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kA75NPq8nGRidAwhPXeCi
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
PR Summary by QodoPreserve settings.json admin flags for hash_dir users
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
Qodo review catch. The previous commit treated every fs.readFile error on the per-user `.adm` file as "file absent" and fell through to settings.json / hash_adm. A `.adm` holding "false" that loses read permission (or is otherwise unreadable) would therefore be overridden by `is_admin: true` in settings.json and silently re-grant admin rights. Only ENOENT now counts as absent; any other error logs a warning and denies admin. Regression test uses a directory at the `.adm` path (EISDIR) so it is deterministic regardless of the uid the tests run as. Refs: ether/etherpad#8110 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kA75NPq8nGRidAwhPXeCi
Root cause
A site can list a user in
settings.jsonwithis_adminbut nohash, keeping the password in ahash_dirhash file:authenticate()then takes thehash_dirbranch, and that branch ended with:i.e. it replaced the configured user object with a fresh one, deriving
is_adminonly from the.admfile or the globalhash_admdefault. Theis_admin: truethe administrator had written insettings.jsonwas silently discarded.Symptom: Basic auth succeeds (
200), the session user exists, but/adminanswers 403.Fix
Resolve the admin flag most-specific-first:
.admfile (hash_adm_ext) — including one containingfalse, so a deliberate demotion still wins;is_adminalready configured for that user insettings.json;hash_admdefault..admkeeps absolute precedence, so nothing that worked before changes. README documents the precedence.Evidence
Unit tests (
static/tests/backend/specs/authenticate.js)Six new cases in
is_admin declared in settings.json (etherpad#8110). Before the fix (pluginmain, core with #8155 merged):After the fix, the whole plugin backend suite is green:
End to end against Etherpad core
Real server (core with ether/etherpad#8155 merged), bcrypt
.hashinhash_dir, no.admfile,"users": {"hashadmin": {"is_admin": true}},POST /admin-auth/:The third run adds
.admcontainingfalseand confirms explicit demotion still overridessettings.json.Follow-up: fail closed on an unreadable
.adm(Qodo review)The first commit treated every
fs.readFileerror on.admas "file absent", so a.admcontainingfalsethat lost read permission would be overridden byis_admin: trueinsettings.json. Commit764f687restricts the fallback toENOENT; any other error logs a warning and denies admin. Covered bydenies admin when .adm exists but cannot be read(uses a directory at the.admpath, soEISDIRis deterministic regardless of uid — achmod 000test would pass vacuously under root). Fails on the previous commit, passes now. Full suite: 31 passing.Context
ep_*settings blocks not being visible throughrequire(), which madeep_hash_authfall back to its defaulthash_dirand find no hash file at all — shipped in fix(settings): expose plugin ep_* config blocks to CJS require (#8110) etherpad#8155.Notes
pnpm run lintis currently broken onmainfor an unrelated reason (typescript@7.0.2vs@typescript-eslint@7/ts-api-utils: "Failed to load plugin '@typescript-eslint' … Cannot read properties of undefined (reading 'Intrinsic')"). Linting withtypescript@5.9.3pinned, the changed files report 0 errors; the only 2 errors in the repo are pre-existingmocha/no-synchronous-testsinstatic/tests/backend/specs/no_scrypt.js. Not touched here.🤖 Generated with Claude Code
https://claude.ai/code/session_012kA75NPq8nGRidAwhPXeCi