using EntitlementDAL.DTO.User;
using EntitlementDAL.Interfaces;
using GB5Shared.Connection;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.GenerateAutoNumber;
using GB5Shared.User;
namespace EntitlementBLL.Implementations;
///
/// Adapter behind GB5Shared.User.IClientUserProvisioner — inserts the first admin MUSER row
/// directly via EntitlementDAL rather than delegating to FrameworkBLL.User.IUserBLL.
///
/// PlatformHost (which hosts Entitlement) has zero reference to FrameworkBLL/FrameworkDAL/
/// FrameworkSL, so the original design (an adapter in FrameworkBLL wrapping IUserBLL.SaveUser)
/// could never actually resolve there. This duplicates the narrow "new user, UserId==0" slice of
/// IUserBLL.SaveUser's logic (FrameworkBLL/User/UserBLL.cs) — auto-number generation, temp
/// password generation/encryption, MUSER insert — using EntitlementUserQB.SAVE_USER/SAVE_USER_PG,
/// copied verbatim from FrameworkDAL.Query.User.UserQB (SQL Server + Postgres only; Entitlement
/// never runs against Oracle/MySQL, unlike Framework's UserDAL).
///
public class ClientUserProvisioner : IClientUserProvisioner
{
private readonly IEntitlementUserDAL _UserDAL;
private readonly AutoNumber _AutoNumber;
private readonly IApplicationConnection _ApplicationConnection;
public ClientUserProvisioner(
IEntitlementUserDAL userDAL,
AutoNumber autoNumber,
IApplicationConnection applicationConnection)
{
_UserDAL = userDAL;
_AutoNumber = autoNumber;
_ApplicationConnection = applicationConnection;
}
public async Task ProvisionAsync(ProvisionClientUserRequest request, LoginDTO login)
{
var userDto = new EntitlementUserDTO
{
UserCode = request.UserCode,
UserName = request.UserName,
// MUSER.USERLOGINNAME is NOT NULL with no default (confirmed live, tracker §31.13) —
// never set here before, meaning no user could ever have been created through this
// pipeline. Real live rows use either the email or the user code as login name; user
// code is used here since it's guaranteed present (email is required too, but the
// code is this DTO's own natural unique identifier for a login name).
UserLoginName = request.UserCode,
UserPrimaryMail = request.UserPrimaryMail,
UserPrimaryMobile = request.UserPrimaryMobile,
RoleId = request.RoleId,
// LOGINMACHINE/LOGINIP/LANGUAGEID are NOT NULL with no default and no backing field
// initializer (confirmed live, tracker §31.13) — real rows use empty string for
// LOGINMACHINE/LOGINIP (a system-initiated user, no interactive login session yet)
// and "EN" for LANGUAGEID (the only value seen on any real row checked).
UserLoginMachine = "",
UserLoginIp = "",
UserLanguageId = "EN",
// CKMUSER_SOURCETYPE only allows 1-5, confirmed live (tracker §31.13) — 0 fails
// outright. 1 = "Framework" per this DTO's own documented taxonomy comment, matching
// this row's real origin (a system/framework-driven onboarding action).
UserSourceType = 1,
};
// A 12-char plaintext here (matching FrameworkBLL.User.UserBLL.SaveUser's own choice)
// AES-GCM-encrypts to IV(12)+ciphertext(12)+tag(16) = 40 raw bytes = 56 Base64 chars —
// confirmed live (tracker §31.13) that this overflows MUSER.PASSWORD's real NVARCHAR(50)
// outright. This is a pre-existing bug shared with FrameworkBLL.User.UserBLL.SaveUser and
// PayRoll.EmployeeBLL (both call RandomGenerateAlphaNumeric(12) into the same column) —
// flagged, not fixed there (out of scope: shared framework code affecting other modules).
// 8 chars here encrypts to 36 raw bytes = 48 Base64 chars, safely under 50.
var password = _ApplicationConnection.RandomGenerateAlphaNumeric(8);
userDto.UserGeneratedPassword = password;
userDto.UserPassword = _ApplicationConnection.Encrypt(password, userDto.UserCode);
var autoNumberDto = await _AutoNumber.GetAutoNumber(1, "USER", login).ConfigureAwait(false);
userDto.UserId = autoNumberDto.StartNumber;
userDto.UserCreatedById = login.UserId;
userDto.UserCreatedOn = DateTime.UtcNow;
userDto.UserModifiedById = login.UserId;
userDto.UserModifiedOn = DateTime.UtcNow;
userDto.TenantId = login.ClientId;
await _UserDAL.SaveUserAsync(userDto, login, CancellationToken.None).ConfigureAwait(false);
return new ProvisionClientUserResult
{
UserId = userDto.UserId,
UserGeneratedPassword = userDto.UserGeneratedPassword,
};
}
}