Skip to content

feat: auth (2FA/passkeys), activity log, domain status checks, user/team management#19

Merged
DGINXREAL merged 68 commits into
mainfrom
new-features
Jun 19, 2026
Merged

feat: auth (2FA/passkeys), activity log, domain status checks, user/team management#19
DGINXREAL merged 68 commits into
mainfrom
new-features

Conversation

@DGINXREAL

Copy link
Copy Markdown
Member

Summary

Large feature branch bringing several capabilities to Marketix. 65 commits, 130 files. Grouped by area below.

Auth & account security

  • TOTP two-factor: enable/confirm/disable/recovery codes, login gated behind a TOTP challenge, rate-limited challenge endpoints.
  • Passkeys (laravel/passkeys) as a second factor, with login/challenge buttons and profile management UI.
  • Forced password change: force_password_change column, gate, dedicated page/endpoint.

Activity log (Spatie activitylog v5)

  • Auto-logging for Url (with password redaction), Domain, QrCode, Pixel, Project.
  • Security event logging (login, password, 2FA, passkeys) and membership/invitation events.
  • ActivityRecorder helper for manual events; project-scoped project_id column and custom model.
  • Project activity feed, admin audit view with filters, per-resource history panel with lazy loading.
  • Daily pruning scheduled (365-day retention).

Domains

  • On-create + 15-minute status checks via CheckDomainStatusJob (single write path) and DomainStatusChecker.
  • DNS resolver / certificate reader seams, /.well-known/marketix signature route, on-demand check endpoint.
  • Status columns + derived accessor; SSRF hardening and tighter cert matching; status UI (pills, info box, check-now).

Users & team

  • Sectioned user edit page (projects + security actions), user-centric membership endpoints, admin "send password reset link".
  • Invitations: resend endpoint/button, last_sent_at, expired badges, cross-project isolation test.

Admin & layout

  • marketix:create-admin console command (create or promote a super admin).
  • Renamed existing commands under the marketix: prefix.
  • Horizon link in admin sidebar; app version shown in sidebar/admin/auth layouts.
  • SweetAlert2 replaces native delete confirms.

Stats

  • Trust the reverse proxy so the real client IP is recorded.

Test plan

  • Full suite green: 254 passing.
  • Design specs and implementation plans for the larger features are committed under docs/.

🤖 Generated with Claude Code

DGINXREAL and others added 30 commits June 18, 2026 16:53
Add a Horizon navigation point to AdminSidebar. Uses a plain anchor
(full-page navigation) since Horizon is a Blade-rendered dashboard, not
an Inertia page. Access stays consistent with the super_admin-gated
admin area and Horizon's viewHorizon gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Share the package.json version as an Inertia prop and render it as a
muted label at the bottom of the sidebar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render the shared version prop at the AdminSidebar bottom and in the
GuestLayout (branding panel on desktop, footer on mobile).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a themed confirmDelete() helper (resources/js/lib/confirm.ts) that
follows the app's light/dark theme and defaults focus to Cancel, then
wire it into all delete actions across links, domains, QR codes, pixels,
team, and admin pages. The previously unguarded team invitation revoke
now requires confirmation too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents double-submission of the disable and recovery-code regeneration
requests, matching the existing processing guard on the confirm button.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ings

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…led button hover

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… matching

- Block SSRF in checkReachable: resolve host IPs first; refuse if empty or any IP is
  private/reserved/loopback/link-local using isPublicIp() (filter_var FILTER_FLAG_NO_PRIV_RANGE |
  FILTER_FLAG_NO_RES_RANGE); disable redirect following with allow_redirects=false.
- Tighten certCoversHost wildcard matching: *.example.com now matches only hosts with
  the same dot-label count (one wildcard label), preventing deep.sub.example.com from matching.
- Guard SystemCertificateReader::read() against openssl_x509_parse() returning false.
- Add TDD tests: SSRF private IP refused, SSRF unresolvable refused, wildcard single-label
  match, wildcard multi-label rejection, fault isolation (throwing resolver does not abort SSL check).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DGINXREAL and others added 24 commits June 18, 2026 22:32
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… and custom model

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove unused Spatie Activity import from published config
- Add down() to published create_activity_log_table migration
- Remove unused ActivitylogServiceProvider import from SchemaTest

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nges

spatie/laravel-activitylog v5 renamed the subject hook from tapActivity to
beforeActivityLogged and stores attribute diffs in $activity->attribute_changes
rather than ->properties. The original trait targeted the v4 names, so it was
silently inert: the project tag was never set and sensitive values were never
redacted in production. Retarget the hook and redact attribute_changes (plus
properties defensively).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ attribute_changes)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Array

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erns, Support\LogOptions, dontLogEmptyChanges)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ot authenticatable)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… spacing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oject in admin feed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Spatie-published migration created subject_id/causer_id as bigint, but
all subjects (Url/Domain/QrCode/Pixel/Project) and causers (User) use ULID
keys. On MariaDB this truncated inserted ULIDs (demo:setup crash); SQLite's
loose type affinity hid it in the test suite. Switch both morphs to
nullableUlidMorphs (char 26).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
$request->ip() returned the Docker proxy's internal IP because no
proxies were trusted, so X-Forwarded-For was ignored. Configure
trustProxies(at: '*') to read forwarded headers, fixing client IP,
geo lookup, unique-click dedup, and activity log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Create or promote a global super admin from the CLI. Accepts --name,
--email, --password flags with interactive fallback (password prompted
securely with confirmation). Promotes an existing user without resetting
their password; no-ops when already a super admin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DGINXREAL DGINXREAL requested a review from noidee-dev as a code owner June 19, 2026 07:32
DGINXREAL and others added 3 commits June 19, 2026 09:34
Import ordering, brace position, and unused-import cleanup across
controllers, routes, config, and tests. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI copies .env.example, which sets TRAEFIK_DYNAMIC_FILE=/traefik/...,
an unwritable path on the runner. Tests that create a Domain ran the
synchronous RegenerateTraefikConfigJob and failed on file_put_contents.

Add Queue::fake([RegenerateTraefikConfigJob::class]) in setUp() for the
six affected files, matching the existing convention used across the
suite. The check endpoint still persists via dispatchSync of the real
CheckDomainStatusJob.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The partial Queue::fake([RegenerateTraefikConfigJob::class]) was
duplicated in setUp() across 15 test files. Centralize it in
Tests\TestCase::setUp() so every test inherits it and future
domain-creating tests can't regress on the unwritable Traefik path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DGINXREAL DGINXREAL merged commit 73473ad into main Jun 19, 2026
3 checks passed
@DGINXREAL DGINXREAL deleted the new-features branch June 19, 2026 08:04
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.

1 participant