Compare commits

...
52 Commits
Author SHA1 Message Date
Ersei Saggi fc58b8e80f Update upload action 2025-10-20 22:43:33 -04:00
Ersei Saggi da21ebffa3 Specify that pushed authorization may need to be disabled for Authelia 2025-10-20 12:40:02 -04:00
Ersei Saggi 0d18ebae03 Update config UI links
https://github.com/9p4/jellyfin-plugin-sso/issues/285
2025-10-20 12:30:55 -04:00
Ersei Saggi 77e2a229f2 Don't use rc libs for Jellyfin 10.11 2025-10-20 12:30:15 -04:00
Ersei Saggi 71bb9a2f14 Jellyfin 10.11.0-rc5 2025-08-14 12:43:04 -04:00
Ersei Saggi 8baa922eea Manifest should be for 4.0.0.0 / JF10.11 2025-08-12 14:57:22 -04:00
Ersei Saggi 0738ad85f7 Fix warnings 2025-08-12 14:22:31 -04:00
Ersei Saggi 8d049705c4 Update workflows to use dotnet9 2025-08-12 13:51:13 -04:00
Ersei Saggi 3459baffdf More Jellyfin 10.11 fixes 2025-08-12 13:49:09 -04:00
9p4 de6cd5d5c2 Merge pull request #265 from babbitt/punycode
Use punycode URL for redirect page - OIDC
2025-08-11 14:13:48 -04:00
9p4 299d3436ec Merge pull request #269 from kernelb00t/patch-1
Match the styling of the redirection page with Jellyfin colors and font
2025-08-11 14:12:38 -04:00
9p4 5808f34064 Merge pull request #280 from hendrik1120/patch-1
remove deprecated redirect uri from readme
2025-08-11 14:12:22 -04:00
Ersei Saggi 5ed4c8bbcc Update to net9.0 for JF 10.11 2025-08-11 14:11:52 -04:00
Hendrik Sievers f78a0f3108 remove deprecated redirect uri 2025-07-18 11:04:29 +02:00
Leo THIVILLON aa0e361265 Match the styling of the redirection page with Jellyfin colors and font
- Added a style tag
- Set color to Jellyfin's dark background and light text
- Set font to Noto Sans
2025-05-09 23:26:51 +02:00
Ersei Saggi efc997c39e Add security policy
Closes #266
2025-05-06 13:46:32 -04:00
Joe Babbitt caab66b653 use punycode URL for redirect page
Redirect page content gets encoded as LATIN1, non LATIN characters in URLs get malformed.
2025-04-29 17:22:38 -04:00
9p4 79ac628c4c Merge pull request #251 from FeikoJoosten/main
Prevent force updating username
2025-04-07 02:30:43 -04:00
Ersei Saggi fe3ca6337d We support >=10.8 2025-04-07 02:16:48 -04:00
Feiko Joosten f761dea356 Removed trailing whitespace 2025-04-05 18:59:36 +02:00
Feiko Joosten 55f58ecedd Prevent force updating username
Fixed an issue where you cannot modify the username as the code tried to find users by name instead of its id.
2025-04-04 01:58:01 +02:00
Ersei Saggi 556feba864 Update credits in README to point to Duende library 2025-03-26 13:55:40 -04:00
Ersei Saggi 1f08ccd213 Prepare for 3.5.3.0 2025-03-26 13:51:26 -04:00
Ersei Saggi f1f57723c3 Make prettier happy again 2025-03-26 13:14:36 -04:00
Borja Domínguez 216908acd5 Fix artifact names 2025-03-26 16:40:18 +01:00
9p4 9365ca2a33 Merge pull request #246 from bdovaz/allow-port-override
Allow port override
2025-03-26 11:34:08 -04:00
Borja Domínguez ae0ee62f21 Merge branch 'main' into allow-port-override 2025-03-26 16:32:12 +01:00
Ersei Saggi 728a994728 Make prettier happy 2025-03-26 11:20:29 -04:00
9p4 9c0597a5bc Merge pull request #202 from Estyms/main
Avatar via OIDC Provider
2025-03-26 11:18:24 -04:00
9p4 fb9335d908 Merge pull request #247 from bdovaz/fix-nightly-build
Update oddstr13/jellyfin-plugin-repository-manager
2025-03-26 11:15:43 -04:00
9p4 cbbafc976b Merge pull request #244 from bdovaz/replace-oidc-dependency
Allow to disable pushed authorization
2025-03-26 11:11:31 -04:00
Borja Domínguez Vázquez f7bde6f1a0 Change to commit hash 2025-03-24 21:46:49 +01:00
Borja Domínguez Vázquez d7351ef596 Change EOL 2025-03-24 21:44:34 +01:00
Borja Domínguez Vázquez 07315a124c Change EOL 2025-03-24 21:43:44 +01:00
Borja Domínguez Vázquez 9e96bd5218 Update oddstr13/jellyfin-plugin-repository-manager 2025-03-24 20:26:18 +01:00
Borja Domínguez Vázquez 6f77735248 Allow to disable pushed authorization 2025-03-24 20:12:48 +01:00
Borja Domínguez Vázquez 73b6c17518 Allow port override 2025-03-24 20:06:28 +01:00
Borja Domínguez Vázquez 86dcd4c579 Better error handling 2025-03-24 20:04:44 +01:00
Borja Domínguez Vázquez 553c88873e Replace deprecated oidc dependency 2025-03-23 15:00:26 +01:00
9p4 cc86549c2e Merge pull request #239 from andreblanke/main
Support DisableHttps/DoNotValidateIssuerName in SSOController.OidChallenge
2025-03-16 23:10:35 -04:00
Andre Blanke a2064c1f17 Support DisableHttps/DoNotValidateIssuerName in OidChallenge 2025-03-03 16:54:28 +01:00
Evann Regnault 2cf4230bd6 Merge branch 'main' into main 2025-02-28 04:28:39 +01:00
Ersei Saggi 91c57b18f5 Allow for Fsharp code to be included 2024-11-05 15:17:02 -05:00
Evann Regnault feb56ebdd6 [AvatarUrlFormat] Better Handling
- Mimetype is now inferred by the Response header.
- Extension is now derived from the Mimetype
2024-08-04 13:41:37 +02:00
Evann Regnault 2d6e2fc938 Text and JSON fields can now be emptied in configuration 2024-08-04 03:12:50 +02:00
Evann Regnault ef085e9fa3 Updated README.md
- Avatar Url Format Explanation
2024-08-04 03:12:15 +02:00
Evann Regnault 95d2c36e2c Added AvatarFormatUrl to programatically set avatar on SSO Connect with OIDC 2024-08-04 03:00:06 +02:00
Ersei Saggi b8e56cefab Update infra files 2024-06-04 10:26:15 -04:00
Ersei Saggi e65f358c79 Update webresponse to use updated JS code 2024-06-04 10:25:58 -04:00
Ersei Saggi cd35ef45ae Formatting 2024-06-04 09:56:19 -04:00
9p4 b5068f53a6 Merge pull request #193 from CFenner/patch-1
fix: consistently use OpenID instead of OID on the config page
2024-06-04 09:54:04 -04:00
Christopher Fenner 6f584597f6 consistently use OpenID instead of OID on the config page 2024-06-04 12:03:11 +02:00
22 changed files with 406 additions and 169 deletions
+4 -4
View File
@@ -3,12 +3,12 @@ on:
inputs:
dotnet-version:
required: false
default: "8.0.x"
default: "9.0.x"
description: "The .NET version to setup for the build"
type: string
dotnet-target:
required: false
default: "net8.0"
default: "net9.0"
description: "The .NET target to set for JPRM"
type: string
@@ -25,13 +25,13 @@ jobs:
dotnet-version: "${{ inputs.dotnet-version }}"
- name: Build Jellyfin Plugin
uses: oddstr13/jellyfin-plugin-repository-manager@b9e92867a6aa279d611a5ea80cf61f6358838c39
uses: oddstr13/jellyfin-plugin-repository-manager@9497a0a499416cc572ed2e07a391d9f943a37b4d # v1.1.1
id: jprm
with:
dotnet-target: "${{ inputs.dotnet-target }}"
- name: Upload Artifact
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # tag=v4.6.2
with:
name: build-artifact
retention-days: 30
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
+3 -3
View File
@@ -15,7 +15,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build Dotnet
@@ -30,12 +30,12 @@ jobs:
updateFile: true
- name: "JPRM: Build"
id: jrpm
uses: oddstr13/jellyfin-plugin-repository-manager@b9e92867a6aa279d611a5ea80cf61f6358838c39
uses: oddstr13/jellyfin-plugin-repository-manager@9497a0a499416cc572ed2e07a391d9f943a37b4d # v1.1.1
with:
version: "0.0.0.9000"
verbosity: debug
path: .
dotnet-target: "net8.0"
dotnet-target: "net9.0"
output: _dist
- name: Prepare GitHub Release assets
run: |-
+2 -2
View File
@@ -10,8 +10,8 @@ jobs:
build:
uses: ./.github/workflows/build.yml
with:
dotnet-version: "8.0.*"
dotnet-target: "net8.0"
dotnet-version: "9.0.*"
dotnet-target: "net9.0"
upload:
runs-on: ubuntu-latest
needs:
+3 -4
View File
@@ -34,7 +34,7 @@ This is 100% alpha software! PRs are welcome to improve the code.
~~There is NO admin configuration! You must use the API to configure the program!~~ Added by [strazto](https://github.com/strazto) in PR [#18](https://github.com/9p4/jellyfin-plugin-sso/pull/18) and [#27](https://github.com/9p4/jellyfin-plugin-sso/pull/27).
**[This is for Jellyfin 10.8](https://github.com/9p4/jellyfin-plugin-sso/issues/3) and only on the Web UI and clients supporting [Quick Connect](https://jellyfin.org/docs/general/server/quick-connect)**
**[This is for Jellyfin >=10.8](https://github.com/9p4/jellyfin-plugin-sso/issues/3) and only on the Web UI or clients supporting [Quick Connect](https://jellyfin.org/docs/general/server/quick-connect)**
**This README reflects the branch it is currently on! Switch tags to view version-specific documentation!**
@@ -91,8 +91,6 @@ The nightly build may have new features unavailable in other builds, but **be wa
## Examples
**Note that you should add both "/r/" and "/redirect/" paths to your SSO provider's configuration!**
### Creating A Login Button On The Main Page
In the Jellyfin administration UI, under "General", there is a "Branding" section. In that section, add the following code in the "Login disclaimer" block (replacing `PROVIDER_NAME` and the domain):
@@ -239,6 +237,7 @@ These all require authorization. Append an API key to the end of the request: `c
- Leave empty to only request the default scopes.
- `defaultProvider`: string. The set provider then gets assigned to the user after they have logged in. If it is not set, nothing is changed. With this, a user can login with SSO but is still able to log in via other providers later. See the `Unregister` endpoint.
- `defaultUsernameClaim`: string. The provider will use the claim to create the users' usernames. If not set, it fallbacks to `preferred_username`.
- `avatarUrlFormat`: string. The URL format for the users avatars. OIDC claims can be used by using the `@{claim_type}` syntax. If not set, the avatars won't change.
- `disableHttps`: boolean. Determines whether the OpenID discovery endpoint requires HTTPS.
- `doNotValidateEndpoints`: boolean. Determines whether the OpenID discovery process will validate endpoints. This may be required for Google.
- `doNotValidateIssuerName`: boolean. Determines whether the OpenID discovery process will validate the OpenID issuer name.
@@ -310,7 +309,7 @@ Much thanks to the [Jellyfin LDAP plugin](https://github.com/jellyfin/jellyfin-p
I use the [AspNet SAML](https://github.com/jitbit/AspNetSaml/) library for the SAML side of things (patched to work with Base64 on non-Windows machines).
I use the [IdentityModel OIDC Client](https://github.com/IdentityModel/IdentityModel.OidcClient/) library for the OpenID side of things.
I use the [Duende IdentityModel OIDC Client](https://github.com/DuendeSoftware/foss) library for the OpenID side of things.
Thanks to these projects, without which I would have been pulling my hair out implementing these protocols from scratch.
+3
View File
@@ -0,0 +1,3 @@
Please email all security vulnerabilities and issues found to the email "contact at ersei dot net". If using LLMs/AI to find the issues, first verify the issue exists manually. Please do not publicly disclose security vulnerabilities until after a stable release for the fix has been released for 30 days.
The latest released version is the only supported version.
+2 -2
View File
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSO-Auth", "SSO-Auth\SSO-Auth.csproj", "{C30A5CFB-B27E-4E83-9E96-1E0362B36748}"
EndProject
+2 -1
View File
@@ -9,7 +9,8 @@
using System;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Data;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http;
+136 -18
View File
@@ -1,28 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using IdentityModel.OidcClient;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Duende.IdentityModel.OidcClient;
using Jellyfin.Data;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Plugin.SSO_Auth.Config;
using Jellyfin.Plugin.SSO_Auth.Helpers;
using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Cryptography;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SSO_Auth.Lib;
namespace Jellyfin.Plugin.SSO_Auth.Api;
@@ -38,6 +43,8 @@ public class SSOController : ControllerBase
private readonly IAuthorizationContext _authContext;
private readonly ILogger<SSOController> _logger;
private readonly ICryptoProvider _cryptoProvider;
private readonly IProviderManager _providerManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private static readonly IDictionary<string, TimedAuthorizeState> StateManager = new Dictionary<string, TimedAuthorizeState>();
/// <summary>
@@ -48,13 +55,24 @@ public class SSOController : ControllerBase
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="cryptoProvider">Instance of the <see cref="ICryptoProvider"/> interface.</param>
public SSOController(ILogger<SSOController> logger, ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, ICryptoProvider cryptoProvider)
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
public SSOController(
ILogger<SSOController> logger,
ISessionManager sessionManager,
IUserManager userManager,
IAuthorizationContext authContext,
ICryptoProvider cryptoProvider,
IProviderManager providerManager,
IServerConfigurationManager serverConfigurationManager)
{
_sessionManager = sessionManager;
_userManager = userManager;
_authContext = authContext;
_cryptoProvider = cryptoProvider;
_logger = logger;
_providerManager = providerManager;
_serverConfigurationManager = serverConfigurationManager;
_logger.LogInformation("SSO Controller initialized");
}
@@ -89,8 +107,9 @@ public class SSOController : ControllerBase
Authority = config.OidEndpoint?.Trim(),
ClientId = config.OidClientId?.Trim(),
ClientSecret = config.OidSecret?.Trim(),
RedirectUri = GetRequestBase(config.SchemeOverride) + $"/sso/OID/{(Request.Path.Value.Contains("/start/", StringComparison.InvariantCultureIgnoreCase) ? "redirect" : "r")}/" + provider,
RedirectUri = GetRequestBase(config.SchemeOverride, config.PortOverride) + $"/sso/OID/{(Request.Path.Value.Contains("/start/", StringComparison.InvariantCultureIgnoreCase) ? "redirect" : "r")}/" + provider,
Scope = string.Join(" ", scopes.Prepend("openid profile")),
DisablePushedAuthorization = config.DisablePushedAuthorization,
};
var oidEndpointUri = new Uri(config.OidEndpoint?.Trim());
options.Policy.Discovery.AdditionalEndpointBaseAddresses.Add(oidEndpointUri.GetLeftPart(UriPartial.Authority));
@@ -100,9 +119,10 @@ public class SSOController : ControllerBase
var oidcClient = new OidcClient(options);
var currentState = StateManager[state].State;
var result = await oidcClient.ProcessResponseAsync(Request.QueryString.Value, currentState).ConfigureAwait(false);
if (result.IsError)
{
return ReturnError(StatusCodes.Status400BadRequest, result.Error + " Try logging in again.");
return ReturnError(StatusCodes.Status400BadRequest, $"Error logging in: {result.Error} - {result.ErrorDescription}");
}
if (!config.EnableFolderRoles && config.EnabledFolders != null)
@@ -117,6 +137,13 @@ public class SSOController : ControllerBase
StateManager[state].EnableLiveTv = config.EnableLiveTv;
StateManager[state].EnableLiveTvManagement = config.EnableLiveTvManagement;
if (config.AvatarUrlFormat is not null)
{
StateManager[state].AvatarURL = result.User.Claims.Aggregate(
config.AvatarUrlFormat,
(s, claim) => s.Contains($"@{{{claim.Type}}}") ? s.Replace($"@{{{claim.Type}}}", claim.Value) : s);
}
foreach (var claim in result.User.Claims)
{
if (claim.Type == (config.DefaultUsernameClaim?.Trim() ?? "preferred_username"))
@@ -250,7 +277,7 @@ public class SSOController : ControllerBase
if (StateManager[state].Valid)
{
_logger.LogInformation($"Is request linking: {isLinking}");
return Content(WebResponse.Generator(data: state, provider: provider, baseUrl: GetRequestBase(config.SchemeOverride), mode: "OID", isLinking: isLinking), MediaTypeNames.Text.Html);
return Content(WebResponse.Generator(data: state, provider: provider, baseUrl: GetRequestBase(config.SchemeOverride, config.PortOverride), mode: "OID", isLinking: isLinking), MediaTypeNames.Text.Html);
}
else
{
@@ -298,7 +325,7 @@ public class SSOController : ControllerBase
config.NewPath = newPath;
}
string redirectUri = GetRequestBase(config.SchemeOverride) + $"/sso/OID/{(newPath ? "redirect" : "r")}/" + provider;
string redirectUri = GetRequestBase(config.SchemeOverride, config.PortOverride) + $"/sso/OID/{(newPath ? "redirect" : "r")}/" + provider;
var options = new OidcClientOptions
{
@@ -307,12 +334,21 @@ public class SSOController : ControllerBase
ClientSecret = config.OidSecret?.Trim(),
RedirectUri = redirectUri,
Scope = string.Join(" ", config.OidScopes.Prepend("openid profile")),
DisablePushedAuthorization = config.DisablePushedAuthorization,
};
var oidEndpointUri = new Uri(config.OidEndpoint?.Trim());
options.Policy.Discovery.AdditionalEndpointBaseAddresses.Add(oidEndpointUri.GetLeftPart(UriPartial.Authority));
options.Policy.Discovery.ValidateEndpoints = !config.DoNotValidateEndpoints; // For Google and other providers with different endpoints
options.Policy.Discovery.RequireHttps = !config.DisableHttps;
options.Policy.Discovery.ValidateIssuerName = !config.DoNotValidateIssuerName;
var oidcClient = new OidcClient(options);
var state = await oidcClient.PrepareLoginAsync().ConfigureAwait(false);
if (state.IsError)
{
return ReturnError(StatusCodes.Status400BadRequest, $"Error preparing login: {state.Error} - {state.ErrorDescription}");
}
StateManager.Add(state.State, new TimedAuthorizeState(state, DateTime.Now));
// Track whether this is a linking request or not.
@@ -421,7 +457,7 @@ public class SSOController : ControllerBase
{
Guid userId = await CreateCanonicalLinkAndUserIfNotExist("oid", provider, kvp.Value.Username);
var authenticationResult = await Authenticate(userId, kvp.Value.Admin, config.EnableAuthorization, config.EnableAllFolders, kvp.Value.Folders.ToArray(), kvp.Value.EnableLiveTv, kvp.Value.EnableLiveTvManagement, response, config.DefaultProvider?.Trim())
var authenticationResult = await Authenticate(userId, kvp.Value.Admin, config.EnableAuthorization, config.EnableAllFolders, kvp.Value.Folders.ToArray(), kvp.Value.EnableLiveTv, kvp.Value.EnableLiveTvManagement, response, config.DefaultProvider?.Trim(), kvp.Value.AvatarURL)
.ConfigureAwait(false);
return Ok(authenticationResult);
}
@@ -489,7 +525,7 @@ public class SSOController : ControllerBase
WebResponse.Generator(
data: Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(samlResponse.Xml)),
provider: provider,
baseUrl: GetRequestBase(config.SchemeOverride),
baseUrl: GetRequestBase(config.SchemeOverride, config.PortOverride),
mode: "SAML",
isLinking: isLinking),
MediaTypeNames.Text.Html);
@@ -535,7 +571,7 @@ public class SSOController : ControllerBase
config.NewPath = newPath;
}
string redirectUri = GetRequestBase(config.SchemeOverride) + $"/sso/SAML/{(newPath ? "post" : "p")}/" + provider;
string redirectUri = GetRequestBase(config.SchemeOverride, config.PortOverride) + $"/sso/SAML/{(newPath ? "post" : "p")}/" + provider;
string relayState = null;
if (isLinking)
{
@@ -686,7 +722,7 @@ public class SSOController : ControllerBase
Guid userId = await CreateCanonicalLinkAndUserIfNotExist("saml", provider, samlResponse.GetNameID());
var authenticationResult = await Authenticate(userId, isAdmin, config.EnableAuthorization, config.EnableAllFolders, folders.ToArray(), liveTv, liveTvManagement, response, config.DefaultProvider?.Trim())
var authenticationResult = await Authenticate(userId, isAdmin, config.EnableAuthorization, config.EnableAllFolders, folders.ToArray(), liveTv, liveTvManagement, response, config.DefaultProvider?.Trim(), null)
.ConfigureAwait(false);
return Ok(authenticationResult);
}
@@ -737,7 +773,28 @@ public class SSOController : ControllerBase
private async Task<Guid> CreateCanonicalLinkAndUserIfNotExist(string mode, string provider, string canonicalName)
{
User user = null;
user = _userManager.GetUserByName(canonicalName);
// First try to get the user by its id in case it was already registered before
Guid userId = Guid.Empty;
try
{
userId = GetCanonicalLink(mode, provider, canonicalName);
}
catch (KeyNotFoundException)
{
userId = Guid.Empty;
}
// No userId found? Let's try and find the user by name instead
if (userId == Guid.Empty)
{
user = _userManager.GetUserByName(canonicalName);
}
else
{
user = _userManager.GetUserById(userId);
}
if (user == null)
{
_logger.LogInformation($"SSO user {canonicalName} doesn't exist, creating...");
@@ -752,7 +809,7 @@ public class SSOController : ControllerBase
UpdateCanonicalLinkConfig(links, mode, provider);
}
Guid userId = Guid.Empty;
userId = Guid.Empty;
try
{
userId = GetCanonicalLink(mode, provider, canonicalName);
@@ -1020,7 +1077,8 @@ public class SSOController : ControllerBase
/// <param name="enableLiveTvAdmin">Determines whether live TV can be managed by this user.</param>
/// <param name="authResponse">The client information to authenticate the user with.</param>
/// <param name="defaultProvider">The default provider of the user to be set after logging in.</param>
private async Task<AuthenticationResult> Authenticate(Guid userId, bool isAdmin, bool enableAuthorization, bool enableAllFolders, string[] enabledFolders, bool enableLiveTv, bool enableLiveTvAdmin, AuthResponse authResponse, string defaultProvider)
/// <param name="avatarUrl">The new avatar url for the user.</param>
private async Task<AuthenticationResult> Authenticate(Guid userId, bool isAdmin, bool enableAuthorization, bool enableAllFolders, string[] enabledFolders, bool enableLiveTv, bool enableLiveTvAdmin, AuthResponse authResponse, string defaultProvider, string avatarUrl)
{
User user = _userManager.GetUserById(userId);
if (enableAuthorization)
@@ -1033,6 +1091,50 @@ public class SSOController : ControllerBase
}
}
if (avatarUrl is not null)
{
try
{
using var client = new HttpClient();
var avatarResponse = await client.GetAsync(avatarUrl);
if (!avatarResponse.Content.Headers.TryGetValues("content-type", out var contentTypeList))
{
throw new Exception("Cannot get Content-Type of image : " + avatarUrl);
}
var contentType = contentTypeList.First();
if (!contentType.StartsWith("image"))
{
throw new Exception("Content type of avatar URL is not an image, got : " + contentType);
}
var extension = contentType.Split("/").Last();
var stream = await avatarResponse.Content.ReadAsStreamAsync();
if (user != null)
{
var userDataPath =
Path.Combine(
_serverConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath,
user.Username);
if (user.ProfileImage is not null)
{
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
}
user.ProfileImage = new ImageInfo(Path.Combine(userDataPath, "profile" + extension));
await _providerManager.SaveImage(stream, contentType, user.ProfileImage.Path)
.ConfigureAwait(false);
}
}
catch (Exception e)
{
_logger.LogError(e.Message);
}
}
user.SetPermission(PermissionKind.EnableLiveTvAccess, enableLiveTv);
user.SetPermission(PermissionKind.EnableLiveTvManagement, enableLiveTvAdmin);
@@ -1068,9 +1170,19 @@ public class SSOController : ControllerBase
}
}
private string GetRequestBase(string schemeOverride = null)
private string GetRequestBase(string schemeOverride = null, int? portOverride = null)
{
int requestPort = Request.Host.Port ?? -1;
int requestPort;
if (portOverride != null)
{
requestPort = portOverride.Value;
}
else
{
requestPort = Request.Host.Port ?? -1;
}
if ((requestPort == 80 && string.Equals(Request.Scheme, "http", StringComparison.OrdinalIgnoreCase)) || (requestPort == 443 && string.Equals(Request.Scheme, "https", StringComparison.OrdinalIgnoreCase)))
{
requestPort = -1;
@@ -1150,6 +1262,7 @@ public class TimedAuthorizeState
IsLinking = false;
EnableLiveTv = false;
EnableLiveTvManagement = false;
AvatarURL = null;
}
/// <summary>
@@ -1197,4 +1310,9 @@ public class TimedAuthorizeState
/// Gets or sets a value indicating whether the user is allowed to manage live TV.
/// </summary>
public bool EnableLiveTvManagement { get; set; }
/// <summary>
/// Gets or sets the user avatar url.
/// </summary>
public string AvatarURL { get; set; }
}
+21 -1
View File
@@ -131,6 +131,11 @@ public class SamlConfig
/// </summary>
public string SchemeOverride { get; set; }
/// <summary>
/// Gets or sets the redirect port override.
/// </summary>
public int? PortOverride { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the new, more descriptive paths are to be used.
/// </summary>
@@ -265,6 +270,11 @@ public class OidConfig
/// </summary>
public string SchemeOverride { get; set; }
/// <summary>
/// Gets or sets the redirect port override.
/// </summary>
public int? PortOverride { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the new, more descriptive paths are to be used.
/// </summary>
@@ -293,10 +303,20 @@ public class OidConfig
/// </summary>
public string DefaultUsernameClaim { get; set; }
/// <summary>
/// Gets or sets the URL format of the new user avatar.
/// </summary>
public string AvatarUrlFormat { get; set; }
/// <summary>
/// Gets or sets a value indicating whether HTTPS in the discovery endpoint is required.
/// </summary>
public bool DisableHttps { get; set; }
public bool DisableHttps { get; set; }
/// <summary>
/// Gets or sets a value indicating whether pushed authorization is required.
/// </summary>
public bool DisablePushedAuthorization { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the OpenID endpoints are validated.
+10 -2
View File
@@ -301,12 +301,20 @@ const ssoConfigurationPage = {
form_elements.text_fields.forEach((id) => {
const value = page.querySelector("#" + id).value;
if (value) current_config[id] = page.querySelector("#" + id).value;
if (value) {
current_config[id] = page.querySelector("#" + id).value;
} else {
current_config[id] = null;
}
});
form_elements.json_fields.forEach((id) => {
const value = page.querySelector("#" + id).value;
if (value) current_config[id] = JSON.parse(value);
if (value) {
current_config[id] = JSON.parse(value);
} else {
current_config[id] = null;
}
});
form_elements.check_fields.forEach((id) => {
+60 -9
View File
@@ -73,7 +73,7 @@
<div class="collapseContent">
<div class="selectContainer">
<label class="selectLabel" for="selectProvider"
>Name of OID Provider:
>Name of OpenID Provider:
</label>
<select
is="emby-select"
@@ -123,7 +123,7 @@
<label
class="inputLabel inputLabelUnfocused"
for="OidProviderName"
>Name of OID Provider:</label
>Name of OpenID Provider:</label
>
<input
is="emby-input"
@@ -133,20 +133,20 @@
class="sso-text"
/>
<div class="fieldDescription">
The name used by Jellyfin to identify the OID provider.
The name used by Jellyfin to identify the OpenID provider.
<br />
If an OID provider with a matching name does not exist, a
If an OpenID provider with a matching name does not exist, a
new provider with this name will be created.
<br />
If an OID provider with a matching name already exists, the
settings for that provider will be updated.
If an OpenID provider with a matching name already exists,
the settings for that provider will be updated.
</div>
</div>
<div class="inputContainer">
<label
class="inputLabel inputLabelUnfocused"
for="OidEndpoint"
>OID Endpoint:</label
>OpenID Endpoint:</label
>
<input
is="emby-input"
@@ -181,7 +181,7 @@
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="OidSecret"
>OID Secret:</label
>OpenID client secret:</label
>
<input
is="emby-input"
@@ -191,7 +191,7 @@
class="sso-text"
/>
<div class="fieldDescription">
The OpenID secret. Randomly generated & shared.
The OpenID client secret. Randomly generated & shared.
</div>
</div>
@@ -559,6 +559,24 @@
</div>
</div>
<div class="inputContainer">
<label
class="inputLabel inputLabelUnfocused"
for="AvatarUrlFormat"
>Set avatar url format</label
>
<input
is="emby-input"
id="AvatarUrlFormat"
type="text"
class="sso-text"
/>
<div class="fieldDescription">
The url of the avatar with sso variable format: example :
<code>https://example.com/@{user_id}.png</code>
</div>
</div>
<div class="checkboxContainer">
<label>
<input
@@ -573,6 +591,23 @@
<div class="fieldDescription checkboxFieldDescription"></div>
</div>
<div class="checkboxContainer">
<label>
<input
is="emby-checkbox"
id="DisablePushedAuthorization"
name="DisablePushedAuthorization"
type="checkbox"
class="sso-toggle"
/>
<span
>Disable Pushed Authorization (Insecure). May be needed
for Authelia.</span
>
</label>
<div class="fieldDescription checkboxFieldDescription"></div>
</div>
<div
class="checkboxContainer checkboxContainer-withDescription"
>
@@ -619,6 +654,22 @@
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="RoleClaim"
>Port Override</label
>
<input
is="emby-input"
id="PortOverride"
type="text"
class="sso-text"
/>
<div class="fieldDescription">
If the plugin is redirecting to an incorrect port, set this
to the appropiate port
</div>
</div>
<button
id="SaveProvider"
is="emby-button"
+3 -3
View File
@@ -61,14 +61,14 @@
is="emby-linkbutton"
href="https://github.com/9p4/jellyfin-plugin-sso"
class="button-link"
>help page</a
>homepage</a
>
and
<a
is="emby-linkbutton"
href="https://github.com/9p4/jellyfin-plugin-sso/projects/1"
href="https://github.com/9p4/jellyfin-plugin-sso/issues"
class="button-link"
>roadmap
>issue tracker
</a>
for more information.
</p>
+13
View File
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>SSO_Auth</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
</Project>
+5
View File
@@ -0,0 +1,5 @@
namespace SSO_Auth.Lib
module Say =
let hello name =
printfn "Hello %s" name
+10 -6
View File
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>Jellyfin.Plugin.SSO_Auth</RootNamespace>
<AssemblyVersion>3.5.2.4</AssemblyVersion>
<FileVersion>3.5.2.4</FileVersion>
<AssemblyVersion>4.0.0.0</AssemblyVersion>
<FileVersion>4.0.0.0</FileVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
@@ -29,9 +29,9 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="IdentityModel.OidcClient" Version="5.2.1" />
<PackageReference Include="Jellyfin.Controller" Version="10.*-*" />
<PackageReference Include="Jellyfin.Model" Version="10.*-*" />
<PackageReference Include="Duende.IdentityModel.OidcClient" Version="6.0.1" />
<PackageReference Include="Jellyfin.Controller" Version="10.11.0" />
<PackageReference Include="Jellyfin.Model" Version="10.11.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Security.Cryptography.Xml" Version="6.0.1" />
</ItemGroup>
@@ -42,6 +42,10 @@
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="Lib\Lib.fsproj" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
+1 -1
View File
@@ -61,7 +61,7 @@ public class Response
/// <param name="certificateBytes">The certificate formatted as an array of bytes.</param>
public Response(byte[] certificateBytes)
{
_certificate = new X509Certificate2(certificateBytes);
_certificate = X509CertificateLoader.LoadCertificate(certificateBytes);
}
/// <summary>
+18 -12
View File
@@ -240,36 +240,42 @@ Emby Button
/* fonts.scss */
html {
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR",
"Noto Sans SC", "Noto Sans TC", sans-serif;
font-family:
"Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC",
"Noto Sans TC", sans-serif;
text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
html[lang|="ja"] {
font-family: "Noto Sans", "Noto Sans JP", "Noto Sans HK", "Noto Sans KR",
"Noto Sans SC", "Noto Sans TC", sans-serif;
font-family:
"Noto Sans", "Noto Sans JP", "Noto Sans HK", "Noto Sans KR", "Noto Sans SC",
"Noto Sans TC", sans-serif;
}
html[lang|="ko"] {
font-family: "Noto Sans", "Noto Sans KR", "Noto Sans HK", "Noto Sans JP",
"Noto Sans SC", "Noto Sans TC", sans-serif;
font-family:
"Noto Sans", "Noto Sans KR", "Noto Sans HK", "Noto Sans JP", "Noto Sans SC",
"Noto Sans TC", sans-serif;
}
html[lang|="zh-CN"] {
font-family: "Noto Sans", "Noto Sans SC", "Noto Sans HK", "Noto Sans JP",
"Noto Sans KR", "Noto Sans TC", sans-serif;
font-family:
"Noto Sans", "Noto Sans SC", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR",
"Noto Sans TC", sans-serif;
}
html[lang|="zh-TW"] {
font-family: "Noto Sans", "Noto Sans TC", "Noto Sans HK", "Noto Sans JP",
"Noto Sans KR", "Noto Sans SC", sans-serif;
font-family:
"Noto Sans", "Noto Sans TC", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR",
"Noto Sans SC", sans-serif;
}
html[lang|="zh-HK"] {
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR",
"Noto Sans SC", "Noto Sans TC", sans-serif;
font-family:
"Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC",
"Noto Sans TC", sans-serif;
}
.layout-tv {
+98 -91
View File
@@ -1,3 +1,5 @@
using System.Globalization;
namespace Jellyfin.Plugin.SSO_Auth;
/// <summary>
@@ -9,7 +11,16 @@ public static class WebResponse
/// The shared HTML between all of the responses.
/// </summary>
public static readonly string Base = @"<!DOCTYPE html>
<html><head></head><body>
<html><head>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<style>
body {
background: #101010;
color: #d1cfce;
font-family: Noto Sans, Noto Sans HK, Noto Sans JP, Noto Sans KR, Noto Sans SC, Noto Sans TC, sans-serif;
}
</style>
</head><body>
<p>Logging in...</p>
<noscript>Please enable Javascript to complete the login</noscript>
<script>
@@ -35,25 +46,14 @@ function isTv() {
return true;
}
if (isWeb0s()) {
return true;
}
return false;
return isWeb0s();
}
function isWeb0s() {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf('netcast') !== -1) {
return true;
}
if (userAgent.indexOf('web0s') !== -1) {
return true;
}
return false;
return userAgent.indexOf('netcast') !== -1
|| userAgent.indexOf('web0s') !== -1;
}
function isMobile(userAgent) {
@@ -100,11 +100,7 @@ function hasKeyboard(browser) {
return true;
}
if (browser.tv) {
return true;
}
return false;
return !!browser.tv;
}
function iOSversion() {
@@ -147,8 +143,12 @@ function web0sVersion(browser) {
// The next is only valid for the app
if (browser.versionMajor >= 79) {
return 6;
if (browser.versionMajor >= 94) {
return 23;
} else if (browser.versionMajor >= 87) {
return 22;
} else if (browser.versionMajor >= 79) {
return 6;
} else if (browser.versionMajor >= 68) {
return 5;
} else if (browser.versionMajor >= 53) {
@@ -179,14 +179,11 @@ let _supportsCssAnimation;
let _supportsCssAnimationWithPrefix;
function supportsCssAnimation(allowPrefix) {
// TODO: Assess if this is still needed, as all of our targets should natively support CSS animations.
if (allowPrefix) {
if (_supportsCssAnimationWithPrefix === true || _supportsCssAnimationWithPrefix === false) {
return _supportsCssAnimationWithPrefix;
}
} else {
if (_supportsCssAnimation === true || _supportsCssAnimation === false) {
return _supportsCssAnimation;
}
if (allowPrefix && (_supportsCssAnimationWithPrefix === true || _supportsCssAnimationWithPrefix === false)) {
return _supportsCssAnimationWithPrefix;
}
if (_supportsCssAnimation === true || _supportsCssAnimation === false) {
return _supportsCssAnimation;
}
let animation = false;
@@ -198,8 +195,8 @@ function supportsCssAnimation(allowPrefix) {
}
if (animation === false && allowPrefix) {
for (let i = 0; i < domPrefixes.length; i++) {
if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
for (const domPrefix of domPrefixes) {
if (elm.style[domPrefix + 'AnimationName'] !== undefined) {
animation = true;
break;
}
@@ -218,25 +215,25 @@ function supportsCssAnimation(allowPrefix) {
const uaMatch = function (ua) {
ua = ua.toLowerCase();
const match = /(edg)[ /]([\w.]+)/.exec(ua) ||
/(edga)[ /]([\w.]+)/.exec(ua) ||
/(edgios)[ /]([\w.]+)/.exec(ua) ||
/(edge)[ /]([\w.]+)/.exec(ua) ||
/(opera)[ /]([\w.]+)/.exec(ua) ||
/(opr)[ /]([\w.]+)/.exec(ua) ||
/(chrome)[ /]([\w.]+)/.exec(ua) ||
/(safari)[ /]([\w.]+)/.exec(ua) ||
/(firefox)[ /]([\w.]+)/.exec(ua) ||
ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[];
const match = /(chrome)[ /]([\w.]+)/.exec(ua)
|| /(edg)[ /]([\w.]+)/.exec(ua)
|| /(edga)[ /]([\w.]+)/.exec(ua)
|| /(edgios)[ /]([\w.]+)/.exec(ua)
|| /(edge)[ /]([\w.]+)/.exec(ua)
|| /(opera)[ /]([\w.]+)/.exec(ua)
|| /(opr)[ /]([\w.]+)/.exec(ua)
|| /(safari)[ /]([\w.]+)/.exec(ua)
|| /(firefox)[ /]([\w.]+)/.exec(ua)
|| ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)
|| [];
const versionMatch = /(version)[ /]([\w.]+)/.exec(ua);
let platform_match = /(ipad)/.exec(ua) ||
/(iphone)/.exec(ua) ||
/(windows)/.exec(ua) ||
/(android)/.exec(ua) ||
[];
let platform_match = /(ipad)/.exec(ua)
|| /(iphone)/.exec(ua)
|| /(windows)/.exec(ua)
|| /(android)/.exec(ua)
|| [];
let browser = match[1] || '';
@@ -255,7 +252,7 @@ const uaMatch = function (ua) {
version = version || match[2] || '0';
let versionMajor = parseInt(version.split('.')[0]);
let versionMajor = parseInt(version.split('.')[0], 10);
if (isNaN(versionMajor)) {
versionMajor = 0;
@@ -313,7 +310,9 @@ if (userAgent.toLowerCase().indexOf('xbox') !== -1) {
browser.tv = true;
}
browser.animate = typeof document !== 'undefined' && document.documentElement.animate != null;
browser.hisense = userAgent.toLowerCase().includes('hisense');
browser.tizen = userAgent.toLowerCase().indexOf('tizen') !== -1 || window.tizen != null;
browser.vidaa = userAgent.toLowerCase().includes('vidaa');
browser.web0s = isWeb0s();
browser.edgeUwp = browser.edge && (userAgent.toLowerCase().indexOf('msapphost') !== -1 || userAgent.toLowerCase().indexOf('webview') !== -1);
@@ -324,7 +323,7 @@ if (browser.web0s) {
delete browser.safari;
const v = (navigator.appVersion).match(/Tizen (\d+).(\d+)/);
browser.tizenVersion = parseInt(v[1]);
browser.tizenVersion = parseInt(v[1], 10);
} else {
browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1;
}
@@ -340,11 +339,9 @@ if (browser.mobile || browser.tv) {
browser.slow = true;
}
if (typeof document !== 'undefined') {
/* eslint-disable-next-line compat/compat */
if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
browser.touch = true;
}
/* eslint-disable-next-line compat/compat */
if (typeof document !== 'undefined' && ('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) {
browser.touch = true;
}
browser.keyboard = hasKeyboard(browser);
@@ -361,39 +358,41 @@ if (browser.iOS) {
}
function getDeviceName() {
var deviceName = '';
if (browser.tizen) {
deviceName = 'Samsung Smart TV';
} else if (browser.web0s) {
deviceName = 'LG Smart TV';
} else if (browser.operaTv) {
deviceName = 'Opera TV';
} else if (browser.xboxOne) {
deviceName = 'Xbox One';
} else if (browser.ps4) {
deviceName = 'Sony PS4';
} else if (browser.chrome) {
deviceName = 'Chrome';
} else if (browser.edgeChromium) {
deviceName = 'Edge Chromium';
} else if (browser.edge) {
deviceName = 'Edge';
} else if (browser.firefox) {
deviceName = 'Firefox';
} else if (browser.opera) {
deviceName = 'Opera';
} else if (browser.safari) {
deviceName = 'Safari';
} else {
deviceName = 'Web Browser';
}
var deviceName = '';
if (!deviceName) {
if (browser.tizen) {
deviceName = 'Samsung Smart TV';
} else if (browser.web0s) {
deviceName = 'LG Smart TV';
} else if (browser.operaTv) {
deviceName = 'Opera TV';
} else if (browser.xboxOne) {
deviceName = 'Xbox One';
} else if (browser.ps4) {
deviceName = 'Sony PS4';
} else if (browser.chrome) {
deviceName = 'Chrome';
} else if (browser.edgeChromium) {
deviceName = 'Edge Chromium';
} else if (browser.edge) {
deviceName = 'Edge';
} else if (browser.firefox) {
deviceName = 'Firefox';
} else if (browser.opera) {
deviceName = 'Opera';
} else if (browser.safari) {
deviceName = 'Safari';
} else {
deviceName = 'Web Browser';
}
if (browser.ipad) {
deviceName += ' iPad';
} else if (browser.iphone) {
deviceName += ' iPhone';
} else if (browser.android) {
deviceName += ' Android';
if (browser.ipad) {
deviceName += ' iPad';
} else if (browser.iphone) {
deviceName += ' iPhone';
} else if (browser.android) {
deviceName += ' Android';
}
}
return deviceName;
@@ -416,6 +415,14 @@ const sleep = (milliseconds) => {
/// <returns>A string with the HTML to serve to the client.</returns>
public static string Generator(string data, string provider, string baseUrl, string mode, bool isLinking = false)
{
// Strip out the protocol (http:// or https://) and convert the domain to Punycode
var idnMapping = new IdnMapping();
var protocolSeparatorIndex = baseUrl.IndexOf("//");
var protocol = baseUrl.Substring(0, protocolSeparatorIndex + 2);
var domain = baseUrl.Substring(protocolSeparatorIndex + 2);
var punycodeDomain = idnMapping.GetAscii(domain);
var punycodeBaseUrl = protocol + punycodeDomain;
return Base + @"
async function link(request) {
const jfCredentialsString = localStorage.getItem(""jellyfin_credentials"");
@@ -429,7 +436,7 @@ async function link(request) {
if (jfUser == null) return;
if (jfToken == null) return;
const url = '" + $"{baseUrl}/sso/{mode}/Link/{provider}/" + @"' + jfUser;
const url = '" + $"{punycodeBaseUrl}/sso/{mode}/Link/{provider}/" + @"' + jfUser;
return new Promise(resolve => {
var xhr = new XMLHttpRequest();
@@ -454,7 +461,7 @@ async function link(request) {
async function main() {
localStorage.removeItem('jellyfin_credentials');
document.getElementById('iframe-main').src = '" + baseUrl + @"/web/index.html';
document.getElementById('iframe-main').src = '" + punycodeBaseUrl + @"/web/index.html';
var data = '" + data + @"';
while (localStorage.getItem(""_deviceId2"") == null ||
@@ -472,7 +479,7 @@ async function main() {
if (" + $"{isLinking}".ToLower() + @") await link(request);
var url = '" + baseUrl + "/sso/" + mode + "/Auth/" + provider + @"';
var url = '" + punycodeBaseUrl + "/sso/" + mode + "/Auth/" + provider + @"';
let response = await new Promise(resolve => {
var xhr = new XMLHttpRequest();
@@ -496,7 +503,7 @@ async function main() {
jfCreds['Servers'][0]['UserId'] = responseJson['User']['Id'];
localStorage.setItem('jellyfin_credentials', JSON.stringify(jfCreds));
localStorage.setItem('enableAutoLogin', 'true');
window.location.replace('" + baseUrl + @"/web/index.html');
window.location.replace('" + punycodeBaseUrl + @"/web/index.html');
}
document.addEventListener('DOMContentLoaded', function () {
+7 -5
View File
@@ -1,9 +1,9 @@
name: "SSO Authentication"
guid: "505ce9d1-d916-42fa-86ca-673ef241d7df"
imageUrl: "https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/main/img/logo.png"
version: "3.5.2.4"
targetAbi: "10.9.0.0"
framework: "net8.0"
version: "4.0.0.0"
targetAbi: "10.11.0.0"
framework: "net9.0"
owner: "9p4"
overview: "Authenticate users against an SSO provider."
description: |
@@ -12,9 +12,11 @@ description: |
category: "Authentication"
artifacts:
- "SSO-Auth.dll"
- "IdentityModel.OidcClient.dll"
- "IdentityModel.dll"
- "Duende.IdentityModel.OidcClient.dll"
- "Duende.IdentityModel.dll"
changelog: |
4.0.0.0: Jellyfin 10.11
3.5.3.0: Allow for OID-provided avatars, various bugfixes and workarounds
3.5.2.4: Updates for Jellyfin 10.9
3.5.2.3: Improve OpenID discovery policy security rules, fix iOS login bugs related to cache
3.5.2.2: Fix linking page when using new paths
Generated
+3 -3
View File
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1715499532,
"narHash": "sha256-9UJLb8rdi2VokYcfOBQHUzP3iNxOPNWcbK++ENElpk0=",
"lastModified": 1760934318,
"narHash": "sha256-/oUYsC0lUCBory65VK+UHqCCsCspbL1Vgfcf1KUYqVw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "af8b9db5c00f1a8e4b83578acc578ff7d823b786",
"rev": "87848bf0cc4f87717fc813a4575f07330c3e743c",
"type": "github"
},
"original": {
+1 -1
View File
@@ -5,6 +5,6 @@
let pkgs = nixpkgs.legacyPackages.x86_64-linux;
in {
devShell.x86_64-linux =
pkgs.mkShell { buildInputs = [ pkgs.nodePackages.prettier pkgs.dotnet-sdk_8 ]; };
pkgs.mkShell { buildInputs = [ pkgs.nodePackages.prettier pkgs.dotnet-sdk_9 ]; };
};
}