Skip to content

fix: resolve the identity field and prefer webware's user contract - #100

Merged
tyrsson merged 3 commits into
2.0.xfrom
fix/monolog-identity-enrichment
Oct 15, 2026
Merged

tyrsson merged 3 commits into
2.0.xfrom
fix/monolog-identity-enrichment

Conversation

@tyrsson

@tyrsson tyrsson commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

What this fixes

Two defects that made user_identifier NULL on every row this package has ever written.

1. The attribute key never matched. MonologMiddleware looked the authenticated user up under Mezzio\Authentication\UserInterface. IdentityMiddleware attaches the user under Webware\Core\UserInterface. Request attributes are keyed by class name, so the lookup missed every time, the processor was never pushed, and extra never carried an identity for PhpDbHandler to strip into the column. Verified against the running app: attaching the identity under webware's key produces user_identifier = 'jsmith@webinertia.net'; under mezzio's key the column stays NULL.

The lookup now tries webware's contract first and mezzio's second, both as attribute keys, so a host on either contract is enriched and a request carrying both resolves to webware's.

2. The producer and the consumer disagreed about the extra key. The processor wrote extra['email'] unconditionally, while PhpDbHandlerFactory read authentication.username (default email) to decide which key to strip. Point authentication.username at a non-email identity column and the two fall apart: the column goes NULL and the identity leaks into the serialized context instead. Both sides now resolve the name through a new IdentityField class, so renaming the column moves both.

auth_attribute is removed

The key could only ever hold one of the two contract names, and a host maps its implementation onto one of those contracts, so the name is not the host's to choose. A configuration still setting it is unaffected: the key is ignored, and the fixed lookup is strictly better than what it replaced.

Dependency change

webware/webware-core joins require-dev, alongside the webware-tools bump to 1.0.0-rc.6 that takes the mago pin from 1.55.0 to 1.57.0.

It is a dev requirement on purpose. The middleware names webware's contract as one of the two attribute keys it reads, and it cannot be written as a string literal: naming an absent class is an analyze error, and the literal trips no-literal-namespace-string, whose centre config fails the gate at Help level. Requiring it as a dev dependency is the same shape this package already uses for Mezzio's contract, which src/ConfigProvider.php references through ::class while mezzio/mezzio-authentication sits in require-dev. A consumer that uses no other webware component still installs none of it.

Net effect on this package's dev install: 6 packages, all laminas and psr, no conflicts.

Verification

  • Unit suite: 109 tests, 188 assertions, green (100 tests at HEAD, +9 from the new IdentityField cases and the contract-preference cases).
  • Integration suite: 10 tests, 21 assertions, green.
  • mago format --check, lint, analyze and guard all exit 0 against the 1.57.0 config, with no new findings.
  • Confirmed end to end in the webware app against MySQL and Mailpit: an admin-created user accepts the set-password link and the first sign-in writes one security row.

Commits

  1. chore(deps): bump webware-tools to 1.0.0-rc.6 and add webware-core
  2. chore: apply mago 1.57.0 formatting - 10 files, whitespace only, proven by comparing each against HEAD with all whitespace stripped.
  3. fix: resolve the identity field and prefer webware's user contract - the change itself, its tests, the docs, and the regenerated analysis baseline.

Note for review

analysis-baseline.toml is regenerated. Dropping auth_attribute from the inferred LogDefaults shape rewrote the message text of six baselined entries, which orphaned them and made the same findings reappear as new. The regenerated file suppresses the same findings; the entry count moves from 22 to 20 because two message variants collapsed.

Three test fixtures in LogFactoryTest, MezzioErrorHandlerDelegatorTest and PhpDbHandlerFactoryTest had the now-dead auth_attribute key removed alongside their reformatting.

A correction to an earlier revision of this description: I first claimed lint warnings do not fail the gate. They do. The centre sets [linter] minimum-fail-level = "Help", and my measurement had read a stale exit code. The dev requirement above is the consequence.

@codecov

codecov Bot commented Oct 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

webware-tools moves from 1.0.0-rc.5 to 1.0.0-rc.6, which takes the mago
pin from 1.55.0 to 1.57.0.

webware-core joins require-dev so the middleware can name its user
contract through ::class. It stays a dev requirement: a consumer that
uses no other webware component never installs it, and the constant
resolves to the attribute key without the interface being loaded.
Reformats the files mago 1.57.0 lays out differently from 1.55.0: a chain
whose receiver is a variable now starts on the receiver's own line.
Whitespace only, verified by comparing each file against HEAD with all
whitespace stripped.
MonologMiddleware looked the authenticated user up under one hardcoded
attribute, mezzio's contract, while IdentityMiddleware attaches the user
under Webware\Core\UserInterface. Two different class names never match,
so every log row this package has written carries a NULL user_identifier.

The lookup now tries webware's contract first and mezzio's second, both
as attribute keys, so a host on either contract is enriched and a request
carrying both resolves to webware's.

The processor also wrote the identity to a fixed extra['email'], while
PhpDbHandlerFactory read authentication.username - defaulting to 'email'
- to decide which extra key to strip into the user_identifier column.
Setting authentication.username to anything else made the producer and
the consumer disagree, leaving the column NULL and leaking the identity
into the serialized context instead. Both sides now resolve that name
through a new IdentityField class, so renaming the identity column moves
both at once.

The auth_attribute config key is removed: it could only ever hold one of
the two contract names, and a host maps its implementation onto one of
those contracts, so the name is not the host's to choose. A
configuration still setting it is unaffected - the key is simply ignored.
@tyrsson
tyrsson force-pushed the fix/monolog-identity-enrichment branch from 8a82c1a to 09fa7a9 Compare October 15, 2026 17:01
@tyrsson
tyrsson merged commit 4cd3632 into 2.0.x Oct 15, 2026
12 checks passed
@tyrsson
tyrsson deleted the fix/monolog-identity-enrichment branch October 15, 2026 19:27
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