Skip to content

Commit 8a6b3be

Browse files
committed
auth contracts
1 parent af3f5d4 commit 8a6b3be

File tree

7 files changed

+234
-31
lines changed

7 files changed

+234
-31
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace App\Contracts;
4+
5+
use App\Models\User;
6+
use Illuminate\Http\Request;
7+
8+
interface AuthServiceContract
9+
{
10+
public function login(Request $request, User $user): array;
11+
public function logout(Request $request): void;
12+
public function handleCallback(Request $request, User $user): array;
13+
public function getDevices(Request $request): array;
14+
public function disconnectDevice(Request $request): void;
15+
}

app/Http/Controllers/AuthController.php

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace App\Http\Controllers;
44

5-
use App\Helpers\Utils;
65
use App\Models\User;
76
use App\Models\UserProvider;
87
use Illuminate\Auth\Events\PasswordReset;
@@ -19,9 +18,14 @@
1918
use Illuminate\Validation\ValidationException;
2019
use Illuminate\View\View;
2120
use Laravel\Socialite\Facades\Socialite;
21+
use App\Contracts\AuthServiceContract;
2222

2323
class AuthController extends Controller
2424
{
25+
public function __construct(
26+
private AuthServiceContract $authService
27+
) {}
28+
2529
/**
2630
* Register new user
2731
*/
@@ -113,14 +117,28 @@ public function callback(Request $request, string $provider): View
113117
$user = $userProvider->user;
114118
}
115119

116-
Auth::login($user, true);
117-
$request->session()->regenerate();
120+
$message = [
121+
'ok' => true,
122+
'provider' => $provider,
123+
];
124+
125+
// If the guard is web, we will use the default login process
126+
if (config('auth.defaults.guard') === 'web') {
127+
Auth::login($user, true);
128+
$request->session()->regenerate();
129+
} else {
130+
// If the guard is api, we will use the token based authentication
131+
$token = $user->createDeviceToken(
132+
device: $request->deviceName(),
133+
ip: $request->ip(),
134+
remember: $request->input('remember', false)
135+
);
136+
137+
$message['token'] = $token;
138+
}
118139

119140
return view('oauth', [
120-
'message' => [
121-
'ok' => true,
122-
'provider' => $provider,
123-
],
141+
'message' => $message,
124142
]);
125143
}
126144

@@ -135,25 +153,25 @@ public function login(Request $request): JsonResponse
135153
'password' => ['required', 'string'],
136154
]);
137155

138-
if (!Auth::attempt($request->only('email', 'password'), $request->remember)) {
156+
$user = User::select(['id', 'password'])->where('email', $request->email)->first();
157+
158+
if (!$user) {
139159
throw ValidationException::withMessages([
140160
'email' => __('auth.failed'),
141161
]);
142162
}
143163

144-
$request->session()->regenerate();
164+
$result = $this->authService->login($request, $user);
145165

146-
return response()->json([
147-
'ok' => true,
148-
]);
166+
return response()->json($result);
149167
}
150168

