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>
60 lines
1.8 KiB
Ruby
60 lines
1.8 KiB
Ruby
# Az engine ActiveJob-ra épülő jobot szállít (PipelineSyncJob), ezért a
|
|
# framework betöltése a mi dolgunk: a host application.rb-je nem feltétlenül
|
|
# require-öli az active_job/railtie-t, és eager loadnál (production) a
|
|
# WarpEngine::ApplicationJob különben uninitialized constant-tal elszáll.
|
|
require "active_job/railtie"
|
|
|
|
require "blueprinter"
|
|
require "apipie-rails"
|
|
|
|
require "warp_engine/version"
|
|
require "warp_engine/configuration"
|
|
require "warp_engine/storage"
|
|
require "warp_engine/access"
|
|
|
|
module WarpEngine
|
|
# A tábláink prefix nélküliek (softwares, releases, ...) — az isolate_namespace
|
|
# által generált "warp_engine_" prefixet üresre cseréljük. Az engine.rb require-je
|
|
# előtt kell definiálva lennie.
|
|
def self.table_name_prefix
|
|
""
|
|
end
|
|
|
|
def self.config
|
|
@config ||= Configuration.new
|
|
end
|
|
|
|
def self.configure
|
|
yield(config)
|
|
end
|
|
|
|
def self.woodpecker_configured?
|
|
config.woodpecker_url.present? && config.woodpecker_api_token.present?
|
|
end
|
|
|
|
# A host innen tudja, hogy a publikálás ActiveSupport::Notifications-t szór
|
|
# ("warp_engine.publish"), és nem kell modell-callbackre kapaszkodnia.
|
|
# Régebbi engine-verziókon a metódus nem létezik, ezért a hívó oldalon
|
|
# respond_to?-val kérdezendő.
|
|
def self.instruments_publish?
|
|
true
|
|
end
|
|
|
|
def self.storage
|
|
Storage.adapter
|
|
end
|
|
|
|
# Who may see and download what. :open by default — see WarpEngine::AccessPolicy.
|
|
def self.access_policy
|
|
AccessPolicy.current
|
|
end
|
|
|
|
# The host has configured a subject class, so client sign-in is available. A client
|
|
# asks GET /api/service rather than this, but the engine's own controllers need it.
|
|
def self.identity_configured?
|
|
config.access_token_owner_class.present?
|
|
end
|
|
end
|
|
|
|
require "warp_engine/engine"
|