master
嘉祥 詹 2026-02-03 11:50:22 +08:00
parent ae62d249a2
commit 80247fef2e
10 changed files with 463 additions and 7 deletions

View File

@ -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> 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<mailToken>("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<mailToken>(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<ActionResult> 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<mailToken>(objMailToken);
string htmlBody = "";
htmlBody += "<H2>重設登入PC或公槽密碼確認信</H2>";
htmlBody += "<br/>如果您有重設登入PC或公槽密碼需求的話請點下面連結開始網頁重設密碼。如果沒有請忽略此信!";
htmlBody += "<br/><br/>重設密碼連結:<a href='" + "https://ad-pass.bremen.com.tw/Home/reset?token=" + objMailToken.mailToken_value + "' target='_blank'>" + "https://ad-pass.bremen.com.tw/Home/reset?token=" + objMailToken.mailToken_value + "</a>";
htmlBody += "<br/><br/>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<String> 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";

View File

@ -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();

View File

@ -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
{

View File

@ -10,13 +10,13 @@
}
@section Script{
<script src="~/assets/javascript/custom/Signin.js?v=5"></script>
<script src="~/assets/javascript/custom/Signin.js?v=6"></script>
}
<!-- .form-group -->
<div class="text-center">
<div style="font-size:25px; font-weight:bold;">PC電腦與公槽密碼修改</div>
<div style="font-size:14.6px;">ActiveDirectory Password Change</div>
<div style="font-size:14.6px;">Windows的密碼與公槽密碼是連動的更改後另一個密碼也會跟著更改</div>
<br />
</div>
<!-- /.form-group -->
@ -48,4 +48,9 @@
<div class="form-group">
<button class="btn btn-lg btn-primary btn-block" type="button" id="chgBtn">更改密碼</button>
</div><!-- /.form-group -->
<!-- .form-group -->
<!-- .form-group -->
<!-- recovery links -->
<div class="text-center pt-3">
<a href="/Home/forgetwd" class="link">忘記密碼?</a>
</div>
<!-- /recovery links -->

View File

@ -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{
<script src="~/assets/javascript/custom/forgetwd.js?v=6"></script>
}
<!-- .form-group -->
<div class="text-center">
<div style="font-size:25px; font-weight:bold;">忘記密碼重設</div>
<div style="font-size:14.6px;">ActiveDirectory Password Reset</div>
<br />
</div>
<!-- /.form-group -->
<!-- .form-group -->
<div class="form-group">
<div class="form-label-group">
<input type="text" id="inputUser" class="form-control" placeholder="PC或公槽帳號(例:vivian.lin)" autofocus=""> <label for="inputUser">PC或公槽帳號(例:vivian.lin)</label>
</div>
</div><!-- /.form-group -->
<!-- .form-group -->
<div class="form-group">
<div class="form-label-group">
<input type="text" id="inputEmail" class="form-control" placeholder="公司Email"> <label for="inputEmail">公司Email</label>
</div>
</div>
<!-- /.form-group
<!-- .form-group -->
<div class="form-group">
<button class="btn btn-lg btn-primary btn-block" type="button" id="checkBtn">寄出重設驗證信</button>
</div><!-- /.form-group -->
<!-- .form-group -->

41
Views/Home/reset.cshtml Normal file
View File

@ -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 {
<script src="~/assets/javascript/custom/reset.js?v=6"></script>
}
<!-- .form-group -->
<div class="text-center">
<div style="font-size:25px; font-weight:bold;">重設新密碼</div>
<div style="font-size:14.6px;">ActiveDirectory Password Reset</div>
<br />
</div>
<!-- /.form-group -->
<!-- .form-group -->
<input type="hidden" id="token"/>
<div class="form-group">
<div class="form-label-group">
<input type="password" id="inputNewPassword" class="form-control" placeholder="新密碼"> <label for="inputNewPassword">新密碼</label>
</div>
</div><!-- /.form-group -->
<!-- .form-group -->
<div class="form-group">
<div class="form-label-group">
<input type="password" id="inputChkPassword" class="form-control" placeholder="新密碼再確認"> <label for="inputChkPassword">新密碼再確認</label>
</div>
</div><!-- /.form-group -->
<!-- .form-group -->
<div class="form-group">
<button class="btn btn-lg btn-primary btn-block" type="button" id="resetBtn">重設密碼</button>
</div><!-- /.form-group -->
<!-- .form-group -->

View File

@ -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",

View File

@ -10,7 +10,10 @@
return;
}
if (!confirm('請注意Windows的密碼與公槽密碼是連動的更改後登入Windows與登入公槽的密碼會跟著一起改!')) {
return;
}
if (new_pwd != chk_pwd) {
alert("新密碼與新密碼再確認不一致!");

View File

@ -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('網路或伺服器發生錯誤,請稍後重試!');
}
});
});
});

View File

@ -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
}