Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -37,6 +37,16 @@ class AuthenticatedSessionController extends Controller
$request->session()->regenerate();
$user = $request->authenticatedUser();
if ($user && $request->authenticatedViaUsername() && ! $user->hasCompletedOnboarding()) {
$request->session()->put('username_login_upgrade', true);
return redirect()->route('setup.email.create')
->with('status', 'Add and verify your email address to continue setup.');
}
$request->session()->forget('username_login_upgrade');
return redirect()->intended(route('dashboard'));
}

View File

@@ -10,6 +10,7 @@ use App\Services\Auth\DisposableEmailService;
use App\Services\Auth\RegistrationVerificationTokenService;
use App\Services\Security\CaptchaVerifier;
use App\Services\Security\TurnstileVerifier;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
@@ -101,45 +102,47 @@ class RegisteredUserController extends Controller
$user = User::query()->where('email', $email)->first();
if ($user && $user->email_verified_at !== null) {
$this->logEmailEvent($email, $ip, (int) $user->id, 'blocked', 'already-verified');
return $this->redirectToRegisterNotice($email);
if ($user && $user->hasCompletedOnboarding()) {
return back()
->withInput($request->except('website'))
->withErrors(['email' => 'An account with this email already exists.']);
}
if (! $user) {
$user = User::query()->create([
$user = new User();
$user->forceFill([
'username' => null,
'name' => Str::before($email, '@'),
'email' => $email,
'password' => Hash::make(Str::random(64)),
'is_active' => false,
'onboarding_step' => 'email',
'is_active' => true,
'email_verified_at' => null,
'onboarding_step' => 'verified',
'needs_password_reset' => true,
'username_changed_at' => now(),
'last_username_change_at' => now(),
]);
$user->save();
} else {
$user->forceFill([
'email_verified_at' => $user->email_verified_at,
'is_active' => true,
'onboarding_step' => strtolower((string) ($user->onboarding_step ?? '')) === 'password' ? 'password' : 'verified',
'needs_password_reset' => strtolower((string) ($user->onboarding_step ?? '')) === 'password'
? (bool) $user->needs_password_reset
: true,
])->save();
}
if ($this->isWithinEmailCooldown($user)) {
$this->logEmailEvent($email, $ip, (int) $user->id, 'blocked', 'cooldown');
Auth::login($user);
return $this->redirectToRegisterNotice($email);
}
$needsPasswordSetup = strtolower((string) ($user->onboarding_step ?? '')) !== 'password'
|| (bool) $user->needs_password_reset;
$token = $this->verificationTokenService->createForUser((int) $user->id);
$event = $this->logEmailEvent($email, $ip, (int) $user->id, 'queued', null);
SendVerificationEmailJob::dispatch(
emailEventId: (int) $event->id,
email: $email,
token: $token,
userId: (int) $user->id,
ip: $ip
);
$this->markVerificationEmailSent($user);
return $this->redirectToRegisterNotice($email);
return redirect(route($needsPasswordSetup ? 'setup.password.create' : 'setup.username.create', absolute: false))
->with('status', $needsPasswordSetup
? 'Continue with password setup.'
: 'Continue with username setup.');
}
public function resendVerification(Request $request): RedirectResponse

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Mail\EmailChangedSecurityAlertMail;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
class SetupEmailController extends Controller
{
public function create(Request $request): View|RedirectResponse
{
$user = $request->user();
if ($user->hasCompletedOnboarding()) {
return redirect()->route('dashboard');
}
return view('auth.setup-email', [
'email' => User::isEmailLoginUpgradePlaceholder((string) $user->email)
? ''
: strtolower(trim((string) $user->email)),
]);
}
public function requestCode(Request $request): RedirectResponse
{
$user = $request->user();
$validated = $request->validate([
'email' => [
'required',
'string',
'lowercase',
'email',
'max:255',
Rule::unique(User::class, 'email')->ignore((int) $user->id),
function (string $attribute, mixed $value, \Closure $fail): void {
if (User::isEmailLoginUpgradePlaceholder((string) $value)) {
$fail('Please enter a real email address you can access.');
}
},
],
]);
$newEmail = strtolower(trim((string) $validated['email']));
$oldEmail = strtolower((string) ($user->email ?? ''));
$nextStep = 'password';
DB::transaction(function () use ($user, $newEmail, &$nextStep): void {
$lockedUser = User::query()->whereKey((int) $user->id)->lockForUpdate()->firstOrFail();
$nextStep = (bool) $lockedUser->needs_password_reset ? 'verified' : 'password';
$lockedUser->email = $newEmail;
$lockedUser->email_verified_at = null;
$lockedUser->onboarding_step = $nextStep;
$lockedUser->save();
});
if ($oldEmail !== '' && $oldEmail !== $newEmail && ! User::isEmailLoginUpgradePlaceholder($oldEmail)) {
Mail::to($oldEmail)->queue(new EmailChangedSecurityAlertMail($newEmail));
}
return redirect()->route($nextStep === 'verified' ? 'setup.password.create' : 'setup.username.create')
->with('status', $nextStep === 'verified'
? 'Email saved. Continue with password setup.'
: 'Email saved. Continue with username setup.');
}
}

View File

@@ -90,6 +90,8 @@ class SetupUsernameController extends Controller
])->save();
});
$request->session()->forget('username_login_upgrade');
return redirect('/@' . strtolower($candidate));
}
}