66 lines
1.5 KiB
PHP
66 lines
1.5 KiB
PHP
<?php
|
|
// phpcs:ignoreFile
|
|
/**
|
|
* POST /api/auth/refresh
|
|
*
|
|
* Exchange a valid refresh token for a new access token.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
$body = api_get_json_body();
|
|
api_require_fields($body, ['refresh_token']);
|
|
|
|
$refreshToken = trim((string) $body['refresh_token']);
|
|
|
|
// Decode the refresh token
|
|
$payload = jwt_decode_token($refreshToken);
|
|
|
|
if ($payload === null) {
|
|
api_error('Invalid or expired refresh token', 401);
|
|
}
|
|
|
|
if (!isset($payload['type']) || $payload['type'] !== 'refresh') {
|
|
api_error('Invalid token type', 401);
|
|
}
|
|
|
|
$username = $payload['sub'] ?? null;
|
|
$tokenId = $payload['jti'] ?? null;
|
|
|
|
if ($username === null || $tokenId === null) {
|
|
api_error('Invalid refresh token payload', 401);
|
|
}
|
|
|
|
// Verify token exists in database and is not revoked
|
|
if (!jwt_validate_refresh_token($username, $tokenId)) {
|
|
api_error('Refresh token has been revoked or does not exist', 401);
|
|
}
|
|
|
|
// Look up current user data
|
|
$member = auth_find_member($username);
|
|
|
|
if ($member === null) {
|
|
api_error('User no longer exists', 401);
|
|
}
|
|
|
|
// Revoke the old refresh token (rotation for security)
|
|
jwt_revoke_refresh_token($tokenId);
|
|
|
|
// Generate new tokens
|
|
$accessToken = jwt_create_access_token($member);
|
|
$newRefreshData = jwt_create_refresh_token($member);
|
|
|
|
// Store new refresh token
|
|
jwt_store_refresh_token(
|
|
$member['username'],
|
|
$newRefreshData['token_id'],
|
|
$newRefreshData['expires_at']
|
|
);
|
|
|
|
api_success([
|
|
'access_token' => $accessToken,
|
|
'refresh_token' => $newRefreshData['token'],
|
|
'token_type' => 'Bearer',
|
|
'expires_in' => 900,
|
|
]);
|