diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs
index 7fe2f24992..37a61e55fb 100644
--- a/src/Tgstation.Server.Api/Models/ErrorCode.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs
@@ -461,7 +461,7 @@ namespace Tgstation.Server.Api.Models
RepoTestMergeConflict,
///
- /// Attempted to create an instance outside of the .
+ /// Attempted to create an instance outside of the .
///
[Description("The new instance's path is not under a white-listed path.")]
InstanceNotAtWhitelistedPath,
diff --git a/src/Tgstation.Server.Api/Models/Internal/GatewayInformation.cs b/src/Tgstation.Server.Api/Models/Internal/GatewayInformation.cs
deleted file mode 100644
index 48939aba6f..0000000000
--- a/src/Tgstation.Server.Api/Models/Internal/GatewayInformation.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Collections.Generic;
-
-namespace Tgstation.Server.Api.Models.Internal
-{
- ///
- /// Information about the local tgstation-server.
- ///
- public class GatewayInformation : GatewayInformationBase
- {
- ///
- /// If the server is running on a windows operating system.
- ///
- public bool WindowsHost { get; set; }
-
- ///
- /// Map of to the for them.
- ///
- public Dictionary? OAuthProviderInfos { get; set; }
- }
-}
diff --git a/src/Tgstation.Server.Api/Models/Internal/GatewayInformationBase.cs b/src/Tgstation.Server.Api/Models/Internal/ServerInformationBase.cs
similarity index 88%
rename from src/Tgstation.Server.Api/Models/Internal/GatewayInformationBase.cs
rename to src/Tgstation.Server.Api/Models/Internal/ServerInformationBase.cs
index f1f53b26bb..15fafe90b1 100644
--- a/src/Tgstation.Server.Api/Models/Internal/GatewayInformationBase.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/ServerInformationBase.cs
@@ -5,7 +5,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// Base class for .
///
- public abstract class GatewayInformationBase
+ public abstract class ServerInformationBase
{
///
/// Minimum length of database user passwords.
@@ -31,6 +31,6 @@ namespace Tgstation.Server.Api.Models.Internal
/// Limits the locations instances may be created or attached from.
///
[ResponseOptions]
- public ICollection? ValidInstancePaths { get; set; }
+ public List? ValidInstancePaths { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs b/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs
index a912fa127e..4e80e80564 100644
--- a/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs
+++ b/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models.Response
///
/// Represents basic server information.
///
- public sealed class ServerInformationResponse : Internal.GatewayInformation
+ public sealed class ServerInformationResponse : Internal.ServerInformationBase
{
///
/// The version of the host.
@@ -23,6 +23,16 @@ namespace Tgstation.Server.Api.Models.Response
///
public Version? DMApiVersion { get; set; }
+ ///
+ /// If the server is running on a windows operating system.
+ ///
+ public bool WindowsHost { get; set; }
+
+ ///
+ /// Map of to the for them.
+ ///
+ public Dictionary? OAuthProviderInfos { get; set; }
+
///
/// If there is a server update in progress.
///
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ServerInformation.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ServerInformation.graphql
deleted file mode 100644
index 5e04dc4a66..0000000000
--- a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ServerInformation.graphql
+++ /dev/null
@@ -1,38 +0,0 @@
-query ServerInformation {
- swarm {
- metadata {
- apiVersion
- dmApiVersion
- updateInProgress
- version
- }
- currentNode {
- gateway {
- information {
- instanceLimit
- minimumPasswordLength
- userGroupLimit
- userLimit
- validInstancePaths
- windowsHost
- oAuthProviderInfos {
- value {
- clientId
- redirectUri
- serverUrl
- }
- key
- }
- }
- }
- }
- nodes {
- info {
- address
- controller
- identifier
- publicAddress
- }
- }
- }
-}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/UnauthenticatedServerInformation.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/UnauthenticatedServerInformation.graphql
new file mode 100644
index 0000000000..44bff278f0
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/UnauthenticatedServerInformation.graphql
@@ -0,0 +1,19 @@
+query UnauthenticatedServerInformation {
+ swarm {
+ currentNode {
+ gateway {
+ information {
+ majorApiVersion
+ oAuthProviderInfos {
+ key
+ value {
+ clientId
+ redirectUri
+ serverUrl
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs
index 3a5ccd8ca2..1790316e37 100644
--- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs
+++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Configuration
///
/// General configuration options.
///
- public sealed class GeneralConfiguration : GatewayInformationBase
+ public sealed class GeneralConfiguration : ServerInformationBase
{
///
/// The key for the the resides in.
@@ -29,22 +29,22 @@ namespace Tgstation.Server.Host.Configuration
public const ushort DefaultApiPort = 5000;
///
- /// The default value for .
+ /// The default value for .
///
const uint DefaultMinimumPasswordLength = 15;
///
- /// The default value for .
+ /// The default value for .
///
const uint DefaultInstanceLimit = 10;
///
- /// The default value for .
+ /// The default value for .
///
const uint DefaultUserLimit = 100;
///
- /// The default value for .
+ /// The default value for .
///
const uint DefaultUserGroupLimit = 25;
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 8c6d33e8b4..de1599b484 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Globalization;
@@ -57,7 +57,6 @@ using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.GraphQL;
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;
@@ -297,7 +296,10 @@ namespace Tgstation.Server.Host.Core
services
.AddGraphQLServer(disableCostAnalyzer: true)
.AddAuthorization()
- .TryAddTypeInterceptor()
+ .ModifyOptions(options =>
+ {
+ options.EnsureAllNodesCanBeResolved = true;
+ })
.AddMutationConventions()
.AddGlobalObjectIdentification()
.AddQueryFieldToMutationPayloads()
@@ -314,6 +316,7 @@ namespace Tgstation.Server.Host.Core
.AddSorting()
.AddHostTypes()
.AddErrorFilter()
+ .AddType()
.AddType()
.AddType()
.AddType()
diff --git a/src/Tgstation.Server.Host/GraphQL/Interfaces/IGateway.cs b/src/Tgstation.Server.Host/GraphQL/Interfaces/IGateway.cs
index e8846abbea..56722119d2 100644
--- a/src/Tgstation.Server.Host/GraphQL/Interfaces/IGateway.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Interfaces/IGateway.cs
@@ -1,12 +1,4 @@
-using HotChocolate;
-using HotChocolate.Authorization;
-
-using Microsoft.Extensions.Options;
-using Tgstation.Server.Api.Models.Internal;
-using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.GraphQL.Types;
-using Tgstation.Server.Host.Security.OAuth;
-using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.GraphQL.Types;
namespace Tgstation.Server.Host.GraphQL.Interfaces
{
@@ -18,14 +10,7 @@ namespace Tgstation.Server.Host.GraphQL.Interfaces
///
/// Gets .
///
- /// The to use.
- /// The to use.
- /// The containing the to use.
- /// A new .
- [AllowAnonymous]
- GatewayInformation Information(
- [Service] IOAuthProviders oAuthProviders,
- [Service] IPlatformIdentifier platformIdentifier,
- [Service] IOptionsSnapshot generalConfigurationOptions);
+ /// The for the .
+ GatewayInformation Information();
}
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Interfaces/IServerNode.cs b/src/Tgstation.Server.Host/GraphQL/Interfaces/IServerNode.cs
new file mode 100644
index 0000000000..63ca2b3b31
--- /dev/null
+++ b/src/Tgstation.Server.Host/GraphQL/Interfaces/IServerNode.cs
@@ -0,0 +1,21 @@
+using HotChocolate;
+
+using Microsoft.Extensions.Options;
+
+using Tgstation.Server.Host.Configuration;
+
+namespace Tgstation.Server.Host.GraphQL.Interfaces
+{
+ ///
+ /// Represents a tgstation-server installation.
+ ///
+ public interface IServerNode
+ {
+ ///
+ /// Access the for the .
+ ///
+ /// The of .
+ /// The for the .
+ public IGateway Gateway([Service] IOptionsSnapshot swarmConfigurationOptions);
+ }
+}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/GatewayInformation.cs b/src/Tgstation.Server.Host/GraphQL/Types/GatewayInformation.cs
new file mode 100644
index 0000000000..aefc2562bb
--- /dev/null
+++ b/src/Tgstation.Server.Host/GraphQL/Types/GatewayInformation.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+
+using HotChocolate;
+
+using Microsoft.Extensions.Options;
+
+using Tgstation.Server.Api;
+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.Properties;
+using Tgstation.Server.Host.Security;
+using Tgstation.Server.Host.Security.OAuth;
+using Tgstation.Server.Host.System;
+
+namespace Tgstation.Server.Host.GraphQL.Types
+{
+ ///
+ /// Represents information about a retrieved via a .
+ ///
+ public sealed class GatewayInformation
+ {
+ ///
+ /// Gets the minimum valid password length for TGS users.
+ ///
+ /// The containing the .
+ /// A specifying the minimumn valid password length for TGS users.
+ [TgsGraphQLAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword)]
+ public uint? MinimumPasswordLength(
+ [Service] IOptionsSnapshot generalConfigurationOptions)
+ {
+ ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
+ return generalConfigurationOptions.Value.MinimumPasswordLength;
+ }
+
+ ///
+ /// Gets the maximum allowed attached instances for the .
+ ///
+ /// The containing the .
+ /// A specifying the maximum allowed attached instances for the .
+ [TgsGraphQLAuthorize(InstanceManagerRights.Create)]
+ public uint? InstanceLimit(
+ [Service] IOptionsSnapshot generalConfigurationOptions)
+ {
+ ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
+ return generalConfigurationOptions.Value.InstanceLimit;
+ }
+
+ ///
+ /// Gets the maximum allowed registered s for the .
+ ///
+ /// The containing the .
+ /// A specifying the maximum allowed registered users for the .
+ /// This limit only applies to user creation attempts made via the current .
+ [TgsGraphQLAuthorize(AdministrationRights.WriteUsers)]
+ public uint? UserLimit(
+ [Service] IOptionsSnapshot generalConfigurationOptions)
+ {
+ ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
+ return generalConfigurationOptions.Value.UserLimit;
+ }
+
+ ///
+ /// Gets the maximum allowed registered s for the .
+ ///
+ /// The containing the .
+ /// A specifying the maximum allowed registered s for the .
+ /// This limit only applies to creation attempts made via the current .
+ [TgsGraphQLAuthorize(AdministrationRights.WriteUsers)]
+ public uint? UserGroupLimit(
+ [Service] IOptionsSnapshot generalConfigurationOptions)
+ {
+ ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
+ return generalConfigurationOptions.Value.UserGroupLimit;
+ }
+
+ ///
+ /// Gets the locations s may be created or attached from if there are restrictions.
+ ///
+ /// The containing the .
+ /// The locations s may be created or attached from if there are restrictions, otherwise.
+ [TgsGraphQLAuthorize(InstanceManagerRights.Create | InstanceManagerRights.Relocate)]
+ public IReadOnlyCollection? ValidInstancePaths(
+ [Service] IOptionsSnapshot generalConfigurationOptions)
+ {
+ ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
+ return generalConfigurationOptions.Value.ValidInstancePaths;
+ }
+
+ ///
+ /// Gets a flag indicating whether or not the current runs on a Windows operating system.
+ ///
+ /// The to use.
+ /// if the runs on a Windows operating system, otherwise.
+ [TgsGraphQLAuthorize]
+ public bool? WindowsHost(
+ [Service] IPlatformIdentifier platformIdentifier)
+ {
+ ArgumentNullException.ThrowIfNull(platformIdentifier);
+ return platformIdentifier.IsWindows;
+ }
+
+ ///
+ /// Gets the swarm protocol .
+ ///
+ [TgsGraphQLAuthorize]
+ public Version? SwarmProtocolVersion => global::System.Version.Parse(MasterVersionsAttribute.Instance.RawSwarmProtocolVersion);
+
+ ///
+ /// Gets the of tgstation-server the is running.
+ ///
+ /// The to use.
+ /// The of tgstation-server the is running.
+ [TgsGraphQLAuthorize]
+ public Version? Version(
+ [Service] IAssemblyInformationProvider assemblyInformationProvider)
+ {
+ ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
+ return assemblyInformationProvider.Version;
+ }
+
+ ///
+ /// Gets the major HTTP API number of the .
+ ///
+ public int MajorApiVersion => ApiHeaders.Version.Major;
+
+ ///
+ /// Gets the HTTP API of the .
+ ///
+ [TgsGraphQLAuthorize]
+ public Version? ApiVersion => ApiHeaders.Version;
+
+ ///
+ /// Gets the DMAPI interop the uses.
+ ///
+ [TgsGraphQLAuthorize]
+ public Version? DMApiVersion => DMApiConstants.InteropVersion;
+
+ ///
+ /// Gets the information needed to perform open authentication with the .
+ ///
+ /// The to use.
+ /// A map of enabled s to their .
+ public IReadOnlyDictionary OAuthProviderInfos(
+ [Service] IOAuthProviders oAuthProviders)
+ {
+ ArgumentNullException.ThrowIfNull(oAuthProviders);
+ return oAuthProviders.ProviderInfos();
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Interceptors/PublicAuthorizeDirectiveTypeInterceptor.cs b/src/Tgstation.Server.Host/GraphQL/Types/Interceptors/PublicAuthorizeDirectiveTypeInterceptor.cs
deleted file mode 100644
index d4cb7407bd..0000000000
--- a/src/Tgstation.Server.Host/GraphQL/Types/Interceptors/PublicAuthorizeDirectiveTypeInterceptor.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using HotChocolate.Configuration;
-using HotChocolate.Types.Descriptors.Definitions;
-
-namespace Tgstation.Server.Host.GraphQL.Types.Interceptors
-{
- ///
- /// Makes the @authorize directive public (It is internal by default).
- ///
- public sealed class PublicAuthorizeDirectiveTypeInterceptor : TypeInterceptor
- {
- ///
- public override void OnBeforeRegisterDependencies(ITypeDiscoveryContext discoveryContext, DefinitionBase definition)
- {
- if (definition is DirectiveTypeDefinition dtd
- && dtd.Name == "authorize")
- {
- dtd.IsPublic = true;
- }
-
- base.OnBeforeRegisterDependencies(discoveryContext, definition);
- }
- }
-}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/LocalGateway.cs b/src/Tgstation.Server.Host/GraphQL/Types/LocalGateway.cs
index 107df473e1..09d21f7f2d 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/LocalGateway.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/LocalGateway.cs
@@ -1,14 +1,4 @@
-using System;
-
-using HotChocolate;
-
-using Microsoft.Extensions.Options;
-
-using Tgstation.Server.Api.Models.Internal;
-using Tgstation.Server.Host.Configuration;
-using Tgstation.Server.Host.GraphQL.Interfaces;
-using Tgstation.Server.Host.Security.OAuth;
-using Tgstation.Server.Host.System;
+using Tgstation.Server.Host.GraphQL.Interfaces;
namespace Tgstation.Server.Host.GraphQL.Types
{
@@ -18,26 +8,6 @@ namespace Tgstation.Server.Host.GraphQL.Types
public sealed class LocalGateway : IGateway
{
///
- public GatewayInformation Information(
- [Service] IOAuthProviders oAuthProviders,
- [Service] IPlatformIdentifier platformIdentifier,
- [Service] IOptionsSnapshot generalConfigurationOptions)
- {
- ArgumentNullException.ThrowIfNull(oAuthProviders);
- ArgumentNullException.ThrowIfNull(platformIdentifier);
- ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
-
- var generalConfiguration = generalConfigurationOptions.Value;
- return new GatewayInformation
- {
- MinimumPasswordLength = generalConfiguration.MinimumPasswordLength,
- InstanceLimit = generalConfiguration.InstanceLimit,
- UserLimit = generalConfiguration.UserLimit,
- UserGroupLimit = generalConfiguration.UserGroupLimit,
- ValidInstancePaths = generalConfiguration.ValidInstancePaths,
- WindowsHost = platformIdentifier.IsWindows,
- OAuthProviderInfos = oAuthProviders.ProviderInfos(),
- };
- }
+ public GatewayInformation Information() => new();
}
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/NodeInformation.cs b/src/Tgstation.Server.Host/GraphQL/Types/NodeInformation.cs
deleted file mode 100644
index 42af3c8176..0000000000
--- a/src/Tgstation.Server.Host/GraphQL/Types/NodeInformation.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Linq;
-
-using HotChocolate;
-using HotChocolate.Types.Relay;
-
-using Tgstation.Server.Host.Swarm;
-
-namespace Tgstation.Server.Host.GraphQL.Types
-{
- ///
- /// Represent a server in the TGS server swarm.
- ///
- [Node]
- public sealed class NodeInformation
- {
- ///
- /// Node resolver for s.
- ///
- /// The to lookup.
- /// The to use.
- /// The queried , if present.
- public NodeInformation? GetNodeInformation(
- string identifier,
- [Service] ISwarmService swarmService)
- {
- ArgumentNullException.ThrowIfNull(identifier);
- ArgumentNullException.ThrowIfNull(swarmService);
-
- var node = swarmService.GetSwarmServers()
- ?.FirstOrDefault(node => node.Identifier == identifier);
- if (node == null)
- return null;
-
- return new NodeInformation(node);
- }
-
- ///
- /// The swarm server ID.
- ///
- [ID]
- public string Identifier { get; }
-
- ///
- /// The swarm server's internal .
- ///
- public Uri Address { get; }
-
- ///
- /// The swarm server's optional public address.
- ///
- public Uri? PublicAddress { get; }
-
- ///
- /// Whether or not the server is the swarm's controller.
- ///
- public bool Controller { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The to build from.
- public NodeInformation(Api.Models.Internal.SwarmServerInformation swarmServerInformation)
- {
- ArgumentNullException.ThrowIfNull(swarmServerInformation);
-
- Identifier = swarmServerInformation.Identifier!;
- Address = swarmServerInformation.Address!;
- PublicAddress = swarmServerInformation.PublicAddress;
- Controller = swarmServerInformation.Controller;
- }
- }
-}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs b/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
index 9f0734f316..ba0c070582 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
@@ -1,7 +1,5 @@
using System.Diagnostics.CodeAnalysis;
-using HotChocolate.Types.Relay;
-
using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Host.GraphQL.Types
@@ -9,7 +7,6 @@ namespace Tgstation.Server.Host.GraphQL.Types
///
/// Represents a set of permissions for the server.
///
- [Node]
public sealed class PermissionSet : Entity
{
///
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/RemoteGateway.cs b/src/Tgstation.Server.Host/GraphQL/Types/RemoteGateway.cs
index 5c3305e56d..1a17b636e9 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/RemoteGateway.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/RemoteGateway.cs
@@ -1,15 +1,6 @@
using System;
-using HotChocolate;
-
-using Microsoft.Extensions.Options;
-
-using Tgstation.Server.Api.Models;
-using Tgstation.Server.Api.Models.Internal;
-using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.GraphQL.Interfaces;
-using Tgstation.Server.Host.Security.OAuth;
-using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.GraphQL.Types
{
@@ -20,16 +11,6 @@ namespace Tgstation.Server.Host.GraphQL.Types
public sealed class RemoteGateway : IGateway
{
///
- public GatewayInformation Information(
- [Service] IOAuthProviders oAuthProviders,
- [Service] IPlatformIdentifier platformIdentifier,
- [Service] IOptionsSnapshot generalConfigurationOptions)
- {
- ArgumentNullException.ThrowIfNull(oAuthProviders);
- ArgumentNullException.ThrowIfNull(platformIdentifier);
- ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
-
- throw new ErrorMessageException(ErrorCode.RemoteGatewaysNotImplemented);
- }
+ public GatewayInformation Information() => throw new NotImplementedException();
}
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs b/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs
index 1b874c13b1..286aad2adf 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs
@@ -8,8 +8,10 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.GraphQL.Interfaces;
+using Tgstation.Server.Host.Properties;
+using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Swarm;
-using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.GraphQL.Types
{
@@ -19,56 +21,62 @@ namespace Tgstation.Server.Host.GraphQL.Types
public sealed class ServerSwarm
{
///
- /// Gets the for the swarm.
+ /// If there is a swarm update in progress.
///
- /// The to use.
/// The to use.
- /// A new .
- public SwarmMetadata Metadata(
- [Service] IAssemblyInformationProvider assemblyInformationProvider,
+ /// if there is an update in progress, otherwise.
+ [TgsGraphQLAuthorize]
+ public bool UpdateInProgress(
[Service] IServerControl serverControl)
{
- ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
ArgumentNullException.ThrowIfNull(serverControl);
- return new SwarmMetadata(assemblyInformationProvider, serverControl.UpdateInProgress);
+ return serverControl.UpdateInProgress;
}
+ ///
+ /// Gets the swarm protocol major version in use.
+ ///
+ [TgsGraphQLAuthorize]
+ public int ProtocolMajorVersion => Version.Parse(MasterVersionsAttribute.Instance.RawSwarmProtocolVersion).Major;
+
///
/// Gets the swarm's .
///
/// A new .
+ [TgsGraphQLAuthorize]
public Users Users() => new();
///
/// Gets the connected server.
///
- /// The to use.
/// The containing the current .
- /// A new .
- public SwarmNode CurrentNode(
- [Service] ISwarmService swarmService,
- [Service] IOptionsSnapshot swarmConfigurationOptions)
+ /// The to use.
+ /// A new for the local node if it is part of a swarm, otherwise.
+ public IServerNode CurrentNode(
+ [Service] IOptionsSnapshot swarmConfigurationOptions,
+ [Service] ISwarmService swarmService)
{
- ArgumentNullException.ThrowIfNull(swarmService);
ArgumentNullException.ThrowIfNull(swarmConfigurationOptions);
+ ArgumentNullException.ThrowIfNull(swarmService);
- var nodeInfos = Nodes(swarmService);
- if (nodeInfos != null)
- return nodeInfos.First(x => x.Info!.Identifier == swarmConfigurationOptions.Value.Identifier);
+ var ourIdentifier = swarmConfigurationOptions.Value.Identifier;
+ if (ourIdentifier == null)
+ return new StandaloneNode();
- return new SwarmNode(null);
+ return (IServerNode?)SwarmNode.GetSwarmNode(ourIdentifier, swarmService) ?? new StandaloneNode();
}
///
/// Gets all servers in the swarm.
///
/// The to use.
- /// A of s if the local server is part of a swarm, otherwise.
+ /// A of s if the local node is part of a swarm, otherwise.
+ [TgsGraphQLAuthorize]
public List? Nodes(
[Service] ISwarmService swarmService)
{
ArgumentNullException.ThrowIfNull(swarmService);
- return swarmService.GetSwarmServers()?.Select(x => new SwarmNode(new NodeInformation(x))).ToList();
+ return swarmService.GetSwarmServers()?.Select(x => new SwarmNode(x)).ToList();
}
}
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/StandaloneNode.cs b/src/Tgstation.Server.Host/GraphQL/Types/StandaloneNode.cs
new file mode 100644
index 0000000000..335bee3543
--- /dev/null
+++ b/src/Tgstation.Server.Host/GraphQL/Types/StandaloneNode.cs
@@ -0,0 +1,19 @@
+using HotChocolate;
+
+using Microsoft.Extensions.Options;
+
+using Tgstation.Server.Host.Configuration;
+using Tgstation.Server.Host.GraphQL.Interfaces;
+
+namespace Tgstation.Server.Host.GraphQL.Types
+{
+ ///
+ /// A not running as part of a larger .
+ ///
+ public sealed class StandaloneNode : IServerNode
+ {
+ ///
+ public IGateway Gateway([Service] IOptionsSnapshot swarmConfigurationOptions)
+ => new LocalGateway();
+ }
+}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/SwarmMetadata.cs b/src/Tgstation.Server.Host/GraphQL/Types/SwarmMetadata.cs
deleted file mode 100644
index 19fe192b23..0000000000
--- a/src/Tgstation.Server.Host/GraphQL/Types/SwarmMetadata.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System;
-
-using Tgstation.Server.Api;
-using Tgstation.Server.Host.Components.Interop;
-using Tgstation.Server.Host.System;
-
-namespace Tgstation.Server.Host.GraphQL.Types
-{
- ///
- /// Represents information that is constant across all servers in a .
- ///
- public sealed class SwarmMetadata
- {
- ///
- /// The version of the host.
- ///
- public Version Version { get; }
-
- ///
- /// The version of the host.
- ///
- public Version ApiVersion => ApiHeaders.Version;
-
- ///
- /// The DMAPI interop version the server uses.
- ///
- public Version DMApiVersion => DMApiConstants.InteropVersion;
-
- ///
- /// If there is a server update in progress.
- ///
- public bool UpdateInProgress { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The used to derive the .
- /// The value of .
- public SwarmMetadata(IAssemblyInformationProvider assemblyInformationProvider, bool updateInProgress)
- {
- ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
- Version = assemblyInformationProvider.Version;
- UpdateInProgress = updateInProgress;
- }
- }
-}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/SwarmNode.cs b/src/Tgstation.Server.Host/GraphQL/Types/SwarmNode.cs
index f864287f3e..2701e828b5 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/SwarmNode.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/SwarmNode.cs
@@ -1,32 +1,80 @@
using System;
+using System.Linq;
using HotChocolate;
+using HotChocolate.Types.Relay;
using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
+using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.GraphQL.Interfaces;
+using Tgstation.Server.Host.Swarm;
namespace Tgstation.Server.Host.GraphQL.Types
{
///
/// Represents a node server in a swarm.
///
- public sealed class SwarmNode
+ [Node(IdField = nameof(Identifier))]
+ public sealed class SwarmNode : IServerNode
{
///
- /// Gets the .
+ /// The swarm server ID.
///
- public NodeInformation? Info { get; }
+ [ID]
+ public string Identifier { get; }
+
+ ///
+ /// The swarm server's internal .
+ ///
+ public Uri Address { get; }
+
+ ///
+ /// The swarm server's optional public address.
+ ///
+ public Uri? PublicAddress { get; }
+
+ ///
+ /// Whether or not the server is the swarm's controller.
+ ///
+ public bool Controller { get; }
+
+ ///
+ /// Node resolver for s.
+ ///
+ /// The .
+ /// The to load from.
+ /// A new with the matching if found, otherwise.
+ public static SwarmNode? GetSwarmNode(
+ string identifier,
+ [Service] ISwarmService swarmService)
+ {
+ ArgumentNullException.ThrowIfNull(identifier);
+ ArgumentNullException.ThrowIfNull(swarmService);
+ var info = swarmService
+ .GetSwarmServers()
+ ?.FirstOrDefault(x => x.Identifier == identifier);
+
+ if (info == null)
+ return null;
+
+ return new SwarmNode(info);
+ }
///
/// Initializes a new instance of the class.
///
- /// The value of .
- public SwarmNode(NodeInformation? info)
+ /// The to use for initialization.
+ public SwarmNode(SwarmServerInformation? nodeInformation)
{
- Info = info;
+ ArgumentNullException.ThrowIfNull(nodeInformation);
+
+ Identifier = nodeInformation.Identifier!;
+ Address = nodeInformation.Address!;
+ PublicAddress = nodeInformation.PublicAddress;
+ Controller = nodeInformation.Controller;
}
///
@@ -35,11 +83,11 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// The containing the current .
/// A new .
/// The 's .
- public IGateway? Gateway([Service] IOptionsSnapshot swarmConfigurationOptions)
+ public IGateway Gateway([Service] IOptionsSnapshot swarmConfigurationOptions)
{
ArgumentNullException.ThrowIfNull(swarmConfigurationOptions);
- bool local = Info == null || Info.Identifier == swarmConfigurationOptions.Value.Identifier;
+ bool local = Identifier == swarmConfigurationOptions.Value.Identifier;
if (local)
return new LocalGateway();
diff --git a/src/Tgstation.Server.Host/Security/TgsGraphQLAuthorizeAttribute.cs b/src/Tgstation.Server.Host/Security/TgsGraphQLAuthorizeAttribute.cs
new file mode 100644
index 0000000000..e064440e18
--- /dev/null
+++ b/src/Tgstation.Server.Host/Security/TgsGraphQLAuthorizeAttribute.cs
@@ -0,0 +1,118 @@
+using System;
+
+using HotChocolate.Authorization;
+
+using Tgstation.Server.Api.Rights;
+
+namespace Tgstation.Server.Host.Security
+{
+ ///
+ /// Helper for using the with the system.
+ ///
+#pragma warning disable CA1019
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
+ sealed class TgsGraphQLAuthorizeAttribute : AuthorizeAttribute
+ {
+ ///
+ /// Gets the associated with the if any.
+ ///
+ public RightsType? RightsType { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TgsGraphQLAuthorizeAttribute()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(AdministrationRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.Administration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(InstanceManagerRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.InstanceManager;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(RepositoryRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.Repository;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(EngineRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.Engine;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(DreamMakerRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.DreamMaker;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(DreamDaemonRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.DreamDaemon;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(ChatBotRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.ChatBots;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(ConfigurationRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.Configuration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The required.
+ public TgsGraphQLAuthorizeAttribute(InstancePermissionSetRights requiredRights)
+ {
+ Roles = RightsHelper.RoleNames(requiredRights).Split(',');
+ RightsType = Api.Rights.RightsType.InstancePermissionSet;
+ }
+ }
+}
diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs
index bc55b33077..1c17f587c0 100644
--- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs
+++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs
@@ -1380,23 +1380,8 @@ namespace Tgstation.Server.Tests.Live
await multiClient.ExecuteReadOnlyConfirmEquivalence(
restClient => restClient.ServerInformation(cancellationToken),
- async gqlClient => (await gqlClient.ServerInformation.ExecuteAsync(cancellationToken)).Data,
- (restServerInfo, gqlServerInfo) => restServerInfo.UpdateInProgress == gqlServerInfo.Swarm.Metadata.UpdateInProgress
- && restServerInfo.Version == gqlServerInfo.Swarm.Metadata.Version
- && restServerInfo.DMApiVersion == gqlServerInfo.Swarm.Metadata.DmApiVersion
- && restServerInfo.InstanceLimit == gqlServerInfo.Swarm.CurrentNode.Gateway.Information.InstanceLimit
- && restServerInfo.UserGroupLimit == gqlServerInfo.Swarm.CurrentNode.Gateway.Information.UserGroupLimit
- && restServerInfo.ValidInstancePaths.SequenceEqual(gqlServerInfo.Swarm.CurrentNode.Gateway.Information.ValidInstancePaths)
- && restServerInfo.UserLimit == gqlServerInfo.Swarm.CurrentNode.Gateway.Information.UserLimit
- && restServerInfo.MinimumPasswordLength == gqlServerInfo.Swarm.CurrentNode.Gateway.Information.MinimumPasswordLength
- && ((object)restServerInfo.SwarmServers == gqlServerInfo.Swarm.Nodes
- || restServerInfo.SwarmServers.SequenceEqual(gqlServerInfo.Swarm.Nodes.Select(x => new SwarmServerResponse(new Api.Models.Internal.SwarmServerInformation
- {
- Address = x.Info.Address,
- PublicAddress = x.Info.PublicAddress,
- Controller = x.Info.Controller,
- Identifier = x.Info.Identifier,
- }))))
+ async gqlClient => (await gqlClient.UnauthenticatedServerInformation.ExecuteAsync(cancellationToken)).Data,
+ (restServerInfo, gqlServerInfo) => restServerInfo.ApiVersion.Major == gqlServerInfo.Swarm.CurrentNode.Gateway.Information.MajorApiVersion
&& (restServerInfo.OAuthProviderInfos == gqlServerInfo.Swarm.CurrentNode.Gateway.Information.OAuthProviderInfos
|| restServerInfo.OAuthProviderInfos.All(kvp =>
{