210 lines
6.4 KiB
PHP
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;
|
|
|
|
/**
|
|
* CodeIgniter `AuthController` web (session) flow: session keys, dashboards, redirects, preferences.
|
|
*/
|
|
class AuthSessionService
|
|
{
|
|
private const FALLBACK_DASHBOARD = '/landing_page/guest_dashboard';
|
|
|
|
public function __construct(
|
|
private ApplicationUrlService $urls,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Same-host relative redirect sanitization as CI `sanitizeRedirectTarget`.
|
|
*/
|
|
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 CI-style preference keys into session (style/menu colors).
|
|
*/
|
|
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 + CI-compatible session keys.
|
|
*
|
|
* @param list<string> $roleNames
|
|
*/
|
|
public function writeCiAuthenticatedSession(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 row(s) without logout_time for this user (CI parity).
|
|
*/
|
|
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->roleNamesLikeCodeIgniter();
|
|
|
|
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->writeCiAuthenticatedSession($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),
|
|
];
|
|
}
|
|
}
|