mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Finishing Login/Register handling
This commit is contained in:
parent
1a534d2325
commit
673cb48b75
@ -31,25 +31,30 @@ namespace Kyoo.Abstractions.Models.Utils
|
||||
/// </summary>
|
||||
public const int AlternativeRoute = 1;
|
||||
|
||||
/// <summary>
|
||||
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints used by users.
|
||||
/// </summary>
|
||||
public const string UsersGroup = "0:Users";
|
||||
|
||||
/// <summary>
|
||||
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for main resources of kyoo.
|
||||
/// </summary>
|
||||
public const string ResourcesGroup = "0:Resources";
|
||||
public const string ResourcesGroup = "1:Resources";
|
||||
|
||||
/// <summary>
|
||||
/// A group name for <see cref="ApiDefinitionAttribute"/>.
|
||||
/// It should be used for sub resources of kyoo that help define the main resources.
|
||||
/// </summary>
|
||||
public const string MetadataGroup = "1:Metadata";
|
||||
public const string MetadataGroup = "2:Metadata";
|
||||
|
||||
/// <summary>
|
||||
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints useful for playback.
|
||||
/// </summary>
|
||||
public const string WatchGroup = "2:Watch";
|
||||
public const string WatchGroup = "3:Watch";
|
||||
|
||||
/// <summary>
|
||||
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints used by admins.
|
||||
/// </summary>
|
||||
public const string AdminGroup = "3:Admin";
|
||||
public const string AdminGroup = "4:Admin";
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ namespace Kyoo.Authentication
|
||||
public string Name => "Authentication";
|
||||
|
||||
/// <inheritdoc />
|
||||
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).";
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, Type> Configuration => new()
|
||||
@ -62,7 +62,7 @@ namespace Kyoo.Authentication
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new authentication module instance and use the given configuration and environment.
|
||||
/// Create a new authentication module instance and use the given configuration.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration to use</param>
|
||||
public AuthenticationModule(IConfiguration configuration)
|
||||
|
@ -22,33 +22,34 @@ using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Kyoo.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// The service that controls jwt creation and validation.
|
||||
/// </summary>
|
||||
public interface ITokenController
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new access token for the given user.
|
||||
/// The service that controls jwt creation and validation.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create a token for.</param>
|
||||
/// <param name="expireIn">When this token will expire.</param>
|
||||
/// <returns>A new, valid access token.</returns>
|
||||
string CreateAccessToken([NotNull] User user, out TimeSpan expireIn);
|
||||
public interface ITokenController
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new access token for the given user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create a token for.</param>
|
||||
/// <param name="expireIn">When this token will expire.</param>
|
||||
/// <returns>A new, valid access token.</returns>
|
||||
string CreateAccessToken([NotNull] User user, out TimeSpan expireIn);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new refresh token for the given user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create a token for.</param>
|
||||
/// <returns>A new, valid refresh token.</returns>
|
||||
Task<string> CreateRefreshToken([NotNull] User user);
|
||||
/// <summary>
|
||||
/// Create a new refresh token for the given user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create a token for.</param>
|
||||
/// <returns>A new, valid refresh token.</returns>
|
||||
Task<string> CreateRefreshToken([NotNull] User user);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given refresh token is valid and if it is, retrieve the id of the user this token belongs to.
|
||||
/// </summary>
|
||||
/// <param name="refreshToken">The refresh token to validate.</param>
|
||||
/// <exception cref="SecurityTokenException">The given refresh token is not valid.</exception>
|
||||
/// <returns>The id of the token's user.</returns>
|
||||
int GetRefreshTokenUserID(string refreshToken);
|
||||
/// <summary>
|
||||
/// Check if the given refresh token is valid and if it is, retrieve the id of the user this token belongs to.
|
||||
/// </summary>
|
||||
/// <param name="refreshToken">The refresh token to validate.</param>
|
||||
/// <exception cref="SecurityTokenException">The given refresh token is not valid.</exception>
|
||||
/// <returns>The id of the token's user.</returns>
|
||||
int GetRefreshTokenUserID(string refreshToken);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.12" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
|
||||
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
|
||||
|
@ -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<string, string>()
|
||||
};
|
||||
|
@ -20,39 +20,40 @@ using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Kyoo.Authentication;
|
||||
|
||||
/// <summary>
|
||||
/// A container representing the response of a login or token refresh.
|
||||
/// </summary>
|
||||
public class JwtToken
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of this token (always a Bearer).
|
||||
/// A container representing the response of a login or token refresh.
|
||||
/// </summary>
|
||||
[JsonProperty("token_token")]
|
||||
[JsonPropertyName("token_type")]
|
||||
public string TokenType => "Bearer";&é"bbbbR"
|
||||
public class JwtToken
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of this token (always a Bearer).
|
||||
/// </summary>
|
||||
[JsonProperty("token_token")]
|
||||
[JsonPropertyName("token_type")]
|
||||
public string TokenType => "Bearer";
|
||||
|
||||
/// <summary>
|
||||
/// The access token used to authorize requests.
|
||||
/// </summary>
|
||||
[JsonProperty("access_token")]
|
||||
[JsonPropertyName("access_token")]
|
||||
public string AccessToken { get; set; }p
|
||||
/// <summary>
|
||||
/// The access token used to authorize requests.
|
||||
/// </summary>
|
||||
[JsonProperty("access_token")]
|
||||
[JsonPropertyName("access_token")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The refresh token used to retrieve a new access/refresh token when the access token has expired.
|
||||
/// </summary>
|
||||
[JsonProperty("refresh_token")]
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
/// <summary>
|
||||
/// The refresh token used to retrieve a new access/refresh token when the access token has expired.
|
||||
/// </summary>
|
||||
[JsonProperty("refresh_token")]
|
||||
[JsonPropertyName("refresh_token")]
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date when the access token will expire. After this date, the refresh token should be used to retrieve.
|
||||
/// a new token.cs
|
||||
/// </summary>
|
||||
[JsonProperty("expire_in")]
|
||||
[JsonPropertyName("expire_in")]
|
||||
public TimeSpan ExpireIn { get; set; }
|
||||
/// <summary>
|
||||
/// When the access token will expire. After this tume, the refresh token should be used to retrieve.
|
||||
/// a new token.cs
|
||||
/// </summary>
|
||||
[JsonProperty("expire_in")]
|
||||
[JsonPropertyName("expire_in")]
|
||||
public TimeSpan ExpireIn { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Sign in, Sign up or refresh tokens.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("auth")]
|
||||
public class AuthView : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The repository used to check if the user exists.
|
||||
/// </summary>
|
||||
private readonly IUserRepository _users;
|
||||
|
||||
/// <summary>
|
||||
/// The token generator.
|
||||
/// </summary>
|
||||
private readonly ITokenController _token;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AuthView"/>.
|
||||
/// </summary>
|
||||
/// <param name="users">The repository used to check if the user exists.</param>
|
||||
/// <param name="token">The token generator.</param>
|
||||
public AuthView(IUserRepository users, ITokenController token)
|
||||
{
|
||||
_users = users;
|
||||
_token = token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Login.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Login as a user and retrieve an access and a refresh token.
|
||||
/// </remarks>
|
||||
/// <param name="request">The body of the request.</param>
|
||||
/// <returns>A new access and a refresh token.</returns>
|
||||
/// <response code="400">The user and password does not match.</response>
|
||||
[HttpPost("login")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<JwtToken>> 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." });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Register a new user and get a new access/refresh token for this new user.
|
||||
/// </remarks>
|
||||
/// <param name="request">The body of the request.</param>
|
||||
/// <returns>A new access and a refresh token.</returns>
|
||||
/// <response code="400">The request is invalid.</response>
|
||||
/// <response code="409">A user already exists with this username or email address.</response>
|
||||
[HttpPost("register")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||
public async Task<ActionResult<JwtToken>> 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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh a token.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="token">A valid refresh token.</param>
|
||||
/// <returns>A new access and refresh token.</returns>
|
||||
/// <response code="400">The given refresh token is invalid.</response>
|
||||
[HttpGet("refresh")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<JwtToken>> 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<AniListOptions> anilist)
|
||||
{
|
||||
Dictionary<string, string> 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<ActionResult<User>> 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<ActionResult<JwtToken>> 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<ActionResult<User>> GetMe()
|
||||
{
|
||||
if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
|
||||
return BadRequest("Invalid access token");
|
||||
return await _users.GetById(userID);
|
||||
}
|
||||
}
|
174
src/Kyoo.Authentication/Views/AuthApi.cs
Normal file
174
src/Kyoo.Authentication/Views/AuthApi.cs
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Sign in, Sign up or refresh tokens.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
[ApiDefinition("Authentication", Group = UsersGroup)]
|
||||
public class AuthApi : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The repository to handle users.
|
||||
/// </summary>
|
||||
private readonly IUserRepository _users;
|
||||
|
||||
/// <summary>
|
||||
/// The token generator.
|
||||
/// </summary>
|
||||
private readonly ITokenController _token;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AuthApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="users">The repository used to check if the user exists.</param>
|
||||
/// <param name="token">The token generator.</param>
|
||||
public AuthApi(IUserRepository users, ITokenController token)
|
||||
{
|
||||
_users = users;
|
||||
_token = token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Login.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Login as a user and retrieve an access and a refresh token.
|
||||
/// </remarks>
|
||||
/// <param name="request">The body of the request.</param>
|
||||
/// <returns>A new access and a refresh token.</returns>
|
||||
/// <response code="400">The user and password does not match.</response>
|
||||
[HttpPost("login")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<JwtToken>> 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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Register a new user and get a new access/refresh token for this new user.
|
||||
/// </remarks>
|
||||
/// <param name="request">The body of the request.</param>
|
||||
/// <returns>A new access and a refresh token.</returns>
|
||||
/// <response code="400">The request is invalid.</response>
|
||||
/// <response code="409">A user already exists with this username or email address.</response>
|
||||
[HttpPost("register")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status409Conflict, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<JwtToken>> 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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh a token.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Refresh an access token using the given refresh token. A new access and refresh token are generated.
|
||||
/// </remarks>
|
||||
/// <param name="token">A valid refresh token.</param>
|
||||
/// <returns>A new access and refresh token.</returns>
|
||||
/// <response code="400">The given refresh token is invalid.</response>
|
||||
[HttpGet("refresh")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<JwtToken>> 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<ActionResult<User>> GetMe()
|
||||
{
|
||||
if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
|
||||
return BadRequest("Invalid access token");
|
||||
return await _users.GetById(userID);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user