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:
@@ -12,6 +12,12 @@ public class Employee : TenantEntity
|
||||
public decimal BaseSalary { get; set; }
|
||||
public string? PinCode { get; set; }
|
||||
|
||||
/// <summary>Optional username for password-based dashboard/POS login (set by cafe admin).</summary>
|
||||
public string? Username { get; set; }
|
||||
|
||||
/// <summary>PBKDF2/SHA-256 hash. Null means password login is not enabled for this employee.</summary>
|
||||
public string? PasswordHash { get; set; }
|
||||
|
||||
public Cafe Cafe { get; set; } = null!;
|
||||
public Branch? Branch { get; set; }
|
||||
public ICollection<Order> Orders { get; set; } = [];
|
||||
|
||||
@@ -5,4 +5,10 @@ public class SystemAdmin : BaseEntity
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Phone { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>Optional username for password-based login (alternative to OTP).</summary>
|
||||
public string? Username { get; set; }
|
||||
|
||||
/// <summary>PBKDF2/SHA-256 hash. Null means password login is not enabled.</summary>
|
||||
public string? PasswordHash { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Meezi.Core.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// PBKDF2/SHA-256 password hashing with no external dependencies.
|
||||
/// Format stored: "{iterations}.{salt_b64}.{hash_b64}"
|
||||
/// </summary>
|
||||
public static class PasswordHasher
|
||||
{
|
||||
private const int SaltSize = 16; // 128-bit salt
|
||||
private const int HashSize = 32; // 256-bit hash
|
||||
private const int Iterations = 100_000; // NIST-recommended minimum
|
||||
private static readonly HashAlgorithmName Algo = HashAlgorithmName.SHA256;
|
||||
|
||||
public static string Hash(string password)
|
||||
{
|
||||
var salt = RandomNumberGenerator.GetBytes(SaltSize);
|
||||
var hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, Iterations, Algo, HashSize);
|
||||
return $"{Iterations}.{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}";
|
||||
}
|
||||
|
||||
public static bool Verify(string password, string storedHash)
|
||||
{
|
||||
var parts = storedHash.Split('.');
|
||||
if (parts.Length != 3) return false;
|
||||
if (!int.TryParse(parts[0], out var iterations)) return false;
|
||||
|
||||
byte[] salt, expectedHash;
|
||||
try
|
||||
{
|
||||
salt = Convert.FromBase64String(parts[1]);
|
||||
expectedHash = Convert.FromBase64String(parts[2]);
|
||||
}
|
||||
catch (FormatException) { return false; }
|
||||
|
||||
var actual = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, Algo, expectedHash.Length);
|
||||
return CryptographicOperations.FixedTimeEquals(actual, expectedHash);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user