Skip to content

chore: implement oauth state for every provider#116

Merged
danielhe4rt merged 15 commits into
3.xfrom
chore/oauth-state-revamp
Nov 23, 2025
Merged

chore: implement oauth state for every provider#116
danielhe4rt merged 15 commits into
3.xfrom
chore/oauth-state-revamp

Conversation

@danielhe4rt
Copy link
Copy Markdown
Contributor

@danielhe4rt danielhe4rt commented Nov 21, 2025

Summary by CodeRabbit

  • New Features

    • OAuth provider buttons (Discord, Twitch) on the login screen
    • Tenant-specific logout action available in the user menu
  • Improvements

    • Multi-tenant-aware OAuth flow using state that preserves panel and tenant context
    • Tenant-aware color theming applied to admin panels
    • OAuth providers can be enabled/disabled via configuration
  • Chores

    • Streamlined session handling during login/logout flows

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 21, 2025

Walkthrough

Implements tenant-aware OAuth state and routing, updates OAuth clients/contracts to accept an OAuthStateDTO, refactors authentication to handle tenant flows, adds tenant logout controller/response and route, updates login UI and Filament theming for tenant context, and adds service toggles for OAuth providers.

Changes

Cohort / File(s) Summary
Authentication Routing
app-modules/authentication/routes/authentication-routes.php
Adds TenantLogoutController import and POST /{tenantSlug}/logout route; replaces single OAuth redirect route with GET /{panel}/{provider}/redirect and GET /{tenant}/{panel}/{provider}/redirect.
OAuth State DTO
app-modules/authentication/src/DTO/OAuthStateDTO.php
Adds OAuthStateDTO (panel:string, tenant:?string) implementing JsonSerializable and Stringable; provides __toString() encryption and fromHashedString() decryption.
OAuth Provider & Contract
app-modules/authentication/src/Enums/OAuthProviderEnum.php, app-modules/authentication/src/Contracts/OAuthClientContract.php
OAuthClientContract::redirectUrl now accepts ?OAuthStateDTO. OAuthProviderEnum adds isEnabled() and getRedirectUri(?string $tenant = null) which builds redirect using OAuthStateDTO.
OAuth Client Implementations
app-modules/integrations/src/Discord/.../DiscordOAuthClient.php, app-modules/integrations/src/Twitch/.../TwitchOAuthClient.php
redirectUrl signatures updated to ?OAuthStateDTO; URL construction switched to http_build_query, passing state as string.
Authentication Logic
app-modules/authentication/src/Actions/AuthenticateAction.php, app-modules/authentication/src/Http/Controllers/OAuthController.php
withOAuth now accepts OAuthStateDTO; adds private authenticateTenant() for tenant flows. OAuthController now parses/decrypts state and invokes action with the DTO; removes session-based tenant persistence during redirect.
Tenant Logout Flow
app-modules/authentication/src/Http/Controllers/TenantLogoutController.php, app-modules/authentication/src/Http/Responses/TenantLogoutResponse.php
Adds TenantLogoutController::__invoke(string $tenantSlug) to perform logout while preserving tenant/panel in session. Adds TenantLogoutResponse implementing Filament LogoutResponse, redirecting to panel-aware login.
Service Provider & Configuration
app-modules/authentication/src/Providers/AuthenticationServiceProvider.php, config/services.php
Removes boot() from AuthenticationServiceProvider. Adds enabled flags for discord and twitch in config/services.php (env-driven, default true).
Login UI & Theming
app-modules/events/src/Filament/Shared/EventLogin.php, app-modules/he4rt/resources/views/components/partials/oauth-connect.blade.php, app/Providers/Filament/EventPanelProvider.php, app/Providers/FilamentServiceProvider.php
EventLogin gains tenantSlug and renders oauth-connect partial with providers. Blade partial renders provider buttons using provider redirect URIs. EventPanelProvider adds tenant-scoped logout menu item. FilamentServiceProvider adds tenant-aware color theming and stores panel/tenant in session.
Static Analysis
phpstan.ignore.neon
Adjusts PHPStan ignore entry count for FilamentServiceProvider::getId() from 1 to 2.
Tests
app-modules/authentication/tests/Feature/Actions/AuthenticateActionTest.php
Tests updated to pass OAuthStateDTO to AuthenticateAction calls.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant Provider as OAuthProvider
    participant OAuthController
    participant AuthenticateAction
    participant Session

    User->>Frontend: Clicks OAuth login (tenant context)
    Frontend->>Provider: GET /authorize?state=OAuthStateDTO(panel,tenant)
    Provider->>OAuthController: Callback(code, state)
    OAuthController->>AuthenticateAction: withOAuth(state: OAuthStateDTO, code)
    AuthenticateAction->>AuthenticateAction: authenticateTenant() [if state.tenant]
    AuthenticateAction->>Session: create/authenticate tenant user
    OAuthController-->>Frontend: Redirect to panel-aware URL
