1.0.6
Added
- Change email (WorkOS-verified, conflict-guarded) (#22) — self-service and admin-triggered email-change flow. Self-service
[workos:change-email]shortcode, a "Change email" row action under the WorkOS column onwp-admin/users.php, and a "Change Email" panel on the user-edit screen. The new address is confirmed via hashed (HMAC-SHA256 +wp_salt('auth')) single-use, expiry-bounded tokens stored as_workos_pending_email_changeuser_meta and validated withhash_equals(WorkOS'semail_verificationendpoints can't verify a pending address); the old address simultaneously gets a one-click cancel link. Three new REST endpoints (initiate / confirm / cancel) are capability-gated, per-IP + per-user rate-limited, and enumeration-safe. Configurable conflict policy (blockdefault,allow_orphan,merge_request) prevents silently overwriting another local WP user. Commits to WorkOS first (update_user) then mirrors into WordPress, guarded by a 60-second in-progress transient that short-circuits the webhook fan-back. Adds 8 filters, 5 actions, 7 activity-log event types, and 40 WPUnit tests. Seedocs/change-email.md. - Per-form magic-code registration toggles (CONS-350) (#25) — two independent per-environment checkboxes on the WorkOS settings page gate whether an unknown email signing in with a magic code provisions a new account. Email Code Registration controls the default sign-in form (
/login/); Legacy Email Code Registration controls the legacy form (/login/legacy/, slug filterable viaworkos_legacy_profile_slug). When a form's toggle is off,POST /auth/magic/sendskips the WorkOS call for unknown addresses and still returns200 ok: true, andPOST /auth/magic/verifyearly-returns a generic400 workos_authkit_invalid_codeinstead of proceeding toLoginCompleter/UserSync— closing the account-enumeration leak wheresendpreviously returned404 workos_authkit_no_account. Both options (allow_magic_code_registration,allow_legacy_magic_code_registration) default totrue, preserving historical behavior.
Fixed
- Username generation could exhaust memory and block provisioning (CONS-513) (#27) — when a popular email local part (e.g.
info@) already had a deepinfo_1,info_2, … collision chain,generate_username()probed it oneusername_exists()call at a time, hydrating aWP_Userinto the object cache per probe until the request OOM'd — so those users could log in via WorkOS but never got a WordPress account. Collisions are now resolved with a suffix derived fromsha256( email )(e.g.info_48f25), which is unique by construction: at most 2 lookups regardless of chain depth, and the same email always derives the same username, so crash retries and replayed webhooks converge instead of minting new names. Existing accounts keep their current usernames. - WorkOS → Users settings would not save — the "Sync Roles to WorkOS" button rendered its own
<form>inside the Settings API<form action="options.php">. Browsers don't allow nested forms: the inner</form>closed the outer settings form early, leaving the "Save Settings" submit button outside the form, so saving did nothing. The sync form is now deferred toadmin_footer(body level, like the Create Organization modal) and its button is wired back via the HTML5form="workos-role-sync-form"attribute, so the settings form stays intact and saves normally.
Full Changelog: 1.0.5...1.0.6