Handling autologin and logout

This commit is contained in:
Zoe Roux 2021-05-09 15:58:55 +02:00
parent d7972704dd
commit 440e5f4f14
7 changed files with 42 additions and 104 deletions

View File

@ -87,6 +87,12 @@ namespace Kyoo.Authentication
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<User>()
// .AddSignInManager()
// .AddDefaultTokenProviders()
@ -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"};

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Security.Claims;
using IdentityModel;
using IdentityServer4;
using Kyoo.Models;
namespace Kyoo.Authentication
@ -26,14 +27,16 @@ namespace Kyoo.Authentication
}
/// <summary>
/// Convert a user to a ClaimsPrincipal.
/// Convert a user to an <see cref="IdentityServerUser"/>.
/// </summary>
/// <param name="user">The user to convert</param>
/// <returns>A ClaimsPrincipal representing the user</returns>
public static ClaimsPrincipal ToPrincipal(this User user)
/// <returns>The corresponding identity server user.</returns>
public static IdentityServerUser ToIdentityUser(this User user)
{
ClaimsIdentity id = new (user.GetClaims());
return new ClaimsPrincipal(id);
return new(user.ID.ToString())
{
DisplayName = user.Username
};
}
}
}

View File

@ -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<User>, IPersistedGrantDbContext
// {
// private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
//
// public IdentityDatabase(DbContextOptions<IdentityDatabase> options, IOptions<OperationalStoreOptions> operationalStoreOptions)
// : base(options)
// {
// _operationalStoreOptions = operationalStoreOptions;
// }
//
// public DbSet<User> Accounts { get; set; }
//
// protected override void OnModelCreating(ModelBuilder modelBuilder)
// {
// base.OnModelCreating(modelBuilder);
// modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
//
// modelBuilder.Entity<User>().ToTable("User");
// modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRole");
// modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogin");
// modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaim");
// modelBuilder.Entity<IdentityRole>().ToTable("UserRoles");
// modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("UserRoleClaim");
// modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserToken");
// }
//
// public Task<int> SaveChangesAsync() => base.SaveChangesAsync();
//
// public DbSet<PersistedGrant> PersistedGrants { get; set; }
// public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
//
// }
// }

View File

@ -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;
// }
// }
// }

View File

@ -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
/// </summary>
private readonly IUserRepository _users;
/// <summary>
/// The identity server interaction service to login users.
/// </summary>
// private readonly IIdentityServerInteractionService _interaction;
/// <summary>
/// A file manager to send profile pictures
/// </summary>
private readonly IFileManager _files;
// private readonly SignInManager<User> _signInManager;
/// <summary>
/// Options about authentication. Those options are monitored and reloads are supported.
/// </summary>
@ -54,20 +47,15 @@ namespace Kyoo.Authentication.Views
/// Create a new <see cref="AccountApi"/> handle to handle login/users requests.
/// </summary>
/// <param name="users">The user repository to create and manage users</param>
/// <param name="interaction">The identity server interaction service to login users.</param>
/// <param name="files">A file manager to send profile pictures</param>
/// <param name="options">Authentication options (this may be hot reloaded)</param>
public AccountApi(IUserRepository users,
// IIdentityServerInteractionService interaction,
IFileManager files,
IOptions<AuthenticationOption> options)
//, SignInManager<User> signInManager)
{
_users = users;
// _interaction = interaction;
_files = files;
_options = options;
// _signInManager = signInManager;
}
@ -119,7 +107,6 @@ namespace Kyoo.Authentication.Views
[HttpPost("login")]
public async Task<IActionResult> 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<IActionResult> Logout()
{
// await _signInManager.SignOutAsync();
await HttpContext.SignOutAsync();
return Ok();
}
// TODO check with the extension method
/// <inheritdoc />
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)));
}
/// <inheritdoc />
public async Task IsActiveAsync(IsActiveContext context)
{
User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
context.IsActive = user != null;
}
/// <summary>
/// Get the user's profile picture.
/// </summary>
/// <param name="slug">The user slug</param>
/// <returns>The profile picture of the user or 404 if not found</returns>
[HttpGet("picture/{slug}")]
public async Task<IActionResult> GetPicture(string slug)
{
@ -197,6 +186,11 @@ namespace Kyoo.Authentication.Views
return _files.FileResult(path);
}
/// <summary>
/// Update profile information (email, username, profile picture...)
/// </summary>
/// <param name="data">The new information</param>
/// <returns>The edited user</returns>
[HttpPut]
[Authorize]
public async Task<ActionResult<User>> Update([FromForm] AccountUpdateRequest data)
@ -218,6 +212,10 @@ namespace Kyoo.Authentication.Views
return await _users.Edit(user, false);
}
/// <summary>
/// Get permissions for a non connected user.
/// </summary>
/// <returns>The list of permissions of a default user.</returns>
[HttpGet("permissions")]
public ActionResult<IEnumerable<string>> GetDefaultPermissions()
{

View File

@ -19,7 +19,8 @@ namespace Kyoo.Controllers
/// or proxy them from a distant server
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="path">The path of the file.</param>
/// <param name="rangeSupport">

@ -1 +1 @@
Subproject commit d3a860fa8ffccade9e3b17022482e11c9a18303e
Subproject commit a0f8fe4de48a0f0770646d6052a09c551b6442dd