Merge authentication into Questions API
This commit is contained in:
@@ -1,13 +1,24 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using QuestionsApi.Services;
|
||||
using MongoDB.Bson;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.RateLimiting;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||
options.ForwardLimit = 2;
|
||||
options.KnownNetworks.Clear();
|
||||
options.KnownProxies.Clear();
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<MongoDatabaseProvider>();
|
||||
builder.Services.AddSingleton<QuestionStore>();
|
||||
@@ -26,10 +37,43 @@ builder.Services.AddSingleton<QuestionBookmarkStore>();
|
||||
builder.Services.AddSingleton<ReviewQueueStore>();
|
||||
builder.Services.AddSingleton<DashboardStore>();
|
||||
builder.Services.AddSingleton<UserProfileStore>();
|
||||
builder.Services.AddSingleton<AuthUserStore>();
|
||||
builder.Services.AddSingleton<TokenBlacklistStore>();
|
||||
builder.Services.AddSingleton<JwtTokenService>();
|
||||
builder.Services.AddSingleton<LegalPolicy>();
|
||||
builder.Services.AddSingleton<SteamAuthTransactionStore>();
|
||||
builder.Services.AddSingleton<SteamOpenIdService>();
|
||||
builder.Services.AddSingleton<PasswordResetStore>();
|
||||
builder.Services.AddSingleton<PasswordResetCodeService>();
|
||||
builder.Services.AddSingleton<IPasswordResetEmailSender, SmtpPasswordResetEmailSender>();
|
||||
builder.Services.AddSingleton<AuthDataInitializer>();
|
||||
builder.Services.AddSingleton<DonationStore>();
|
||||
builder.Services.AddSingleton<IStripeDonationGateway, StripeDonationGateway>();
|
||||
builder.Services.AddSingleton<ServiceMetrics>();
|
||||
builder.Services.AddHostedService<AnswerStatisticsMaintenanceService>();
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
static RateLimitPartition<string> Fixed(HttpContext context, int permits, TimeSpan window) =>
|
||||
RateLimitPartition.GetFixedWindowLimiter(
|
||||
context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
|
||||
_ => new FixedWindowRateLimiterOptions
|
||||
{
|
||||
PermitLimit = permits,
|
||||
Window = window,
|
||||
QueueLimit = 0,
|
||||
AutoReplenishment = true
|
||||
});
|
||||
options.AddPolicy("auth-register", context => Fixed(context, 5, TimeSpan.FromMinutes(15)));
|
||||
options.AddPolicy("auth-login", context => Fixed(context, 20, TimeSpan.FromMinutes(5)));
|
||||
options.AddPolicy("auth-refresh", context => Fixed(context, 60, TimeSpan.FromMinutes(5)));
|
||||
options.AddPolicy("password-reset-request", context => Fixed(context, 5, TimeSpan.FromMinutes(15)));
|
||||
options.AddPolicy("password-reset-confirm", context => Fixed(context, 10, TimeSpan.FromMinutes(15)));
|
||||
options.AddPolicy("steam-start", context => Fixed(context, 10, TimeSpan.FromMinutes(1)));
|
||||
options.AddPolicy("steam-poll", context => Fixed(context, 120, TimeSpan.FromMinutes(1)));
|
||||
options.AddPolicy("steam-complete", context => Fixed(context, 20, TimeSpan.FromMinutes(1)));
|
||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
});
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
@@ -78,12 +122,30 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
{
|
||||
OnTokenValidated = async context =>
|
||||
{
|
||||
var userId = context.Principal?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (!string.IsNullOrWhiteSpace(userId))
|
||||
var userId = context.Principal?.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
var profiles = context.HttpContext.RequestServices.GetRequiredService<UserProfileStore>();
|
||||
if (await profiles.IsDeletedAsync(userId)) context.Fail("Account deleted");
|
||||
context.Fail("User identity missing");
|
||||
return;
|
||||
}
|
||||
var services = context.HttpContext.RequestServices;
|
||||
var userTask = services.GetRequiredService<AuthUserStore>().GetByIdAsync(userId);
|
||||
var deletedTask = services.GetRequiredService<UserProfileStore>().IsDeletedAsync(userId);
|
||||
var jti = context.Principal?.FindFirstValue(JwtRegisteredClaimNames.Jti);
|
||||
var revokedTask = string.IsNullOrWhiteSpace(jti)
|
||||
? Task.FromResult(false)
|
||||
: services.GetRequiredService<TokenBlacklistStore>().ContainsAsync(jti);
|
||||
await Task.WhenAll(userTask, deletedTask, revokedTask);
|
||||
var user = await userTask;
|
||||
var tokenVersion = int.TryParse(context.Principal?.FindFirstValue("auth_ver"), out var parsedVersion)
|
||||
? parsedVersion
|
||||
: 0;
|
||||
if (user is null || user.AuthVersion != tokenVersion)
|
||||
context.Fail("Session revoked");
|
||||
else if (await deletedTask)
|
||||
context.Fail("Account deleted");
|
||||
else if (await revokedTask)
|
||||
context.Fail("Token revoked");
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -91,7 +153,9 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
var app = builder.Build();
|
||||
await app.Services.GetRequiredService<AuthDataInitializer>().InitializeAsync();
|
||||
|
||||
app.UseForwardedHeaders();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var started = System.Diagnostics.Stopwatch.StartNew();
|
||||
@@ -152,6 +216,7 @@ app.UseSwaggerUI(o =>
|
||||
o.SwaggerEndpoint("/swagger/v1/swagger.json", "Questions API v1");
|
||||
o.RoutePrefix = "swagger";
|
||||
});
|
||||
app.UseRateLimiter();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
Reference in New Issue
Block a user