Cli: Add HTTP Basic Auth

This commit is contained in:
genteure 2022-06-08 00:15:05 +08:00
parent e01d12c36a
commit 4d0e52c87d
5 changed files with 127 additions and 3 deletions

View File

@ -39,7 +39,8 @@ namespace BililiveRecorder.Cli
var cmd_run = new Command("run", "Run BililiveRecorder in standard mode")
{
new Option<string?>(new []{ "--http-bind", "--bind", "-b" }, () => null, "Bind address for http service"),
// new Option<int?>(new []{ "--http-port", "--port", "-p" }, () => null, "Port number for http service"),
new Option<string?>(new []{ "--http-basic-user" }, () => null, "Web interface username"),
new Option<string?>(new []{ "--http-basic-pass" }, () => null, "Web interface password"),
new Option<LogEventLevel>(new []{ "--loglevel", "--log", "-l" }, () => LogEventLevel.Information, "Minimal log level output to console"),
new Option<LogEventLevel>(new []{ "--logfilelevel", "--flog" }, () => LogEventLevel.Debug, "Minimal log level output to file"),
new Option<string?>(new []{ "--cert-pem-path", "--pem" }, "Path of the certificate pem file"),
@ -55,7 +56,8 @@ namespace BililiveRecorder.Cli
var cmd_portable = new Command("portable", "Run BililiveRecorder in config-less mode")
{
new Option<string?>(new []{ "--http-bind", "--bind", "-b" }, () => null, "Bind address for http service"),
// new Option<int?>(new []{ "--http-port", "--port", "-p" }, () => null, "Port number for http service"),
new Option<string?>(new []{ "--http-basic-user" }, () => null, "Web interface username"),
new Option<string?>(new []{ "--http-basic-pass" }, () => null, "Web interface password"),
new Option<LogEventLevel>(new []{ "--loglevel", "--log", "-l" }, () => LogEventLevel.Information, "Minimal log level output to console"),
new Option<LogEventLevel>(new []{ "--logfilelevel", "--flog" }, () => LogEventLevel.Debug, "Minimal log level output to file"),
new Option<string?>(new []{ "--cert-pem-path", "--pem" }, "Path of the certificate pem file"),
@ -170,6 +172,11 @@ namespace BililiveRecorder.Cli
.ConfigureServices(services =>
{
services.AddSingleton(recorderAccessProxy);
if (sharedArguments.HttpBasicUser is not null || sharedArguments.HttpBasicPass is not null)
{
services.AddSingleton(new BasicAuthCredential(sharedArguments.HttpBasicUser ?? string.Empty, sharedArguments.HttpBasicPass ?? string.Empty));
}
})
.ConfigureWebHost(webBuilder =>
{
@ -419,7 +426,9 @@ namespace BililiveRecorder.Cli
public string? HttpBind { get; set; } = null;
// public int? HttpPort { get; set; } = null;
public string? HttpBasicUser { get; set; } = null;
public string? HttpBasicPass { get; set; } = null;
public string? CertPemPath { get; set; } = null;

View File

@ -0,0 +1,19 @@
using System;
using System.Text;
namespace BililiveRecorder.Web
{
public class BasicAuthCredential
{
public BasicAuthCredential(string username, string password)
{
this.Username = username ?? throw new ArgumentNullException(nameof(username));
this.Password = password ?? throw new ArgumentNullException(nameof(password));
this.EncoededValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
}
public string Username { get; }
public string Password { get; }
public string EncoededValue { get; }
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using Microsoft.Net.Http.Headers;
namespace BililiveRecorder.Web
{
public class BasicAuthMiddleware
{
private readonly RequestDelegate next;
private readonly ManifestEmbeddedFileProvider fileProvider;
private readonly BasicAuthCredential? credential;
private const string BasicAndSpace = "Basic ";
private static string? Html401Page;
public BasicAuthMiddleware(RequestDelegate next, ManifestEmbeddedFileProvider fileProvider, BasicAuthCredential? credential)
{
this.next = next ?? throw new ArgumentNullException(nameof(next));
this.fileProvider = fileProvider ?? throw new ArgumentNullException(nameof(fileProvider));
this.credential = credential;
}
public Task InvokeAsync(HttpContext context)
{
if (this.credential is null)
{
// 没有启用身份验证
return this.next(context);
}
string headerValue = context.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(headerValue) ||
!headerValue.StartsWith(BasicAndSpace, StringComparison.OrdinalIgnoreCase))
{
return this.ResponseWith401Async(context);
}
var requestCredential = headerValue[BasicAndSpace.Length..].Trim();
if (string.IsNullOrEmpty(requestCredential))
{
return this.ResponseWith401Async(context);
}
if (this.credential.EncoededValue.Equals(requestCredential, StringComparison.Ordinal))
{
return this.next(context);
}
else
{
return this.ResponseWith401Async(context);
}
}
private async Task ResponseWith401Async(HttpContext context)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "text/html";
context.Response.Headers.Append(HeaderNames.WWWAuthenticate, $"{BasicAndSpace}realm=\"BililiveRecorder {GitVersionInformation.FullSemVer}\"");
if (Html401Page is null)
{
using var file = this.fileProvider.GetFileInfo("/401.html").CreateReadStream();
using var reader = new StreamReader(file);
var str = await reader.ReadToEndAsync().ConfigureAwait(false);
Html401Page = str.Replace("__VERSION__", GitVersionInformation.FullSemVer).Replace("__FULL_VERSION__", GitVersionInformation.InformationalVersion);
}
await context.Response.WriteAsync(Html401Page, System.Text.Encoding.UTF8).ConfigureAwait(false);
}
}
}

View File

@ -122,6 +122,8 @@ namespace BililiveRecorder.Web
{
const string PAGE404 = "/404.html";
app.UseMiddleware<BasicAuthMiddleware>();
app.UseCors().UseWebSockets();
app.Use(static next => async context =>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<meta name="robots" content="noindex">
<meta content="width=device-width,initial-scale=1" name=viewport>
<title>401 未登录 - B站录播姬 __VERSION__</title>
<style>*{text-align:center;}p{font-size:10px;}</style>
</head>
<body>
<h1 style="color:red;font-weight:bold;font-size:3em;">HTTP 401</h1>
<p title="__FULL_VERSION__">
<a href="https://rec.danmuji.org">B站录播姬</a> __VERSION__
</p>
</body>
</html>