Add multi-user MAX authentication and tenant isolation

This commit is contained in:
Курнат Андрей
2026-07-14 07:35:04 +03:00
parent 582f99ed0e
commit 440de7325f
36 changed files with 904 additions and 118 deletions
+14 -10
View File
@@ -10,6 +10,7 @@ using QMax.Api.Data.Entities;
using QMax.Api.Infrastructure.Hubs;
using QMax.Api.Infrastructure.Max;
using QMax.Api.Services;
using QMax.Api.Infrastructure.Auth;
namespace QMax.Api.Controllers;
@@ -21,11 +22,8 @@ public sealed class MaxController(
MaxBridgeSyncService syncService,
QMaxDbContext db,
IHubContext<QMaxHub> hubContext,
ChatProjectionService projection,
IOptions<QMaxOptions> options) : ControllerBase
ChatProjectionService projection) : ControllerBase
{
private readonly QMaxOptions _options = options.Value;
[HttpGet("status")]
public async Task<ActionResult<MaxBridgeStatusDto>> Status(CancellationToken cancellationToken)
{
@@ -37,7 +35,12 @@ public sealed class MaxController(
[HttpPost("login/start")]
public async Task<ActionResult<MaxBridgeStatusDto>> BeginLogin(BeginMaxLoginRequest request, CancellationToken cancellationToken)
{
var phone = string.IsNullOrWhiteSpace(request.PhoneNumber) ? _options.MaxPhoneNumber : request.PhoneNumber;
var phone = request.PhoneNumber;
if (string.IsNullOrWhiteSpace(phone))
{
var userId = User.GetUserId();
phone = await db.Users.Where(x => x.Id == userId).Select(x => x.PhoneNumber).FirstOrDefaultAsync(cancellationToken);
}
if (string.IsNullOrWhiteSpace(phone))
{
return BadRequest("Phone number is required.");
@@ -111,27 +114,28 @@ public sealed class MaxController(
return BadRequest("Channel was joined but was not saved in QMAX.");
}
await hubContext.Clients.All.SendAsync("ChatListInvalidated", cancellationToken);
await hubContext.Clients.User(User.GetUserId().ToString()).SendAsync("ChatListInvalidated", cancellationToken);
return projection.ToDto(chat);
}
private async Task SaveStateAsync(MaxBridgeStatus status, CancellationToken cancellationToken)
{
var state = await db.MaxAccountStates.FirstOrDefaultAsync(x => x.Id == 1, cancellationToken);
var userId = User.GetUserId();
var state = await db.MaxAccountStates.FirstOrDefaultAsync(cancellationToken);
if (state is null)
{
state = new MaxAccountState { Id = 1 };
state = new MaxAccountState { UserId = userId };
db.MaxAccountStates.Add(state);
}
state.PhoneNumber = _options.MaxPhoneNumber;
state.PhoneNumber = await db.Users.Where(x => x.Id == userId).Select(x => x.PhoneNumber).FirstOrDefaultAsync(cancellationToken) ?? "";
state.Status = status.Status;
state.IsAuthorized = status.IsAuthorized;
state.LastUrl = status.Url;
state.LastError = status.LastError;
state.UpdatedAt = DateTimeOffset.UtcNow;
await db.SaveChangesAsync(cancellationToken);
await hubContext.Clients.All.SendAsync("MaxStatusChanged", ToDto(status), cancellationToken);
await hubContext.Clients.User(userId.ToString()).SendAsync("MaxStatusChanged", ToDto(status), cancellationToken);
}
private static MaxBridgeStatusDto ToDto(MaxBridgeStatus status)