Consolidate all X-API-Key auth into gitea_saas/auth.py (#24) #27

Merged
pbwraith merged 3 commits from issue-24 into main 2026-08-30 00:31:15 +02:00
Owner

Closes #24.

What

Issue #24 found several front-ends each hand-rolling the same X-API-Key check — one of them (intake_factory.py) originally with a non-constant-time == and hmac not imported, and (post-#9) dashboard.py repeating both defects. The security half is already fixed by the merged auth PRs; this change completes the consistency work: ONE auth path.

New gitea_saas/auth.py owns:

  • API_KEY_HEADER — the only "X-API-Key" literal in the package.
  • PUBLIC_PATHS — the single exemption list (/health, /healthz, /api/factory/health) — /dashboard is NOT in it.
  • key_is_valid() — the only hmac.compare_digest on the API-key path, fail-closed on unset/empty/non-str configured key.
  • UNAUTHORIZED_BODY + unauthorized_response() — the canonical 401 body, byte-identical on every front-end's wire.

Every front-end delegates; config.py re-exports API_KEY_HEADER; stripe_client.py:88 (webhook signature, different secret) is intentionally untouched.

Rebase on !26 (bounce finding, PR #27 review 2026-08-26)

Rebased this branch onto !26's head — the #25 /dashboard guard (verified green: 160 passed, 1 skipped, CONFLICTS=0; this PR's diff currently includes that single commit and shrinks to the #24 commits only once !26 merges). The measured conflict (gitea_saas/dashboard.py, tests/test_dashboard.py) was resolved deliberately per the finding:

  • /dashboard stays guarded (never unguarded) and now flows directly through gitea_saas.auth.key_is_valid + unauthorized_response — the dashboard's local _guard closure is deleted, so there is still exactly one auth path.
  • Unset configured key refuses both dashboard endpoints with the canonical 401 (was: 503 on the HTML page via !26's local guard; the consolidation normalizes every refusal to one status/body).
  • test_dashboard_page_guard_blocks_anonymous_access (!26's self-check) is kept: removing the guard from /dashboard fails it.
  • test_dashboard_wrong_key_same_length_rejected (HTML page) kept; bare-mount + composed-app exposure tests updated and passing.

Acceptance criteria

  1. git grep -c compare_digest gitea_saas/ — 1 for the API-key path (auth.py:70) plus stripe_client.py:88 (webhook signature, exempt). Asserted by test_single_compare_digest_on_api_key_path.
  2. git grep -n '"X-API-Key"' gitea_saas/ — exactly 1 line (auth.py:28), down from 3. Asserted by test_single_x_api_key_literal.
  3. Wrong key of the same length rejected; unset configured key refuses every request with the same canonical 401 (dashboard's 503 API_KEY_NOT_CONFIGURED removed).
  4. 401 body byte-identical from all four front-ends (test_401_body_identical_across_all_front_ends).
  5. Suite green: 173 passed, 1 skipped (before: 158 passed, 1 skipped; the rebase additionally folds in !26's verified suite). git diff --check clean.

Plus the owner-requested composition test test_composed_app_exposure (every front-end on one app; liveness public, everything else 401-without-key / 200-with-key).

Testing

  • python3 -m pytest -q → 173 passed, 1 skipped
  • git diff --check → clean

Closes #24

Closes #24. ## What Issue #24 found several front-ends each hand-rolling the same X-API-Key check — one of them (`intake_factory.py`) originally with a non-constant-time `==` and `hmac` not imported, and (post-#9) `dashboard.py` repeating both defects. The security half is already fixed by the merged auth PRs; this change completes the **consistency** work: **ONE auth path**. New `gitea_saas/auth.py` owns: - `API_KEY_HEADER` — the only `"X-API-Key"` literal in the package. - `PUBLIC_PATHS` — the single exemption list (`/health`, `/healthz`, `/api/factory/health`) — **`/dashboard` is NOT in it**. - `key_is_valid()` — the only `hmac.compare_digest` on the API-key path, fail-closed on unset/empty/non-str configured key. - `UNAUTHORIZED_BODY` + `unauthorized_response()` — the canonical 401 body, byte-identical on every front-end's wire. Every front-end delegates; `config.py` re-exports `API_KEY_HEADER`; `stripe_client.py:88` (webhook signature, different secret) is intentionally untouched. ## Rebase on !26 (bounce finding, PR #27 review 2026-08-26) Rebased this branch onto !26's head — the #25 `/dashboard` guard (verified green: 160 passed, 1 skipped, CONFLICTS=0; this PR's diff currently includes that single commit and shrinks to the #24 commits only once !26 merges). The measured conflict (`gitea_saas/dashboard.py`, `tests/test_dashboard.py`) was resolved deliberately per the finding: - `/dashboard` stays **guarded** (never unguarded) and now flows directly through `gitea_saas.auth.key_is_valid` + `unauthorized_response` — the dashboard's local `_guard` closure is deleted, so there is still exactly one auth path. - Unset configured key refuses **both** dashboard endpoints with the canonical 401 (was: 503 on the HTML page via !26's local guard; the consolidation normalizes every refusal to one status/body). - `test_dashboard_page_guard_blocks_anonymous_access` (!26's self-check) is kept: removing the guard from `/dashboard` fails it. - `test_dashboard_wrong_key_same_length_rejected` (HTML page) kept; bare-mount + composed-app exposure tests updated and passing. ## Acceptance criteria 1. `git grep -c compare_digest gitea_saas/` — **1** for the API-key path (`auth.py:70`) plus `stripe_client.py:88` (webhook signature, exempt). Asserted by `test_single_compare_digest_on_api_key_path`. 2. `git grep -n '"X-API-Key"' gitea_saas/` — **exactly 1** line (`auth.py:28`), down from 3. Asserted by `test_single_x_api_key_literal`. 3. Wrong key of the **same length** rejected; unset configured key refuses every request with the same canonical 401 (dashboard's 503 `API_KEY_NOT_CONFIGURED` removed). 4. 401 body **byte-identical** from all four front-ends (`test_401_body_identical_across_all_front_ends`). 5. Suite green: **173 passed, 1 skipped** (before: 158 passed, 1 skipped; the rebase additionally folds in !26's verified suite). `git diff --check` clean. Plus the owner-requested composition test `test_composed_app_exposure` (every front-end on one app; liveness public, everything else 401-without-key / 200-with-key). ## Testing - `python3 -m pytest -q` → **173 passed, 1 skipped** - `git diff --check` → clean Closes #24
pbwraith added 1 commit 2026-08-26 10:12:16 +02:00
Four front-ends (server, delivery, intake_factory, dashboard) hand-rolled
the same X-API-Key check with one hardcoded literal each and four distinct
401 bodies. Add the single auth path:

- gitea_saas/auth.py owns API_KEY_HEADER (the only "X-API-Key" literal),
  PUBLIC_PATHS (liveness exemptions: /health, /healthz,
  /api/factory/health), key_is_valid() (the one compare_digest call on the
  API-key path, fail-closed on unset/non-str keys), and UNAUTHORIZED_BODY +
  unauthorized_response() (canonical 401, byte-identical on every wire).
- config.py re-exports API_KEY_HEADER from auth.py for existing importers;
  intake_factory.py drops its dead try/except ImportError fallback (config
  is long merged; a failed import must be loud); dashboard.py replaces its
  stale "lands with #2/#6" comment/literal with the real import.
- server before_request, delivery.authorized, intake _require_auth and
  dashboard _guard all delegate to the shared helper; /healthz is now
  open on the composed app (was 401 for a liveness route).
- Unset-key refusal is now the same canonical 401 as a wrong key
  (dashboard's 503 API_KEY_NOT_CONFIGURED removed — one status for every
  refusal). stripe_client.py's webhook-signature check is untouched
  (explicitly out of scope).

Tests: new tests/test_auth.py pins the acceptance criteria (one
compare_digest on the API-key path + one exempt stripe site, one literal,
same-length wrong key rejected, unset key refuses every front-end,
byte-identical 401 body across all front-ends, composed-app route
exposure). Existing suites updated to the canonical body.
Suite: 158 passed -> 171 passed (1 skipped).

Closes #24
Author
Owner

Implemented and pushed for review.

  • Branch: issue-24 (base main)
  • Commit: 5968f27 — "Consolidate all X-API-Key auth into gitea_saas/auth.py (#24)"
  • Suite: 171 passed, 1 skipped (before: 158 passed, 1 skipped) — new tests/test_auth.py pins all five acceptance criteria;
  • git diff --check clean.

Acceptance summary:

  1. ONE API-key-path compare_digest (gitea_saas/auth.py:70); stripe_client.py:88 is webhook-signature verification, explicitly out of scope and untouched.
  2. git grep -n '"X-API-Key"' gitea_saas/ → exactly 1 line (auth.py:28), down from 3.
  3. Same-length wrong key rejected + unset configured key refuses every request (401, same canonical body).
  4. 401 body byte-identical across all four front-ends (server, delivery, intake_factory, dashboard).
  5. Composed-app route-exposure test added (liveness paths public, everything else guarded; /dashboard left to issue #25).
Implemented and pushed for review. - Branch: `issue-24` (base `main`) - Commit: `5968f27` — "Consolidate all X-API-Key auth into gitea_saas/auth.py (#24)" - Suite: **171 passed, 1 skipped** (before: 158 passed, 1 skipped) — new `tests/test_auth.py` pins all five acceptance criteria; - `git diff --check` clean. Acceptance summary: 1. ONE API-key-path `compare_digest` (`gitea_saas/auth.py:70`); `stripe_client.py:88` is webhook-signature verification, explicitly out of scope and untouched. 2. `git grep -n '"X-API-Key"' gitea_saas/` → exactly 1 line (`auth.py:28`), down from 3. 3. Same-length wrong key rejected + unset configured key refuses every request (401, same canonical body). 4. 401 body byte-identical across all four front-ends (`server`, `delivery`, `intake_factory`, `dashboard`). 5. Composed-app route-exposure test added (liveness paths public, everything else guarded; `/dashboard` left to issue #25).
Author
Owner

Bounced -- not on quality, and not merged: this PR is on the sweep's
escalation list (auth) and the repo is held for an owner decision. But there is
a concrete, measured hazard that must not be lost, so it is recorded here and
labelled so the rework is reachable.

Finding: this PR and !26 conflict, and the resolution can revert a p1 fix

Both report mergeable=true. That flag is unreliable in both directions, so I
merged them into a throwaway tree:

git checkout -B t origin/main
git merge origin/issue-25    ->  clean   (!26, the #25 fix)
git merge origin/issue-24    ->  CONFLICT
    gitea_saas/dashboard.py
    tests/test_dashboard.py

Why that specific conflict is dangerous

This branch predates !26, so its side of the conflict still describes
/dashboard as public. On origin/issue-24:

@app.get("/dashboard")
def dashboard_page():
    # No auth - this page is safe only because the server binds to
    # loopback. Recompute from the DB at request time (no caching).

and on origin/issue-25 (the fix):

@app.get("/dashboard")
@_guard
def dashboard_page():

The docstring hunk conflicts the same way -- this branch's side reads
"GET /dashboard -- HTML operator page (NO auth ...)".

So taking this branch's side of the conflict -- the ordinary reflex when the
incoming branch is the larger, newer-looking change -- removes @_guard from
/dashboard and reinstates the unauthenticated billing dashboard that #25
exists to close
. Worse, it would likely look fine: this branch's tests were
written against a world where /dashboard is public, so they would pass.

What to do

  1. Land !26 first (it is verified green: 160 passed, 1 skipped, CONFLICTS=0).
  2. Rebase this branch onto that main. When resolving gitea_saas/dashboard.py,
    take the guarded route and then express it through this PR's own
    consolidation -- /dashboard must end up going through
    gitea_saas.auth.key_is_valid / unauthorized_response, not through the
    local _guard closure and not unguarded.
  3. Add the assertion that makes the resolution self-checking: after the rebase,
    removing the guard from /dashboard must still fail
    test_dashboard_page_guard_blocks_anonymous_access. If it does not, the
    consolidation has quietly dropped the route from the auth path.
  4. /dashboard must NOT be added to auth.PUBLIC_PATHS.

Not assessed

I did not gate this PR on its merits -- the consolidation itself (one
X-API-Key literal, one hmac.compare_digest, one canonical 401 body) is
exactly the right direction, but it is an auth-design change and that decision
is the owner's, not this sweep's.

Bounced -- **not on quality**, and not merged: this PR is on the sweep's escalation list (auth) and the repo is held for an owner decision. But there is a concrete, measured hazard that must not be lost, so it is recorded here and labelled so the rework is reachable. ## Finding: this PR and !26 conflict, and the resolution can revert a p1 fix Both report `mergeable=true`. That flag is unreliable in both directions, so I merged them into a throwaway tree: git checkout -B t origin/main git merge origin/issue-25 -> clean (!26, the #25 fix) git merge origin/issue-24 -> CONFLICT gitea_saas/dashboard.py tests/test_dashboard.py ## Why that specific conflict is dangerous This branch predates `!26`, so its side of the conflict still describes `/dashboard` as public. On `origin/issue-24`: @app.get("/dashboard") def dashboard_page(): # No auth - this page is safe only because the server binds to # loopback. Recompute from the DB at request time (no caching). and on `origin/issue-25` (the fix): @app.get("/dashboard") @_guard def dashboard_page(): The docstring hunk conflicts the same way -- this branch's side reads "``GET /dashboard`` -- HTML operator page (NO auth ...)". So taking this branch's side of the conflict -- the ordinary reflex when the incoming branch is the larger, newer-looking change -- **removes `@_guard` from `/dashboard` and reinstates the unauthenticated billing dashboard that #25 exists to close**. Worse, it would likely look fine: this branch's tests were written against a world where `/dashboard` is public, so they would pass. ## What to do 1. Land `!26` first (it is verified green: 160 passed, 1 skipped, CONFLICTS=0). 2. Rebase this branch onto that main. When resolving `gitea_saas/dashboard.py`, **take the guarded route** and then express it through this PR's own consolidation -- `/dashboard` must end up going through `gitea_saas.auth.key_is_valid` / `unauthorized_response`, not through the local `_guard` closure and not unguarded. 3. Add the assertion that makes the resolution self-checking: after the rebase, removing the guard from `/dashboard` must still fail `test_dashboard_page_guard_blocks_anonymous_access`. If it does not, the consolidation has quietly dropped the route from the auth path. 4. `/dashboard` must NOT be added to `auth.PUBLIC_PATHS`. ## Not assessed I did not gate this PR on its merits -- the consolidation itself (one `X-API-Key` literal, one `hmac.compare_digest`, one canonical 401 body) is exactly the right direction, but it is an auth-design change and that decision is the owner's, not this sweep's.
pbwraith added the
bounced
label 2026-08-26 13:30:35 +02:00
pbwraith force-pushed issue-24 from 5968f27490 to 3819b49498 2026-08-26 13:43:58 +02:00 Compare
pbwraith removed the
bounced
label 2026-08-26 13:44:29 +02:00
Author
Owner

[worker-bounce-fix attempt 1]

Finding-by-finding (bounce review 2026-08-26):

  1. Land !26 first — !26 is still open (manual merge, not this lane's to perform), so the branch was rebased onto !26's EXACT head (08febd5, the verified-green #25 guard, 160 passed/1 skipped). This PR's diff currently includes that one commit and shrinks to the #24 commits only once !26 merges — both landing orders are then conflict-free and the guard can never be dropped.
  2. Rebase + deliberate resolution — the measured conflict (gitea_saas/dashboard.py, tests/test_dashboard.py) was resolved exactly per the finding: /dashboard stays GUARDED and now flows directly through gitea_saas.auth.key_is_valid + unauthorized_response; the dashboard's local _guard closure is deleted — one auth path, no closure, not unguarded.
  3. Self-checking assertion — test_dashboard_page_guard_blocks_anonymous_access (from !26) is kept and passes: removing the guard from /dashboard fails it. Also kept: test_dashboard_wrong_key_same_length_rejected.
  4. PUBLIC_PATHS — /dashboard is NOT in auth.PUBLIC_PATHS (verified: /health, /healthz, /api/factory/health only).

Resolution semantics: unset configured key now refuses BOTH dashboard endpoints with the canonical 401 (the !26 local guard's 503-on-unset was normalized to the single #24 refusal shape, per the finding's 'through gitea_saas.auth' requirement); bare-mount and composed-app exposure tests updated accordingly.

Evidence: commits 64ab847 (#24 consolidation, rebased + resolved) and 3819b49 (guard-through-auth resolution); python3 -m pytest -q → 173 passed, 1 skipped (fine: 171+1 on the old base + !26's suite); git diff --check clean; head pushed via force-with-lease, PR re-verified open/unmerged with 11 changed files.

[worker-bounce-fix attempt 1] Finding-by-finding (bounce review 2026-08-26): 1. **Land !26 first** — !26 is still open (manual merge, not this lane's to perform), so the branch was rebased onto !26's EXACT head (`08febd5`, the verified-green #25 guard, 160 passed/1 skipped). This PR's diff currently includes that one commit and shrinks to the #24 commits only once !26 merges — both landing orders are then conflict-free and the guard can never be dropped. 2. **Rebase + deliberate resolution** — the measured conflict (`gitea_saas/dashboard.py`, `tests/test_dashboard.py`) was resolved exactly per the finding: `/dashboard` stays GUARDED and now flows directly through `gitea_saas.auth.key_is_valid` + `unauthorized_response`; the dashboard's local `_guard` closure is deleted — one auth path, no closure, not unguarded. 3. **Self-checking assertion** — `test_dashboard_page_guard_blocks_anonymous_access` (from !26) is kept and passes: removing the guard from `/dashboard` fails it. Also kept: `test_dashboard_wrong_key_same_length_rejected`. 4. **PUBLIC_PATHS** — `/dashboard` is NOT in `auth.PUBLIC_PATHS` (verified: `/health`, `/healthz`, `/api/factory/health` only). Resolution semantics: unset configured key now refuses BOTH dashboard endpoints with the canonical 401 (the !26 local guard's 503-on-unset was normalized to the single #24 refusal shape, per the finding's 'through gitea_saas.auth' requirement); bare-mount and composed-app exposure tests updated accordingly. Evidence: commits `64ab847` (#24 consolidation, rebased + resolved) and `3819b49` (guard-through-auth resolution); `python3 -m pytest -q` → **173 passed, 1 skipped** (fine: 171+1 on the old base + !26's suite); `git diff --check` clean; head pushed via force-with-lease, PR re-verified open/unmerged with 11 changed files.
Author
Owner

Escalated to the owner — not merged by the sweep

This PR is on the sweep's escalation list and I am deliberately leaving it for a
human. The standing rule: a PR that touches auth, credentials, execute_action,
the action allow-list, or the adversarial corpus is escalated rather than merged.

gitea-saas-core is additionally a standing owner escalation in its own right —
it holds a key authorizing debits, a Stripe secret, and a repo-creating Gitea
token, so the blast radius of an auth mistake here is money and repo control, not
a failed test.

This is not a defect finding. I have not reviewed the diff on its merits and
nothing here says the change is wrong. It is a "who is allowed to merge this"
call, not a "is this correct" call.

Deliberately not labelling this bounced: that label means "rework wanted"
and would put it in the worker rework queue, which is the opposite of what this
needs.

I also could not have run a gate this sweep even if the change were mergeable —
see gitea-worker-ops#40: wsl.exe exec fails on the review box with
Wsl/Service/0x8007274c (reproduced twice, 31s each) while wsl -l -v still
reports the distro Running.

What the owner needs to do: review and merge, or hand it back with a finding.

## Escalated to the owner — not merged by the sweep This PR is on the sweep's escalation list and I am deliberately leaving it for a human. The standing rule: *a PR that touches auth, credentials, `execute_action`, the action allow-list, or the adversarial corpus is escalated rather than merged.* `gitea-saas-core` is additionally a standing owner escalation in its own right — it holds a key authorizing debits, a Stripe secret, and a repo-creating Gitea token, so the blast radius of an auth mistake here is money and repo control, not a failed test. **This is not a defect finding.** I have not reviewed the diff on its merits and nothing here says the change is wrong. It is a "who is allowed to merge this" call, not a "is this correct" call. Deliberately **not** labelling this `bounced`: that label means "rework wanted" and would put it in the worker rework queue, which is the opposite of what this needs. I also could not have run a gate this sweep even if the change were mergeable — see `gitea-worker-ops#40`: `wsl.exe` exec fails on the review box with `Wsl/Service/0x8007274c` (reproduced twice, 31s each) while `wsl -l -v` still reports the distro `Running`. **What the owner needs to do:** review and merge, or hand it back with a finding.
Author
Owner

Gate evidence -- the "I could not run a gate" caveat on my earlier escalation is now stale

My 2026-08-26 escalation said I could not gate this because wsl.exe exec was
failing on the review box (gitea-worker-ops#40). That has cleared -- probed
wsl -e echo alive -> alive, exit 0. So I have now run the real gate.

The escalation itself is unchanged and this PR is still not merged by the
sweep.
This comment adds evidence only; it is not a defect finding and not an
approval.

Gate: pytest -q under C:\Users\Videostation\AppData\Local\Programs\Python\Python312\python.exe
(pytest 8.3.2), in a throwaway detached worktree, against the merge result
(git merge origin/main into the PR branch), never the branch alone.

The suite is safe to run here: ARCHITECTURE.md:72 states the tests are
hermetic (mock Stripe + mock Gitea + tmp SQLite), and the run touches no live
service, no Stripe key and no real Gitea token.

baseline  origin/main  ccccfbf      158 passed, 1 skipped
merge result  !27 (issue-24 @ 3819b49 + origin/main)
  conflicts : 0
  result    : 173 passed, 1 skipped

Count arithmetic checks exactly: 158 + 15 = 173, and the diff adds 15
def test_ under tests/ and removes 0. So no coverage was deleted --
worth stating explicitly, because a consolidation PR that collapses three auth
implementations into one is exactly the shape where tests quietly disappear.

The thing the owner most needs to know: !27 already contains !26

These two PRs look like they must collide -- both edit gitea_saas/dashboard.py
and tests/test_dashboard.py. They do not. Measured rather than inferred:

git merge-base --is-ancestor origin/issue-25 origin/issue-24   ->  YES

issue-25 (!26) is a direct ancestor of issue-24 (!27); !27 is stacked on
top of it. Confirmed from the other direction by composing them in a throwaway
tree in both orders:

main + !26 + !27   ->  0 conflicts, 173 passed, 1 skipped
main + !27 + !26   ->  0 conflicts, 173 passed, 1 skipped

Both orders give 173, the same as !27 alone -- not 158 + 2 + 15 = 175 --
because !26's two tests
(test_dashboard_page_guard_blocks_anonymous_access,
test_dashboard_wrong_key_same_length_rejected) are already present on this
branch.

Consequence for whoever merges: merging !27 alone delivers both changes.
There is no ordering hazard and no rebase needed either way. I am deliberately
not closing !26 as subsumed -- that is a call on an escalated auth PR, so it is
yours, not mine.

## Gate evidence -- the "I could not run a gate" caveat on my earlier escalation is now stale My 2026-08-26 escalation said I could not gate this because `wsl.exe` exec was failing on the review box (`gitea-worker-ops#40`). That has cleared -- probed `wsl -e echo alive` -> `alive`, exit 0. So I have now run the real gate. **The escalation itself is unchanged and this PR is still not merged by the sweep.** This comment adds evidence only; it is not a defect finding and not an approval. Gate: `pytest -q` under `C:\Users\Videostation\AppData\Local\Programs\Python\Python312\python.exe` (pytest 8.3.2), in a throwaway detached worktree, against the **merge result** (`git merge origin/main` into the PR branch), never the branch alone. The suite is safe to run here: `ARCHITECTURE.md:72` states the tests are hermetic (mock Stripe + mock Gitea + tmp SQLite), and the run touches no live service, no Stripe key and no real Gitea token. ``` baseline origin/main ccccfbf 158 passed, 1 skipped ``` ``` merge result !27 (issue-24 @ 3819b49 + origin/main) conflicts : 0 result : 173 passed, 1 skipped ``` Count arithmetic checks exactly: `158 + 15 = 173`, and the diff adds 15 `def test_` under `tests/` and removes **0**. So no coverage was deleted -- worth stating explicitly, because a consolidation PR that collapses three auth implementations into one is exactly the shape where tests quietly disappear. ## The thing the owner most needs to know: !27 already contains !26 These two PRs look like they must collide -- both edit `gitea_saas/dashboard.py` and `tests/test_dashboard.py`. They do not. Measured rather than inferred: ``` git merge-base --is-ancestor origin/issue-25 origin/issue-24 -> YES ``` `issue-25` (!26) is a direct **ancestor** of `issue-24` (!27); !27 is stacked on top of it. Confirmed from the other direction by composing them in a throwaway tree in both orders: ``` main + !26 + !27 -> 0 conflicts, 173 passed, 1 skipped main + !27 + !26 -> 0 conflicts, 173 passed, 1 skipped ``` Both orders give **173**, the same as !27 alone -- not `158 + 2 + 15 = 175` -- because !26's two tests (`test_dashboard_page_guard_blocks_anonymous_access`, `test_dashboard_wrong_key_same_length_rejected`) are already present on this branch. **Consequence for whoever merges:** merging !27 alone delivers both changes. There is no ordering hazard and no rebase needed either way. I am deliberately not closing !26 as subsumed -- that is a call on an escalated auth PR, so it is yours, not mine.
pbwraith merged commit b4e577b31b into main 2026-08-30 00:31:15 +02:00
Author
Owner

Owner-authorized auth merge — exact current-base result verified

Merged head 3819b49498030f49b3ecc842f9ef5e1f29d5985e only after independently constructing its synthetic merge onto current base 8588033d73999f06c937f059194f3c3cab405138 in an isolated worktree.

Evidence on that exact merge result:

  • merge completed without conflicts and git diff --check <base> was clean;
  • python3 -m pytest -q → 218 passed, 1 skipped in 55.21s;
  • the shared gitea_saas/auth.py owns the single API-key comparison via hmac.compare_digest, the header constant, canonical 401 body, and explicit public-health route set;
  • /dashboard and /api/dashboard/summary both fail closed through that shared path; missing, wrong, same-length wrong, and unset configured keys are covered;
  • composed-app and bare-dashboard tests prove billing/client/queue data is never served anonymously;
  • public access remains limited to /health, /healthz, and /api/factory/health;
  • Stripe webhook-signature verification remains a separate, unchanged secret path.

PR #27 contains PR #26 head 08febd56ca1ea4e23efb0f99887a76efa52ddf13 as an ancestor, including its dashboard guard and mutation-sensitive tests. PR #26 is therefore being closed as subsumed rather than merged again. Linked auth issues #24 and #25 are resolved.

The PR head/base/mergeability were re-read immediately before merge and matched the tested SHAs. Owner authorization was given in the governor session on 2026-08-29.

## Owner-authorized auth merge — exact current-base result verified Merged head `3819b49498030f49b3ecc842f9ef5e1f29d5985e` only after independently constructing its synthetic merge onto current base `8588033d73999f06c937f059194f3c3cab405138` in an isolated worktree. Evidence on that exact merge result: - merge completed without conflicts and `git diff --check <base>` was clean; - `python3 -m pytest -q` → **218 passed, 1 skipped** in 55.21s; - the shared `gitea_saas/auth.py` owns the single API-key comparison via `hmac.compare_digest`, the header constant, canonical 401 body, and explicit public-health route set; - `/dashboard` and `/api/dashboard/summary` both fail closed through that shared path; missing, wrong, same-length wrong, and unset configured keys are covered; - composed-app and bare-dashboard tests prove billing/client/queue data is never served anonymously; - public access remains limited to `/health`, `/healthz`, and `/api/factory/health`; - Stripe webhook-signature verification remains a separate, unchanged secret path. PR #27 contains PR #26 head `08febd56ca1ea4e23efb0f99887a76efa52ddf13` as an ancestor, including its dashboard guard and mutation-sensitive tests. PR #26 is therefore being closed as subsumed rather than merged again. Linked auth issues #24 and #25 are resolved. The PR head/base/mergeability were re-read immediately before merge and matched the tested SHAs. Owner authorization was given in the governor session on 2026-08-29.
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: pbwraith/gitea-saas-core#27
No description provided.