Add a SHA-256 token_hash lookup column to workspace_invitations so that
findByToken and findPendingByToken can locate the candidate row with a
single indexed SQL query instead of loading up to 1000 rows and running
bcrypt against each one sequentially.
The bcrypt hash in the token column is still verified after the O(1)
lookup, preserving the existing security guarantee while eliminating
both the timing side-channel and the performance bottleneck.
Changes:
- Migration to add nullable indexed token_hash column
- Model booted() creating/updating events compute SHA-256 alongside bcrypt
- findByToken/findPendingByToken rewritten to WHERE token_hash then Hash::check
- HashInvitationTokens command updated to populate token_hash for existing rows
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add phpunit.xml and tests/Pest.php for standalone test execution.
Apply Laravel Pint formatting fixes across all source files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add encrypted cast to UserTwoFactorAuth secret and recovery_codes
- Hash invitation tokens on creation using Hash::make()
- Update token verification to use Hash::check()
- Add migration commands for existing data:
- security:encrypt-2fa-secrets
- security:hash-invitation-tokens
- Add tests for encryption and hashing
Fixes SEC-003, SEC-004 from security audit.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>