Api protected

main
Paul Schneider 10 months ago
parent 968859babe
commit 84e58bb9eb
6 changed files with 82 additions and 182 deletions

@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>1c73094f-959f-4211-b1a1-6a69b236c283</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.13" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.13" />

@ -17,116 +17,17 @@ namespace Yavsc.WebApi.Controllers
[Authorize("ApiScope")] [Authorize("ApiScope")]
public class ApiAccountController : Controller public class ApiAccountController : Controller
{ {
private UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
readonly ApplicationDbContext _dbContext; readonly ApplicationDbContext _dbContext;
private readonly ILogger _logger; private readonly ILogger _logger;
public ApiAccountController(UserManager<ApplicationUser> userManager, public ApiAccountController(
SignInManager<ApplicationUser> signInManager,
RoleManager<IdentityRole> roleManager,
ILoggerFactory loggerFactory, ApplicationDbContext dbContext) ILoggerFactory loggerFactory, ApplicationDbContext dbContext)
{ {
UserManager = userManager;
this.roleManager = roleManager;
_signInManager = signInManager;
_logger = loggerFactory.CreateLogger(nameof(ApiAccountController)); _logger = loggerFactory.CreateLogger(nameof(ApiAccountController));
_dbContext = dbContext; _dbContext = dbContext;
} }
public UserManager<ApplicationUser> UserManager
{
get
{
return _userManager;
}
private set
{
_userManager = value;
}
}
private readonly RoleManager<IdentityRole> roleManager;
// POST api/Account/ChangePassword
public async Task<IActionResult> ChangePassword(ChangePasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var user = await _userManager.FindByIdAsync(User.GetUserId());
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) {
IdentityResult result = await UserManager.ChangePasswordAsync(user, model.OldPassword,
model.NewPassword);
if (!result.Succeeded)
{
AddErrors("NewPassword",result);
return new BadRequestObjectResult(ModelState);
}
}
return Ok();
}
// POST api/Account/SetPassword
public async Task<IActionResult> SetPassword(SetPasswordBindingModel model)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var user = await _userManager.FindByIdAsync(User.GetUserId());
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) {
IdentityResult result = await UserManager.AddPasswordAsync(user, model.NewPassword);
if (!result.Succeeded)
{
AddErrors ("NewPassword",result);
return new BadRequestObjectResult(ModelState);
}
}
return Ok();
}
// POST api/Account/Register
[AllowAnonymous]
public async Task<IActionResult> Register(RegisterModel model)
{
if (!ModelState.IsValid)
{
return new BadRequestObjectResult(ModelState);
}
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
AddErrors ("Register",result);
return new BadRequestObjectResult(ModelState);
}
await _signInManager.SignInAsync(user, isPersistent: false);
return Ok();
}
private void AddErrors(string key, IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(key, error.Description);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
UserManager.Dispose();
}
base.Dispose(disposing);
}
[HttpGet("me")] [HttpGet("me")]
public async Task<IActionResult> Me() public async Task<IActionResult> Me()
@ -136,10 +37,7 @@ namespace Yavsc.WebApi.Controllers
new { error = "user not found" }); new { error = "user not found" });
var uid = User.GetUserId(); var uid = User.GetUserId();
var userData = await _dbContext.Users var userData = await GetUserData(uid);
.Include(u=>u.PostalAddress)
.Include(u=>u.AccountBalance)
.FirstAsync(u=>u.Id == uid);
var user = new Yavsc.Models.Auth.Me(userData.Id, userData.UserName, userData.Email, var user = new Yavsc.Models.Auth.Me(userData.Id, userData.UserName, userData.Email,
userData.Avatar, userData.Avatar,
@ -154,28 +52,21 @@ namespace Yavsc.WebApi.Controllers
return Ok(user); return Ok(user);
} }
private async Task<ApplicationUser> GetUserData(string uid)
{
return await _dbContext.Users
.Include(u => u.PostalAddress)
.Include(u => u.AccountBalance)
.FirstAsync(u => u.Id == uid);
}
[HttpGet("myhost")] [HttpGet("myhost")]
public IActionResult MyHost () public IActionResult MyHost ()
{ {
return Ok(new { host = Request.ForHost() }); return Ok(new { host = Request.ForHost() });
} }
/// <summary>
/// Actually only updates the user's name.
/// </summary>
/// <param name="me">MyUpdate containing the new user name </param>
/// <returns>Ok when all is ok.</returns>
[HttpPut("me")]
public async Task<IActionResult> UpdateMe(UserInfo me)
{
if (!ModelState.IsValid) return new BadRequestObjectResult(
new { error = "Specify some valid user update request." });
var user = await _userManager.FindByIdAsync(User.GetUserId());
var result = await _userManager.SetUserNameAsync(user, me.UserName);
if (result.Succeeded)
return Ok();
else return new BadRequestObjectResult(result);
}
/// <summary> /// <summary>
/// Updates the avatar /// Updates the avatar
/// </summary> /// </summary>
@ -184,11 +75,11 @@ namespace Yavsc.WebApi.Controllers
public async Task<IActionResult> SetAvatar() public async Task<IActionResult> SetAvatar()
{ {
var root = User.InitPostToFileSystem(null); var root = User.InitPostToFileSystem(null);
var user = await _userManager.FindByIdAsync(User.GetUserId()); var user = await GetUserData(User.GetUserId());
if (Request.Form.Files.Count!=1) if (Request.Form.Files.Count!=1)
return new BadRequestResult(); return new BadRequestResult();
var info = user.ReceiveAvatar(Request.Form.Files[0]); var info = user.ReceiveAvatar(Request.Form.Files[0]);
await _userManager.UpdateAsync(user); await _dbContext.SaveChangesAsync();
return Ok(info); return Ok(info);
} }

