From 80247fef2e89260706638bb9fa84e3dc5b855012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=98=89=E7=A5=A5=20=E8=A9=B9?= Date: Tue, 3 Feb 2026 11:50:22 +0800 Subject: [PATCH] updates --- Controllers/AdApiController.cs | 206 ++++++++++++++++++- Controllers/HomeController.cs | 8 + Models/DbTableClass.cs | 13 ++ Views/Home/Index.cshtml | 11 +- Views/Home/forgetwd.cshtml | 42 ++++ Views/Home/reset.cshtml | 41 ++++ appsettings.json | 5 +- wwwroot/assets/javascript/custom/Signin.js | 5 +- wwwroot/assets/javascript/custom/forgetwd.js | 47 +++++ wwwroot/assets/javascript/custom/reset.js | 92 +++++++++ 10 files changed, 463 insertions(+), 7 deletions(-) create mode 100644 Views/Home/forgetwd.cshtml create mode 100644 Views/Home/reset.cshtml create mode 100644 wwwroot/assets/javascript/custom/forgetwd.js create mode 100644 wwwroot/assets/javascript/custom/reset.js diff --git a/Controllers/AdApiController.cs b/Controllers/AdApiController.cs index 3cd22b4..8417f44 100644 --- a/Controllers/AdApiController.cs +++ b/Controllers/AdApiController.cs @@ -1,8 +1,13 @@ -using Microsoft.AspNetCore.Cors; +using Dapper; +using Dapper.Contrib.Extensions; +using MailKit.Security; +using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.Extensions.Options; +using MimeKit; using Newtonsoft.Json; using System.DirectoryServices; using System.DirectoryServices.AccountManagement; @@ -12,6 +17,7 @@ using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using static ad_login.Controllers.ApiController; using static DbTableClass; namespace ad_login.Controllers @@ -28,6 +34,10 @@ namespace ad_login.Controllers private readonly string _domain; private readonly string _baseDn; + DbConn dbConn = new DbConn(); + SqlConnection conn = new SqlConnection(GlobalClass.appsettings("ConnectionStrings:SQLConnectionString")); + + public AdApiController(IHttpContextAccessor httpContextAccessor, IWebHostEnvironment webHostEnvironment, PasswordManagementService passwordManagement, IOptions ldapSettings) { this._httpContextAccessor = httpContextAccessor; @@ -39,11 +49,174 @@ namespace ad_login.Controllers } [EnableCors("any")] + [Route("resetPassword")] + [SupportedOSPlatform("windows")] + public ActionResult ResetPassword(IFormCollection obj) + { + Result ret = new Result(); + + string new_password = obj["new_password"].ToString(); + string token = obj["token"].ToString(); + + if (new_password == "") + { + ret.message = "new_password is null"; + ret.err_code = "1003"; + ret.ret = "no"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + mailToken objMailToken = conn.QueryFirstOrDefault("select * from mailToken where mailToken_value=@mailToken_value and mailToken_useed='N' and mailToken_endDate>=getdate()", new { mailToken_value = token }); + + if (objMailToken == null) + { + ret.ret = "no"; + ret.err_code = "1001"; + ret.message = "token無效或過期,或是已經變更密碼過了,請重新申請忘記密碼步驟"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + + try { + DirectoryEntry entry = new DirectoryEntry($"LDAP://{_ldapServer}/{_baseDn}", GlobalClass.appsettings("LdapSettings:User"), GlobalClass.appsettings("LdapSettings:Password")); // 使用 LDAP 伺服器和基礎 DN 建立 DirectoryEntry 物件。 + DirectorySearcher mySearcher = new DirectorySearcher(entry); + mySearcher.Filter = ("(&(objectClass=user)(sAMAccountName=" + objMailToken.mailToken_account + "))"); + + SearchResult searchResult = mySearcher.FindOne(); + DirectoryEntry userEntry = searchResult.GetDirectoryEntry(); + userEntry.Invoke("SetPassword", new object[] { new_password }); + + ret.ret = "yes"; + + objMailToken.mailToken_useed = "Y"; + + conn.Update(objMailToken); + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + + } catch (Exception ex) { + ret.ret = "no"; + ret.err_code = "1002"; + ret.message = "修改密碼失敗,請聯絡系統管理員 原因為" + ex.Message; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + + + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + [EnableCors("any")] + [Route("forgetPassword")] + [SupportedOSPlatform("windows")] + public async Task ForgetPasswordAsync(IFormCollection obj) + { + Result ret = new Result(); + string userEmail = obj["userEmail"].ToString(); + string userAccount = obj["userAccount"].ToString(); + + + if (userAccount == "") + { + ret.message = "userAccount is null"; + ret.err_code = "1003"; + ret.ret = "no"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + if (userEmail == "") + { + ret.message = "userEmail is null"; + ret.err_code = "1004"; + ret.ret = "no"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, _domain, _baseDn, GlobalClass.appsettings("LdapSettings:User"), GlobalClass.appsettings("LdapSettings:Password"))) + { + UserPrincipal user = UserPrincipal.FindByIdentity(pc, userAccount); + if (user != null) + { + + if (user.Enabled == false) + { + ret.message = "此帳號已被停用"; + ret.err_code = "1007"; + ret.ret = "no"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + string emailaddress = user.EmailAddress ?? ""; + + if (emailaddress != userEmail) + { + ret.message = "帳號與Email不符,不是公司發給此帳號的Email"; + ret.err_code = "1005"; + ret.ret = "no"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + mailToken objMailToken = new mailToken(); + objMailToken.mailToken_value = GlobalClass.CreateRandomCode(24); + objMailToken.mailToken_mail = userEmail; + objMailToken.mailToken_endDate = DateTime.Now.AddHours(12); + objMailToken.mailToken_account = userAccount; + + conn.Insert(objMailToken); + + string htmlBody = ""; + + htmlBody += "

重設登入PC或公槽密碼確認信

"; + htmlBody += "
如果您有重設登入PC或公槽密碼需求的話,請點下面連結開始網頁重設密碼。如果沒有請忽略此信!"; + htmlBody += "

重設密碼連結:" + "https://ad-pass.bremen.com.tw/Home/reset?token=" + objMailToken.mailToken_value + ""; + htmlBody += "

PS:此連結於" + objMailToken.mailToken_endDate.ToString("yyyy/MM/dd HH:mm") + "前有效!"; + + MailRequest mailRequest = new MailRequest(); + mailRequest.ToEmail = userEmail; + + mailRequest.Body = htmlBody; + + mailRequest.Subject = "重設登入PC或公槽密碼確認信"; + + await SendEmailAsync(mailRequest); + + ret.ret = "yes"; + + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + else + { + ret.message = "User not found"; + ret.err_code = "1006"; + ret.ret = "no"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + } + + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + [EnableCors("any")] [Route("aduserList")] [SupportedOSPlatform("windows")] public ActionResult AduserList(IFormCollection obj) { Result ret = new Result(); + + string query_token = obj["query_token"].ToString(); + + if (query_token == "") { + ret.message = "query_token is null"; + ret.err_code = "1001"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + + if (query_token != GlobalClass.appsettings("LdapSettings:query_token")) + { + ret.message = "query_token is wrong"; + ret.err_code = "1002"; + return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); + } + List expiringUsers = []; DirectoryEntry entry = new DirectoryEntry($"LDAP://{_ldapServer}/{_baseDn}", GlobalClass.appsettings("LdapSettings:User"), GlobalClass.appsettings("LdapSettings:Password")); // 使用 LDAP 伺服器和基礎 DN 建立 DirectoryEntry 物件。 @@ -85,6 +258,37 @@ namespace ad_login.Controllers return Content(JsonConvert.SerializeObject(ret), "application/json;charset=utf-8"); } + public async Task SendEmailAsync(MailRequest mailRequest) + { + var email = new MimeMessage(); + email.Sender = MailboxAddress.Parse(GlobalClass.appsettings("MailServer:smtp_username")); + email.To.Add(MailboxAddress.Parse(GlobalClass.appsettings("MailServer:smtp_username"))); + + foreach (string item in mailRequest.ToEmail.Split(',')) + { + email.Bcc.Add(MailboxAddress.Parse(item)); + } + + email.Subject = "重設登入PC或公槽密碼確認信"; + + var builder = new BodyBuilder(); + + if (mailRequest.attach != null) + { + builder.Attachments.Add(mailRequest.attachName, mailRequest.attach); + } + + builder.HtmlBody = mailRequest.Body; + email.Body = builder.ToMessageBody(); + + using var smtp = new MailKit.Net.Smtp.SmtpClient(); + smtp.Connect(GlobalClass.appsettings("MailServer:smtp_host"), int.Parse(GlobalClass.appsettings("MailServer:smtp_port")), SecureSocketOptions.StartTls); + smtp.Authenticate(GlobalClass.appsettings("MailServer:smtp_username"), GlobalClass.appsettings("MailServer:smtp_password")); + await smtp.SendAsync(email); + smtp.Dispose(); + + } + public class Result { public string ret = "no"; diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index 3ddb3ed..1b495fd 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -13,6 +13,14 @@ namespace ad_login.Controllers _logger = logger; } + public IActionResult reset() + { + return View(); + } + public IActionResult forgetwd() + { + return View(); + } public IActionResult Index() { return View(); diff --git a/Models/DbTableClass.cs b/Models/DbTableClass.cs index 3031b2b..c880ba6 100644 --- a/Models/DbTableClass.cs +++ b/Models/DbTableClass.cs @@ -5,6 +5,19 @@ using Newtonsoft.Json; public class DbTableClass { + [Table("mailToken")] + public class mailToken + { + [JsonIgnore] + [Key] + public int mailToken_sn { get; set; } + public string mailToken_value { get; set; } = ""; + public string mailToken_mail { get; set; } = ""; + public string mailToken_account { get; set; } = ""; + public DateTime mailToken_endDate { get; set; } = DateTime.Now.AddDays(3); + public string mailToken_useed { get; set; } = "N"; + } + [Table("tags")] public class tags { diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 3a02cef..aa08f4d 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -10,13 +10,13 @@ } @section Script{ - + }
PC電腦與公槽密碼修改
-
ActiveDirectory Password Change
+
Windows的密碼與公槽密碼是連動的,更改後另一個密碼也會跟著更改

@@ -48,4 +48,9 @@
- \ No newline at end of file + + + + \ No newline at end of file diff --git a/Views/Home/forgetwd.cshtml b/Views/Home/forgetwd.cshtml new file mode 100644 index 0000000..84f6800 --- /dev/null +++ b/Views/Home/forgetwd.cshtml @@ -0,0 +1,42 @@ +@* + For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 +*@ +@{ + Layout = "_Signin"; +} + +@section Style { + +} + +@section Script{ + +} + +
+ +
忘記密碼重設
+
ActiveDirectory Password Reset
+
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ + diff --git a/Views/Home/reset.cshtml b/Views/Home/reset.cshtml new file mode 100644 index 0000000..a4edfcb --- /dev/null +++ b/Views/Home/reset.cshtml @@ -0,0 +1,41 @@ +@* + For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 +*@ + +@{ + Layout = "_Signin"; +} + +@section Style { + +} +@section Script { + +} + +
+ +
重設新密碼
+
ActiveDirectory Password Reset
+
+
+ + + +
+
+ +
+
+ +
+
+ +
+
+ + +
+ +
+ \ No newline at end of file diff --git a/appsettings.json b/appsettings.json index 8139a24..ce7b600 100644 --- a/appsettings.json +++ b/appsettings.json @@ -11,10 +11,11 @@ "Domain": "office.bremen.tw", "BaseDn": "DC=office,DC=bremen,DC=tw", "User": "Administrator", // AD 的使用者名稱 - "Password": "<%Bremen%>" // AD 的使用者密碼 + "Password": "<%Bremen%>", // AD 的使用者密碼 + "query_token": "G3nMiGM6H9FNFUROf3wh7SmqJpQV30" }, "ConnectionStrings": { - "SQLConnectionString": "Data Source=sql.bremen.com.tw;Initial Catalog=bremen_db;User ID=bremen_db;Password=4zI5j?45p;Max Pool Size=500;" + "SQLConnectionString": "Data Source=sql.bremen.com.tw;Initial Catalog=bremen_db;User ID=bremen_db;Password=4zI5j?45p;Max Pool Size=500;TrustServerCertificate=True" }, "MailServer": { "smtp_host": "smtp.gmail.com", diff --git a/wwwroot/assets/javascript/custom/Signin.js b/wwwroot/assets/javascript/custom/Signin.js index c7361d3..cd53c86 100644 --- a/wwwroot/assets/javascript/custom/Signin.js +++ b/wwwroot/assets/javascript/custom/Signin.js @@ -10,7 +10,10 @@ return; } - + if (!confirm('請注意,Windows的密碼與公槽密碼是連動的,更改後登入Windows與登入公槽的密碼會跟著一起改!')) { + + return; + } if (new_pwd != chk_pwd) { alert("新密碼與新密碼再確認不一致!"); diff --git a/wwwroot/assets/javascript/custom/forgetwd.js b/wwwroot/assets/javascript/custom/forgetwd.js new file mode 100644 index 0000000..62e063f --- /dev/null +++ b/wwwroot/assets/javascript/custom/forgetwd.js @@ -0,0 +1,47 @@ +$(document).ready(function () { + + $('#checkBtn').on('click', function () { + var user_name = $('#inputUser').val(); + var user_email = $('#inputEmail').val(); + + var formData = { + userAccount: user_name, + userEmail: user_email + } + $.ajax({ + url: "/adApi/forgetPassword", + type: "POST", + data: formData, + success: function (data, textStatus, jqXHR) { + if (data.ret === "yes") { + + alert("已寄出密碼重設驗證信已經寄出,請至信箱收信重設!"); + $('#inputUser').val(''); + $('#inputEmail').val(''); + + } else { + if (data.err_code == "1003") { + alert('無此帳號!'); + return; + } + + if (data.err_code == "1004") { + alert('無此Email!'); + return; + } + + if (data.err_code == "1006") { + alert('無此帳號!'); + return; + } + + alert(data.message); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + alert('網路或伺服器發生錯誤,請稍後重試!'); + } + }); + }); + +}); \ No newline at end of file diff --git a/wwwroot/assets/javascript/custom/reset.js b/wwwroot/assets/javascript/custom/reset.js new file mode 100644 index 0000000..be7fe95 --- /dev/null +++ b/wwwroot/assets/javascript/custom/reset.js @@ -0,0 +1,92 @@ +(function ($, document) { + (function ($) { + $.UrlParam = function (name) { + //宣告正規表達式 + var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); + /* + * window.location.search 獲取URL ?之後的參數(包含問號) + * substr(1) 獲取第一個字以後的字串(就是去除掉?號) + * match(reg) 用正規表達式檢查是否符合要查詢的參數 + */ + var r = window.location.search.substr(1).match(reg); + //如果取出的參數存在則取出參數的值否則回穿null + if (r != null) return unescape(r[2]); return null; + } + })(jQuery); + + +})(jQuery, document); + +$(document).ready(function () { + $('#token').val($.UrlParam("token")); + + $('#resetBtn').on('click', function () { + + var new_pwd = $('#inputNewPassword').val(); + var chk_pwd = $('#inputChkPassword').val(); + var token = $('#token').val(); + if (token === null || token === "") { + alert("無效的密碼重設驗證,請重新申請!"); + return; + } + if (pwdStrengthChecke(new_pwd, 6, 2) === false) { + alert("新密碼強度不足,請至少包含英文小寫、英文大寫、阿拉伯數字、特殊符號四項中的2項,且長度至少為6個字元!"); + return; + } + if (new_pwd != chk_pwd) { + alert("新密碼與新密碼再確認不一致!"); + return; + } + + if (!confirm('請注意,Windows的密碼與公槽密碼是連動的,更改後登入Windows與登入公槽的密碼會跟著一起改!')) { + return; + } + + var formData = { + new_password: new_pwd, + token: token + } + $.ajax({ + url: "/adApi/resetPassword", + type: "POST", + data: formData, + success: function (data, textStatus, jqXHR) { + if (data.ret === "yes") { + alert("密碼重設成功,請使用新密碼登入PC與公槽!"); + + $('#inputNewPassword').val(''); + $('#inputChkPassword').val(''); + + } else { + if (data.err_code == "1005") { + alert('無效的密碼重設驗證,請重新申請!'); + return; + } + alert(data.message); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + alert('網路或伺服器發生錯誤,請稍後重試!'); + } + }); + }); +}); + +function pwdStrengthChecke(str, length, strength) { + if (!str || str.length < length) { + return false + } + let n = 0 + const regex = [ + /[a-z]/, + /[A-Z]/, + /[0-9]/, + /[`~!@#$%^&*()_+=,<>\-\[\]\{\}\:;\.'"\/\\?\|]/ + ] + for (const r of regex) { + if (str.match(r)) { + n++ + } + } + return n >= strength +} \ No newline at end of file