# Login Audit — Improvement & Growth Plan

> Package: capell-app/login-audit · Kind: package · Tier: premium · Product group: Capell Operations · Bundle: operations · Status: Draft

## 1. Snapshot

Login Audit is an admin-only operations package (`surfaces: ["admin"]`) that wraps `rappasoft/laravel-authentication-log` + `tapp/filament-authentication-log` to record login / failed-login / logout events for Capell users and surface them in Filament. It owns the `login_audit` table (`database/migrations/2026_05_10_190857_01_create_login_audit_table.php`), a `LoginAudit` model extending the vendor `AuthenticationLog`, a settings group (`login_audit`), and admin surfaces: `LoginAuditResource`, `LoginAuditsWidget`, `LoginAuditsRelationManager`, plus a user-edit sidebar summary via `LoginAuditUserSchemaExtender`. Domain logic lives in four small Actions (`ResolveLoginAuditIpAddressAction`, `ShouldTrackUserIpAddressesAction`, `BuildLoginAuditsQueryAction`, `ApplyLoginAuditSettingsAction`); event capture itself is delegated entirely to the vendor listeners configured in `config/login-audit.php`. Two middlewares (`AdminActivityMiddleware`, `UserActivityMiddleware`) update `last_seen_at` on the most recent matching row. Deps: `capell-app/admin`, the two auth-log vendor packages. Current marketplace summary, verbatim: _"Tamper-proof access logging for Capell — track every login, failed attempt, logout, device, and session, with retention controls and an admin audit trail built for security and compliance reviews."_ **Screenshot state:** `capell.json` now promotes the extension card plus all 8 committed PNGs (4 surfaces × light/dark); `docs/screenshots.json` still names two user-resource captures that are not committed yet.

## 2. Improvements (existing functionality)

Prioritized.

1. **Done/Shipped: Implement the real health check** — `LoginAuditHealthCheck` now reports critical Diagnostics checks for the login audit storage table, required vendor listener classes, vendor capture configuration, and the `frontend.activity` middleware alias. Evidence: `tests/Feature/Health/LoginAuditHealthCheckTest.php` covers the passing diagnostic set and each failure mode. — `src/Health/LoginAuditHealthCheck.php` — **S**

2. **Done/Shipped: populate `last_activity_at`.** The shared `UpdateLastSeenForActorAction` now stamps both `last_seen_at` and `last_activity_at` for the matched session row, and `LoginAudit` casts/documents the activity timestamp as immutable. This closes the permanently-null activity field without duplicating middleware logic. — `src/Actions/UpdateLastSeenForActorAction.php`, `src/Models/LoginAudit.php`, migration — **S**

3. **Done/Shipped: de-duplicate the two middleware bodies.** `AdminActivityMiddleware` and `UserActivityMiddleware` now perform guard-specific actor resolution only, then delegate session matching and activity writes to `UpdateLastSeenForActorAction`. — `src/Http/Middleware/*`, `src/Actions/UpdateLastSeenForActorAction.php` — **M**

4. **Done/Shipped: `UserActivityMiddleware` matches the wrong row** — fixed by routing user activity through `UpdateLastSeenForActorAction`, which matches the authenticated actor by morph type/id, resolved IP, user agent, `login_at < trackedAt`, and explicit `login_at DESC, id DESC` ordering. Evidence: `LoginAuditActivityMiddlewareTest` covers latest matching user session selection, future-row exclusion, unrelated-row preservation, and insertion-order disagreement. — `src/Actions/UpdateLastSeenForActorAction.php`, `src/Http/Middleware/UserActivityMiddleware.php`, `tests/Feature/Http/LoginAuditActivityMiddlewareTest.php` — **S**

5. **Fix the duplicate/typo vendor config key** — `AdminServiceProvider::register()` sets both `filament-authentication-log.resources.AuthenticationLogResource` and the misspelled `...AutenticationLogResource`. If this is a defensive shim for a vendor typo, add a one-line comment; otherwise remove it so the intent is clear. — `src/Providers/AdminServiceProvider.php:33-40` — **S**

