Update Personal Access Tokens

staging
Daniel Supernault 2 weeks ago
parent fb92949a71
commit 0f781cba34
No known key found for this signature in database
GPG Key ID: 23740873EE6F76A1

@ -2,7 +2,13 @@
namespace App\Exceptions;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Throwable;
@ -15,8 +21,8 @@ class Handler extends ExceptionHandler
*/
protected $dontReport = [
OAuthServerException::class,
\GuzzleHttp\Exception\ConnectException::class,
\Illuminate\Http\Client\ConnectionException::class,
ConnectException::class,
ConnectionException::class,
];
/**
@ -51,7 +57,7 @@ class Handler extends ExceptionHandler
return app()->environment() !== 'production';
});
$this->reportable(function (\Illuminate\Http\Client\ConnectionException $e) {
$this->reportable(function (ConnectionException $e) {
return app()->environment() !== 'production';
});
}
@ -59,24 +65,30 @@ class Handler extends ExceptionHandler
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
* @return Response
*/
public function render($request, Throwable $exception)
{
if ($exception instanceof \Illuminate\Validation\ValidationException && $request->wantsJson()) {
return response()->json(
[
if ($request->wantsJson()) {
if ($exception instanceof HttpResponseException) {
return $exception->getResponse();
}
if ($exception instanceof ValidationException) {
return response()->json([
'message' => $exception->getMessage(),
'errors' => $exception->validator->getMessageBag(),
],
method_exists($exception, 'getStatusCode') ? $exception->getStatusCode() : 500
);
} elseif ($request->wantsJson()) {
], $exception->status);
}
$isHttp = $this->isHttpException($exception);
return response()->json(
['error' => $exception->getMessage()],
method_exists($exception, 'getStatusCode') ? $exception->getStatusCode() : 500
$isHttp ? $exception->getStatusCode() : 500,
$isHttp ? $exception->getHeaders() : [],
);
}

@ -78,6 +78,34 @@ class PersonalAccessTokenController extends Controller
]);
}
public function renew(Request $request, string $token_id): JsonResponse
{
$oldToken = $request->user()
->tokens()
->with('client')
->whereKey($token_id)
->firstOrFail();
abort_unless($this->isPersonalAccessToken($oldToken), 404);
abort_if($oldToken->revoked, 422, 'This token has already been revoked.');
$scopes = array_values(array_unique($oldToken->scopes ?? []));
$result = $request->user()->createToken(
$oldToken->name,
$scopes
);
$oldToken->revoke();
return response()->json([
'accessToken' => $result->accessToken,
'token' => $this->serializeToken($result->token),
'renewedTokenId' => $oldToken->id,
]);
}
public function destroy(Request $request, string $token)
{
$token = $request->user()

@ -2,7 +2,37 @@
namespace App\Http;
use App\Http\Middleware\AccountInterstitial;
use App\Http\Middleware\Admin;
use App\Http\Middleware\DangerZone;
use App\Http\Middleware\EmailVerificationCheck;
use App\Http\Middleware\EncryptCookies;
use App\Http\Middleware\FrameGuard;
use App\Http\Middleware\Localization;
use App\Http\Middleware\RedirectIfAuthenticated;
use App\Http\Middleware\TrimStrings;
use App\Http\Middleware\TrustProxies;
use App\Http\Middleware\TwoFactorAuth;
use App\Http\Middleware\VerifyCsrfToken;
use Illuminate\Auth\Middleware\Authenticate;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Http\Middleware\HandleCors;
use Illuminate\Http\Middleware\SetCacheHeaders;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Routing\Middleware\ValidateSignature;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
use Laravel\Passport\Http\Middleware\CheckForAnyScope;
use Laravel\Passport\Http\Middleware\CheckScopes;
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
class Kernel extends HttpKernel
{
@ -14,12 +44,12 @@ class Kernel extends HttpKernel
* @var array
*/
protected $middleware = [
\Illuminate\Http\Middleware\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
HandleCors::class,
CheckForMaintenanceMode::class,
ValidatePostSize::class,
TrustProxies::class,
TrimStrings::class,
ConvertEmptyStringsToNull::class,
];
/**
@ -29,18 +59,29 @@ class Kernel extends HttpKernel
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\App\Http\Middleware\FrameGuard::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
EncryptCookies::class,
FrameGuard::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
CreateFreshApiToken::class,
// 'restricted',
],
'oauth-web' => [
EncryptCookies::class,
FrameGuard::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
CreateFreshApiToken::class,
],
'api' => [
'bindings',
],
@ -54,23 +95,23 @@ class Kernel extends HttpKernel
* @var array
*/
protected $routeMiddleware = [
'api.admin' => \App\Http\Middleware\Api\Admin::class,
'admin' => \App\Http\Middleware\Admin::class,
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'dangerzone' => \App\Http\Middleware\DangerZone::class,
'localization' => \App\Http\Middleware\Localization::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'twofactor' => \App\Http\Middleware\TwoFactorAuth::class,
'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
'interstitial' => \App\Http\Middleware\AccountInterstitial::class,
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
'api.admin' => Middleware\Api\Admin::class,
'admin' => Admin::class,
'auth' => Authenticate::class,
'auth.basic' => AuthenticateWithBasicAuth::class,
'bindings' => SubstituteBindings::class,
'cache.headers' => SetCacheHeaders::class,
'can' => Authorize::class,
'dangerzone' => DangerZone::class,
'localization' => Localization::class,
'guest' => RedirectIfAuthenticated::class,
'signed' => ValidateSignature::class,
'throttle' => ThrottleRequests::class,
'twofactor' => TwoFactorAuth::class,
'validemail' => EmailVerificationCheck::class,
'interstitial' => AccountInterstitial::class,
'scopes' => CheckScopes::class,
'scope' => CheckForAnyScope::class,
// 'restricted' => \App\Http\Middleware\RestrictedAccess::class,
];
}

@ -112,6 +112,36 @@ class AppServiceProvider extends ServiceProvider
return Limit::perDay(50)->by($request->ip());
});
RateLimiter::for('oauth-pat', function (Request $request) {
$user = $request->user('web');
$actor = $user
? 'u:'.$user->getAuthIdentifier()
: 'ip:'.$request->ip();
$tooMany = function (Request $request, array $headers) {
return response()->json([
'message' => 'Too many requests',
'retry_after' => isset($headers['Retry-After'])
? (int) $headers['Retry-After']
: null,
'debug' => 'oauth-pat limiter hit',
'headers' => $headers,
], 429)->withHeaders($headers)->header('X-Debug-Limiter', 'oauth-pat');
};
return [
Limit::perMinute(3)
->by("minute:{$actor}"),
Limit::perHour(15)
->by("hour:{$actor}"),
Limit::perDay(20)
->by("day:{$actor}"),
];
});
Passport::useTokenModel(OAuthToken::class);
Passport::tokensExpireIn(now()->addDays(config('instance.oauth.token_expiration', 356)));
Passport::refreshTokensExpireIn(now()->addDays(config('instance.oauth.refresh_expiration', 400)));