@ -28,7 +28,7 @@ internal class Program
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var services = builder.Services; var services = builder.Services;
builder.Services.AddDistributedMemoryCache(); // builder.Services.AddDistributedMemoryCache();
// accepts any access token issued by identity server // accepts any access token issued by identity server
// adds an authorization policy for scope 'scope1' // adds an authorization policy for scope 'scope1'
@ -64,21 +64,20 @@ internal class Program
options.TokenValidationParameters = options.TokenValidationParameters =
new() { ValidateAudience = false }; new() { ValidateAudience = false };
}); });
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
services.AddScoped<UserManager<ApplicationUser>>();
services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>();
/*
services.AddSingleton<IConnexionManager, HubConnectionManager>(); services.AddSingleton<IConnexionManager, HubConnectionManager>();
services.AddSingleton<ILiveProcessor, LiveProcessor>(); services.AddSingleton<ILiveProcessor, LiveProcessor>();
services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>(); services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
services.AddIdentityApiEndpoints<ApplicationUser>(); services.AddIdentityApiEndpoints<ApplicationUser>();
services.AddSession(); services.AddSession();
*/
services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>()
.AddTransient<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>()
.AddTransient<DbContext>();
using (var app = builder.Build()) using (var app = builder.Build())
{ {
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
@ -89,18 +88,20 @@ internal class Program
.UseAuthentication() .UseAuthentication()
.UseAuthorization() .UseAuthorization()
.UseCors("default") .UseCors("default")
.UseEndpoints(endpoints => /* .UseEndpoints(endpoints =>
{ {
endpoints.MapDefaultControllerRoute() endpoints.MapDefaultControllerRoute()
.RequireAuthorization(); .RequireAuthorization();
}); })*/
app.MapIdentityApi<ApplicationUser>().RequireAuthorization("ApiScope");
;
// app.MapIdentityApi<ApplicationUser>().RequireAuthorization("ApiScope");
app.MapDefaultControllerRoute();
app.MapGet("/identity", (HttpContext context) => app.MapGet("/identity", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value })) new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
); );
app.UseSession(); // app.UseSession();
await app.RunAsync(); await app.RunAsync();
}; };

