feat(auth): OIDC link by preferred_username (opt-in)
Adds OIDC_LINK_BY_USERNAME as a last-resort linking step after (issuer, sub) and email both miss. Matches IdP preferred_username against users.username. Why: local accounts created before OIDC never collected an email (no UI for it), so the email fallback cannot relink them. A new SSO login therefore falls into JIT and creates username-1. On a single-tenant homelab where the IdP owns the namespace, matching by username is safe and makes first-time SSO transparent for pre-existing users. Gated behind a flag so multi-tenant deployments keep the stricter default.
This commit is contained in:
@@ -130,6 +130,13 @@ class Settings(BaseSettings):
|
||||
# promotes the user to role=admin; otherwise role=user. Role is
|
||||
# refreshed on every sign-in so removals demote automatically.
|
||||
oidc_admin_groups: str = Field(default="", env="OIDC_ADMIN_GROUPS")
|
||||
# Last-resort link step: if (issuer, sub) AND email fallback both
|
||||
# miss, try matching the IdP's `preferred_username` claim against
|
||||
# `users.username`. Safe in single-tenant setups where the IdP is
|
||||
# the source of truth for usernames (homelab, family instance).
|
||||
# Leave off in multi-tenant — a name collision would hand someone
|
||||
# else's account to a new SSO user.
|
||||
oidc_link_by_username: bool = Field(default=False, env="OIDC_LINK_BY_USERNAME")
|
||||
# Starlette session cookie secret — only used to hold PKCE/state
|
||||
# during the brief OIDC round-trip. Falls back to secret_key when
|
||||
# unset.
|
||||
|
||||
Reference in New Issue
Block a user