Kareadita/Kavita · warning · KavitaException
oidc-managed
oidc-managed
Error message
oidc-managed
What it means
Thrown as KavitaException with localized key 'oidc-managed' by AccountService.ChangeIdentityProvider when OIDC SyncUserSettings is enabled and an attempt is made to set a user's provider to OpenIdConnect while it is already OpenIdConnect — i.e. re-asserting OIDC on an already OIDC-managed user. Guards the sync path from no-op/loop writes.
Source
Thrown at Kavita.Services/AccountService.cs:145
throw new KavitaException(await localizationService.TranslateAsync(actingUserId, "cannot-change-identity-provider-original-user"));
}
return false;
}
// Allow changes if users aren't being synced
var oidcSettings = (await unitOfWork.SettingsRepository.GetSettingsDtoAsync(ct)).OidcConfig;
if (!oidcSettings.SyncUserSettings)
{
user.IdentityProvider = identityProvider;
await unitOfWork.CommitAsync(ct);
return false;
}
// Don't allow changes to the user if they're managed by oidc, and their identity provider isn't being changed to something else
if (user.IdentityProvider == IdentityProvider.OpenIdConnect && identityProvider == IdentityProvider.OpenIdConnect)
{
throw new KavitaException(await localizationService.TranslateAsync(actingUserId, "oidc-managed"));
}
user.IdentityProvider = identityProvider;
await unitOfWork.CommitAsync(ct);
return user.IdentityProvider == IdentityProvider.OpenIdConnect;
}
public async Task UpdateLibrariesForUser(AppUser user, IList<int> librariesIds, bool hasAdminRole, CancellationToken ct = default)
{
var allLibraries = (await unitOfWork.LibraryRepository.GetLibrariesAsync(LibraryIncludes.AppUser, ct: ct)).ToList();
var currentLibrary = allLibraries.Where(l => l.AppUsers.Contains(user)).ToList();
List<Library> libraries;
if (hasAdminRole)
{
logger.LogDebug("{UserId} is admin. Granting access to all libraries", user.Id);
libraries = allLibraries;View on GitHub (pinned to 9c3e540000)
Solutions
- Skip the call (or pass a different provider) when the user is already OIDC-managed and SyncUserSettings is on.
- Check user.IdentityProvider before calling ChangeIdentityProvider to avoid a no-op that triggers the guard.
- Turn off SyncUserSettings only if you intentionally want to bypass OIDC-managed protection.
Example fix
// before
await accountService.ChangeIdentityProvider(actingUserId, user, IdentityProvider.OpenIdConnect, ct);
// after
if (user.IdentityProvider != IdentityProvider.OpenIdConnect)
await accountService.ChangeIdentityProvider(actingUserId, user, IdentityProvider.OpenIdConnect, ct); Defensive patterns
Strategy: validation
Validate before calling
if (user.IdentityProvider == IdentityProvider.OpenIdConnect
&& identityProvider == IdentityProvider.OpenIdConnect)
return BadRequest("oidc-managed"); Type guard
static bool IsAlreadyOidcManaged(AppUser u, IdentityProvider target)
=> u.IdentityProvider == IdentityProvider.OpenIdConnect
&& target == IdentityProvider.OpenIdConnect; Try / catch
try { await accountService.ChangeIdentityProvider(actingUserId, user, provider, ct); }
catch (KavitaException ex) { return BadRequest(ex.Message); } Prevention
- Skip the call when the user is already OIDC-managed and SyncUserSettings is on.
- Check user.IdentityProvider before calling ChangeIdentityProvider.
- Have sync routines skip no-op provider reassignments.
When it happens
Trigger: With SyncUserSettings on, ChangeIdentityProvider is called for a user whose IdentityProvider is already OpenIdConnect and the requested provider is also OpenIdConnect.
Common situations: A sync routine unconditionally re-applies the OIDC provider to all OIDC users; admin UI resubmits an unchanged provider selection; repeated OIDC login callbacks re-run the change.
Related errors
- cannot-change-identity-provider-original-user
- errors.oidc.no-account
- User is not authenticated
- errors.oidc.missing-external-id
- errors.oidc.missing-email
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/cd5ea3178d74e038.
Report an issue: GitHub.