@ -73,12 +73,12 @@ public static class Config
ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) }, ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
AllowedGrantTypes = GrantTypes.Code, AllowedGrantTypes = GrantTypes.Code,
AlwaysIncludeUserClaimsInIdToken = true,
RedirectUris = { "https://localhost:5003/signin-oidc", RedirectUris = { "https://localhost:5003/signin-oidc",
"http://localhost:5002/signin-oidc" }, "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" }, PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = { AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.OpenId,

@ -4,6 +4,7 @@ using System.Security.Cryptography.X509Certificates;
using Google.Apis.Util.Store; using Google.Apis.Util.Store;
using IdentityServer8; using IdentityServer8;
using IdentityServer8.Services; using IdentityServer8.Services;
using IdentityServerHost.Quickstart.UI;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
@ -138,21 +139,13 @@ public static class HostingExtensions
{ {
IServiceCollection services = LoadConfiguration(builder); IServiceCollection services = LoadConfiguration(builder);
services.AddRazorPages(); //services.AddRazorPages();
services.AddSignalR(o =>
{
o.EnableDetailedErrors = true;
});
AddIdentityDBAndStores(builder).AddDefaultTokenProviders();
AddIdentityServer(builder);
services.AddSession(); services.AddSession();
// TODO .AddServerSideSessionStore<YavscServerSideSessionStore>() // TODO .AddServerSideSessionStore<YavscServerSideSessionStore>()
AddAuthentication(services, builder.Configuration);
// Add the system clock service // Add the system clock service
_ = services.AddSingleton<ISystemClock, SystemClock>(); _ = services.AddSingleton<ISystemClock, SystemClock>();
@ -160,6 +153,14 @@ public static class HostingExtensions
_ = services.AddSingleton<ILiveProcessor, LiveProcessor>(); _ = services.AddSingleton<ILiveProcessor, LiveProcessor>();
_ = services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>(); _ = services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
AddIdentityDBAndStores(builder).AddDefaultTokenProviders();
AddIdentityServer(builder);
services.AddSignalR(o =>
{
o.EnableDetailedErrors = true;
});
services.AddMvc(config => services.AddMvc(config =>
{ {
/* var policy = new AuthorizationPolicyBuilder() /* var policy = new AuthorizationPolicyBuilder()
@ -207,6 +208,7 @@ public static class HostingExtensions
services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
AddAuthentication(builder);
// accepts any access token issued by identity server // accepts any access token issued by identity server
return builder.Build(); return builder.Build();
@ -296,20 +298,16 @@ public static class HostingExtensions
return services; return services;
} }
private static void AddAuthentication(IServiceCollection services, IConfigurationRoot configurationRoot) private static void AddAuthentication(WebApplicationBuilder builder)
{ {
IServiceCollection services=builder.Services;
IConfigurationRoot configurationRoot=builder.Configuration;
string? googleClientId = configurationRoot["Authentication:Google:ClientId"]; string? googleClientId = configurationRoot["Authentication:Google:ClientId"];
string? googleClientSecret = configurationRoot["Authentication:Google:ClientSecret"]; string? googleClientSecret = configurationRoot["Authentication:Google:ClientSecret"];
var authenticationBuilder = services.AddAuthentication() var authenticationBuilder = services.AddAuthentication();
.AddJwtBearer("Bearer", options =>
{
options.IncludeErrorDetails = true;
options.Authority = "https://localhost:5001";
options.TokenValidationParameters =
new() { ValidateAudience = false };
});
if (googleClientId!=null && googleClientSecret!=null)
authenticationBuilder.AddGoogle(options => authenticationBuilder.AddGoogle(options =>
{ {
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
@ -323,13 +321,23 @@ public static class HostingExtensions
} }
private static IIdentityServerBuilder AddIdentityServer(WebApplicationBuilder builder) private static IIdentityServerBuilder AddIdentityServer(WebApplicationBuilder builder)
{ {
var identityServerBuilder = builder.Services.AddIdentityServer() builder.Services.AddTransient<IProfileService,ProfileService>();
var identityServerBuilder = builder.Services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://IdentityServer8.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryClients(Config.Clients) .AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryApiScopes(Config.ApiScopes)
.AddAspNetIdentity<ApplicationUser>() .AddAspNetIdentity<ApplicationUser>()
// .AddProfileService<ProfileService>() .AddProfileService<ProfileService>()
.AddJwtBearerClientAuthentication()
; ;
if (builder.Environment.IsDevelopment()) if (builder.Environment.IsDevelopment())
{ {
@ -381,7 +389,7 @@ public static class HostingExtensions
} }
public static WebApplication ConfigurePipeline(this WebApplication app) internal static WebApplication ConfigurePipeline(this WebApplication app)
{ {
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
@ -398,13 +406,10 @@ public static class HostingExtensions
app.UseIdentityServer(); app.UseIdentityServer();
app.UseAuthorization(); app.UseAuthorization();
app.UseCors("default"); app.UseCors("default");
app.MapControllerRoute( app.MapDefaultControllerRoute();
name: "default", //pp.MapRazorPages();
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages()
.RequireAuthorization();
app.MapHub<ChatHub>("/chatHub"); app.MapHub<ChatHub>("/chatHub");
app.MapAreaControllerRoute("api", "api", "~/api/{controller}/{action}/{id?}");
ConfigureWorkflow(); ConfigureWorkflow();
var services = app.Services; var services = app.Services;
ILoggerFactory loggerFactory = services.GetRequiredService<ILoggerFactory>(); ILoggerFactory loggerFactory = services.GetRequiredService<ILoggerFactory>();

@ -33,6 +33,8 @@ builder.Services
options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0"; options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
options.ResponseType = "code"; options.ResponseType = "code";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("scope2"); options.Scope.Add("scope2");
options.SaveTokens = true; options.SaveTokens = true;

Loading…