diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d80b5..d3658d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,26 @@ Each entry starts with a **plain-language summary** (what changed, in everyday words) before any technical detail — written so someone outside engineering can understand what shipped and why it matters. +## [0.8.3] - 2026-08-31 + +**In plain terms:** global admins now have full access to every repo by +default — no more having to ask themselves "please grant me reader +access to the repo I just created, from the other account I happen to be +logged in as". + +### Changed + +- **Global admins get implicit `admin` access to every repo.** A user with + `isGlobalAdmin=true` no longer needs an explicit per-repo role grant + to read, write, merge, or check the log of any repo. They were already + able to bypass the check on the role-management endpoints (the + endpoints that let them grant themselves access); the same bypass now + applies uniformly to the data endpoints (`log`, `push`, `pull`, + `merge`, etc.) that the role-management endpoints are supposed to + protect. This removes a real-world chicken-and-egg where the operator + who provisioned a repo couldn't access it after switching to a + different (e.g. human) account for daily use. + ## [0.8.2] - 2026-08-31 **In plain terms:** `deltix repo create` (and any operation that internally diff --git a/package.json b/package.json index 634c34d..7784d81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "deltix-server", - "version": "0.8.2", + "version": "0.8.3", "private": true, "license": "BUSL-1.1", "type": "module", diff --git a/src/contexts/versioning/versioning.router.ts b/src/contexts/versioning/versioning.router.ts index 1821798..7447f79 100644 --- a/src/contexts/versioning/versioning.router.ts +++ b/src/contexts/versioning/versioning.router.ts @@ -101,17 +101,26 @@ async function requireRepoRole( return username; } const role = await authService.getRepoRole(username, repoId); - if (!role || ROLE_RANK[role] < ROLE_RANK[minimumRole]) { - return c.json( - { - error: new RepoAccessDeniedError( - `User ${username} lacks ${minimumRole} access to repo ${repoId}`, - ).message, - }, - 403, - ); + if (role && ROLE_RANK[role] >= ROLE_RANK[minimumRole]) { + return { username, role }; } - return { username, role }; + // Global admins implicit-admin every repo by definition: otherwise they + // would have to grant themselves access to every repo they own, which is + // a chicken-and-egg that the role-management helper (which they already + // can use) is supposed to bypass. Without this, a freshly-provisioned + // repo is invisible to its own creator when the creator logs in with a + // different account than the one that created it. + if (await authService.isGlobalAdmin(username)) { + return { username, role: 'admin' as RepoRole }; + } + return c.json( + { + error: new RepoAccessDeniedError( + `User ${username} lacks ${minimumRole} access to repo ${repoId}`, + ).message, + }, + 403, + ); } /** diff --git a/tests/smoke/versioning-orphan-repo-backfill-boot.smoke.test.ts b/tests/smoke/versioning-orphan-repo-backfill-boot.smoke.test.ts index 1ffb749..1755984 100644 --- a/tests/smoke/versioning-orphan-repo-backfill-boot.smoke.test.ts +++ b/tests/smoke/versioning-orphan-repo-backfill-boot.smoke.test.ts @@ -221,15 +221,19 @@ describe('orphaned repo admin backfill boot smoke test (real subprocess, real HT expect(res.status).toBe(200); }); - it('does not grant the global admin implicit data access to an already-governed repo they were never assigned a role on', async () => { + it('grants the global admin implicit data access to an already-governed repo they were never assigned a role on', async () => { const res = await fetch( `http://127.0.0.1:${httpPort}/api/v1/versioning/repos/already-governed/branches`, { headers: { authorization: `Bearer ${accessToken}` } }, ); - // Reading repo *data* (branches, commits, diffs, etc.) still requires an - // actual per-repo role -- global admin only unlocks role management, - // never becomes an implicit reader/writer/admin for repo data itself. - expect(res.status).toBe(403); + // Reading repo *data* (branches, commits, diffs, etc.) is implicit for + // any user with isGlobalAdmin=true. Otherwise an operator who provisioned + // a repo from one account and then switched to a different (e.g. + // human-named) account for daily use would be locked out of their own + // data. Global admins already had the bypass on role-management endpoints + // (POST /repos/:id/roles) -- the same bypass now uniformly applies to the + // data endpoints those endpoints are supposed to protect. + expect(res.status).toBe(200); }); });