Skip to content

Use guard-declared password brokers - #420

Merged
binaryfire merged 11 commits into
0.4from
feature/guard-declared-password-brokers
Jul 7, 2026
Merged

Use guard-declared password brokers#420
binaryfire merged 11 commits into
0.4from
feature/guard-declared-password-brokers

Conversation

@binaryfire

@binaryfire binaryfire commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR makes the current auth guard the single source of default password broker selection. A guard that participates in password resets now declares its broker with a passwords key. Bare password broker calls resolve through that declaration. Explicit broker names still work exactly as before.

For more details, see: docs/plans/2026-07-06-auth-guard-declared-password-brokers.md

Why

Hypervel already lets middleware select the current auth guard per coroutine. Password brokers did not follow that model: they still read auth.defaults.passwords from process-global config.

That split is awkward in a long-lived Swoole worker. In Laravel, an app can mutate config per request to align password broker state with the current guard. Hypervel cannot use that pattern safely because config is shared for the worker lifetime.

The old Fortify provider inference tried to paper over the gap, but provider matching is not a stable rule. Multiple guards may share a provider. Multiple brokers may point at a provider. Adding an unrelated broker could change whether Fortify could infer the original guard's broker.

The simpler rule is the one this PR implements:

  • use an explicit broker name, or
  • use the broker declared by the current guard, or
  • fail with a clear configuration error.

What Changed

Auth broker resolution

  • Removed auth.defaults.passwords from the framework auth config.
  • Added auth.guards.web.passwords => users to the base config.
  • Added PasswordBrokerManager::resolveBrokerNameForGuard(string $guard): ?string and exposed it through the contract and facade docblock.
  • Changed PasswordBrokerManager::getDefaultDriver() to resolve in this order:
    1. coroutine-local Password::setDefaultDriver() override
    2. current auth guard's passwords key
    3. configuration exception naming the guard and key to add
  • Changed PasswordBrokerManager::setDefaultDriver() to write to coroutine context instead of process-global config.
  • Kept explicit Password::broker('name') independent from default guard resolution.

Reset notification expiry

  • Gave PasswordBroker its resolved broker name.
  • Stamped the sending broker name into coroutine context while sendResetLink() calls the reset notification or user callback.
  • Restored any previous broker stamp in a finally block.
  • Changed ResetPassword to capture expireMinutes at construction from the broker that created the token.
  • This makes the email expiry text match the token that was actually issued, including queued notifications.

Fortify

  • Deleted Fortify::passwordBrokerName() and its provider inference rule.
  • Updated Fortify password controllers to call bare Password::broker().
  • Fortify now uses the same password broker defaulting as the rest of the framework.

Command cleanup

  • Updated auth:clear-resets help text to explain that the missing broker argument defaults through the current guard's broker.
  • Replaced container array access with make() in the command.

Documentation

  • Updated password reset, authentication, and Fortify docs for the guard passwords key.
  • Updated the Fortify README difference from Laravel.
  • Added a superseded note to the older Fortify passkeys plan so its historical provider-inference text is not mistaken for current behavior.

Tests

Added or updated coverage for:

  • guard-declared broker resolution
  • missing, empty, malformed, and falsey broker declarations
  • coroutine isolation for Password::setDefaultDriver()
  • explicit broker calls bypassing default guard resolution
  • broker stamping during sendResetLink() notification and callback paths
  • restoring or clearing the broker stamp after send completion
  • reset notification expiry capture and serialization
  • Fortify following the current guard's declared broker
  • Fortify failing clearly when the selected guard has no passwords key
  • auth:clear-resets default and explicit broker paths

Final verification:

  • ./vendor/bin/phpunit --no-progress tests/Auth/AuthPasswordBrokerManagerTest.php
    • OK (13 tests, 23 assertions)
  • composer fix
    • cs-fixer: fixed 0 files
    • phpstan: no errors
    • main ParaTest: OK, but some tests were skipped! Tests: 21378, Assertions: 60590, Skipped: 643.
    • second ParaTest pass: OK, but some tests were skipped! Tests: 315, Assertions: 861, Skipped: 3.
    • dogfood testbench package: OK (4 tests, 7 assertions)

Review

This went through a Codesonic review loop with Claude. Two review findings were applied:

  • removed an extra defensive context type guard so getDefaultDriver() matches the natural typed-return fail-fast behavior used by AuthManager
  • fixed Fortify docs to attribute the missing-key exception to Hypervel's shared broker manager rather than Fortify-owned logic