6. **Avoid `now()->subHours()` drift in the widget query** — `BuildLoginAuditsQueryAction` filters `login_at >= now()->subHours(24)` with `latest('login_at')->limit(20)`. On a quiet site the widget shows nothing rather than the last N logins. Consider "last 20 logins" without the hard time floor, or make the window a parameter sourced from settings. — `src/Actions/BuildLoginAuditsQueryAction.php:27-31` — **S**

7. **Done/Shipped: Make the persistent admin middleware respect a setting and throttle writes** — `AdminActivityMiddleware` now short-circuits through `ShouldTrackAdminActivityAction`, while `UpdateLastSeenForActorAction` honours `activity_update_grace_seconds` so repeated admin requests do not update `last_seen_at` inside the grace window. Evidence: `LoginAuditActivityMiddlewareTest` covers disabled admin tracking and throttled repeated writes; `LoginAuditSettingsTest` covers the settings-backed defaults. — `src/Http/Middleware/AdminActivityMiddleware.php`, `src/Actions/ShouldTrackAdminActivityAction.php`, `src/Actions/UpdateLastSeenForActorAction.php`, `src/Settings/LoginAuditSettings.php` — **S**

8. **Done/Shipped: surface trusted-device and activity columns in the UI.** The Login Audit resource now exposes device name, trusted-device state, a trusted filter, and `last_activity_at`; the user relation manager exposes trusted state, last activity, and the same trusted filter. — `src/Filament/Resources/LoginAudits/Tables/LoginAuditsTable.php`, `src/Filament/Resources/Users/RelationManagers/LoginAuditsRelationManager.php` — **S**

## 3. Missing Features (gaps)

Manifest `capabilities` now includes logging/admin visibility, device activity, CSV export, suspicious-login detection, and admin alerts. Remaining high-value security-audit depth has moved to geo/session adjacencies:

- **Done/Shipped: Suspicious-activity detection.** `DetectSuspiciousLoginAction` now marks audit rows suspicious for repeated failed attempts, successful logins after failed attempts from the same device, rapid country changes, and optional unusual login hours. `DetectSuspiciousLoginFromAuthEvent` runs the action after real `Login`/`Failed` events, settings expose detection thresholds/toggles, and the manifest advertises `login-audit-detection`.
- **Done/Shipped: country-level geo anomaly detection.** `enable_geo_location` now controls the vendor geo lookup for new-device and failed-login events, and `DetectSuspiciousLoginAction` marks audit rows suspicious when one actor has successful logins from multiple countries inside the configured `location_window_minutes`. This is intentionally country-level anomaly detection rather than precise haversine travel velocity; hosts that need city/coordinate impossible-travel scoring can add a dedicated geo provider later. — `src/Settings/LoginAuditSettings.php`, `src/Actions/ApplyLoginAuditSettingsAction.php`, `src/Actions/DetectSuspiciousLoginAction.php`
- **Done/Shipped: Alerting.** `SendLoginAuditAdminAlertAction` sends persistent Filament/database alerts to the Login Audit security alert group. New-device, failed-login, and suspicious-login toggles are exposed in settings, sync into vendor notification config, and drive Capell admin alerts after real auth events.
- **Done/Shipped: CSV / data export.** `BuildLoginAuditsCsvAction` now builds access-log CSV output for either all audit rows or one authenticatable user. The global Login Audit resource and the user-owned authentication history relation manager both expose an `Export CSV` header action, and the manifest advertises `login-audit-csv-export`.
- **Done/Positioned: active-session/device surfacing and support bundling.** The user sidebar exposes recent logins, failed attempts, recent devices, and active sessions; the user authentication-history relation manager exposes device, trusted-state, and last-activity context; and the manifest declares support pairings with Password Policy, Privacy Center, Diagnostics, and Access Gate. Destructive logout-other-devices/session termination is a host session-store boundary documented in the README rather than an audit-log responsibility.
- **Done/Shipped: Retention rotation depth.** `ApplyLoginAuditSettingsAction` clamps `retention_days` into the vendor purge config, the scheduled `authentication-log:purge` task now runs daily, and successful purges stamp `last_purged_at` for the settings screen. Evidence: `LoginAuditSettingsTest` covers retention config application and purge timestamp recording.
- **Per-event-type coverage gaps.** Config wires `Login`, `Failed`, `Logout`, `OtherDeviceLogout` (all vendor). There is no handling of password-reset, lockout, or 2FA-challenge events, and no admin-impersonation audit trail — natural adjacencies for an "operations" security package.

