Skip to content

Commit 47911d2

Browse files
authored
Merge pull request #3 from MujahidAbbas/feature/user-authentication
feat: Add user authentication with Laravel Breeze
2 parents 3befe12 + db33d6e commit 47911d2

File tree

79 files changed

+2508
-505
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+2508
-505
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Auth;
4+
5+
use App\Http\Controllers\Controller;
6+
use Illuminate\Auth\Events\Verified;
7+
use Illuminate\Foundation\Auth\EmailVerificationRequest;
8+
use Illuminate\Http\RedirectResponse;
9+
10+
class VerifyEmailController extends Controller
11+
{
12+
/**
13+
* Mark the authenticated user's email address as verified.
14+
*/
15+
public function __invoke(EmailVerificationRequest $request): RedirectResponse
16+
{
17+
if ($request->user()->hasVerifiedEmail()) {
18+
return redirect()->intended(route('projects.index', absolute: false).'?verified=1');
19+
}
20+
21+
if ($request->user()->markEmailAsVerified()) {
22+
event(new Verified($request->user()));
23+
}
24+
25+
return redirect()->intended(route('projects.index', absolute: false).'?verified=1');
26+
}
27+
}

app/Http/Controllers/Controller.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace App\Http\Controllers;
44

5+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
6+
57
abstract class Controller
68
{
7-
//
9+
use AuthorizesRequests;
810
}

