Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.base
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ SESSION_TABLE=laravel_sessions
QUEUE_CONNECTION=database
SANCTUM_STATEFUL_DOMAINS=

# =============================================================================
# AUTH CONFIGURATION
# =============================================================================
AUTH_STRATEGY=local # 'local', 'ifixit'
AUTH_REQUIRE_CONSENT=true
AUTH_REQUIRE_API_TOKEN=true

# =============================================================================
# REDIS CONFIGURATION
# =============================================================================
Expand Down
7 changes: 7 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ SESSION_TABLE="$SESSION_TABLE"
QUEUE_CONNECTION="$QUEUE_CONNECTION"
SANCTUM_STATEFUL_DOMAINS="$SANCTUM_STATEFUL_DOMAINS"

# =============================================================================
# AUTH CONFIGURATION
# =============================================================================
AUTH_STRATEGY="$AUTH_STRATEGY" # 'local', 'ifixit'
AUTH_REQUIRE_CONSENT="$AUTH_REQUIRE_CONSENT"
AUTH_REQUIRE_API_TOKEN="$AUTH_REQUIRE_API_TOKEN"

# =============================================================================
# REDIS CONFIGURATION
# =============================================================================
Expand Down
5 changes: 1 addition & 4 deletions app/Http/Controllers/API/GroupController.php
Original file line number Diff line number Diff line change
Expand Up @@ -877,10 +877,7 @@ public function createGroupv2(Request $request): JsonResponse {
event(new \App\Events\ApproveGroup($group, $data));

// Notify the creator that their group was approved
Notification::send($user, new \App\Notifications\GroupConfirmed([
'group_name' => $name,
'group_url' => url('/group/view/'.$idGroup),
]));
Notification::send($user, new \App\Notifications\GroupConfirmed($group));

Log::info("Auto-approved group: $idGroup for user {$user->id} (role {$user->role})");
} else {
Expand Down
59 changes: 42 additions & 17 deletions app/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,48 @@ public static function middleware(): array
];
}

/**
* Show the application's login form or redirect to external auth
*/
public function showLoginForm(): View|\Illuminate\Http\RedirectResponse
{
$authManager = app(\App\Services\Auth\AuthStrategyManager::class);

if ($authManager->isUsingIFixitAuth()) {
// For iFixit, redirect to external login
return redirect($authManager->getLoginUrl(url('/dashboard')));
}

// For local auth, show login form
$stats = Fixometer::loginRegisterStats();
$deviceCount = array_key_exists(0, $stats['device_count_status']) ? $stats['device_count_status'][0]->counter : 0;

return view('auth.login', [
'co2Total' => $stats['waste_stats'][0]->powered_footprint + $stats['waste_stats'][0]->unpowered_footprint,
'wasteTotal' => $stats['waste_stats'][0]->powered_waste + $stats['waste_stats'][0]->unpowered_waste,
'partiesCount' => count($stats['allparties']),
'deviceCount' => $deviceCount,
]);
}

/**
* Handle logout for any auth strategy
*/
public function logout(): \Illuminate\Http\RedirectResponse
{
$authManager = app(\App\Services\Auth\AuthStrategyManager::class);
return $authManager->handleLogout();
}

/**
* Get login URL for current auth strategy
*/
public function getLoginUrl(string $callbackUrl = null): string
{
$authManager = app(\App\Services\Auth\AuthStrategyManager::class);
return $authManager->getLoginUrl($callbackUrl);
}

/**
* Override login from AuthenticateUsers
*
Expand Down Expand Up @@ -97,21 +139,4 @@ protected function validateLogin(Request $request): void
'my_time' => 'required|honeytime:1',
]);
}

/**
* Override showLoginForm from AuthenticateUsers
*/
public function showLoginForm(): View
{
$stats = Fixometer::loginRegisterStats();

$deviceCount = array_key_exists(0, $stats['device_count_status']) ? $stats['device_count_status'][0]->counter : 0;

return view('auth.login', [
'co2Total' => $stats['waste_stats'][0]->powered_footprint + $stats['waste_stats'][0]->unpowered_footprint,
'wasteTotal' => $stats['waste_stats'][0]->powered_waste + $stats['waste_stats'][0]->unpowered_waste,
'partiesCount' => count($stats['allparties']),
'deviceCount' => $deviceCount,
]);
}
}
40 changes: 33 additions & 7 deletions app/Http/Controllers/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,15 @@ public function postAdminEdit(Request $request): RedirectResponse
return redirect()->back()->with('message', __('profile.admin_success'));
}