## 4. Issues / Risks

- **PII / IP retention & GDPR.** `login_audit` stores IP, full user-agent, and (optionally) geo `location` until the daily purge removes rows older than the configured retention window. `track_user_ip_addresses=false` nulls IP on save via `LoginAuditObserver::saving()` and in `ResolveLoginAuditIpAddressAction`, which is good — but there is no equivalent control for user-agent or geo, and no anonymisation/truncation option (e.g. /24 masking). For a stronger GDPR posture, document the lawful basis and add UA/geo controls. — `src/Observers/LoginAuditObserver.php`, `AdminServiceProvider.php:80-92`
- **Done/Shipped: Write-volume vs performance budget.** The persistent admin middleware now respects `track_admin_activity` and the shared activity update action only stamps `last_seen_at` when the configured grace window has elapsed. Evidence: `LoginAuditActivityMiddlewareTest` covers disabled tracking and the 60-second throttle. — `src/Http/Middleware/AdminActivityMiddleware.php`, `src/Actions/UpdateLastSeenForActorAction.php`
- **Done/Shipped: Event-listener coverage is entirely vendor-owned and was previously untested here.** The package now has focused integration coverage dispatching real `Illuminate\Auth\Events\Login` and `Illuminate\Auth\Events\Failed` events through the vendor listeners, asserting `login_audit` rows are written with actor, IP, user-agent, success state, and login timestamp. Evidence: `tests/Feature/Auth/LoginAuditAuthEventsTest.php`.
- **Done/Shipped: schema fields now map to shipped surfaces.** Suspicious flags/reasons are written by `DetectSuspiciousLoginAction`, `last_activity_at` is stamped by `UpdateLastSeenForActorAction`, trusted/device fields are surfaced in the resource and relation manager, and geo location is controlled by settings before vendor capture. — migration, `src/Actions/*`, `LoginAuditsTable.php`, `LoginAuditsRelationManager.php`
- **Public-output safety: low risk, one thing to watch.** Package is admin-only (`surfaces: ["admin"]`, `frontendRenderBudgetMs: 0`), so the strict anonymous-leak rules mostly don't apply. However `UserActivityMiddleware` is aliased as `frontend.activity` and writes audit rows for _authenticated frontend users_; confirm it is never applied to anonymous routes (guarded by `$request->user() !== null`, which is correct) and that it never emits output. The widget builds raw `HtmlString` for the "last active" tooltip (`LoginAuditsWidget.php:116-122`) — interpolated values are framework-formatted dates/translations, not user input, so currently safe, but any future field injected there must be escaped. Authenticatable name rendering is already covered by the "renders authenticatable names as safe text instead of raw html" test.
- **Authorization surface.** `LoginAuditPolicy` only implements `viewAny`/`view` (read-only), and the relation manager hard-disables create/edit/delete — appropriate for an immutable audit log. `SiteScope::isGlobalActor()` short-circuits to allow; confirm that is the intended bypass for global admins. No `delete`/`forceDelete` policy means rows are only removable via the purge command, which is good for audit integrity but means an operator cannot redact a single erroneous PII row from the UI — a deliberate trade-off worth documenting.
- **Done/Shipped: i18n.** `LoginAuditDashboardSettingsContributor::settingsKeys()` now resolves the dashboard label/group through `capell-login-audit::dashboard.*` translation strings. Evidence: `src/Filament/Settings/Contributors/LoginAuditDashboardSettingsContributor.php`, `resources/lang/en/dashboard.php`.
- **Accepted residual test gaps.** Covered: migration columns/custom-table, settings schema + retention clamp, table/relation/widget metadata, IP resolver (CDN header valid/invalid/direct), IP-null-when-tracking-off, observer IP stripping, both middleware happy/guard paths, real `Login`/`Failed` event → row capture, bridge enable/disable matrix, policy permission gate, orphaned-row placeholder, name-as-safe-text, provider registration, suspicious detection/admin alerts, CSV export, and user-resource surfaces. Remaining hardening: purge command actually deleting old rows and the `now()->subHours(24)` empty-window behaviour of the widget query.

