From 440e5f4f141ac090b06e0c19cf707f9a82ae809e Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 9 May 2021 15:58:55 +0200 Subject: [PATCH] Handling autologin and logout --- Kyoo.Authentication/AuthenticationModule.cs | 9 +++- Kyoo.Authentication/Extensions.cs | 15 ++++--- Kyoo.Authentication/IdentityDatabase.cs | 47 -------------------- Kyoo.Authentication/User.cs | 22 ---------- Kyoo.Authentication/Views/AccountApi.cs | 48 ++++++++++----------- Kyoo.Common/Controllers/IFileManager.cs | 3 +- Kyoo.WebApp | 2 +- 7 files changed, 42 insertions(+), 104 deletions(-) delete mode 100644 Kyoo.Authentication/IdentityDatabase.cs delete mode 100644 Kyoo.Authentication/User.cs diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 26b50dd1..e0bd5628 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -86,6 +86,12 @@ namespace Kyoo.Authentication IdentityModelEventSource.ShowPII = true; services.AddControllers(); + + // TODO handle direct-videos with bearers (probably add a ?token query param and a app.Use to translate that for videos) + + // TODO Support sign-out, check if login work, check if tokens should be stored. + + // TODO remove unused/commented code, add documentation. // services.AddIdentityCore() // .AddSignInManager() @@ -157,8 +163,7 @@ namespace Kyoo.Authentication services.AddAuthorization(options => { - AuthorizationPolicyBuilder scheme = new(IdentityConstants.ApplicationScheme, - JwtBearerDefaults.AuthenticationScheme); + AuthorizationPolicyBuilder scheme = new(JwtBearerDefaults.AuthenticationScheme); options.DefaultPolicy = scheme.RequireAuthenticatedUser().Build(); string[] permissions = {"Read", "Write", "Play", "Admin"}; diff --git a/Kyoo.Authentication/Extensions.cs b/Kyoo.Authentication/Extensions.cs index 4cac9dc8..5c65fade 100644 --- a/Kyoo.Authentication/Extensions.cs +++ b/Kyoo.Authentication/Extensions.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Security.Claims; using IdentityModel; +using IdentityServer4; using Kyoo.Models; namespace Kyoo.Authentication @@ -24,16 +25,18 @@ namespace Kyoo.Authentication new Claim(JwtClaimTypes.Picture, $"api/account/picture/{user.Slug}") }; } - + /// - /// Convert a user to a ClaimsPrincipal. + /// Convert a user to an . /// /// The user to convert - /// A ClaimsPrincipal representing the user - public static ClaimsPrincipal ToPrincipal(this User user) + /// The corresponding identity server user. + public static IdentityServerUser ToIdentityUser(this User user) { - ClaimsIdentity id = new (user.GetClaims()); - return new ClaimsPrincipal(id); + return new(user.ID.ToString()) + { + DisplayName = user.Username + }; } } } \ No newline at end of file diff --git a/Kyoo.Authentication/IdentityDatabase.cs b/Kyoo.Authentication/IdentityDatabase.cs deleted file mode 100644 index 8b4f321e..00000000 --- a/Kyoo.Authentication/IdentityDatabase.cs +++ /dev/null @@ -1,47 +0,0 @@ -// using System.Threading.Tasks; -// using IdentityServer4.EntityFramework.Entities; -// using IdentityServer4.EntityFramework.Extensions; -// using IdentityServer4.EntityFramework.Interfaces; -// using IdentityServer4.EntityFramework.Options; -// using Kyoo.Models; -// using Microsoft.AspNetCore.Identity; -// using Microsoft.AspNetCore.Identity.EntityFrameworkCore; -// using Microsoft.EntityFrameworkCore; -// using Microsoft.Extensions.Options; -// -// namespace Kyoo -// { -// // The configuration's database is named ConfigurationDbContext. -// public class IdentityDatabase : IdentityDbContext, IPersistedGrantDbContext -// { -// private readonly IOptions _operationalStoreOptions; -// -// public IdentityDatabase(DbContextOptions options, IOptions operationalStoreOptions) -// : base(options) -// { -// _operationalStoreOptions = operationalStoreOptions; -// } -// -// public DbSet Accounts { get; set; } -// -// protected override void OnModelCreating(ModelBuilder modelBuilder) -// { -// base.OnModelCreating(modelBuilder); -// modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value); -// -// modelBuilder.Entity().ToTable("User"); -// modelBuilder.Entity>().ToTable("UserRole"); -// modelBuilder.Entity>().ToTable("UserLogin"); -// modelBuilder.Entity>().ToTable("UserClaim"); -// modelBuilder.Entity().ToTable("UserRoles"); -// modelBuilder.Entity>().ToTable("UserRoleClaim"); -// modelBuilder.Entity>().ToTable("UserToken"); -// } -// -// public Task SaveChangesAsync() => base.SaveChangesAsync(); -// -// public DbSet PersistedGrants { get; set; } -// public DbSet DeviceFlowCodes { get; set; } -// -// } -// } \ No newline at end of file diff --git a/Kyoo.Authentication/User.cs b/Kyoo.Authentication/User.cs deleted file mode 100644 index 896d3004..00000000 --- a/Kyoo.Authentication/User.cs +++ /dev/null @@ -1,22 +0,0 @@ -// using System; -// using IdentityModel; -// using Microsoft.AspNetCore.Identity; -// -// namespace Kyoo.Models -// { -// public class User : IdentityUser -// { -// public string OTAC { get; set; } -// public DateTime? OTACExpires { get; set; } -// -// public string GenerateOTAC(TimeSpan validFor) -// { -// string otac = CryptoRandom.CreateUniqueId(); -// string hashed = otac; // TODO should add a good hashing here. -// -// OTAC = hashed; -// OTACExpires = DateTime.UtcNow.Add(validFor); -// return otac; -// } -// } -// } \ No newline at end of file diff --git a/Kyoo.Authentication/Views/AccountApi.cs b/Kyoo.Authentication/Views/AccountApi.cs index 81bc3a55..d15ececc 100644 --- a/Kyoo.Authentication/Views/AccountApi.cs +++ b/Kyoo.Authentication/Views/AccountApi.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using IdentityServer4; using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Services; @@ -35,15 +34,9 @@ namespace Kyoo.Authentication.Views /// private readonly IUserRepository _users; /// - /// The identity server interaction service to login users. - /// - // private readonly IIdentityServerInteractionService _interaction; - /// /// A file manager to send profile pictures /// private readonly IFileManager _files; - // private readonly SignInManager _signInManager; - /// /// Options about authentication. Those options are monitored and reloads are supported. /// @@ -54,20 +47,15 @@ namespace Kyoo.Authentication.Views /// Create a new handle to handle login/users requests. /// /// The user repository to create and manage users - /// The identity server interaction service to login users. /// A file manager to send profile pictures /// Authentication options (this may be hot reloaded) public AccountApi(IUserRepository users, - // IIdentityServerInteractionService interaction, IFileManager files, IOptions options) - //, SignInManager signInManager) { _users = users; - // _interaction = interaction; _files = files; _options = options; - // _signInManager = signInManager; } @@ -119,7 +107,6 @@ namespace Kyoo.Authentication.Views [HttpPost("login")] public async Task Login([FromBody] LoginRequest login) { - // AuthorizationRequest context = await _interaction.GetAuthorizationContextAsync(login.ReturnURL); User user = await _users.GetOrDefault(x => x.Username == login.Username); if (user == null) @@ -127,7 +114,7 @@ namespace Kyoo.Authentication.Views if (!PasswordUtils.CheckPassword(login.Password, user.Password)) return Unauthorized(); - // await _signInManager.SignInAsync(user, login.StayLoggedIn); + await HttpContext.SignInAsync(user.ToIdentityUser(), StayLogged(login.StayLoggedIn)); return Ok(new { RedirectUrl = login.ReturnURL, IsOk = true }); } @@ -143,20 +130,16 @@ namespace Kyoo.Authentication.Views User user = (await _users.GetAll()).FirstOrDefault(x => x.ExtraData.GetValueOrDefault("otac") == otac.Otac); if (user == null) return Unauthorized(); - if (DateTime.ParseExact(user.ExtraData["otac-expire"], "s", CultureInfo.InvariantCulture) <= DateTime.UtcNow) + if (DateTime.ParseExact(user.ExtraData["otac-expire"], "s", CultureInfo.InvariantCulture) <= + DateTime.UtcNow) + { return BadRequest(new { code = "ExpiredOTAC", description = "The OTAC has expired. Try to login with your password." }); + } - - IdentityServerUser iduser = new(user.ID.ToString()) - { - DisplayName = user.Username - }; - - await HttpContext.SignInAsync(iduser, StayLogged(otac.StayLoggedIn)); - // await _signInManager.SignInAsync(user, otac.StayLoggedIn); + await HttpContext.SignInAsync(user.ToIdentityUser(), StayLogged(otac.StayLoggedIn)); return Ok(); } @@ -167,11 +150,11 @@ namespace Kyoo.Authentication.Views [Authorize] public async Task Logout() { - // await _signInManager.SignOutAsync(); + await HttpContext.SignOutAsync(); return Ok(); } - // TODO check with the extension method + /// public async Task GetProfileDataAsync(ProfileDataRequestContext context) { User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId())); @@ -181,12 +164,18 @@ namespace Kyoo.Authentication.Views context.IssuedClaims.Add(new Claim("permissions", string.Join(',', user.Permissions))); } + /// public async Task IsActiveAsync(IsActiveContext context) { User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId())); context.IsActive = user != null; } + /// + /// Get the user's profile picture. + /// + /// The user slug + /// The profile picture of the user or 404 if not found [HttpGet("picture/{slug}")] public async Task GetPicture(string slug) { @@ -197,6 +186,11 @@ namespace Kyoo.Authentication.Views return _files.FileResult(path); } + /// + /// Update profile information (email, username, profile picture...) + /// + /// The new information + /// The edited user [HttpPut] [Authorize] public async Task> Update([FromForm] AccountUpdateRequest data) @@ -218,6 +212,10 @@ namespace Kyoo.Authentication.Views return await _users.Edit(user, false); } + /// + /// Get permissions for a non connected user. + /// + /// The list of permissions of a default user. [HttpGet("permissions")] public ActionResult> GetDefaultPermissions() { diff --git a/Kyoo.Common/Controllers/IFileManager.cs b/Kyoo.Common/Controllers/IFileManager.cs index 0765c1d0..03f22e79 100644 --- a/Kyoo.Common/Controllers/IFileManager.cs +++ b/Kyoo.Common/Controllers/IFileManager.cs @@ -19,7 +19,8 @@ namespace Kyoo.Controllers /// or proxy them from a distant server /// /// - /// If no file exists at the given path, you should return a NotFoundResult or handle it gracefully. + /// If no file exists at the given path or if the path is null, a NotFoundResult is returned + /// to handle it gracefully. /// /// The path of the file. /// diff --git a/Kyoo.WebApp b/Kyoo.WebApp index d3a860fa..a0f8fe4d 160000 --- a/Kyoo.WebApp +++ b/Kyoo.WebApp @@ -1 +1 @@ -Subproject commit d3a860fa8ffccade9e3b17022482e11c9a18303e +Subproject commit a0f8fe4de48a0f0770646d6052a09c551b6442dd