From 84e58bb9ebfebb9957bafebfab8a0f8193205fdd Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 16 Feb 2025 22:40:51 +0000 Subject: [PATCH] Api protected --- src/Api/Api.csproj | 3 +- .../accounting/AccountController.cs | 157 +++--------------- src/Api/Program.cs | 35 ++-- src/Yavsc.Server/Config.cs | 2 +- src/Yavsc/Extensions/HostingExtensions.cs | 65 ++++---- src/sampleWebAsWebApiClient/Program.cs | 2 + 6 files changed, 82 insertions(+), 182 deletions(-) diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index b1e26b9c..6e024e9b 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -1,8 +1,9 @@ - + net8.0 enable enable + 1c73094f-959f-4211-b1a1-6a69b236c283 diff --git a/src/Api/Controllers/accounting/AccountController.cs b/src/Api/Controllers/accounting/AccountController.cs index 6137c544..5ae21995 100644 --- a/src/Api/Controllers/accounting/AccountController.cs +++ b/src/Api/Controllers/accounting/AccountController.cs @@ -17,165 +17,56 @@ namespace Yavsc.WebApi.Controllers [Authorize("ApiScope")] public class ApiAccountController : Controller { - private UserManager _userManager; - private readonly SignInManager _signInManager; readonly ApplicationDbContext _dbContext; private readonly ILogger _logger; - public ApiAccountController(UserManager userManager, - SignInManager signInManager, - RoleManager roleManager, + public ApiAccountController( ILoggerFactory loggerFactory, ApplicationDbContext dbContext) { - UserManager = userManager; - this.roleManager = roleManager; - _signInManager = signInManager; _logger = loggerFactory.CreateLogger(nameof(ApiAccountController)); _dbContext = dbContext; } - public UserManager UserManager - { - get - { - return _userManager; - } - private set - { - _userManager = value; - } - } - private readonly RoleManager roleManager; - - // POST api/Account/ChangePassword - - public async Task 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 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 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")] public async Task Me() { - if (User==null) - return new BadRequestObjectResult( - new { error = "user not found" }); + if (User == null) + return new BadRequestObjectResult( + new { error = "user not found" }); var uid = User.GetUserId(); - - var userData = await _dbContext.Users - .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, - userData.Avatar , - userData.PostalAddress, userData.DedicatedGoogleCalendar ); + var userData = await GetUserData(uid); - var userRoles = _dbContext.UserRoles.Where(u=>u.UserId == uid).Select(r => r.RoleId).ToArray(); + var user = new Yavsc.Models.Auth.Me(userData.Id, userData.UserName, userData.Email, + userData.Avatar, + userData.PostalAddress, userData.DedicatedGoogleCalendar); - IdentityRole [] roles = _dbContext.Roles.Where(r=>userRoles.Contains(r.Id)).ToArray(); - - user.Roles = roles.Select(r=>r.Name).ToArray(); + var userRoles = _dbContext.UserRoles.Where(u => u.UserId == uid).Select(r => r.RoleId).ToArray(); + + IdentityRole[] roles = _dbContext.Roles.Where(r => userRoles.Contains(r.Id)).ToArray(); + + user.Roles = roles.Select(r => r.Name).ToArray(); return Ok(user); } + private async Task GetUserData(string uid) + { + return await _dbContext.Users + .Include(u => u.PostalAddress) + .Include(u => u.AccountBalance) + .FirstAsync(u => u.Id == uid); + } + [HttpGet("myhost")] public IActionResult MyHost () { return Ok(new { host = Request.ForHost() }); } - /// - /// Actually only updates the user's name. - /// - /// MyUpdate containing the new user name - /// Ok when all is ok. - [HttpPut("me")] - public async Task 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); - } + /// /// Updates the avatar /// @@ -184,11 +75,11 @@ namespace Yavsc.WebApi.Controllers public async Task SetAvatar() { var root = User.InitPostToFileSystem(null); - var user = await _userManager.FindByIdAsync(User.GetUserId()); + var user = await GetUserData(User.GetUserId()); if (Request.Form.Files.Count!=1) return new BadRequestResult(); var info = user.ReceiveAvatar(Request.Form.Files[0]); - await _userManager.UpdateAsync(user); + await _dbContext.SaveChangesAsync(); return Ok(info); } diff --git a/src/Api/Program.cs b/src/Api/Program.cs index a638add4..dd8a4606 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -28,7 +28,7 @@ internal class Program var builder = WebApplication.CreateBuilder(args); var services = builder.Services; - builder.Services.AddDistributedMemoryCache(); + // builder.Services.AddDistributedMemoryCache(); // accepts any access token issued by identity server // adds an authorization policy for scope 'scope1' @@ -63,22 +63,21 @@ internal class Program options.Authority = "https://localhost:5001"; options.TokenValidationParameters = new() { ValidateAudience = false }; - }); - services.AddDbContext(options => - options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); - services.AddScoped>(); + }); + + services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("Default"))); + + services.AddTransient() + .AddTransient() + .AddTransient(); + /* services.AddSingleton(); services.AddSingleton(); services.AddTransient(); services.AddIdentityApiEndpoints(); services.AddSession(); - - services.AddTransient() - .AddTransient() - .AddTransient() - .AddTransient, UserStore>() - .AddTransient(); - +*/ using (var app = builder.Build()) { if (app.Environment.IsDevelopment()) @@ -89,18 +88,20 @@ internal class Program .UseAuthentication() .UseAuthorization() .UseCors("default") - .UseEndpoints(endpoints => + /* .UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute() .RequireAuthorization(); - }); - app.MapIdentityApi().RequireAuthorization("ApiScope"); - + })*/ + + ; + // app.MapIdentityApi().RequireAuthorization("ApiScope"); + app.MapDefaultControllerRoute(); app.MapGet("/identity", (HttpContext context) => new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value })) ); - app.UseSession(); + // app.UseSession(); await app.RunAsync(); }; diff --git a/src/Yavsc.Server/Config.cs b/src/Yavsc.Server/Config.cs index a479c4cc..17d12cff 100644 --- a/src/Yavsc.Server/Config.cs +++ b/src/Yavsc.Server/Config.cs @@ -73,12 +73,12 @@ public static class Config ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) }, AllowedGrantTypes = GrantTypes.Code, + AlwaysIncludeUserClaimsInIdToken = true, RedirectUris = { "https://localhost:5003/signin-oidc", "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" }, - AllowOfflineAccess = true, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, diff --git a/src/Yavsc/Extensions/HostingExtensions.cs b/src/Yavsc/Extensions/HostingExtensions.cs index 251d9e3f..d781de86 100644 --- a/src/Yavsc/Extensions/HostingExtensions.cs +++ b/src/Yavsc/Extensions/HostingExtensions.cs @@ -4,6 +4,7 @@ using System.Security.Cryptography.X509Certificates; using Google.Apis.Util.Store; using IdentityServer8; using IdentityServer8.Services; +using IdentityServerHost.Quickstart.UI; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; @@ -138,28 +139,28 @@ public static class HostingExtensions { IServiceCollection services = LoadConfiguration(builder); - services.AddRazorPages(); + //services.AddRazorPages(); - services.AddSignalR(o => - { - o.EnableDetailedErrors = true; - }); - - AddIdentityDBAndStores(builder).AddDefaultTokenProviders(); - AddIdentityServer(builder); - + services.AddSession(); // TODO .AddServerSideSessionStore() - AddAuthentication(services, builder.Configuration); // Add the system clock service _ = services.AddSingleton(); _ = services.AddSingleton(); _ = services.AddSingleton(); _ = services.AddTransient(); + + AddIdentityDBAndStores(builder).AddDefaultTokenProviders(); + AddIdentityServer(builder); + services.AddSignalR(o => + { + o.EnableDetailedErrors = true; + }); + services.AddMvc(config => { /* var policy = new AuthorizationPolicyBuilder() @@ -207,6 +208,7 @@ public static class HostingExtensions services.AddSingleton(); + AddAuthentication(builder); // accepts any access token issued by identity server return builder.Build(); @@ -296,20 +298,16 @@ public static class HostingExtensions return services; } - private static void AddAuthentication(IServiceCollection services, IConfigurationRoot configurationRoot) + private static void AddAuthentication(WebApplicationBuilder builder) { - string? googleClientId = configurationRoot["Authentication:Google:ClientId"]; + IServiceCollection services=builder.Services; + IConfigurationRoot configurationRoot=builder.Configuration; + string? googleClientId = configurationRoot["Authentication:Google:ClientId"]; string? googleClientSecret = configurationRoot["Authentication:Google:ClientSecret"]; - var authenticationBuilder = services.AddAuthentication() - .AddJwtBearer("Bearer", options => - { - options.IncludeErrorDetails = true; - options.Authority = "https://localhost:5001"; - options.TokenValidationParameters = - new() { ValidateAudience = false }; - }); + var authenticationBuilder = services.AddAuthentication(); + if (googleClientId!=null && googleClientSecret!=null) authenticationBuilder.AddGoogle(options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; @@ -323,13 +321,23 @@ public static class HostingExtensions } private static IIdentityServerBuilder AddIdentityServer(WebApplicationBuilder builder) { - var identityServerBuilder = builder.Services.AddIdentityServer() + builder.Services.AddTransient(); + 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) .AddInMemoryClients(Config.Clients) .AddInMemoryApiScopes(Config.ApiScopes) + .AddAspNetIdentity() - // .AddProfileService() - .AddJwtBearerClientAuthentication() + .AddProfileService() ; 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()) @@ -398,13 +406,10 @@ public static class HostingExtensions app.UseIdentityServer(); app.UseAuthorization(); app.UseCors("default"); - app.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); - app.MapRazorPages() - .RequireAuthorization(); + app.MapDefaultControllerRoute(); + //pp.MapRazorPages(); app.MapHub("/chatHub"); - app.MapAreaControllerRoute("api", "api", "~/api/{controller}/{action}/{id?}"); + ConfigureWorkflow(); var services = app.Services; ILoggerFactory loggerFactory = services.GetRequiredService(); diff --git a/src/sampleWebAsWebApiClient/Program.cs b/src/sampleWebAsWebApiClient/Program.cs index 2aab156a..31426483 100644 --- a/src/sampleWebAsWebApiClient/Program.cs +++ b/src/sampleWebAsWebApiClient/Program.cs @@ -33,6 +33,8 @@ builder.Services options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0"; options.ResponseType = "code"; + options.Scope.Add("openid"); + options.Scope.Add("profile"); options.Scope.Add("scope2"); options.SaveTokens = true;