## 5. Marketplace & Selling

**Critique.** The manifest summary and description now sell the real shipped value: immutable access logging, retention controls, CDN-aware IP capture, suspicious detection, admin alerts, CSV export, per-user access summaries, and read-only admin review. The marketplace gallery includes all 8 committed PNG captures, so the listing is no longer card-only.

**Improved 1-sentence summary:**

> Tamper-proof access logging for Capell — track every login, failed attempt, logout, device, and session, with retention controls and an admin audit trail built for security and compliance reviews.

**Improved 3–4 sentence description:**

> Login Audit gives Capell operators a complete, immutable record of who accessed the admin and storefront, when, from which IP and device, and whether each attempt succeeded. A dedicated Filament resource, dashboard widget, and per-user access summary make it easy to spot unusual activity and respond to incidents, while configurable retention keeps the log lean and aligned with your data-protection policy. IP capture can be disabled or resolved through your CDN (Cloudflare and similar), and the audit log is read-only by design so records can't be quietly altered. Built on the battle-tested Laravel authentication-log foundation and wired into Capell's settings, permissions, and Diagnostics.

(Note: device, suspicious, and geo language is now justified by the shipped device surfaces, suspicious detection, geo-location setting, and rapid-country-change detection.)

**Screenshot/media status.** The original 8 PNGs are promoted into `marketplace.screenshots`, and the two previously missing user-resource captures are now committed: `user-edit-access-summary.png` and `user-login-audits-relation-manager.png`. Add a short "spotting a suspicious login" GIF once detection lands.

**Pricing / tier / bundle positioning.** Tier `premium`, bundle `operations`, group `Capell Operations`, `proposedLicense: paid`, `requestedCertification: first-party`, `supportPolicy: priority`. The premium positioning is now defensible because the package goes beyond the vendor log wrapper with Capell Diagnostics, settings, retention, CSV export, suspicious detection, admin alerts, user-resource surfaces, screenshot coverage, and security-bundle support pairings. Bundle fit is correct.

**Cross-sell.** Declared deps: `capell-app/admin` (hard requires). The manifest `dependencies.supports`/`conflicts` are empty — populate `supports` to enable bundling. Natural Extension-Suite cross-sells:

- **password-policy** — pair "enforce strong credentials" with "audit who logged in"; obvious security bundle.
- **privacy-center** — Login Audit holds IP/UA/geo PII; privacy-center can expose subject-access/erasure over the audit log (retention + anonymisation hooks).
- **diagnostics** — already the consumer of the (stub) health check; a working check makes Login Audit a first-class Diagnostics citizen.
- **access-gate** — gated areas + access logging is a coherent "who got in where" story.

**Differentiators / value props / target buyer.** Buyer: security-conscious site operators, agencies managing client Capell installs, and teams with compliance obligations (GDPR/SOC-ish). Value props: immutable read-only audit trail; CDN-aware accurate IP capture; configurable PII retention; suspicious detection/admin alerts; country-level geo anomaly detection when location capture is enabled; per-user access summary inside the user editor; zero frontend cost (admin-only).

