Reorganize GraphQL types and make OAuthProviders stronger

This commit is contained in:
Jordan Dominion
2024-10-06 13:24:59 -04:00
parent 8f734af210
commit 86a6971581
16 changed files with 198 additions and 19 deletions
@@ -6,6 +6,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Authority.Core;
using Tgstation.Server.Host.GraphQL.Types.OAuth;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
@@ -40,8 +41,8 @@ namespace Tgstation.Server.Host.Authority
/// </summary>
/// <param name="userId">The <see cref="EntityId.Id"/> of the <see cref="User"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an <see cref="global::System.Array"/> of <see cref="GraphQL.Types.OAuthConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
ValueTask<AuthorityResponse<GraphQL.Types.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken);
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an <see cref="global::System.Array"/> of <see cref="Tgstation.Server.Host.GraphQL.Types.OAuth.OAuthConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken);
/// <summary>
/// Gets all registered <see cref="User"/>s.
@@ -102,7 +102,7 @@ namespace Tgstation.Server.Host.Authority
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a <see cref="Dictionary{TKey, TValue}"/> of the requested <see cref="User"/>s.</returns>
[DataLoader]
public static async ValueTask<ILookup<long, GraphQL.Types.OAuthConnection>> GetOAuthConnections(
public static async ValueTask<ILookup<long, GraphQL.Types.OAuth.OAuthConnection>> GetOAuthConnections(
IReadOnlyList<long> userIds,
IDatabaseContext databaseContext,
CancellationToken cancellationToken)
@@ -118,7 +118,7 @@ namespace Tgstation.Server.Host.Authority
return list.ToLookup(
oauthConnection => oauthConnection.UserId,
x => new GraphQL.Types.OAuthConnection(x.ExternalUserId!, x.Provider));
x => new GraphQL.Types.OAuth.OAuthConnection(x.ExternalUserId!, x.Provider));
}
/// <summary>
@@ -285,8 +285,8 @@ namespace Tgstation.Server.Host.Authority
=> Queryable(includeJoins, false);
/// <inheritdoc />
public async ValueTask<AuthorityResponse<GraphQL.Types.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken)
=> new AuthorityResponse<GraphQL.Types.OAuthConnection[]>(
public async ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken)
=> new AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>(
await oAuthConnectionsDataLoader.LoadRequiredAsync(userId, cancellationToken));
/// <inheritdoc />
@@ -57,10 +57,10 @@ using Tgstation.Server.Host.Controllers.Results;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.GraphQL;
using Tgstation.Server.Host.GraphQL.Interceptors;
using Tgstation.Server.Host.GraphQL.Scalars;
using Tgstation.Server.Host.GraphQL.Subscriptions;
using Tgstation.Server.Host.GraphQL.Types;
using Tgstation.Server.Host.GraphQL.Types.Interceptors;
using Tgstation.Server.Host.GraphQL.Types.Scalars;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Properties;
@@ -6,10 +6,10 @@ using System.Reflection;
using HotChocolate.Configuration;
using HotChocolate.Types;
using HotChocolate.Types.Descriptors.Definitions;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.GraphQL.Types;
namespace Tgstation.Server.Host.GraphQL.Types.Interceptors
namespace Tgstation.Server.Host.GraphQL.Interceptors
{
/// <summary>
/// Fixes the names used for the default flags types in API rights.
@@ -1,6 +1,6 @@
using HotChocolate;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.GraphQL.Types.Scalars;
using Tgstation.Server.Host.GraphQL.Scalars;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.GraphQL.Mutations.Payloads
@@ -13,6 +13,7 @@ using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
using Tgstation.Server.Host.GraphQL.Types;
using Tgstation.Server.Host.GraphQL.Types.OAuth;
using Tgstation.Server.Host.Models.Transformers;
using Tgstation.Server.Host.Security;
@@ -3,7 +3,7 @@
using HotChocolate.Language;
using HotChocolate.Types;
namespace Tgstation.Server.Host.GraphQL.Types.Scalars
namespace Tgstation.Server.Host.GraphQL.Scalars
{
/// <summary>
/// A <see cref="ScalarType{TRuntimeType, TLiteral}"/> for encoded JSON Web Tokens.
@@ -2,10 +2,9 @@
using HotChocolate.Language;
using HotChocolate.Types;
using Tgstation.Server.Common.Extensions;
namespace Tgstation.Server.Host.GraphQL.Types.Scalars
namespace Tgstation.Server.Host.GraphQL.Scalars
{
/// <summary>
/// A <see cref="ScalarType{TRuntimeType, TLiteral}"/> for semantic <see cref="Version"/>s.
@@ -10,6 +10,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.GraphQL.Types.OAuth;
using Tgstation.Server.Host.Properties;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Security.OAuth;
@@ -148,12 +149,12 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// Gets the information needed to perform open authentication with the <see cref="SwarmNode"/>.
/// </summary>
/// <param name="oAuthProviders">The <see cref="IOAuthProviders"/> to use.</param>
/// <returns>A map of enabled <see cref="OAuthProvider"/>s to their <see cref="OAuthProviderInfo"/>.</returns>
public IReadOnlyDictionary<OAuthProvider, OAuthProviderInfo> OAuthProviderInfos(
/// <returns>A map of enabled <see cref="OAuthProvider"/>s to their <see cref="FullOAuthProviderInfo"/>.</returns>
public OAuthProviderInfos OAuthProviderInfos(
[Service] IOAuthProviders oAuthProviders)
{
ArgumentNullException.ThrowIfNull(oAuthProviders);
return oAuthProviders.ProviderInfos();
return new OAuthProviderInfos(oAuthProviders);
}
}
}
@@ -0,0 +1,28 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// Basic OAuth provider info.
/// </summary>
public class BasicOAuthProviderInfo
{
/// <summary>
/// The client ID.
/// </summary>
public string ClientID { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BasicOAuthProviderInfo"/> class.
/// </summary>
/// <param name="providerInfo">The <see cref="OAuthProviderInfo"/> to build from.</param>
public BasicOAuthProviderInfo(OAuthProviderInfo providerInfo)
{
ArgumentNullException.ThrowIfNull(providerInfo);
ClientID = providerInfo.ClientId ?? throw new InvalidOperationException("ClientID not set!");
}
}
}
@@ -0,0 +1,29 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// OAuth provider info with a <see cref="RedirectOAuthProviderInfo.RedirectUri"/> and <see cref="ServerUrl"/>.
/// </summary>
public sealed class FullOAuthProviderInfo : RedirectOAuthProviderInfo
{
/// <summary>
/// The remote service URL.
/// </summary>
public Uri ServerUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="FullOAuthProviderInfo"/> class.
/// </summary>
/// <param name="providerInfo">The <see cref="OAuthProviderInfo"/> to build from.</param>
public FullOAuthProviderInfo(OAuthProviderInfo providerInfo)
: base(providerInfo)
{
ArgumentNullException.ThrowIfNull(providerInfo);
ServerUrl = providerInfo.ServerUrl ?? throw new InvalidOperationException("Missing OAuthProviderInfo ServerUrl!");
}
}
}
@@ -1,8 +1,7 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// Represents a valid OAuth connection.
@@ -0,0 +1,66 @@
using System;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Security.OAuth;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// Description of configured OAuth services.
/// </summary>
public sealed class OAuthProviderInfos
{
/// <summary>
/// https://discord.com.
/// </summary>
public BasicOAuthProviderInfo? Discord { get; }
/// <summary>
/// https://github.com.
/// </summary>
public RedirectOAuthProviderInfo? GitHub { get; }
/// <summary>
/// https://tgstation13.org.
/// </summary>
public RedirectOAuthProviderInfo? TGForums { get; }
/// <summary>
/// https://www.keycloak.org.
/// </summary>
public FullOAuthProviderInfo? Keycloak { get; }
/// <summary>
/// https://invisioncommunity.com.
/// </summary>
public FullOAuthProviderInfo? InvisionCommunity { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OAuthProviderInfos"/> class.
/// </summary>
/// <param name="oAuthProviders">The <see cref="IOAuthProviders"/> to get data from.</param>
public OAuthProviderInfos(IOAuthProviders oAuthProviders)
{
ArgumentNullException.ThrowIfNull(oAuthProviders);
var dic = oAuthProviders.ProviderInfos();
TProviderInfo? TryBuild<TProviderInfo>(OAuthProvider oAuthProvider, Func<OAuthProviderInfo, TProviderInfo> contructor)
where TProviderInfo : BasicOAuthProviderInfo
{
if (dic.TryGetValue(oAuthProvider, out var providerInfo))
{
return contructor(providerInfo);
}
return null;
}
Discord = TryBuild(OAuthProvider.Discord, info => new BasicOAuthProviderInfo(info));
GitHub = TryBuild(OAuthProvider.GitHub, info => new RedirectOAuthProviderInfo(info));
TGForums = TryBuild(OAuthProvider.TGForums, info => new RedirectOAuthProviderInfo(info));
Keycloak = TryBuild(OAuthProvider.Keycloak, info => new FullOAuthProviderInfo(info));
InvisionCommunity = TryBuild(OAuthProvider.InvisionCommunity, info => new FullOAuthProviderInfo(info));
}
}
}
@@ -0,0 +1,27 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// OAuth provider info with a <see cref="RedirectUri"/>.
/// </summary>
public class RedirectOAuthProviderInfo : BasicOAuthProviderInfo
{
/// <summary>
/// The authentication server URL.
/// </summary>
public Uri RedirectUri { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RedirectOAuthProviderInfo"/> class.
/// </summary>
/// <param name="providerInfo">The <see cref="OAuthProviderInfo"/> to build from.</param>
public RedirectOAuthProviderInfo(OAuthProviderInfo providerInfo)
: base(providerInfo)
{
RedirectUri = providerInfo!.RedirectUri ?? throw new InvalidOperationException("RedirectUri not set!");
}
}
}
@@ -0,0 +1,27 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// OAuth provider info with a <see cref="ServerUrl"/>.
/// </summary>
public sealed class ServerUrlOAuthProviderInfo : BasicOAuthProviderInfo
{
/// <summary>
/// The remote service URL.
/// </summary>
public Uri ServerUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerUrlOAuthProviderInfo"/> class.
/// </summary>
/// <param name="providerInfo">The <see cref="OAuthProviderInfo"/> to build from.</param>
public ServerUrlOAuthProviderInfo(OAuthProviderInfo providerInfo)
: base(providerInfo)
{
ServerUrl = providerInfo!.ServerUrl ?? throw new InvalidOperationException("ServerUrl not set!");
}
}
}
@@ -7,6 +7,7 @@ using HotChocolate.Types.Relay;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.GraphQL.Interfaces;
using Tgstation.Server.Host.GraphQL.Types.OAuth;
using Tgstation.Server.Host.Models.Transformers;
using Tgstation.Server.Host.Security;