finally implemented oauth 2.0
This commit is contained in:
@ -7,6 +7,7 @@ using System.Text.Json.Serialization;
|
||||
using Yuna.Website.Server.Services.TokenService;
|
||||
using Yuna.Website.Server.Model;
|
||||
using Yuna.Website.Server.Services.UserService;
|
||||
using Yuna.Website.Server.API.DTO;
|
||||
|
||||
namespace Yuna.Website.Server.API
|
||||
{
|
||||
@ -27,14 +28,7 @@ namespace Yuna.Website.Server.API
|
||||
|
||||
}
|
||||
|
||||
public class LoginRequest
|
||||
{
|
||||
[JsonPropertyName("password")]
|
||||
public string RawPassword { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("username")]
|
||||
public string UserName { get; set; } = null!;
|
||||
}
|
||||
|
||||
public async Task<IResult> Login(HttpContext context, [FromBody] LoginRequest request, IUserService userService, ITokenService tokenService)
|
||||
{
|
||||
var userFromDb = await userService.GetByUsername(request.UserName);
|
||||
|
13
Yuna.Website/Yuna.Website.Server/API/DTO/LoginRequest.cs
Normal file
13
Yuna.Website/Yuna.Website.Server/API/DTO/LoginRequest.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Yuna.Website.Server.API.DTO
|
||||
{
|
||||
public class LoginRequest
|
||||
{
|
||||
[JsonPropertyName("password")]
|
||||
public string RawPassword { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("username")]
|
||||
public string UserName { get; set; } = null!;
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using Yuna.Website.Server.API.DTO;
|
||||
using Yuna.Website.Server.Infrastructure;
|
||||
using Yuna.Website.Server.Services.OpenAuthService;
|
||||
using Yuna.Website.Server.Services.UserService;
|
||||
using Yuna.Website.Server.Storage.Repositories.UserBindings;
|
||||
|
||||
namespace Yuna.Website.Server.API
|
||||
{
|
||||
@ -9,11 +13,20 @@ namespace Yuna.Website.Server.API
|
||||
{
|
||||
public void Define(WebApplication app)
|
||||
{
|
||||
app.MapGet("~/.well-known/openid-configuration", GetConfiguration);
|
||||
app.MapGet("~/.well-known/openid-configuration", GetConfiguration)
|
||||
.WithTags("oauth");
|
||||
|
||||
app.MapMethods("/v1.0", ["HEAD"], Ping);
|
||||
app.MapMethods("/v1.0", ["HEAD"], Ping)
|
||||
.WithTags("oauth");
|
||||
|
||||
app.MapGet("api/oauth/login", LoginViaOauth);
|
||||
app.MapGet("api/oauth/login", LoginViaOauth)
|
||||
.WithTags("oauth");
|
||||
|
||||
app.MapPost("api/oauth/auth", AuthorizeViaOauth)
|
||||
.WithTags("oauth");
|
||||
|
||||
app.MapPost("/api/oauth/token", GetTokenByCode)
|
||||
.WithTags("oauth");
|
||||
}
|
||||
|
||||
public class DiscoveryResponse
|
||||
@ -141,10 +154,11 @@ namespace Yuna.Website.Server.API
|
||||
}
|
||||
|
||||
public IResult LoginViaOauth(
|
||||
[FromQuery] string state,
|
||||
[FromQuery] string redirect_uri,
|
||||
[FromQuery]string response_type,
|
||||
[FromQuery]string client_id, HttpContext context,
|
||||
[FromQuery] string state,
|
||||
[FromQuery] string redirect_uri,
|
||||
[FromQuery] string response_type,
|
||||
[FromQuery] string client_id,
|
||||
HttpContext context,
|
||||
IOpenAuthService openAuthService)
|
||||
{
|
||||
|
||||
@ -153,7 +167,136 @@ namespace Yuna.Website.Server.API
|
||||
return Results.Unauthorized();
|
||||
|
||||
//TODO LOGIN PAGE URL IN SETTINGS
|
||||
return Results.Redirect($"https://localhost:5173/login?redirect_to={redirect_uri}");
|
||||
return Results.Redirect($"https://localhost:5173/login?redirect_to={redirect_uri}&client_id={client_id}&state={state}");
|
||||
}
|
||||
|
||||
|
||||
public class OauthLoginRequest : LoginRequest
|
||||
{
|
||||
[JsonPropertyName("state")]
|
||||
public string State { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("redirectUri")]
|
||||
public string RedirectUri { get; set; } = null!;
|
||||
}
|
||||
public async Task<IResult> AuthorizeViaOauth([FromBody] OauthLoginRequest request, IUserService userService, IOpenAuthService openAuthService, HttpContext context)
|
||||
{
|
||||
var user = await userService.GetByUsername(request.UserName);
|
||||
|
||||
if (user is null) return Results.Problem(statusCode: 401, detail:"invalid user input data");
|
||||
|
||||
var inputHashedPassword = Encrypter.HashPassword(request.RawPassword, user.UserName);
|
||||
|
||||
if (!inputHashedPassword.Equals(user.HashedPassword)) return Results.Problem(statusCode: 401, detail: "invalid login attempt");
|
||||
|
||||
var binding = openAuthService.CreateBinding(user);
|
||||
|
||||
if(binding is null) return Results.Problem(statusCode: 401, detail: "Unable to create binding");
|
||||
|
||||
return Results.Ok(request.RedirectUri + $"?code={binding.Code}&state={request.State}");
|
||||
}
|
||||
|
||||
|
||||
public class OauthTokenResponse
|
||||
{
|
||||
[JsonPropertyName("access_token")]
|
||||
public string AccessToken { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("token_type")]
|
||||
public string TokenType => "Bearer";
|
||||
|
||||
[JsonPropertyName("expires_in")]
|
||||
public int ExpiresIn => int.MaxValue;
|
||||
}
|
||||
|
||||
public class TokenRequest
|
||||
{
|
||||
public string Code { get; set; }
|
||||
public string ClientId { get; set; } = null!;
|
||||
|
||||
public string ClientSecret { get; set; } = null!;
|
||||
|
||||
public string GrantType { get; set; } = null!;
|
||||
|
||||
public string RedirectUri { get; set; } = null!;
|
||||
}
|
||||
|
||||
public async Task<IResult> GetTokenByCode(
|
||||
HttpContext context,
|
||||
ILogger<OpenAuthEndpoints> logger,
|
||||
IOpenAuthService openAuthService)
|
||||
{
|
||||
var request = context.Request;
|
||||
var sb = new StringBuilder();
|
||||
// Записываем метод и URL запроса
|
||||
sb.AppendLine($"{request.Method} {request.Path}{request.QueryString}");
|
||||
|
||||
// Записываем заголовки
|
||||
sb.AppendLine("Headers:");
|
||||
foreach (var header in request.Headers)
|
||||
{
|
||||
sb.AppendLine($"{header.Key}: {header.Value}");
|
||||
}
|
||||
|
||||
// Записываем тело запроса
|
||||
string bodyStr = "";
|
||||
sb.AppendLine("Body:");
|
||||
request.EnableBuffering(); // Для того чтобы можно было прочитать тело несколько раз
|
||||
using (var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true))
|
||||
{
|
||||
var body = await reader.ReadToEndAsync();
|
||||
sb.AppendLine(body);
|
||||
bodyStr = body;
|
||||
request.Body.Position = 0; // Возвращаем позицию в начале потока
|
||||
}
|
||||
|
||||
logger.LogInformation(sb.ToString());
|
||||
|
||||
|
||||
|
||||
var tokenRequest = ParseQueryStringToTokenRequest(bodyStr);
|
||||
|
||||
|
||||
if (tokenRequest.GrantType != "authorization_code")
|
||||
{
|
||||
return Results.BadRequest(new { error = "unsupported_grant_type" });
|
||||
}
|
||||
|
||||
if(!tokenRequest.ClientId.Equals("vasich_yandex_server"))
|
||||
{
|
||||
return Results.BadRequest(new { error = "client id invalid" });
|
||||
}
|
||||
|
||||
if(!tokenRequest.ClientSecret.Equals("vasich_molodets_sdelal_oauth"))
|
||||
{
|
||||
return Results.BadRequest(new { error = "client secret invalid" });
|
||||
}
|
||||
|
||||
var userBinding = openAuthService.GetByCode(tokenRequest.Code);
|
||||
|
||||
if (userBinding is null) return Results.BadRequest(new { error = "Invalid binding" });
|
||||
|
||||
return Results.Ok(new OauthTokenResponse()
|
||||
{
|
||||
AccessToken = userBinding.AccessToken
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static TokenRequest ParseQueryStringToTokenRequest(string queryString)
|
||||
{
|
||||
var queryParams = queryString.Split('&')
|
||||
.Select(param => param.Split('='))
|
||||
.ToDictionary(pair => pair[0], pair => Uri.UnescapeDataString(pair[1]));
|
||||
|
||||
return new TokenRequest
|
||||
{
|
||||
Code = queryParams["code"],
|
||||
ClientId = queryParams["client_id"],
|
||||
ClientSecret = queryParams["client_secret"],
|
||||
GrantType = queryParams["grant_type"],
|
||||
RedirectUri = queryParams["redirect_uri"]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user