// Kyoo - A portable and vast media library solution. // Copyright (c) Kyoo. // // See AUTHORS.md and LICENSE file in the project root for full license information. // // Kyoo is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // Kyoo is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Autofac; using Kyoo.Abstractions.Controllers; using Kyoo.Authentication.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; namespace Kyoo.Authentication { /// /// A module that enable OpenID authentication for Kyoo. /// /// /// Create a new authentication module instance and use the given configuration. /// public class AuthenticationModule( IConfiguration configuration, ILogger logger ) : IPlugin { /// public string Name => "Authentication"; /// /// The configuration to use. /// private readonly IConfiguration _configuration = configuration; /// public void Configure(ContainerBuilder builder) { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); } /// public void Configure(IServiceCollection services) { string secret = _configuration.GetValue( "AUTHENTICATION_SECRET", AuthenticationOption.DefaultSecret )!; PermissionOption permissions = new() { Default = _configuration .GetValue("UNLOGGED_PERMISSIONS", "overall.read")! .Split(','), NewUser = _configuration .GetValue("DEFAULT_PERMISSIONS", "overall.read")! .Split(','), PublicUrl = _configuration.GetValue("PUBLIC_URL") ?? "http://localhost:8901", ApiKeys = _configuration.GetValue("KYOO_APIKEYS", string.Empty)!.Split(','), OIDC = _configuration .AsEnumerable() .Where((pair) => pair.Key.StartsWith("OIDC_")) .Aggregate( new Dictionary(), (acc, val) => { if (val.Value is null) return acc; if (val.Key.Split("_") is not ["OIDC", string provider, string key]) { logger.LogError("Invalid oidc config value: {Key}", val.Key); return acc; } provider = provider.ToLowerInvariant(); key = key.ToLowerInvariant(); if (!acc.ContainsKey(provider)) acc.Add(provider, new(provider)); switch (key) { case "clientid": acc[provider].ClientId = val.Value; break; case "secret": acc[provider].Secret = val.Value; break; case "scope": acc[provider].Scope = val.Value; break; case "authorization": acc[provider].AuthorizationUrl = val.Value; break; case "token": acc[provider].TokenUrl = val.Value; break; case "userinfo": case "profile": acc[provider].ProfileUrl = val.Value; break; case "name": acc[provider].DisplayName = val.Value; break; case "logo": acc[provider].LogoUrl = val.Value; break; default: logger.LogError("Invalid oidc config value: {Key}", key); return acc; } return acc; } ), }; services.AddSingleton(permissions); services.AddSingleton( new AuthenticationOption() { Secret = secret, Permissions = permissions, } ); // TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos) services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Events = new() { OnMessageReceived = (ctx) => { ctx.Token ??= ctx.Request.Cookies["X-Bearer"]; return Task.CompletedTask; } }; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)) }; }); } /// public IEnumerable ConfigureSteps => new IStartupAction[] { SA.New(app => app.UseAuthentication(), SA.Authentication), }; } }