Files
alrahma_sunday_school_api/app/Services/Auth/AuthSessionService.php
T
2026-06-04 02:41:08 -04:00

210 lines
6.4 KiB
PHP

<?php
namespace App\Services\Auth;
use App\Models\Configuration;
use App\Services\ApplicationUrlService;
use App\Models\LoginActivity;
use App\Models\Preferences;
use App\Models\Role;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
/**
* Session-auth flow: session keys, dashboards, redirects, and preferences.
*/
class AuthSessionService
{
private const FALLBACK_DASHBOARD = '/landing_page/guest_dashboard';
public function __construct(
private ApplicationUrlService $urls,
) {
}
/**
* Sanitizes login redirects to same-host relative paths only.
*/
public function sanitizeRedirectTarget(string $redirectTo): ?string
{
$redirectTo = trim($redirectTo);
if ($redirectTo === '') {
return null;
}
if (preg_match('#^https?://#i', $redirectTo)) {
$appHost = (string) parse_url($this->urls->docsHomeUrl(), PHP_URL_HOST);
$targetHost = (string) parse_url($redirectTo, PHP_URL_HOST);
$targetPath = (string) parse_url($redirectTo, PHP_URL_PATH);
$targetQuery = (string) parse_url($redirectTo, PHP_URL_QUERY);
if ($appHost === '' || $targetHost === '' || strcasecmp($appHost, $targetHost) !== 0) {
return null;
}
$redirectTo = $targetPath !== '' ? $targetPath : '/';
if ($targetQuery !== '') {
$redirectTo .= '?'.$targetQuery;
}
}
if (! str_starts_with($redirectTo, '/')) {
$redirectTo = '/'.ltrim($redirectTo, '/');
}
if (str_starts_with($redirectTo, '//')) {
return null;
}
return $redirectTo;
}
/**
* Highest-priority dashboard route matching any of the role names/slugs.
*
* @param list<string> $roleNames
*/
public function dashboardRouteForRoles(array $roleNames): string
{
if ($roleNames === []) {
return self::FALLBACK_DASHBOARD;
}
$rows = Role::findByNamesOrSlugs($roleNames);
if ($rows !== []) {
$route = $rows[0]->dashboard_route ?? null;
if (is_string($route) && $route !== '') {
return $route;
}
}
return self::FALLBACK_DASHBOARD;
}
/**
* Apply stored style preferences into the session.
*/
public function applyStylePreferencesToSession(int $userId): void
{
if ($userId <= 0) {
return;
}
$prefs = Preferences::query()->where('user_id', $userId)->first();
if (! $prefs) {
return;
}
$styleColor = (string) ($prefs->style_color ?? '');
if ($styleColor !== '') {
session()->put('style_color', $styleColor);
}
$menuColor = (string) ($prefs->menu_color ?? '');
if ($menuColor === 'custom') {
session()->put('menu_color', 'custom');
session()->put('menu_custom_bg', (string) ($prefs->menu_custom_bg ?? '#0f172a'));
session()->put('menu_custom_text', (string) ($prefs->menu_custom_text ?? '#ffffff'));
session()->put('menu_custom_mode', (string) ($prefs->menu_custom_mode ?? 'dark'));
} elseif ($menuColor !== '') {
session()->put('menu_color', $menuColor);
}
}
/**
* Populate Laravel web auth and session state for the web portal.
*
* @param list<string> $roleNames
*/
public function writeAuthenticatedSession(User $user, array $roleNames): void
{
Auth::guard('web')->login($user);
$schoolYear = Configuration::getConfig('school_year');
$semester = Configuration::getConfig('semester');
$teacherContext = $user->teacherSessionContext();
session([
'user_id' => $user->id,
'user_email' => $user->email,
'user_name' => trim(($user->firstname ?? '').' '.($user->lastname ?? '')),
'user_type' => $user->user_type ?? null,
'is_logged_in' => true,
'login_time' => time(),
'last_activity' => time(),
'roles' => $roleNames,
'semester' => $semester,
'school_year' => $schoolYear,
'class_section_id' => $teacherContext['class_section_id'],
'class_section_name' => $teacherContext['class_section_name'],
]);
$this->applyStylePreferencesToSession((int) $user->id);
}
/**
* Close open login_activity rows without logout_time for this user.
*/
public function logLogout(?int $userId, ?string $email): void
{
if (! $userId || ! $email) {
return;
}
LoginActivity::query()
->where('user_id', $userId)
->whereNull('logout_time')
->update([
'logout_time' => utc_now(),
'email' => $email,
]);
}
/**
* Determine next navigation after credentials validated (single vs multi-role).
*
* @return array{kind: string, redirect_url?: string|null, roles?: array<int, string>, reason?: string}
*/
public function resolvePostLoginDestination(User $user, ?string $sanitizedRedirect): array
{
$roleNames = $user->roleNames();
if ($roleNames === []) {
Log::notice('session_login: user has no roles', ['user_id' => $user->id]);
return [
'kind' => 'no_roles',
'reason' => 'Role not assigned. Please contact support.',
];
}
$this->writeAuthenticatedSession($user, $roleNames);
if (count($roleNames) === 1) {
session()->put('role', $roleNames[0]);
if ($sanitizedRedirect !== null) {
return ['kind' => 'redirect', 'redirect_url' => $sanitizedRedirect];
}
return [
'kind' => 'redirect',
'redirect_url' => $this->dashboardRouteForRoles([$roleNames[0]]),
];
}
if ($sanitizedRedirect !== null) {
session()->put('post_login_redirect', $sanitizedRedirect);
} else {
session()->forget('post_login_redirect');
}
return [
'kind' => 'select_role',
'roles' => array_values($roleNames),
'redirect_url' => $this->urls->webSelectRoleUrl(false),
];
}
}