Fixes#6630 (partial — deletion causing broken PAT)
If a user deletes the OAuth client that serves as the personal access
client, all PAT creation breaks for the entire instance with a 500 error.
Changes:
- Add custom OAuthClientController@destroy that checks if the client
has the personal_access grant type before allowing deletion
- Returns 403 with a clear error message if deletion is blocked
- Add confirmation dialog before client deletion in the frontend
- Add error handling to show server error messages to the user
This prevents accidental destruction of the PAT infrastructure.
Fixes#6630 (partial — PAT 500 error)
Previously, POST /oauth/personal-access-tokens would throw an unhandled
RuntimeException (HTTP 500) when:
- OAUTH_PAT_ENABLED is false (the default), or
- No personal access client exists in the database
Now the endpoint:
1. Returns 403 with a clear message if PAT is disabled in config
2. Catches RuntimeException from the token factory and returns 500
with an actionable error message instead of a stack trace
Fixes#6643
The POST /api/v1/accounts/{id}/remove_from_followers endpoint was missing
the token existence check (! $request->user()->token()). While the
tokenCan('follow') scope check was already present, the missing token
guard meant unauthenticated token-less requests could potentially bypass
the scope enforcement.
Added the standard guard pattern consistent with accountFollowById and
accountUnfollowById endpoints.
Also adds tests verifying:
- Read-only tokens are denied (403)
- Follow-scoped tokens succeed (200)
- Unauthenticated requests are denied (403)
Fixes#6695
When no pagination params are provided, the default min_id was set to 1
and the query used 'id > 1', which excluded the very first follower row
(id=1) on fresh instances.
Changed default min_id from 1 to 0 and switched the direction check from
truthy evaluation to !== null, so the query becomes 'id > 0' which
correctly includes all records.
PHP 5.5.9 adds the new static `class` property which provides the fully qualified class name. This is preferred over using strings for class names since the `class` property references are checked by PHP.
- Remove unmaintained jenssegers/agent package (no releases since 2021)
- Add matomo/device-detector v6.5 as actively maintained replacement
- Create App\Services\UserAgentService wrapper for drop-in compatibility
- Update UserDevice model and ApiV1Dot1Controller to use new service
StatusEditController's constructor applies the web `auth` guard, which
Bearer/OAuth clients cannot satisfy. PUT /api/v1/statuses/{id} and
GET /api/v1/statuses/{id}/history therefore fail for every third-party
API client, while status create/delete keep working because
ApiV1Controller has no controller-level web auth.
The controller is routed only from routes/api.php, where the route group
already applies ['auth:api', 'validemail'], so no web/session route
depends on the old guard. Switch the constructor to match.
`GET /api/v1/timelines/home?max_id=` (empty value) fails validation
because `min_id`/`max_id` use the `sometimes|integer` rule. The global
`ConvertEmptyStringsToNull` middleware turns `?max_id=` into `null`, and
since the field is present, `sometimes` does not skip it while `null`
fails the `integer` rule — returning HTTP 422.
Every other timeline/listing endpoint in this controller (timelinePublic,
accountStatusesById, etc.) uses `nullable|integer` for these params, so
`timelineHome` was the lone outlier. Mastodon-API clients such as Pixelfed
for iOS send `max_id=` on first page load and could not paginate the home
timeline.
Switch `min_id`/`max_id` to `nullable|integer` to match the rest of the
controller.
Fixes#6610
Co-Authored-By: Claude <noreply@anthropic.com>