This commit is contained in:
2026-10-29 13:28:11 -04:00
parent 7ec64194e5
commit 0bffd7d993
19 changed files with 1750 additions and 7 deletions
+97
View File
@@ -210,6 +210,103 @@ but membership no longer checks directory recreation during login. Preserve the
connector mapping for other services; migrating Dex to immutable user IDs is a
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.
| 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) |
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.
**Directory prerequisites:**
- The service account needs read/search access to the profile attributes above,
plus `uid`, `entryUUID`, `objectClass` 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`,
`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
control must fail closed; do not silently retry without it.
- Maintain directory-side unique `mail` and `uid` enforcement across all
registrars. A client-side email preflight cannot prevent concurrent claims
and may be restricted by self-read ACLs; the server uniqueness overlay is
required. Review which services use `mail` as an account/recovery identifier.
Email is changed separately: the user enters their current password, receives
a one-hour verification link at the new mailbox, then signs into the same local
account and enters their password again. GET/scanner visits never modify LDAP.
Tokens are stored only as hashes, bound to account/LDAP UUID, and consumed once.
Confirming replaces **all existing mail values** with the verified address and
updates the local contact address; it does not attach another user's billing
history. Old Dex/downstream tokens may retain the prior email until refreshed.
Password changes send current/new passwords through Password Modify as the
authenticated user, never a service-account reset or direct `userPassword`
write. **Hashing remains OpenLDAP's policy**: no MD5-Crypt hash is generated by
the application, no stored hash is exposed, and the application does not force
or silently change the server's scheme. A legacy MD5-Crypt directory can still
verify current passwords; do not introduce MD5-Crypt as a new application-side
hashing scheme. Audit/migrate weak server hashing separately. The existing
Argon2 gate for new registration remains unchanged. Password changes sign out
the current membership browser; other membership/Dex/service sessions are not
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
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.
### Database ProgrammingError during callback
`Dex callback rejected kind=ProgrammingError` in older images is a database