File diff suppressed because one or more lines are too long

@ -9,7 +9,7 @@
"/js/compose.js": "/js/compose.js?id=026f9f5ad4c97335697df06eb89458fc",
"/js/compose-classic.js": "/js/compose-classic.js?id=b335bd735825156da46bd75c881855fc",
"/js/search.js": "/js/search.js?id=9dfe84a2449a214a2b0e6dd5b327e07d",
"/js/developers.js": "/js/developers.js?id=73ea3e8690ad2b28819b345cddcc347d",
"/js/developers.js": "/js/developers.js?id=710bbc2918adfccec57e74be4b2ba8b6",
"/js/hashtag.js": "/js/hashtag.js?id=e379f9a05a735bd1c3b867573e6e7b78",
"/js/collectioncompose.js": "/js/collectioncompose.js?id=8cc4606d6008b6a8c013462135ee958a",
"/js/collections.js": "/js/collections.js?id=750737fae261565e19f49c4d373fcd83",

@ -69,7 +69,12 @@
</span>
</td>
<td style="vertical-align: middle;" class="text-right">
<td class="d-flex">
<a class="btn btn-warning btn-sm" style="font-weight: bold;" @click="renew(token)">
Renew
</a>
<span class="mx-1"></span>
<a class="btn btn-danger btn-sm" style="font-weight: bold;" @click="revoke(token)">
Delete
</a>
@ -299,14 +304,49 @@
},
revoke(token) {
if (! window.confirm('Delete this token? Any applications using it will stop working immediately.')) {
return;
}
swal({
title: 'Confirm token deletion',
text: 'Are you sure you want to delete this token? Any applications using it will stop working immediately.',
icon: "warning",
dangerMode: true,
buttons: true,
})
.then((val) => {
if (val) {
axios.delete('/oauth/personal-access-tokens/' + token.id)
.then(() => {
this.getTokens();
});
}
});
},
axios.delete('/oauth/personal-access-tokens/' + token.id)
.then(() => {
this.getTokens();
});
renew(token) {
swal({
title: 'Confirm token renewal',
text: 'Are you sure you want to renew this token? Any applications using it will stop working immediately, a new token will be generated and only shown once.',
icon: "warning",
dangerMode: true,
buttons: true
})
.then((val) => {
if (val) {
this.accessToken = null;
axios.post('/oauth/personal-access-tokens/' + token.id + '/renew')
.then(response => {
this.tokens = this.tokens
.filter(t => t.id !== response.data.renewedTokenId);
this.tokens.push(response.data.token);
this.showAccessToken(response.data.accessToken);
})
.catch(error => {
alert('Unable to renew this token. Please try again.');
});
}
});
},
formatDate(value) {

@ -54,6 +54,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::group([
'as' => 'passport.',
'prefix' => 'oauth',
'middleware' => ['oauth-web'],
], function () {
Route::post('/token', [
'uses' => '\App\Http\Controllers\OAuth\ApiTokenController@issueToken',
@ -64,10 +65,10 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('/authorize', [
'uses' => '\Laravel\Passport\Http\Controllers\AuthorizationController@authorize',
'as' => 'authorizations.authorize',
'middleware' => ['web', 'throttle:10,1'],
'middleware' => ['throttle:10,1'],
]);
Route::middleware(['web', 'auth:web', 'validemail'])->group(function () {
Route::middleware(['auth:web', 'validemail'])->group(function () {
Route::post('/token/refresh', [
'uses' => '\Laravel\Passport\Http\Controllers\TransientTokenController@refresh',
'as' => 'token.refresh',
@ -126,14 +127,18 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('/personal-access-tokens', [
'uses' => 'PersonalAccessTokenController@store',
'as' => 'personal.tokens.store',
]);
])->middleware(['throttle:oauth-pat']);
Route::post('/personal-access-tokens/{token_id}/renew', [
'uses' => 'PersonalAccessTokenController@renew',
'as' => 'personal.tokens.renew',
])->middleware(['throttle:oauth-pat']);
Route::delete('/personal-access-tokens/{token_id}', [
'uses' => 'PersonalAccessTokenController@destroy',
'as' => 'personal.tokens.destroy',
]);
});
});
Route::get('discover', 'DiscoverController@home')->name('discover');

Loading…
Cancel
Save