diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateSystemUserWithPermissionSet.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateSystemUserWithPermissionSet.graphql
new file mode 100644
index 0000000000..5c9e176dc4
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateSystemUserWithPermissionSet.graphql
@@ -0,0 +1,16 @@
+mutation CreateSystemUserWithPermissionSet($systemIdentifier: String!) {
+ createUserBySystemIDAndPermissionSet(
+ input: { permissionSet: {}, systemIdentifier: $systemIdentifier }
+ ) {
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ user {
+ id
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserFromOAuthConnection.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserFromOAuthConnection.graphql
new file mode 100644
index 0000000000..9d374b5ff0
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserFromOAuthConnection.graphql
@@ -0,0 +1,14 @@
+mutation CreateUserFromOAuthConnection($name: String!, $oAuthConnections: [OAuthConnectionInput!]!) {
+ createUserByOAuthAndPermissionSet(input: { name: $name, oAuthConnections: $oAuthConnections }) {
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ user {
+ id
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroup.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroup.graphql
new file mode 100644
index 0000000000..2d8489ecb6
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroup.graphql
@@ -0,0 +1,40 @@
+mutation CreateUserGroup($name: String!) {
+ createUserGroup(input: { name: $name }) {
+ userGroup {
+ id
+ name
+ permissionSet {
+ administrationRights {
+ canChangeVersion
+ canDownloadLogs
+ canEditOwnOAuthConnections
+ canEditOwnPassword
+ canReadUsers
+ canRestartHost
+ canUploadVersion
+ canWriteUsers
+ }
+ instanceManagerRights {
+ canCreate
+ canDelete
+ canGrantPermissions
+ canList
+ canRead
+ canRelocate
+ canRename
+ canSetAutoUpdate
+ canSetChatBotLimit
+ canSetConfiguration
+ canSetOnline
+ }
+ }
+ }
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroupWithInstanceListPerm.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroupWithInstanceListPerm.graphql
new file mode 100644
index 0000000000..6a99c7ede0
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroupWithInstanceListPerm.graphql
@@ -0,0 +1,40 @@
+mutation CreateUserGroupWithInstanceListPerm($name: String!) {
+ createUserGroup(input: { name: $name, permissionSet: { instanceManagerRights: { canList: true } } }) {
+ userGroup {
+ id
+ name
+ permissionSet {
+ administrationRights {
+ canChangeVersion
+ canDownloadLogs
+ canEditOwnOAuthConnections
+ canEditOwnPassword
+ canReadUsers
+ canRestartHost
+ canUploadVersion
+ canWriteUsers
+ }
+ instanceManagerRights {
+ canCreate
+ canDelete
+ canGrantPermissions
+ canList
+ canRead
+ canRelocate
+ canRename
+ canSetAutoUpdate
+ canSetChatBotLimit
+ canSetConfiguration
+ canSetOnline
+ }
+ }
+ }
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserWithPassword.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserWithPassword.graphql
new file mode 100644
index 0000000000..8f330b0364
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserWithPassword.graphql
@@ -0,0 +1,14 @@
+mutation CreateUserWithPassword($name: String!, $password: String!) {
+ createUserByPasswordAndPermissionSet(input: { name: $name, password: $password }) {
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ user {
+ id
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserWithPasswordSelectOAuthConnections.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserWithPasswordSelectOAuthConnections.graphql
new file mode 100644
index 0000000000..2d6b335889
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserWithPasswordSelectOAuthConnections.graphql
@@ -0,0 +1,18 @@
+mutation CreateUserWithPasswordSelectOAuthConnections($name: String!, $password: String!) {
+ createUserByPasswordAndPermissionSet(input: { name: $name, password: $password }) {
+ user {
+ id
+ oAuthConnections {
+ externalUserId
+ provider
+ }
+ }
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/DeleteUserGroup.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/DeleteUserGroup.graphql
new file mode 100644
index 0000000000..0b94be4e05
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/DeleteUserGroup.graphql
@@ -0,0 +1,11 @@
+mutation DeleteUserGroup($id: ID!) {
+ deleteEmptyUserGroup(input: { id: $id }) {
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetFullPermsOnUserGroup.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetFullPermsOnUserGroup.graphql
new file mode 100644
index 0000000000..1766cb4149
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetFullPermsOnUserGroup.graphql
@@ -0,0 +1,69 @@
+mutation SetFullPermsOnUserGroup($id: ID!) {
+ updateUserGroup(
+ input: {
+ id: $id
+ newPermissionSet: {
+ administrationRights: {
+ canChangeVersion: true
+ canDownloadLogs: true
+ canEditOwnOAuthConnections: true
+ canEditOwnPassword: true
+ canReadUsers: true
+ canWriteUsers: true
+ canUploadVersion: true
+ canRestartHost: true
+ }
+ instanceManagerRights: {
+ canCreate: true
+ canDelete: true
+ canGrantPermissions: true
+ canSetOnline: true
+ canSetConfiguration: true
+ canSetChatBotLimit: true
+ canSetAutoUpdate: true
+ canRename: true
+ canRead: true
+ canList: true
+ canRelocate: true
+ }
+ }
+ }
+ ) {
+ userGroup {
+ id
+ name
+ permissionSet {
+ administrationRights {
+ canChangeVersion
+ canDownloadLogs
+ canEditOwnOAuthConnections
+ canEditOwnPassword
+ canReadUsers
+ canRestartHost
+ canUploadVersion
+ canWriteUsers
+ }
+ instanceManagerRights {
+ canCreate
+ canDelete
+ canGrantPermissions
+ canList
+ canRead
+ canRelocate
+ canRename
+ canSetAutoUpdate
+ canSetChatBotLimit
+ canSetConfiguration
+ canSetOnline
+ }
+ }
+ }
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserGroup.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserGroup.graphql
new file mode 100644
index 0000000000..d68865e550
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserGroup.graphql
@@ -0,0 +1,41 @@
+mutation SetUserGroup($id: ID!, $newGroupId: ID!) {
+ updateUserSetGroup(input: { id: $id, newGroupId: $newGroupId }) {
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ user {
+ ownedPermissionSet {
+ instanceManagerRights {
+ canCreate
+ canDelete
+ canGrantPermissions
+ canList
+ canRead
+ canRelocate
+ canRename
+ canSetAutoUpdate
+ canSetChatBotLimit
+ canSetConfiguration
+ canSetOnline
+ }
+ administrationRights {
+ canChangeVersion
+ canDownloadLogs
+ canEditOwnOAuthConnections
+ canEditOwnPassword
+ canReadUsers
+ canRestartHost
+ canUploadVersion
+ canWriteUsers
+ }
+ }
+ group {
+ id
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserOAuthConnections.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserOAuthConnections.graphql
new file mode 100644
index 0000000000..81746cf0b4
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserOAuthConnections.graphql
@@ -0,0 +1,25 @@
+mutation SetUserOAuthConnections($id: ID!, $newOAuthConnections: [OAuthConnectionInput!]!) {
+ updateUser(
+ input: { id: $id, newOAuthConnections: $newOAuthConnections }
+ ) {
+ user {
+ canonicalName
+ createdAt
+ enabled
+ id
+ name
+ systemIdentifier
+ oAuthConnections {
+ externalUserId
+ provider
+ }
+ }
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserPermissionSet.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserPermissionSet.graphql
new file mode 100644
index 0000000000..a1dbcb24e6
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserPermissionSet.graphql
@@ -0,0 +1,66 @@
+mutation SetUserPermissionSet($id: ID!, $permissionSet: PermissionSetInput!) {
+ updateUserSetOwnedPermissionSet(input: { newPermissionSet: $permissionSet, id: $id }) {
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ user {
+ effectivePermissionSet {
+ administrationRights {
+ canChangeVersion
+ canDownloadLogs
+ canEditOwnOAuthConnections
+ canEditOwnPassword
+ canReadUsers
+ canRestartHost
+ canUploadVersion
+ canWriteUsers
+ }
+ instanceManagerRights {
+ canCreate
+ canDelete
+ canGrantPermissions
+ canList
+ canRead
+ canRelocate
+ canRename
+ canSetAutoUpdate
+ canSetChatBotLimit
+ canSetConfiguration
+ canSetOnline
+ }
+ }
+ ownedPermissionSet {
+ administrationRights {
+ canChangeVersion
+ canDownloadLogs
+ canEditOwnOAuthConnections
+ canEditOwnPassword
+ canReadUsers
+ canRestartHost
+ canUploadVersion
+ canWriteUsers
+ }
+ instanceManagerRights {
+ canCreate
+ canDelete
+ canGrantPermissions
+ canList
+ canRead
+ canRelocate
+ canRename
+ canSetAutoUpdate
+ canSetChatBotLimit
+ canSetConfiguration
+ canSetOnline
+ }
+ }
+ group {
+ id
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/UpdateUserOAuthConnections.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/UpdateUserOAuthConnections.graphql
new file mode 100644
index 0000000000..8cd18d2dc5
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/UpdateUserOAuthConnections.graphql
@@ -0,0 +1,18 @@
+mutation UpdateUserOAuthConnections($id: ID!, $newOAuthConnections: [OAuthConnectionInput!]) {
+ updateUser(input: { id: $id, newOAuthConnections: $newOAuthConnections }) {
+ user {
+ id
+ oAuthConnections {
+ externalUserId
+ provider
+ }
+ }
+ errors {
+ ... on ErrorMessageError {
+ additionalData
+ errorCode
+ message
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetSomeGroupInfo.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetSomeGroupInfo.graphql
new file mode 100644
index 0000000000..f36dee754a
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetSomeGroupInfo.graphql
@@ -0,0 +1,41 @@
+query GetSomeGroupInfo($id: ID!) {
+ swarm {
+ users {
+ groups {
+ byId(id: $id) {
+ permissionSet {
+ instanceManagerRights {
+ canCreate
+ canDelete
+ canGrantPermissions
+ canList
+ canRead
+ canRelocate
+ canRename
+ canSetAutoUpdate
+ canSetChatBotLimit
+ canSetConfiguration
+ canSetOnline
+ }
+ administrationRights {
+ canChangeVersion
+ canDownloadLogs
+ canEditOwnOAuthConnections
+ canEditOwnPassword
+ canReadUsers
+ canRestartHost
+ canUploadVersion
+ canWriteUsers
+ }
+ }
+ queryableUsersByGroup(first: 1) {
+ totalCount
+ nodes {
+ id
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUserById.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUserById.graphql
new file mode 100644
index 0000000000..1dc6c3a8de
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUserById.graphql
@@ -0,0 +1,14 @@
+query GetUserById($id: ID!) {
+ swarm {
+ users {
+ byId(id: $id) {
+ canonicalName
+ createdAt
+ enabled
+ id
+ name
+ systemIdentifier
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUserNameByNodeId.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUserNameByNodeId.graphql
new file mode 100644
index 0000000000..5565345e8a
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUserNameByNodeId.graphql
@@ -0,0 +1,8 @@
+query GetUserNameByNodeId($id: ID!) {
+ node(id: $id) {
+ ... on UserName {
+ id
+ name
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ListUserGroups.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ListUserGroups.graphql
new file mode 100644
index 0000000000..08bc93b4fd
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ListUserGroups.graphql
@@ -0,0 +1,14 @@
+query ListUserGroups {
+ swarm {
+ users {
+ groups {
+ queryableGroups {
+ totalCount
+ nodes {
+ id
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ListUsers.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ListUsers.graphql
new file mode 100644
index 0000000000..1553df9fc0
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ListUsers.graphql
@@ -0,0 +1,12 @@
+query ListUsers {
+ swarm {
+ users {
+ queryableUsers {
+ nodes {
+ id
+ }
+ totalCount
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/PageUserIds.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/PageUserIds.graphql
new file mode 100644
index 0000000000..8db5d7db4a
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/PageUserIds.graphql
@@ -0,0 +1,16 @@
+query PageUserIds($first: Int, $after: String) {
+ swarm {
+ users {
+ queryableUsers(first: $first, after: $after) {
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ totalCount
+ nodes {
+ id
+ }
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ReadCurrentUser.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ReadCurrentUser.graphql
new file mode 100644
index 0000000000..dea2849d19
--- /dev/null
+++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ReadCurrentUser.graphql
@@ -0,0 +1,51 @@
+query ReadCurrentUser {
+ swarm {
+ users {
+ current {
+ canonicalName
+ createdAt
+ enabled
+ id
+ name
+ systemIdentifier
+ group {
+ id
+ name
+ }
+ oAuthConnections {
+ externalUserId
+ provider
+ }
+ effectivePermissionSet {
+ administrationRights {
+ canChangeVersion
+ canDownloadLogs
+ canEditOwnOAuthConnections
+ canEditOwnPassword
+ canReadUsers
+ canRestartHost
+ canUploadVersion
+ canWriteUsers
+ }
+ instanceManagerRights {
+ canCreate
+ canDelete
+ canGrantPermissions
+ canList
+ canRead
+ canRelocate
+ canRename
+ canSetAutoUpdate
+ canSetChatBotLimit
+ canSetConfiguration
+ canSetOnline
+ }
+ }
+ createdBy {
+ id
+ name
+ }
+ }
+ }
+ }
+}
diff --git a/tests/Tgstation.Server.Tests/Live/ApiAssert.cs b/tests/Tgstation.Server.Tests/Live/ApiAssert.cs
index 1fe6742015..5bd736c6cc 100644
--- a/tests/Tgstation.Server.Tests/Live/ApiAssert.cs
+++ b/tests/Tgstation.Server.Tests/Live/ApiAssert.cs
@@ -1,9 +1,15 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
-using Tgstation.Server.Api.Models;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using StrawberryShake;
+
using Tgstation.Server.Client;
+using Tgstation.Server.Client.GraphQL;
namespace Tgstation.Server.Tests.Live
{
@@ -19,7 +25,7 @@ namespace Tgstation.Server.Tests.Live
/// A resulting in a .
/// The expected .
/// A representing the running operation,
- public static async ValueTask ThrowsException(Func action, ErrorCode? expectedErrorCode = null)
+ public static async ValueTask ThrowsException(Func action, Api.Models.ErrorCode? expectedErrorCode = null)
where TApiException : ApiException
{
try
@@ -43,7 +49,7 @@ namespace Tgstation.Server.Tests.Live
/// A resulting in a .
/// The expected .
/// A representing the running operation,
- public static async ValueTask ThrowsException(Func> action, ErrorCode? expectedErrorCode = null)
+ public static async ValueTask ThrowsException(Func> action, Api.Models.ErrorCode? expectedErrorCode = null)
where TApiException : ApiException
{
try
@@ -58,5 +64,25 @@ namespace Tgstation.Server.Tests.Live
Assert.Fail($"Expected exception {typeof(TApiException)}!");
}
+
+ public static async ValueTask OperationFails(
+ IGraphQLServerClient client,
+ Func>> operationInvoker,
+ Func payloadSelector,
+ Client.GraphQL.ErrorCode expectedErrorCode,
+ CancellationToken cancellationToken)
+ where TResultData : class
+ {
+ var operationResult = await client.RunOperation(operationInvoker, cancellationToken);
+ operationResult.EnsureNoErrors();
+
+ var payload = payloadSelector(operationResult.Data);
+
+ var payloadErrors = (IEnumerable