app/Http/Controllers/ProjectExportController.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@ class ProjectExportController extends Controller
1212
{
1313
public function projectKit(Project $project, ProjectKitExporter $exporter): StreamedResponse
1414
{
15-
// Authorization disabled for now - add back when auth is set up
16-
// $this->authorize('view', $project);
15+
$this->authorize('view', $project);
1716

1817
$export = $exporter->export(
1918
$project,
20-
1 // Would be auth()->id() with real auth
19+
auth()->id()
2120
);
2221

2322
return Storage::disk($export->disk)->download($export->path, $export->filename);
2423
}
2524

2625
public function tasksJson(Project $project): JsonResponse
2726
{
28-
// Authorization disabled for now - add back when auth is set up
29-
// $this->authorize('view', $project);
27+
$this->authorize('view', $project);
3028

3129
$project->load('tasks');
3230

app/Livewire/Actions/Logout.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace App\Livewire\Actions;
4+
5+
use Illuminate\Support\Facades\Auth;
6+
use Illuminate\Support\Facades\Session;
7+
8+
class Logout
9+
{
10+
/**
11+
* Log the current user out of the application.
12+
*/
13+
public function __invoke(): void
14+
{
15+
Auth::guard('web')->logout();
16+
17+
Session::invalidate();
18+
Session::regenerateToken();
19+
}
20+
}

app/Livewire/Forms/LoginForm.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Livewire\Forms;
4+
5+
use Illuminate\Auth\Events\Lockout;
6+
use Illuminate\Support\Facades\Auth;
7+
use Illuminate\Support\Facades\RateLimiter;
8+
use Illuminate\Support\Str;
9+
use Illuminate\Validation\ValidationException;
10+
use Livewire\Attributes\Validate;
11+
use Livewire\Form;
12+
13+
class LoginForm extends Form
14+
{
15+
#[Validate('required|string|email')]
16+
public string $email = '';
17+
18+
#[Validate('required|string')]
19+
public string $password = '';
20+
21+
#[Validate('boolean')]
22+
public bool $remember = false;
23+
24+
/**
25+
* Attempt to authenticate the request's credentials.
26+
*
27+
* @throws \Illuminate\Validation\ValidationException
28+
*/
29+
public function authenticate(): void
30+
{
31+
$this->ensureIsNotRateLimited();
32+
33+
if (! Auth::attempt($this->only(['email', 'password']), $this->remember)) {
34+
RateLimiter::hit($this->throttleKey());
35+
36+
throw ValidationException::withMessages([
37+
'form.email' => trans('auth.failed'),
38+
]);
39+
}
40+
41+
RateLimiter::clear($this->throttleKey());
42+
}
43+
44+
/**
45+
* Ensure the authentication request is not rate limited.
46+
*/
47+
protected function ensureIsNotRateLimited(): void
48+
{
49+
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
50+
return;
51+
}
52+
53+
event(new Lockout(request()));
54+
55+
$seconds = RateLimiter::availableIn($this->throttleKey());
56+
57+
throw ValidationException::withMessages([
58+
'form.email' => trans('auth.throttle', [
59+
'seconds' => $seconds,
60+
'minutes' => ceil($seconds / 60),
61+
]),
62+
]);
63+
}
64+
65+
/**
66+
* Get the authentication rate limiting throttle key.
67+
*/
68+
protected function throttleKey(): string
69+
{
70+
return Str::transliterate(Str::lower($this->email).'|'.request()->ip());
71+
}
72+
}

app/Livewire/Projects/Index.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ public function createProject(): void
4848

4949
$this->validate($rules);
5050

51-
// For now, use user_id = 1 (test user) since we don't have auth yet
5251
$project = Project::create([
53-
'user_id' => 1,
52+
'user_id' => auth()->id(),
5453
'name' => $this->name,
5554
'idea' => $this->idea,
5655
'preferred_provider' => $this->selectedProvider,
@@ -64,8 +63,8 @@ public function createProject(): void
6463

6564
public function render()
6665
{
67-
// For now, show all projects (would filter by auth user later)
6866
$projects = Project::query()
67+
->where('user_id', auth()->id())
6968
->where('status', ProjectStatus::Active)
7069
->latest()
7170
->get();

app/Livewire/Projects/SettingsModal.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
use App\Enums\AiProvider;
66
use App\Livewire\Concerns\ManagesProviderSelection;
77
use App\Models\Project;
8+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
89
use Livewire\Attributes\Computed;
910
use Livewire\Attributes\On;
1011
use Livewire\Component;
1112

1213
class SettingsModal extends Component
1314
{
15+
use AuthorizesRequests;
1416
use ManagesProviderSelection;
1517

1618
public bool $show = false;
@@ -20,8 +22,10 @@ class SettingsModal extends Component
2022
#[On('openSettings')]
2123
public function open(string $projectId): void
2224
{
23-
$this->projectId = $projectId;
2425
$project = Project::findOrFail($projectId);
26+
$this->authorize('update', $project);
27+
28+
$this->projectId = $projectId;
2529

2630
$this->initializeFromProject(
2731
$project->preferred_provider ?? '',
@@ -42,6 +46,8 @@ public function save(): void
4246
$this->validate($this->getProviderValidationRules());
4347

4448
$project = Project::findOrFail($this->projectId);
49+
$this->authorize('update', $project);
50+
4551
$project->update([
4652
'preferred_provider' => $this->selectedProvider,
4753
'preferred_model' => $this->getFinalModel(),

app/Livewire/Projects/Tabs/Export.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44

55
use App\Enums\DocumentType;
66
use App\Models\Project;
7+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
78
use Livewire\Attributes\Computed;
89
use Livewire\Component;
910

1011
class Export extends Component
1112
{
13+
use AuthorizesRequests;
14+
1215
public string $projectId;
1316

1417
public function mount(string $projectId): void
1518
{
19+
$project = Project::findOrFail($projectId);
20+
$this->authorize('view', $project);
21+
1622
$this->projectId = $projectId;
1723
}
1824

app/Livewire/Projects/Tabs/KanbanBoard.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Models\Project;
88
use App\Models\Task;
99
use App\Models\TaskSet;
10+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
1011
use Filament\Actions\Concerns\InteractsWithActions;
1112
use Filament\Actions\Contracts\HasActions;
1213
use Filament\Actions\CreateAction;
@@ -29,6 +30,7 @@
2930

3031
class KanbanBoard extends Component implements HasActions, HasBoard, HasForms
3132
{
33+
use AuthorizesRequests;
3234
use InteractsWithActions {
3335
InteractsWithBoard::getDefaultActionRecord insteadof InteractsWithActions;
3436
}
@@ -39,6 +41,9 @@ class KanbanBoard extends Component implements HasActions, HasBoard, HasForms
3941

4042
public function mount(string $projectId): void
4143
{
44+
$project = Project::findOrFail($projectId);
45+
$this->authorize('view', $project);
46+
4247
$this->projectId = $projectId;
4348
}
4449

app/Livewire/Projects/Tabs/Prd.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
use App\Enums\DocumentType;
66
use App\Models\Document;
77
use App\Models\DocumentVersion;
8+
use App\Models\Project;
9+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
810
use Livewire\Attributes\Computed;
911
use Livewire\Attributes\On;
1012
use Livewire\Component;
1113

1214
class Prd extends Component
1315
{
16+
use AuthorizesRequests;
17+
1418
public string $projectId;
1519

1620
public string $content = '';
@@ -19,6 +23,9 @@ class Prd extends Component
1923

2024
public function mount(string $projectId): void
2125
{
26+
$project = Project::findOrFail($projectId);
27+
$this->authorize('view', $project);
28+
2229
$this->projectId = $projectId;
2330
$this->loadContent();
2431
}
@@ -58,7 +65,7 @@ public function save(): void
5865
// Create a new version (append-only)
5966
$version = DocumentVersion::create([
6067
'document_id' => $document->id,
61-
'created_by' => 1, // Would be auth()->id() with real auth
68+
'created_by' => auth()->id(),
6269
'content_md' => $this->content,
6370
]);
6471

0 commit comments

Comments
 (0)