Add multi-user MAX authentication and tenant isolation
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using QMax.Api.Data;
|
||||
using QMax.Api.Data.Entities;
|
||||
using QMax.Api.Infrastructure.Auth;
|
||||
using QMax.Api.Configuration;
|
||||
using QMax.Api.Contracts;
|
||||
using QMax.Api.Controllers;
|
||||
using QMax.Api.Infrastructure.Max;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace QMax.Tests;
|
||||
|
||||
public sealed class TenantIsolationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Phone_challenge_issues_tokens_only_after_max_code_completion()
|
||||
{
|
||||
await using var connection = new SqliteConnection("Data Source=:memory:");
|
||||
await connection.OpenAsync();
|
||||
var options = new DbContextOptionsBuilder<QMaxDbContext>().UseSqlite(connection).Options;
|
||||
var currentUser = new CurrentUserAccessor();
|
||||
await using var db = new QMaxDbContext(options, currentUser);
|
||||
using (currentUser.Push(null, bypassTenantFilter: true)) await db.Database.EnsureCreatedAsync();
|
||||
var qmax = Options.Create(new QMaxOptions
|
||||
{
|
||||
PairingCode = "invite",
|
||||
JwtSecret = "tenant-test-secret-that-is-at-least-32-characters"
|
||||
});
|
||||
var controller = new AuthController(db, new TokenService(qmax), qmax, new MockMaxBridgeClient(), currentUser);
|
||||
|
||||
var started = await controller.BeginPhoneAuth(
|
||||
new BeginPhoneAuthRequest("8 (900) 000-00-01", "test phone", "invite"),
|
||||
CancellationToken.None);
|
||||
var challenge = Assert.IsType<PhoneAuthChallengeResponse>(started.Value);
|
||||
Assert.NotEqual(Guid.Empty, challenge.ChallengeId);
|
||||
Assert.Empty(await db.UserSessions.IgnoreQueryFilters().ToArrayAsync());
|
||||
|
||||
var completed = await controller.CompletePhoneAuth(
|
||||
new CompletePhoneAuthRequest(challenge.ChallengeId, challenge.ChallengeToken, "123456"),
|
||||
CancellationToken.None);
|
||||
var auth = Assert.IsType<AuthResponse>(completed.Value);
|
||||
Assert.False(string.IsNullOrWhiteSpace(auth.AccessToken));
|
||||
Assert.Single(await db.UserSessions.IgnoreQueryFilters().ToArrayAsync());
|
||||
Assert.Equal("+79000000001", auth.User.PhoneNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Chats_and_messages_are_visible_only_to_the_current_user()
|
||||
{
|
||||
await using var connection = new SqliteConnection("Data Source=:memory:");
|
||||
await connection.OpenAsync();
|
||||
var options = new DbContextOptionsBuilder<QMaxDbContext>().UseSqlite(connection).Options;
|
||||
var currentUser = new CurrentUserAccessor();
|
||||
var firstUser = new User { PhoneNumber = "+79000000001" };
|
||||
var secondUser = new User { PhoneNumber = "+79000000002" };
|
||||
|
||||
await using (var setup = new QMaxDbContext(options, currentUser))
|
||||
using (currentUser.Push(null, bypassTenantFilter: true))
|
||||
{
|
||||
await setup.Database.EnsureCreatedAsync();
|
||||
setup.Users.AddRange(firstUser, secondUser);
|
||||
await setup.SaveChangesAsync();
|
||||
}
|
||||
|
||||
Guid firstChatId;
|
||||
await using (var db = new QMaxDbContext(options, currentUser))
|
||||
using (currentUser.Push(firstUser.Id))
|
||||
{
|
||||
var chat = new Chat { ExternalId = "same-max-chat", Title = "First" };
|
||||
chat.Messages.Add(new Message { Text = "first secret", Direction = MessageDirection.Incoming });
|
||||
db.Chats.Add(chat);
|
||||
await db.SaveChangesAsync();
|
||||
firstChatId = chat.Id;
|
||||
}
|
||||
|
||||
await using (var db = new QMaxDbContext(options, currentUser))
|
||||
using (currentUser.Push(secondUser.Id))
|
||||
{
|
||||
var chat = new Chat { ExternalId = "same-max-chat", Title = "Second" };
|
||||
chat.Messages.Add(new Message { Text = "second secret", Direction = MessageDirection.Incoming });
|
||||
db.Chats.Add(chat);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
Assert.Single(await db.Chats.ToArrayAsync());
|
||||
Assert.Equal("Second", (await db.Chats.SingleAsync()).Title);
|
||||
Assert.Single(await db.Messages.ToArrayAsync());
|
||||
Assert.False(await db.Chats.AnyAsync(x => x.Id == firstChatId));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user