Use a consistent http and oidc clients Use the access token to send an a authenticated request to retrieve avatar and do it at the same time as requesting the userinfo endpoint Merge JTokens instead of re-parsing json strings to merge configuration
145 lines
6.0 KiB
C#
145 lines
6.0 KiB
C#
namespace Jellyfin.Plugin.OIDC_Auth;
|
|
|
|
using Logging = Microsoft.Extensions.Logging;
|
|
|
|
/// <summary>
|
|
/// The OIDC plugin class.
|
|
/// </summary>
|
|
public class OIDCPlugin : MediaBrowser.Common.Plugins.BasePlugin<Jellyfin.Plugin.OIDC_Auth.PluginConfiguration>,
|
|
MediaBrowser.Common.Plugins.IPlugin, MediaBrowser.Model.Plugins.IHasWebPages {
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="OIDCPlugin" /> class.
|
|
/// </summary>
|
|
/// <param name="applicationPaths">Internal Jellyfin interface for the ApplicationPath.</param>
|
|
/// <param name="xmlSerializer">Internal Jellyfin interface for the XML information.</param>
|
|
/// <param name="logger">Fuck C#.</param>
|
|
public OIDCPlugin(
|
|
MediaBrowser.Common.Configuration.IApplicationPaths applicationPaths,
|
|
MediaBrowser.Model.Serialization.IXmlSerializer xmlSerializer,
|
|
Logging.ILogger<OIDCPlugin> logger
|
|
) : base(applicationPaths, xmlSerializer) {
|
|
if (OIDCPlugin.Instance == null) {
|
|
OIDCPlugin.Instance = this;
|
|
foreach (var config in this.Configuration.Configs) {
|
|
config.Value.FixCanonicalLinks();
|
|
}
|
|
}
|
|
if (string.IsNullOrWhiteSpace(OIDCPlugin.IndexHTML)) {
|
|
var indexPath = System.IO.Path.Combine(applicationPaths.WebPath, "index.html");
|
|
string content = System.IO.File.ReadAllText(indexPath);
|
|
content = content.Substring(0, content.IndexOf("</head>"));
|
|
// See GetHTMLPage for how this is used
|
|
OIDCPlugin.IndexHTML = content;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the instance of the OIDC plugin.
|
|
/// </summary>
|
|
public static OIDCPlugin Instance { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets index HTML page.
|
|
/// </summary>
|
|
public static string IndexHTML { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the name of the OIDC plugin.
|
|
/// </summary>
|
|
public override string Name => "OIDC Authentication";
|
|
|
|
/// <summary>
|
|
/// Gets the GUID of the OIDC plugin.
|
|
/// </summary>
|
|
public override System.Guid Id => System.Guid.Parse("c2e83d4a-1a92-11f1-a789-f84d896c963f");
|
|
|
|
/// <summary>
|
|
/// Returns html page with content injected into it.
|
|
/// </summary>
|
|
/// <param name="content">The content to inject.</param>
|
|
/// <param name="baseURL">The base url so that JellyFin react app can load.</param>
|
|
/// <returns>A list of internal webpages in this application.</returns>
|
|
public string GetHTMLPage(string content, string baseURL) {
|
|
string pre = """
|
|
<body class="force-scroll dashboardDocument mouseIdle">
|
|
<div id="reactRoot" style="display: none;"></div>
|
|
""";
|
|
// loads jellyfin so that styles get set appropriately. ApiClient will not get set if we are not logged in
|
|
// Make sure jellyfin doesnt show. But we still need reactRoot in order for jellyfin to initialize.
|
|
string post = """
|
|
<script>
|
|
function waitForWindowProperty(propName, callback, interval = 100) {
|
|
const timer = setInterval(() => {
|
|
if (window[propName] !== undefined) {
|
|
clearInterval(timer);
|
|
callback(window[propName]);
|
|
}
|
|
}, interval);
|
|
}
|
|
waitForWindowProperty("ApiClient", () => {
|
|
console.log("ApiClientFound");
|
|
document.dispatchEvent(new CustomEvent("ApiClientFound", { }));
|
|
});
|
|
</script>
|
|
</body></html>
|
|
"""; // Jellyfin-web doesn't have a way to inject public html pages otherwise none of this would be needed
|
|
return OIDCPlugin.IndexHTML
|
|
.Replace("<head>", $"<head><base href=\"{baseURL.TrimEnd('/')}/web/\">") // Set the base meta property so that includes are resolved
|
|
+ pre + content + post;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the available internal web pages of this plugin.
|
|
/// </summary>
|
|
/// <returns>A list of internal webpages in this application.</returns>
|
|
public System.Collections.Generic.IEnumerable<MediaBrowser.Model.Plugins.PluginPageInfo> GetPages() {
|
|
return [
|
|
new() {
|
|
Name = "OIDC-Auth-config.html",
|
|
EmbeddedResourcePath = $"{this.GetType().Namespace}.Assets.OIDC-Auth-config.html",
|
|
EnableInMainMenu = true
|
|
},
|
|
new() {
|
|
Name = "OIDC-Auth-config.js",
|
|
EmbeddedResourcePath = $"{this.GetType().Namespace}.Assets.OIDC-Auth-config.js"
|
|
},
|
|
new() {
|
|
Name = "OIDC-Auth-config.css",
|
|
EmbeddedResourcePath = $"{this.GetType().Namespace}.Assets.OIDC-Auth-config.css"
|
|
},
|
|
new() {
|
|
Name = "OIDC-Auth-linking.html",
|
|
EmbeddedResourcePath = $"{this.GetType().Namespace}.Assets.OIDC-Auth-linking.html"
|
|
},
|
|
new() {
|
|
Name = "OIDC-Auth-linking.js",
|
|
EmbeddedResourcePath = $"{this.GetType().Namespace}.Assets.OIDC-Auth-linking.js"
|
|
},
|
|
new() {
|
|
Name = "OIDC-Auth-auth.html",
|
|
EmbeddedResourcePath = $"{this.GetType().Namespace}.Assets.OIDC-Auth-auth.html"
|
|
}
|
|
];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the content of a page embedded in the plugin.
|
|
/// </summary>
|
|
/// <param name="name">The name of the file to extract from the plugin.</param>
|
|
/// <returns>A list of internal webpages in this application.</returns>
|
|
public string GetPage(string name) {
|
|
foreach (MediaBrowser.Model.Plugins.PluginPageInfo pi in this.GetPages()) {
|
|
if (pi.Name != "OIDC-Auth-" + name) {
|
|
continue;
|
|
}
|
|
using (var s = OIDCPlugin.Instance.GetType().Assembly.GetManifestResourceStream(pi.EmbeddedResourcePath)) {
|
|
if (s == null) {
|
|
break;
|
|
}
|
|
return new System.IO.StreamReader(s).ReadToEnd();
|
|
}
|
|
}
|
|
return string.Empty;
|
|
}
|
|
}
|