Files
fotospiel-app/app/Http/Controllers/Api/Tenant/TenantAdminTokenController.php
Codex Agent 3de1d3deab
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Misc unrelated updates
2026-01-12 10:31:31 +01:00

251 lines
7.2 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Tenant;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\TenantAdminTokenRequest;
use App\Models\EventMember;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class TenantAdminTokenController extends Controller
{
public function store(TenantAdminTokenRequest $request): JsonResponse
{
$credentials = $request->credentials();
/** @var User|null $user */
$user = User::query()->when(
isset($credentials['email']),
fn ($query) => $query->where('email', $credentials['email']),
fn ($query) => $query->where('username', $credentials['username'] ?? null)
)->first();
if (! $user || ! Hash::check($credentials['password'], (string) $user->password)) {
throw ValidationException::withMessages([
'login' => [trans('auth.failed')],
]);
}
$this->ensureUserCanAccessPanel($user);
if ($user->email_verified_at === null) {
throw ValidationException::withMessages([
'login' => [trans('auth.unverified')],
]);
}
[$token, $abilities] = $this->issueTokenFor($user);
return response()->json([
'token' => $token,
'token_type' => 'Bearer',
'abilities' => $abilities,
'user' => [
'id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'role' => $user->role,
'tenant_id' => $user->tenant_id,
],
]);
}
public function destroy(Request $request): JsonResponse
{
$user = $request->user();
$token = $user?->currentAccessToken();
if ($token) {
$token->delete();
}
return response()->json(['ok' => true]);
}
public function me(Request $request): JsonResponse
{
$user = $request->user();
/** @var Tenant|null $tenant */
$tenant = $request->attributes->get('tenant');
if (! $tenant && $user?->tenant_id) {
$tenant = Tenant::query()->find($user->tenant_id);
}
$tenantPayload = null;
if ($tenant) {
$tenantPayload = [
'id' => $tenant->id,
'tenant_id' => $tenant->id,
'name' => $tenant->name,
'slug' => $tenant->slug,
'features' => $tenant->features,
];
}
return response()->json([
'user' => $user ? Arr::only($user->toArray(), [
'id',
'name',
'email',
'role',
'tenant_id',
]) : null,
'tenant' => $tenantPayload,
'abilities' => $user?->currentAccessToken()?->abilities ?? [],
]);
}
public function legacyTenantMe(Request $request): JsonResponse
{
/** @var Tenant|null $tenant */
$tenant = $request->attributes->get('tenant')
?? $request->user()?->tenant;
if (! $tenant) {
return response()->json([
'error' => 'tenant_not_found',
'message' => 'Tenant context missing.',
], 404);
}
$tenant->loadMissing('activeResellerPackage');
$user = $request->user();
$abilities = $user?->currentAccessToken()?->abilities ?? [];
$fullName = null;
if ($user) {
$first = trim((string) ($user->first_name ?? ''));
$last = trim((string) ($user->last_name ?? ''));
$fullName = trim($first.' '.$last) ?: null;
}
$activePackage = $tenant->activeResellerPackage;
return response()->json([
'id' => $tenant->id,
'tenant_id' => $tenant->id,
'name' => $tenant->name,
'slug' => $tenant->slug,
'email' => $tenant->contact_email,
'fullName' => $fullName,
'active_reseller_package_id' => $activePackage?->id,
'remaining_events' => $activePackage?->remaining_events ?? 0,
'package_expires_at' => $activePackage?->expires_at,
'features' => $tenant->features ?? [],
'scopes' => $abilities,
]);
}
public function exchange(Request $request): JsonResponse|Response
{
/** @var User|null $user */
$user = Auth::guard('web')->user();
if (! $user) {
return response()->noContent();
}
$this->ensureUserCanAccessPanel($user);
if ($user->email_verified_at === null) {
return response()->json([
'error' => 'unverified',
'message' => trans('auth.unverified'),
], 422);
}
[$token, $abilities] = $this->issueTokenFor($user);
return response()->json([
'token' => $token,
'token_type' => 'Bearer',
'abilities' => $abilities,
'user' => [
'id' => $user->id,
'email' => $user->email,
'name' => $user->name,
'role' => $user->role,
'tenant_id' => $user->tenant_id,
],
]);
}
/**
* @return array<int, string>
*/
private function resolveTokenAbilities(User $user): array
{
$abilities = ['tenant-member'];
if ($user->tenant_id) {
$abilities[] = 'tenant:'.$user->tenant_id;
}
if (in_array($user->role, ['tenant_admin', 'admin', 'super_admin', 'superadmin'], true)) {
$abilities[] = 'tenant-admin';
}
if ($user->isSuperAdmin()) {
$abilities[] = 'super-admin';
}
return $abilities;
}
/**
* @return array{0: string, 1: array<int, string>}
*/
private function issueTokenFor(User $user): array
{
$user->tokens()->where('name', 'tenant-admin')->delete();
$abilities = $this->resolveTokenAbilities($user);
$token = $user->createToken('tenant-admin', $abilities);
return [$token->plainTextToken, $abilities];
}
private function ensureUserCanAccessPanel(User $user): void
{
if (in_array($user->role, ['tenant_admin', 'admin', 'super_admin', 'superadmin'], true)) {
return;
}
if ($user->role === 'member' && $this->userHasCollaboratorMembership($user)) {
return;
}
throw ValidationException::withMessages([
'login' => [trans('auth.not_authorized')],
]);
}
private function userHasCollaboratorMembership(User $user): bool
{
if (! $user->tenant_id) {
return false;
}
return EventMember::query()
->where('tenant_id', $user->tenant_id)
->where(function ($query) use ($user) {
$query->where('user_id', $user->id)
->orWhere('email', $user->email);
})
->whereIn('status', ['active', 'invited'])
->exists();
}
}