diff --git a/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/LoginCommand.cs b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/LoginCommand.cs deleted file mode 100644 index 1119bf9..0000000 --- a/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/LoginCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; - -public class LoginCommand : ICommand -{ - public required string UsernameOrEmail { get; set; } - public required string Password { get; set; } -} \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/ValidateCredentialsCommand.cs b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/ValidateCredentialsCommand.cs new file mode 100644 index 0000000..20bee0f --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/ValidateCredentialsCommand.cs @@ -0,0 +1,13 @@ +namespace Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; + +public class ValidateCredentialsCommand : ICommand +{ + public required string UsernameOrEmail { get; set; } + public required string Password { get; set; } +} + +public class ValidateCredentialsResponse +{ + public required bool IsValid { get; set; } + public required long SubjectId { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Core.Abstractions/DTOs/Internal/Auth/TokenRequest.cs b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/DTOs/Internal/Auth/TokenRequest.cs new file mode 100644 index 0000000..c5bcd1c --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Core.Abstractions/DTOs/Internal/Auth/TokenRequest.cs @@ -0,0 +1,53 @@ +using Suspectus.Gandalf.Core.Abstractions.CQRS; + +namespace Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth; + +public enum GrandType +{ + Password, + RefreshToken, +} + +public interface ITokenRequestBase +{ + public GrandType GrandType { get; set; } + public string ClientId { get; set; } +} + +public interface ITokenRequestWithScope : ITokenRequestBase +{ + public string Scope { get; set; } +} + +public interface IResourceOwnerPasswordCredentialsTokenRequest : ITokenRequestWithScope +{ + public string ClientSecret { get; set; } + public string Username { get; set; } + public string Password { get; set; } +} + +public interface IRefreshTokenTokenRequest : ITokenRequestBase +{ + public string RefreshToken { get; set; } +} + +public class TokenRequestCommand : IResourceOwnerPasswordCredentialsTokenRequest, IRefreshTokenTokenRequest, ICommand +{ + public required GrandType GrandType { get; set; } + public required string ClientId { get; set; } = string.Empty; + public string Scope { get; set; } = string.Empty; + public string ClientSecret { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public string RefreshToken { get; set; } = string.Empty; +} + +public class TokenRequestResponse +{ + public required string SubjectId { get; set; } + public required string Scope { get; set; } + public required string AccessToken { get; set; } + public required string RefreshToken { get; set; } + public required DateTimeOffset AccessTokenExpiresAt { get; set; } + public required DateTimeOffset RefreshTokenExpiresAt { get; set; } +} \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/AuthCodeRequestCommand.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/AuthCodeRequestCommand.cs index 95216ad..43b4d77 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/AuthCodeRequestCommand.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/AuthCodeRequestCommand.cs @@ -1,4 +1,5 @@ using LanguageExt.Common; +using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth; using Suspectus.Gandalf.Palantir.Data.Dto; namespace Suspectus.Gandalf.Palantir.Api.Commands; @@ -11,7 +12,8 @@ public class AuthCodeRequestCommand : IGrCommand> public required string Algorithm { get; set; } }; -public class GetTokensCommand : IGrCommand> +[Obsolete("Use TokenRequestCommand instead.")] +public class GetTokensCommand : IGrCommand> { public required string AuthCode { get; set; } public required string ProofKey { get; set; } diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/CreateTokensCommand.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/CreateTokensCommand.cs index a66dc8e..98dc95c 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/CreateTokensCommand.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Commands/CreateTokensCommand.cs @@ -1,9 +1,13 @@ using LanguageExt.Common; +using Suspectus.Gandalf.Core.Abstractions.CQRS; +using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth; using Suspectus.Gandalf.Palantir.Data.Dto; namespace Suspectus.Gandalf.Palantir.Api.Commands; -public class CreateTokensCommand : IGrCommand> +public class CreateTokensCommand : ICommand { public required long SubjectId { get; set; } + public required long ClientId { get; set; } + public required string Scope { get; set; } } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AppController.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/Internal/AppController.cs similarity index 69% rename from src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AppController.cs rename to src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/Internal/AppController.cs index 385a7a8..72af7f1 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AppController.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/Internal/AppController.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore; using Suspectus.Gandalf.Core.Abstractions.DTOs; using Suspectus.Gandalf.Palantir.Data.Database; -namespace Suspectus.Gandalf.Palantir.Api.Controllers; +namespace Suspectus.Gandalf.Palantir.Api.Controllers.Internal; [ApiController] [Route("api/internal/[controller]")] @@ -33,7 +33,7 @@ public class AppController : ControllerBase Id = appId, IsMaster = x.Tenant!.IsMaster, Name = x.Name, - BaseAddress = x.Tenant!.IsMaster ? "http://localhost:5035/" : "null" + BaseAddress = x.BaseAddress }) .SingleOrDefaultAsync(cancellationToken: cancellationToken); return Ok(appInfo); @@ -48,9 +48,26 @@ public class AppController : ControllerBase Id = _hashids.EncodeLong(x.Id!.Value), IsMaster = x.Tenant!.IsMaster, Name = x.Name, - BaseAddress = x.Tenant!.IsMaster ? "http://localhost:5035/" : "null" + BaseAddress = x.BaseAddress }).ToListAsync(cancellationToken: cancellationToken); return Ok(appInfos); } + + [HttpGet("master/info")] + [AllowAnonymous] + public async Task GetMasterInfo(CancellationToken cancellationToken) + { + var appInfo = await _context.Apps + .Where(x => x.Tenant!.IsMaster) + .Select(x => new AppInfo + { + Id = _hashids.EncodeLong(x.Id!.Value), + IsMaster = x.Tenant!.IsMaster, + Name = x.Name, + BaseAddress = x.BaseAddress + }) + .SingleOrDefaultAsync(cancellationToken: cancellationToken); + return Ok(appInfo); + } } \ 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/Internal/AuthController.cs similarity index 72% rename from src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AuthController.cs rename to src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/Internal/AuthController.cs index d4beec0..d8a5b0c 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/AuthController.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/Internal/AuthController.cs @@ -1,14 +1,17 @@ +using System.ComponentModel.DataAnnotations; using MediatR; using Microsoft.AspNetCore.Mvc; +using Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; +using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth; using Suspectus.Gandalf.Palantir.Api.Commands; -using Suspectus.Gandalf.Palantir.Contracts.Controller.Auth; -namespace Suspectus.Gandalf.Palantir.Api.Controllers; +namespace Suspectus.Gandalf.Palantir.Api.Controllers.Internal; [ApiController] [Route("api/internal/[controller]")] public class AuthController(IMediator mediator) : ControllerBase { + [Obsolete("Unused, will be removed in future versions.")] [HttpPost("[action]")] public async Task Register([FromBody] RegisterCommand registerCommand) { @@ -16,6 +19,7 @@ public class AuthController(IMediator mediator) : ControllerBase return result.Match(Ok, e => BadRequest($"{e.Message}\n{e.InnerException?.Message}")); } + [Obsolete("Unused, will be removed in future versions.")] [HttpPost("[action]")] public async Task Login([FromBody] AuthCodeRequestCommand authCodeRequestCommand) { @@ -24,9 +28,9 @@ public class AuthController(IMediator mediator) : ControllerBase } [HttpPost("[action]")] - public async Task Token([FromBody] GetTokensCommand getTokensCommand) + public async Task Token([FromBody][Required] TokenRequestCommand tokenRequestCommand) { - var result = await mediator.Send(getTokensCommand); + var result = await mediator.Send(tokenRequestCommand); return result.Match(Ok, e => BadRequest($"{e.Message}\n{e.InnerException?.Message}")); } diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/ValidateCredentialsCommandHandler.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/ValidateCredentialsCommandHandler.cs index 942fa42..9e2280a 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/ValidateCredentialsCommandHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Commands/ValidateCredentialsCommandHandler.cs @@ -2,17 +2,17 @@ using LanguageExt.Common; using MediatR; using Microsoft.EntityFrameworkCore; using Suspectus.Gandalf.Core.Abstractions.CQRS; +using Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; 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 class ValidateCredentialsCommandHandler(ApplicationContext applicationContext, IMediator mediator) : ICommandHandler { - public async Task> Handle(ValidateCredentialsCommand request, CancellationToken cancellationToken) + public async Task> Handle(ValidateCredentialsCommand request, CancellationToken cancellationToken) { var subject = await applicationContext.Subjects .Include(x => x.SignInMethods) @@ -26,10 +26,21 @@ public class ValidateCredentialsCommandHandler(ApplicationContext applicationCon ), cancellationToken); if (subject is null) - return "User does not exist.".AsErrorResult(); + 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); + var verifyPasswordResponse= await mediator.Send(new VerifyPasswordCommand { RawPassword = request.Password, SignInId = signIn.Id!.Value }, cancellationToken); + + if (verifyPasswordResponse.IsFaulted) + { + return verifyPasswordResponse.AsErrorResult(); + } + + return new ValidateCredentialsResponse + { + IsValid = verifyPasswordResponse.GetValue(), + SubjectId = signIn.SubjectId + }; } } \ No newline at end of file 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 595b9e7..886f119 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/CreateTokensCommandHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/CreateTokensCommandHandler.cs @@ -3,24 +3,35 @@ using JWT.Algorithms; using JWT.Builder; using JWT.Serializers; using LanguageExt.Common; +using Microsoft.EntityFrameworkCore; +using Suspectus.Gandalf.Core.Abstractions.CQRS; +using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth; using Suspectus.Gandalf.Core.Abstractions.Extensions; using Suspectus.Gandalf.Palantir.Api.Commands; -using Suspectus.Gandalf.Palantir.Api.Handlers.Commands; +using Suspectus.Gandalf.Palantir.Data.Database; using Suspectus.Gandalf.Palantir.Data.Database.Repositories; -using Suspectus.Gandalf.Palantir.Data.Dto; using Suspectus.Gandalf.Palantir.Data.Entities.Security; using Suspectus.Gandalf.Palantir.Security.Scheme; namespace Suspectus.Gandalf.Palantir.Api.Handlers.Security; -public class CreateTokensCommandHandler(TimeProvider timeProvider, IConfiguration configuration, IHashids hashids, ITokenMetadataRepository tokenMetadataRepository) : IGrCommandHandler> +public class CreateTokensCommandHandler(TimeProvider timeProvider, IConfiguration configuration, IHashids hashids, ITokenMetadataRepository tokenMetadataRepository, ApplicationContext context) : ICommandHandler { - public async Task> Handle(CreateTokensCommand command, CancellationToken cancellationToken) + public async Task> Handle(CreateTokensCommand command, CancellationToken cancellationToken) { + var subject = await context.Subjects.SingleOrDefaultAsync(x => x.Id == command.SubjectId, cancellationToken: cancellationToken); + var app = await context.Apps.SingleOrDefaultAsync(x => x.Id == command.ClientId, cancellationToken: cancellationToken); + + if (subject is null) + return "Subject not found.".AsErrorResult(); + + if (app is null) + return "App not found.".AsErrorResult(); + var iat = timeProvider.GetUtcNow(); var accessExp = iat.AddMinutes(5); var refreshExp = iat.AddDays(7); - + var builder = JwtBuilder.Create() .WithAlgorithm(new HMACSHA512Algorithm()) .WithSecret(configuration.GetValue("JwtSecret")) @@ -30,50 +41,59 @@ public class CreateTokensCommandHandler(TimeProvider timeProvider, IConfiguratio { Expiration = accessExp, IsRevoked = false, - TokenType = TokenType.User, - UsedBy = command.SubjectId + TokenType = TokenType.Access, + UsedBy = command.SubjectId, + UsedFor = command.ClientId, + Scope = command.Scope }; var refreshTokenMetadata = new TokenMetadataEntity { Expiration = refreshExp, IsRevoked = false, - TokenType = TokenType.User, - UsedBy = command.SubjectId + TokenType = TokenType.Refresh, + UsedBy = command.SubjectId, + UsedFor = command.ClientId, + Scope = command.Scope }; var entities = await tokenMetadataRepository.Upsert([accessTokenMetadata, refreshTokenMetadata]); if (entities.IsFaulted) - return entities.AsErrorResult>(); + return entities.AsErrorResult>(); var baseUrl = configuration.GetValue("BaseUrl") ?? "https://localhost:7269"; + var aud = $"{app.Name.ToLower()}-api"; + var sub = hashids.EncodeLong(command.SubjectId); var accessToken = builder.Encode(new GandalfRebornJwtBody { Id = hashids.EncodeLong(accessTokenMetadata.Id!.Value), - Sub = hashids.EncodeLong(command.SubjectId), + Sub = sub, Iat = iat, Exp = accessExp, Iss = baseUrl, - Aud = baseUrl - + Aud = aud }); var refreshToken = builder.Encode(new GandalfRebornJwtBody { Id = hashids.EncodeLong(refreshTokenMetadata.Id!.Value), - Sub = hashids.EncodeLong(command.SubjectId), + Sub = sub, Iat = iat, Exp = refreshExp, Iss = baseUrl, - Aud = baseUrl + Aud = aud }); - return new Result(new TokenDto + return new TokenRequestResponse { + SubjectId = sub, AccessToken = accessToken, - RefreshToken = refreshToken - }); + RefreshToken = refreshToken, + Scope = command.Scope, + AccessTokenExpiresAt = accessExp, + RefreshTokenExpiresAt = refreshExp + }; } } \ No newline at end of file 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 9fecedb..8fb5cb8 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/GetTokensCommandHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Handlers/Security/GetTokensCommandHandler.cs @@ -1,19 +1,30 @@ using System.Security.Cryptography; using System.Text; +using HashidsNet; +using JWT.Algorithms; +using JWT.Builder; +using JWT.Serializers; using LanguageExt.Common; using MediatR; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Suspectus.Gandalf.Core.Abstractions.CQRS; +using Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; +using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth; using Suspectus.Gandalf.Core.Abstractions.Extensions; using Suspectus.Gandalf.Palantir.Api.Commands; using Suspectus.Gandalf.Palantir.Api.Handlers.Commands; +using Suspectus.Gandalf.Palantir.Data.Database; using Suspectus.Gandalf.Palantir.Data.Database.Repositories; using Suspectus.Gandalf.Palantir.Data.Dto; +using Suspectus.Gandalf.Palantir.Data.Entities.Security; +using Suspectus.Gandalf.Palantir.Security.Scheme; namespace Suspectus.Gandalf.Palantir.Api.Handlers.Security; -public class GetTokensCommandHandler(IAuthCodeRepository authCodeRepository, TimeProvider timeProvider, IMediator mediator) : IGrCommandHandler> +public class GetTokensCommandHandler(IAuthCodeRepository authCodeRepository, TimeProvider timeProvider, IMediator mediator) : IGrCommandHandler> { - public async Task> Handle(GetTokensCommand command, CancellationToken cancellationToken) + public async Task> Handle(GetTokensCommand command, CancellationToken cancellationToken) { var authCode = await authCodeRepository .Query(q => q.Where(x => x.Code == command.AuthCode)) @@ -21,7 +32,7 @@ public class GetTokensCommandHandler(IAuthCodeRepository authCodeRepository, Tim if (authCode is null) { - return "Auth code could not be found.".AsErrorResult(); + return "Auth code could not be found.".AsErrorResult(); } if (authCode.Expiration <= timeProvider.GetUtcNow()) @@ -30,7 +41,7 @@ public class GetTokensCommandHandler(IAuthCodeRepository authCodeRepository, Tim .Query(q => q.Where(x => x.Id == authCode.Id)) .ExecuteDeleteAsync(cancellationToken); - return "Auth code is expired.".AsErrorResult(); + return "Auth code is expired.".AsErrorResult(); } switch (authCode.Algorithm) @@ -40,17 +51,84 @@ public class GetTokensCommandHandler(IAuthCodeRepository authCodeRepository, Tim var sha256Bytes = SHA256.HashData(proofKeyBytes); var hexProofKey = BitConverter.ToString(sha256Bytes).Replace("-", string.Empty).ToLower(); if (authCode.Challenge != hexProofKey) - return "Code challenge failed.".AsErrorResult(); + return "Code challenge failed.".AsErrorResult(); break; default: - return $"Algorithm '{authCode.Algorithm}' not supported".AsErrorResult(); + return $"Algorithm '{authCode.Algorithm}' not supported".AsErrorResult(); } await authCodeRepository .Query(q => q.Where(x => x.Id == authCode.Id)) .ExecuteDeleteAsync(cancellationToken); - return await mediator.Send(new CreateTokensCommand {SubjectId = authCode.SubjectId}, cancellationToken); + return await mediator.Send(new CreateTokensCommand {SubjectId = authCode.SubjectId, Scope = "null", ClientId = 0}, cancellationToken); + } +} + +public class TokenRequestCommandHandler(TimeProvider timeProvider, IHashids hashids, ApplicationContext context, IConfiguration configuration, IMediator mediator) : ICommandHandler +{ + public async Task> Handle(TokenRequestCommand command, CancellationToken cancellationToken) + { + ValidateCredentialsResponse validateCredentialsResponse; + CreateTokensCommand createTokensCommand; + if (command.GrandType == GrandType.Password) + { + var validateCredentialsResult = await mediator.Send(new ValidateCredentialsCommand + { + UsernameOrEmail = command.Username, + Password = command.Password + }, cancellationToken); + + if (validateCredentialsResult.IsFaulted) + { + return validateCredentialsResult.AsErrorResult(); + } + + validateCredentialsResponse = validateCredentialsResult.GetValue(); + + if (!validateCredentialsResponse.IsValid) + { + return "Username or password wrong.".AsErrorResult(); + } + + createTokensCommand = new CreateTokensCommand + { + SubjectId = validateCredentialsResponse.SubjectId, + ClientId = hashids.DecodeSingleLong(command.ClientId), + Scope = command.Scope + }; + } + else + { + var decodedToken = JwtBuilder.Create() + .WithAlgorithm(new HMACSHA512Algorithm()) + .WithSecret(configuration.GetValue("JwtSecret")) + .MustVerifySignature() + .WithJsonSerializer(new JsonNetSerializer()) + .Decode(command.RefreshToken); + + if (decodedToken.Exp is null || decodedToken.Exp?.ToUniversalTime() <= timeProvider.GetUtcNow()) + return "One does not simply provide an expired token.".AsErrorResult(); + + var refreshTokenMetadata = await context.TokenMetadata.SingleOrDefaultAsync(x => x.Id == hashids.DecodeSingleLong(decodedToken.Sub), cancellationToken: cancellationToken); + + if (refreshTokenMetadata is null) + { + return "Token not found.".AsErrorResult(); + } + + if (refreshTokenMetadata.TokenType != TokenType.Refresh) + return "Token is not a refresh token".AsErrorResult(); + + createTokensCommand = new CreateTokensCommand + { + SubjectId = refreshTokenMetadata.UsedBy, + ClientId = refreshTokenMetadata.UsedFor, + Scope = refreshTokenMetadata.Scope + }; + } + + return await mediator.Send(createTokensCommand, cancellationToken); } } \ 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 4a3c5df..a91b7a9 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Services/InitService.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Services/InitService.cs @@ -18,13 +18,15 @@ public class InitService private readonly ApplicationContext _applicationContext; private readonly IMediator _mediator; + private readonly IConfiguration _configuration; private readonly ILogger _logger; private readonly InvokerContext _invokerContext; - public InitService(ApplicationContext applicationContext, IMediator mediator, ILogger logger, InvokerContext invokerContext) + public InitService(ApplicationContext applicationContext, IMediator mediator, IConfiguration configuration, ILogger logger, InvokerContext invokerContext) { _applicationContext = applicationContext; _mediator = mediator; + _configuration = configuration; _logger = logger; _invokerContext = invokerContext; } @@ -48,10 +50,30 @@ public class InitService private async Task InitializeMaster() { + var baseAddress = _configuration.GetValue("BaseUrl") ?? "https://localhost:7269"; var masterTenant = await _applicationContext.Tenants.SingleOrDefaultAsync(x => x.IsMaster); if (masterTenant is not null) + { + var existingMasterApp = await _applicationContext.Apps + .Include(x => x.Tenant) + .SingleAsync(x => x.TenantId == masterTenant.Id); + + if (existingMasterApp.BaseAddress == baseAddress) return; + + existingMasterApp.BaseAddress = baseAddress; + + _invokerContext.Invoker = new Invoker + { + SubjectId = existingMasterApp.Tenant!.OwnerId, + TenantAuthorityDictionary = new Dictionary>(), + AppAuthorityDictionary = new Dictionary>(), + IsAuthenticated = true + }; + + await _applicationContext.SaveChangesAsync(); return; + } var masterTenantPassword = GeneratePassword(); var masterTenantPasswordHashResult = await _mediator.Send(new HashPasswordCommand { RawPassword = masterTenantPassword }); @@ -103,7 +125,8 @@ public class InitService { Visibility = EntityVisibility.Active, TenantId = masterTenant.Id!.Value, - Name = "Master" + Name = "Master", + BaseAddress = baseAddress }; _applicationContext.Apps.Add(masterApp); diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Client/PalantirClient.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Client/PalantirClient.cs index 6e16e67..dec10f9 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Client/PalantirClient.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Client/PalantirClient.cs @@ -1,8 +1,9 @@ using System.Net.Http.Json; using LanguageExt.Common; +using Suspectus.Gandalf.Core.Abstractions.CQRS.Commands; using Suspectus.Gandalf.Core.Abstractions.DTOs; +using Suspectus.Gandalf.Core.Abstractions.DTOs.Internal.Auth; using Suspectus.Gandalf.Core.Abstractions.Extensions; -using Suspectus.Gandalf.Palantir.Contracts.Controller.Auth; namespace Suspectus.Gandalf.Palantir.Client; @@ -36,11 +37,13 @@ public class PalantirInternalClient : IPalantirInternalClient public interface IPalantirInternalAuthClient { public Task> ValidateCredentials(ValidateCredentialsCommand validateCredentialsCommand); + public Task> Token(TokenRequestCommand command); } public interface IPalantirInternalAppClient { public Task> GetInfo(string appId); + public Task> GetMasterInfo(); } public class PalantirClient : IPalantirClient @@ -83,6 +86,26 @@ public class PalantirInternalAuthClient : IPalantirInternalAuthClient return $"status: {e.StatusCode} message: {e.Message}".AsErrorResult(); } } + + public async Task> Token(TokenRequestCommand command) + { + try + { + var response = await _http.PostAsJsonAsync("token", command); + + 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(); + } + } } public class PalantirInternalAppClient : IPalantirInternalAppClient @@ -119,4 +142,30 @@ public class PalantirInternalAppClient : IPalantirInternalAppClient return $"status: {e.StatusCode} message: {e.Message}".AsErrorResult(); } } + + public async Task> GetMasterInfo() + { + try + { + var response = await _http.GetAsync($"master/info"); + + if (!response.IsSuccessStatusCode) + { + return $"status: {response.StatusCode}".AsErrorResult(); + } + + var result = await response.Content.ReadFromJsonAsync(); + + if (result is null) + { + return "InternalApp not found.".AsErrorResult(); + } + + 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.Contracts/Controller/Auth/ValidateCredentialsCommand.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Controller/Auth/ValidateCredentialsCommand.cs deleted file mode 100644 index 94c735d..0000000 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Controller/Auth/ValidateCredentialsCommand.cs +++ /dev/null @@ -1,5 +0,0 @@ -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.Data/Entities/App/AppEntity.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/App/AppEntity.cs index ccfcac6..60b8ac7 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/App/AppEntity.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/App/AppEntity.cs @@ -23,4 +23,5 @@ public class AppVersionEntity : AppData, IVersionEntity public abstract class AppData : TenantRelationData { public required string Name { get; set; } + public required string BaseAddress { get; set; } } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Security/TokenMetaDataEntity.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Security/TokenMetaDataEntity.cs index 1d86e0b..b436b62 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Security/TokenMetaDataEntity.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Security/TokenMetaDataEntity.cs @@ -8,4 +8,6 @@ public class TokenMetadataEntity : IdData public required bool IsRevoked { get; set; } public required TokenType TokenType { get; set; } public required long UsedBy { get; set; } + public required long UsedFor { get; set; } + public required string Scope { get; set; } } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Security/TokenType.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Security/TokenType.cs index b482920..2667d2c 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Security/TokenType.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Entities/Security/TokenType.cs @@ -2,6 +2,6 @@ namespace Suspectus.Gandalf.Palantir.Data.Entities.Security; public enum TokenType { - Application, - User + Access, + Refresh } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530220220_updateTokenMetadata.Designer.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530220220_updateTokenMetadata.Designer.cs new file mode 100644 index 0000000..4cb2d8d --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530220220_updateTokenMetadata.Designer.cs @@ -0,0 +1,731 @@ +// +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("20250530220220_updateTokenMetadata")] + partial class updateTokenMetadata + { + /// + 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("Scope") + .IsRequired() + .HasColumnType("text"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UsedBy") + .HasColumnType("bigint"); + + b.Property("UsedFor") + .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/20250530220220_updateTokenMetadata.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530220220_updateTokenMetadata.cs new file mode 100644 index 0000000..a86438b --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530220220_updateTokenMetadata.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace W542.GandalfReborn.Data.Migrations +{ + /// + public partial class updateTokenMetadata : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Scope", + schema: "gr", + table: "TokenMetadata", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "UsedFor", + schema: "gr", + table: "TokenMetadata", + type: "bigint", + nullable: false, + defaultValue: 0L); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Scope", + schema: "gr", + table: "TokenMetadata"); + + migrationBuilder.DropColumn( + name: "UsedFor", + schema: "gr", + table: "TokenMetadata"); + } + } +} diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530223053_addBaseAddress.Designer.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530223053_addBaseAddress.Designer.cs new file mode 100644 index 0000000..b9bfe66 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530223053_addBaseAddress.Designer.cs @@ -0,0 +1,739 @@ +// +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("20250530223053_addBaseAddress")] + partial class addBaseAddress + { + /// + 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("BaseAddress") + .IsRequired() + .HasColumnType("text"); + + 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("BaseAddress") + .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("Scope") + .IsRequired() + .HasColumnType("text"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UsedBy") + .HasColumnType("bigint"); + + b.Property("UsedFor") + .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/20250530223053_addBaseAddress.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530223053_addBaseAddress.cs new file mode 100644 index 0000000..0581e03 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530223053_addBaseAddress.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace W542.GandalfReborn.Data.Migrations +{ + /// + public partial class addBaseAddress : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BaseAddress", + schema: "gr", + table: "AppVersion", + type: "text", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "BaseAddress", + schema: "gr", + table: "App", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BaseAddress", + schema: "gr", + table: "AppVersion"); + + migrationBuilder.DropColumn( + name: "BaseAddress", + schema: "gr", + table: "App"); + } + } +} diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530235831_tokenType.Designer.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530235831_tokenType.Designer.cs new file mode 100644 index 0000000..b2f7af1 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530235831_tokenType.Designer.cs @@ -0,0 +1,739 @@ +// +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("20250530235831_tokenType")] + partial class tokenType + { + /// + 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("BaseAddress") + .IsRequired() + .HasColumnType("text"); + + 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("BaseAddress") + .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("Scope") + .IsRequired() + .HasColumnType("text"); + + b.Property("TokenType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UsedBy") + .HasColumnType("bigint"); + + b.Property("UsedFor") + .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/20250530235831_tokenType.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530235831_tokenType.cs new file mode 100644 index 0000000..ee94fe0 --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530235831_tokenType.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace W542.GandalfReborn.Data.Migrations +{ + /// + public partial class tokenType : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + UPDATE gr."TokenMetadata" + SET "TokenType" = 'Access' + WHERE "TokenType" = 'User'; + + UPDATE gr."TokenMetadata" + SET "TokenType" = 'Refresh' + WHERE "TokenType" = 'Application'; + """ + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + UPDATE gr.TokenMetadata + SET TokenType = 'User' + WHERE TokenType = 'Access'; + + UPDATE gr.TokenMetadata + SET TokenType = 'Application' + WHERE TokenType = 'Refresh'; + """ + ); + } + } +} diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/ApplicationContextModelSnapshot.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/ApplicationContextModelSnapshot.cs index cff0e06..f9d7bd1 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/ApplicationContextModelSnapshot.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/ApplicationContextModelSnapshot.cs @@ -30,6 +30,10 @@ namespace W542.GandalfReborn.Data.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("BaseAddress") + .IsRequired() + .HasColumnType("text"); + b.Property("Name") .IsRequired() .HasColumnType("text"); @@ -100,6 +104,10 @@ namespace W542.GandalfReborn.Data.Migrations .IsRequired() .HasColumnType("text"); + b.Property("BaseAddress") + .IsRequired() + .HasColumnType("text"); + b.Property("Name") .IsRequired() .HasColumnType("text"); @@ -304,6 +312,10 @@ namespace W542.GandalfReborn.Data.Migrations b.Property("IsRevoked") .HasColumnType("boolean"); + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + b.Property("TokenType") .IsRequired() .HasColumnType("text"); @@ -311,6 +323,9 @@ namespace W542.GandalfReborn.Data.Migrations b.Property("UsedBy") .HasColumnType("bigint"); + b.Property("UsedFor") + .HasColumnType("bigint"); + b.HasKey("Id"); b.ToTable("TokenMetadata", "gr"); diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Security/Scheme/GandalfRebornJwtTokenAuthSchemeHandler.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Security/Scheme/GandalfRebornJwtTokenAuthSchemeHandler.cs index e17cd45..2471281 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Security/Scheme/GandalfRebornJwtTokenAuthSchemeHandler.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Security/Scheme/GandalfRebornJwtTokenAuthSchemeHandler.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Suspectus.Gandalf.Palantir.Abstractions; using Suspectus.Gandalf.Palantir.Data.Database; +using Suspectus.Gandalf.Palantir.Data.Entities.Security; namespace Suspectus.Gandalf.Palantir.Security.Scheme; @@ -50,7 +51,7 @@ public partial class GandalfRebornJwtTokenAuthSchemeHandler( { try { - var jwtBody = GetJwtTokenBody(httpContextAccessor.HttpContext); + var jwtBody = await GetJwtTokenBody(httpContextAccessor.HttpContext); if (!hashIds.TryDecodeSingleLong(jwtBody.Sub, out var subjectId)) { @@ -121,7 +122,7 @@ public partial class GandalfRebornJwtTokenAuthSchemeHandler( } } - private GandalfRebornJwtBody GetJwtTokenBody(HttpContext? httpContext) + private async Task GetJwtTokenBody(HttpContext? httpContext) { var authHeader = httpContext?.Request.Headers.Authorization.ToString(); @@ -141,8 +142,15 @@ public partial class GandalfRebornJwtTokenAuthSchemeHandler( .MustVerifySignature() .WithJsonSerializer(new JsonNetSerializer()) .Decode(token); + + var tokenType = await context.TokenMetadata.Where(x => x.Id == hashIds.DecodeSingleLong(decodedToken.Id)).Select(x => x.TokenType).SingleOrDefaultAsync(); + + if (tokenType != TokenType.Access) + throw new UnauthorizedAccessException("One does not simply provide a refresh token as access token."); - if (decodedToken.Aud is null || !decodedToken.Aud.StartsWith(Options.BaseUrl)) + var audience = await context.Apps.Where(x => x.Tenant!.IsMaster && x.Name == "Master").Select(x => $"{x.Name.ToLower()}-api").SingleAsync(); + + if (decodedToken.Aud is null || decodedToken.Aud != audience) throw new UnauthorizedAccessException("One does not simply provide a token for a different audience."); if (decodedToken.Iss is null || !decodedToken.Iss.StartsWith(Options.BaseUrl))