Loading
sequenceDiagram
    participant User
    participant Browser
    participant TenantLogoutController
    participant Filament
    participant Session
    participant TenantLogoutResponse

    User->>Browser: Clicks Logout
    Browser->>TenantLogoutController: POST /{tenantSlug}/logout
    TenantLogoutController->>Session: capture panel & tenant
    TenantLogoutController->>Filament: perform logout
    TenantLogoutController->>Session: invalidate & regenerate
    TenantLogoutController->>Session: restore tenant & panel
    TenantLogoutController->>TenantLogoutResponse: return response
    TenantLogoutResponse->>Filament: set current panel
    TenantLogoutResponse-->>User: redirect to panel login
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Extra attention suggested:
    • OAuthStateDTO encryption/decryption edge cases and error paths.
    • Consistency of state handling across all OAuth clients (Discord, Twitch).
    • Session lifecycle in TenantLogoutController (invalidate/regenerate/restore).
    • AuthenticateAction.authenticateTenant parity with previous auth flow.
    • FilamentServiceProvider session writes and their effect on request lifecycle.

Possibly related PRs

Suggested reviewers

  • PilsAraujo
  • gvieira18

Poem

🐰 I hopped through states and panel light,

Encrypted tokens tucked in tight.
Tenants routed, logout neat,
Redirects dancing on their feet —
A rabbit cheers this auth delight! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: implementing OAuth state management across OAuth providers, which is reflected throughout the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/oauth-state-revamp

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

@danielhe4rt danielhe4rt marked this pull request as ready for review November 23, 2025 21:15
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (15)
app-modules/events/src/Filament/Events/EventLandingPage.php (2)

16-17: Clarify the purpose of the empty mount() method.

The mount() lifecycle hook is present but contains no logic. Comparing to EventLogin.php (lines 59-69), a typical mount implementation would validate the tenant and handle authenticated user redirects. Since getView() already validates tenant existence (line 33), consider either:

  • Implementing authentication checks and redirects in mount() if needed, or
  • Removing the empty method if it serves no purpose

21-35: Consider extracting duplicated tenant resolution logic.

The tenant slug extraction logic (lines 21-29) is duplicated in ParticipantDashboard.php (lines 24-32). This pattern appears in multiple places and could be extracted into a shared helper method or trait to improve maintainability.

app-modules/he4rt/resources/views/components/partials/oauth-connect.blade.php (1)

16-16: Consider graceful handling when OAuth provider credentials are missing.

While isEnabled() checks if the provider is enabled, it may not validate that all required credentials (client_id, client_secret) are configured. If getRedirectUri() is called with incomplete credentials, the OAuth flow could fail silently or produce unclear errors for users.

app-modules/events/resources/views/components/themes/3pontos/participant-dashboard.blade.php (1)

1-23: Tenant dashboard layout and footer look consistent

The dashboard composition and footer props mirror the homepage theme and are straightforward. Since the :columns structure is duplicated here and in the homepage, consider extracting this footer config into a shared partial/config if it starts to diverge across pages, but it’s fine as-is.

app-modules/events/resources/views/components/themes/3pontos/homepage.blade.php (2)

12-14: Consider guarding the decorative image against accidental overlap

The decorative logo with alt="" is good for accessibility. Depending on how other sections set their z-index, the absolute + transforms at z-0 could still sit over interactive content. If you notice click/hover issues, consider adding pointer-events-none or adjusting z-index to keep it firmly in the background.


16-34: Shared footer config duplicated with dashboard

Footer usage and props look correct and consistent with the participant dashboard. As this navigation grows, you may want to centralize the :columns/copy for 3pontos so homepage and dashboard stay in sync without duplication, but it’s not urgent.

app-modules/he4rt/resources/css/support/themes.css (1)

162-165: Helper tokens now map to lighter tints – verify contrast in usages

