mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
feat: add import restore options, OAuth confirmation, and fixes
- database import: add keep_owners (restore PostgreSQL owners and privileges) and sqlite_database (pick the target SQLite file) to the API, OpenAPI spec, and import form - oauth: require password confirmation and support team selection on OAuth login - remote process: send team broadcast events to the team of the server that ran the process instead of the causer's user id - servers API: handle the legacy build server setting - compose: support parent bind paths and volume driver options - dns: improve record hints and provider management - notifications: fix throttling and missing backup notifications - ui: rework the confirmation modal - add feature and unit tests for the new behavior
This commit is contained in:
@@ -4,11 +4,15 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\OauthSetting;
|
||||
use App\Services\Auth\OauthLoginService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class OauthController extends Controller
|
||||
{
|
||||
public const string PASSWORD_CONFIRMATION_SESSION_KEY = 'oauth.password_confirmation';
|
||||
|
||||
public function redirect(string $provider)
|
||||
{
|
||||
$oauthSetting = $this->enabledProvider($provider);
|
||||
@@ -17,8 +21,40 @@ class OauthController extends Controller
|
||||
return $socialiteProvider->redirect();
|
||||
}
|
||||
|
||||
public function callback(string $provider, OauthLoginService $oauthLoginService)
|
||||
/**
|
||||
* Start confirming a destructive action by re-authenticating the logged-in
|
||||
* user through an OAuth provider the user has a linked identity with.
|
||||
*/
|
||||
public function confirm(Request $request, string $provider)
|
||||
{
|
||||
$user = $request->user();
|
||||
$oauthSetting = $this->enabledProvider($provider);
|
||||
if (! $user->oauthIdentities()->where('provider', $oauthSetting->provider)->exists()) {
|
||||
abort(403, 'No identity from this provider is linked to your account.');
|
||||
}
|
||||
|
||||
$request->session()->put(self::PASSWORD_CONFIRMATION_SESSION_KEY, [
|
||||
'user_id' => $user->getKey(),
|
||||
'provider' => $oauthSetting->provider,
|
||||
'return_to' => $this->sameOriginUrl(url()->previous()),
|
||||
]);
|
||||
|
||||
$socialiteProvider = get_socialite_provider($oauthSetting->provider);
|
||||
$parameters = socialite_reauthentication_parameters($oauthSetting);
|
||||
if ($parameters !== []) {
|
||||
$socialiteProvider->with($parameters);
|
||||
}
|
||||
|
||||
return $socialiteProvider->redirect();
|
||||
}
|
||||
|
||||
public function callback(Request $request, string $provider, OauthLoginService $oauthLoginService)
|
||||
{
|
||||
$pendingConfirmation = $request->session()->pull(self::PASSWORD_CONFIRMATION_SESSION_KEY);
|
||||
if (is_array($pendingConfirmation) && $request->user()) {
|
||||
return $this->confirmCallback($request, $provider, $pendingConfirmation, $oauthLoginService);
|
||||
}
|
||||
|
||||
try {
|
||||
$oauthSetting = $this->enabledProvider($provider);
|
||||
$oauthUser = get_socialite_provider($oauthSetting->provider)->user();
|
||||
@@ -38,6 +74,67 @@ class OauthController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete an OAuth confirmation. The returned identity must already be
|
||||
* linked to the logged-in user; the session is never switched to another
|
||||
* user and no identity is linked or updated.
|
||||
*
|
||||
* @param array{user_id?: mixed, provider?: mixed, return_to?: mixed} $pendingConfirmation
|
||||
*/
|
||||
private function confirmCallback(Request $request, string $provider, array $pendingConfirmation, OauthLoginService $oauthLoginService): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
try {
|
||||
$oauthSetting = $this->enabledProvider($provider);
|
||||
if (
|
||||
data_get($pendingConfirmation, 'user_id') !== $user->getKey()
|
||||
|| data_get($pendingConfirmation, 'provider') !== $oauthSetting->provider
|
||||
) {
|
||||
throw new HttpException(403, 'OAuth confirmation does not match the current session');
|
||||
}
|
||||
|
||||
$oauthUser = get_socialite_provider($oauthSetting->provider)->user();
|
||||
if (! $oauthLoginService->identityBelongsToUser($user, $oauthSetting, $oauthUser)) {
|
||||
throw new HttpException(403, 'OAuth identity is not linked to the current user');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
auditLog('auth.oauth.password_confirmation_failed', [
|
||||
'provider' => $provider,
|
||||
'actor_id' => $user->getKey(),
|
||||
'actor_email' => $user->email,
|
||||
'exception_class' => $e::class,
|
||||
], 'warning');
|
||||
Log::warning('OAuth password confirmation failed.', [
|
||||
'provider' => $provider,
|
||||
'user_id' => $user->getKey(),
|
||||
'exception_class' => $e::class,
|
||||
'exception_message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
abort(403, 'Confirmation failed. Sign in with the account linked to your Coolify user.');
|
||||
}
|
||||
|
||||
$request->session()->passwordConfirmed();
|
||||
auditLog('auth.oauth.password_confirmed', [
|
||||
'provider' => $oauthSetting->provider,
|
||||
'actor_id' => $user->getKey(),
|
||||
'actor_email' => $user->email,
|
||||
]);
|
||||
|
||||
return redirect()->to($this->sameOriginUrl(data_get($pendingConfirmation, 'return_to')));
|
||||
}
|
||||
|
||||
private function sameOriginUrl(mixed $url): string
|
||||
{
|
||||
$home = url('/');
|
||||
if (! is_string($url) || ($url !== $home && ! str_starts_with($url, $home.'/') && ! str_starts_with($url, $home.'?'))) {
|
||||
return $home;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
private function logCallbackFailure(string $provider, \Throwable $exception): void
|
||||
{
|
||||
auditLog('auth.oauth.callback_failed', [
|
||||
|
||||
Reference in New Issue
Block a user