better-auth's account-linking guard requires either the IdP asserting
email_verified or the sso_provider row being domainVerified before it
will link an SSO login to an existing local user with the same email.
Entra ID never asserts email_verified (neither via its OIDC userinfo
endpoint nor via SAML attributes), and Dokploy never persisted
domainVerified at all, so linking was permanently impossible for any
Entra-backed provider (OIDC or SAML) regardless of the local user's
own emailVerified state.
Add the domainVerified column, defaulting to true — Dokploy already
scopes providers to an admin-configured domain and gates registration
behind enterprise/admin permissions, so that domain match (still
enforced by better-auth's validateEmailDomain check) is trust enough.
Fixes#5099
Fixes#5279. The Domains table (and grid) view mounts one AddDomain
instance per row for the edit action. Its 4 queries (domain.one,
application.one/compose.one, domain.canGenerateTraefikMeDomains,
compose.loadServices) ran unconditionally on mount instead of only
when the edit dialog is open, so any table interaction that causes a
re-render (typing in the host filter, sorting, toggling columns) fired
all 4 queries for every domain row again.
upsertRecord pulled every record of a zone just to find the one it was about to
write. The API can filter, so ask it to.
The match is still verified locally, because filter[source] matches substrings
rather than exact values. Verified against a live account:
filter[source]=autoconfig -> [autoconfig]
filter[source]=auto -> [autoconfig, autodiscover]
filter[source]=disco -> [autodiscover]
Letting the filter decide would therefore have upserted "auto" onto the existing
"autoconfig" record. The filter narrows what is transferred; the exact
comparison still decides. Covered by a test that fails if the first candidate is
trusted.
The apex was the other thing worth checking, since Infomaniak writes it as ".":
filter[source]=. does return the apex records, and an upsert on an existing apex
TXT returns its id rather than creating a second one.
Suggested by @narcisonunez on #5257.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A git add -A while merging canary swept in .watch/, a local scratch directory
holding monitoring scripts, screenshots and draft comment text. None of it
belongs in this PR.
#5257 merged, so canary now carries the Infomaniak provider and its
0191_cool_christian_walker. This branch's 0191_elite_ultragirl collided on the
number, and the conflicts were the usual "infomaniak and ovh added at the same
spot" across the enum, the config union, the client registry, SENSITIVE_FIELDS,
both providerLabels maps, the icons and the provider form — all resolved by
keeping both.
Regenerated as 0192_light_lake with a current timestamp rather than renaming the
file, so drizzle still applies it on a database that has already run canary's
0191.
restoreRecord had the restore POST and its zone refresh inside one catch. That
was harmless while refreshZone swallowed failures, but the previous commit made
it throw, which brought a new case into that catch: the restore succeeds and
only the publication fails.
The message then told the user the record "has been deleted" and to recreate it
by hand. It exists at OVH, just unpublished, so following that advice duplicates
it as soon as the zone is refreshed.
The two failures are now reported separately. A failed POST still means the
record is really gone and prints what to recreate. A failed refresh after a
successful restore says the record is back but not served yet, and explicitly
says not to recreate it. Either way the original replacement error is kept, so
the user still learns why the type change failed.
Reported by Greptile on #5258.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
refreshZone runs after the record write has already succeeded, so a failure
there means the change exists at the provider but is not being served yet. It
was left unguarded at all six call sites, so the caller saw a bare transport
error and would reasonably read it as "nothing was applied" — while a type
change had already deleted the old record and created its replacement.
Rolling the write back would destroy correct state over a publish failure, and
re-deleting after an uncertain network error is how records get lost. The error
now says what actually happened and how it resolves: the next successful change
to the zone publishes it, or the user can refresh the zone from the OVH manager.
Guarding the helper covers upsertRecord and deleteRecord too, not just the type
change Greptile pointed at.
Reported by Greptile on #5258.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(auth): enforce SSO on server-side sign-in endpoints
Resolves#5288.
- In better-auth hooks.before, reject /sign-in/email and /sign-in/social with FORBIDDEN when webServerSettings.enforceSSO is enabled
- Prevents direct API password authentication when SSO enforcement is enabled
* [autofix.ci] apply automated fixes
* fix(auth): block passkey authentication and email signup when SSO is enforced
- Include /sign-in/passkey, /passkey/verify-authentication, /passkey/generate-authenticate-options, and /sign-up/email in enforced SSO block
- Prevents registered passkeys from bypassing enforced SSO policies
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
- Include /sign-in/passkey, /passkey/verify-authentication, /passkey/generate-authenticate-options, and /sign-up/email in enforced SSO block
- Prevents registered passkeys from bypassing enforced SSO policies