diff --git a/src/Kyoo.Abstractions/Models/Utils/Constants.cs b/src/Kyoo.Abstractions/Models/Utils/Constants.cs
index 7f857df1..1640d779 100644
--- a/src/Kyoo.Abstractions/Models/Utils/Constants.cs
+++ b/src/Kyoo.Abstractions/Models/Utils/Constants.cs
@@ -31,25 +31,30 @@ namespace Kyoo.Abstractions.Models.Utils
///
public const int AlternativeRoute = 1;
+ ///
+ /// A group name for . It should be used for endpoints used by users.
+ ///
+ public const string UsersGroup = "0:Users";
+
///
/// A group name for . It should be used for main resources of kyoo.
///
- public const string ResourcesGroup = "0:Resources";
+ public const string ResourcesGroup = "1:Resources";
///
/// A group name for .
/// It should be used for sub resources of kyoo that help define the main resources.
///
- public const string MetadataGroup = "1:Metadata";
+ public const string MetadataGroup = "2:Metadata";
///
/// A group name for . It should be used for endpoints useful for playback.
///
- public const string WatchGroup = "2:Watch";
+ public const string WatchGroup = "3:Watch";
///
/// A group name for . It should be used for endpoints used by admins.
///
- public const string AdminGroup = "3:Admin";
+ public const string AdminGroup = "4:Admin";
}
}
diff --git a/src/Kyoo.Authentication/AuthenticationModule.cs b/src/Kyoo.Authentication/AuthenticationModule.cs
index 8b9d7e62..0b87af6d 100644
--- a/src/Kyoo.Authentication/AuthenticationModule.cs
+++ b/src/Kyoo.Authentication/AuthenticationModule.cs
@@ -47,7 +47,7 @@ namespace Kyoo.Authentication
public string Name => "Authentication";
///
- public string Description => "Enable an authentication/permission system for Kyoo (via Jwt or ApKeys).";
+ public string Description => "Enable an authentication/permission system for Kyoo (via Jwt or ApiKeys).";
///
public Dictionary Configuration => new()
@@ -62,7 +62,7 @@ namespace Kyoo.Authentication
private readonly IConfiguration _configuration;
///
- /// Create a new authentication module instance and use the given configuration and environment.
+ /// Create a new authentication module instance and use the given configuration.
///
/// The configuration to use
public AuthenticationModule(IConfiguration configuration)
diff --git a/src/Kyoo.Authentication/Controllers/ITokenController.cs b/src/Kyoo.Authentication/Controllers/ITokenController.cs
index 1bb29c03..f0ae7670 100644
--- a/src/Kyoo.Authentication/Controllers/ITokenController.cs
+++ b/src/Kyoo.Authentication/Controllers/ITokenController.cs
@@ -22,33 +22,34 @@ using JetBrains.Annotations;
using Kyoo.Abstractions.Models;
using Microsoft.IdentityModel.Tokens;
-namespace Kyoo.Authentication;
-
-///
-/// The service that controls jwt creation and validation.
-///
-public interface ITokenController
+namespace Kyoo.Authentication
{
///
- /// Create a new access token for the given user.
+ /// The service that controls jwt creation and validation.
///
- /// The user to create a token for.
- /// When this token will expire.
- /// A new, valid access token.
- string CreateAccessToken([NotNull] User user, out TimeSpan expireIn);
+ public interface ITokenController
+ {
+ ///
+ /// Create a new access token for the given user.
+ ///
+ /// The user to create a token for.
+ /// When this token will expire.
+ /// A new, valid access token.
+ string CreateAccessToken([NotNull] User user, out TimeSpan expireIn);
- ///
- /// Create a new refresh token for the given user.
- ///
- /// The user to create a token for.
- /// A new, valid refresh token.
- Task CreateRefreshToken([NotNull] User user);
+ ///
+ /// Create a new refresh token for the given user.
+ ///
+ /// The user to create a token for.
+ /// A new, valid refresh token.
+ Task CreateRefreshToken([NotNull] User user);
- ///
- /// Check if the given refresh token is valid and if it is, retrieve the id of the user this token belongs to.
- ///
- /// The refresh token to validate.
- /// The given refresh token is not valid.
- /// The id of the token's user.
- int GetRefreshTokenUserID(string refreshToken);
+ ///
+ /// Check if the given refresh token is valid and if it is, retrieve the id of the user this token belongs to.
+ ///
+ /// The refresh token to validate.
+ /// The given refresh token is not valid.
+ /// The id of the token's user.
+ int GetRefreshTokenUserID(string refreshToken);
+ }
}
diff --git a/src/Kyoo.Authentication/Kyoo.Authentication.csproj b/src/Kyoo.Authentication/Kyoo.Authentication.csproj
index bf1ebbef..0c3f072d 100644
--- a/src/Kyoo.Authentication/Kyoo.Authentication.csproj
+++ b/src/Kyoo.Authentication/Kyoo.Authentication.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs b/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs
index 776ba2a7..59cf66b1 100644
--- a/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs
+++ b/src/Kyoo.Authentication/Models/DTO/RegisterRequest.cs
@@ -20,6 +20,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Models;
using Kyoo.Utils;
+using BCryptNet = BCrypt.Net.BCrypt;
namespace Kyoo.Authentication.Models.DTO
{
@@ -56,7 +57,7 @@ namespace Kyoo.Authentication.Models.DTO
{
Slug = Utility.ToSlug(Username),
Username = Username,
- Password = Password,
+ Password = BCryptNet.HashPassword(Password),
Email = Email,
ExtraData = new Dictionary()
};
diff --git a/src/Kyoo.Authentication/Models/JwtToken.cs b/src/Kyoo.Authentication/Models/JwtToken.cs
index e0676203..5e885a17 100644
--- a/src/Kyoo.Authentication/Models/JwtToken.cs
+++ b/src/Kyoo.Authentication/Models/JwtToken.cs
@@ -20,39 +20,40 @@ using System;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
-namespace Kyoo.Authentication;
-
-///
-/// A container representing the response of a login or token refresh.
-///
-public class JwtToken
+namespace Kyoo.Authentication
{
///
- /// The type of this token (always a Bearer).
+ /// A container representing the response of a login or token refresh.
///
- [JsonProperty("token_token")]
- [JsonPropertyName("token_type")]
- public string TokenType => "Bearer";&é"bbbbR"
+ public class JwtToken
+ {
+ ///
+ /// The type of this token (always a Bearer).
+ ///
+ [JsonProperty("token_token")]
+ [JsonPropertyName("token_type")]
+ public string TokenType => "Bearer";
- ///
- /// The access token used to authorize requests.
- ///
- [JsonProperty("access_token")]
- [JsonPropertyName("access_token")]
- public string AccessToken { get; set; }p
+ ///
+ /// The access token used to authorize requests.
+ ///
+ [JsonProperty("access_token")]
+ [JsonPropertyName("access_token")]
+ public string AccessToken { get; set; }
- ///
- /// The refresh token used to retrieve a new access/refresh token when the access token has expired.
- ///
- [JsonProperty("refresh_token")]
- [JsonPropertyName("refresh_token")]
- public string RefreshToken { get; set; }
+ ///
+ /// The refresh token used to retrieve a new access/refresh token when the access token has expired.
+ ///
+ [JsonProperty("refresh_token")]
+ [JsonPropertyName("refresh_token")]
+ public string RefreshToken { get; set; }
- ///
- /// The date when the access token will expire. After this date, the refresh token should be used to retrieve.
- /// a new token.cs
- ///
- [JsonProperty("expire_in")]
- [JsonPropertyName("expire_in")]
- public TimeSpan ExpireIn { get; set; }
+ ///
+ /// When the access token will expire. After this tume, the refresh token should be used to retrieve.
+ /// a new token.cs
+ ///
+ [JsonProperty("expire_in")]
+ [JsonPropertyName("expire_in")]
+ public TimeSpan ExpireIn { get; set; }
+ }
}
diff --git a/src/Kyoo.Authentication/Views/AccountApi.cs b/src/Kyoo.Authentication/Views/AccountApi.cs
deleted file mode 100644
index 2957c73f..00000000
--- a/src/Kyoo.Authentication/Views/AccountApi.cs
+++ /dev/null
@@ -1,212 +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 .
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security.Claims;
-using System.Threading.Tasks;
-using Kyoo.Abstractions.Controllers;
-using Kyoo.Abstractions.Models;
-using Kyoo.Authentication;
-using Kyoo.Authentication.Models.DTO;
-using Kyoo.Utils;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Options;
-using Microsoft.IdentityModel.Tokens;
-using BCryptNet = BCrypt.Net.BCrypt;
-
-namespace Amadeus.Server.Views.Auth;
-
-///
-/// Sign in, Sign up or refresh tokens.
-///
-[ApiController]
-[Route("auth")]
-public class AuthView : ControllerBase
-{
- ///
- /// The repository used to check if the user exists.
- ///
- private readonly IUserRepository _users;
-
- ///
- /// The token generator.
- ///
- private readonly ITokenController _token;
-
- ///
- /// Create a new .
- ///
- /// The repository used to check if the user exists.
- /// The token generator.
- public AuthView(IUserRepository users, ITokenController token)
- {
- _users = users;
- _token = token;
- }
-
- ///
- /// Login.
- ///
- ///
- /// Login as a user and retrieve an access and a refresh token.
- ///
- /// The body of the request.
- /// A new access and a refresh token.
- /// The user and password does not match.
- [HttpPost("login")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- public async Task> Login([FromBody] LoginRequest request)
- {
- User user = (await _users.GetAll()).FirstOrDefault(x => x.Username == request.Username);
- if (user != null && BCryptNet.Verify(request.Password, user.Password))
- {
- return new JwtToken
- {
- AccessToken = _token.CreateAccessToken(user, out TimeSpan expireDate),
- RefreshToken = await _token.CreateRefreshToken(user),
- ExpireIn = expireDate
- };
- }
- return BadRequest(new { Message = "The user and password does not match." });
- }
-
- ///
- /// Register.
- ///
- ///
- /// Register a new user and get a new access/refresh token for this new user.
- ///
- /// The body of the request.
- /// A new access and a refresh token.
- /// The request is invalid.
- /// A user already exists with this username or email address.
- [HttpPost("register")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- [ProducesResponseType(StatusCodes.Status409Conflict)]
- public async Task> Register([FromBody] RegisterRequest request)
- {
- User user = request.ToUser();
- user.Password = BCryptNet.HashPassword(request.Password);
- try
- {
- await _users.Create(user);
- }
- catch (DuplicateField)
- {
- return Conflict(new { Message = "A user already exists with this username." });
- }
-
-
- return new JwtToken
- {
- AccessToken = _token.CreateAccessToken(user, out TimeSpan expireDate),
- RefreshToken = await _token.CreateRefreshToken(user),
- ExpireIn = expireDate
- };
- }
-
- ///
- /// Refresh a token.
- ///
- ///
- /// Refresh an access token using the given refresh token. A new access and refresh token are generated.
- /// The old refresh token should not be used anymore.
- ///
- /// A valid refresh token.
- /// A new access and refresh token.
- /// The given refresh token is invalid.
- [HttpGet("refresh")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest)]
- public async Task> Refresh([FromQuery] string token)
- {
- try
- {
- int userId = _token.GetRefreshTokenUserID(token);
- User user = await _users.GetById(userId);
- return new JwtToken
- {
- AccessToken = _token.CreateAccessToken(user, out TimeSpan expireDate),
- RefreshToken = await _token.CreateRefreshToken(user),
- ExpireIn = expireDate
- };
- }
- catch (ElementNotFound)
- {
- return BadRequest(new { Message = "Invalid refresh token." });
- }
- catch (SecurityTokenException ex)
- {
- return BadRequest(new { ex.Message });
- }
- }
-
- [HttpGet("anilist")]
- [ProducesResponseType(StatusCodes.Status302Found)]
- public IActionResult AniListLogin([FromQuery] Uri redirectUrl, [FromServices] IOptions anilist)
- {
- Dictionary query = new()
- {
- ["client_id"] = anilist.Value.ClientID,
- ["redirect_uri"] = redirectUrl.ToString(),
- ["response_type"] = "code"
- };
- return Redirect($"https://anilist.co/api/v2/oauth/authorize{query.ToQueryString()}");
- }
-
- [HttpPost("link/anilist")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
- public async Task> AniListLink([FromQuery] string code, [FromServices] AniListService anilist)
- {
- // TODO prevent link if someone has already linked this account.
- // TODO allow unlink.
- if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
- return BadRequest("Invalid access token");
- return await anilist.LinkAccount(userID, code);
- }
-
- [HttpPost("login/anilist")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> AniListLogin([FromQuery] string code, [FromServices] AniListService anilist)
- {
- User user = await anilist.Login(code);
- return new JwtToken
- {
- AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn),
- RefreshToken = await _token.CreateRefreshToken(user),
- ExpireIn = expireIn
- };
- }
-
- [HttpGet("me")]
- [Authorize]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> GetMe()
- {
- if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
- return BadRequest("Invalid access token");
- return await _users.GetById(userID);
- }
-}
diff --git a/src/Kyoo.Authentication/Views/AuthApi.cs b/src/Kyoo.Authentication/Views/AuthApi.cs
new file mode 100644
index 00000000..8c2bb376
--- /dev/null
+++ b/src/Kyoo.Authentication/Views/AuthApi.cs
@@ -0,0 +1,174 @@
+// 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;
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Kyoo.Abstractions.Controllers;
+using Kyoo.Abstractions.Models;
+using Kyoo.Abstractions.Models.Attributes;
+using Kyoo.Abstractions.Models.Exceptions;
+using Kyoo.Abstractions.Models.Utils;
+using Kyoo.Authentication.Models.DTO;
+using Kyoo.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Microsoft.IdentityModel.Tokens;
+using static Kyoo.Abstractions.Models.Utils.Constants;
+using BCryptNet = BCrypt.Net.BCrypt;
+
+namespace Kyoo.Authentication.Views
+{
+ ///
+ /// Sign in, Sign up or refresh tokens.
+ ///
+ [ApiController]
+ [Route("api/auth")]
+ [ApiDefinition("Authentication", Group = UsersGroup)]
+ public class AuthApi : ControllerBase
+ {
+ ///
+ /// The repository to handle users.
+ ///
+ private readonly IUserRepository _users;
+
+ ///
+ /// The token generator.
+ ///
+ private readonly ITokenController _token;
+
+ ///
+ /// Create a new .
+ ///
+ /// The repository used to check if the user exists.
+ /// The token generator.
+ public AuthApi(IUserRepository users, ITokenController token)
+ {
+ _users = users;
+ _token = token;
+ }
+
+ ///
+ /// Login.
+ ///
+ ///
+ /// Login as a user and retrieve an access and a refresh token.
+ ///
+ /// The body of the request.
+ /// A new access and a refresh token.
+ /// The user and password does not match.
+ [HttpPost("login")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
+ public async Task> Login([FromBody] LoginRequest request)
+ {
+ User user = await _users.GetOrDefault(x => x.Username == request.Username);
+ if (user == null || !BCryptNet.Verify(request.Password, user.Password))
+ return BadRequest(new RequestError("The user and password does not match."));
+
+ return new JwtToken
+ {
+ AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn),
+ RefreshToken = await _token.CreateRefreshToken(user),
+ ExpireIn = expireIn
+ };
+ }
+
+ ///
+ /// Register.
+ ///
+ ///
+ /// Register a new user and get a new access/refresh token for this new user.
+ ///
+ /// The body of the request.
+ /// A new access and a refresh token.
+ /// The request is invalid.
+ /// A user already exists with this username or email address.
+ [HttpPost("register")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
+ [ProducesResponseType(StatusCodes.Status409Conflict, Type = typeof(RequestError))]
+ public async Task> Register([FromBody] RegisterRequest request)
+ {
+ User user = request.ToUser();
+ try
+ {
+ await _users.Create(user);
+ }
+ catch (DuplicatedItemException)
+ {
+ return Conflict(new RequestError("A user already exists with this username."));
+ }
+
+ return new JwtToken
+ {
+ AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn),
+ RefreshToken = await _token.CreateRefreshToken(user),
+ ExpireIn = expireIn
+ };
+ }
+
+ ///
+ /// Refresh a token.
+ ///
+ ///
+ /// Refresh an access token using the given refresh token. A new access and refresh token are generated.
+ ///
+ /// A valid refresh token.
+ /// A new access and refresh token.
+ /// The given refresh token is invalid.
+ [HttpGet("refresh")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
+ public async Task> Refresh([FromQuery] string token)
+ {
+ try
+ {
+ int userId = _token.GetRefreshTokenUserID(token);
+ User user = await _users.Get(userId);
+ return new JwtToken
+ {
+ AccessToken = _token.CreateAccessToken(user, out TimeSpan expireIn),
+ RefreshToken = await _token.CreateRefreshToken(user),
+ ExpireIn = expireIn
+ };
+ }
+ catch (ItemNotFoundException)
+ {
+ return BadRequest(new RequestError("Invalid refresh token."));
+ }
+ catch (SecurityTokenException ex)
+ {
+ return BadRequest(new RequestError(ex.Message));
+ }
+ }
+
+ [HttpGet("me")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> GetMe()
+ {
+ if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
+ return BadRequest("Invalid access token");
+ return await _users.GetById(userID);
+ }
+ }
+}