Skip to content

[pull] main from forem:main - #389

Merged
pull[bot] merged 4 commits into
amishakov:mainfrom
forem:main
Sep 15, 2026
Merged

pull[bot] merged 4 commits into
amishakov:mainfrom
forem:main

Conversation

@pull

@pull pull Bot commented Sep 15, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 馃挅 Please sponsor : )

jcsawyer123 and others added 4 commits September 14, 2026 18:07
* Add provider-neutral account-switch interstitial

Completes the cross-account guard from #23783: the Authenticator already
raises AccountSwitchConfirmation when a signed-in user's incoming identity
resolves to a different account, but nothing rescued it. Now the callback
stages the OmniAuth payload encrypted in the session, renders a
confirm/cancel interstitial, and only attaches the identity and swaps the
session after explicit confirmation. The Authenticator re-verifies the
staged target with expected_user so a target that changed email, became
unconfirmed, was suspended, or whose identity was reassigned in the meantime
is rejected without mutation.

Nothing in this layer knows about a specific provider. Providers that must
not hold OAuth credentials beyond the callback request declare
`persist_credentials?` false and have them blanked before staging; MLH does
so because access is brokered upstream.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Add temporary MLH Core return bridge

Everything MLH-Core-specific now lives in two clearly marked places:

- Authentication::MlhCoreBridge points the omniauth-mlh strategy at Core
  (MLH_OAUTH_BASE_URL / MLH_API_BASE_URL), narrows scopes to what Core
  issues, and disables credential persistence. It carries the removal
  checklist for the whole bridge.
- Authentication::ExternalReturn returns the browser to Core after sign-in.
  The controller only sees an opaque capture/resolve interface: `capture`
  turns OmniAuth request params into a session-safe context that rides
  through the account-switch interstitial, and `resolve` turns it back into
  a redirect URL. The `continuation` wire format is known only to this class,
  so swapping in OIDC target_link_uri later touches nothing else.

The bridge is inert unless FOREM_EXTERNAL_RETURN_ENABLED=true and an HTTPS
FOREM_EXTERNAL_RETURN_URL is configured. The Core-matching seed users only
load when the bridge is configured.

The earlier after_sign_in_path_for hook is intentionally gone: the callback
short-circuits to the return URL before Devise's redirect runs, so the hook
only widened the bridge into every password sign-in for no benefit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Pin bullseye-security apt source to snapshot.debian.org

Container builds fail in the builder stage's apt install with 404s for
bullseye-security packages. Bullseye LTS ended 2026-08-31 and the
bullseye-security pool is being purged from deb.debian.org and
security.debian.org: their CDN edges return 404 for .deb files the index
still lists, and which edge you hit decides whether the build passes.
archive.debian.org has not picked the suite up yet. snapshot.debian.org
keeps every package permanently, so pin the security source to a snapshot
from the last LTS day in the base stage so every derived stage builds
again. This is a stopgap until the base image moves to a supported Debian
release.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* build: pin jwt for delegated authentication

* feat: add JWKS-backed delegated-access verifier

* Harden delegated access JWKS client and configuration loading

- Accept the RFC 7517 JWK Set media type (application/jwk-set+json) as well
  as application/json, case-insensitively, and advertise both in Accept. An
  issuer using the registered type previously failed every verification as
  unavailable.
- Add race_condition_ttl to the JWKS cache so that when the cached set
  expires under load, one caller per process refreshes it while the others
  keep the just-expired set for a few seconds instead of all hitting the
  issuer at once, and a single failed refresh no longer fails every
  concurrent request.
- Build the configuration inside to_prepare and drop the hand-written
  require/require_relative lines so Zeitwerk owns loading of the
  DelegatedAccess constants.
- Raise the same clear ArgumentError for a missing variable as for a blank
  one, naming the variable.
- Document the key-rotation contract implied by the JWKS cache lifetime.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: jcsawyer123 <joshua@jcsawyer.me>
Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* feat: accept delegated bearer authentication

Co-authored-by: Erin Osher <4386583+erinosher@users.noreply.github.com>

* Only interpret Bearer tokens when delegated access is enabled

API v1 ignored the Authorization header before this feature existed. With
the feature disabled (the default on every instance), a request carrying a
Bearer header alongside a valid api-key or session was being rejected with
401, which is a regression for any client that sends one. Check the feature
flag before looking at the header so disabled instances behave exactly as
before; the strict handling is unchanged when the feature is on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Erin Osher <4386583+erinosher@users.noreply.github.com>
Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* docs: document delegated JWKS authentication

Co-authored-by: jcsawyer123 <joshua@jcsawyer.me>

* Document Bearer auth on every api-key endpoint and the 503 outcome

- Make bearer_auth part of the top-level security default alongside
  api-key, since ApiController accepts a delegated token on every endpoint
  that accepts an api-key. The eight per-operation overrides are now
  redundant and removed; public operations keep `security []`.
- Document the 503 "delegated access unavailable" response on
  /api/users/me with a real generated example, and explain in the schema
  that it applies to every Bearer-capable endpoint.
- Note in the docs that the Authorization header is ignored while the
  feature is disabled, and describe the short refresh grace window on the
  JWKS cache.
- Regenerate swagger/v1/api_v1.json.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: jcsawyer123 <joshua@jcsawyer.me>
Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@pull pull Bot locked and limited conversation to collaborators Sep 15, 2026
@pull pull Bot added the 猡碉笍 pull label Sep 15, 2026
@pull
pull Bot merged commit 2ffcc68 into amishakov:main Sep 15, 2026
1 of 2 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants