A desktop client reading /api/software had no way to learn that a title costs money. There was nothing in the response to say so, no way to sign in, and no way to be told "you do not own this" — so a store with paid titles could only hand the client a 403 at download time and let it guess why. The fix belongs here rather than in the client. A client serves more than one store, so anything it knows about a particular one has to arrive from that store's own API; a rule compiled into the client is a rule that breaks every other catalog it reads. Three seams, each following the storage adapter's shape — documented contract, default that is byte for byte the old behaviour, one config key to replace it: - **access policy** — visible_software_scope / access_for / authorize_download. Every catalog entry now carries an `access` block (gated, entitled, price, purchaseUrl, webUrl) and both /api/download and /file/* ask before serving. The vocabulary is deliberately generic: a word from one host's domain would make every client that reads it specific to that host. - **client sign-in** — the device authorization grant (RFC 8628), over the host's own user model. The approval page stays the host's, because approving needs a session and HTML. Tokens are ApplicationTokens with a `catalog` scope, so publishing and reading stay separable. - **service descriptor** — GET /api/service says what this deployment is and whether it has a sign-in at all, which is how a client stops guessing. With no policy and no subject class configured — every deployment today — the API is unchanged: /api/auth/* answers 404, /api/service reports auth: null, and the 187 pre-existing examples pass untouched. A policy that raises is treated as a refusal, not permission. An artifact served because the gatekeeper crashed is the one failure mode this must not have, so a broken policy empties the catalog and denies the download. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
34 lines
1.3 KiB
Ruby
34 lines
1.3 KiB
Ruby
WarpEngine::Engine.routes.draw do
|
|
namespace :api do
|
|
# What this deployment is and what it can do. A client reads it before it can have
|
|
# a credential, so it is public and cheap.
|
|
get "service", to: "service#show"
|
|
|
|
# Device sign-in, for clients that have no browser of their own (RFC 8628).
|
|
# Inactive — 404 on every action — unless the host configured a subject class.
|
|
namespace :auth do
|
|
post "device", to: "devices#create"
|
|
post "device/token", to: "devices#token"
|
|
delete "token", to: "tokens#destroy"
|
|
end
|
|
|
|
get "software", to: "software#index"
|
|
get "software/highlighted", to: "software_highlighted#index"
|
|
get "image/:id", to: "images#show"
|
|
get "download", to: "downloads#show"
|
|
get "builds", to: "builds#index"
|
|
get "softwares/:name/builds", to: "software_builds#show"
|
|
|
|
get "ci/pipelines", to: "ci#pipelines"
|
|
get "ci/pipelines/:id/status", to: "ci#status"
|
|
post "ci/pipelines/:id/trigger", to: "ci#trigger"
|
|
end
|
|
|
|
post "build/upload", to: "build/uploads#create"
|
|
post "build/publish", to: "build/publish#create"
|
|
get "build/config", to: "build/configs#show"
|
|
post "build/config", to: "build/configs#create"
|
|
|
|
get "file/*path", to: "files#show", format: false
|
|
end
|