Api protected

This commit is contained in:
Paul Schneider
2025-02-16 22:40:51 +00:00
parent 968859babe
commit 84e58bb9eb
6 changed files with 82 additions and 182 deletions

View File

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

View File

@ -17,165 +17,56 @@ namespace Yavsc.WebApi.Controllers
[Authorize("ApiScope")]
public class ApiAccountController : Controller
{
private UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
readonly ApplicationDbContext _dbContext;
private readonly ILogger _logger;
public ApiAccountController(UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
RoleManager<IdentityRole> roleManager,
public ApiAccountController(
ILoggerFactory loggerFactory, ApplicationDbContext dbContext)
{
UserManager = userManager;
this.roleManager = roleManager;
_signInManager = signInManager;
_logger = loggerFactory.CreateLogger(nameof(ApiAccountController));
_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")]
public async Task<IActionResult> 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<ApplicationUser> 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() });
}
/// <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>
/// Updates the avatar
/// </summary>
@ -184,11 +75,11 @@ namespace Yavsc.WebApi.Controllers
public async Task<IActionResult> 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);
}

View File

@ -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<ApplicationDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
services.AddScoped<UserManager<ApplicationUser>>();
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>();
/*
services.AddSingleton<IConnexionManager, HubConnectionManager>();
services.AddSingleton<ILiveProcessor, LiveProcessor>();
services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
services.AddIdentityApiEndpoints<ApplicationUser>();
services.AddSession();
services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>()
.AddTransient<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>()
.AddTransient<DbContext>();
*/
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<ApplicationUser>().RequireAuthorization("ApiScope");
})*/
;
// app.MapIdentityApi<ApplicationUser>().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();
};

View File

@ -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,

View File

@ -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<YavscServerSideSessionStore>()
AddAuthentication(services, builder.Configuration);
// Add the system clock service
_ = services.AddSingleton<ISystemClock, SystemClock>();
_ = services.AddSingleton<IConnexionManager, HubConnectionManager>();
_ = services.AddSingleton<ILiveProcessor, LiveProcessor>();
_ = services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
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<IAuthorizationHandler, PermissionHandler>();
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<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)
.AddInMemoryClients(Config.Clients)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddAspNetIdentity<ApplicationUser>()
// .AddProfileService<ProfileService>()
.AddJwtBearerClientAuthentication()
.AddProfileService<ProfileService>()
;
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>("/chatHub");
app.MapAreaControllerRoute("api", "api", "~/api/{controller}/{action}/{id?}");
ConfigureWorkflow();
var services = app.Services;
ILoggerFactory loggerFactory = services.GetRequiredService<ILoggerFactory>();

View File

@ -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;