Initial Ladill Hosting app with Gitea deploy pipeline.
Deploy Ladill Hosting / deploy (push) Failing after 17s

Shared web hosting extracted from the platform monolith, with CI deploy
to /var/www/ladill-hosting matching Bird/Domains/Email.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-06 16:24:20 +00:00
co-authored by Cursor
commit e251a4cf60
367 changed files with 66268 additions and 0 deletions
@@ -0,0 +1,140 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
/**
* "Sign in with Ladill" Authorization Code + PKCE against auth.ladill.com.
* Establishes a local session + thin user mirror keyed by the OIDC `sub`.
*/
class SsoLoginController extends Controller
{
public function connect(Request $request): RedirectResponse
{
if (Auth::check()) {
return redirect()->route('hosting.dashboard');
}
$verifier = Str::random(64);
$state = Str::random(40);
$request->session()->put('sso.verifier', $verifier);
$request->session()->put('sso.state', $state);
$request->session()->put('sso.intended', $request->query('redirect', route('hosting.dashboard')));
$challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
$query = http_build_query([
'response_type' => 'code',
'client_id' => (string) config('services.ladill_sso.client_id'),
'redirect_uri' => (string) config('services.ladill_sso.redirect'),
'scope' => 'openid profile email',
'state' => $state,
'code_challenge' => $challenge,
'code_challenge_method' => 'S256',
]);
return redirect()->away(rtrim((string) config('services.ladill_sso.issuer'), '/').'/oauth/authorize?'.$query);
}
public function callback(Request $request): RedirectResponse
{
$authLogin = 'https://'.config('app.auth_domain').'/login';
if ($request->filled('error') || ! $request->filled('code')
|| $request->query('state') !== $request->session()->pull('sso.state')) {
return redirect()->away($authLogin);
}
$issuer = rtrim((string) config('services.ladill_sso.issuer'), '/');
$tokenRes = Http::asForm()->post($issuer.'/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => (string) config('services.ladill_sso.client_id'),
'client_secret' => (string) config('services.ladill_sso.client_secret'),
'redirect_uri' => (string) config('services.ladill_sso.redirect'),
'code' => (string) $request->query('code'),
'code_verifier' => (string) $request->session()->pull('sso.verifier'),
]);
if ($tokenRes->failed()) {
return redirect()->away($authLogin);
}
$claims = Http::withToken((string) $tokenRes->json('access_token'))->acceptJson()->get($issuer.'/oauth/userinfo');
if ($claims->failed() || ! $claims->json('sub')) {
return redirect()->away($authLogin);
}
$user = User::updateOrCreate(
['public_id' => (string) $claims->json('sub')],
[
'name' => $claims->json('name'),
'email' => $claims->json('email') ?: (string) $claims->json('sub').'@users.ladill.com',
'avatar_url' => $claims->json('picture'),
],
);
// Accept any pending team invites for this email on first sign-in.
\App\Models\HostingTeamMember::linkPendingInvitesFor($user);
Auth::login($user, remember: true);
$request->session()->regenerate();
$request->session()->forget('mailbox_link_banner_dismissed');
return redirect()->intended($request->session()->pull('sso.intended', route('hosting.dashboard')));
}
public function logout(Request $request): RedirectResponse
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
$home = 'https://'.config('app.platform_domain');
$endSession = 'https://'.config('app.auth_domain').'/logout/sso?redirect='.urlencode($home);
return redirect()->away($endSession);
}
public function frontchannelLogout(Request $request): Response|RedirectResponse
{
if ($this->shouldLogoutForMailbox($request)) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
}
// SLO chain: if the central sequencer passed a (ladill.com) return URL,
// continue the top-level redirect chain to the next app; else acknowledge.
$return = (string) $request->query('return', '');
$root = (string) config('app.platform_domain', 'ladill.com');
$host = parse_url($return, PHP_URL_HOST);
if (str_starts_with($return, 'https://') && is_string($host) && ($host === $root || str_ends_with($host, '.'.$root))) {
return redirect()->away($return);
}
return response('', 204);
}
private function shouldLogoutForMailbox(Request $request): bool
{
$mailbox = strtolower(trim((string) $request->query('mailbox', '')));
if ($mailbox === '' || ! str_contains($mailbox, '@')) {
return true;
}
$user = Auth::user();
if (! $user) {
return false;
}
return strtolower((string) $user->email) !== $mailbox;
}
}