Switching --helper-success|warning|error|info to *-300 makes helpers noticeably lighter. That’s fine, but it would be good to spot-check key components (alerts, badges, form feedback) for sufficient contrast in both light and dark themes; if needed, you can later add .dark-specific overrides for these helper tokens.

app/Providers/FilamentServiceProvider.php (1)

40-47: Consider externalizing tenant color configuration.

The hardcoded match expression limits flexibility. As more tenants are added, this will become a maintenance burden.

Consider moving color configuration to a config file or database:

-    $color = match ($tenantSlug) {
-        '3pontos' => Color::Gray,
-        default => Color::Indigo
-    };
+    $color = config("tenants.colors.{$tenantSlug}", Color::Indigo);

Then create config/tenants.php:

return [
    'colors' => [
        '3pontos' => \Filament\Support\Colors\Color::Gray,
        // Add more tenants here
    ],
];
app-modules/authentication/src/Http/Responses/TenantLogoutResponse.php (1)

22-24: Consider extracting redirect logic for clarity.

The ternary with Filament::hasLogin() is compact but could be clearer. The redirect targets also assume certain contexts.

-    return redirect()->to(
-        Filament::hasLogin() ? Filament::getLoginUrl(['tenantSlug' => $tenantSlug]) : Filament::getUrl($tenant),
-    );
+    $redirectUrl = Filament::hasLogin() 
+        ? Filament::getLoginUrl(['tenantSlug' => $tenantSlug])
+        : Filament::getUrl($tenant);
+
+    return redirect()->to($redirectUrl);
app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php (1)

18-26: State parameter is optional for Twitch OAuth—current implementation is acceptable.

Twitch's OAuth documentation shows the state parameter is optional, though strongly recommended to be non-empty for CSRF protection. The current code passes an empty string when $state is null, which Twitch will accept without issue.

However, for cleaner URLs and clearer intent, consider omitting the state parameter when null:

 public function redirectUrl(?OAuthStateDTO $state = null): string
 {
-    return 'https://id.twitch.tv/oauth2/authorize?'.http_build_query([
+    $params = [
         'client_id' => config('services.twitch.client_id'),
         'redirect_uri' => config('services.twitch.redirect_uri'),
         'response_type' => 'code',
         'scope' => config('services.twitch.scopes'),
-        'state' => (string) $state,
-    ]);
+    ];
+    if ($state) {
+        $params['state'] = (string) $state;
+    }
+    return 'https://id.twitch.tv/oauth2/authorize?'.http_build_query($params);
 }
app-modules/authentication/src/DTO/OAuthStateDTO.php (1)

18-26: Consider more defensive handling of malformed state payloads

fromHashedString() assumes successful decryption and JSON decoding; a tampered or corrupted $state will cause json_decode() to return null or a non-array and then fail at argument unpacking. Wrapping decryption/decoding in a try/catch and validating the decoded array shape before new self(...) would give you a controlled failure path (e.g., 400/403) instead of a generic runtime error.

app-modules/events/src/Filament/Events/ParticipantDashboard.php (1)

13-43: Align auth guard and consider reusing tenant view resolution logic

Two small suggestions here:

  • shouldRegisterNavigation() currently uses auth()->check(). To stay in lockstep with the panel’s configured guard, consider using Filament::auth()->check() instead, so navigation visibility always matches Filament’s notion of an authenticated user.
  • getView() duplicates the tenant‑slug and view‑path resolution from EventLandingPage. Extracting this into a shared helper (or trait) would reduce duplication and keep tenant routing rules in one place.
app/Providers/Filament/EventPanelProvider.php (1)

41-68: Harden tenant‑dependent login/logout URLs against missing tenant context

The panel setup looks coherent with the new tenant login/logout flow; a couple of edge cases are worth double‑checking:

  • In the TOPBAR_END hook, Filament::getLoginUrl(['tenantSlug' => session()->get('tenant')]) assumes session('tenant') is always set. If a guest ever hits the panel without that session key (e.g., first request before GuestTenantIdentifier runs), getLoginUrl() could error due to a missing required route parameter. A defensive fallback (e.g., deriving the slug from the current tenant or host) would make this more robust.
  • The logout user‑menu Action builds its URL from filament()->getTenant()?->slug. If getTenant() can be null in any scenario, route('tenant.logout', ['tenantSlug' => null]) will similarly fail. You might want to guard this or hide the logout item when no tenant is resolved.
  • @guest uses Laravel’s default auth guard; if the event panel ever uses a custom guard, using Filament::auth()->check() (or the corresponding Blade directives) would keep the button visibility in sync with Filament authentication.