**Keywords / tags (8–12):** login audit, authentication log, security audit, access log, failed login tracking, GDPR retention, suspicious login detection, device tracking, session monitoring, admin activity, IP logging, compliance.

## 6. Prioritized Roadmap

| Item                                                                                                                                                                                                                                                              | Bucket | Effort | Impact | Section ref |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ----------- |
| Done/Shipped: Implement real `LoginAuditHealthCheck` (table/listeners/alias/capture). Evidence: `LoginAuditHealthCheckTest` covers pass/fail diagnostics.                                                                                                         | Done   | S      | High   | 2.1, 4      |
| Done/Shipped: Add daily purge + honour `retention_days` precisely; show "last purged". Evidence: daily schedule plus `LoginAuditSettingsTest` retention and timestamp coverage.                                                                                   | Done   | S      | High   | 3, 4        |
| Done/Shipped: Throttle/guard `AdminActivityMiddleware` writes (query-budget risk). Evidence: `LoginAuditActivityMiddlewareTest` covers disabled tracking and throttled writes.                                                                                    | Done   | S      | High   | 2.7, 4      |
| Done/Shipped: Fix `UserActivityMiddleware` row-matching ordering. Evidence: shared `UpdateLastSeenForActorAction` uses `login_at DESC, id DESC`; middleware regression covers insertion-order disagreement.                                                       | Done   | S      | Med    | 2.4         |
| Done/Shipped: Add integration test: real `Login`/`Failed` event → audit row. Evidence: `tests/Feature/Auth/LoginAuditAuthEventsTest.php` dispatches real events and asserts persisted audit rows.                                                                 | Done   | S      | High   | 4           |
| Done/Shipped: Promote 8 screenshots into `marketplace.screenshots`; rewrite summary + manifest description. Evidence: `capell.json` references all committed light/dark captures.                                                                                 | Done   | S      | High   | 5           |
| Done/Shipped: Translate `DashboardSettingsContributor` label/group strings. Evidence: contributor resolves `capell-login-audit::dashboard.access_logs` and `capell-login-audit::dashboard.group`.                                                                 | Done   | S      | Low    | 4           |
| Done/Shipped: Build `DetectSuspiciousLoginAction` (writes `is_suspicious`/`reason`) + `login-audit-detection` capability. Evidence: `DetectSuspiciousLoginAction`, `DetectSuspiciousLoginFromAuthEvent`, detection settings, and manifest capability.             | Done   | L      | High   | 3           |
| Done/Shipped: Wire failed-login / new-device alerts into Capell admin notifications + expose in settings. Evidence: `SendLoginAuditAdminAlertAction`, notification group registration, alert settings, and `login-audit-admin-alerts`.                            | Done   | M      | High   | 3           |
| Done/Shipped: Add CSV export action to resource + relation manager. Evidence: `BuildLoginAuditsCsvAction`, the Login Audit resource table header action, the user relation manager header action, and `login-audit-csv-export`.                                   | Done   | M      | Med    | 3           |
| Done/Shipped: Surface `is_trusted`/device columns + filters; populate `last_activity_at`                                                                                                                                                                          | Done   | M      | Med    | 2.2, 2.8, 3 |
| Done/Shipped: Extract shared `UpdateLastSeenForActorAction`; collapse duplicate middleware                                                                                                                                                                        | Done   | M      | Med    | 2.3         |
| Done/Shipped: Capture the two missing screenshots (access summary, relation manager)                                                                                                                                                                              | Done   | S      | Med    | 5           |
| Done/Shipped: Geo resolution on login + country-level impossible-travel check. Evidence: `enable_geo_location` syncs vendor geo lookup flags, and `DetectSuspiciousLoginAction` flags rapid country changes within the configured location window.                | Done   | L      | High   | 3           |
| Done/Positioned: active-session/device sidebar and relation-manager surfaces plus `supports[]` for password-policy/privacy-center/diagnostics/access-gate bundling. Evidence: README documents destructive logout-other-devices as a host session-store boundary. | Done   | L      | Med    | 3, 5        |