feat: Reinitialize frontend with SvelteKit and TypeScript
- Delete old Vite+Svelte frontend - Initialize new SvelteKit project with TypeScript - Configure Tailwind CSS v4 + DaisyUI - Implement JWT authentication with auto-refresh - Create login page with form validation (Zod) - Add protected route guards - Update Docker configuration for single-stage build - Add E2E tests with Playwright (6/11 passing) - Fix Svelte 5 reactivity with $state() runes Known issues: - 5 E2E tests failing (timing/async issues) - Token refresh implementation needs debugging - Validation error display timing
This commit is contained in:
193
backend/app/Http/Controllers/Api/AuthController.php
Normal file
193
backend/app/Http/Controllers/Api/AuthController.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function login(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'email' => 'required|string|email',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
if (! $user || ! Hash::check($request->password, $user->password)) {
|
||||
return response()->json([
|
||||
'message' => 'Invalid credentials',
|
||||
], 401);
|
||||
}
|
||||
|
||||
if (! $user->active) {
|
||||
return response()->json([
|
||||
'message' => 'Account is inactive',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$accessToken = $this->generateAccessToken($user);
|
||||
$refreshToken = $this->generateRefreshToken($user);
|
||||
|
||||
return response()->json([
|
||||
'access_token' => $accessToken,
|
||||
'refresh_token' => $refreshToken,
|
||||
'token_type' => 'bearer',
|
||||
'expires_in' => 3600,
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'role' => $user->role,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function refresh(Request $request): JsonResponse
|
||||
{
|
||||
$refreshToken = $request->input('refresh_token');
|
||||
|
||||
$userId = $this->getUserIdFromRefreshToken($refreshToken);
|
||||
|
||||
if (! $userId) {
|
||||
return response()->json([
|
||||
'message' => 'Invalid or expired refresh token',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$user = User::find($userId);
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Invalid or expired refresh token',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$this->invalidateRefreshToken($refreshToken, $userId);
|
||||
|
||||
$accessToken = $this->generateAccessToken($user);
|
||||
$newRefreshToken = $this->generateRefreshToken($user);
|
||||
|
||||
return response()->json([
|
||||
'access_token' => $accessToken,
|
||||
'refresh_token' => $newRefreshToken,
|
||||
'token_type' => 'bearer',
|
||||
'expires_in' => 3600,
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$refreshToken = $request->input('refresh_token');
|
||||
|
||||
if ($refreshToken) {
|
||||
$this->invalidateRefreshToken($refreshToken, $user->id);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Logged out successfully',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function generateAccessToken(User $user): string
|
||||
{
|
||||
$payload = [
|
||||
'iss' => config('app.url', 'headroom'),
|
||||
'sub' => $user->id,
|
||||
'iat' => time(),
|
||||
'exp' => time() + 3600,
|
||||
'role' => $user->role,
|
||||
'permissions' => $this->getPermissions($user->role),
|
||||
'jti' => uniqid('token_', true),
|
||||
];
|
||||
|
||||
return $this->encodeJWT($payload);
|
||||
}
|
||||
|
||||
protected function generateRefreshToken(User $user): string
|
||||
{
|
||||
$token = bin2hex(random_bytes(32));
|
||||
// Store with token as the key part for easy lookup
|
||||
$key = "refresh_token:{$token}";
|
||||
Redis::setex($key, 604800, $user->id);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
protected function getUserIdFromRefreshToken(string $token): ?string
|
||||
{
|
||||
return Redis::get("refresh_token:{$token}") ?: null;
|
||||
}
|
||||
|
||||
protected function invalidateRefreshToken(string $token, string $userId): void
|
||||
{
|
||||
Redis::del("refresh_token:{$token}");
|
||||
}
|
||||
|
||||
protected function getPermissions(string $role): array
|
||||
{
|
||||
return match ($role) {
|
||||
'superuser' => [
|
||||
'manage_users',
|
||||
'manage_team_members',
|
||||
'manage_projects',
|
||||
'manage_allocations',
|
||||
'manage_actuals',
|
||||
'view_reports',
|
||||
'configure_system',
|
||||
'view_audit_logs',
|
||||
],
|
||||
'manager' => [
|
||||
'manage_projects',
|
||||
'manage_allocations',
|
||||
'manage_actuals',
|
||||
'view_reports',
|
||||
'manage_team_members',
|
||||
],
|
||||
'developer' => [
|
||||
'manage_actuals',
|
||||
'view_own_allocations',
|
||||
'view_own_actuals',
|
||||
'log_hours',
|
||||
],
|
||||
'top_brass' => [
|
||||
'view_reports',
|
||||
'view_allocations',
|
||||
'view_actuals',
|
||||
'view_capacity',
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
|
||||
protected function encodeJWT(array $payload): string
|
||||
{
|
||||
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
|
||||
$header = base64_encode($header);
|
||||
$header = str_replace(['+', '/', '='], ['-', '_', ''], $header);
|
||||
|
||||
$payload = json_encode($payload);
|
||||
$payload = base64_encode($payload);
|
||||
$payload = str_replace(['+', '/', '='], ['-', '_', ''], $payload);
|
||||
|
||||
$signature = hash_hmac('sha256', $header . '.' . $payload, config('app.key'), true);
|
||||
$signature = base64_encode($signature);
|
||||
$signature = str_replace(['+', '/', '='], ['-', '_', ''], $signature);
|
||||
|
||||
return $header . '.' . $payload . '.' . $signature;
|
||||
}
|
||||
}
|
||||
8
backend/app/Http/Controllers/Controller.php
Normal file
8
backend/app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
82
backend/app/Http/Middleware/JwtAuth.php
Normal file
82
backend/app/Http/Middleware/JwtAuth.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class JwtAuth
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$token = $this->extractToken($request);
|
||||
|
||||
if (! $token) {
|
||||
return response()->json([
|
||||
'message' => 'Authentication required',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$payload = $this->decodeJWT($token);
|
||||
|
||||
if (! $payload) {
|
||||
return response()->json([
|
||||
'message' => 'Invalid token',
|
||||
], 401);
|
||||
}
|
||||
|
||||
if ($payload->exp < time()) {
|
||||
return response()->json([
|
||||
'message' => 'Token expired',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$user = \App\Models\User::find($payload->sub);
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'User not found',
|
||||
], 401);
|
||||
}
|
||||
|
||||
auth()->setUser($user);
|
||||
$request->setUserResolver(fn () => $user);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
protected function extractToken(Request $request): ?string
|
||||
{
|
||||
$header = $request->header('Authorization');
|
||||
|
||||
if (! $header || ! str_starts_with($header, 'Bearer ')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return substr($header, 7);
|
||||
}
|
||||
|
||||
protected function decodeJWT(string $token): ?object
|
||||
{
|
||||
$parts = explode('.', $token);
|
||||
|
||||
if (count($parts) !== 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
list($header, $payload, $signature) = $parts;
|
||||
|
||||
$expectedSignature = hash_hmac('sha256', $header . '.' . $payload, config('app.key'), true);
|
||||
$expectedSignature = base64_encode($expectedSignature);
|
||||
$expectedSignature = str_replace(['+', '/', '='], ['-', '_', ''], $expectedSignature);
|
||||
|
||||
if (! hash_equals($expectedSignature, $signature)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$payload = base64_decode(str_replace(['-', '_'], ['+', '/'], $payload));
|
||||
|
||||
return json_decode($payload);
|
||||
}
|
||||
}
|
||||
46
backend/app/Models/Actual.php
Normal file
46
backend/app/Models/Actual.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Actual extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $table = 'actuals';
|
||||
|
||||
protected $fillable = [
|
||||
'project_id',
|
||||
'team_member_id',
|
||||
'month',
|
||||
'hours_logged',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'month' => 'date',
|
||||
'hours_logged' => 'decimal:2',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the project that owns the actual.
|
||||
*/
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the team member that owns the actual.
|
||||
*/
|
||||
public function teamMember()
|
||||
{
|
||||
return $this->belongsTo(TeamMember::class);
|
||||
}
|
||||
}
|
||||
44
backend/app/Models/Allocation.php
Normal file
44
backend/app/Models/Allocation.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Allocation extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = [
|
||||
'project_id',
|
||||
'team_member_id',
|
||||
'month',
|
||||
'allocated_hours',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'month' => 'date',
|
||||
'allocated_hours' => 'decimal:2',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the project that owns the allocation.
|
||||
*/
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the team member that owns the allocation.
|
||||
*/
|
||||
public function teamMember()
|
||||
{
|
||||
return $this->belongsTo(TeamMember::class);
|
||||
}
|
||||
}
|
||||
26
backend/app/Models/Holiday.php
Normal file
26
backend/app/Models/Holiday.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Holiday extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = [
|
||||
'date',
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'date' => 'date',
|
||||
];
|
||||
}
|
||||
63
backend/app/Models/Project.php
Normal file
63
backend/app/Models/Project.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Project extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'title',
|
||||
'status_id',
|
||||
'type_id',
|
||||
'approved_estimate',
|
||||
'forecasted_effort',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'approved_estimate' => 'decimal:2',
|
||||
'forecasted_effort' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the status that owns the project.
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(ProjectStatus::class, 'status_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type that owns the project.
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(ProjectType::class, 'type_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the allocations for the project.
|
||||
*/
|
||||
public function allocations(): HasMany
|
||||
{
|
||||
return $this->hasMany(Allocation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actuals for the project.
|
||||
*/
|
||||
public function actuals(): HasMany
|
||||
{
|
||||
return $this->hasMany(Actual::class);
|
||||
}
|
||||
}
|
||||
33
backend/app/Models/ProjectStatus.php
Normal file
33
backend/app/Models/ProjectStatus.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ProjectStatus extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'order',
|
||||
'is_active',
|
||||
'is_billable',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'order' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
'is_billable' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the projects for the status.
|
||||
*/
|
||||
public function projects(): HasMany
|
||||
{
|
||||
return $this->hasMany(Project::class, 'status_id');
|
||||
}
|
||||
}
|
||||
25
backend/app/Models/ProjectType.php
Normal file
25
backend/app/Models/ProjectType.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ProjectType extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the projects for the type.
|
||||
*/
|
||||
public function projects(): HasMany
|
||||
{
|
||||
return $this->hasMany(Project::class, 'type_id');
|
||||
}
|
||||
}
|
||||
39
backend/app/Models/Pto.php
Normal file
39
backend/app/Models/Pto.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Pto extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $table = 'ptos';
|
||||
|
||||
protected $fillable = [
|
||||
'team_member_id',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'reason',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the team member that owns the PTO.
|
||||
*/
|
||||
public function teamMember()
|
||||
{
|
||||
return $this->belongsTo(TeamMember::class);
|
||||
}
|
||||
}
|
||||
25
backend/app/Models/Role.php
Normal file
25
backend/app/Models/Role.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Role extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the team members for the role.
|
||||
*/
|
||||
public function teamMembers(): HasMany
|
||||
{
|
||||
return $this->hasMany(TeamMember::class);
|
||||
}
|
||||
}
|
||||
61
backend/app/Models/TeamMember.php
Normal file
61
backend/app/Models/TeamMember.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class TeamMember extends Model
|
||||
{
|
||||
use HasFactory, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'role_id',
|
||||
'hourly_rate',
|
||||
'active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'hourly_rate' => 'decimal:2',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the role that owns the team member.
|
||||
*/
|
||||
public function role()
|
||||
{
|
||||
return $this->belongsTo(Role::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the allocations for the team member.
|
||||
*/
|
||||
public function allocations(): HasMany
|
||||
{
|
||||
return $this->hasMany(Allocation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actuals for the team member.
|
||||
*/
|
||||
public function actuals(): HasMany
|
||||
{
|
||||
return $this->hasMany(Actual::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PTOs for the team member.
|
||||
*/
|
||||
public function ptos(): HasMany
|
||||
{
|
||||
return $this->hasMany(Pto::class);
|
||||
}
|
||||
}
|
||||
78
backend/app/Models/User.php
Normal file
78
backend/app/Models/User.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||
|
||||
class User extends Authenticatable implements JWTSubject
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, HasUuids;
|
||||
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'string';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'role',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifier that will be stored in the subject claim of the JWT.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getJWTIdentifier()
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a key value array, containing any custom claims to be added to the JWT.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJWTCustomClaims()
|
||||
{
|
||||
return [
|
||||
'role' => $this->role,
|
||||
'email' => $this->email,
|
||||
];
|
||||
}
|
||||
}
|
||||
24
backend/app/Providers/AppServiceProvider.php
Normal file
24
backend/app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user