Token generation: - Replace Str::ulid() with bin2hex(random_bytes(32)) for 256-bit entropy - Store SHA-256 hash in database, never plaintext tokens - Hash input before lookup on all token endpoints Invitation tokens: - InvitationService: generate crypto random, store hash, pass plain token transiently for email URL via UserInvitation::$plainToken - InvitationController show/accept: hash input before DB lookup - AcceptInvitationRequest: hash token before invitation lookup - Migration: widen user_invitations.token and artists.portal_token from char(26) to char(64) for SHA-256 hex digests Portal token auth: - PortalTokenController: remove Schema::hasTable() runtime checks, hash token before lookup, return shaped response via PortalEventResource instead of raw model data - Create PortalEventResource (name, dates, status only — no internals) - Handle missing production_requests table gracefully via try/catch Portal token middleware: - Implement full token validation: extract from Bearer header or ?token= query param, hash, look up in artists/production_requests, verify event exists and is not draft/closed, set portal context on request - Return generic 401 on any failure (no information leakage) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
33 lines
996 B
PHP
33 lines
996 B
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Requests\Api\V1;
|
|
|
|
use App\Models\User;
|
|
use App\Models\UserInvitation;
|
|
use Illuminate\Foundation\Http\FormRequest;
|
|
use Illuminate\Validation\Rules\Password;
|
|
|
|
final class AcceptInvitationRequest extends FormRequest
|
|
{
|
|
public function authorize(): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public function rules(): array
|
|
{
|
|
$hashedToken = hash('sha256', $this->route('token'));
|
|
$invitation = UserInvitation::where('token', $hashedToken)->first();
|
|
$userExists = $invitation && User::where('email', $invitation->email)->exists();
|
|
|
|
return [
|
|
'first_name' => [$userExists ? 'nullable' : 'required', 'string', 'max:255'],
|
|
'last_name' => [$userExists ? 'nullable' : 'required', 'string', 'max:255'],
|
|
'password' => [$userExists ? 'nullable' : 'required', 'string', 'confirmed', Password::min(8)->mixedCase()->numbers()],
|
|
];
|
|
}
|
|
}
|