151169
/**
152170
* Revoke token; only remove token that is used to perform logout (i.e. will not revoke all tokens)
153171
*/
154172
public function logout(Request $request): JsonResponse
155173
{
156-
Auth::logout();
174+
$this->authService->logout($request);
157175

158176
return response()->json([
159177
'ok' => true,
@@ -294,21 +312,7 @@ public function verificationNotification(Request $request): JsonResponse
294312
*/
295313
public function devices(Request $request): JsonResponse
296314
{
297-
$user = $request->user();
298-
299-
$currentSessionId = $request->session()->getId();
300-
301-
$devices = $user->sessions()
302-
->select(['id as key', 'ip_address as ip', 'user_agent as name', 'last_activity'])
303-
->orderBy('last_activity', 'DESC')
304-
->get()
305-
->map(function ($device) use ($currentSessionId) {
306-
$device->is_current = $currentSessionId === $device->key;
307-
$device->name = Utils::getDeviceNameFromDetector(Utils::getDeviceDetectorByUserAgent($device->name));
308-
$device->last_used_at = now()->parse($device->last_activity);
309-
310-
return $device;
311-
});
315+
$devices = $this->authService->getDevices($request);
312316

313317
return response()->json([
314318
'ok' => true,
@@ -322,11 +326,10 @@ public function devices(Request $request): JsonResponse
322326
public function deviceDisconnect(Request $request): JsonResponse
323327
{
324328
$request->validate([
325-
'key' => 'required|size:40',
329+
'key' => 'required|string',
326330
]);
327331

328-
$user = $request->user();
329-
$user->sessions()->where('id', $request->key)->delete();
332+
$this->authService->disconnectDevice($request);
330333

331334
return response()->json([
332335
'ok' => true,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Providers;
4+
5+
use App\Contracts\AuthServiceContract;
6+
use App\Services\Auth\AuthServiceFactory;
7+
use Illuminate\Support\ServiceProvider;
8+
9+
class AuthServiceProvider extends ServiceProvider
10+
{
11+
public function register(): void
12+
{
13+
$this->app->bind(AuthServiceContract::class, function () {
14+
return AuthServiceFactory::create();
15+
});
16+
}
17+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace App\Services\Auth;
4+
5+
use App\Contracts\AuthServiceContract;
6+
use App\Models\User;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Support\Facades\Crypt;
9+
use Illuminate\Support\Facades\Hash;
10+
use Illuminate\Validation\ValidationException;
11+
12+
class ApiAuthService implements AuthServiceContract
13+
{
14+
public function login(Request $request, User $user): array
15+
{
16+
if (!Hash::check($request->password, $user->password)) {
17+
throw ValidationException::withMessages([
18+
'email' => __('auth.failed'),
19+
]);
20+
}
21+
22+
$token = $user->createDeviceToken(
23+
device: $request->deviceName(),
24+
ip: $request->ip(),
25+
remember: $request->input('remember', false)
26+
);
27+
28+
return [
29+
'ok' => true,
30+
'token' => $token,
31+
];
32+
}
33+
34+
public function logout(Request $request): void
35+
{
36+
$request->user()->currentAccessToken()->delete();
37+
}
38+
39+
public function handleCallback(Request $request, User $user): array
40+
{
41+
$token = $user->createDeviceToken(
42+
device: $request->deviceName(),
43+
ip: $request->ip(),
44+
remember: $request->input('remember', false)
45+
);
46+
47+
return [
48+
'ok' => true,
49+
'provider' => $request->route('provider'),
50+
'token' => $token,
51+
];
52+
}
53+
54+
public function getDevices(Request $request): array
55+
{
56+
$user = $request->user();
57+
$currentToken = $user->currentAccessToken();
58+
59+
$devices = $user->tokens()
60+
->select('id', 'name', 'ip', 'last_used_at')
61+
->orderBy('last_used_at', 'DESC')
62+
->get()
63+
->map(function ($device) use ($currentToken) {
64+
$device->key = Crypt::encryptString($device->id);
65+
$device->is_current = $currentToken->id === $device->id;
66+
unset($device->id);
67+
68+
return $device;
69+
});
70+
71+
return $devices->toArray();
72+
}
73+
74+
public function disconnectDevice(Request $request): void
75+
{
76+
$id = (int) Crypt::decryptString($request->key);
77+
78+
if (!empty($id)) {
79+
$request->user()->tokens()->where('id', $id)->delete();
80+
}
81+
}
82+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Services\Auth;
4+
5+
use App\Contracts\AuthServiceContract;
6+
7+
class AuthServiceFactory
8+
{
9+
public static function create(?string $guard = null): AuthServiceContract
10+
{
11+
$guard = $guard ?? config('auth.defaults.guard');
12+
13+
return match ($guard) {
14+
'web' => new WebAuthService(),
15+
'api' => new ApiAuthService(),
16+
default => throw new \InvalidArgumentException("Unsupported guard: {$guard}"),
17+
};
18+
}
19+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace App\Services\Auth;
4+
5+
use App\Contracts\AuthServiceContract;
6+
use App\Helpers\Utils;
7+
use App\Models\User;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Facades\Auth;
10+
11+
class WebAuthService implements AuthServiceContract
12+
{
13+
public function login(Request $request, User $user): array
14+
{
15+
if (!Auth::attempt($request->only('email', 'password'), $request->remember)) {
16+
throw ValidationException::withMessages([
17+
'email' => __('auth.failed'),
18+
]);
19+
}
20+
21+
$request->session()->regenerate();
22+
23+
return ['ok' => true];
24+
}
25+
26+
public function logout(Request $request): void
27+
{
28+
Auth::logout();
29+
}
30+
31+
public function handleCallback(Request $request, User $user): array
32+
{
33+
Auth::login($user, true);
34+
$request->session()->regenerate();
35+
36+
return [
37+
'ok' => true,
38+
'provider' => $request->route('provider'),
39+
];
40+
}
41+
42+
public function getDevices(Request $request): array
43+
{
44+
$user = $request->user();
45+
$currentSessionId = $request->session()->getId();
46+
47+
$devices = $user->sessions()
48+
->select(['id as key', 'ip_address as ip', 'user_agent as name', 'last_activity'])
49+
->orderBy('last_activity', 'DESC')
50+
->get()
51+
->map(function ($device) use ($currentSessionId) {
52+
$device->is_current = $currentSessionId === $device->key;
53+
$device->name = Utils::getDeviceNameFromDetector(Utils::getDeviceDetectorByUserAgent($device->name));
54+
$device->last_used_at = now()->parse($device->last_activity);
55+
56+
return $device;
57+
});
58+
59+
return $devices->toArray();
60+
}
61+
62+
public function disconnectDevice(Request $request): void
63+
{
64+
$request->user()->sessions()->where('id', $request->key)->delete();
65+
}
66+
}

bootstrap/providers.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
return [
44
App\Providers\AppServiceProvider::class,
5+
App\Providers\AuthServiceProvider::class,
56
Spatie\Permission\PermissionServiceProvider::class,
67
];

0 commit comments

Comments
 (0)