From 3228d9df2485709d1239672323fa34af4246ea68 Mon Sep 17 00:00:00 2001 From: trueai-org Date: Tue, 19 Nov 2024 11:55:10 +0800 Subject: [PATCH] v6.0.0-beta.7 --- .../Controllers/TwoFAController.cs | 53 ++++++++++++ src/Midjourney.Captcha.API/Startup.cs | 3 + .../wwwroot/favicon.ico | Bin 0 -> 16958 bytes .../GlobalConfiguration.cs | 2 +- .../Services/TwoFAHelper.cs | 77 ++++++++++++++++++ 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/Midjourney.Captcha.API/Controllers/TwoFAController.cs create mode 100644 src/Midjourney.Captcha.API/wwwroot/favicon.ico create mode 100644 src/Midjourney.Infrastructure/Services/TwoFAHelper.cs diff --git a/src/Midjourney.Captcha.API/Controllers/TwoFAController.cs b/src/Midjourney.Captcha.API/Controllers/TwoFAController.cs new file mode 100644 index 00000000..32da4221 --- /dev/null +++ b/src/Midjourney.Captcha.API/Controllers/TwoFAController.cs @@ -0,0 +1,53 @@ +using Microsoft.AspNetCore.Mvc; +using Midjourney.Infrastructure.Services; + +namespace Midjourney.Captcha.API.Controllers +{ + /// + /// 2FA + /// + [ApiController] + [Route("/")] + public class TwoFAController : Controller + { + [HttpGet("{secret}")] + public IActionResult GetOtp(string secret) + { + if (string.IsNullOrEmpty(secret)) + { + return BadRequest("Missing secret parameter"); + } + + var loadTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var otp = TwoFAHelper.GenerateOtp(secret, loadTime); + + var remainingTime = TwoFAHelper.CalculateRemainingTime(loadTime); + + var htmlContent = $@" + + + 2FA Auth + + + +
{{
+  ""token"": ""{otp}""
+}}
+ + +"; + return Content(htmlContent, "text/html"); + } + } +} \ No newline at end of file diff --git a/src/Midjourney.Captcha.API/Startup.cs b/src/Midjourney.Captcha.API/Startup.cs index 68d70352..b6c285f0 100644 --- a/src/Midjourney.Captcha.API/Startup.cs +++ b/src/Midjourney.Captcha.API/Startup.cs @@ -139,6 +139,9 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env) app.UseSwaggerUI(); } + app.UseDefaultFiles(); // 启用默认文件(index.html) + app.UseStaticFiles(); // 配置提供静态文件 + app.UseCors(builder => { builder.AllowAnyMethod().AllowAnyHeader().SetIsOriginAllowed(origin => true).AllowCredentials(); diff --git a/src/Midjourney.Captcha.API/wwwroot/favicon.ico b/src/Midjourney.Captcha.API/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5b6e32940dce0d1f58db0499818ad8e0a80d2840 GIT binary patch literal 16958 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

g- zUGyUCP7XM<LV{A?(^wvin0l_8;TgpHvX;x8mEMKwR4sSl9OL zKg9d%i%k`?VED{J80g}_P`?M9Iqd~q7v@3tg@slf7env1oe#to>J6Gs@V;+6!uoSV zk6WC&-RI?wG{zs=)2C)NrZX+ho|+zg?(DnYjhvmXyLiKDQhV%b^S0HIx0_c+;qk() z*uH+{mo-a6eVVuz=C|`c_^#9Q0(NlDV^7!x>6>Q^O#&-V2gocRYP^Ti*56XFv>xX_>BbE5`Sok9(o@*vj!WmFEpw zhgr}(~b#4cUONh(Vd_GO6d9W|8>G3x)ZBH<^8-is@t?c4%rJ`oApfz{A3v=olC*uoc20Z#QzOFMP=Jv=cuv!Y4u zI`Pc0_uxayAIFkY;c#m5{GpVTACDv_e?5?#l-sFHe6?GV3Mbz|=-}T`^sq*;TQBTU z>|uR=yNmrv&=J;MIFML2QJc}Qt}&YYSSx0Q``U&)kBarJ$i-mT*CU!FVDqx zKXF-4*pcE@-gpbLJOBB7_wOo6|4#PokP)0cY-W1ygpvBpQ47|kGgG0nWdR)8k_gb? z4=1y-B_}d7j~-3Wh(4T=^>SZw3Jl0npvU2-hfJqh-?Jv!U%KI*58Ei=%1 zU4k;gtKQf&L6@uo-Nq)U%ThyH{cVV&cNW5y#~|$a@|CgTqzvJfK#wZv$8c5;?X9wAEMqe}f zy`dl~2~Ka^0=Ei^;bcY@5PoPSyLYFg!QqT-sE<7Y*WN-H@Vmnw;EslBJ#J0vaeD-s zkjv2K{RvcfjX+6ODICZsCp{b-ljqNy(D#UJ{{sk!-#{h8y^ELN@#TMx-#%YMKVq8x z=QRAy`0&{@Qd|=m(7QN{9~=AO^|A4${#$fB{Aqmp)A%D@hg3uwza5)`7jM#38awU9 zet3~))5iD1_^0rCzkd7{UQ9Ef5#U95HckY1O8;d6OrYP}Pa_?v`pzJ?4Rb@&+JJ0PaFIGPj)pR SJLxC%7SxX~J{w /// 版本号 /// - public static string Version { get; set; } = "v6.0.0-beta.6"; + public static string Version { get; set; } = "v6.0.0-beta.7"; ///

/// 全局配置项 diff --git a/src/Midjourney.Infrastructure/Services/TwoFAHelper.cs b/src/Midjourney.Infrastructure/Services/TwoFAHelper.cs new file mode 100644 index 00000000..906012b6 --- /dev/null +++ b/src/Midjourney.Infrastructure/Services/TwoFAHelper.cs @@ -0,0 +1,77 @@ +using System.Globalization; +using System.Security.Cryptography; +using System.Text; + +namespace Midjourney.Infrastructure.Services +{ + /// + /// 2fa 验证帮助类 + /// https://github.com/wuzf/2fa/blob/main/worker.js + /// + public class TwoFAHelper + { + public static string GenerateOtp(string secret) + { + var loadTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var otp = GenerateOtp(secret, loadTime); + return otp; + } + + public static string GenerateOtp(string secret, long loadTime) + { + const int timeStep = 30; + + var counter = loadTime / timeStep; + var counterBytes = BitConverter.GetBytes(counter); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(counterBytes); + } + + var key = Base32Decode(secret); + + using var hmac = new HMACSHA1(key); + var hash = hmac.ComputeHash(counterBytes); + + var offset = hash[^1] & 0xF; + var binaryCode = ((hash[offset] & 0x7F) << 24) | + ((hash[offset + 1] & 0xFF) << 16) | + ((hash[offset + 2] & 0xFF) << 8) | + (hash[offset + 3] & 0xFF); + + var otp = (binaryCode % 1_000_000).ToString("D6", CultureInfo.InvariantCulture); + return otp; + } + + private static byte[] Base32Decode(string base32) + { + const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + base32 = base32.ToUpperInvariant(); + + var bits = new StringBuilder(); + foreach (var c in base32) + { + var index = alphabet.IndexOf(c); + if (index < 0) throw new ArgumentException("Invalid Base32 character."); + bits.Append(Convert.ToString(index, 2).PadLeft(5, '0')); + } + + var byteList = new List(); + for (int i = 0; i + 8 <= bits.Length; i += 8) + { + byteList.Add(Convert.ToByte(bits.ToString(i, 8), 2)); + } + + return byteList.ToArray(); + } + + public static int CalculateRemainingTime(long loadTime) + { + const int timeStep = 30; + var epochTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var currentCounter = epochTime / timeStep; + var expirationTime = (currentCounter + 1) * timeStep; + return (int)(expirationTime - loadTime); + } + } +} \ No newline at end of file