app-modules/events/src/Filament/Shared/EventLogin.php (2)

46-57: Document dynamic form properties to satisfy phpstan (and future readers)

Phpstan is flagging $form, $multiFactorChallengeForm, and $registerAction as undefined properties, even though Filament’s page base/traits provide them dynamically. To make this explicit and quiet static analysis, consider adding a class‑level phpdoc block describing these properties as dynamically resolved:

+/**
+ * @property mixed $form
+ * @property mixed $multiFactorChallengeForm
+ * @property \Filament\Actions\Action $registerAction
+ */
 class EventLogin extends SimplePage
 {

This doesn’t affect runtime behavior (the properties are still provided by Filament / Livewire), but it gives tools and maintainers a clear contract that these members exist.

Also applies to: 69-70, 82-83, 100-105, 162-167, 194-197


60-70: Confirm tenant slug handling in mount() and avoid exposing non-existent tenants

mount() immediately aborts with 404 when the given $tenantSlug doesn’t exist and then stores it on the instance:

abort_unless(Tenant::query()->where('slug', $tenantSlug)->exists(), 404);
$this->tenantSlug = $tenantSlug;

That’s a good safety net; just make sure:

  • Every route pointing at EventLogin actually passes a non‑null {tenantSlug} that matches your Tenant model.
  • There’s no scenario where guests can hit the login URL without having a valid tenant (e.g., direct /event/login without slug), otherwise they’ll see an immediate 404.

If you expect a “default” tenant or want a friendlier error page, you might eventually want a dedicated exception handler or redirect instead of a raw 404.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80c1170 and 7266a80.

📒 Files selected for processing (30)
  • app-modules/authentication/routes/authentication-routes.php (1 hunks)
  • app-modules/authentication/src/Actions/AuthenticateAction.php (2 hunks)
  • app-modules/authentication/src/Contracts/OAuthClientContract.php (1 hunks)
  • app-modules/authentication/src/DTO/OAuthStateDTO.php (1 hunks)
  • app-modules/authentication/src/Enums/OAuthProviderEnum.php (2 hunks)
  • app-modules/authentication/src/Http/Controllers/OAuthController.php (1 hunks)
  • app-modules/authentication/src/Http/Controllers/TenantLogoutController.php (1 hunks)
  • app-modules/authentication/src/Http/Responses/TenantLogoutResponse.php (1 hunks)
  • app-modules/authentication/src/Providers/AuthenticationServiceProvider.php (0 hunks)
  • app-modules/events/resources/views/components/themes/3pontos/components/sections/event-progress.blade.php (1 hunks)
  • app-modules/events/resources/views/components/themes/3pontos/homepage.blade.php (1 hunks)
  • app-modules/events/resources/views/components/themes/3pontos/participant-dashboard.blade.php (1 hunks)
  • app-modules/events/src/EventPanelPlugin.php (2 hunks)
  • app-modules/events/src/Filament/Events/EventLandingPage.php (1 hunks)
  • app-modules/events/src/Filament/Events/ParticipantDashboard.php (1 hunks)
  • app-modules/events/src/Filament/Shared/EventLogin.php (1 hunks)
  • app-modules/events/src/Filament/Shared/GuestSidebar.php (1 hunks)
  • app-modules/events/src/Filament/Shared/GuestTopbar.php (1 hunks)
  • app-modules/he4rt/resources/css/components/avatar.css (1 hunks)
  • app-modules/he4rt/resources/css/components/button.css (2 hunks)
  • app-modules/he4rt/resources/css/index.css (1 hunks)
  • app-modules/he4rt/resources/css/support/themes.css (1 hunks)
  • app-modules/he4rt/resources/views/components/button.blade.php (1 hunks)
  • app-modules/he4rt/resources/views/components/card.blade.php (1 hunks)
  • app-modules/he4rt/resources/views/components/partials/oauth-connect.blade.php (1 hunks)
  • app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php (1 hunks)
  • app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php (1 hunks)
  • app/Providers/Filament/EventPanelProvider.php (3 hunks)
  • app/Providers/FilamentServiceProvider.php (2 hunks)
  • config/services.php (1 hunks)
💤 Files with no reviewable changes (1)
  • app-modules/authentication/src/Providers/AuthenticationServiceProvider.php
🧰 Additional context used
🧬 Code graph analysis (12)
app-modules/events/src/Filament/Events/EventLandingPage.php (3)
app-modules/events/src/Filament/Shared/EventLogin.php (1)
  • mount (60-70)
app/Filament/Pages/Login.php (1)
  • mount (9-19)
app-modules/events/src/Filament/Events/ParticipantDashboard.php (1)
  • getView (25-43)
app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php (3)
app-modules/authentication/src/DTO/OAuthStateDTO.php (2)
  • OAuthStateDTO (11-36)
  • __construct (13-16)
app-modules/authentication/src/Contracts/OAuthClientContract.php (1)
  • redirectUrl (13-13)
app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php (1)
  • redirectUrl (15-24)
app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php (4)
app-modules/authentication/src/DTO/OAuthStateDTO.php (1)
  • OAuthStateDTO (11-36)
app-modules/authentication/src/DTO/OAuthUserDTO.php (1)
  • OAuthUserDTO (9-31)
app-modules/authentication/src/Contracts/OAuthClientContract.php (1)
  • redirectUrl (13-13)
app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php (1)
  • redirectUrl (18-27)
app-modules/authentication/src/Actions/AuthenticateAction.php (1)
app-modules/authentication/src/DTO/OAuthStateDTO.php (1)
  • OAuthStateDTO (11-36)
app-modules/authentication/src/Http/Controllers/OAuthController.php (5)
app-modules/authentication/src/DTO/OAuthStateDTO.php (2)
  • OAuthStateDTO (11-36)
  • fromHashedString (23-26)
app-modules/authentication/src/Enums/OAuthProviderEnum.php (1)
  • getClient (22-28)
app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php (1)
  • redirectUrl (15-24)
app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php (1)
  • redirectUrl (18-27)
app-modules/authentication/src/Actions/AuthenticateAction.php (2)
  • AuthenticateAction (18-87)
  • withOAuth (20-29)
app-modules/authentication/src/Http/Responses/TenantLogoutResponse.php (2)
app-modules/tenant/src/Models/Tenant.php (1)
  • Tenant (22-94)
app/Providers/Filament/EventPanelProvider.php (1)
  • panel (35-82)
app-modules/authentication/src/Enums/OAuthProviderEnum.php (4)
app-modules/authentication/src/DTO/OAuthStateDTO.php (1)
  • OAuthStateDTO (11-36)
app-modules/authentication/src/Contracts/OAuthClientContract.php (1)
  • redirectUrl (13-13)
app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php (1)
  • redirectUrl (15-24)
app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php (1)
  • redirectUrl (18-27)
app-modules/authentication/src/Http/Controllers/TenantLogoutController.php (2)
app-modules/authentication/src/Http/Responses/TenantLogoutResponse.php (1)
  • TenantLogoutResponse (11-26)
app/Providers/Filament/EventPanelProvider.php (1)
  • panel (35-82)
app-modules/authentication/src/DTO/OAuthStateDTO.php (4)
app-modules/authentication/src/DTO/OAuthAccessDTO.php (4)
  • OAuthAccessDTO (7-25)
  • __construct (9-13)
  • toDatabase (17-24)
  • make (15-15)
app-modules/authentication/src/DTO/OAuthUserDTO.php (3)
  • OAuthUserDTO (7-29)
  • __construct (9-17)
  • make (19-19)
app-modules/integrations/src/Twitch/OAuth/DTO/TwitchOAuthDTO.php (1)
  • TwitchOAuthDTO (10-26)
app-modules/integrations/src/Twitch/OAuth/DTO/TwitchOAuthAccessDTO.php (1)
  • TwitchOAuthAccessDTO (9-19)
app-modules/events/src/Filament/Events/ParticipantDashboard.php (2)
app-modules/events/src/Filament/Shared/EventLogin.php (1)
  • getTitle (207-210)
app-modules/events/src/Filament/Events/EventLandingPage.php (1)
  • getView (18-36)
app-modules/authentication/routes/authentication-routes.php (3)
app-modules/authentication/src/Http/Controllers/TenantLogoutController.php (1)
  • TenantLogoutController (11-25)
app-modules/authentication/src/Http/Controllers/OAuthController.php (1)
  • OAuthController (14-33)
app-modules/authentication/src/Providers/AuthenticationServiceProvider.php (1)
  • AuthenticationServiceProvider (13-25)
app-modules/events/src/EventPanelPlugin.php (1)
app-modules/events/src/Filament/Events/ParticipantDashboard.php (1)
  • ParticipantDashboard (11-44)
🪛 GitHub Check: Perform Phpstan Check / Run
app/Providers/FilamentServiceProvider.php

[failure] 49-49:
Call to an undefined method App\Providers\FilamentServiceProvider::getId().


[failure] 45-45:
Call to an undefined method App\Providers\FilamentServiceProvider::colors().

app-modules/events/src/Filament/Shared/EventLogin.php

[failure] 434-434:
Attribute class He4rt\Events\Filament\Shared\SensitiveParameter does not exist.


[failure] 294-294:
Attribute class He4rt\Events\Filament\Shared\SensitiveParameter does not exist.


[failure] 231-231:
Access to an undefined property He4rt\Events\Filament\Shared\EventLogin::$registerAction.


[failure] 121-121:
Access to an undefined property He4rt\Events\Filament\Shared\EventLogin::$multiFactorChallengeForm.


[failure] 104-104:
Access to an undefined property He4rt\Events\Filament\Shared\EventLogin::$multiFactorChallengeForm.


[failure] 82-82:
Access to an undefined property He4rt\Events\Filament\Shared\EventLogin::$form.


[failure] 69-69:
Access to an undefined property He4rt\Events\Filament\Shared\EventLogin::$form.

🪛 PHPMD (2.15.0)
app-modules/authentication/src/Http/Responses/TenantLogoutResponse.php

13-13: Avoid unused parameters such as '$request'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (19)
app-modules/he4rt/resources/css/components/avatar.css (1)

16-18: LGTM!

The new hp-size-xl modifier fits logically in the avatar sizing scale and follows the established pattern consistently. The change integrates well with existing size modifiers.

app-modules/he4rt/resources/views/components/card.blade.php (1)

62-76: Good optimization – no issues found, safe to merge.

CSS styling for .hp-card-body is semantically tied to content existence (flex layout for spacing between title and description). All current card usages include either title or description, making the conditional optimization always true in practice. No JavaScript selectors depend on this element, and the change prevents unnecessary DOM rendering without affecting layout or behavior.

app-modules/events/resources/views/components/themes/3pontos/components/sections/event-progress.blade.php (2)

38-60: The suggested implementation is architecturally incorrect for this codebase.

The buttons are indeed non-functional and the data is hardcoded, but the proposed solution using wire:click="logout" and wire:click="disconnectAccount" is incompatible with the actual architecture.

Why the review is incorrect:

  1. Wrong component type: ParticipantDashboard is a Filament Page (not a Livewire component), and event-progress is a plain Blade component. Livewire directives like wire:click require a Livewire component class, which doesn't exist in this codebase.

  2. Correct approach: The logout should use a form submission with POST method to the existing route auth/{tenantSlug}/logout, not Livewire method calls.

  3. Missing feature: A "disconnect account" (disconnectAccount) method was suggested but doesn't exist anywhere in the codebase. Only the TenantLogoutController logout functionality is implemented.

The buttons need to be connected to the logout route via form submission or a POST link, and the data should be dynamically bound if props are passed to the component. However, this requires understanding how the ParticipantDashboard page should pass user data to the component and implementing the form submission pattern appropriately for a Filament Page context.

Likely an incorrect or invalid review comment.


63-92: Rewrite review comment: Clarify that OAuth management belongs in ConnectionHub, not event-progress.

The event-progress component is part of the event participant dashboard and does not appear to be intended for managing user OAuth connections. The codebase already has a dedicated ConnectionHub Livewire component (app/Livewire/ConnectionHub.php) that properly handles OAuth provider connections with functional disconnect buttons. The hardcoded platform cards in event-progress may be intentional for displaying event-specific metrics rather than user connection status.

If this review is targeting user OAuth connection management, the work should focus on ConnectionHub (which already has disconnect functionality), not on modifying the event-progress component.

Likely an incorrect or invalid review comment.

app-modules/events/src/Filament/Shared/GuestSidebar.php (1)

5-5: LGTM! Namespace refactor aligns with shared component organization.

The namespace migration from Events to Shared properly reflects the component's role as a shared guest UI element, consistent with the parallel change in GuestTopbar.php.

app-modules/he4rt/resources/css/index.css (1)

137-163: LGTM! Comprehensive gray palette enhances theming.

The addition of light and dark mode gray color tokens using OKLCH color space provides better color consistency and accessibility. The !important flags ensure these tokens take precedence in the theming system.

app-modules/events/src/Filament/Shared/GuestTopbar.php (1)

5-5: LGTM! Namespace refactor completes shared component migration.

Consistent with GuestSidebar.php, this namespace change properly organizes shared guest UI components.

config/services.php (1)

41-41: LGTM! OAuth provider enable flags provide flexible runtime control.

The addition of enabled configuration keys for Discord and Twitch OAuth services allows runtime control over provider availability without code changes. The default value of true maintains backward compatibility.

Also applies to: 48-48

app-modules/he4rt/resources/views/components/button.blade.php (1)

7-7: LGTM! Default rounding change aligns with updated design system.

Changing the default rounded prop from "sm" to "md" increases border radius for buttons across the application. This aligns with the corresponding CSS updates in button.css where the rounding token mappings have been reorganized.

app-modules/he4rt/resources/css/components/button.css (1)

25-25: LGTM! Button styling updates improve consistency.

The changes realign CSS class names with their actual Tailwind rounding utilities:

  • hp-button-rounded-sm now correctly applies rounded-sm (previously rounded-md)
  • hp-button-rounded-md now correctly applies rounded-md (previously rounded-lg)
  • New hp-button-rounded-xl class adds an additional rounding option

The py-2 padding for xs buttons (line 25) provides slightly more breathing room. These changes work cohesively with the default rounding update in button.blade.php.

Also applies to: 77-87

app-modules/events/src/EventPanelPlugin.php (1)

11-25: ParticipantDashboard registration in the panel looks correct

Importing and registering ParticipantDashboard::class alongside EventLandingPage::class is the right way to expose the new page on the event panel; this matches Filament’s expectations and the dynamic tenant view resolution implemented in ParticipantDashboard.

app-modules/authentication/src/Contracts/OAuthClientContract.php (1)

8-13: LGTM! Clean interface update for state-driven OAuth flow.

The change from ?string $state to ?OAuthStateDTO $state aligns with the PR's objective to implement structured OAuth state handling. All visible implementations (Discord, Twitch) have been updated accordingly.

app-modules/authentication/src/Http/Controllers/OAuthController.php (1)

27-29: Verify intended behavior for missing tenant.

The firstOrFail() will throw a ModelNotFoundException if the tenant doesn't exist. Confirm this is the desired behavior rather than gracefully handling the error.

If graceful handling is preferred, consider:

-    $redirectUri = filament()
-        ->getPanel($state->panel)
-        ->getUrl(Tenant::query()->where('slug', $state->tenant)->firstOrFail());
+    $tenant = Tenant::query()->where('slug', $state->tenant)->first();
+    if (! $tenant) {
+        abort(404, 'Tenant not found');
+    }
+    
+    $redirectUri = filament()
+        ->getPanel($state->panel)
+        ->getUrl($tenant);
app/Providers/FilamentServiceProvider.php (2)

45-47: Static analysis hints are false positives.

The PHPStan errors about undefined colors() and getId() methods are false positives. These methods exist on the Panel class, and within the macro closure, $this refers to the Panel instance, not the ServiceProvider.

Also applies to: 49-50


49-50: Reconsider the performance concern—session writes occur during bootstrap, not per-request.

The macro is called only once during panel registration in EventPanelProvider.php:50, not on every request. However, the session writes in lines 49-50 still warrant scrutiny: session values are derived from request data (tenant slug from path/host), yet this initialization happens during app bootstrap before typical request handling. Writing request-dependent values to session during bootstrap is problematic because:

  1. Request context may not be properly initialized at bootstrap time
  2. These values are re-derived on each macro invocation anyway (lines 25-33), making session caching ineffective
  3. Session storage during bootstrap creates unnecessary coupling between initialization and request handling

Consider either removing the session writes entirely (if these values aren't needed elsewhere) or deferring them to middleware where proper request context exists.

app-modules/authentication/src/Http/Responses/TenantLogoutResponse.php (1)

13-13: Static analysis hint about unused parameter is expected.

The $request parameter is unused but required by the LogoutResponse interface contract. This is a false positive.

app-modules/authentication/src/Actions/AuthenticateAction.php (1)

50-54: Clarify authentication flow logic.

The auth flow on Lines 50-54 checks ! auth()->check(), then calls Auth::logout(), then Auth::login(). This seems contradictory:

  1. If not authenticated (! auth()->check()), why call Auth::logout()?
  2. If already authenticated, the user is not logged out as the intended user.

Consider simplifying:

-    if (! auth()->check()) {
-        Auth::logout();
-        Auth::login($provider->user);
-        filament()->auth()->setUser($provider->user);
-    }
+    if (! auth()->check() || auth()->id() !== $provider->user->id) {
+        Auth::logout();
+        Auth::login($provider->user);
+        filament()->auth()->setUser($provider->user);
+    }

Or clarify the intended logic with comments.

app-modules/authentication/routes/authentication-routes.php (1)

12-18: Verify OAuth redirect routes are correctly encoding panel and tenant into state

The new redirect routes (oauth.single.redirect and oauth.tenant.redirect) add {panel} and {tenant} segments but still point at OAuthController::getRedirect(OAuthProviderEnum $provider), which only takes $provider as an argument. That’s fine in Laravel (panel/tenant remain available via request()->route()), but it does mean the logic that builds OAuthStateDTO must now read those route params explicitly (likely inside the OAuth client or controller).

Please double‑check:

  • The code that calls route('oauth.single.redirect', ...) / route('oauth.tenant.redirect', ...) passes keys matching {tenant} / {panel} / {provider}.
  • The redirect handler ultimately constructs OAuthStateDTO with the correct panel and tenant from those route params before sending the user to the provider.
app-modules/events/src/Filament/Shared/EventLogin.php (1)

72-141: Overall authenticate flow looks solid; just ensure MFA providers are wired as expected

The authenticate() method:

  • Applies rate limiting via WithRateLimiting.
  • Validates credentials against the auth provider.
  • Orchestrates multi‑factor authentication using Filament::getMultiFactorAuthenticationProviders(), including HasBeforeChallengeHook support and challenge form validation.
  • Uses attemptWhen() to enforce FilamentUser::canAccessPanel() checks before logging in.
  • Regenerates the session and returns the panel’s LoginResponse on success.

This aligns well with Filament’s recommended patterns. The main thing to verify in integration tests is that:

  • When multiple MFA providers are enabled for a user, the provider radio and conditional challenge groups behave correctly.
  • The encrypted userUndertakingMultiFactorAuthentication token cannot be tampered with from the client (the #[Locked] attribute should cover this) and always decrypts to the same user id being authenticated.

Functionally, the structure looks good.

Comment thread app-modules/authentication/src/Actions/AuthenticateAction.php
Comment thread app-modules/authentication/src/Actions/AuthenticateAction.php
Comment thread app-modules/authentication/src/Enums/OAuthProviderEnum.php
Comment thread app-modules/authentication/src/Enums/OAuthProviderEnum.php
Comment thread app-modules/authentication/src/Http/Controllers/OAuthController.php
Comment thread app-modules/events/src/Filament/Shared/EventLogin.php
Comment thread app-modules/events/src/Filament/Shared/EventLogin.php
Comment thread app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php
Copy link
Copy Markdown
Contributor Author

@danielhe4rt danielhe4rt left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 484269d and f7957f0.

📒 Files selected for processing (1)
  • app-modules/authentication/tests/Feature/Actions/AuthenticateActionTest.php (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app-modules/authentication/tests/Feature/Actions/AuthenticateActionTest.php (2)
app-modules/authentication/src/DTO/OAuthStateDTO.php (1)
  • OAuthStateDTO (11-36)
app-modules/authentication/src/Actions/AuthenticateAction.php (1)
  • withOAuth (20-29)
🔇 Additional comments (2)
app-modules/authentication/tests/Feature/Actions/AuthenticateActionTest.php (2)

6-6: LGTM!

The import of OAuthStateDTO is correct and necessary for the refactored OAuth state handling.


60-60: LGTM!

The OAuth state is correctly instantiated with the panel ('admin') and tenant slug ('he4rt'), and named parameters improve readability.

@danielhe4rt danielhe4rt merged commit 826d9e9 into 3.x Nov 23, 2025
6 checks passed
@danielhe4rt danielhe4rt deleted the chore/oauth-state-revamp branch November 23, 2025 21:58
This was referenced Nov 23, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Dec 8, 2025
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.

5 participants