mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Create robot tests for login and register
This commit is contained in:
parent
ad57df45d7
commit
14d23af0fd
@ -70,7 +70,6 @@ csharp_indent_case_contents = true
|
|||||||
csharp_indent_switch_labels = true
|
csharp_indent_switch_labels = true
|
||||||
# Modifiers
|
# Modifiers
|
||||||
dotnet_style_readonly_field = true:suggestion
|
dotnet_style_readonly_field = true:suggestion
|
||||||
csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
|
|
||||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||||
# Naming style
|
# Naming style
|
||||||
dotnet_naming_symbols.privates.applicable_kinds = property,method,event,delegate
|
dotnet_naming_symbols.privates.applicable_kinds = property,method,event,delegate
|
||||||
|
@ -10,7 +10,7 @@ title: Build
|
|||||||
To develop for Kyoo, you will need the **.NET 5.0 SDK**, **node** and **npm** for the webapp. If you want to build the transcoder, you will also need a cmake compatible environment.
|
To develop for Kyoo, you will need the **.NET 5.0 SDK**, **node** and **npm** for the webapp. If you want to build the transcoder, you will also need a cmake compatible environment.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
To run the development server, simply open the .sln file with your favorite C# IDE (like Jetbrain's Rider or Visual Studio) and press run or you can use the CLI and use the ```dotnet run dotnet run -p src/Kyoo.Host.Console --launch-profile "Console"``` command.
|
To run the development server, simply open the .sln file with your favorite C# IDE (like Jetbrain's Rider or Visual Studio) and press run or you can use the CLI and use the ```dotnet run --project src/Kyoo.Host.Console --launch-profile "Console"``` command.
|
||||||
To pack the application, run the ```dotnet publish -c Release -o <build_path> Kyoo.Host.Console``` command. This will build the server, the webapp and the transcoder and output files in the <build_path> directory.
|
To pack the application, run the ```dotnet publish -c Release -o <build_path> Kyoo.Host.Console``` command. This will build the server, the webapp and the transcoder and output files in the <build_path> directory.
|
||||||
|
|
||||||
## Skipping parts
|
## Skipping parts
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
|
||||||
namespace Kyoo.Abstractions.Models
|
namespace Kyoo.Abstractions.Models
|
||||||
{
|
{
|
||||||
@ -62,11 +63,13 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of shows the user has finished.
|
/// The list of shows the user has finished.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[SerializeIgnore]
|
||||||
public ICollection<Show> Watched { get; set; }
|
public ICollection<Show> Watched { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of episodes the user is watching (stopped in progress or the next episode of the show)
|
/// The list of episodes the user is watching (stopped in progress or the next episode of the show)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[SerializeIgnore]
|
||||||
public ICollection<WatchedEpisode> CurrentlyWatching { get; set; }
|
public ICollection<WatchedEpisode> CurrentlyWatching { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,14 +74,16 @@ namespace Kyoo.Authentication
|
|||||||
public void Configure(ContainerBuilder builder)
|
public void Configure(ContainerBuilder builder)
|
||||||
{
|
{
|
||||||
builder.RegisterType<PermissionValidator>().As<IPermissionValidator>().SingleInstance();
|
builder.RegisterType<PermissionValidator>().As<IPermissionValidator>().SingleInstance();
|
||||||
|
builder.RegisterType<TokenController>().As<ITokenController>().SingleInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Configure(IServiceCollection services)
|
public void Configure(IServiceCollection services)
|
||||||
{
|
{
|
||||||
Uri publicUrl = _configuration.GetPublicUrl();
|
Uri publicUrl = _configuration.GetPublicUrl();
|
||||||
AuthenticationOption jwt = new();
|
AuthenticationOption jwt = ConfigurationBinder.Get<AuthenticationOption>(
|
||||||
_configuration.GetSection(AuthenticationOption.Path).Bind(jwt);
|
_configuration.GetSection(AuthenticationOption.Path)
|
||||||
|
);
|
||||||
|
|
||||||
// TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos)
|
// TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos)
|
||||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
|
@ -72,7 +72,7 @@ namespace Kyoo.Authentication
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The permission to validate.
|
/// The permission to validate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly string _permission;
|
private readonly string? _permission;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The kind of permission needed.
|
/// The kind of permission needed.
|
||||||
@ -133,7 +133,7 @@ namespace Kyoo.Authentication
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||||
{
|
{
|
||||||
string permission = _permission;
|
string? permission = _permission;
|
||||||
Kind? kind = _kind;
|
Kind? kind = _kind;
|
||||||
|
|
||||||
if (permission == null || kind == null)
|
if (permission == null || kind == null)
|
||||||
|
@ -31,119 +31,120 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace Kyoo.Authentication;
|
namespace Kyoo.Authentication
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The service that controls jwt creation and validation.
|
|
||||||
/// </summary>
|
|
||||||
public class TokenController : ITokenController
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The options that this controller will use.
|
/// The service that controls jwt creation and validation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IOptions<AuthenticationOption> _options;
|
public class TokenController : ITokenController
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The configuration used to retrieve the public URL of kyoo.
|
|
||||||
/// </summary>
|
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new <see cref="TokenController"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options">The options that this controller will use.</param>
|
|
||||||
/// <param name="configuration">The configuration used to retrieve the public URL of kyoo.</param>
|
|
||||||
public TokenController(IOptions<AuthenticationOption> options, IConfiguration configuration)
|
|
||||||
{
|
{
|
||||||
_options = options;
|
/// <summary>
|
||||||
_configuration = configuration;
|
/// The options that this controller will use.
|
||||||
}
|
/// </summary>
|
||||||
|
private readonly IOptions<AuthenticationOption> _options;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
public string CreateAccessToken(User user, out TimeSpan expireIn)
|
/// The configuration used to retrieve the public URL of kyoo.
|
||||||
{
|
/// </summary>
|
||||||
if (user == null)
|
private readonly IConfiguration _configuration;
|
||||||
throw new ArgumentNullException(nameof(user));
|
|
||||||
|
|
||||||
expireIn = new TimeSpan(1, 0, 0);
|
/// <summary>
|
||||||
|
/// Create a new <see cref="TokenController"/>.
|
||||||
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
/// </summary>
|
||||||
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
|
/// <param name="options">The options that this controller will use.</param>
|
||||||
string permissions = user.Permissions != null
|
/// <param name="configuration">The configuration used to retrieve the public URL of kyoo.</param>
|
||||||
? string.Join(',', user.Permissions)
|
public TokenController(IOptions<AuthenticationOption> options, IConfiguration configuration)
|
||||||
: string.Empty;
|
|
||||||
List<Claim> claims = new()
|
|
||||||
{
|
{
|
||||||
new Claim(ClaimTypes.NameIdentifier, user.ID.ToString(CultureInfo.InvariantCulture)),
|
_options = options;
|
||||||
new Claim(ClaimTypes.Name, user.Username),
|
_configuration = configuration;
|
||||||
new Claim(ClaimTypes.Role, permissions),
|
}
|
||||||
new Claim("type", "access")
|
|
||||||
};
|
|
||||||
if (user.Email != null)
|
|
||||||
claims.Add(new Claim(ClaimTypes.Email, user.Email));
|
|
||||||
JwtSecurityToken token = new(
|
|
||||||
signingCredentials: credential,
|
|
||||||
issuer: _configuration.GetPublicUrl().ToString(),
|
|
||||||
audience: _configuration.GetPublicUrl().ToString(),
|
|
||||||
claims: claims,
|
|
||||||
expires: DateTime.UtcNow.Add(expireIn)
|
|
||||||
);
|
|
||||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<string> CreateRefreshToken(User user)
|
public string CreateAccessToken(User user, out TimeSpan expireIn)
|
||||||
{
|
{
|
||||||
if (user == null)
|
if (user == null)
|
||||||
throw new ArgumentNullException(nameof(user));
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
|
||||||
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
expireIn = new TimeSpan(1, 0, 0);
|
||||||
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
|
|
||||||
JwtSecurityToken token = new(
|
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
||||||
signingCredentials: credential,
|
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
|
||||||
issuer: _configuration.GetPublicUrl().ToString(),
|
string permissions = user.Permissions != null
|
||||||
audience: _configuration.GetPublicUrl().ToString(),
|
? string.Join(',', user.Permissions)
|
||||||
claims: new[]
|
: string.Empty;
|
||||||
|
List<Claim> claims = new()
|
||||||
{
|
{
|
||||||
new Claim(ClaimTypes.NameIdentifier, user.ID.ToString(CultureInfo.InvariantCulture)),
|
new Claim(ClaimTypes.NameIdentifier, user.ID.ToString(CultureInfo.InvariantCulture)),
|
||||||
new Claim("guid", Guid.NewGuid().ToString()),
|
new Claim(ClaimTypes.Name, user.Username),
|
||||||
new Claim("type", "refresh")
|
new Claim(ClaimTypes.Role, permissions),
|
||||||
},
|
new Claim("type", "access")
|
||||||
expires: DateTime.UtcNow.AddYears(1)
|
};
|
||||||
);
|
if (user.Email != null)
|
||||||
// TODO refresh keys are unique (thanks to the guid) but we could store them in DB to invalidate them if requested by the user.
|
claims.Add(new Claim(ClaimTypes.Email, user.Email));
|
||||||
return Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token));
|
JwtSecurityToken token = new(
|
||||||
}
|
signingCredentials: credential,
|
||||||
|
issuer: _configuration.GetPublicUrl().ToString(),
|
||||||
|
audience: _configuration.GetPublicUrl().ToString(),
|
||||||
|
claims: claims,
|
||||||
|
expires: DateTime.UtcNow.Add(expireIn)
|
||||||
|
);
|
||||||
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int GetRefreshTokenUserID(string refreshToken)
|
public Task<string> CreateRefreshToken(User user)
|
||||||
{
|
|
||||||
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
|
||||||
JwtSecurityTokenHandler tokenHandler = new();
|
|
||||||
ClaimsPrincipal principal;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
principal = tokenHandler.ValidateToken(refreshToken, new TokenValidationParameters
|
if (user == null)
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
|
||||||
|
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
||||||
|
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
|
||||||
|
JwtSecurityToken token = new(
|
||||||
|
signingCredentials: credential,
|
||||||
|
issuer: _configuration.GetPublicUrl().ToString(),
|
||||||
|
audience: _configuration.GetPublicUrl().ToString(),
|
||||||
|
claims: new[]
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, user.ID.ToString(CultureInfo.InvariantCulture)),
|
||||||
|
new Claim("guid", Guid.NewGuid().ToString()),
|
||||||
|
new Claim("type", "refresh")
|
||||||
|
},
|
||||||
|
expires: DateTime.UtcNow.AddYears(1)
|
||||||
|
);
|
||||||
|
// TODO refresh keys are unique (thanks to the guid) but we could store them in DB to invalidate them if requested by the user.
|
||||||
|
return Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int GetRefreshTokenUserID(string refreshToken)
|
||||||
|
{
|
||||||
|
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
|
||||||
|
JwtSecurityTokenHandler tokenHandler = new();
|
||||||
|
ClaimsPrincipal principal;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
ValidateIssuer = true,
|
principal = tokenHandler.ValidateToken(refreshToken, new TokenValidationParameters
|
||||||
ValidateAudience = true,
|
{
|
||||||
ValidateIssuerSigningKey = true,
|
ValidateIssuer = true,
|
||||||
ValidateLifetime = true,
|
ValidateAudience = true,
|
||||||
ValidIssuer = _configuration.GetPublicUrl().ToString(),
|
ValidateIssuerSigningKey = true,
|
||||||
ValidAudience = _configuration.GetPublicUrl().ToString(),
|
ValidateLifetime = true,
|
||||||
IssuerSigningKey = key
|
ValidIssuer = _configuration.GetPublicUrl().ToString(),
|
||||||
}, out SecurityToken _);
|
ValidAudience = _configuration.GetPublicUrl().ToString(),
|
||||||
}
|
IssuerSigningKey = key
|
||||||
catch (Exception ex)
|
}, out SecurityToken _);
|
||||||
{
|
}
|
||||||
throw new SecurityTokenException(ex.Message);
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
|
throw new SecurityTokenException(ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
if (principal.Claims.First(x => x.Type == "type").Value != "refresh")
|
if (principal.Claims.First(x => x.Type == "type").Value != "refresh")
|
||||||
throw new SecurityTokenException("Invalid token type. The token should be a refresh token.");
|
throw new SecurityTokenException("Invalid token type. The token should be a refresh token.");
|
||||||
Claim identifier = principal.Claims.First(x => x.Type == ClaimTypes.NameIdentifier);
|
Claim identifier = principal.Claims.First(x => x.Type == ClaimTypes.NameIdentifier);
|
||||||
if (int.TryParse(identifier.Value, out int id))
|
if (int.TryParse(identifier.Value, out int id))
|
||||||
return id;
|
return id;
|
||||||
throw new SecurityTokenException("Token not associated to any user.");
|
throw new SecurityTokenException("Token not associated to any user.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
@ -34,7 +35,8 @@ namespace Kyoo.Authentication
|
|||||||
/// <returns>The list of permissions</returns>
|
/// <returns>The list of permissions</returns>
|
||||||
public static ICollection<string> GetPermissions(this ClaimsPrincipal user)
|
public static ICollection<string> GetPermissions(this ClaimsPrincipal user)
|
||||||
{
|
{
|
||||||
return user.Claims.FirstOrDefault(x => x.Type == "permissions")?.Value.Split(',');
|
return user.Claims.FirstOrDefault(x => x.Type == "permissions")?.Value.Split(',')
|
||||||
|
?? Array.Empty<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<LoginRoot>../Kyoo.WebLogin/</LoginRoot>
|
<LoginRoot>../Kyoo.WebLogin/</LoginRoot>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace Kyoo.Authentication.Models.DTO
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A model only used on account update requests.
|
|
||||||
/// </summary>
|
|
||||||
public class AccountUpdateRequest
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The new email address of the user
|
|
||||||
/// </summary>
|
|
||||||
[EmailAddress(ErrorMessage = "The email is invalid.")]
|
|
||||||
public string Email { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The new username of the user.
|
|
||||||
/// </summary>
|
|
||||||
[MinLength(4, ErrorMessage = "The username must have at least 4 characters")]
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The picture icon.
|
|
||||||
/// </summary>
|
|
||||||
public IFormFile Picture { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -34,13 +34,14 @@ namespace Kyoo.Authentication.Models.DTO
|
|||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should the user stay logged in? If true a cookie will be put.
|
/// Initializes a new instance of the <see cref="LoginRequest"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool StayLoggedIn { get; set; }
|
/// <param name="username">The user's username.</param>
|
||||||
|
/// <param name="password">The user's password.</param>
|
||||||
/// <summary>
|
public LoginRequest(string username, string password)
|
||||||
/// The return url of the login flow.
|
{
|
||||||
/// </summary>
|
Username = username;
|
||||||
public string ReturnURL { get; set; }
|
Password = password;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
namespace Kyoo.Authentication.Models.DTO
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A model to represent an otac request
|
|
||||||
/// </summary>
|
|
||||||
public class OtacRequest
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The One Time Access Code
|
|
||||||
/// </summary>
|
|
||||||
public string Otac { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Should the user stay logged
|
|
||||||
/// </summary>
|
|
||||||
public bool StayLoggedIn { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
namespace Kyoo.Authentication.Models.DTO
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A one time access token
|
|
||||||
/// </summary>
|
|
||||||
public class OtacResponse
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The One Time Access Token that allow one to connect to an account without typing a password or without
|
|
||||||
/// any kind of verification. This is valid only one time and only for a short period of time.
|
|
||||||
/// </summary>
|
|
||||||
public string OTAC { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new <see cref="OtacResponse"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="otac">The one time access token.</param>
|
|
||||||
public OtacResponse(string otac)
|
|
||||||
{
|
|
||||||
OTAC = otac;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -44,9 +44,22 @@ namespace Kyoo.Authentication.Models.DTO
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user's password.
|
/// The user's password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MinLength(8, ErrorMessage = "The password must have at least {1} characters")]
|
[MinLength(4, ErrorMessage = "The password must have at least {1} characters")]
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="RegisterRequest"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="email">The user email address.</param>
|
||||||
|
/// <param name="username">The user's username.</param>
|
||||||
|
/// <param name="password">The user's password.</param>
|
||||||
|
public RegisterRequest(string email, string username, string password)
|
||||||
|
{
|
||||||
|
Email = email;
|
||||||
|
Username = username;
|
||||||
|
Password = password;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert this register request to a new <see cref="User"/> class.
|
/// Convert this register request to a new <see cref="User"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -55,5 +55,18 @@ namespace Kyoo.Authentication
|
|||||||
[JsonProperty("expire_in")]
|
[JsonProperty("expire_in")]
|
||||||
[JsonPropertyName("expire_in")]
|
[JsonPropertyName("expire_in")]
|
||||||
public TimeSpan ExpireIn { get; set; }
|
public TimeSpan ExpireIn { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="JwtToken"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="accessToken">The access token used to authorize requests.</param>
|
||||||
|
/// <param name="refreshToken">The refresh token to retrieve a new access token.</param>
|
||||||
|
/// <param name="expireIn">When the access token will expire.</param>
|
||||||
|
public JwtToken(string accessToken, string refreshToken, TimeSpan expireIn)
|
||||||
|
{
|
||||||
|
AccessToken = accessToken;
|
||||||
|
RefreshToken = refreshToken;
|
||||||
|
ExpireIn = expireIn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,19 +28,24 @@ namespace Kyoo.Authentication.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string Path = "authentication";
|
public const string Path = "authentication";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default jwt secret.
|
||||||
|
/// </summary>
|
||||||
|
public const string DefaultSecret = "jwt-secret";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The secret used to encrypt the jwt.
|
/// The secret used to encrypt the jwt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Secret { get; set; }
|
public string Secret { get; set; } = DefaultSecret;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options for permissions
|
/// Options for permissions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PermissionOption Permissions { get; set; }
|
public PermissionOption Permissions { get; set; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Root path of user's profile pictures.
|
/// Root path of user's profile pictures.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ProfilePicturePath { get; set; }
|
public string ProfilePicturePath { get; set; } = "users/";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,11 +31,11 @@ namespace Kyoo.Authentication.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default permissions that will be given to a non-connected user.
|
/// The default permissions that will be given to a non-connected user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] Default { get; set; }
|
public string[] Default { get; set; } = new[] { "overall.read", "overall.write" };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Permissions applied to a new user.
|
/// Permissions applied to a new user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] NewUser { get; set; }
|
public string[] NewUser { get; set; } = new[] { "overall.read", "overall.write" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,16 @@ namespace Kyoo.Authentication.Views
|
|||||||
_token = token;
|
_token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new Forbidden result from an object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The json value to output on the response.</param>
|
||||||
|
/// <returns>A new forbidden result with the given json object.</returns>
|
||||||
|
public static ObjectResult Forbid(object value)
|
||||||
|
{
|
||||||
|
return new ObjectResult(value) { StatusCode = StatusCodes.Status403Forbidden };
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Login.
|
/// Login.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -71,7 +81,7 @@ namespace Kyoo.Authentication.Views
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="request">The body of the request.</param>
|
/// <param name="request">The body of the request.</param>
|
||||||
/// <returns>A new access and a refresh token.</returns>
|
/// <returns>A new access and a refresh token.</returns>
|
||||||
/// <response code="400">The user and password does not match.</response>
|
/// <response code="403">The user and password does not match.</response>
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
@ -79,14 +89,13 @@ namespace Kyoo.Authentication.Views
|
|||||||
{
|
{
|
||||||
User user = await _users.GetOrDefault(x => x.Username == request.Username);
|
User user = await _users.GetOrDefault(x => x.Username == request.Username);
|
||||||
if (user == null || !BCryptNet.Verify(request.Password, user.Password))
|
if (user == null || !BCryptNet.Verify(request.Password, user.Password))
|
||||||
return BadRequest(new RequestError("The user and password does not match."));
|
return Forbid(new RequestError("The user and password does not match."));
|
||||||
|
|
||||||
return new JwtToken
|
return new JwtToken(
|
||||||
{
|
_token.CreateAccessToken(user, out TimeSpan expireIn),
|
||||||
AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn),
|
await _token.CreateRefreshToken(user),
|
||||||
RefreshToken = await _token.CreateRefreshToken(user),
|
expireIn
|
||||||
ExpireIn = expireIn
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -115,12 +124,11 @@ namespace Kyoo.Authentication.Views
|
|||||||
return Conflict(new RequestError("A user already exists with this username."));
|
return Conflict(new RequestError("A user already exists with this username."));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JwtToken
|
return new JwtToken(
|
||||||
{
|
_token.CreateAccessToken(user, out TimeSpan expireIn),
|
||||||
AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn),
|
await _token.CreateRefreshToken(user),
|
||||||
RefreshToken = await _token.CreateRefreshToken(user),
|
expireIn
|
||||||
ExpireIn = expireIn
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -141,12 +149,11 @@ namespace Kyoo.Authentication.Views
|
|||||||
{
|
{
|
||||||
int userId = _token.GetRefreshTokenUserID(token);
|
int userId = _token.GetRefreshTokenUserID(token);
|
||||||
User user = await _users.Get(userId);
|
User user = await _users.Get(userId);
|
||||||
return new JwtToken
|
return new JwtToken(
|
||||||
{
|
_token.CreateAccessToken(user, out TimeSpan expireIn),
|
||||||
AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn),
|
await _token.CreateRefreshToken(user),
|
||||||
RefreshToken = await _token.CreateRefreshToken(user),
|
expireIn
|
||||||
ExpireIn = expireIn
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
{
|
{
|
||||||
@ -176,5 +183,7 @@ namespace Kyoo.Authentication.Views
|
|||||||
return Forbid();
|
return Forbid();
|
||||||
return await _users.Get(userID);
|
return await _users.Get(userID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add a put to edit the current user.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,8 @@
|
|||||||
|
|
||||||
"authentication": {
|
"authentication": {
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"default": ["overall.read", "overall.write", "overall.create", "overall.delete", "admin.read", "admin.write"],
|
"default": ["overall.read", "overall.write"],
|
||||||
"newUser": ["overall.read", "overall.write", "overall.create", "overall.delete", "admin.read", "admin.write"]
|
"newUser": ["overall.read", "overall.write"]
|
||||||
},
|
},
|
||||||
"profilePicturePath": "users/",
|
"profilePicturePath": "users/",
|
||||||
"secret": "jwt-secret"
|
"secret": "jwt-secret"
|
||||||
|
@ -1,7 +1,18 @@
|
|||||||
*** Settings ***
|
*** Settings ***
|
||||||
Documentation Tests of the /auth route.
|
Documentation Tests of the /auth route.
|
||||||
... Ensures that the user can authenticate on kyoo.
|
... Ensures that the user can authenticate on kyoo.
|
||||||
Resource ${RESOURCES}/rest.resource
|
Resource ../rest.resource
|
||||||
|
|
||||||
|
|
||||||
*** Test Cases ***
|
*** Test Cases ***
|
||||||
|
BadAccount
|
||||||
|
[Documentation] Login fails if user does not exist
|
||||||
|
POST /auth/login {"username": "toto", "password": "tata"}
|
||||||
|
Output
|
||||||
|
Integer response status 403
|
||||||
|
|
||||||
|
Register
|
||||||
|
[Documentation] Create a new user and login in it
|
||||||
|
POST /auth/register {"username": "toto", "password": "tata", "email": "mail@kyoo.moe"}
|
||||||
|
Output
|
||||||
|
Integer response status 403
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
*** Settings ***
|
*** Settings ***
|
||||||
Documentation Common things to handle rest requests
|
Documentation Common things to handle rest requests
|
||||||
Library REST
|
|
||||||
|
Library REST http://localhost:5000/api
|
||||||
|
Loading…
x
Reference in New Issue
Block a user