9p4 / jellyfin-plugin-sso

This plugin allows users to sign in through an SSO provider (such as Google, Microsoft, or your own provider). This enables one-click signin.
GNU General Public License v3.0
571 stars 27 forks source link

Jellyfin only allows specific characters in username, SSO provider can send special characters #199

Open qrkourier opened 2 months ago

qrkourier commented 2 months ago

Describe the bug Successful authN results in error while creating Jellyfin user because the username contains a pipe | character. I encountered the same issue with several OpenID Connect providers through Auth0: Google, Microsoft, Amazon, and Apple.

To Reproduce

  1. In Auth0, create an application for Jellyfin and enable at least one social connection, e.g., Google
  2. In Jellyfin, create a provider for Auth0
  3. In Jellyfin, visit the SSO start page to be redirected to OIDC provider (Auth0 app entrypoint domain)
  4. Authenticate with any OIDC provider to be redirected to the Jellyfin SSO callback, e.g., /sso/OID/redirect/auth0
  5. Note the SSO errors in Jellyfin log

Expected behavior I expected the Jellyfin user to be created with preferred_username claim

Configuration

<?xml version="1.0" encoding="utf-8"?>
<ServerConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <LogFileRetentionDays>3</LogFileRetentionDays>
  <IsStartupWizardCompleted>true</IsStartupWizardCompleted>
  <CachePath>/cache</CachePath>
  <EnableMetrics>false</EnableMetrics>
  <EnableNormalizedItemByNameIds>true</EnableNormalizedItemByNameIds>
  <IsPortAuthorized>true</IsPortAuthorized>
  <QuickConnectAvailable>true</QuickConnectAvailable>
  <EnableCaseSensitiveItemIds>true</EnableCaseSensitiveItemIds>
  <DisableLiveTvChannelUserDataName>true</DisableLiveTvChannelUserDataName>
  <MetadataPath>/config/metadata</MetadataPath>
  <MetadataNetworkPath />
  <PreferredMetadataLanguage>en</PreferredMetadataLanguage>
  <MetadataCountryCode>US</MetadataCountryCode>
  <SortReplaceCharacters>
    <string>.</string>
    <string>+</string>
    <string>%</string>
  </SortReplaceCharacters>
  <SortRemoveCharacters>
    <string>,</string>
    <string>&amp;</string>
    <string>-</string>
    <string>{</string>
    <string>}</string>
    <string>'</string>
  </SortRemoveCharacters>
  <SortRemoveWords>
    <string>the</string>
    <string>a</string>
    <string>an</string>
  </SortRemoveWords>
  <MinResumePct>5</MinResumePct>
  <MaxResumePct>90</MaxResumePct>
  <MinResumeDurationSeconds>300</MinResumeDurationSeconds>
  <MinAudiobookResume>5</MinAudiobookResume>
  <MaxAudiobookResume>5</MaxAudiobookResume>
  <InactiveSessionThreshold>0</InactiveSessionThreshold>
  <LibraryMonitorDelay>60</LibraryMonitorDelay>
  <LibraryUpdateDuration>30</LibraryUpdateDuration>
  <ImageSavingConvention>Legacy</ImageSavingConvention>
  <MetadataOptions>
    <MetadataOptions>
      <ItemType>Book</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers />
      <MetadataFetcherOrder />
      <DisabledImageFetchers />
      <ImageFetcherOrder />
    </MetadataOptions>
    <MetadataOptions>
      <ItemType>Movie</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers />
      <MetadataFetcherOrder />
      <DisabledImageFetchers />
      <ImageFetcherOrder />
    </MetadataOptions>
    <MetadataOptions>
      <ItemType>MusicVideo</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers>
        <string>The Open Movie Database</string>
      </DisabledMetadataFetchers>
      <MetadataFetcherOrder />
      <DisabledImageFetchers>
        <string>The Open Movie Database</string>
      </DisabledImageFetchers>
      <ImageFetcherOrder />
    </MetadataOptions>
    <MetadataOptions>
      <ItemType>Series</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers />
      <MetadataFetcherOrder />
      <DisabledImageFetchers />
      <ImageFetcherOrder />
    </MetadataOptions>
    <MetadataOptions>
      <ItemType>MusicAlbum</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers>
        <string>TheAudioDB</string>
      </DisabledMetadataFetchers>
      <MetadataFetcherOrder />
      <DisabledImageFetchers />
      <ImageFetcherOrder />
    </MetadataOptions>
    <MetadataOptions>
      <ItemType>MusicArtist</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers>
        <string>TheAudioDB</string>
      </DisabledMetadataFetchers>
      <MetadataFetcherOrder />
      <DisabledImageFetchers />
      <ImageFetcherOrder />
    </MetadataOptions>
    <MetadataOptions>
      <ItemType>BoxSet</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers />
      <MetadataFetcherOrder />
      <DisabledImageFetchers />
      <ImageFetcherOrder />
    </MetadataOptions>
    <MetadataOptions>
      <ItemType>Season</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers />
      <MetadataFetcherOrder />
      <DisabledImageFetchers />
      <ImageFetcherOrder />
    </MetadataOptions>
    <MetadataOptions>
      <ItemType>Episode</ItemType>
      <DisabledMetadataSavers />
      <LocalMetadataReaderOrder />
      <DisabledMetadataFetchers />
      <MetadataFetcherOrder />
      <DisabledImageFetchers />
      <ImageFetcherOrder />
    </MetadataOptions>
  </MetadataOptions>
  <SkipDeserializationForBasicTypes>true</SkipDeserializationForBasicTypes>
  <ServerName>Autumnvale</ServerName>
  <UICulture>en-US</UICulture>
  <SaveMetadataHidden>false</SaveMetadataHidden>
  <ContentTypes />
  <RemoteClientBitrateLimit>0</RemoteClientBitrateLimit>
  <EnableFolderView>false</EnableFolderView>
  <EnableGroupingIntoCollections>false</EnableGroupingIntoCollections>
  <DisplaySpecialsWithinSeasons>true</DisplaySpecialsWithinSeasons>
  <CodecsUsed />
  <PluginRepositories>
    <RepositoryInfo>
      <Name>Jellyfin Stable</Name>
      <Url>https://repo.jellyfin.org/files/plugin/manifest.json</Url>
      <Enabled>true</Enabled>
    </RepositoryInfo>
    <RepositoryInfo>
      <Name>jellyfin-plugin-sso</Name>
      <Url>https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json</Url>
      <Enabled>true</Enabled>
    </RepositoryInfo>
  </PluginRepositories>
  <EnableExternalContentInSuggestions>true</EnableExternalContentInSuggestions>
  <ImageExtractionTimeoutMs>0</ImageExtractionTimeoutMs>
  <PathSubstitutions />
  <EnableSlowResponseWarning>true</EnableSlowResponseWarning>
  <SlowResponseThresholdMs>500</SlowResponseThresholdMs>
  <CorsHosts>
    <string>*</string>
  </CorsHosts>
  <ActivityLogRetentionDays>30</ActivityLogRetentionDays>
  <LibraryScanFanoutConcurrency>0</LibraryScanFanoutConcurrency>
  <LibraryMetadataRefreshConcurrency>0</LibraryMetadataRefreshConcurrency>
  <RemoveOldPlugins>false</RemoveOldPlugins>
  <AllowClientLogUpload>true</AllowClientLogUpload>
  <DummyChapterDuration>0</DummyChapterDuration>
  <ChapterImageResolution>MatchSource</ChapterImageResolution>
  <ParallelImageEncodingLimit>0</ParallelImageEncodingLimit>
  <CastReceiverApplications>
    <CastReceiverApplication>
      <Id>F007D354</Id>
      <Name>Stable</Name>
    </CastReceiverApplication>
    <CastReceiverApplication>
      <Id>6F511C87</Id>
      <Name>Unstable</Name>
    </CastReceiverApplication>
  </CastReceiverApplications>
  <TrickplayOptions>
    <EnableHwAcceleration>false</EnableHwAcceleration>
    <EnableHwEncoding>false</EnableHwEncoding>
    <ScanBehavior>NonBlocking</ScanBehavior>
    <ProcessPriority>BelowNormal</ProcessPriority>
    <Interval>10000</Interval>
    <WidthResolutions>
      <int>320</int>
    </WidthResolutions>
    <TileWidth>10</TileWidth>
    <TileHeight>10</TileHeight>
    <Qscale>4</Qscale>
    <JpegQuality>90</JpegQuality>
    <ProcessThreads>1</ProcessThreads>
  </TrickplayOptions>