Claude signed off after those changes.

Summary by CodeRabbit

  • New Features
    • Password reset flows now use the active guard’s declared reset broker, improving multi-guard support.
  • Bug Fixes
    • Reset emails now display the correct expiry time for the broker that actually sends the link (including queued/custom notification paths).
    • Clearing expired reset tokens now reliably targets the current broker when no broker name is provided.
    • Permission middleware now correctly treats empty guard values as the default guard.
  • Documentation
    • Updated authentication, Fortify, and password reset docs to reflect guard-declared broker configuration and new error behavior.

Record the agreed design for making the current auth guard the single source of password broker defaulting.

The plan captures why provider inference and auth.defaults.passwords were removed, how reset notification expiry should follow the sending broker, and the test and documentation coverage expected for the PR.
Remove auth.defaults.passwords as a second defaulting root and make bare password broker calls derive from the current auth guard's passwords key.

PasswordBrokerManager now exposes resolveBrokerNameForGuard(), reads coroutine-scoped broker overrides before guard config, and fails with a configuration error when the active guard does not declare a broker.

The framework auth config now declares the web guard's broker directly, the Password facade and contract expose the resolver, and the auth.password.broker binding stays dynamic for the current coroutine's default broker.
Add coverage for resolving a broker from a guard's passwords key, missing and empty declarations, malformed config values, and explicit broker names bypassing default guard resolution.

Also cover coroutine isolation for setDefaultDriver() and the falsey broker-name edge so a valid broker name such as '0' is not treated as missing.
Teach PasswordBroker its resolved name and stamp that name into coroutine context while sendResetLink() builds the reset notification or runs a custom callback.

ResetPassword now captures the expiry minutes at construction from the sending broker, falling back to the current default broker outside a send flow. Capturing the value on the notification keeps queued mail text consistent with the token that was actually created.

The context stamp is restored in a finally block so nested sends and surrounding coroutine state remain isolated.
Extend PasswordBroker tests to prove sendResetLink() stamps the broker name for notification and callback paths, restores an existing stamp, and forgets the stamp when none existed.

Add ResetPassword notification coverage proving expiry minutes come from the sending broker, fall back through the current guard's broker outside a send flow, and survive serialization for queued notifications.

The callback test now also asserts that a callback path does not send the stock password reset notification.
Delete Fortify's provider-based password broker inference now that the auth package has a declared guard-to-broker relationship.

Fortify password controllers now call bare Password::broker(), so they follow the same current-guard passwords key and coroutine-scoped broker override as all other password broker consumers.

This removes a config-shape-sensitive rule where adding another broker for the same provider could change or break an unrelated Fortify flow.
Declare password brokers on the Fortify test guards so the suite exercises the same key-only broker relationship as applications.

Replace the deleted provider-inference tests with coverage that the selected guard's passwords key controls Password::getDefaultDriver(), and that a selected guard without the key fails through the shared password broker manager.
Update auth:clear-resets help text so the optional broker name documents the new default: the current guard's declared broker.

Switch the command from container array access to make(), matching Hypervel's container style, and add coverage for both the default broker path and an explicitly named broker.
Document that guards which send password reset links declare their broker with a passwords key, and that bare Password calls resolve through the current guard unless a broker is named explicitly.

Update Fortify docs and README to describe delegation to the shared auth password broker resolution instead of provider inference.

Add a superseded note to the older Fortify passkeys plan so historical provider-inference text is not mistaken for current guidance.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b8c264f4-bd48-408a-bd8c-762310e4f2bf

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Password broker selection now follows each guard’s declared passwords config, with coroutine-scoped overrides and sending-broker context tracking. Fortify now uses the default broker path, docs and base auth config were updated, permission middleware normalizes empty guard names, and tests cover the new flows.

Changes

Guard-declared password broker resolution and guard normalization

