Register -> Next step manage exceptions and tests
This commit is contained in:
parent
b3223df0f9
commit
4c98577eed
22
src/chat.API/AuthController/AuthController.cs
Normal file
22
src/chat.API/AuthController/AuthController.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using chat.Application.Authentication.Commands.Register;
|
||||||
|
using chat.Application.Authentication.Dtos;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace chat.API.AuthController;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("chat_api/auth")]
|
||||||
|
[Authorize]
|
||||||
|
public class AuthController(IMediator mediator) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpPost]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<RegisterResponseDto>> Register([FromBody] RegisterCommand command)
|
||||||
|
{
|
||||||
|
RegisterResponseDto dto = await mediator.Send(command);
|
||||||
|
return Ok(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/chat.API/Extensions/WebApplicationBuilderExtensions.cs
Normal file
43
src/chat.API/Extensions/WebApplicationBuilderExtensions.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using chat.API.Middlewares;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
|
namespace chat.API.Extensions;
|
||||||
|
|
||||||
|
public static class WebApplicationBuilderExtensions
|
||||||
|
{
|
||||||
|
public static void AddPresentation(this WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddAuthentication();
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
|
||||||
|
builder.Services.AddSwaggerGen(c =>
|
||||||
|
{
|
||||||
|
// Définit le schéma de sécurité pour l'authentification Bearer (JWT)
|
||||||
|
c.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme()
|
||||||
|
{
|
||||||
|
Type = SecuritySchemeType.Http,
|
||||||
|
Scheme = "bearer",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajoute l'exigence de sécurité pour que Swagger utilise le schéma Bearer
|
||||||
|
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||||
|
{
|
||||||
|
{
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = "bearerAuth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
builder.Services.AddScoped<ErrorHandlingMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/chat.API/Middlewares/ErrorHandlingMiddleware.cs
Normal file
28
src/chat.API/Middlewares/ErrorHandlingMiddleware.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using chat.Domain.Exceptions;
|
||||||
|
|
||||||
|
namespace chat.API.Middlewares;
|
||||||
|
|
||||||
|
public class ErrorHandlingMiddleware(ILogger<ErrorHandlingMiddleware> logger) : IMiddleware
|
||||||
|
{
|
||||||
|
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await next(context);
|
||||||
|
}
|
||||||
|
catch (HttpResponseException ex)
|
||||||
|
{
|
||||||
|
logger.LogWarning(ex, "HTTP {StatusCode}: {Message}", ex.StatusCode, ex.Message);
|
||||||
|
context.Response.StatusCode = ex.StatusCode;
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
await context.Response.WriteAsJsonAsync(new { error = ex.Message });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Unexpected error occurred");
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
await context.Response.WriteAsJsonAsync(new { error = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,27 @@
|
|||||||
|
using chat.API.Extensions;
|
||||||
|
using chat.API.Middlewares;
|
||||||
|
using chat.Application.Extensions;
|
||||||
|
using chat.Infrastructure.Extensions;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.AddPresentation();
|
||||||
|
builder.Services.AddApplication();
|
||||||
|
builder.Services.AddInfrastructure();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseMiddleware<ErrorHandlingMiddleware>();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.MapOpenApi();
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseAuthentication();
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|||||||
@ -5,5 +5,15 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"Logto": {
|
||||||
|
"Endpoint": "https://en7hny.logto.app/", // URL de base de ton tenant Logto
|
||||||
|
"Issuer": "https://en7hny.logto.app/oidc", // URL pour valider les JWT (utilisé par le middleware auth)
|
||||||
|
"Audience": "https://localhost:7169", // Identifiant de ton API (pour valider les tokens)
|
||||||
|
"ManagementApi": "https://en7hny.logto.app/api", // URL de l'API Management Logto (pour créer des users)
|
||||||
|
"M2M": {
|
||||||
|
"AppId": "", // Client ID de l'app M2M (pour obtenir un token Management) dans user-secrets
|
||||||
|
"AppSecret": "" // Client Secret de l'app M2M dans user-secrets
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,16 +1,19 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>7224f703-f9b3-4ef2-a34d-b2ba0038d160</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\chat.Application\chat.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\chat.Domain\chat.Domain.csproj" />
|
||||||
<ProjectReference Include="..\chat.Infrastructure\chat.Infrastructure.csproj" />
|
<ProjectReference Include="..\chat.Infrastructure\chat.Infrastructure.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
using chat.Application.Authentication.Dtos;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace chat.Application.Authentication.Commands.Register;
|
||||||
|
|
||||||
|
public class RegisterCommand : IRequest<RegisterResponseDto>
|
||||||
|
{
|
||||||
|
public string Username { get; set; } = default!;
|
||||||
|
public string Email { get; set; } = default!;
|
||||||
|
public string Password { get; set; } = default!;
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using chat.Application.Authentication.Dtos;
|
||||||
|
using chat.Domain.Entities;
|
||||||
|
using chat.Domain.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace chat.Application.Authentication.Commands.Register;
|
||||||
|
|
||||||
|
public class RegisterCommandHandler(
|
||||||
|
ILogger<RegisterCommandHandler> logger,
|
||||||
|
IMapper mapper,
|
||||||
|
ILogtoAuthService authRepository
|
||||||
|
) : IRequestHandler<RegisterCommand, RegisterResponseDto>
|
||||||
|
{
|
||||||
|
public async Task<RegisterResponseDto> Handle(RegisterCommand request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Creating a new user with the following credentials {Username} {Email}", request.Username, request.Email);
|
||||||
|
var user = mapper.Map<User>(request);
|
||||||
|
var userRegistered = await authRepository.Register(user , cancellationToken);
|
||||||
|
if (userRegistered == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Registration failed");
|
||||||
|
}
|
||||||
|
var response = mapper.Map<RegisterResponseDto>(userRegistered);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace chat.Application.Authentication.Commands.Register;
|
||||||
|
|
||||||
|
public class RegisterCommandValidator : AbstractValidator<RegisterCommand>
|
||||||
|
{
|
||||||
|
public RegisterCommandValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Username)
|
||||||
|
.NotEmpty().WithMessage("Username is required.")
|
||||||
|
.MinimumLength(3).WithMessage("Username must be at least 3 characters long.");
|
||||||
|
RuleFor(x => x.Password)
|
||||||
|
.NotEmpty().WithMessage("Password is required.")
|
||||||
|
.MinimumLength(6).WithMessage("Password must be at least 6 characters long.");
|
||||||
|
RuleFor(x => x.Email)
|
||||||
|
.NotEmpty().WithMessage("Email is required.")
|
||||||
|
.EmailAddress().WithMessage("A valid email address is required.");
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/chat.Application/Authentication/Dtos/AuthProfile.cs
Normal file
24
src/chat.Application/Authentication/Dtos/AuthProfile.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using chat.Application.Authentication.Commands.Register;
|
||||||
|
using chat.Domain.Entities;
|
||||||
|
|
||||||
|
namespace chat.Application.Authentication.Dtos
|
||||||
|
{
|
||||||
|
public class AuthProfile : Profile
|
||||||
|
{
|
||||||
|
public AuthProfile()
|
||||||
|
{
|
||||||
|
CreateMap<RegisterCommand, User>()
|
||||||
|
.ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.Username))
|
||||||
|
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email))
|
||||||
|
.ForMember(dest => dest.Password, opt => opt.MapFrom(src => src.Password))
|
||||||
|
.ReverseMap();
|
||||||
|
|
||||||
|
CreateMap<RegisterResponseDto, User>()
|
||||||
|
.ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.Username))
|
||||||
|
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email))
|
||||||
|
.ForMember(dest => dest.Password, opt => opt.MapFrom(src => src.Password))
|
||||||
|
.ReverseMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
namespace chat.Application.Authentication.Dtos;
|
||||||
|
|
||||||
|
public class RegisterResponseDto
|
||||||
|
{
|
||||||
|
public string Username { get; set; } = default!;
|
||||||
|
public string Email { get; set; } = default!;
|
||||||
|
public string Password { get; set; } = default!;
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using SharpGrip.FluentValidation.AutoValidation.Mvc.Extensions;
|
||||||
|
|
||||||
|
namespace chat.Application.Extensions;
|
||||||
|
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static void AddApplication(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
var applicationAssembly = typeof(ServiceCollectionExtensions).Assembly;
|
||||||
|
|
||||||
|
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(applicationAssembly));
|
||||||
|
|
||||||
|
services.AddAutoMapper(cfg => cfg.AddMaps(applicationAssembly));
|
||||||
|
|
||||||
|
services.AddValidatorsFromAssembly(applicationAssembly)
|
||||||
|
.AddFluentValidationAutoValidation();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,4 +6,17 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoMapper" Version="16.1.0" />
|
||||||
|
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||||
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
|
||||||
|
<PackageReference Include="MediatR" Version="14.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
||||||
|
<PackageReference Include="SharpGrip.FluentValidation.AutoValidation.Mvc" Version="2.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\chat.Domain\chat.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
8
src/chat.Domain/Entities/User.cs
Normal file
8
src/chat.Domain/Entities/User.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace chat.Domain.Entities;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public string Username { get; set; } = default!;
|
||||||
|
public string Email { get; set; } = default!;
|
||||||
|
public string Password { get; set; } = default!;
|
||||||
|
}
|
||||||
11
src/chat.Domain/Exceptions/HttpResponseException.cs
Normal file
11
src/chat.Domain/Exceptions/HttpResponseException.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace chat.Domain.Exceptions;
|
||||||
|
|
||||||
|
public class HttpResponseException : Exception
|
||||||
|
{
|
||||||
|
public int StatusCode { get; }
|
||||||
|
|
||||||
|
public HttpResponseException(int statusCode, string message) : base(message)
|
||||||
|
{
|
||||||
|
StatusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/chat.Domain/Interfaces/ILogtoAuthService.cs
Normal file
9
src/chat.Domain/Interfaces/ILogtoAuthService.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using chat.Domain.Entities;
|
||||||
|
|
||||||
|
namespace chat.Domain.Interfaces
|
||||||
|
{
|
||||||
|
public interface ILogtoAuthService
|
||||||
|
{
|
||||||
|
Task<User?> Register(User request, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,4 +6,8 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
using chat.Domain.Interfaces;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace chat.Infrastructure.Extensions;
|
||||||
|
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static void AddInfrastructure(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHttpClient<LogtoAuthService>();
|
||||||
|
services.AddScoped<ILogtoAuthService, LogtoAuthService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/chat.Infrastructure/LogtoAuthService.cs
Normal file
115
src/chat.Infrastructure/LogtoAuthService.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
using chat.Domain.Entities;
|
||||||
|
using chat.Domain.Interfaces;
|
||||||
|
using chat.Domain.Exceptions;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace chat.Infrastructure;
|
||||||
|
|
||||||
|
public class LogtoAuthService(
|
||||||
|
ILogger<LogtoAuthService> _logger,
|
||||||
|
HttpClient _httpClient,
|
||||||
|
IConfiguration _configuration) : ILogtoAuthService
|
||||||
|
{
|
||||||
|
public async Task<User?> Register(User request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var token = await RetrieveManagementToken(cancellationToken);
|
||||||
|
var managementApiUrl = _configuration["Logto:ManagementApi"]!;
|
||||||
|
var createUserEndpoint = $"{managementApiUrl}/users";
|
||||||
|
|
||||||
|
var createUserRequest = new
|
||||||
|
{
|
||||||
|
username = request.Username,
|
||||||
|
primaryEmail = request.Email,
|
||||||
|
password = request.Password,
|
||||||
|
name = request.Username
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, createUserEndpoint)
|
||||||
|
{
|
||||||
|
Content = JsonContent.Create(createUserRequest),
|
||||||
|
Headers =
|
||||||
|
{
|
||||||
|
{ "Authorization", $"Bearer {token}" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await _httpClient.SendAsync(requestMessage, cancellationToken);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var error = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
throw new HttpResponseException((int)response.StatusCode, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
var logtoUser = await response.Content.ReadFromJsonAsync<LogtoUserResponse>(cancellationToken);
|
||||||
|
|
||||||
|
if (logtoUser == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to deserialize Logto user response");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("User created successfully in Logto. UserId: {UserId}, Username: {Username}",
|
||||||
|
logtoUser.Id, logtoUser.Username);
|
||||||
|
|
||||||
|
return new User
|
||||||
|
{
|
||||||
|
Username = logtoUser.Username ?? request.Username,
|
||||||
|
Email = logtoUser.PrimaryEmail ?? request.Email,
|
||||||
|
Password = "********"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> RetrieveManagementToken(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tokenEndpoint = $"{_configuration["Logto:Issuer"]}/token";
|
||||||
|
var managementApiUrl = _configuration["Logto:ManagementApi"]!;
|
||||||
|
|
||||||
|
var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["grant_type"] = "client_credentials",
|
||||||
|
["client_id"] = _configuration["Logto:M2M:AppId"]!,
|
||||||
|
["client_secret"] = _configuration["Logto:M2M:AppSecret"]!,
|
||||||
|
["resource"] = managementApiUrl,
|
||||||
|
["scope"] = "all"
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync(tokenEndpoint, tokenRequest, cancellationToken);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var error = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
throw new HttpResponseException((int)response.StatusCode, $"{error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenResponse = await response.Content.ReadFromJsonAsync<TokenResponse>(cancellationToken);
|
||||||
|
|
||||||
|
if (tokenResponse?.AccessToken == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("M2M token response is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("M2M token obtained successfully. Type: {TokenType}, Expires in: {ExpiresIn}s, scope: {Scope}",
|
||||||
|
tokenResponse.TokenType,
|
||||||
|
tokenResponse.ExpiresIn,
|
||||||
|
tokenResponse.Scope);
|
||||||
|
|
||||||
|
return tokenResponse.AccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TokenResponse(
|
||||||
|
[property: JsonPropertyName("access_token")] string AccessToken,
|
||||||
|
[property: JsonPropertyName("token_type")] string TokenType,
|
||||||
|
[property: JsonPropertyName("expires_in")] int ExpiresIn,
|
||||||
|
[property: JsonPropertyName("scope")] string Scope
|
||||||
|
);
|
||||||
|
|
||||||
|
private record LogtoUserResponse(
|
||||||
|
[property: JsonPropertyName("id")] string Id,
|
||||||
|
[property: JsonPropertyName("username")] string? Username,
|
||||||
|
[property: JsonPropertyName("primaryEmail")] string? PrimaryEmail,
|
||||||
|
[property: JsonPropertyName("name")] string? Name
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user