552 lines
22 KiB
C#
552 lines
22 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Net.Mime;
|
|
using System.Text.Json;
|
|
using Kafka.Lib.Auth;
|
|
using Kafka.Lib.Auth.Model;
|
|
using MassTransit;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using OpenWarehouse.auth.api.Common.AuthTokenRepo;
|
|
using OpenWarehouse.auth.api.Common.CustomIdentifyManager;
|
|
using OpenWarehouse.auth.lib.Model;
|
|
using OpenWarehouse.auth.lib.Model.Account;
|
|
|
|
namespace OpenWarehouse.auth.api.Controller;
|
|
|
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
|
[ApiController]
|
|
[Route("[controller]/[action]")]
|
|
public class Account(
|
|
RoleManager<ApplicationRole> roleManager,
|
|
PrivilegeManager privilegeManager,
|
|
UserManager<ApplicationUser> userManager,
|
|
AuthTokenRepo tokenRepo,
|
|
ITopicProducer<UserChangeMessageType> userChangeProducer,
|
|
ITopicProducer<UserChangeClaimsMessageType> userClaimChangeProducer,
|
|
ILogger<Account> logger) : ControllerBase
|
|
{
|
|
[HttpDelete]
|
|
[HasPrivilege(GlobalPrivilage.Privilege.GlobalDeleteUser)]
|
|
[Consumes(MediaTypeNames.Application.Json, otherContentTypes: [MediaTypeNames.Application.Xml])]
|
|
public async Task<ActionResult<DeleteUserReturnModel>> DeleteUser([FromBody]DeleteUserRequestModel requestModel)
|
|
{
|
|
logger.LogInformation("DeleteUser called with UserId: {UserId}", requestModel.UserId);
|
|
|
|
if (requestModel.UserId != null)
|
|
{
|
|
var user = await userManager.FindByIdAsync(requestModel.UserId);
|
|
if (user != null)
|
|
{
|
|
logger.LogInformation("User found: {UserId}", user.Id);
|
|
|
|
var tokenR = await tokenRepo.DeleteAllUserTokenAsync(user);
|
|
var userR = await userManager.DeleteAsync(user);
|
|
DeleteUserReturnModel returnModel = new DeleteUserReturnModel
|
|
{
|
|
UserId = requestModel.UserId,
|
|
DedeltedTokenCount = tokenR,
|
|
IsOperationSuccess = userR.Succeeded,
|
|
OperationMessage = string.Join(";", userR.Errors)
|
|
};
|
|
|
|
var topic = new UserChangeMessageType()
|
|
{
|
|
UserId = user.Id,
|
|
Changes = new Dictionary<string, string> { { "AccountChange", "Delete" } },
|
|
Id = HttpContext.TraceIdentifier,
|
|
};
|
|
|
|
_ = userChangeProducer.Produce(topic);
|
|
|
|
logger.LogInformation("User deleted: {UserId}", user.Id);
|
|
|
|
return Ok(returnModel);
|
|
}
|
|
else
|
|
{
|
|
logger.LogWarning("User not found: {UserId}", requestModel.UserId);
|
|
return BadRequest("User not found");
|
|
}
|
|
}
|
|
|
|
logger.LogWarning("DeleteUser request with empty UserId");
|
|
return BadRequest("User cannot be empty");
|
|
}
|
|
|
|
[HttpPost]
|
|
[HasPrivilege(GlobalPrivilage.Privilege.GlobalCreateUser)]
|
|
[Consumes(MediaTypeNames.Application.Json, otherContentTypes: [MediaTypeNames.Application.Xml])]
|
|
public async Task<ActionResult<CreateUserModelReturn>> CreateUser([FromBody] CreateUserModel newUser)
|
|
{
|
|
logger.LogInformation("CreateUser called with Username: {Username}, Email: {Email}", newUser.Username, newUser.Email);
|
|
|
|
var userReturn = new CreateUserModelReturn()
|
|
{
|
|
Username = newUser.Username,
|
|
Email = newUser.Email,
|
|
Password = "*******",
|
|
Roles = new List<CreateUserModelReturn.RoleStatus>(),
|
|
};
|
|
|
|
if (newUser is { Username: not null, Email: not null, Password: not null })
|
|
{
|
|
var user = new ApplicationUser {UserName = newUser.Username, Email = newUser.Email,
|
|
EmailConfirmed = true, DialCode = newUser.Dialcode, PhoneNumber = newUser.Phone};
|
|
|
|
var userResult = await userManager.CreateAsync(user, newUser.Password);
|
|
if (userResult.Succeeded)
|
|
{
|
|
logger.LogInformation("User created: {UserId}", user.Id);
|
|
|
|
var resultClaim = await userManager.AddClaimAsync(user, AccountType.User.Claim());
|
|
|
|
if (resultClaim.Succeeded && newUser.Roles != null)
|
|
{
|
|
userReturn.IsCreated = true;
|
|
userReturn.NewUserId = user.Id;
|
|
|
|
foreach (var role in newUser.Roles)
|
|
{
|
|
var appRole = await roleManager.FindByNameAsync(role);
|
|
|
|
CreateUserModelReturn.RoleStatus roleStatus = new CreateUserModelReturn.RoleStatus();
|
|
var result = await userManager.AddToRoleAsync(user, role);
|
|
|
|
roleStatus.Added = result.Succeeded;
|
|
roleStatus.Name = role;
|
|
roleStatus.Exist = appRole != null;
|
|
roleStatus.Mess = JsonSerializer.Serialize(string.Join(";", result.Errors));
|
|
|
|
userReturn.Roles.Add(roleStatus);
|
|
}
|
|
|
|
var topicMess = new UserChangeMessageType
|
|
{
|
|
UserId = user.Id,
|
|
Id = user.Id,
|
|
Changes =
|
|
{
|
|
["RoleChange"] = string
|
|
.Join(";", userReturn.Roles
|
|
.Select(status => $"{status.Name}:{status.Added}/{status.Exist}")),
|
|
["UserChange"] = "Created",
|
|
["Success"] = $"{userReturn.IsCreated}"
|
|
}
|
|
};
|
|
|
|
await userChangeProducer.Produce(topicMess);
|
|
|
|
logger.LogInformation("User created successfully: {UserId}", user.Id);
|
|
return Ok(userReturn);
|
|
}
|
|
else
|
|
{
|
|
userReturn.ReturnMess = string.Join(';', userResult.Errors.Select(q => $"{q.Code}:{q.Description}"));
|
|
logger.LogWarning("Failed to add claims or roles to user: {UserId}", user.Id);
|
|
return StatusCode(406, userReturn);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
userReturn.ReturnMess = JsonSerializer.Serialize(string.Join(";", userResult.Errors.Select(q => $"{q.Code}:{q.Description}")));
|
|
logger.LogWarning("Failed to create user: {Username}", newUser.Username);
|
|
userReturn.IsCreated = false;
|
|
return StatusCode(406, userReturn);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.LogWarning("Invalid CreateUser request");
|
|
userReturn.IsCreated = false;
|
|
userReturn.ReturnMess = "Invalid request";
|
|
}
|
|
|
|
return StatusCode(406, userReturn);
|
|
}
|
|
|
|
[HttpPost]
|
|
[Consumes(MediaTypeNames.Application.Json, otherContentTypes: [MediaTypeNames.Application.Xml])]
|
|
public async Task<ActionResult<ChangePasswordReturn>> ChangePassword([FromBody] ChangePassword req)
|
|
{
|
|
logger.LogInformation("ChangePassword called for UserId: {UserId}", User.FindFirstValue(ClaimTypes.NameIdentifier));
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
var user = await userManager.FindByIdAsync(userId ?? "");
|
|
|
|
if ((user != null) && (req is { Password: not null, NewPassword1: not null, NewPassword2: not null }))
|
|
{
|
|
if (req.NewPassword1.Equals(req.NewPassword2))
|
|
{
|
|
var result = await userManager.ChangePasswordAsync(user, req.Password, req.NewPassword1);
|
|
if (result.Succeeded)
|
|
{
|
|
logger.LogInformation("Password changed successfully for UserId: {UserId}", user.Id);
|
|
var count = 0;
|
|
if (req.LogOutAllSession) count = await tokenRepo.LockAllUserTokenAsync(user);
|
|
return Ok(new ChangePasswordReturn { Mess = "Operation success.", IsSuccess = true, LockedTokenCount = count});
|
|
}
|
|
else
|
|
{
|
|
logger.LogWarning("Failed to change password for UserId: {UserId}", user.Id);
|
|
return StatusCode(418, new ChangePasswordReturn {Mess = string.Join(";", result.Errors
|
|
.Select(q => q.Description) ), IsSuccess = false});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.LogWarning("New passwords do not match for UserId: {UserId}", user.Id);
|
|
return StatusCode(418, new ChangePasswordReturn {Mess = "New password not match.", IsSuccess = false});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.LogWarning("Invalid ChangePassword request for UserId: {UserId}", userId);
|
|
return StatusCode(400, new ChangePasswordReturn {Mess = "Bad request", IsSuccess = false});
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
[HasPrivilege(GlobalPrivilage.Privilege.GlobalDeactivateUserToken)]
|
|
public async Task<ActionResult<OkModel>> DeactivateToken([FromBody] DeactiveTokenModel req)
|
|
{
|
|
logger.LogInformation("DeactivateToken called for Token: {Token}", req.Token);
|
|
var i = await tokenRepo.LockTokenAsync(req.Token ?? throw new Exception("Token is null"));
|
|
var resultMessage = i > 0 ? "Token lock success." : "Token Lock Fail.";
|
|
logger.LogInformation(resultMessage);
|
|
return new OkModel(resultMessage, i > 0);
|
|
}
|
|
|
|
[HttpPost]
|
|
[HasPrivilege(GlobalPrivilage.Privilege.GlobalEditUser)]
|
|
public async Task<ActionResult<AddClaimsToUserReturnModel>> AddClaimsToUser([FromBody] AddClaimsToUserModel req)
|
|
{
|
|
logger.LogInformation("AddClaimsToUser called for UserId: {UserId}", req.UserId);
|
|
var userId = req.UserId;
|
|
var user = await userManager.FindByIdAsync(userId) ?? throw new Exception("User not found");
|
|
var topic = new UserChangeClaimsMessageType()
|
|
{
|
|
UserId = userId,
|
|
Id = HttpContext.TraceIdentifier,
|
|
ClaimChages = new List<ClaimChagesModel>()
|
|
};
|
|
|
|
var actionResult = new AddClaimsToUserReturnModel() { UserId = userId };
|
|
|
|
var curClaims = await userManager.GetClaimsAsync(user);
|
|
|
|
foreach (var newClaim in req.NewClaims)
|
|
{
|
|
var existingClaim = curClaims.FirstOrDefault(c => c.Type == newClaim.Key);
|
|
|
|
topic.ClaimChages.Add(new ClaimChagesModel()
|
|
{
|
|
Old = existingClaim != null ?
|
|
new ClaimModel
|
|
{
|
|
ClaimType = existingClaim.Type,
|
|
ClaimValue = existingClaim.Value,
|
|
ClaimIssuer = existingClaim.Issuer
|
|
} : null,
|
|
New = new ClaimModel
|
|
{
|
|
ClaimType = newClaim.Key,
|
|
ClaimValue = newClaim.Value,
|
|
ClaimIssuer = req.Issuer
|
|
}
|
|
});
|
|
}
|
|
|
|
var resultAddClaims = await userManager.AddClaimsAsync(user,
|
|
req.NewClaims.Select(pair => new Claim(type: pair.Key, value: pair.Value, null, req.Issuer)).ToList());
|
|
|
|
if (!resultAddClaims.Succeeded)
|
|
{
|
|
actionResult.IsSucess = false;
|
|
actionResult.Error = string.Join(";", resultAddClaims.Errors.Select(error => error.Description));
|
|
|
|
topic.IsSuccess = false;
|
|
topic.Errors =
|
|
resultAddClaims.Errors.Select(error =>
|
|
new KeyValuePair<string, string>(error.Code, error.Description))
|
|
.ToDictionary();
|
|
|
|
logger.LogWarning("Failed to add claims to user: {UserId}", userId);
|
|
}
|
|
else
|
|
{
|
|
actionResult.IsSucess = true;
|
|
topic.IsSuccess = true;
|
|
logger.LogInformation("Claims added successfully to UserId: {UserId}", userId);
|
|
}
|
|
|
|
_ = userClaimChangeProducer.Produce(topic);
|
|
|
|
var allUserClaim = await userManager.GetClaimsAsync(user);
|
|
|
|
foreach (var claim in allUserClaim)
|
|
{
|
|
actionResult.AllUserClaims[claim.Type] = claim.Value;
|
|
}
|
|
|
|
return Ok(actionResult);
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<AddClaimsToUserReturnModel>> GetAllClaimsUser()
|
|
{
|
|
var userId = User.Claims.Where(q => q.Type == ClaimTypes.NameIdentifier)
|
|
.Select(q => q.Value).FirstOrDefault();
|
|
logger.LogInformation("GetAllClaimsUser called for UserId: {UserId}", userId);
|
|
|
|
var user = await userManager.FindByIdAsync(userId ?? "") ?? throw new Exception("User not found");
|
|
|
|
var result = new AddClaimsToUserReturnModel() { UserId = userId, IsSucess = true, Error = null};
|
|
|
|
var allUserClaim = await userManager.GetClaimsAsync(user);
|
|
foreach (var claim in allUserClaim)
|
|
{
|
|
result.AllUserClaims[claim.Type] = claim.Value;
|
|
}
|
|
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<ChangeEmailRespond>> UpdateUserEmail([FromBody] ChangeEmailRequest request)
|
|
{
|
|
logger.LogInformation("UpdateUserEmail called with NewEmail: {NewEmail}", request.NewEmail);
|
|
|
|
var response = new ChangeEmailRespond
|
|
{
|
|
NewEmail = request.NewEmail
|
|
};
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
{
|
|
response.Problems.Add("UserId", "User ID not found in token.");
|
|
logger.LogWarning("UserId not found in token");
|
|
return BadRequest(response);
|
|
}
|
|
|
|
var user = await userManager.FindByIdAsync(userId);
|
|
if (user == null)
|
|
{
|
|
response.Problems.Add("User", "User not found.");
|
|
logger.LogWarning("User not found: {UserId}", userId);
|
|
return Unauthorized(response);
|
|
}
|
|
|
|
response.OldEmail = user.Email;
|
|
|
|
if (string.IsNullOrEmpty(request.NewEmail))
|
|
{
|
|
response.Problems.Add("NewEmail", "New email is required.");
|
|
logger.LogWarning("New email is required for UserId: {UserId}", userId);
|
|
return BadRequest(response);
|
|
}
|
|
|
|
user.Email = request.NewEmail;
|
|
var result = await userManager.UpdateAsync(user);
|
|
if (result.Succeeded)
|
|
{
|
|
response.ValSuccess = true;
|
|
response.UpdateSuccess = true;
|
|
logger.LogInformation("User email updated successfully: {UserId}", userId);
|
|
return Ok(response);
|
|
}
|
|
else
|
|
{
|
|
response.ValSuccess = true;
|
|
response.Problems = result.Errors.ToDictionary(
|
|
error => error.Code,
|
|
error => error.Description
|
|
);
|
|
logger.LogWarning("Failed to update user email for UserId: {UserId}", userId);
|
|
return StatusCode(418, response);
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<ChangePhoneNumberRespond>> UpdatePhoneNumber([FromBody] ChangePhoneNumberRequest request)
|
|
{
|
|
logger.LogInformation("UpdatePhoneNumber called with Prefix: {Prefix}, Number: {Number}", request.Prefix, request.Number);
|
|
|
|
var response = new ChangePhoneNumberRespond
|
|
{
|
|
Prefix = request.Prefix,
|
|
Number = request.Number
|
|
};
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
{
|
|
response.Problems.Add("UserId", "User ID not found in token.");
|
|
logger.LogWarning("UserId not found in token");
|
|
return BadRequest(response);
|
|
}
|
|
|
|
var user = await userManager.FindByIdAsync(userId);
|
|
if (user == null)
|
|
{
|
|
response.Problems.Add("User", "User not found.");
|
|
logger.LogWarning("User not found: {UserId}", userId);
|
|
return Unauthorized(response);
|
|
}
|
|
|
|
response.OldPrefix = user.DialCode;
|
|
response.OldPhone = user.PhoneNumber;
|
|
|
|
if (string.IsNullOrEmpty(request.Prefix) || string.IsNullOrEmpty(request.Number))
|
|
{
|
|
response.Problems.Add("PhoneNumber", "Prefix and number are required.");
|
|
logger.LogWarning("Prefix or number missing for UserId: {UserId}", userId);
|
|
return BadRequest(response);
|
|
}
|
|
|
|
user.PhoneNumber = request.Number;
|
|
user.DialCode = request.Prefix;
|
|
|
|
var result = await userManager.UpdateAsync(user);
|
|
if (result.Succeeded)
|
|
{
|
|
response.ValSuccess = true;
|
|
response.UpdateSuccess = true;
|
|
logger.LogInformation("User phone number updated successfully: {UserId}", userId);
|
|
return Ok(response);
|
|
}
|
|
else
|
|
{
|
|
response.ValSuccess = true;
|
|
response.Problems = result.Errors.ToDictionary(
|
|
error => error.Code,
|
|
error => error.Description
|
|
);
|
|
logger.LogWarning("Failed to update phone number for UserId: {UserId}", userId);
|
|
return StatusCode(418, response);
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<AccountDetailRespond>> GetMyDetail()
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
var response = new AccountDetailRespond();
|
|
|
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
{
|
|
response.Problems.Add("UserId", "User ID not found in token.");
|
|
logger.LogWarning("UserId not found in token");
|
|
return BadRequest(response);
|
|
}
|
|
|
|
var user = await userManager.FindByIdAsync(userId);
|
|
|
|
if (user == null)
|
|
{
|
|
response.Problems.Add("User", "User not found.");
|
|
logger.LogWarning("User not found: {UserId}", userId);
|
|
return Unauthorized(response);
|
|
}
|
|
|
|
response.Id = user.Id;
|
|
response.Email = user.Email;
|
|
response.UserName = user.UserName;
|
|
response.Phone = user.PhoneNumber;
|
|
response.DialCode = user.DialCode;
|
|
|
|
return Ok(response);
|
|
}
|
|
|
|
[HttpPost]
|
|
[HasPrivilege(GlobalPrivilage.Privilege.GlobalEditUser, GlobalPrivilage.Privilege.GlobalDeleteUser)]
|
|
public async Task<ActionResult<AccountListsResponse>> GetListOfUsers([FromBody] AccountListsRequest request)
|
|
{
|
|
if (request.Page < 1 || request.PageSize < 1)
|
|
{
|
|
return BadRequest("Page and page size must be greater than 0.");
|
|
}
|
|
|
|
var result = new AccountListsResponse
|
|
{
|
|
AccountsDetail = await userManager.Users
|
|
.OrderBy(u => u.UserName)
|
|
.Skip((request.Page - 1) * request.PageSize)
|
|
.Take(request.PageSize)
|
|
.Select(q
|
|
=>new AccountDetailRespond()
|
|
{
|
|
UserName = q.UserName, DialCode = q.DialCode, Email = q.Email, Id = q.Id, Phone = q.PhoneNumber
|
|
}).ToListAsync(),
|
|
Page = request.Page,
|
|
PageSize = request.PageSize,
|
|
NextPageExist = await userManager.Users
|
|
.OrderBy(u => u.UserName)
|
|
.Skip((request.Page) * request.PageSize)
|
|
.Take(request.PageSize).AnyAsync(),
|
|
LastPageNum = (await userManager.Users.CountAsync()) / request.PageSize
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
[HttpPost]
|
|
[HasPrivilege(GlobalPrivilage.Privilege.GlobalEditUser)]
|
|
public async Task<ActionResult<EditAccountRespond>> EditAccount([FromBody] EditAccountRequest request)
|
|
{
|
|
if (request is { UserId: null })
|
|
{
|
|
return BadRequest(new EditAccountRespond()
|
|
{
|
|
Message = "Invalid request", Success = false
|
|
});
|
|
}
|
|
|
|
var user = await userManager.FindByIdAsync(request.UserId);
|
|
|
|
if (user == null)
|
|
{
|
|
return BadRequest(new EditAccountRespond()
|
|
{
|
|
Message = "User not found", Success = false
|
|
});
|
|
}
|
|
|
|
var respond = new EditAccountRespond();
|
|
|
|
if (user.UserName != request.NewUserName)
|
|
{
|
|
respond.NewUserName = request.NewUserName;
|
|
user.UserName = request.NewUserName;
|
|
respond.UserNameEdited = true;
|
|
}
|
|
|
|
if (user.Email != request.NewEmail)
|
|
{
|
|
respond.NewEmail = request.NewEmail;
|
|
user.Email = request.NewEmail;
|
|
respond.EmailEdited = true;
|
|
}
|
|
|
|
if (user.DialCode != request.NewDialCode || user.PhoneNumber != request.NewPhone)
|
|
{
|
|
respond.NewDialCode = request.NewDialCode;
|
|
respond.NewPhone = request.NewPhone;
|
|
|
|
user.PhoneNumber = request.NewPhone;
|
|
user.DialCode = request.NewDialCode;
|
|
respond.PhoneEdited = true;
|
|
|
|
}
|
|
|
|
var result = await userManager.UpdateAsync(user);
|
|
|
|
respond.Success = result.Succeeded;
|
|
respond.Message = string.Join("\n",result.Errors.Select(error => $"{error.Code}:{error.Description}"));
|
|
return respond;
|
|
}
|
|
}
|