Layer / File(s) Summary
Plan and contract updates
docs/plans/2026-07-01-fortify-passkeys-port.md, docs/plans/2026-07-06-auth-guard-declared-password-brokers.md, src/contracts/src/Auth/PasswordBrokerFactory.php, src/support/src/Facades/Password.php
Plan notes and public broker-resolution contract surface are updated for guard-declared passwords selection and guard-to-broker lookup.
Broker resolution and reset flow
src/auth/src/AuthManager.php, src/auth/src/Passwords/PasswordBrokerManager.php, src/auth/src/Passwords/PasswordBroker.php, src/auth/src/Notifications/ResetPassword.php, src/auth/src/Console/ClearResetsCommand.php, src/auth/src/Passwords/PasswordResetServiceProvider.php
Default-broker lookup, coroutine overrides, broker context stamping, reset expiry capture, and clear-resets wiring are updated together.
Fortify and auth config updates
src/fortify/src/Fortify.php, src/fortify/src/Http/Controllers/*.php, src/foundation/config/auth.php, src/boost/docs/authentication.md, src/boost/docs/fortify.md, src/boost/docs/passwords.md, src/fortify/README.md
Fortify stops inferring broker names, auth config declares guard brokers, and related docs describe the new selection path.
Permission guard normalization
src/permission/src/Guard.php, src/permission/src/Middleware/*.php, src/permission/src/PermissionServiceProvider.php
Empty guard names are normalized before auth-guard resolution in permission middleware and blade helpers.
Test updates
tests/Auth/*.php, tests/Fortify/*.php, tests/Permission/Middleware/*.php
Tests cover coroutine isolation, sending-broker context behavior, expiry capture, Fortify guard behavior, clear-resets execution, falsey auth inputs, and empty-guard middleware behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: password brokers are now declared on the auth guard.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/guard-declared-password-brokers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces auth.defaults.passwords process-global config with a per-guard passwords key as the single source of truth for default password broker selection, and writes Password::setDefaultDriver() overrides to coroutine context instead of process-global config. The change is a targeted coroutine-safety improvement that aligns password broker defaulting with how auth guard selection already works in Hypervel.

  • PasswordBrokerManager::getDefaultDriver() now resolves from (1) coroutine-local override, (2) the current guard's passwords key, or (3) throws a clear InvalidArgumentException — removing the race-prone auth.defaults.passwords config read.
  • PasswordBroker receives its resolved name at construction and stamps it into coroutine context during sendResetLink, so ResetPassword captures the correct expireMinutes at construction time rather than re-reading config at render/queue time.
  • Fortify::passwordBrokerName() and its fragile provider-inference heuristic are deleted; Fortify controllers call bare Password::broker() and follow the same defaulting path as the rest of the framework.

Confidence Score: 5/5

Safe to merge - the change correctly isolates password broker state per coroutine, all edge cases are covered by new tests, and the full test suite passes.

The guard-declared broker resolution, coroutine-context isolation for setDefaultDriver, broker-name stamping during sendResetLink, and expireMinutes capture in the ResetPassword constructor are all implemented correctly. The falsy-name fix in AuthManager is sound. No behavioral regressions were identified.

No files require special attention.

Important Files Changed

Filename Overview
src/auth/src/Passwords/PasswordBrokerManager.php Core of the PR: replaces process-global config reads with coroutine-context overrides and guard-declared broker resolution; logic is clean and well-structured
src/auth/src/Passwords/PasswordBroker.php Adds $name to constructor and stamps SENDING_BROKER_CONTEXT_KEY during sendResetLink with correct save/restore in finally; nested broker calls handled correctly
src/auth/src/Notifications/ResetPassword.php Captures expireMinutes at construction from broker context; correctly survives serialization for queued notifications
src/auth/src/AuthManager.php Fixes falsy guard-name handling by switching from ?: to ??= in guard(), shouldUse(), and from truthy-get to has()+get() in getDefaultDriver()
src/contracts/src/Auth/PasswordBrokerFactory.php Adds resolveBrokerNameForGuard() with @throws annotation to contract; matches implementation
src/fortify/src/Fortify.php Removes passwordBrokerName() and its fragile provider-inference logic; Fortify now uses the same defaulting path as the rest of the framework
src/foundation/config/auth.php Removes auth.defaults.passwords, adds passwords key to the web guard; comments updated to explain the new guard-declared broker pattern
src/permission/src/Guard.php Adds normalizeName() helper that converts empty string to null; cleanly shared across all three permission middleware classes and bladeMethodWrapper
tests/Auth/AuthPasswordBrokerManagerTest.php Comprehensive coverage: guard-declared resolution, missing/empty/malformed broker declarations, coroutine isolation, explicit broker bypass, falsy-name edge cases
tests/Auth/ResetPasswordNotificationTest.php Tests expiry capture from sending context, fallback to default broker, and serialization survival - correctly uses Testbench to bootstrap config
tests/Auth/AuthPasswordBrokerTest.php Adds tests for SENDING_BROKER_CONTEXT_KEY stamping, previous-value restore, forget-on-exit, and callback path; getMocks updated to include broker name
tests/Auth/ClearResetsCommandTest.php New test covering default and explicit broker paths for the auth:clear-resets command

Reviews (2): Last reviewed commit: "permission: normalize empty optional gua..." | Re-trigger Greptile

Comment thread src/contracts/src/Auth/PasswordBrokerFactory.php
Comment thread src/auth/src/Notifications/ResetPassword.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/Auth/ResetPasswordNotificationTest.php (1)

16-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add teardown to reset CoroutineContext and ResetPassword static state.

testExpiryIsCapturedFromSendingBrokerContext stamps SENDING_BROKER_CONTEXT_KEY but never forgets it; the next test happens to forget() first so passes today, but this is order-dependent. Separately, mailLines() calls ResetPassword::createUrlUsing(...), which per its own docblock is "Boot-only. The callback persists in a static property for the worker lifetime and runs on every reset-password notification." None of these tests call ResetPassword::flushState(), so the callback can leak into other test classes sharing the same worker process.

Add a tearDown() that forgets the context key and calls ResetPassword::flushState().

🧹 Suggested teardown
+    protected function tearDown(): void
+    {
+        CoroutineContext::forget(PasswordBroker::SENDING_BROKER_CONTEXT_KEY);
+        ResetPassword::flushState();
+
+        parent::tearDown();
+    }
+
     public function testExpiryIsCapturedFromSendingBrokerContext(): void

Also applies to: 58-76

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Auth/ResetPasswordNotificationTest.php` around lines 16 - 27, The
reset-password notification tests are leaking shared state between cases because
`testExpiryIsCapturedFromSendingBrokerContext()` leaves
`CoroutineContext::SENDING_BROKER_CONTEXT_KEY` set and `mailLines()` registers a
persistent callback via `ResetPassword::createUrlUsing()`. Add a `tearDown()` in
`ResetPasswordNotificationTest` that always clears the broker context and calls
`ResetPassword::flushState()` so the `ResetPassword` static callback and
`CoroutineContext` state do not affect later tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/boost/docs/authentication.md`:
- Around line 391-407: The middleware example in the authentication docs uses
inconsistent syntax for the guard name. Update the example text that mentions
auth.guard:admin so it matches the rest of the documentation’s auth:admin form,
and ensure the surrounding explanation in the authentication section stays
aligned with that same middleware syntax.

---

Nitpick comments:
In `@tests/Auth/ResetPasswordNotificationTest.php`:
- Around line 16-27: The reset-password notification tests are leaking shared
state between cases because `testExpiryIsCapturedFromSendingBrokerContext()`
leaves `CoroutineContext::SENDING_BROKER_CONTEXT_KEY` set and `mailLines()`
registers a persistent callback via `ResetPassword::createUrlUsing()`. Add a
`tearDown()` in `ResetPasswordNotificationTest` that always clears the broker
context and calls `ResetPassword::flushState()` so the `ResetPassword` static
callback and `CoroutineContext` state do not affect later tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f7cc0b3f-1930-405b-81be-d3ffcd1841ac

📥 Commits

Reviewing files that changed from the base of the PR and between 212f17d and 75c69ae.

📒 Files selected for processing (24)
  • docs/plans/2026-07-01-fortify-passkeys-port.md
  • docs/plans/2026-07-06-auth-guard-declared-password-brokers.md
  • src/auth/src/Console/ClearResetsCommand.php
  • src/auth/src/Notifications/ResetPassword.php
  • src/auth/src/Passwords/PasswordBroker.php
  • src/auth/src/Passwords/PasswordBrokerManager.php
  • src/auth/src/Passwords/PasswordResetServiceProvider.php
  • src/boost/docs/authentication.md
  • src/boost/docs/fortify.md
  • src/boost/docs/passwords.md
  • src/contracts/src/Auth/PasswordBrokerFactory.php
  • src/fortify/README.md
  • src/fortify/src/Fortify.php
  • src/fortify/src/Http/Controllers/NewPasswordController.php
  • src/fortify/src/Http/Controllers/PasswordController.php
  • src/fortify/src/Http/Controllers/PasswordResetLinkController.php
  • src/foundation/config/auth.php
  • src/support/src/Facades/Password.php
  • tests/Auth/AuthPasswordBrokerManagerTest.php
  • tests/Auth/AuthPasswordBrokerTest.php
  • tests/Auth/ClearResetsCommandTest.php
  • tests/Auth/ResetPasswordNotificationTest.php
  • tests/Fortify/FortifyStaticStateTest.php
  • tests/Fortify/TestCase.php
💤 Files with no reviewable changes (1)
  • src/fortify/src/Fortify.php

Comment thread src/boost/docs/authentication.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes password broker defaulting follow the current auth guard (via a per-guard passwords key) instead of relying on auth.defaults.passwords, aligning password reset behavior with Hypervel’s coroutine-scoped guard selection model in long-lived Swoole workers.

Changes:

  • Switch default password broker resolution to: coroutine override → current guard’s auth.guards.{guard}.passwords → clear configuration exception; remove auth.defaults.passwords.
  • Stamp the sending broker into coroutine context during sendResetLink() and capture expiry minutes in ResetPassword at construction to keep queued email expiry text accurate.
  • Remove Fortify’s provider-based broker inference and update Fortify controllers/docs/tests to use the unified defaulting behavior.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Fortify/TestCase.php Adds per-guard passwords declarations used by Fortify tests.
tests/Fortify/FortifyStaticStateTest.php Updates Fortify broker expectations to follow Password defaulting; adds missing-key failure case.
tests/Auth/ResetPasswordNotificationTest.php New tests ensuring reset email expiry is captured from the sending broker and survives serialization.
tests/Auth/ClearResetsCommandTest.php New tests covering default vs explicit broker behavior for auth:clear-resets.
tests/Auth/AuthPasswordBrokerTest.php Updates broker construction and adds tests for broker stamping during notification/callback flows.
tests/Auth/AuthPasswordBrokerManagerTest.php Adds coverage for guard-declared broker resolution, malformed values, coroutine isolation, and explicit broker bypass.
src/support/src/Facades/Password.php Documents the new resolveBrokerNameForGuard() facade method.
src/foundation/config/auth.php Removes auth.defaults.passwords and adds passwords to the default web guard.
src/fortify/src/Http/Controllers/PasswordResetLinkController.php Switches Fortify to Password::broker() (guard-driven default).
src/fortify/src/Http/Controllers/PasswordController.php Switches Fortify to Password::broker() and removes unused Fortify import.
src/fortify/src/Http/Controllers/NewPasswordController.php Switches Fortify to Password::broker() (guard-driven default).
src/fortify/src/Fortify.php Deletes Fortify::passwordBrokerName() provider-inference logic.
src/fortify/README.md Updates “Differences From Laravel” to reflect guard-declared brokers.
src/contracts/src/Auth/PasswordBrokerFactory.php Extends the contract with resolveBrokerNameForGuard().
src/boost/docs/passwords.md Documents the guard passwords key and default broker resolution order.
src/boost/docs/fortify.md Updates Fortify docs to describe guard-declared broker selection and failure mode.
src/boost/docs/authentication.md Documents per-guard passwords configuration for multi-guard apps.
src/auth/src/Passwords/PasswordResetServiceProvider.php Clarifies the per-coroutine default broker behavior in the provider comments.
src/auth/src/Passwords/PasswordBrokerManager.php Implements guard-declared resolution + coroutine-scoped default override; updates container access patterns.
src/auth/src/Passwords/PasswordBroker.php Adds broker name + stamps sending broker into coroutine context during sendResetLink().
src/auth/src/Notifications/ResetPassword.php Captures expiry minutes at construction based on the sending broker (or default outside send flow).
src/auth/src/Console/ClearResetsCommand.php Updates help text and uses $this->hypervel->make(...) instead of array access.
docs/plans/2026-07-06-auth-guard-declared-password-brokers.md Adds/records the full design plan and rationale for the new model.
docs/plans/2026-07-01-fortify-passkeys-port.md Adds a superseded note to prevent outdated broker inference guidance being reused.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/auth/src/Passwords/PasswordBrokerManager.php
Comment thread src/auth/src/Passwords/PasswordBrokerManager.php
Default manager lookups now only fall back when the caller omits the name. This keeps explicit names such as '0' meaningful for both auth guards and password brokers instead of silently resolving the configured default.

Align AuthManager's default-driver context read with the password broker manager by checking context presence before reading the value, and make shouldUse() use the same null-only defaulting rule.

Document the exception surface for guard-declared broker resolution and reset notification expiry fallback, and update the authentication docs with the owner-approved auth.guard wording for guest routes.

The guard-declared password broker plan is also updated with a follow-up note so it no longer contradicts the AuthManager alignment made during bot-review cleanup.
Permission's Blade and middleware APIs accept an optional guard argument, and existing views can pass an empty string to mean no guard was provided.

Normalize that boundary input before calling AuthManager so the auth manager can stay strict about explicit runtime names while Permission keeps its documented optional-guard behavior.

Add middleware coverage for empty guard values using the default guard; existing Blade tests cover the same path for directives.
@binaryfire

Copy link
Copy Markdown
Collaborator Author

@greptileai @coderabbitai @copilot thanks for the review pass.

Follow-up is pushed in 6833ebcdf and 6109e8f17.

What changed:

  • Added the missing password-broker @throws docs.
  • Clarified reset notification expiry fallback docs.
  • Kept auth.guard:admin, but rewrote the docs to explain why guest routes need it.
  • Fixed explicit falsey names: Password::broker('0'), Auth::guard('0'), Auth::shouldUse('0'), and default-guard context value '0'.
  • Normalized empty optional guard arguments inside Permission's Blade/middleware boundary, where '' means "no guard provided".

Two suggestions were intentionally not applied:

  • No ResetPasswordNotificationTest::tearDown(): tests run in fresh coroutines, and AfterEachTestSubscriber already flushes ResetPassword static state.
  • No setDefaultDriver('') clear behavior: runtime empty strings remain caller errors; optional-input surfaces normalize before calling the auth runtime API.

Verification: composer fix is green.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

@binaryfire Thanks for the detailed rundown — this all sounds reasonable:

  • The @throws docs and expiry-fallback clarifications close the gaps we flagged.
  • Rewriting the admin guard docs to explain the guest-route rationale is a good call rather than just removing it.
  • Handling explicit falsey names ('0') across Password::broker(), Auth::guard(), Auth::shouldUse(), and the default-guard context value closes a real edge case that loose falsy checks would've missed.
  • Normalizing empty optional guard args at the Permission Blade/middleware boundary (treating '' as "no guard") makes sense as an input-surface concern rather than a runtime API concern.

On the two declined suggestions:

  • Skipping ResetPasswordNotificationTest::tearDown() is fine given fresh-coroutine test isolation plus AfterEachTestSubscriber already flushing ResetPassword static state.
  • Not adding a setDefaultDriver('') clear path is a reasonable boundary — keeping runtime empty-string handling as a caller error while normalizing at the optional-input surfaces is a consistent design choice.

I'll take a look at the new commits.

(๑˃̵ᴗ˂̵)و

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown

Both commits look clean. Here's what I verified:

6833ebcdf — falsey name preservation

The has() + get() pattern in AuthManager::getDefaultDriver() and PasswordBrokerManager::getDefaultDriver() correctly distinguishes "key not set" from "key set to '0'". The broker() and guard() entry points use ??=, which only coalesces on null — so an explicit '0' passed by the caller is preserved end-to-end without touching the default resolution path. shouldUse() is aligned the same way.

ResetPassword::resolveExpireMinutes() uses ?? rather than has() + get(). That's a minor asymmetry but functionally sound: PasswordBroker::sendResetLink() only stamps non-null broker name strings, so null in context unambiguously means "not inside a send flow". The method comment documents this intent. One consequence worth knowing: the Password::getDefaultDriver() fallback in that method can throw InvalidArgumentException in test/preview contexts if the current guard has no passwords key. That's the intended fail-fast behavior, just noting it so it doesn't surprise anyone writing test helpers that construct ResetPassword directly outside a coroutine with a configured guard.

6109e8f17 — Permission empty guard normalization

The architecture is right: normalize at the boundary, keep AuthManager strict. Guard::normalizeName() is a one-liner in the right place. The PermissionServiceProvider::bladeMethodWrapper() also picks it up, so middleware and Blade directives are consistent — the commit message's claim that "existing Blade tests cover the same path" checks out.

Test coverage

testDefaultDriverAcceptsFalseyBrokerNames, testBrokerWithExplicitFalseyNameDoesNotFallBackToDefaultDriver, testGuardWithExplicitFalseyNameDoesNotFallBackToDefaultDriver, testShouldUseAcceptsFalseyGuardName, and the parallel-coroutine isolation test together make the behavioral guarantees concrete. Good set.

Skipped suggestions

Both non-applications are reasonable. AfterEachTestSubscriber flushing ResetPassword static state in fresh coroutines is a valid substitute for tearDown(), and keeping setDefaultDriver('') as a caller error while normalizing at optional-input surfaces is a clean division of responsibility.

No issues blocking merge.

@binaryfire
binaryfire merged commit 22176d3 into 0.4 Jul 7, 2026
37 checks passed
@binaryfire
binaryfire deleted the feature/guard-declared-password-brokers branch July 21, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants