Profile: only edit fields Dex reads back

The Dex LDAP connector publishes uid, cn and mail as claims, so the
profile page now offers just Name (cn), the verified email change and
the password. The photo endpoint, SSH keys, addresses, phones and
other extended attributes are no longer read or written; existing LDAP
values are left untouched. Apache's profile upload limit drops back to
the 256 KiB used by other member routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 21:42:57 -05:00
co-authored by Claude Opus 5.5
parent 67d8caccce
commit d73f7d1b80
9 changed files with 114 additions and 507 deletions
+22 -45
View File
@@ -378,59 +378,38 @@ separate coordinated change, not an automatic part of this release.
### Self-service account profile
Signed-in members can choose **Edit profile** from their membership page.
Deploy the new membership image, apply migration `0005_profile_email_change`,
and deploy the updated static CSS/proxy configuration. No new public settings
or credentials are required. Login remains Dex-only: the profile editor reads
LDAP **only when opened**, because the configured Dex LDAP connector exposes
username/email/name/groups, not arbitrary LDAP attributes. Cached OIDC claims
are neither a complete nor a current editable profile. Do not map the gamer tag
to `preferred_username` or change `idAttr` to expose it.
Login is Dex-only, and the profile editor offers **only the values the Dex LDAP
connector reads back into ID-token claims**, so what a member edits is exactly
what downstream services receive. The editor reads LDAP **only when opened**;
cached OIDC claims are not treated as a current profile.
| Profile label | LDAP attribute |
| --- | --- |
| About you | `description` |
| Gamer tag (one value) | `displayName` |
| Email address (verified separately) | `mail` |
| First name / Last name | `givenName` / `sn` |
| Profile photo | `jpegPhoto` |
| City/town / State/province | `l` / `st` |
| Websites | `labeledURI` |
| Mobile numbers / Phone numbers | `mobile` / `telephoneNumber` |
| Organizations / Job titles | `o` / `title` |
| Postal addresses / Postal codes | `postalAddress` / `postalCode` |
| Street addresses / Room numbers | `street` / `roomNumber` |
| SSH public keys | `sshPublicKey` |
| Password | RFC 3062 Password Modify (never read back) |
| Profile label | LDAP attribute | Dex claim |
| --- | --- | --- |
| Username (read-only) | `uid` | `preferred_username` |
| Name (one value) | `cn` (connector `nameAttr`) | `name` |
| Email address (verified separately) | `mail` | `email` |
| Password | RFC 3062 Password Modify (never read back) | — |
The form preserves existing multi-value attributes. Enter one value per line;
separate multiline postal addresses with a blank line. Blank optional fields
are removed on save; `sn` remains required by the directory schema. JPEGs are
limited to 1 MiB and 4096 pixels per side/16 million pixels, resized to fit 512
pixels and reencoded without metadata. Existing photos are sanitized again
before private, authenticated delivery. No arbitrary URLs are fetched. Supported
SSH keys are Ed25519, RSA >=2048 bits and ECDSA NIST P-256/P-384/P-521; private
keys, authorized_keys options and malformed values are rejected.
Other directory attributes (photos, SSH keys, addresses, `displayName`, etc.)
are neither shown nor modified, so existing values are left in place. If the
connector's `nameAttr` is changed, change `profiles.TEXT_ATTRIBUTES` to match.
Saving the name replaces every `cn` value with the single entered value; Dex
already uses only the first. Account DNs must not use `cn` as their RDN.
**Directory prerequisites:**
- The service account needs read/search access to the profile attributes above,
plus `uid`, `entryUUID`, `objectClass` and `modifyTimestamp`, across the
- The service account needs read/search access to `cn`, `mail`, `uid`,
`entryUUID` and `modifyTimestamp`, across the
intended account search tree. It never needs read access to `userPassword`.
Accounts are pinned to immutable UUIDs; missing/deleted entries do not fall
back to a reused username. LDAP unavailability disables editing rather than
presenting an empty form that could erase values.
- Every write binds with the member's **current LDAP password** and checks that
entry's UUID again. Configure directory **self-write ACLs** only for these
profile attributes and self Password Modify; do not grant writes to `uid`,
entry's UUID again. Configure directory **self-write ACLs** only for `cn`,
`mail` and self Password Modify; do not grant writes to `uid`,
`entryUUID`, account DNs, POSIX IDs, group membership or administrative fields.
This supports existing Dex users outside the website registration OU without
granting the membership service unrestricted directory write permissions.
- Install the `ldapPublicKey` auxiliary schema before SSH editing. When a user
supplies keys and the class is absent, the application adds **only** the fixed
`ldapPublicKey` class in the same modify. Either pre-add that class to editable
accounts or permit precisely that self-add with value-scoped `objectClass`
ACLs. Never grant arbitrary self-write to `objectClass`. No schema/ACL changes
are automatically applied by the website or OpenLDAP role.
- OpenLDAP must support critical RFC 4528 assertion controls. Profile/email
writes assert UUID and last-modified time, and profile forms carry a signed,
one-hour revision to reject stale edits. A server rejecting the critical
@@ -462,15 +441,13 @@ automatically revoked—use each service's session controls when necessary.
All writes are CSRF-protected and rate-limited per account/IP. Passwords and
profile form bodies must never be logged by ingress/APM/LDAP debug logging.
Profile data can be shared by directory-consuming services: the page is private,
but it does not make phone/address/SSH fields private in the directory. Enforce
but it does not make other directory attributes private. Enforce
directory read ACLs and tell users what other services can access. TLS defaults
are unchanged; the existing explicit plaintext opt-in also exposes profile
passwords/data and should not be used on untrusted networks.
The supplied Kubernetes ingress accepts up to 2 MiB; Apache allows that limit
only under `/members/profile/` and retains 256 KiB for other member routes.
Mirror the limits at any additional edge proxy. Oversized or invalid uploads
must be rejected before reaching image processing.
Apache limits all member routes, including the profile, to 256 KiB request
bodies; mirror that limit at any additional edge proxy.
### Database ProgrammingError during callback