mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
f424fe5365
Two places asked "is this the API?" and got it wrong in opposite ways.
EnsureCapability asked $request->expectsJson(). Whether a feature exists
in this installation's edition is a property of the installation, not of
what the caller is willing to parse, so the same route answered
differently per header: `Accept: application/json` got the 403
`capability_unavailable` routes/api.php promises, `Accept: */*` -- curl's
default -- got a bare 404 `not_found`. The mirror image is worse: an
Inertia visit to a capability-gated *web* screen accepts JSON, so it got
403 with Laravel's default error body, naming the exception class, where
the point of the 404 is that an unavailable feature is absent rather than
teased.
ProblemDetails asked $request->is('api/*'). Two staff pages live under
that prefix -- the API dashboard at /api and the OpenAPI reference at
/api/docs, both registered in routes/web.php -- so a signed-out visitor to
either got 401 problem+json, "Send a valid API token in the Authorization
header as \"Bearer <token>\"", instead of the login redirect every other
page gives them.
Both now ask App\Support\ApiSurface: under the API prefix, and not part of
the `web` middleware group. The group is what actually separates the two
-- sessions and CSRF on one side, tokens on the other -- and it keeps
answering correctly for a future /api/v2 without being edited. An
unmatched path has no route to ask, which is the API's answer anyway: a
404 under its prefix is one it should describe in its own format, and the
existing test for that stays green.
Three tests, in the two files that already own these rules. Without the
fix all three go red.
42 lines
1.5 KiB
PHP
42 lines
1.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support;
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
/**
|
|
* Whether a request is on the API rather than on the web site.
|
|
*
|
|
* Two questions used to answer this, and both answer something else. A
|
|
* path test alone (`api/*`) is wrong because two staff *pages* live under
|
|
* that prefix — the API dashboard at /api and the OpenAPI reference at
|
|
* /api/docs, both registered in routes/web.php — and their errors belong
|
|
* to the web site: a signed-out visitor to /api/docs wants the login
|
|
* redirect, not a 401 telling them to send a Bearer token. An
|
|
* `expectsJson()` test is wrong because the Accept header is the caller's
|
|
* preference, not a property of the route: whether an endpoint exists in
|
|
* this edition cannot depend on what the caller is willing to parse.
|
|
*
|
|
* So: under the API prefix, and not part of the `web` middleware group.
|
|
* The group is what actually separates the two — sessions, cookies and
|
|
* CSRF on one side, tokens on the other — and it stays right for a future
|
|
* /api/v2 without this being edited.
|
|
*
|
|
* An unmatched path has no route to ask, and that is the API's answer:
|
|
* a request to a URL under the API prefix that resolves to nothing is a
|
|
* 404 the API should describe in its own error format.
|
|
*/
|
|
class ApiSurface
|
|
{
|
|
public static function matches(Request $request): bool
|
|
{
|
|
if (! $request->is('api/*')) {
|
|
return false;
|
|
}
|
|
|
|
return ! in_array('web', $request->route()?->middleware() ?? [], true);
|
|
}
|
|
}
|