123 lines
4.5 KiB
PHP
123 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Exceptions\OAuthProtocolException;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\OauthClient;
|
|
use App\Services\OAuth\OAuthClientAuthService;
|
|
use App\Services\OAuth\OAuthTokenService;
|
|
use hg\apidoc\annotation as Apidoc;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
#[Apidoc\Title('OAuth 协议令牌端点')]
|
|
class OauthTokenController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly OAuthClientAuthService $clientAuthService,
|
|
private readonly OAuthTokenService $tokenService
|
|
) {}
|
|
|
|
#[Apidoc\Title('OAuth Token'), Apidoc\Method('POST'), Apidoc\Url('/oauth/token')]
|
|
public function token(Request $request): JsonResponse
|
|
{
|
|
try {
|
|
$client = $this->clientAuthService->authenticateConfidentialClient($request);
|
|
|
|
$grantType = trim((string) $request->input('grant_type', ''));
|
|
$responseData = match ($grantType) {
|
|
'authorization_code' => $this->handleAuthorizationCodeGrant($request, $client),
|
|
'refresh_token' => $this->handleRefreshTokenGrant($request, $client),
|
|
default => throw new OAuthProtocolException('unsupported_grant_type', 'Unsupported grant_type.'),
|
|
};
|
|
|
|
return response()
|
|
->json($responseData)
|
|
->header('Cache-Control', 'no-store')
|
|
->header('Pragma', 'no-cache');
|
|
} catch (OAuthProtocolException $exception) {
|
|
$response = response()
|
|
->json($exception->toResponsePayload(), $exception->httpStatus)
|
|
->header('Cache-Control', 'no-store')
|
|
->header('Pragma', 'no-cache');
|
|
|
|
if ($exception->oauthError === 'invalid_client') {
|
|
$response->header('WWW-Authenticate', 'Basic realm="OAuth"');
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
|
|
#[Apidoc\Title('OAuth Revoke'), Apidoc\Method('POST'), Apidoc\Url('/oauth/revoke')]
|
|
public function revoke(Request $request): JsonResponse
|
|
{
|
|
try {
|
|
$client = $this->clientAuthService->authenticateConfidentialClient($request);
|
|
$validator = Validator::make($request->all(), [
|
|
'token' => ['required', 'string'],
|
|
'token_type_hint' => ['nullable', 'string'],
|
|
]);
|
|
if ($validator->fails()) {
|
|
throw new OAuthProtocolException('invalid_request', (string) $validator->errors()->first());
|
|
}
|
|
|
|
$this->tokenService->revokeTokenForClient($client, trim((string) $request->input('token')));
|
|
|
|
return response()->json([], 200);
|
|
} catch (OAuthProtocolException $exception) {
|
|
$response = response()->json($exception->toResponsePayload(), $exception->httpStatus);
|
|
if ($exception->oauthError === 'invalid_client') {
|
|
$response->header('WWW-Authenticate', 'Basic realm="OAuth"');
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function handleAuthorizationCodeGrant(Request $request, OauthClient $client): array
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'code' => ['required', 'string'],
|
|
'redirect_uri' => ['required', 'url:http,https'],
|
|
]);
|
|
if ($validator->fails()) {
|
|
throw new OAuthProtocolException('invalid_request', (string) $validator->errors()->first());
|
|
}
|
|
|
|
$redirectUri = (string) $request->input('redirect_uri');
|
|
if (! $this->clientAuthService->isRedirectUriAllowed($client, $redirectUri)) {
|
|
throw new OAuthProtocolException('invalid_grant', 'redirect_uri mismatch.');
|
|
}
|
|
|
|
return $this->tokenService->exchangeAuthorizationCode(
|
|
client: $client,
|
|
code: trim((string) $request->input('code')),
|
|
redirectUri: $redirectUri
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function handleRefreshTokenGrant(Request $request, OauthClient $client): array
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'refresh_token' => ['required', 'string'],
|
|
]);
|
|
if ($validator->fails()) {
|
|
throw new OAuthProtocolException('invalid_request', (string) $validator->errors()->first());
|
|
}
|
|
|
|
return $this->tokenService->exchangeRefreshToken(
|
|
client: $client,
|
|
refreshToken: trim((string) $request->input('refresh_token'))
|
|
);
|
|
}
|
|
}
|