rename server

This commit is contained in:
2025-04-14 21:38:30 -04:00
parent 6410d84754
commit 93c04b9535
13 changed files with 1 additions and 1 deletions

View File

@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using System.Text.Encodings.Web;
namespace AobaV2;
internal class AobaAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public AobaAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
throw new System.NotImplementedException();
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
//Don't challenge API requests
if (OriginalPath.StartsWithSegments("/api"))
{
Response.StatusCode = StatusCodes.Status401Unauthorized;
Response.BodyWriter.Complete();
return Task.CompletedTask;
}
//Redirect to login page
Response.Redirect($"/auth/login?ReturnUrl={Uri.EscapeDataString(OriginalPath)}");
return Task.CompletedTask;
}
protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
{
//Don't show error page for api requests
if (OriginalPath.StartsWithSegments("/api"))
{
Response.StatusCode = StatusCodes.Status403Forbidden;
Response.BodyWriter.Complete();
return Task.CompletedTask;
}
//Show Error page
Response.Redirect($"/error/{StatusCodes.Status403Forbidden}");
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>9ffcc706-7f1b-48e3-bf30-eab69a90fded</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" />
<PackageReference Include="MaybeError" Version="1.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.3" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.7.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="3.0.71" />
<PackageReference Include="MongoDB.Analyzer" Version="1.5.0" />
<PackageReference Include="MongoDB.Bson" Version="3.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AobaCore\AobaCore.csproj" />
</ItemGroup>
</Project>

74
AobaServer/AuthInfo.cs Normal file
View File

@@ -0,0 +1,74 @@
using MongoDB.Bson.IO;
using System.Security.Cryptography;
using System.Text.Json;
namespace AobaV2;
public class AuthInfo
{
public required string Issuer;
public required string Audience;
public required byte[] SecureKey;
/// <summary>
/// Save this auth into in a json format to the sepcified file
/// </summary>
/// <param name="path">File path</param>
/// <returns></returns>
public AuthInfo Save(string path)
{
File.WriteAllText(path, JsonSerializer.Serialize(this));
return this;
}
/// <summary>
/// Generate a new Auth Info with newly generated keys
/// </summary>
/// <param name="issuer"></param>
/// <param name="audience"></param>
/// <returns></returns>
public static AuthInfo Create(string issuer, string audience)
{
var auth = new AuthInfo
{
Issuer = issuer,
Audience = audience,
SecureKey = GenetateJWTKey()
};
return auth;
}
/// <summary>
/// Load auth info from a json file
/// </summary>
/// <param name="path">File path</param>
/// <returns></returns>
internal static AuthInfo? Load(string path)
{
return JsonSerializer.Deserialize<AuthInfo>(File.ReadAllText(path));
}
internal static AuthInfo LoadOrCreate(string path, string issuer, string audience)
{
if (File.Exists(path))
{
var loaded = Load(path);
if(loaded != null)
return loaded;
}
var info = Create(issuer, audience);
info.Save(path);
return info;
}
/// <summary>
/// Generate a new key for use by JWT
/// </summary>
/// <returns></returns>
public static byte[] GenetateJWTKey(int size = 64)
{
var key = new byte[size];
RandomNumberGenerator.Fill(key);
return key;
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
namespace AobaV2.Controllers.Api;
[Route("/api/auth")]
public class AuthApi : ControllerBase
{
[HttpGet("login")]
public async Task<IActionResult> LoginAsync()
{
throw new NotImplementedException();
}
[HttpGet("register")]
public async Task<IActionResult> RegisterAsync()
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AobaV2.Controllers;
[AllowAnonymous]
[Route("auth")]
public class AuthController : Controller
{
[HttpGet("login")]
public IActionResult Login([FromQuery] string returnUrl)
{
ViewData["returnUrl"] = returnUrl;
return View();
}
[HttpGet("register/{token}")]
public IActionResult Register(string token)
{
return View(token);
}
}

View File

@@ -0,0 +1,27 @@
using AobaCore;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using MongoDB.Driver;
namespace AobaV2.Controllers;
[Route("/m")]
public class MediaController(MediaService media) : Controller
{
[HttpGet("{id}")]
public IActionResult Media(ObjectId id)
{
return View();
}
[HttpGet("/i/{id}/{*rest}")]
public async Task<IActionResult> LegacyRedirectAsync(ObjectId id, string rest, [FromServices] AobaService aoba)
{
var media = await aoba.GetMediaAsync(id);
if (media == null)
return NotFound();
return LocalRedirectPermanent($"/m/{media.Id}/{rest}");
}
}

31
AobaServer/Dockerfile Normal file
View File

@@ -0,0 +1,31 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["AobaV2/AobaV2.csproj", "AobaV2/"]
RUN dotnet restore "./AobaV2/AobaV2.csproj"
RUN npm install
COPY . .
WORKDIR "/src/AobaV2"
RUN dotnet build "./AobaV2.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./AobaV2.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AobaV2.dll"]

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using MongoDB.Bson;
namespace AobaV2.Models;
public class BsonIdModelBinderProvider : IModelBinderProvider
{
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(ObjectId))
return new BsonIdModelBinder();
return default;
}
}
public class BsonIdModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (value == ValueProviderResult.None)
return Task.CompletedTask;
if (ObjectId.TryParse(value.FirstValue, out var id))
bindingContext.Result = ModelBindingResult.Success(id);
else
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
}

100
AobaServer/Program.cs Normal file
View File

@@ -0,0 +1,100 @@
using AobaCore;
using AobaV2;
using AobaV2.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services
.AddControllers(opt => opt.ModelBinderProviders.Add(new BsonIdModelBinderProvider()));
var authInfo = AuthInfo.LoadOrCreate("Auth.json", "aobaV2", "aoba");
builder.Services.AddSingleton(authInfo);
var signingKey = new SymmetricSecurityKey(authInfo.SecureKey);
var validationParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = authInfo.Issuer,
ValidateAudience = true,
ValidAudience = authInfo.Audience,
ValidateLifetime = false,
ClockSkew = TimeSpan.FromMinutes(1),
};
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "Aoba";
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => //Bearer auth
{
options.TokenValidationParameters = validationParams;
options.Events = new JwtBearerEvents
{
OnMessageReceived = ctx => //Retreive token from cookie if not found in headers
{
if (string.IsNullOrWhiteSpace(ctx.Token))
ctx.Token = ctx.Request.Cookies["token"];
if (string.IsNullOrWhiteSpace(ctx.Token))
ctx.Token = ctx.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");
return Task.CompletedTask;
},
OnAuthenticationFailed = ctx =>
{
ctx.Response.Cookies.Append("token", "", new CookieOptions
{
MaxAge = TimeSpan.Zero,
Expires = DateTime.Now
});
ctx.Options.ForwardChallenge = "Aoba";
return Task.CompletedTask;
}
};
}).AddScheme<AuthenticationSchemeOptions, AobaAuthenticationHandler>("Aoba", cfg => { });
builder.Services.AddAoba();
builder.Services.Configure<FormOptions>(opt =>
{
opt.ValueLengthLimit = int.MaxValue;
opt.MultipartBodyLengthLimit = int.MaxValue;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapStaticAssets();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.WithStaticAssets();
app.Run();

View File

@@ -0,0 +1,31 @@
{
"profiles": {
"http": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5164"
},
"https": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7167;http://localhost:5164"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "https://json.schemastore.org/launchsettings.json"
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}