From d360527c59556f0374c9a8af8a354e7a8d037377 Mon Sep 17 00:00:00 2001 From: Simon Wacker Date: Wed, 20 May 2026 17:09:54 +0200 Subject: [PATCH] Use `is 0` instead of `== 0`, use static were no instance data is used, add string extensions to encode and decode base64 (yes, I know these should be separate commits ...) --- .../OpenIdConnectAuthorization.cs | 4 ++-- .../src/Configuration/AuthConfiguration.cs | 6 +++-- backend/src/Data/ApplicationDbContext.cs | 5 ++++- backend/src/Extensions/StringExtensions.cs | 22 +++++++++++++++++++ .../GraphQl/Associations/AssociationType.cs | 1 + backend/src/GraphQl/DataX/Data.cs | 5 +---- .../Entities/AuditableEntityFilterType.cs | 1 + backend/src/GraphQl/Entities/EntityType.cs | 1 + .../Extensions/ResolverContextExtensions.cs | 1 - .../GraphQl/Filters/ScalarFilterInputTypes.cs | 1 + backend/src/GraphQl/Methods/MethodQueries.cs | 1 - .../OpenIdConnectApplicationType.cs | 3 ++- backend/src/GraphQl/PaginationHelpers.cs | 14 +++--------- backend/src/GraphQl/Requests/DataQueries.cs | 11 ++-------- backend/src/GraphQl/Scalars/MyUriType.cs | 4 ++-- .../src/GraphQl/Scalars/NonNegativeIntType.cs | 3 ++- backend/src/GraphQl/Users/UserMutations.cs | 2 +- backend/src/GraphQl/Users/UserType.cs | 7 +++--- frontend/tsconfig.json | 18 +++++++++++---- 19 files changed, 66 insertions(+), 44 deletions(-) diff --git a/backend/src/Authorization/OpenIdConnectAuthorization.cs b/backend/src/Authorization/OpenIdConnectAuthorization.cs index c9509e330..806d56c3d 100644 --- a/backend/src/Authorization/OpenIdConnectAuthorization.cs +++ b/backend/src/Authorization/OpenIdConnectAuthorization.cs @@ -144,7 +144,7 @@ CancellationToken cancellationToken : [GraphQl.OpenIdConnect.Applications.OpenIdConnectConsentType.EXPLICIT]; } - internal async Task> AuthorizedEndpoints( + internal static async Task> AuthorizedEndpoints( ClaimsPrincipal claimsPrincipal, CancellationToken cancellationToken ) @@ -167,7 +167,7 @@ CancellationToken cancellationToken .AsReadOnly(); } - internal async Task> AuthorizedResponseTypes( + internal static async Task> AuthorizedResponseTypes( ClaimsPrincipal claimsPrincipal, CancellationToken cancellationToken ) diff --git a/backend/src/Configuration/AuthConfiguration.cs b/backend/src/Configuration/AuthConfiguration.cs index 2c1290a99..612bd408f 100644 --- a/backend/src/Configuration/AuthConfiguration.cs +++ b/backend/src/Configuration/AuthConfiguration.cs @@ -53,7 +53,7 @@ private static void BootstrapCertificates(IClock clock) distinguishedName, validOnly: true ); - if (certificates.Count == 0) + if (certificates.Count is 0) { store.Add( JwtSigningAndEncryptionCertificateRotationJob.CreateSigningCertificate( @@ -73,7 +73,7 @@ private static void BootstrapCertificates(IClock clock) distinguishedName, validOnly: true ); - if (certificates.Count == 0) + if (certificates.Count is 0) { store.Add( JwtSigningAndEncryptionCertificateRotationJob.CreateEncryptionCertificate( @@ -483,6 +483,8 @@ AppSettings appSettings // Register the OpenIddict validation components. .AddValidation(_ => { + // The validation handler uses OpenID Connect discovery to + // retrieve the issuer signing keys used to validate tokens. _.SetIssuer(appSettings.Uri); // Configure the audience accepted by this resource server. _.AddAudiences(OpenIdConnectConstants.Client.MetabaseClientId); diff --git a/backend/src/Data/ApplicationDbContext.cs b/backend/src/Data/ApplicationDbContext.cs index ab3866238..8d1211e61 100644 --- a/backend/src/Data/ApplicationDbContext.cs +++ b/backend/src/Data/ApplicationDbContext.cs @@ -147,7 +147,10 @@ private void UpdateTimestamps() switch (entry.State) { case EntityState.Added: - entry.Entity.CreatedAt = now; + if (entry.Entity.CreatedAt == default) + { + entry.Entity.CreatedAt = now; + } entry.Entity.UpdatedAt = now; break; case EntityState.Modified: diff --git a/backend/src/Extensions/StringExtensions.cs b/backend/src/Extensions/StringExtensions.cs index 51cd17f58..c915ff80b 100644 --- a/backend/src/Extensions/StringExtensions.cs +++ b/backend/src/Extensions/StringExtensions.cs @@ -1,3 +1,6 @@ +using System; +using System.Text; + namespace Metabase.Extensions; public static class StringExtensions @@ -16,4 +19,23 @@ public static string FirstCharToLower(this string value) public static string? NullIfWhitespace(this string value) => string.IsNullOrWhiteSpace(value) ? null : value; + + public static string Base64Encode(this string plainText) + { + return Convert.ToBase64String( + Encoding.UTF8.GetBytes(plainText) + ); + } + + public static string Base64Decode(this string base64EncodedData) + { + return Encoding.UTF8.GetString( + Convert.FromBase64String(base64EncodedData) + ); + } + + public static string Enquote(this string str) + { + return "\"" + str + "\""; + } } \ No newline at end of file diff --git a/backend/src/GraphQl/Associations/AssociationType.cs b/backend/src/GraphQl/Associations/AssociationType.cs index 206336393..dc28a194e 100644 --- a/backend/src/GraphQl/Associations/AssociationType.cs +++ b/backend/src/GraphQl/Associations/AssociationType.cs @@ -1,5 +1,6 @@ using HotChocolate.Types; using Metabase.Data; +using Metabase.GraphQl.Scalars; namespace Metabase.GraphQl.Associations; diff --git a/backend/src/GraphQl/DataX/Data.cs b/backend/src/GraphQl/DataX/Data.cs index 8e8beb5f6..fe0b9f8c1 100644 --- a/backend/src/GraphQl/DataX/Data.cs +++ b/backend/src/GraphQl/DataX/Data.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -70,9 +69,7 @@ CancellationToken cancellationToken } [ID] - public string Id => Convert.ToBase64String( - Encoding.UTF8.GetBytes($"{DatabaseId}:{Uuid}:{Locale ?? ""}:{DataId}") - ); + public string Id => $"{DatabaseId}:{Uuid}:{Locale ?? ""}:{DataId}".Base64Encode(); public abstract DataKind Kind { get; } diff --git a/backend/src/GraphQl/Entities/AuditableEntityFilterType.cs b/backend/src/GraphQl/Entities/AuditableEntityFilterType.cs index 093748032..e6fbf4d46 100644 --- a/backend/src/GraphQl/Entities/AuditableEntityFilterType.cs +++ b/backend/src/GraphQl/Entities/AuditableEntityFilterType.cs @@ -15,5 +15,6 @@ IFilterInputTypeDescriptor descriptor descriptor.Field(x => x.Id); descriptor.Field(x => x.CreatedAt); descriptor.Field(x => x.UpdatedAt); + // TODO Do we want to filter by: descriptor.Field(x => x.Version); } } \ No newline at end of file diff --git a/backend/src/GraphQl/Entities/EntityType.cs b/backend/src/GraphQl/Entities/EntityType.cs index d87af8132..0afd3506c 100644 --- a/backend/src/GraphQl/Entities/EntityType.cs +++ b/backend/src/GraphQl/Entities/EntityType.cs @@ -2,6 +2,7 @@ using GreenDonut; using HotChocolate.Types; using Metabase.Data; +using Metabase.GraphQl.Scalars; namespace Metabase.GraphQl.Entities; diff --git a/backend/src/GraphQl/Extensions/ResolverContextExtensions.cs b/backend/src/GraphQl/Extensions/ResolverContextExtensions.cs index 49b2a5ba6..a373f2ae4 100644 --- a/backend/src/GraphQl/Extensions/ResolverContextExtensions.cs +++ b/backend/src/GraphQl/Extensions/ResolverContextExtensions.cs @@ -1,5 +1,4 @@ using GreenDonut.Data; -using HotChocolate; using HotChocolate.Data.Filters; using HotChocolate.Data.Sorting; using HotChocolate.Resolvers; diff --git a/backend/src/GraphQl/Filters/ScalarFilterInputTypes.cs b/backend/src/GraphQl/Filters/ScalarFilterInputTypes.cs index 587d362fc..13d45c12d 100644 --- a/backend/src/GraphQl/Filters/ScalarFilterInputTypes.cs +++ b/backend/src/GraphQl/Filters/ScalarFilterInputTypes.cs @@ -1,5 +1,6 @@ using HotChocolate.Data.Filters; using HotChocolate.Types; +using Metabase.GraphQl.Scalars; using DateTimeType = HotChocolate.Types.NodaTime.DateTimeType; using DurationType = HotChocolate.Types.NodaTime.DurationType; using LocalDateTimeType = HotChocolate.Types.NodaTime.LocalDateTimeType; diff --git a/backend/src/GraphQl/Methods/MethodQueries.cs b/backend/src/GraphQl/Methods/MethodQueries.cs index 0f22c3e4e..72a02d2e6 100644 --- a/backend/src/GraphQl/Methods/MethodQueries.cs +++ b/backend/src/GraphQl/Methods/MethodQueries.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using GreenDonut.Data; using HotChocolate.Data; -using HotChocolate.Data.Sorting; using HotChocolate.Resolvers; using HotChocolate.Types; using Metabase.Data; diff --git a/backend/src/GraphQl/OpenIdConnect/Applications/OpenIdConnectApplicationType.cs b/backend/src/GraphQl/OpenIdConnect/Applications/OpenIdConnectApplicationType.cs index d082427d3..5a1ef8cc0 100644 --- a/backend/src/GraphQl/OpenIdConnect/Applications/OpenIdConnectApplicationType.cs +++ b/backend/src/GraphQl/OpenIdConnect/Applications/OpenIdConnectApplicationType.cs @@ -10,6 +10,7 @@ using Metabase.Data.OpenIdConnect; using Metabase.GraphQl.Users; using Metabase.GraphQl.Entities; +using Metabase.GraphQl.Scalars; namespace Metabase.GraphQl.OpenIdConnect.Applications; @@ -228,7 +229,7 @@ IObjectTypeDescriptor descriptor } var uris = JsonSerializer.Deserialize>(urisJson) ?? throw new GraphQLException($"Could not deserialize `{urisJson}` into a list of strings."); - if (uris.Count == 0) + if (uris.Count is 0) { return null; } diff --git a/backend/src/GraphQl/PaginationHelpers.cs b/backend/src/GraphQl/PaginationHelpers.cs index 0d02b7dc5..708bfbb1e 100644 --- a/backend/src/GraphQl/PaginationHelpers.cs +++ b/backend/src/GraphQl/PaginationHelpers.cs @@ -1,5 +1,5 @@ using System; -using System.Text; +using Metabase.Extensions; namespace Metabase.GraphQl; @@ -7,19 +7,11 @@ public static class PaginationHelpers { public static string ConstructCursor(Guid id) { - return Convert.ToBase64String( - Encoding.UTF8.GetBytes( - id.ToString("D") - ) - ); + return id.ToString("D").Base64Encode(); } public static string ConstructCursor(Guid id1, Guid id2) { - return Convert.ToBase64String( - Encoding.UTF8.GetBytes( - $"{id1:D}:{id2:D}" - ) - ); + return $"{id1:D}:{id2:D}".Base64Encode(); } } \ No newline at end of file diff --git a/backend/src/GraphQl/Requests/DataQueries.cs b/backend/src/GraphQl/Requests/DataQueries.cs index bdc0990c6..91115b7cb 100644 --- a/backend/src/GraphQl/Requests/DataQueries.cs +++ b/backend/src/GraphQl/Requests/DataQueries.cs @@ -17,7 +17,6 @@ using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; -using System.Text; using System.Collections.Immutable; using System.Linq; using HotChocolate.Types.Pagination; @@ -221,11 +220,7 @@ private sealed record CompoundCursor private static string SerializeCompoundCursor(CompoundCursor cursor) { - return Convert.ToBase64String( - Encoding.UTF8.GetBytes( - JsonSerializer.Serialize(cursor, JsonSerializerSettings.Compact) - ) - ); + return JsonSerializer.Serialize(cursor, JsonSerializerSettings.Compact).Base64Encode(); } private static CompoundCursor? DeserializeCompoundCursor(string? cursors) @@ -235,9 +230,7 @@ private static string SerializeCompoundCursor(CompoundCursor cursor) return null; } return JsonSerializer.Deserialize( - Encoding.UTF8.GetString( - Convert.FromBase64String(cursors) - ), + cursors.Base64Decode(), JsonSerializerSettings.Compact ); } diff --git a/backend/src/GraphQl/Scalars/MyUriType.cs b/backend/src/GraphQl/Scalars/MyUriType.cs index c2ae572d0..0633b6e86 100644 --- a/backend/src/GraphQl/Scalars/MyUriType.cs +++ b/backend/src/GraphQl/Scalars/MyUriType.cs @@ -3,10 +3,10 @@ using HotChocolate.Features; using HotChocolate.Language; using HotChocolate.Text.Json; -using Metabase.GraphQl; +using HotChocolate.Types; using Microsoft.Extensions.DependencyInjection; -namespace HotChocolate.Types; +namespace Metabase.GraphQl.Scalars; // Inspired by https://github.com/ChilliCream/graphql-platform/blob/main/src/HotChocolate/Core/src/Types/Types/Scalars/UriType.cs /// diff --git a/backend/src/GraphQl/Scalars/NonNegativeIntType.cs b/backend/src/GraphQl/Scalars/NonNegativeIntType.cs index 972a8d191..9bc42972c 100644 --- a/backend/src/GraphQl/Scalars/NonNegativeIntType.cs +++ b/backend/src/GraphQl/Scalars/NonNegativeIntType.cs @@ -1,9 +1,10 @@ using System.Text.Json; using HotChocolate.Language; using HotChocolate.Text.Json; +using HotChocolate.Types; using Microsoft.Extensions.DependencyInjection; -namespace HotChocolate.Types; +namespace Metabase.GraphQl.Scalars; /// /// diff --git a/backend/src/GraphQl/Users/UserMutations.cs b/backend/src/GraphQl/Users/UserMutations.cs index 0c0d86ad6..fbe9ebee7 100644 --- a/backend/src/GraphQl/Users/UserMutations.cs +++ b/backend/src/GraphQl/Users/UserMutations.cs @@ -1167,7 +1167,7 @@ await userManager.VerifyTwoFactorTokenAsync( }; } - if (await userManager.CountRecoveryCodesAsync(user) == 0) + if (await userManager.CountRecoveryCodesAsync(user) is 0) { var recoveryCodes = await userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); diff --git a/backend/src/GraphQl/Users/UserType.cs b/backend/src/GraphQl/Users/UserType.cs index 10e5a042d..b30fce68b 100644 --- a/backend/src/GraphQl/Users/UserType.cs +++ b/backend/src/GraphQl/Users/UserType.cs @@ -214,7 +214,7 @@ await Authorize(context, user => user.WebsiteLocator, Scopes.Profile) .Field("authorizedOpenIdConnectResponseTypes") .Cost(1) .ResolveWith(x => - UserResolvers.GetAuthorizedOpenIdConnectResponseTypesAsync(default!, default!, default!)) + UserResolvers.GetAuthorizedOpenIdConnectResponseTypesAsync(default!, default!)) .UseUserManager(); descriptor .Field("authorizedOpenIdConnectScopes") @@ -418,7 +418,7 @@ public static Task> GetAuthorizedOpenIdConn CancellationToken cancellationToken ) { - return authorization.AuthorizedEndpoints(claimsPrincipal, cancellationToken); + return Authorization.OpenIdConnectAuthorization.AuthorizedEndpoints(claimsPrincipal, cancellationToken); } public static Task> GetAuthorizedOpenIdConnectGrantTypesAsync( @@ -432,11 +432,10 @@ CancellationToken cancellationToken public static Task> GetAuthorizedOpenIdConnectResponseTypesAsync( ClaimsPrincipal claimsPrincipal, - Authorization.OpenIdConnectAuthorization authorization, CancellationToken cancellationToken ) { - return authorization.AuthorizedResponseTypes(claimsPrincipal, cancellationToken); + return Authorization.OpenIdConnectAuthorization.AuthorizedResponseTypes(claimsPrincipal, cancellationToken); } public static Task> GetAuthorizedOpenIdConnectScopesAsync( diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 79f39b3dc..26572294a 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -6,9 +6,12 @@ "forceConsistentCasingInFileNames": true, "isolatedModules": true, "jsx": "react-jsx", - "lib": ["dom", "es2017"], + "lib": [ + "dom", + "es2017" + ], "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "noEmit": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, @@ -20,6 +23,13 @@ "incremental": true // "verbatimModuleSyntax": true }, - "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + ".next/types/**/*.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] }