a855cf1d80
CI/CD / CI · API (dotnet build + test) (push) Successful in 5m6s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 1m30s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m10s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 46s
CI/CD / CI · Koja (tsc) (push) Successful in 1m0s
CI/CD / Deploy · all services (push) Successful in 5m31s
Platform admins can generate a permanent recovery key per café (admin
panel → Cafés). The café Owner uses it to sign in when OTP access is lost;
once authenticated, all server-side data syncs as normal (data is per-café
on the server, the device only caches it).
Backend:
- Cafe.RecoveryKeyHash (SHA-256, unique index) + RecoveryKeyCreatedAt; migration
- RecoveryKeyGenerator util: MZ-XXXXX-XXXXX-XXXXX-XXXXX, ~190-bit entropy,
stored as SHA-256 (API-token pattern — raw key shown once, never retrievable)
- Admin: POST/DELETE /api/admin/cafes/{id}/recovery-key (key returned once);
café list now reports HasRecoveryKey + RecoveryKeyCreatedAt
- Login: POST /api/auth/login-key → exact-hash lookup → resolves café Owner →
issues normal JWT; rate-limited (auth-otp), suspended/no-owner guarded, logged
Admin UI: per-café generate / regenerate / revoke with one-time reveal + copy.
Dashboard login: discreet "ورود با کلید بازیابی" link → key field. fa/en/ar.
86 tests pass; all tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
2.5 KiB
C#
52 lines
2.5 KiB
C#
using Meezi.API.Models.Auth;
|
|
|
|
namespace Meezi.API.Services;
|
|
|
|
public interface IAuthService
|
|
{
|
|
Task<(bool Success, SendOtpResponse? Data, string? ErrorCode, string? ErrorMessage)> SendOtpAsync(
|
|
SendOtpRequest request,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Returns either an AuthTokenResponse (single café) or error code CHOOSE_CAFE
|
|
/// with CafeChoicesResponse serialised in ErrorMessage when multiple cafés found.
|
|
/// </summary>
|
|
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage, CafeChoicesResponse? Choices)> VerifyOtpAsync(
|
|
VerifyOtpRequest request,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage, CafeChoicesResponse? Choices)> LoginWithPasswordAsync(
|
|
LoginWithPasswordRequest request,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>Log in the café Owner using an admin-issued permanent recovery key.</summary>
|
|
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage)> LoginWithRecoveryKeyAsync(
|
|
LoginWithRecoveryKeyRequest request,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage)> SwitchCafeAsync(
|
|
string employeeId, string targetCafeId,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Re-issue a token scoped to a different branch within the current café.
|
|
/// <paramref name="targetBranchId"/> null means café-wide (Owner only).
|
|
/// </summary>
|
|
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage)> SwitchBranchAsync(
|
|
string employeeId, string cafeId, string? targetBranchId,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage)> RefreshAsync(
|
|
RefreshTokenRequest request,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
Task<(bool Success, SendOtpResponse? Data, string? ErrorCode, string? ErrorMessage)> RegisterAsync(
|
|
RegisterRequest request,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
Task<(bool Success, AuthTokenResponse? Data, string? ErrorCode, string? ErrorMessage)> VerifyRegisterAsync(
|
|
VerifyRegisterRequest request,
|
|
CancellationToken cancellationToken = default);
|
|
}
|