feat: sync the profile picture from the OIDC provider
All checks were successful
Build Plugin / build (push) Successful in 1m1s

Pocket ID exposes an avatar through the standard picture claim, pointing at
/api/users/{id}/profile-picture.png. On login the plugin now fetches that URL
and writes it to the Jellyfin user's profile image, so SSO users get their
avatar without setting one by hand.

The picture is fetched anonymously. The access token is deliberately not sent,
because the address ultimately originates from a token payload and must not be
treated as a trusted destination for credentials. Non-http(s) URLs, non-image
content types and anything over 5 MB are rejected, and an identical image is
not rewritten so the image cache is left alone. Any failure is logged and
swallowed: a broken avatar must never block a login.

Controlled by the new "Sync the profile picture" setting, on by default, with
the claim name configurable for providers that do not use "picture".

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-29 15:41:51 +02:00
parent 55c94f7d84
commit fda59741b5
3 changed files with 165 additions and 3 deletions

View File

@@ -41,6 +41,11 @@
<div class="fieldDescription">Claim containing groups/roles. Default: groups.</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="PictureClaim" type="text" label="Picture claim" />
<div class="fieldDescription">Claim holding a URL to the user's avatar. Default: picture.</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="AllowedRoles" type="text" label="Allowed roles" />
<div class="fieldDescription">Comma separated. Only users with one of these roles may log in. Empty = everyone.</div>
@@ -58,6 +63,13 @@
</label>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input is="emby-checkbox" id="SyncProfilePicture" type="checkbox" />
<span>Sync the profile picture from the provider on every login (overwrites a picture set in Jellyfin)</span>
</label>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input is="emby-checkbox" id="CreateUsersIfMissing" type="checkbox" />
@@ -96,8 +108,8 @@
<script type="text/javascript">
(function () {
var pluginId = '96badb44-9940-4ff0-befc-5f987159152c';
var textFields = ['OidIssuer', 'OidClientId', 'OidClientSecret', 'OidScopes', 'UsernameClaim', 'RoleClaim', 'AllowedRoles', 'AdminRoles'];
var boolFields = ['RevokeAdminWithoutRole', 'CreateUsersIfMissing', 'SetRandomPasswordOnCreate', 'DisableEndpointValidation'];
var textFields = ['OidIssuer', 'OidClientId', 'OidClientSecret', 'OidScopes', 'UsernameClaim', 'RoleClaim', 'PictureClaim', 'AllowedRoles', 'AdminRoles'];
var boolFields = ['RevokeAdminWithoutRole', 'SyncProfilePicture', 'CreateUsersIfMissing', 'SetRandomPasswordOnCreate', 'DisableEndpointValidation'];
document.querySelector('#OidcAuthConfigPage').addEventListener('pageshow', function () {
Dashboard.showLoadingMsg();