public function recover(Request $request): View
public function recover(Request $request): View|RedirectResponse
{
$authManager = app(\App\Services\Auth\AuthStrategyManager::class);

// Password recovery not available for external auth
if ($authManager->isUsingIFixitAuth()) {
return redirect('/login')->with('warning', 'Password recovery is not available for iFixit users. Please use iFixit\'s password recovery system.');
}

$User = new User;

$email = $request->get('email');
Expand Down Expand Up @@ -460,8 +467,15 @@ public function recover(Request $request): View
]);
}

public function reset(Request $request)
public function reset(Request $request): View|RedirectResponse
{
$authManager = app(\App\Services\Auth\AuthStrategyManager::class);

// Password reset not available for external auth
if ($authManager->isUsingIFixitAuth()) {
return redirect('/login')->with('warning', 'Password reset is not available for iFixit users. Please use iFixit\'s password recovery system.');
}

$User = new User;
$user = null;

Expand Down Expand Up @@ -906,15 +920,18 @@ public function edit($id, Request $request)
}
}

public function logout(): RedirectResponse
{
Auth::logout();

return redirect('/login');
}

public function getRegister($hash = null)
{
// Check if we should redirect to external registration
$authManager = app(\App\Services\Auth\AuthStrategyManager::class);

if ($authManager->isUsingIFixitAuth()) {
return redirect($authManager->getRegisterUrl(url('/dashboard')));
}

// For local auth, show registration form
if (Auth::check() && Auth::user()->hasUserGivenConsent()) {
return redirect('dashboard');
}
Expand All @@ -936,6 +953,15 @@ public function getRegister($hash = null)
]);
}

/**
* Get registration URL for current auth strategy
*/
public function getRegisterUrl(string $callbackUrl = null): string
{
$authManager = app(\App\Services\Auth\AuthStrategyManager::class);
return $authManager->getRegisterUrl($callbackUrl);
}

public function postRegister(Request $request, $hash = null): RedirectResponse
{
$geocoder = new \App\Helpers\Geocoder();
Expand Down
77 changes: 77 additions & 0 deletions app/Http/Middleware/CentralizedAuth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace App\Http\Middleware;

use App\Services\Auth\AuthStrategyManager;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class CentralizedAuth
{
private AuthStrategyManager $authManager;

public function __construct(AuthStrategyManager $authManager)
{
$this->authManager = $authManager;
}

/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, ?string $mode = null): Response
{
$isOptionalAuth = $mode === 'optional';

// For optional auth mode, use default strategy but skip authentication enforcement
$authStrategy = $this->authManager->getStrategy($isOptionalAuth ? null : $mode);

// Handle optional auth mode: allow public access but run auth logic if authenticated
if ($isOptionalAuth) {
return $this->handleOptionalAuth($request, $next, $authStrategy);
}

// Standard authentication flow - enforce authentication and consent
return $this->handleRequiredAuth($request, $next, $authStrategy);
}

/**
* Handle optional authentication: allow public access but run auth logic if user is authenticated
*/
private function handleOptionalAuth(Request $request, Closure $next, $authStrategy): Response
{
// Check authentication before running controller logic
// This allows controllers to see if user is authenticated and act accordingly
$isAuthenticated = $authStrategy->isAuthenticated();

$response = $next($request);

if ($isAuthenticated) {
return $authStrategy->handlePostAuth($request, $response);
}

return $response;
}

/**
* Handle required authentication: enforce authentication and consent requirements
*/
private function handleRequiredAuth(Request $request, Closure $next, $authStrategy): Response
{
// Check if user is authenticated
if (!$authStrategy->isAuthenticated()) {
return $authStrategy->getUnauthenticatedResponse();
}

// Check if consent is required and given
if ($authStrategy->requiresConsent() && !$authStrategy->hasConsent()) {
return $authStrategy->getConsentResponse();
}

// Process the request and handle post-authentication tasks
$response = $next($request);
return $authStrategy->handlePostAuth($request, $response);
}
}
Loading