From 55d8d0a1c28aaf53e7be0cc28244ee9ad85c66d5 Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 25 May 2025 22:37:46 +0200 Subject: [PATCH] rage quit --- .../Controllers/AuthController.cs | 43 +- .../Program.cs | 4 + .../Suspectus.Gandalf.Bridgekeeper.Api.csproj | 3 +- ...ctus.Gandalf.Bridgekeeper.Contracts.csproj | 4 + .../CQRS/Commands}/LoginCommand.cs | 4 +- .../CQRS/ICommandHandler.cs | 6 + .../Extensions/ResultExtentions.cs | 2 +- .../Commands/HashPasswordCommand.cs | 3 +- .../Commands/VerifyPasswordCommand.cs | 9 + .../Controllers/AuthController.cs | 8 + .../Commands/RegisterCommandHandler.cs | 2 +- .../ValidateCredentialsCommandHandler.cs | 35 + .../Security/AuthCodeRequestCommandHandler.cs | 2 +- .../Security/CreateTokensCommandHandler.cs | 2 +- .../Security/GetTokensCommandHandler.cs | 2 +- .../Security/PasswordHashingHandler.cs | 54 +- .../Services/InitService.cs | 5 +- .../Suspectus.Gandalf.Palantir.Api.csproj | 2 + .../TestController.cs | 3 +- .../Extensions/ServiceCollectionExtensions.cs | 14 + .../PalantirClient.cs | 56 ++ .../Suspectus.Gandalf.Palantir.Client.csproj | 17 + .../Auth/ValidateCredentialsCommand.cs | 5 + ...uspectus.Gandalf.Palantir.Contracts.csproj | 13 + .../Database/CoreContext.cs | 1 + .../Entities/Tenant/TenantEntity.cs | 1 + .../20250525174938_addIsMaster.Designer.cs | 724 ++++++++++++++++++ .../Migrations/20250525174938_addIsMaster.cs | 44 ++ .../ApplicationContextModelSnapshot.cs | 6 + src/dotnet/Suspectus.Gandalf.sln | 14 + 30 files changed, 1058 insertions(+), 30 deletions(-) rename src/dotnet/{Suspectus.Gandalf.Bridgekeeper.Contracts/Controller/Auth => Suspectus.Gandalf.Core.Abstractions/CQRS/Commands}/LoginCommand.cs (56%) create mode 100644 src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/ICommandHandler.cs rename src/dotnet/{Suspectus.Gandalf.Palantir.Api => Suspectus.Gandalf.Core.Abstractions}/Extensions/ResultExtentions.cs (93%) create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/VerifyPasswordCommand.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/ValidateCredentialsCommandHandler.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Client/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Client/PalantirClient.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Client/Suspectus.Gandalf.Palantir.Client.csproj create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Controller/Auth/ValidateCredentialsCommand.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Suspectus.Gandalf.Palantir.Contracts.csproj create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250525174938_addIsMaster.Designer.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250525174938_addIsMaster.cs diff --git a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Controllers/AuthController.cs b/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Controllers/AuthController.cs index c1eb121..bc2c97e 100644 --- a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Controllers/AuthController.cs +++ b/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Controllers/AuthController.cs @@ -1,40 +1,53 @@ using Microsoft.AspNetCore.Mvc; -using Suspectus.Gandalf.Bridgekeeper.Contracts.Controller.Auth; +using Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; +using Suspectus.Gandalf.Palantir.Client; +using Suspectus.Gandalf.Palantir.Contracts.Controller.Auth; namespace Suspectus.Gandalf.Bridgekeeper.Api.Controllers; [ApiController] [Route("api/[controller]")] -public class AuthController : ControllerBase +public class AuthController(IPalantirClient client) : ControllerBase { [HttpGet("[action]")] public async Task Check() { return Ok(true); } - + [HttpPost("[action]")] public async Task Login([FromBody] LoginCommand loginCommand) { - Response.Cookies.Append("MithrandirSession", loginCommand.UsernameOrEmail, new CookieOptions - { - Secure = true, - HttpOnly = true, - SameSite = SameSiteMode.Lax, - Expires = DateTime.UtcNow.AddMinutes(30) - }); - - return Ok(); + var validationResult = await client.Auth.ValidateCredentials(new ValidateCredentialsCommand { Password = loginCommand.Password, UsernameOrEmail = loginCommand.UsernameOrEmail }); + + return validationResult.Match(valid => + { + if (!valid) + { + return BadRequest("Invalid username or password."); + } + + Response.Cookies.Append("MithrandirSession", loginCommand.UsernameOrEmail, new CookieOptions + { + Secure = true, + HttpOnly = true, + SameSite = SameSiteMode.Lax, + Expires = DateTime.UtcNow.AddMinutes(30) + }); + + return Ok(); + }, e => BadRequest($"{e.Message}\n{e.InnerException?.Message}") + ); } - + [HttpGet("[action]")] public async Task Logout() { Response.Cookies.Delete("MithrandirSession"); - + return Ok(); } - + [HttpPost("[action]")] public async Task Register() { diff --git a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Program.cs b/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Program.cs index 0e90986..d7b2ee0 100644 --- a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Program.cs +++ b/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Program.cs @@ -1,8 +1,12 @@ +using Suspectus.Gandalf.Palantir.Client.Extensions; + var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddOpenApi(); +builder.Services.AddPalantirClient(); + var app = builder.Build(); if (app.Environment.IsDevelopment()) diff --git a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Suspectus.Gandalf.Bridgekeeper.Api.csproj b/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Suspectus.Gandalf.Bridgekeeper.Api.csproj index 0be938e..cff35d2 100644 --- a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Suspectus.Gandalf.Bridgekeeper.Api.csproj +++ b/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Api/Suspectus.Gandalf.Bridgekeeper.Api.csproj @@ -7,7 +7,7 @@ - + @@ -18,6 +18,7 @@ + diff --git a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Contracts/Suspectus.Gandalf.Bridgekeeper.Contracts.csproj b/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Contracts/Suspectus.Gandalf.Bridgekeeper.Contracts.csproj index 3553e7f..a215475 100644 --- a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Contracts/Suspectus.Gandalf.Bridgekeeper.Contracts.csproj +++ b/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Contracts/Suspectus.Gandalf.Bridgekeeper.Contracts.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Contracts/Controller/Auth/LoginCommand.cs b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/LoginCommand.cs similarity index 56% rename from src/dotnet/Suspectus.Gandalf.Bridgekeeper.Contracts/Controller/Auth/LoginCommand.cs rename to src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/LoginCommand.cs index 3b08c5f..1119bf9 100644 --- a/src/dotnet/Suspectus.Gandalf.Bridgekeeper.Contracts/Controller/Auth/LoginCommand.cs +++ b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/LoginCommand.cs @@ -1,6 +1,4 @@ -using Suspectus.Gandalf.Core.Abstractions.CQRS; - -namespace Suspectus.Gandalf.Bridgekeeper.Contracts.Controller.Auth; +namespace Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; public class LoginCommand : ICommand { diff --git a/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/ICommandHandler.cs b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/ICommandHandler.cs new file mode 100644 index 0000000..0f79893 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/ICommandHandler.cs @@ -0,0 +1,6 @@ +using LanguageExt.Common; +using MediatR; + +namespace Suspectus.Gandalf.Core.Abstractions.CQRS; + +public interface ICommandHandler : IRequestHandler> where TCommand : ICommand; \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Extensions/ResultExtentions.cs b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/Extensions/ResultExtentions.cs similarity index 93% rename from src/dotnet/Suspectus.Gandalf.Palantir.Api/Extensions/ResultExtentions.cs rename to src/dotnet/Suspectus.Gandalf.Core.Abstractions/Extensions/ResultExtentions.cs index 5316394..805b4e6 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Extensions/ResultExtentions.cs +++ b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/Extensions/ResultExtentions.cs @@ -1,6 +1,6 @@ using LanguageExt.Common; -namespace Suspectus.Gandalf.Palantir.Api.Extensions; +namespace Suspectus.Gandalf.Core.Abstractions.Extensions; public static class ResultExtensions { diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/HashPasswordCommand.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/HashPasswordCommand.cs index 725d24e..69bb99b 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/HashPasswordCommand.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/HashPasswordCommand.cs @@ -1,8 +1,9 @@ using LanguageExt.Common; +using Suspectus.Gandalf.Core.Abstractions.CQRS; namespace Suspectus.Gandalf.Palantir.Api.Commands; -public class HashPasswordCommand : IGrCommand> +public class HashPasswordCommand : ICommand { public required string RawPassword { get; set; } } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/VerifyPasswordCommand.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/VerifyPasswordCommand.cs new file mode 100644 index 0000000..ea4ec9a --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/VerifyPasswordCommand.cs @@ -0,0 +1,9 @@ +using Suspectus.Gandalf.Core.Abstractions.CQRS; + +namespace Suspectus.Gandalf.Palantir.Api.Commands; + +public class VerifyPasswordCommand : ICommand +{ + public required string RawPassword { get; set; } + public required long SignInId { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AuthController.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AuthController.cs index c58fb72..b946def 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AuthController.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AuthController.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.AspNetCore.Mvc; using Suspectus.Gandalf.Palantir.Api.Commands; +using Suspectus.Gandalf.Palantir.Contracts.Controller.Auth; namespace Suspectus.Gandalf.Palantir.Api.Controllers; @@ -28,4 +29,11 @@ public class AuthController(IMediator mediator) : ControllerBase var result = await mediator.Send(getTokensCommand); return result.Match(Ok, e => BadRequest($"{e.Message}\n{e.InnerException?.Message}")); } + + [HttpPost("validate-credentials")] + public async Task ValidateCredentials([FromBody] ValidateCredentialsCommand validateCredentialsCommand) + { + var result = await mediator.Send(validateCredentialsCommand); + return result.Match(s => Ok(s), e => BadRequest($"{e.Message}\n{e.InnerException?.Message}")); + } } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/RegisterCommandHandler.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/RegisterCommandHandler.cs index 4485382..21e3b1c 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/RegisterCommandHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/RegisterCommandHandler.cs @@ -1,7 +1,7 @@ using LanguageExt.Common; using MediatR; +using Suspectus.Gandalf.Core.Abstractions.Extensions; using Suspectus.Gandalf.Palantir.Api.Commands; -using Suspectus.Gandalf.Palantir.Api.Extensions; using Suspectus.Gandalf.Palantir.Data.Database.Repositories; using Suspectus.Gandalf.Palantir.Data.Entities.Base; using Suspectus.Gandalf.Palantir.Data.Entities.Subject; diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/ValidateCredentialsCommandHandler.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/ValidateCredentialsCommandHandler.cs new file mode 100644 index 0000000..942fa42 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/ValidateCredentialsCommandHandler.cs @@ -0,0 +1,35 @@ +using LanguageExt.Common; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Suspectus.Gandalf.Core.Abstractions.CQRS; +using Suspectus.Gandalf.Core.Abstractions.Extensions; +using Suspectus.Gandalf.Palantir.Api.Commands; +using Suspectus.Gandalf.Palantir.Contracts.Controller.Auth; +using Suspectus.Gandalf.Palantir.Data.Database; +using Suspectus.Gandalf.Palantir.Data.Entities.Subject.SignIn; + +namespace Suspectus.Gandalf.Palantir.Api.Handlers.Commands; + +public class ValidateCredentialsCommandHandler(ApplicationContext applicationContext, IMediator mediator) : ICommandHandler +{ + public async Task> Handle(ValidateCredentialsCommand request, CancellationToken cancellationToken) + { + var subject = await applicationContext.Subjects + .Include(x => x.SignInMethods) + .SingleOrDefaultAsync(x => + x.Name == request.UsernameOrEmail + || + x.SignInMethods.Any(s => + s.Method == SignInMethod.Simple + && + s.Email == request.UsernameOrEmail + ), cancellationToken); + + if (subject is null) + return "User does not exist.".AsErrorResult(); + + var signIn = subject.SignInMethods.Single(x => x.Method == SignInMethod.Simple); + + return await mediator.Send(new VerifyPasswordCommand { RawPassword = request.Password, SignInId = signIn.Id!.Value }, cancellationToken); + } +} \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/AuthCodeRequestCommandHandler.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/AuthCodeRequestCommandHandler.cs index 2376004..cbcaeb2 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/AuthCodeRequestCommandHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/AuthCodeRequestCommandHandler.cs @@ -2,9 +2,9 @@ using LanguageExt.Common; using MediatR; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Suspectus.Gandalf.Core.Abstractions.Extensions; using Suspectus.Gandalf.Palantir.Api.Commands; using Suspectus.Gandalf.Palantir.Api.Handlers.Commands; -using Suspectus.Gandalf.Palantir.Api.Extensions; using Suspectus.Gandalf.Palantir.Data.Database.Repositories; using Suspectus.Gandalf.Palantir.Data.Dto; using Suspectus.Gandalf.Palantir.Data.Entities.Subject.SignIn; diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/CreateTokensCommandHandler.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/CreateTokensCommandHandler.cs index c8e2eed..595b9e7 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/CreateTokensCommandHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/CreateTokensCommandHandler.cs @@ -3,9 +3,9 @@ using JWT.Algorithms; using JWT.Builder; using JWT.Serializers; using LanguageExt.Common; +using Suspectus.Gandalf.Core.Abstractions.Extensions; using Suspectus.Gandalf.Palantir.Api.Commands; using Suspectus.Gandalf.Palantir.Api.Handlers.Commands; -using Suspectus.Gandalf.Palantir.Api.Extensions; using Suspectus.Gandalf.Palantir.Data.Database.Repositories; using Suspectus.Gandalf.Palantir.Data.Dto; using Suspectus.Gandalf.Palantir.Data.Entities.Security; diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/GetTokensCommandHandler.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/GetTokensCommandHandler.cs index dc522d1..9fecedb 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/GetTokensCommandHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/GetTokensCommandHandler.cs @@ -3,9 +3,9 @@ using System.Text; using LanguageExt.Common; using MediatR; using Microsoft.EntityFrameworkCore; +using Suspectus.Gandalf.Core.Abstractions.Extensions; using Suspectus.Gandalf.Palantir.Api.Commands; using Suspectus.Gandalf.Palantir.Api.Handlers.Commands; -using Suspectus.Gandalf.Palantir.Api.Extensions; using Suspectus.Gandalf.Palantir.Data.Database.Repositories; using Suspectus.Gandalf.Palantir.Data.Dto; diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/PasswordHashingHandler.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/PasswordHashingHandler.cs index 12a2fd8..688c578 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/PasswordHashingHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/PasswordHashingHandler.cs @@ -1,12 +1,17 @@ using LanguageExt.Common; +using MediatR; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Suspectus.Gandalf.Core.Abstractions.CQRS; +using Suspectus.Gandalf.Core.Abstractions.Extensions; using Suspectus.Gandalf.Palantir.Api.Commands; using Suspectus.Gandalf.Palantir.Api.Handlers.Commands; -using Suspectus.Gandalf.Palantir.Api.Extensions; +using Suspectus.Gandalf.Palantir.Data.Database; +using Suspectus.Gandalf.Palantir.Data.Dto; namespace Suspectus.Gandalf.Palantir.Api.Handlers.Security; -public class PasswordHashingHandler(IPasswordHasher passwordHasher) : IGrCommandHandler> +public class PasswordHashingHandler(ApplicationContext applicationContext, IMediator mediator, IPasswordHasher passwordHasher) : ICommandHandler, ICommandHandler { private static readonly object MicrosoftIsAMeme = new(); @@ -23,4 +28,49 @@ public class PasswordHashingHandler(IPasswordHasher passwordHasher) : IG } } + + public async Task> Handle(VerifyPasswordCommand request, CancellationToken cancellationToken) + { + var signIn = await applicationContext.SignIns.SingleOrDefaultAsync(x => x.Id == request.SignInId, cancellationToken: cancellationToken); + + if (signIn is null) + { + return "Sign-in method not found.".AsErrorResult(); + } + + if (signIn.PasswordHash is null) + { + return "Sign-in method does not have a password hash.".AsErrorResult(); + } + + var verification = passwordHasher.VerifyHashedPassword(MicrosoftIsAMeme, signIn.PasswordHash, request.RawPassword); + + switch (verification) + { + case PasswordVerificationResult.Failed: + return false; + + case PasswordVerificationResult.Success: + return true; + + case PasswordVerificationResult.SuccessRehashNeeded: + var newHashResult = await mediator.Send(new HashPasswordCommand { RawPassword = request.RawPassword }, cancellationToken); + + if (newHashResult.IsFaulted) + { + return newHashResult.AsErrorResult(); + } + + var newHash = newHashResult.GetValue(); + + signIn.PasswordHash = newHash; + + await applicationContext.SaveChangesAsync(cancellationToken); + + return true; + + default: + return $"Password verification type not supported: {verification}".AsErrorResult(); + } + } } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Services/InitService.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Services/InitService.cs index 6b96847..4a3c5df 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Services/InitService.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Services/InitService.cs @@ -1,8 +1,8 @@ using MediatR; using Microsoft.EntityFrameworkCore; +using Suspectus.Gandalf.Core.Abstractions.Extensions; using Suspectus.Gandalf.Palantir.Abstractions; using Suspectus.Gandalf.Palantir.Api.Commands; -using Suspectus.Gandalf.Palantir.Api.Extensions; using Suspectus.Gandalf.Palantir.Data.Database; using Suspectus.Gandalf.Palantir.Data.Entities.App; using Suspectus.Gandalf.Palantir.Data.Entities.Base; @@ -48,7 +48,7 @@ public class InitService private async Task InitializeMaster() { - var masterTenant = await _applicationContext.Tenants.SingleOrDefaultAsync(x => x.Id == 0); + var masterTenant = await _applicationContext.Tenants.SingleOrDefaultAsync(x => x.IsMaster); if (masterTenant is not null) return; @@ -92,6 +92,7 @@ public class InitService { Visibility = EntityVisibility.Active, Name = "Master", + IsMaster = true, OwnerId = housemasterUser.Id!.Value, }; diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Suspectus.Gandalf.Palantir.Api.csproj b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Suspectus.Gandalf.Palantir.Api.csproj index bbad534..f4d769f 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Suspectus.Gandalf.Palantir.Api.csproj +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Suspectus.Gandalf.Palantir.Api.csproj @@ -22,6 +22,8 @@ + + diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/TestController.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/TestController.cs index 870102b..f5deb4c 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/TestController.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/TestController.cs @@ -36,7 +36,8 @@ public class TestController(IHashids hashids, ApplicationContext context, Invoke { Visibility = EntityVisibility.Active, OwnerId = invoker.SubjectId, - Name = command.Name + Name = command.Name, + IsMaster = false }, SubjectId = invoker.SubjectId, InternalAuthorities = authorities.ToHashSet() diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Client/Extensions/ServiceCollectionExtensions.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Client/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..19c404a --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Client/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Suspectus.Gandalf.Palantir.Client.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddPalantirClient(this IServiceCollection services) + { + services.AddHttpClient(opt => opt.BaseAddress = new Uri("https://localhost:7269/")); // TODO: WTF BRO WHY YOU DONT WORK + services.AddScoped(); + services.AddScoped(); + return services; + } +} \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Client/PalantirClient.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Client/PalantirClient.cs new file mode 100644 index 0000000..6bf8f07 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Client/PalantirClient.cs @@ -0,0 +1,56 @@ +using System.Net.Http.Json; +using LanguageExt.Common; +using Suspectus.Gandalf.Core.Abstractions.Extensions; +using Suspectus.Gandalf.Palantir.Contracts.Controller.Auth; + +namespace Suspectus.Gandalf.Palantir.Client; + +public interface IPalantirClient +{ + IPalantirAuthClient Auth { get; init; } +} + +public interface IPalantirAuthClient +{ + public Task> ValidateCredentials(ValidateCredentialsCommand validateCredentialsCommand); +} + +public class PalantirClient : IPalantirClient +{ + public PalantirClient(IPalantirAuthClient authClient) + { + Auth = authClient; + } + + public IPalantirAuthClient Auth { get; init; } +} + +public class PalantirAuthClient : IPalantirAuthClient +{ + private readonly HttpClient _http; + + public PalantirAuthClient(HttpClient http) + { + _http = http; + } + + public async Task> ValidateCredentials(ValidateCredentialsCommand validateCredentialsCommand) + { + try + { + var response = await _http.PostAsJsonAsync("validate-credentials", validateCredentialsCommand); + + if (!response.IsSuccessStatusCode) + { + return $"status: {response.StatusCode}".AsErrorResult(); + } + + var result = await response.Content.ReadFromJsonAsync(); + return result; + } + catch (HttpRequestException e) + { + return $"status: {e.StatusCode} message: {e.Message}".AsErrorResult(); + } + } +} \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Client/Suspectus.Gandalf.Palantir.Client.csproj b/src/dotnet/Suspectus.Gandalf.Palantir.Client/Suspectus.Gandalf.Palantir.Client.csproj new file mode 100644 index 0000000..8792ec3 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Client/Suspectus.Gandalf.Palantir.Client.csproj @@ -0,0 +1,17 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Controller/Auth/ValidateCredentialsCommand.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Controller/Auth/ValidateCredentialsCommand.cs new file mode 100644 index 0000000..94c735d --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Controller/Auth/ValidateCredentialsCommand.cs @@ -0,0 +1,5 @@ +using Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; + +namespace Suspectus.Gandalf.Palantir.Contracts.Controller.Auth; + +public class ValidateCredentialsCommand: LoginCommand; \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Suspectus.Gandalf.Palantir.Contracts.csproj b/src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Suspectus.Gandalf.Palantir.Contracts.csproj new file mode 100644 index 0000000..3553e7f --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Suspectus.Gandalf.Palantir.Contracts.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Database/CoreContext.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Database/CoreContext.cs index 2470811..d93a2c1 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Database/CoreContext.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Database/CoreContext.cs @@ -12,6 +12,7 @@ public abstract class CoreContext(DbContextOptions options) : DbContext(op { public DbSet Tenants { get; set; } public DbSet Subjects { get; set; } + public DbSet SignIns { get; set; } public DbSet Apps { get; set; } public DbSet TenantSubjectRelations { get; set; } public DbSet AppSubjectRelations { get; set; } diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Tenant/TenantEntity.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Tenant/TenantEntity.cs index 43d5728..f8f8fa4 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Tenant/TenantEntity.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Tenant/TenantEntity.cs @@ -25,4 +25,5 @@ public class TenantVersionEntity : TenantData, IVersionEntity public abstract class TenantData : OwnerData { public required string Name { get; set; } + public required bool IsMaster { get; set; } } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250525174938_addIsMaster.Designer.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250525174938_addIsMaster.Designer.cs new file mode 100644 index 0000000..7b0d345 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250525174938_addIsMaster.Designer.cs @@ -0,0 +1,724 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Suspectus.Gandalf.Palantir.Data.Database; + +#nullable disable + +namespace W542.GandalfReborn.Data.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20250525174938_addIsMaster")] + partial class addIsMaster + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.App.AppEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("Visibility") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("TenantId"); + + b.ToTable("App", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.App.AppSubjectRelationEntity", b => + { + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("AppId") + .HasColumnType("bigint"); + + b.HasKey("SubjectId", "AppId"); + + b.HasIndex("AppId"); + + b.ToTable("AppSubjectRelation", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.App.AppSubjectRelationVersionEntity", b => + { + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("AppId") + .HasColumnType("bigint"); + + b.Property("At") + .HasColumnType("timestamp with time zone"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("SuspectId") + .HasColumnType("bigint"); + + b.HasKey("SubjectId", "AppId", "At"); + + b.HasIndex("SuspectId"); + + b.ToTable("AppSubjectRelationVersion", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.App.AppVersionEntity", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("At") + .HasColumnType("timestamp with time zone"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("SuspectId") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("Visibility") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id", "At"); + + b.HasIndex("SuspectId"); + + b.ToTable("AppVersion", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.AppSubjectRelationInternalAuthorityRelationEntity", b => + { + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("AppId") + .HasColumnType("bigint"); + + b.Property("InternalAuthorityId") + .HasColumnType("bigint"); + + b.HasKey("SubjectId", "AppId", "InternalAuthorityId"); + + b.HasIndex("InternalAuthorityId"); + + b.ToTable("AppSubjectRelationInternalAuthorityRelation", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.AppSubjectRelationInternalAuthorityRelationVersionEntity", b => + { + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("AppId") + .HasColumnType("bigint"); + + b.Property("InternalAuthorityId") + .HasColumnType("bigint"); + + b.Property("At") + .HasColumnType("timestamp with time zone"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("SuspectId") + .HasColumnType("bigint"); + + b.HasKey("SubjectId", "AppId", "InternalAuthorityId", "At"); + + b.HasIndex("SuspectId"); + + b.ToTable("AppSubjectRelationInternalAuthorityRelationVersion", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.AuthCodeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Algorithm") + .IsRequired() + .HasColumnType("text"); + + b.Property("Challenge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Code") + .IsRequired() + .HasColumnType("text"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRevoked") + .HasColumnType("boolean"); + + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("AuthCode", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.AuthorityEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Authority", "gr"); + + b.HasData( + new + { + Id = 1L, + Description = "Allows users to read tenants", + Name = "Tenant_Read", + Type = "Tenant" + }, + new + { + Id = 2L, + Description = "Allows users to read apps", + Name = "App_Read", + Type = "App" + }); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.TenantSubjectRelationInternalAuthorityRelationEntity", b => + { + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("InternalAuthorityId") + .HasColumnType("bigint"); + + b.HasKey("SubjectId", "TenantId", "InternalAuthorityId"); + + b.HasIndex("InternalAuthorityId"); + + b.ToTable("TenantSubjectRelationInternalAuthorityRelation", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.TenantSubjectRelationInternalAuthorityRelationVersionEntity", b => + { + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("InternalAuthorityId") + .HasColumnType("bigint"); + + b.Property("At") + .HasColumnType("timestamp with time zone"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("SuspectId") + .HasColumnType("bigint"); + + b.HasKey("SubjectId", "TenantId", "InternalAuthorityId", "At"); + + b.HasIndex("SuspectId"); + + b.ToTable("TenantSubjectRelationInternalAuthorityRelationVersion", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.TokenMetadataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRevoked") + .HasColumnType("boolean"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UsedBy") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("TokenMetadata", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SignIn.SignInEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("IsLegacy") + .HasColumnType("boolean"); + + b.Property("Method") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("Visibility") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("SubjectId"); + + b.ToTable("SignIn", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Visibility") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Subject", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IsMaster") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("bigint"); + + b.Property("Visibility") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("Tenant", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantSubjectRelationEntity", b => + { + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.HasKey("SubjectId", "TenantId"); + + b.HasIndex("TenantId"); + + b.ToTable("TenantSubjectRelation", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantSubjectRelationVersionEntity", b => + { + b.Property("SubjectId") + .HasColumnType("bigint"); + + b.Property("TenantId") + .HasColumnType("bigint"); + + b.Property("At") + .HasColumnType("timestamp with time zone"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("SuspectId") + .HasColumnType("bigint"); + + b.HasKey("SubjectId", "TenantId", "At"); + + b.HasIndex("SuspectId"); + + b.ToTable("TenantSubjectRelationVersion", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantVersionEntity", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("At") + .HasColumnType("timestamp with time zone"); + + b.Property("Action") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsMaster") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("bigint"); + + b.Property("SuspectId") + .HasColumnType("bigint"); + + b.Property("Visibility") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id", "At"); + + b.HasIndex("SuspectId"); + + b.ToTable("TenantVersion", "gr"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.App.AppEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantEntity", "Tenant") + .WithMany("Apps") + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.App.AppSubjectRelationEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.App.AppEntity", "App") + .WithMany() + .HasForeignKey("AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Subject") + .WithMany() + .HasForeignKey("SubjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("App"); + + b.Navigation("Subject"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.App.AppSubjectRelationVersionEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Suspect") + .WithMany() + .HasForeignKey("SuspectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.App.AppSubjectRelationEntity", "Reference") + .WithMany() + .HasForeignKey("SubjectId", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Reference"); + + b.Navigation("Suspect"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.App.AppVersionEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.App.AppEntity", "Reference") + .WithMany() + .HasForeignKey("Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Suspect") + .WithMany() + .HasForeignKey("SuspectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Reference"); + + b.Navigation("Suspect"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.AppSubjectRelationInternalAuthorityRelationEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Security.AuthorityEntity", "InternalAuthority") + .WithMany() + .HasForeignKey("InternalAuthorityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.App.AppSubjectRelationEntity", "AppSubjectRelation") + .WithMany() + .HasForeignKey("SubjectId", "AppId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppSubjectRelation"); + + b.Navigation("InternalAuthority"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.AppSubjectRelationInternalAuthorityRelationVersionEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Suspect") + .WithMany() + .HasForeignKey("SuspectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Security.AppSubjectRelationInternalAuthorityRelationEntity", "Reference") + .WithMany() + .HasForeignKey("SubjectId", "AppId", "InternalAuthorityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Reference"); + + b.Navigation("Suspect"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.TenantSubjectRelationInternalAuthorityRelationEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Security.AuthorityEntity", "InternalAuthority") + .WithMany() + .HasForeignKey("InternalAuthorityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantSubjectRelationEntity", "TenantSubjectRelation") + .WithMany() + .HasForeignKey("SubjectId", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InternalAuthority"); + + b.Navigation("TenantSubjectRelation"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Security.TenantSubjectRelationInternalAuthorityRelationVersionEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Suspect") + .WithMany() + .HasForeignKey("SuspectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Security.TenantSubjectRelationInternalAuthorityRelationEntity", "Reference") + .WithMany() + .HasForeignKey("SubjectId", "TenantId", "InternalAuthorityId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Reference"); + + b.Navigation("Suspect"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SignIn.SignInEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Subject") + .WithMany("SignInMethods") + .HasForeignKey("SubjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Subject"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantSubjectRelationEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Subject") + .WithMany() + .HasForeignKey("SubjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantEntity", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Subject"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantSubjectRelationVersionEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Suspect") + .WithMany() + .HasForeignKey("SuspectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantSubjectRelationEntity", "Reference") + .WithMany() + .HasForeignKey("SubjectId", "TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Reference"); + + b.Navigation("Suspect"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantVersionEntity", b => + { + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantEntity", "Reference") + .WithMany() + .HasForeignKey("Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", "Suspect") + .WithMany() + .HasForeignKey("SuspectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Reference"); + + b.Navigation("Suspect"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Subject.SubjectEntity", b => + { + b.Navigation("SignInMethods"); + }); + + modelBuilder.Entity("Suspectus.Gandalf.Palantir.Data.Entities.Tenant.TenantEntity", b => + { + b.Navigation("Apps"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250525174938_addIsMaster.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250525174938_addIsMaster.cs new file mode 100644 index 0000000..ea8747d --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250525174938_addIsMaster.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace W542.GandalfReborn.Data.Migrations +{ + /// + public partial class addIsMaster : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsMaster", + schema: "gr", + table: "TenantVersion", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "IsMaster", + schema: "gr", + table: "Tenant", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsMaster", + schema: "gr", + table: "TenantVersion"); + + migrationBuilder.DropColumn( + name: "IsMaster", + schema: "gr", + table: "Tenant"); + } + } +} diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/ApplicationContextModelSnapshot.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/ApplicationContextModelSnapshot.cs index bcbe32a..cff0e06 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/ApplicationContextModelSnapshot.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/ApplicationContextModelSnapshot.cs @@ -386,6 +386,9 @@ namespace W542.GandalfReborn.Data.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("IsMaster") + .HasColumnType("boolean"); + b.Property("Name") .IsRequired() .HasColumnType("text"); @@ -456,6 +459,9 @@ namespace W542.GandalfReborn.Data.Migrations .IsRequired() .HasColumnType("text"); + b.Property("IsMaster") + .HasColumnType("boolean"); + b.Property("Name") .IsRequired() .HasColumnType("text"); diff --git a/src/dotnet/Suspectus.Gandalf.sln b/src/dotnet/Suspectus.Gandalf.sln index 6037e87..60eb5f5 100644 --- a/src/dotnet/Suspectus.Gandalf.sln +++ b/src/dotnet/Suspectus.Gandalf.sln @@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Suspectus.Gandalf.Core.Abst EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Suspectus.Gandalf.Bridgekeeper.Contracts", "Suspectus.Gandalf.Bridgekeeper.Contracts\Suspectus.Gandalf.Bridgekeeper.Contracts.csproj", "{4ED2E3BC-FE29-4041-8274-987EB60BD5FF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Suspectus.Gandalf.Palantir.Client", "Suspectus.Gandalf.Palantir.Client\Suspectus.Gandalf.Palantir.Client.csproj", "{1161299F-3623-42C9-9A8D-AB7664BF5FD6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Suspectus.Gandalf.Palantir.Contracts", "Suspectus.Gandalf.Palantir.Contracts\Suspectus.Gandalf.Palantir.Contracts.csproj", "{AEE0EFB9-CA30-47CE-B494-C46A08DDEC72}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,6 +58,14 @@ Global {4ED2E3BC-FE29-4041-8274-987EB60BD5FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {4ED2E3BC-FE29-4041-8274-987EB60BD5FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {4ED2E3BC-FE29-4041-8274-987EB60BD5FF}.Release|Any CPU.Build.0 = Release|Any CPU + {1161299F-3623-42C9-9A8D-AB7664BF5FD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1161299F-3623-42C9-9A8D-AB7664BF5FD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1161299F-3623-42C9-9A8D-AB7664BF5FD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1161299F-3623-42C9-9A8D-AB7664BF5FD6}.Release|Any CPU.Build.0 = Release|Any CPU + {AEE0EFB9-CA30-47CE-B494-C46A08DDEC72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AEE0EFB9-CA30-47CE-B494-C46A08DDEC72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AEE0EFB9-CA30-47CE-B494-C46A08DDEC72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AEE0EFB9-CA30-47CE-B494-C46A08DDEC72}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {44C283FD-2E01-496C-9E8F-4E43C26C9733} = {A82EA24B-1379-41B2-A363-CDCBF9F18833} @@ -63,5 +75,7 @@ Global {264B8A9E-7A70-4DE3-906B-7E0722D09205} = {D54D8A40-A5C3-4273-B4D7-F2B83056161B} {F8333692-CA81-4298-A2F5-CF7D3ACCE230} = {3E672AE3-D2E4-49C0-AB18-65E799E5277A} {4ED2E3BC-FE29-4041-8274-987EB60BD5FF} = {D54D8A40-A5C3-4273-B4D7-F2B83056161B} + {AEE0EFB9-CA30-47CE-B494-C46A08DDEC72} = {A82EA24B-1379-41B2-A363-CDCBF9F18833} + {1161299F-3623-42C9-9A8D-AB7664BF5FD6} = {A82EA24B-1379-41B2-A363-CDCBF9F18833} EndGlobalSection EndGlobal