feat: username/password authentication for admin and merchant panels
CI/CD / CI · API (dotnet build + test) (push) Successful in 49s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 42s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m8s
CI/CD / CI · Admin Web (tsc) (push) Successful in 37s
CI/CD / CI · Website (tsc) (push) Successful in 46s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Has been cancelled
CI/CD / CI · API (dotnet build + test) (push) Successful in 49s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 42s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m8s
CI/CD / CI · Admin Web (tsc) (push) Successful in 37s
CI/CD / CI · Website (tsc) (push) Successful in 46s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Has been cancelled
- Add PasswordHasher utility (PBKDF2/SHA-256, 100k iterations)
- Add Username + PasswordHash fields to Employee and SystemAdmin entities
- EF migration: AddPasswordLogin (nullable columns on both tables)
- Meezi.API: POST /api/auth/login (employee password login, CHOOSE_CAFE support)
- Meezi.API: PUT/DELETE /api/cafes/{id}/employees/{id}/credentials (Owner/Manager only)
- Meezi.Admin.API: POST /api/admin/auth/login + PUT /api/admin/auth/password
- Dashboard login page: OTP / Password tabs
- Admin login page: OTP / Password tabs
- HR screen: new Credentials tab for setting employee username/password
- PlatformDataSeeder: ensure system admin + integration settings in production
- Trial countdown banner: updated deadline to 1 Tir 1405 (Jun 22)
- i18n: fa/en/ar updated for all new UI strings
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,15 @@ public interface IAdminAuthService
|
||||
VerifyOtpRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage)> LoginWithPasswordAsync(
|
||||
LoginWithPasswordRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<(bool Success, string? ErrorCode, string? ErrorMessage)> ChangePasswordAsync(
|
||||
string adminId,
|
||||
ChangePasswordRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage)> RefreshAsync(
|
||||
RefreshTokenRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
@@ -141,6 +150,49 @@ public class AdminAuthService : IAdminAuthService
|
||||
return (true, tokens, null, null);
|
||||
}
|
||||
|
||||
public async Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage)> LoginWithPasswordAsync(
|
||||
LoginWithPasswordRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var username = request.Username.Trim();
|
||||
var admin = await _db.SystemAdmins
|
||||
.FirstOrDefaultAsync(a => a.Username == username && a.IsActive && a.DeletedAt == null, cancellationToken);
|
||||
|
||||
if (admin is null || string.IsNullOrWhiteSpace(admin.PasswordHash))
|
||||
return (false, null, "INVALID_CREDENTIALS", "Invalid username or password.");
|
||||
|
||||
if (!PasswordHasher.Verify(request.Password, admin.PasswordHash))
|
||||
return (false, null, "INVALID_CREDENTIALS", "Invalid username or password.");
|
||||
|
||||
var tokens = await IssueTokensAsync(admin, cancellationToken);
|
||||
return (true, tokens, null, null);
|
||||
}
|
||||
|
||||
public async Task<(bool Success, string? ErrorCode, string? ErrorMessage)> ChangePasswordAsync(
|
||||
string adminId,
|
||||
ChangePasswordRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var admin = await _db.SystemAdmins
|
||||
.FirstOrDefaultAsync(a => a.Id == adminId && a.IsActive && a.DeletedAt == null, cancellationToken);
|
||||
if (admin is null)
|
||||
return (false, "NOT_FOUND", "Admin not found.");
|
||||
|
||||
// If a password is already set, require the current one
|
||||
if (!string.IsNullOrWhiteSpace(admin.PasswordHash))
|
||||
{
|
||||
if (!PasswordHasher.Verify(request.CurrentPassword, admin.PasswordHash))
|
||||
return (false, "INVALID_CREDENTIALS", "Current password is incorrect.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.NewPassword) || request.NewPassword.Length < 8)
|
||||
return (false, "VALIDATION_ERROR", "New password must be at least 8 characters.");
|
||||
|
||||
admin.PasswordHash = PasswordHasher.Hash(request.NewPassword);
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
return (true, null, null);
|
||||
}
|
||||
|
||||
private async Task<AuthTokenResponse> IssueTokensAsync(
|
||||
Core.Entities.SystemAdmin admin,
|
||||
CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user