feat(forge,core): fetch and decode authenticated images #65

Closed
opened 2026-10-08 21:55:09 -05:00 by deadstyle · 1 comment
Owner

Problem

Inline images (#60) need the app to fetch images the forge serves behind a login, and to decode them safely. Nothing user-visible changes in this ticket.

Acceptance criteria

  1. forge.AssetReader (optional capability, forge.ActAssets): OpenAsset(ctx, u *url.URL) (io.ReadCloser, error). The gitea adapter sends Authorization: token only when scheme, host and port all match the session host; any other URL returns forge.ErrUnsupported before any I/O.
  2. A cross-host redirect does not carry the token (httptest proves it).
  3. forgetest.Fake implements it with an embedded demo PNG; the contract suite covers it.
  4. core.Service.Image(ctx, repo, raw) and PeekImage(repo, raw): resolve raw against <host>/<owner>/<repo>/; refuse anything off-host with no I/O; 20s timeout; read at most 10 MiB; refuse over 40 MP via image.DecodeConfig; decode PNG, JPEG, GIF (first frame); downscale to at most 1600px on the longest side; share the existing concurrency semaphore.
  5. Results sit in a separate 16-entry LRU; failures are cached too; the r refresh clears it.
  6. ADR 0015 (inline images via kitty Unicode placeholders) lands in this MR; docs/architecture.md Forge listing updated; ADR 0014 Consequences updated.

Plan

Decided by Opus (design summary on #60). Files: internal/forge/forge.go, internal/forge/gitea/, internal/forge/forgetest/, internal/core/ (new image service file), docs/adr/0015-*.md.

Context

  • Token auth on /attachments/ paths could not be tested live (no attachment on the host). Confirm against a recorded fixture; Forgejo SERVE_DIRECT redirects downloads to object storage, hence the redirect test.
  • The image LRU is a stated exception to ADR 0007's "entries never expire".

Out of scope

Terminal output (#66), markdown and details integration (later ticket), WebP, external hosts.

Refs #60

## Problem Inline images (#60) need the app to fetch images the forge serves behind a login, and to decode them safely. Nothing user-visible changes in this ticket. ## Acceptance criteria 1. `forge.AssetReader` (optional capability, `forge.ActAssets`): `OpenAsset(ctx, u *url.URL) (io.ReadCloser, error)`. The gitea adapter sends `Authorization: token` only when scheme, host and port all match the session host; any other URL returns `forge.ErrUnsupported` before any I/O. 2. A cross-host redirect does not carry the token (httptest proves it). 3. `forgetest.Fake` implements it with an embedded demo PNG; the contract suite covers it. 4. `core.Service.Image(ctx, repo, raw)` and `PeekImage(repo, raw)`: resolve `raw` against `<host>/<owner>/<repo>/`; refuse anything off-host with no I/O; 20s timeout; read at most 10 MiB; refuse over 40 MP via `image.DecodeConfig`; decode PNG, JPEG, GIF (first frame); downscale to at most 1600px on the longest side; share the existing concurrency semaphore. 5. Results sit in a separate 16-entry LRU; failures are cached too; the `r` refresh clears it. 6. ADR 0015 (inline images via kitty Unicode placeholders) lands in this MR; `docs/architecture.md` Forge listing updated; ADR 0014 Consequences updated. ## Plan Decided by Opus (design summary on #60). Files: `internal/forge/forge.go`, `internal/forge/gitea/`, `internal/forge/forgetest/`, `internal/core/` (new image service file), `docs/adr/0015-*.md`. ## Context - Token auth on `/attachments/` paths could not be tested live (no attachment on the host). Confirm against a recorded fixture; Forgejo `SERVE_DIRECT` redirects downloads to object storage, hence the redirect test. - The image LRU is a stated exception to ADR 0007's "entries never expire". ## Out of scope Terminal output (#66), markdown and details integration (later ticket), WebP, external hosts. Refs #60
Author
Owner

🤖 merged: #69

🤖 merged: https://git.bobparsons.dev/deadstyle/lazyforge/pulls/69
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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
deadstyle/lazyforge#65
No description provided.