Add multi-user MAX authentication and tenant isolation
This commit is contained in:
@@ -4,6 +4,8 @@ public interface IMaxBridgeClient
|
||||
{
|
||||
Task<MaxBridgeStatus> GetStatusAsync(CancellationToken cancellationToken);
|
||||
Task<MaxBridgeStatus> BeginPhoneLoginAsync(string phoneNumber, CancellationToken cancellationToken);
|
||||
Task<MaxBridgeStatus> BeginNewPhoneLoginAsync(string phoneNumber, CancellationToken cancellationToken) =>
|
||||
BeginPhoneLoginAsync(phoneNumber, cancellationToken);
|
||||
Task<MaxBridgeStatus> SubmitLoginCodeAsync(string code, CancellationToken cancellationToken);
|
||||
Task<MaxBrowserSnapshot> GetSnapshotAsync(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<MaxChatUpdate>> FetchUpdatesAsync(CancellationToken cancellationToken);
|
||||
|
||||
@@ -45,6 +45,9 @@ public sealed class MockMaxBridgeClient : IMaxBridgeClient
|
||||
return Task.FromResult<IReadOnlyList<MaxChatUpdate>>(_updates);
|
||||
}
|
||||
|
||||
public Task<MaxBridgeStatus> BeginNewPhoneLoginAsync(string phoneNumber, CancellationToken cancellationToken) =>
|
||||
BeginPhoneLoginAsync(phoneNumber, cancellationToken);
|
||||
|
||||
public Task<IReadOnlyList<MaxContact>> FetchContactsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult<IReadOnlyList<MaxContact>>([
|
||||
|
||||
@@ -2,12 +2,14 @@ using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
using QMax.Api.Configuration;
|
||||
using QMax.Api.Infrastructure.Auth;
|
||||
|
||||
namespace QMax.Api.Infrastructure.Max;
|
||||
|
||||
public sealed class WorkerMaxBridgeClient(
|
||||
HttpClient httpClient,
|
||||
IOptions<QMaxOptions> options,
|
||||
ICurrentUserAccessor currentUser,
|
||||
ILogger<WorkerMaxBridgeClient> logger) : IMaxBridgeClient
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
@@ -21,7 +23,17 @@ public sealed class WorkerMaxBridgeClient(
|
||||
|
||||
public async Task<MaxBridgeStatus> BeginPhoneLoginAsync(string phoneNumber, CancellationToken cancellationToken)
|
||||
{
|
||||
return await SendAsync<MaxBridgeStatus>(HttpMethod.Post, "/login/start", new { phoneNumber }, cancellationToken)
|
||||
return await BeginPhoneLoginCoreAsync(phoneNumber, forceNewSession: false, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<MaxBridgeStatus> BeginNewPhoneLoginAsync(string phoneNumber, CancellationToken cancellationToken)
|
||||
{
|
||||
return await BeginPhoneLoginCoreAsync(phoneNumber, forceNewSession: true, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<MaxBridgeStatus> BeginPhoneLoginCoreAsync(string phoneNumber, bool forceNewSession, CancellationToken cancellationToken)
|
||||
{
|
||||
return await SendAsync<MaxBridgeStatus>(HttpMethod.Post, "/login/start", new { phoneNumber, force = forceNewSession }, cancellationToken)
|
||||
?? ErrorStatus("Worker returned an empty login status.");
|
||||
}
|
||||
|
||||
@@ -171,7 +183,9 @@ public sealed class WorkerMaxBridgeClient(
|
||||
{
|
||||
httpClient.BaseAddress ??= new Uri(_options.MaxWorkerBaseUrl.TrimEnd('/') + "/");
|
||||
var path = $"media/fetch?url={Uri.EscapeDataString(remoteUrl)}";
|
||||
var response = await httpClient.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, path);
|
||||
AddAccountHeader(request);
|
||||
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
@@ -217,6 +231,7 @@ public sealed class WorkerMaxBridgeClient(
|
||||
{
|
||||
httpClient.BaseAddress ??= new Uri(_options.MaxWorkerBaseUrl.TrimEnd('/') + "/");
|
||||
using var request = new HttpRequestMessage(method, path.TrimStart('/'));
|
||||
AddAccountHeader(request);
|
||||
if (body is not null)
|
||||
{
|
||||
request.Content = JsonContent.Create(body, options: JsonOptions);
|
||||
@@ -239,6 +254,15 @@ public sealed class WorkerMaxBridgeClient(
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAccountHeader(HttpRequestMessage request)
|
||||
{
|
||||
if (currentUser.UserId is not { } userId)
|
||||
{
|
||||
throw new InvalidOperationException("A QMAX user context is required for a PyMax request.");
|
||||
}
|
||||
request.Headers.Add("X-QMax-Account-Id", userId.ToString("N"));
|
||||
}
|
||||
|
||||
private static MaxBridgeStatus ErrorStatus(string error)
|
||||
{
|
||||
return new MaxBridgeStatus("Worker", false, "Unavailable", "WorkerUnavailable", null, null, error, DateTimeOffset.UtcNow);
|
||||
|
||||
Reference in New Issue
Block a user