From 4ffc2a135c22e0dcb54c76ac373718021abedaeb Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 31 May 2025 02:16:02 +0200 Subject: [PATCH] Refactor token handling and add BaseAddress to apps Replaced LoginCommand and obsolete GetTokensCommand with updated implementations for consistency. Token type enum was updated, enforcing proper access and refresh token validation. Added BaseAddress property to master app, ensuring correct audience and initialization logic improvements. --- .../CQRS/Commands/LoginCommand.cs | 7 - .../Commands/ValidateCredentialsCommand.cs | 13 + .../DTOs/Internal/Auth/TokenRequest.cs | 53 ++ .../Commands/AuthCodeRequestCommand.cs | 4 +- .../Commands/CreateTokensCommand.cs | 6 +- .../{ => Internal}/AppController.cs | 23 +- .../{ => Internal}/AuthController.cs | 12 +- .../ValidateCredentialsCommandHandler.cs | 21 +- .../Security/CreateTokensCommandHandler.cs | 56 +- .../Security/GetTokensCommandHandler.cs | 92 ++- .../Services/InitService.cs | 27 +- .../PalantirClient.cs | 51 +- .../Auth/ValidateCredentialsCommand.cs | 5 - .../Entities/App/AppEntity.cs | 1 + .../Entities/Security/TokenMetaDataEntity.cs | 2 + .../Entities/Security/TokenType.cs | 4 +- ...0530220220_updateTokenMetadata.Designer.cs | 731 +++++++++++++++++ .../20250530220220_updateTokenMetadata.cs | 44 ++ .../20250530223053_addBaseAddress.Designer.cs | 739 ++++++++++++++++++ .../20250530223053_addBaseAddress.cs | 44 ++ .../20250530235831_tokenType.Designer.cs | 739 ++++++++++++++++++ .../Migrations/20250530235831_tokenType.cs | 42 + .../ApplicationContextModelSnapshot.cs | 15 + .../GandalfRebornJwtTokenAuthSchemeHandler.cs | 14 +- 24 files changed, 2686 insertions(+), 59 deletions(-) delete mode 100644 src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/LoginCommand.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Core.Abstractions/CQRS/Commands/ValidateCredentialsCommand.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Core.Abstractions/DTOs/Internal/Auth/TokenRequest.cs rename src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/{ => Internal}/AppController.cs (69%) rename src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/{ => Internal}/AuthController.cs (72%) delete mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Contracts/Controller/Auth/ValidateCredentialsCommand.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530220220_updateTokenMetadata.Designer.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530220220_updateTokenMetadata.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530223053_addBaseAddress.Designer.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530223053_addBaseAddress.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530235831_tokenType.Designer.cs create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Migrations/20250530235831_tokenType.cs 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))