</ServerConfiguration>

Versions (please complete the following information):

Additional context

jellyfin  | [19:32:48] [INF] [52] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO user google-oauth2|{redacted numeric google account id} doesn't exist, creating...
jellyfin  | [19:32:48] [ERR] [52] Jellyfin.Api.Middleware.ExceptionMiddleware: Error processing request. URL POST /sso/OID/Auth/auth0.
jellyfin  | System.ArgumentException: Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) (Parameter 'name')
jellyfin  |    at Jellyfin.Server.Implementations.Users.UserManager.ThrowIfInvalidUsername(String name)
jellyfin  |    at Jellyfin.Server.Implementations.Users.UserManager.CreateUserAsync(String name)
jellyfin  |    at Jellyfin.Plugin.SSO_Auth.Api.SSOController.CreateCanonicalLinkAndUserIfNotExist(String mode, String provider, String canonicalName)
jellyfin  |    at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OidAuth(String provider, AuthResponse response)
9p4 commented 1 month ago

I guess I'll have to add a stripping option in the username generation, but that may cause some security issues with similar but different usernames. Maybe the stripping will remain an opt-in feature.

In the meantime, could you set the username claim field to be "sub" or something?

qrkourier commented 1 month ago

Possibly so! I'm learning how to OpenID Connect and gaining familiarity with claims. Tokens obtained via Auth0 for Google, Microsoft, Amazon, and Apple do have an email claim, and it's the sub claim that contains the pipe | character. It might be sufficient for (most) IdP to configure the Jellyfin SSO plugin to use email. Perhaps I overlooked a step or option in the configuration. I understand things slightly better now than at the time I raised the bug report.

Facebook requires progressive profiling, at least in certain cases, it seems. When I authenticate to FB via Auth0 I never get an email claim in the token, only sub, and yet others have been unable to reproduce this, saying they are able to get an email claim from FB. :shrug: I reasoned it could be because I had restricted email sharing in my FB privacy settings, but I can reproduce it with those relaxed and with a separate test account without any restrictions. It remains a mystery. Here's the Immich issue where I was investigating and documenting the FB via Auth0 issue.

Still, getting this working with Auth0 for Google, Microsoft, Amazon, and Apple would be plenty of login options for my case, so I'm not too hung up on getting Facebook working.

9p4 commented 2 weeks ago

See https://github.com/9p4/jellyfin-plugin-sso/issues/204#issuecomment-2296832132 for more context