diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 61a4f6a558..21776b4fe8 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -38,7 +38,7 @@ env: OD_MIN_COMPAT_DOTNET_VERSION: 7 OD_DOTNET_VERSION: 9 TGS_DOTNET_QUALITY: ga - TGS_WEBPANEL_NODE_VERSION: 20.x + TGS_NODE_VERSION: 20.x TGS_TEST_GITHUB_TOKEN: ${{ secrets.LIVE_TESTS_TOKEN }} PACKAGING_PRIVATE_KEY_PASSPHRASE: ${{ secrets.PACKAGING_PRIVATE_KEY_PASSPHRASE }} @@ -109,6 +109,14 @@ jobs: dotnet-version: ${{ env.TGS_DOTNET_VERSION }}.0.x dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} + - name: Setup Node.JS + uses: actions/setup-node@v4 + with: + node-version: ${{ env.TGS_NODE_VERSION }} + + - name: Enable Corepack + run: corepack enable + - name: Checkout (Branch) uses: actions/checkout@v4 if: github.event_name == 'push' || github.event_name == 'schedule' @@ -475,7 +483,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v4 with: - node-version: ${{ env.TGS_WEBPANEL_NODE_VERSION }} + node-version: ${{ env.TGS_NODE_VERSION }} - name: Checkout (Branch) uses: actions/checkout@v4 @@ -542,7 +550,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v4 with: - node-version: ${{ env.TGS_WEBPANEL_NODE_VERSION }} + node-version: ${{ env.TGS_NODE_VERSION }} - name: Checkout (Branch) uses: actions/checkout@v4 @@ -624,7 +632,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v4 with: - node-version: ${{ env.TGS_WEBPANEL_NODE_VERSION }} + node-version: ${{ env.TGS_NODE_VERSION }} - name: Set TGS_TEST_DUMP_API_SPEC if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Advanced' && matrix.database-type == 'SqlServer' }} @@ -882,7 +890,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v4 with: - node-version: ${{ env.TGS_WEBPANEL_NODE_VERSION }} + node-version: ${{ env.TGS_NODE_VERSION }} - name: Set Sqlite Connection Info if: ${{ matrix.database-type == 'Sqlite' }} @@ -1461,7 +1469,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v4 with: - node-version: ${{ env.TGS_WEBPANEL_NODE_VERSION }} + node-version: ${{ env.TGS_NODE_VERSION }} - name: Checkout (Branch) uses: actions/checkout@v4 @@ -2031,7 +2039,7 @@ jobs: - name: Setup Node.JS uses: actions/setup-node@v4 with: - node-version: ${{ env.TGS_WEBPANEL_NODE_VERSION }} + node-version: ${{ env.TGS_NODE_VERSION }} - name: Checkout uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 87e88b7691..8248dbc3c1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ artifacts/ /src/Tgstation.Server.Host/wwwroot /src/Tgstation.Server.Host/--applicationName /src/Tgstation.Server.Host/ClientApp +/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/.yarn +/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/node_modules +/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/schema.graphql /tools/Tgstation.Server.ReleaseNotes/release_notes.md launchSettings.json release_notes.md @@ -29,3 +32,4 @@ changelog.yml *.sqlite3 packaging/ yarn-error.log* +.graphqlrc.json diff --git a/README.md b/README.md index c0f5290a1b..6483ec7499 100644 --- a/README.md +++ b/README.md @@ -321,11 +321,19 @@ Create an `appsettings.Production.yml` file next to `appsettings.yml`. This will - `Swarm:UpdateRequiredNodeCount`: Should be set to the total number of servers in your swarm minus 1. Prevents updates from occurring unless the non-controller server count in the swarm is greater than or equal to this value. -- `Security:OAuth:`: Sets the OAuth client ID and secret for a given ``. The currently supported providers are `Keycloak`, `GitHub`, `Discord`, `InvisionCommunity` and `TGForums`. Setting these fields to `null` disables logins AND gateway auth with the provider, but does not stop users from associating their accounts using the API. Sample Entry: +- `Telemetry:DisableVersionReporting`: Prevents you installation and the version you're using from being reported on the source repository's deployments list + +- `Telemetry:ServerFriendlyName`: Prevents anonymous TGS version usage statistics from being sent to be displayed on the repository. + +- `Telemetry:VersionReportingRepositoryId`: The repository telemetry is sent to. For security reasons, this is not the main TGS repo. See the [tgstation-server-deployments](https://github.com/tgstation/tgstation-server-deployments) repository for more information. + +#### OAuth Configuration + +- `Security:OAuth:`: Sets the OAuth client ID and secret for a given ``. The currently supported providers are `GitHub`, `Discord`, `InvisionCommunity` and `TGForums`. Setting these fields to `null` disables logins AND gateway auth with the provider, but does not stop users from associating their accounts using the API. Sample Entry: ```yml Security: OAuth: - Keycloak: + InvisionCommunity: ClientId: "..." ClientSecret: "..." RedirectUrl: "..." @@ -336,22 +344,30 @@ Security: The following providers use the `RedirectUrl` setting: - GitHub -- TGForums -- Keycloak - InvisionCommunity The following providers use the `ServerUrl` setting: -- Keycloak - InvisionCommunity Gateway auth simply allows the users to authenticate with the service using the configuration you provide and have their impersonation token passed back to the client. An example of this is using GitHub gateway auth to allow clients to enumerate pull requests without getting rate limited. -- `Telemetry:DisableVersionReporting`: Prevents you installation and the version you're using from being reported on the source repository's deployments list +#### OpenID Connect Configuration +- `Security:Oidc:`: Sets up an OpenID Connect Provider with given "Scheme Key". Sample entry: +```yml +Security: + Oidc: + Example: + Authority: "..." # This is the root of the URL containing the "/.well-known/openid-configuration" endpoint + ClientId: "..." + ClientSecret: "..." + FriendlyName: "Example Provider" # Friendly name is how this provider is displayed in UIs + ThemeColour: "#ff0000" # (Optional) Hex color code indicating the color used to theme UI elements relating to this provider + ThemeIconUrl: "..." # (Optional) Public URL of an image that can be used to theme UI elements relating to this provider + UsernameClaim: "preferred_username" # (Optional) Name of claim used to set TGS usernames upon registration. By default "preferred_username" is used. Only applies when using OidcStrictMode. +``` -- `Telemetry:ServerFriendlyName`: Prevents anonymous TGS version usage statistics from being sent to be displayed on the repository. - -- `Telemetry:VersionReportingRepositoryId`: The repository telemetry is sent to. For security reasons, this is not the main TGS repo. See the [tgstation-server-deployments](https://github.com/tgstation/tgstation-server-deployments) repository for more information. +- `Security:OidcStrictMode`: Boolean flag that, when `true`, disables password and OAuth logins, password changes, individual permission set assignment, and enables user registration using OpenID Connect providers. The claim name `tgstation-server-group-id` is used to dictate what TGS group users are registered to. ### Database Configuration diff --git a/build/TestCommon.props b/build/TestCommon.props index 7d6476170b..c20f0d9001 100644 --- a/build/TestCommon.props +++ b/build/TestCommon.props @@ -18,9 +18,9 @@ - + - + diff --git a/build/Version.props b/build/Version.props index 1436ece2e9..e504a6475f 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,17 +3,17 @@ - 6.14.1 - 5.5.0 - 10.12.1 - 0.5.0 + 6.15.0 + 5.6.0 + 10.13.0 + 0.6.0 7.0.0 - 17.0.1 - 20.0.0 + 18.0.0 + 21.0.0 7.3.1 5.10.0 1.6.0 - 8.0.0 + 9.0.0 1.2.1 netstandard2.0 8 diff --git a/build/WebpanelVersion.props b/build/WebpanelVersion.props index d03002d880..2c3b54d0f1 100644 --- a/build/WebpanelVersion.props +++ b/build/WebpanelVersion.props @@ -1,6 +1,6 @@ - 6.7.3 + 6.9.0 diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index 37a61e55fb..ea2c9ebd67 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -584,7 +584,7 @@ namespace Tgstation.Server.Api.Models /// Attempted to set for the admin user. /// [Description("The admin user cannot use OAuth connections!")] - AdminUserCannotOAuth, + AdminUserCannotHaveServiceConnection, /// /// Attempted to login with a disabled OAuth provider. @@ -669,5 +669,11 @@ namespace Tgstation.Server.Api.Models /// [Description("GraphQL swarm remote gateways not implemented!")] RemoteGatewaysNotImplemented, + + /// + /// Attempted to edit a user security property which is locked down due to the server being in . + /// + [Description("One or more of the fields requested to be updated cannot be due to the server being in OIDC strict mode.")] + BadUserEditDueToOidcStrictMode, } } diff --git a/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs index 5151a1e1bd..cb2158b461 100644 --- a/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs @@ -10,6 +10,11 @@ namespace Tgstation.Server.Api.Models.Internal /// public ICollection? OAuthConnections { get; set; } + /// + /// List of s associated with the user. + /// + public ICollection? OidcConnections { get; set; } + /// /// The directly associated with the user. /// diff --git a/src/Tgstation.Server.Api/Models/OAuthProvider.cs b/src/Tgstation.Server.Api/Models/OAuthProvider.cs index 0581cf3369..5c228ee530 100644 --- a/src/Tgstation.Server.Api/Models/OAuthProvider.cs +++ b/src/Tgstation.Server.Api/Models/OAuthProvider.cs @@ -1,10 +1,12 @@ -using Newtonsoft.Json; +using System; + +using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace Tgstation.Server.Api.Models { /// - /// List of OAuth providers supported by TGS. + /// List of OAuth2.0 providers supported by TGS that do not support OIDC. /// [JsonConverter(typeof(StringEnumConverter))] public enum OAuthProvider @@ -20,13 +22,15 @@ namespace Tgstation.Server.Api.Models Discord, /// - /// https://tgstation13.org. + /// Pre-hostening https://tgstation13.org. No longer supported. /// + [Obsolete("tgstation13.org no longer has a custom OAuth solution. This option will be removed in a future TGS version.")] TGForums, /// /// https://www.keycloak.org. /// + [Obsolete("This should now be implemented as an OIDC provider. This option will be removed in a future TGS version.")] Keycloak, /// diff --git a/src/Tgstation.Server.Api/Models/OidcConnection.cs b/src/Tgstation.Server.Api/Models/OidcConnection.cs new file mode 100644 index 0000000000..57a0c16c93 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/OidcConnection.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; + +using Newtonsoft.Json; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a valid OIDC connection. + /// + public class OidcConnection + { + /// + /// The of the . + /// ] + [Required] + [RequestOptions(FieldPresence.Required)] + [StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)] + public string? SchemeKey { get; set; } + + /// + /// The ID of the user in the OIDC proivder ("sub" claim). + /// + [Required] + [RequestOptions(FieldPresence.Required)] + [StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)] + public string? ExternalUserId { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/OidcProviderInfo.cs b/src/Tgstation.Server.Api/Models/OidcProviderInfo.cs new file mode 100644 index 0000000000..2bbf378327 --- /dev/null +++ b/src/Tgstation.Server.Api/Models/OidcProviderInfo.cs @@ -0,0 +1,30 @@ +using System; + +namespace Tgstation.Server.Api.Models +{ + /// + /// Represents a configured OIDC provider. + /// + public sealed class OidcProviderInfo + { + /// + /// The scheme key used to reference the . Unique amongst providers. + /// + public string? SchemeKey { get; set; } + + /// + /// The provider's name as it should be displayed to the user. + /// + public string? FriendlyName { get; set; } + + /// + /// Colour that should be used to theme this OIDC provider. + /// + public string? ThemeColour { get; set; } + + /// + /// Image URL that should be used to theme this OIDC provider. + /// + public Uri? ThemeIconUrl { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs b/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs index 82f27e7e7c..ea5497acfc 100644 --- a/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs +++ b/src/Tgstation.Server.Api/Models/Response/ServerInformationResponse.cs @@ -36,6 +36,16 @@ namespace Tgstation.Server.Api.Models.Response /// public Dictionary? OAuthProviderInfos { get; set; } + /// + /// List of configured s. + /// + public List? OidcProviderInfos { get; set; } + + /// + /// If only OIDC logins and registration is allowed. + /// + public bool OidcStrictMode { get; set; } + /// /// If there is a server update in progress. /// diff --git a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs index ed530eee44..a0360aaf1a 100644 --- a/src/Tgstation.Server.Api/Rights/AdministrationRights.cs +++ b/src/Tgstation.Server.Api/Rights/AdministrationRights.cs @@ -44,9 +44,9 @@ namespace Tgstation.Server.Api.Rights DownloadLogs = 1 << 5, /// - /// User can modify their own . + /// User can modify their own and . /// - EditOwnOAuthConnections = 1 << 6, + EditOwnServiceConnections = 1 << 6, /// /// User can upgrade/downgrade TGS using file uploads through the API. diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 52412e6e78..adbc1a568d 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserFromOAuthConnection.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserFromOAuthConnection.graphql index 9d374b5ff0..dc69b9ef29 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserFromOAuthConnection.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserFromOAuthConnection.graphql @@ -1,5 +1,5 @@ mutation CreateUserFromOAuthConnection($name: String!, $oAuthConnections: [OAuthConnectionInput!]!) { - createUserByOAuthAndPermissionSet(input: { name: $name, oAuthConnections: $oAuthConnections }) { + createUserByServiceConnectionAndPermissionSet(input: { name: $name, oAuthConnections: $oAuthConnections }) { errors { ... on ErrorMessageError { additionalData diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroup.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroup.graphql index 2d8489ecb6..440994e631 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroup.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroup.graphql @@ -7,7 +7,7 @@ mutation CreateUserGroup($name: String!) { administrationRights { canChangeVersion canDownloadLogs - canEditOwnOAuthConnections + canEditOwnServiceConnections canEditOwnPassword canReadUsers canRestartHost diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroupWithInstanceListPerm.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroupWithInstanceListPerm.graphql index 6a99c7ede0..c4f88b6770 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroupWithInstanceListPerm.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/CreateUserGroupWithInstanceListPerm.graphql @@ -7,7 +7,7 @@ mutation CreateUserGroupWithInstanceListPerm($name: String!) { administrationRights { canChangeVersion canDownloadLogs - canEditOwnOAuthConnections + canEditOwnServiceConnections canEditOwnPassword canReadUsers canRestartHost diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetFullPermsOnUserGroup.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetFullPermsOnUserGroup.graphql index 1766cb4149..8c77199901 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetFullPermsOnUserGroup.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetFullPermsOnUserGroup.graphql @@ -6,7 +6,7 @@ mutation SetFullPermsOnUserGroup($id: ID!) { administrationRights: { canChangeVersion: true canDownloadLogs: true - canEditOwnOAuthConnections: true + canEditOwnServiceConnections: true canEditOwnPassword: true canReadUsers: true canWriteUsers: true @@ -36,7 +36,7 @@ mutation SetFullPermsOnUserGroup($id: ID!) { administrationRights { canChangeVersion canDownloadLogs - canEditOwnOAuthConnections + canEditOwnServiceConnections canEditOwnPassword canReadUsers canRestartHost diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserGroup.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserGroup.graphql index d68865e550..4299d5b537 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserGroup.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserGroup.graphql @@ -10,27 +10,27 @@ mutation SetUserGroup($id: ID!, $newGroupId: ID!) { user { ownedPermissionSet { instanceManagerRights { - canCreate - canDelete - canGrantPermissions - canList - canRead - canRelocate - canRename - canSetAutoUpdate - canSetChatBotLimit - canSetConfiguration - canSetOnline + canCreate + canDelete + canGrantPermissions + canList + canRead + canRelocate + canRename + canSetAutoUpdate + canSetChatBotLimit + canSetConfiguration + canSetOnline } administrationRights { - canChangeVersion - canDownloadLogs - canEditOwnOAuthConnections - canEditOwnPassword - canReadUsers - canRestartHost - canUploadVersion - canWriteUsers + canChangeVersion + canDownloadLogs + canEditOwnServiceConnections + canEditOwnPassword + canReadUsers + canRestartHost + canUploadVersion + canWriteUsers } } group { diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserPermissionSet.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserPermissionSet.graphql index a1dbcb24e6..fa2fa558fe 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserPermissionSet.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/SetUserPermissionSet.graphql @@ -12,7 +12,7 @@ mutation SetUserPermissionSet($id: ID!, $permissionSet: PermissionSetInput!) { administrationRights { canChangeVersion canDownloadLogs - canEditOwnOAuthConnections + canEditOwnServiceConnections canEditOwnPassword canReadUsers canRestartHost @@ -37,7 +37,7 @@ mutation SetUserPermissionSet($id: ID!, $permissionSet: PermissionSetInput!) { administrationRights { canChangeVersion canDownloadLogs - canEditOwnOAuthConnections + canEditOwnServiceConnections canEditOwnPassword canReadUsers canRestartHost diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetSomeGroupInfo.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetSomeGroupInfo.graphql index f36dee754a..5d37e33b07 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetSomeGroupInfo.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetSomeGroupInfo.graphql @@ -20,7 +20,7 @@ query GetSomeGroupInfo($id: ID!) { administrationRights { canChangeVersion canDownloadLogs - canEditOwnOAuthConnections + canEditOwnServiceConnections canEditOwnPassword canReadUsers canRestartHost diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ReadCurrentUser.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ReadCurrentUser.graphql index dea2849d19..79dfa3e5ef 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ReadCurrentUser.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/ReadCurrentUser.graphql @@ -20,7 +20,7 @@ query ReadCurrentUser { administrationRights { canChangeVersion canDownloadLogs - canEditOwnOAuthConnections + canEditOwnServiceConnections canEditOwnPassword canReadUsers canRestartHost diff --git a/src/Tgstation.Server.Client.GraphQL/Tgstation.Server.Client.GraphQL.csproj b/src/Tgstation.Server.Client.GraphQL/Tgstation.Server.Client.GraphQL.csproj index 19901867f0..01d7ff1769 100644 --- a/src/Tgstation.Server.Client.GraphQL/Tgstation.Server.Client.GraphQL.csproj +++ b/src/Tgstation.Server.Client.GraphQL/Tgstation.Server.Client.GraphQL.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 898b10b2f2..384bd5fda3 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -11,9 +11,9 @@ - + - + diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj index c6fd7418cb..fb8a525a78 100644 --- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj +++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj @@ -13,9 +13,9 @@ - + - + diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index 6af556b1fc..07863a2111 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -23,19 +23,19 @@ - + - + - + diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/.graphqlrc.json b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/.graphqlrc.json new file mode 100644 index 0000000000..3ed674432b --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/.graphqlrc.json @@ -0,0 +1,21 @@ +{ + "schema": "schema.graphql", + "documents": "**/*.graphql", + "extensions": { + "strawberryShake": { + "name": "GraphQLClient", + "url": "../../artifacts/gitlab-api.graphql", + "namespace": "Tgstation.Server.Host.Utils.GitLab.GraphQL", + "records": { + "inputs": false, + "entities": false + }, + "transportProfiles": [ + { + "default": "Http", + "subscription": "Http" + } + ] + } + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/.yarnrc.yml b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/.yarnrc.yml new file mode 100644 index 0000000000..007e45b347 --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/.yarnrc.yml @@ -0,0 +1,15 @@ +compressionLevel: mixed + +enableGlobalCache: false + +enableScripts: false + +logFilters: + - code: YN0004 + level: discard + - code: YN0062 + level: discard + +nodeLinker: node-modules + +preferInteractive: true diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Mutations/CreateNote.graphql b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Mutations/CreateNote.graphql new file mode 100644 index 0000000000..53f7647942 --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Mutations/CreateNote.graphql @@ -0,0 +1,12 @@ +mutation CreateNote($id: NoteableID!, $body: String!) { + createNote(input: { noteableId: $id, body: $body }) { + note { + id + body + discussion { + id + } + } + errors + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Mutations/ModifyNote.graphql b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Mutations/ModifyNote.graphql new file mode 100644 index 0000000000..827bf550c0 --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Mutations/ModifyNote.graphql @@ -0,0 +1,9 @@ +mutation ModifyNote($id: NoteID!, $body: String!) { + updateNote(input: { id: $id, body: $body }) { + note { + id + body + } + errors + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetCurrentUser.graphql b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetCurrentUser.graphql new file mode 100644 index 0000000000..c03c5ccef7 --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetCurrentUser.graphql @@ -0,0 +1,6 @@ +query GetCurrentUser { + currentUser + { + username + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequest.graphql b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequest.graphql new file mode 100644 index 0000000000..8b3984e398 --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequest.graphql @@ -0,0 +1,14 @@ +query GetMergeRequest($project: ID!, $number: String!) { + project(fullPath: $project) { + mergeRequest(iid: $number) { + author { username } + description + title + diffHeadSha + mergeCommitSha + webUrl + iid + id + } + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequestNotes.graphql b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequestNotes.graphql new file mode 100644 index 0000000000..7228e4827a --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequestNotes.graphql @@ -0,0 +1,15 @@ +query GetMergeRequestNotes($project: ID!, $number: String!) { + project(fullPath: $project) { + mergeRequest(iid: $number) { + iid + id + notes { + nodes { + author { username } + body + id + } + } + } + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequests.graphql b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequests.graphql new file mode 100644 index 0000000000..0fbf2fe80f --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GQL/Queries/GetMergeRequests.graphql @@ -0,0 +1,14 @@ +query GetMergeRequests($project: ID!, $numbers: [String!]!) { + project(fullPath: $project) { + mergeRequests(iids: $numbers) { + nodes { + state + diffHeadSha + mergeCommitSha + closedAt + iid + id + } + } + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GraphQLGitLabClient.cs b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GraphQLGitLabClient.cs new file mode 100644 index 0000000000..eb57b82799 --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GraphQLGitLabClient.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.Extensions.DependencyInjection; + +namespace Tgstation.Server.Host.Utils.GitLab.GraphQL +{ + /// + sealed class GraphQLGitLabClient : IGraphQLGitLabClient + { + /// + public IGraphQLClient GraphQL { get; } + + /// + /// The containing the client. + /// + readonly ServiceProvider serviceProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + public GraphQLGitLabClient(ServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + GraphQL = serviceProvider.GetService() ?? throw new ArgumentException($"Expected an {nameof(IGraphQLClient)} service in the provider!", nameof(serviceProvider)); + } + + /// + public ValueTask DisposeAsync() + => serviceProvider.DisposeAsync(); + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GraphQLGitLabClientFactory.cs b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GraphQLGitLabClientFactory.cs new file mode 100644 index 0000000000..d12c2b7dee --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/GraphQLGitLabClientFactory.cs @@ -0,0 +1,49 @@ +using System; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +using Microsoft.Extensions.DependencyInjection; + +namespace Tgstation.Server.Host.Utils.GitLab.GraphQL +{ + /// + /// Factory for creating s. + /// + public sealed class GraphQLGitLabClientFactory + { + /// + /// Sets up a . + /// + /// The token to use for authentication, if any. + /// A resulting in a new . + public static async ValueTask CreateClient(string? bearerToken = null) + { + var serviceCollection = new ServiceCollection(); + + var clientBuilder = serviceCollection + .AddGraphQLClient(); + var graphQLEndpoint = new Uri("https://gitlab.com/api/graphql"); + + clientBuilder.ConfigureHttpClient( + client => + { + client.BaseAddress = new Uri("https://gitlab.com/api/graphql"); + if (bearerToken != null) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken); + } + }); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + try + { + return new GraphQLGitLabClient(serviceProvider); + } + catch + { + await serviceProvider.DisposeAsync(); + throw; + } + } + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/IGraphQLGitLabClient.cs b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/IGraphQLGitLabClient.cs new file mode 100644 index 0000000000..6a28de8227 --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/IGraphQLGitLabClient.cs @@ -0,0 +1,15 @@ +using System; + +namespace Tgstation.Server.Host.Utils.GitLab.GraphQL +{ + /// + /// Wrapper for using a GitLab . + /// + public interface IGraphQLGitLabClient : IAsyncDisposable + { + /// + /// Gets the underlying . + /// + IGraphQLClient GraphQL { get; } + } +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/Tgstation.Server.Host.Utils.GitLab.GraphQL.csproj b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/Tgstation.Server.Host.Utils.GitLab.GraphQL.csproj new file mode 100644 index 0000000000..03b3f8fb73 --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/Tgstation.Server.Host.Utils.GitLab.GraphQL.csproj @@ -0,0 +1,49 @@ + + + + + $(TgsFrameworkVersion) + $(TgsCoreVersion) + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + $(IntermediateOutputPath)berry/GraphQLClient.Client.cs + $(IntermediateOutputPath)berry/GraphQLClient.Client.cs + + + + + + + + + + + + + + diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/package.json b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/package.json new file mode 100644 index 0000000000..79610fdd4e --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/package.json @@ -0,0 +1,7 @@ +{ + "license": "AGPL-3.0-only", + "devDependencies": { + "apollo": "^2.34.0" + }, + "packageManager": "yarn@4.7.0" +} diff --git a/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/yarn.lock b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/yarn.lock new file mode 100644 index 0000000000..d1d93cd55d --- /dev/null +++ b/src/Tgstation.Server.Host.Utils.GitLab.GraphQL/yarn.lock @@ -0,0 +1,4531 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10 + +"@apollo/federation@npm:0.27.0": + version: 0.27.0 + resolution: "@apollo/federation@npm:0.27.0" + dependencies: + apollo-graphql: "npm:^0.9.3" + lodash.xorby: "npm:^4.7.0" + peerDependencies: + graphql: ^14.5.0 || ^15.0.0 + checksum: 10/fcec6e6c5cd358fcab3d1830e5bf4641960024529a2fca07ae5bc0ee4740afe3a0dfa5c93dc4a9d232b020dc7a380a2aec4c6afc640df3140235e4c572ddacaf + languageName: node + linkType: hard + +"@apollo/utils.keyvaluecache@npm:^1.0.1": + version: 1.0.2 + resolution: "@apollo/utils.keyvaluecache@npm:1.0.2" + dependencies: + "@apollo/utils.logger": "npm:^1.0.0" + lru-cache: "npm:7.10.1 - 7.13.1" + checksum: 10/353794482ad8c476e36c2152d6a647244a8cffbbc26a9b2b28986b3651aaff16b73df1dfed9edc8eb151fe7bd4c59d06b3b1b4c6b1aa516fceb8119a46fa8f72 + languageName: node + linkType: hard + +"@apollo/utils.logger@npm:^1.0.0": + version: 1.0.1 + resolution: "@apollo/utils.logger@npm:1.0.1" + checksum: 10/621bd80ce43a73f97342568b712fd46fee9041212d4c7264a63676e29d17ab292773c3c21b91f8a2dffb1fe7931ece3954886bd04e3100e1765c6d05e231e2a7 + languageName: node + linkType: hard + +"@apollographql/apollo-tools@npm:0.5.4, @apollographql/apollo-tools@npm:^0.5.4": + version: 0.5.4 + resolution: "@apollographql/apollo-tools@npm:0.5.4" + peerDependencies: + graphql: ^14.2.1 || ^15.0.0 || ^16.0.0 + checksum: 10/4f69566d23ffb77ffedd87c679dcab608400f297e4cd5423151977b917737c427015485a8e0436feeb5154574171742ab626fb1a8f5ae2739070757976fd49f2 + languageName: node + linkType: hard + +"@apollographql/graphql-language-service-interface@npm:^2.0.2": + version: 2.0.2 + resolution: "@apollographql/graphql-language-service-interface@npm:2.0.2" + dependencies: + "@apollographql/graphql-language-service-parser": "npm:^2.0.0" + "@apollographql/graphql-language-service-types": "npm:^2.0.0" + "@apollographql/graphql-language-service-utils": "npm:^2.0.2" + peerDependencies: + graphql: ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 + checksum: 10/1c7a8260a7cc93e84f513b8c7f03f156bb3719d9b52f0ef7a1cf58c118e9fe6c8db2c2ff60ad3d5ecf16d222bd83dceee806e62df0304538cbc80af5a55db134 + languageName: node + linkType: hard + +"@apollographql/graphql-language-service-parser@npm:^2.0.0": + version: 2.0.2 + resolution: "@apollographql/graphql-language-service-parser@npm:2.0.2" + dependencies: + "@apollographql/graphql-language-service-types": "npm:^2.0.0" + peerDependencies: + graphql: ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 + checksum: 10/47b5507b72951f030739592666b3957c7f12a36333b9f89669e152ddea86f5a064813168e08ac81c5a74e3f5bf43aa10fdadec3d4597ddc0680cd97b388729a6 + languageName: node + linkType: hard + +"@apollographql/graphql-language-service-types@npm:^2.0.0": + version: 2.0.2 + resolution: "@apollographql/graphql-language-service-types@npm:2.0.2" + peerDependencies: + graphql: ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 + checksum: 10/d2648de9775bb70920ad5cc6c77ba1f331020c4118a5863e3b163348c6c1cbca4f742611a1ec6a0064a9413260675e37c8b2544d02e4c2a525caf893780360f6 + languageName: node + linkType: hard + +"@apollographql/graphql-language-service-utils@npm:^2.0.2": + version: 2.0.2 + resolution: "@apollographql/graphql-language-service-utils@npm:2.0.2" + dependencies: + "@apollographql/graphql-language-service-types": "npm:^2.0.0" + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 + checksum: 10/ace741509b992d402adcc4fa7cbfcea37aa0f5c62b6eb64f20427449428ebf6c25be0e3f939fc8173191a0e76a32d0d308113e3e2c8ed031a37847e2114712b3 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0": + version: 7.26.2 + resolution: "@babel/code-frame@npm:7.26.2" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.25.9" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10/db2c2122af79d31ca916755331bb4bac96feb2b334cdaca5097a6b467fdd41963b89b14b6836a14f083de7ff887fc78fa1b3c10b14e743d33e12dbfe5ee3d223 + languageName: node + linkType: hard + +"@babel/generator@npm:7.17.10": + version: 7.17.10 + resolution: "@babel/generator@npm:7.17.10" + dependencies: + "@babel/types": "npm:^7.17.10" + "@jridgewell/gen-mapping": "npm:^0.1.0" + jsesc: "npm:^2.5.1" + checksum: 10/32fa924433681d8122aa30a0833d4b25d400d075d143920f559352c066db4e656362d1863cfce316034d424f6345df8a64909cb2a8386e96198cc0ca18d6c4aa + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-string-parser@npm:7.25.9" + checksum: 10/c28656c52bd48e8c1d9f3e8e68ecafd09d949c57755b0d353739eb4eae7ba4f7e67e92e4036f1cd43378cc1397a2c943ed7bcaf5949b04ab48607def0258b775 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-identifier@npm:7.25.9" + checksum: 10/3f9b649be0c2fd457fa1957b694b4e69532a668866b8a0d81eabfa34ba16dbf3107b39e0e7144c55c3c652bf773ec816af8df4a61273a2bb4eb3145ca9cf478e + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.3": + version: 7.26.10 + resolution: "@babel/parser@npm:7.26.10" + dependencies: + "@babel/types": "npm:^7.26.10" + bin: + parser: ./bin/babel-parser.js + checksum: 10/3f87781f46795ba72448168061d9e99c394fdf9cd4aa3ddf053a06334247da4d25d0923ccc89195937d3360d384cee181e99711763c1e8fe81d4f17ee22541fc + languageName: node + linkType: hard + +"@babel/types@npm:7.17.10": + version: 7.17.10 + resolution: "@babel/types@npm:7.17.10" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.16.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10/e128cc776b7c7e48884c0c3665f475cb8432a4404f2fc10ab7edc831998ed6ec423411191171a72a1e08058c7d8faa5b535b5cc9c3bb2677fe35ac3505792045 + languageName: node + linkType: hard + +"@babel/types@npm:^7.17.10, @babel/types@npm:^7.26.10": + version: 7.26.10 + resolution: "@babel/types@npm:7.26.10" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10/6b4f24ee77af853c2126eaabb65328cd44a7d6f439685131cf929c30567e01b6ea2e5d5653b2c304a09c63a5a6199968f0e27228a007acf35032036d79a9dee6 + languageName: node + linkType: hard + +"@endemolshinegroup/cosmiconfig-typescript-loader@npm:^3.0.2": + version: 3.0.2 + resolution: "@endemolshinegroup/cosmiconfig-typescript-loader@npm:3.0.2" + dependencies: + lodash.get: "npm:^4" + make-error: "npm:^1" + ts-node: "npm:^9" + tslib: "npm:^2" + peerDependencies: + cosmiconfig: ">=6" + checksum: 10/a7f5d9e4e262338833f07810fe602dfaf179bd3241aa1d9da2d24a2bfd7df1c43841f2b8b9d4f0cc9e0ef672a71e853a312c80f5d125d707bcb43c0d3fefce35 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.1.0": + version: 0.1.1 + resolution: "@jridgewell/gen-mapping@npm:0.1.1" + dependencies: + "@jridgewell/set-array": "npm:^1.0.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: 10/ba76fae1d8ea52b181474518c705a8eac36405dfc836fb07e9c25730a84d29e05fd6d954f121057742639f3128a24ea45d205c9c989efd464d1114671c19fa6c + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.0.0": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10/832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10/4ed6123217569a1484419ac53f6ea0d9f3b57e5b57ab30d7c267bdb27792a27eb0e4b08e84a2680aa55cc2f2b411ffd6ec3db01c44fdc6dc43aca4b55f8374fd + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 + languageName: node + linkType: hard + +"@oclif/color@npm:^1.0.0, @oclif/color@npm:^1.0.1": + version: 1.0.13 + resolution: "@oclif/color@npm:1.0.13" + dependencies: + ansi-styles: "npm:^4.2.1" + chalk: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + supports-color: "npm:^8.1.1" + tslib: "npm:^2" + checksum: 10/885a6ba4a7d296fef559ba1a7f04a6e67dba92d7aeb46dd23b18d99551d3716710c077d2f3180ff0a4a6d18998b920f723b92865bcd21970ae03dbaff57ba480 + languageName: node + linkType: hard + +"@oclif/command@npm:1.8.16": + version: 1.8.16 + resolution: "@oclif/command@npm:1.8.16" + dependencies: + "@oclif/config": "npm:^1.18.2" + "@oclif/errors": "npm:^1.3.5" + "@oclif/help": "npm:^1.0.1" + "@oclif/parser": "npm:^3.8.6" + debug: "npm:^4.1.1" + semver: "npm:^7.3.2" + peerDependencies: + "@oclif/config": ^1 + checksum: 10/a276315eeda8b375fd7958c95a8757795f4fcfb59eabc88b00254dd40c441b018e39204aff2a80dfa708fc54f2643c2aef846ab7f7f20c641919fc5586f133da + languageName: node + linkType: hard + +"@oclif/config@npm:1.18.16": + version: 1.18.16 + resolution: "@oclif/config@npm:1.18.16" + dependencies: + "@oclif/errors": "npm:^1.3.6" + "@oclif/parser": "npm:^3.8.16" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-wsl: "npm:^2.1.1" + tslib: "npm:^2.6.1" + checksum: 10/f3985ecf92980063258f2c1f9c2791f228c033aeb6cd424cd857d920d2776a25484dbd3b9f9e71ec38399e438d5f5e7b8f616fe7fab3721fc1f6a203aa438781 + languageName: node + linkType: hard + +"@oclif/config@npm:1.18.3": + version: 1.18.3 + resolution: "@oclif/config@npm:1.18.3" + dependencies: + "@oclif/errors": "npm:^1.3.5" + "@oclif/parser": "npm:^3.8.0" + debug: "npm:^4.1.1" + globby: "npm:^11.0.1" + is-wsl: "npm:^2.1.1" + tslib: "npm:^2.3.1" + checksum: 10/8a414a6abf3bf604f7fe94fe2d46331a2f5debafc77f8f1837641b13221642adebd598b70f69be639051852bfab84aa3b3e51fac8639873a74f8f43cb4434c67 + languageName: node + linkType: hard + +"@oclif/config@npm:^1.18.2": + version: 1.18.17 + resolution: "@oclif/config@npm:1.18.17" + dependencies: + "@oclif/errors": "npm:^1.3.6" + "@oclif/parser": "npm:^3.8.17" + debug: "npm:^4.3.4" + globby: "npm:^11.1.0" + is-wsl: "npm:^2.1.1" + tslib: "npm:^2.6.1" + checksum: 10/ba78114673e22860c6444e6950e4bf3e7266015353cc8acec253556ca44eeb0245ab3a44d14561ebb99bc2cb91e664d5eceba231090f701f922800c7494fa2b4 + languageName: node + linkType: hard + +"@oclif/core@npm:^1.0.8, @oclif/core@npm:^1.1.1, @oclif/core@npm:^1.2.0, @oclif/core@npm:^1.2.1, @oclif/core@npm:^1.3.6, @oclif/core@npm:^1.7.0": + version: 1.26.2 + resolution: "@oclif/core@npm:1.26.2" + dependencies: + "@oclif/linewrap": "npm:^1.0.0" + "@oclif/screen": "npm:^3.0.4" + ansi-escapes: "npm:^4.3.2" + ansi-styles: "npm:^4.3.0" + cardinal: "npm:^2.1.1" + chalk: "npm:^4.1.2" + clean-stack: "npm:^3.0.1" + cli-progress: "npm:^3.10.0" + debug: "npm:^4.3.4" + ejs: "npm:^3.1.6" + fs-extra: "npm:^9.1.0" + get-package-type: "npm:^0.1.0" + globby: "npm:^11.1.0" + hyperlinker: "npm:^1.0.0" + indent-string: "npm:^4.0.0" + is-wsl: "npm:^2.2.0" + js-yaml: "npm:^3.14.1" + natural-orderby: "npm:^2.0.3" + object-treeify: "npm:^1.1.33" + password-prompt: "npm:^1.1.2" + semver: "npm:^7.3.7" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + supports-color: "npm:^8.1.1" + supports-hyperlinks: "npm:^2.2.0" + tslib: "npm:^2.4.1" + widest-line: "npm:^3.1.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10/1d4d1e1914e6ef673e7dd36a674b4ac9d40cf08ad09233d908e61c75e4fdb1dbf7f2c8cde4c9b8c7a64c23ff81e56b1fd2e0b0bd2cfcb8f1422b12828f7b9255 + languageName: node + linkType: hard + +"@oclif/errors@npm:1.3.5": + version: 1.3.5 + resolution: "@oclif/errors@npm:1.3.5" + dependencies: + clean-stack: "npm:^3.0.0" + fs-extra: "npm:^8.1" + indent-string: "npm:^4.0.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10/bac7aa3fc4f9a08588453ca365962499e9ae9a2e0638dd65c3875db3136c82335041e9938a0adf461eb5d350023efd386a8ca8d31f74a2bf6eac4cac77ec4cc8 + languageName: node + linkType: hard + +"@oclif/errors@npm:1.3.6, @oclif/errors@npm:^1.3.5, @oclif/errors@npm:^1.3.6": + version: 1.3.6 + resolution: "@oclif/errors@npm:1.3.6" + dependencies: + clean-stack: "npm:^3.0.0" + fs-extra: "npm:^8.1" + indent-string: "npm:^4.0.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10/6240bd1f1893ea93b0efec1bb33865aa51ccf4feddb798a0596b497d6afaa927232a475ca618ab561685a82fb44327bc29bdceba7124ebf5ca8544972907c154 + languageName: node + linkType: hard + +"@oclif/help@npm:^1.0.1": + version: 1.0.15 + resolution: "@oclif/help@npm:1.0.15" + dependencies: + "@oclif/config": "npm:1.18.16" + "@oclif/errors": "npm:1.3.6" + chalk: "npm:^4.1.2" + indent-string: "npm:^4.0.0" + lodash: "npm:^4.17.21" + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + widest-line: "npm:^3.1.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10/5281fa9dba7f414197ad406543b8c431637c9a1fd08a229ed466eca6a8a4a8c4d6b593e339c137bcc6dbd95b1e8b46d4e9a775988c5c0e5b34828a88b069839e + languageName: node + linkType: hard + +"@oclif/linewrap@npm:^1.0.0": + version: 1.0.0 + resolution: "@oclif/linewrap@npm:1.0.0" + checksum: 10/210edd1aac4a0a2b68ec71a2b62e5c3a15f88ac0d5af31aae126fff1d147921c5de06d611999d4958699bafe1298cad9f62a9ff45fc55e4846bf35fcc7a6f331 + languageName: node + linkType: hard + +"@oclif/parser@npm:^3.8.0, @oclif/parser@npm:^3.8.16, @oclif/parser@npm:^3.8.17, @oclif/parser@npm:^3.8.6": + version: 3.8.17 + resolution: "@oclif/parser@npm:3.8.17" + dependencies: + "@oclif/errors": "npm:^1.3.6" + "@oclif/linewrap": "npm:^1.0.0" + chalk: "npm:^4.1.0" + tslib: "npm:^2.6.2" + checksum: 10/6ac2afa1094ad150ed77d2eb1cdb4233552c3c3c0c9e657e029714d1e4523fa1f717b13ebc0aca16f681a648282378d874b2650340dd9817540efa542d4fa0bf + languageName: node + linkType: hard + +"@oclif/plugin-autocomplete@npm:1.3.0": + version: 1.3.0 + resolution: "@oclif/plugin-autocomplete@npm:1.3.0" + dependencies: + "@oclif/core": "npm:^1.7.0" + chalk: "npm:^4.1.0" + debug: "npm:^4.3.4" + fs-extra: "npm:^9.0.1" + checksum: 10/2aa61f820e5e9a1011066a35ecc28f071541786865506fa310b58b4f44eff313222a7c4599921a3030681e494b0d117a0fb03a83d605e8edc19cf0393716b45f + languageName: node + linkType: hard + +"@oclif/plugin-help@npm:5.1.12": + version: 5.1.12 + resolution: "@oclif/plugin-help@npm:5.1.12" + dependencies: + "@oclif/core": "npm:^1.3.6" + checksum: 10/595d4459bd5ab7e4a24c0ca3667ef652a32a8253570a38e19d8c24be24d09b7eb3018a4c4825ce43b5cc6224a4b3f73a5eecdabf07587e017b0ddf38557b04d3 + languageName: node + linkType: hard + +"@oclif/plugin-not-found@npm:2.3.1": + version: 2.3.1 + resolution: "@oclif/plugin-not-found@npm:2.3.1" + dependencies: + "@oclif/color": "npm:^1.0.0" + "@oclif/core": "npm:^1.2.1" + fast-levenshtein: "npm:^3.0.0" + lodash: "npm:^4.17.21" + checksum: 10/1b19d792eb72a40c7f205a3bd988ab2efad91812f8184a68e95928288fd2a2b79d86062b8409cbe491f73af8997dd6823f6e16aa840be3dafb8c15ed3db8316b + languageName: node + linkType: hard + +"@oclif/plugin-plugins@npm:2.1.0": + version: 2.1.0 + resolution: "@oclif/plugin-plugins@npm:2.1.0" + dependencies: + "@oclif/color": "npm:^1.0.1" + "@oclif/core": "npm:^1.2.0" + chalk: "npm:^4.1.2" + debug: "npm:^4.1.0" + fs-extra: "npm:^9.0" + http-call: "npm:^5.2.2" + load-json-file: "npm:^5.3.0" + npm-run-path: "npm:^4.0.1" + semver: "npm:^7.3.2" + tslib: "npm:^2.0.0" + yarn: "npm:^1.22.17" + checksum: 10/019af0a52c915e223b40eaae2f77ad5451c5e554626af99ad071dc836cb658c98db86ce387538b0bd0ca9901ae96c2c1a4aff1acd121e4834e9479ca86a98674 + languageName: node + linkType: hard + +"@oclif/plugin-warn-if-update-available@npm:2.0.4": + version: 2.0.4 + resolution: "@oclif/plugin-warn-if-update-available@npm:2.0.4" + dependencies: + "@oclif/core": "npm:^1.0.8" + chalk: "npm:^4.1.0" + debug: "npm:^4.1.0" + fs-extra: "npm:^9.0.1" + http-call: "npm:^5.2.2" + lodash: "npm:^4.17.21" + semver: "npm:^7.3.2" + checksum: 10/5318a6666a8e47cba5cff0195bb38e13de361ee9d9d3e3ce05e49f7d168e3bab5138d8f657c575cb512e9a1f83c226b095ac20fc4424e60e710aaf6153d2431d + languageName: node + linkType: hard + +"@oclif/screen@npm:^1.0.4 ": + version: 1.0.4 + resolution: "@oclif/screen@npm:1.0.4" + checksum: 10/fb0027fe8c46c68a14258f7c87add28258844faf51033d5c5546be39747d64ef043feb71cfd71b0576d1c9e00892ca5aed2e280419d072177645962564609d53 + languageName: node + linkType: hard + +"@oclif/screen@npm:^3.0.4": + version: 3.0.8 + resolution: "@oclif/screen@npm:3.0.8" + checksum: 10/6a93c701b4daed2f17f1d7149d8accc3c4294ee1c2741e0ba9a039eeed5db6ddf2a182a3ea36cf9dc47a1875d073f053a32f2b19c8b81165dc855b2f47d86071 + languageName: node + linkType: hard + +"@samverschueren/stream-to-observable@npm:^0.3.0": + version: 0.3.1 + resolution: "@samverschueren/stream-to-observable@npm:0.3.1" + dependencies: + any-observable: "npm:^0.3.0" + peerDependenciesMeta: + rxjs: + optional: true + zen-observable: + optional: true + checksum: 10/2b62bff492d58b4fdc8339ecc29ac3d8e1c37ae920c9d41fcb490a574422c3df1eae26b07103198b97b586c5e7106d47440ce24580a2a919aa5f9359d9914f2c + languageName: node + linkType: hard + +"@types/node-fetch@npm:^2.5.10": + version: 2.6.12 + resolution: "@types/node-fetch@npm:2.6.12" + dependencies: + "@types/node": "npm:*" + form-data: "npm:^4.0.0" + checksum: 10/8107c479da83a3114fcbfa882eba95ee5175cccb5e4dd53f737a96f2559ae6262f662176b8457c1656de09ec393cc7b20a266c077e4bfb21e929976e1cf4d0f9 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 22.13.10 + resolution: "@types/node@npm:22.13.10" + dependencies: + undici-types: "npm:~6.20.0" + checksum: 10/57dc6a5e0110ca9edea8d7047082e649fa7fa813f79e4a901653b9174141c622f4336435648baced5b38d9f39843f404fa2d8d7a10981610da26066bc8caab48 + languageName: node + linkType: hard + +"@types/parse-json@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/parse-json@npm:4.0.2" + checksum: 10/5bf62eec37c332ad10059252fc0dab7e7da730764869c980b0714777ad3d065e490627be9f40fc52f238ffa3ac4199b19de4127196910576c2fe34dd47c7a470 + languageName: node + linkType: hard + +"@wry/equality@npm:^0.1.2": + version: 0.1.11 + resolution: "@wry/equality@npm:0.1.11" + dependencies: + tslib: "npm:^1.9.3" + checksum: 10/b3672e3c1be3b19589eff5135af835dc84ba57c1caddd6d37ec2f1910fb4054ac2693c48d39750bcad21e733d7652169b6842ebb2cbf7a1a835502fec7f86480 + languageName: node + linkType: hard + +"ajv@npm:^8.0.1": + version: 8.17.1 + resolution: "ajv@npm:8.17.1" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10/ee3c62162c953e91986c838f004132b6a253d700f1e51253b99791e2dbfdb39161bc950ebdc2f156f8568035bb5ed8be7bd78289cd9ecbf3381fe8f5b82e3f33 + languageName: node + linkType: hard + +"ansi-escapes@npm:^3.0.0": + version: 3.2.0 + resolution: "ansi-escapes@npm:3.2.0" + checksum: 10/0f94695b677ea742f7f1eed961f7fd8d05670f744c6ad1f8f635362f6681dcfbc1575cb05b43abc7bb6d67e25a75fb8c7ea8f2a57330eb2c76b33f18cb2cef0a + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10/8661034456193ffeda0c15c8c564a9636b0c04094b7f78bd01517929c17c504090a60f7a75f949f5af91289c264d3e1001d91492c1bd58efc8e100500ce04de2 + languageName: node + linkType: hard + +"ansi-regex@npm:^2.0.0": + version: 2.1.1 + resolution: "ansi-regex@npm:2.1.1" + checksum: 10/190abd03e4ff86794f338a31795d262c1dfe8c91f7e01d04f13f646f1dcb16c5800818f886047876f1272f065570ab86b24b99089f8b68a0e11ff19aed4ca8f1 + languageName: node + linkType: hard + +"ansi-regex@npm:^3.0.0": + version: 3.0.1 + resolution: "ansi-regex@npm:3.0.1" + checksum: 10/09daf180c5f59af9850c7ac1bd7fda85ba596cc8cbeb210826e90755f06c818af86d9fa1e6e8322fab2c3b9e9b03f56c537b42241139f824dd75066a1e7257cc + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10/2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b + languageName: node + linkType: hard + +"ansi-styles@npm:^2.2.1": + version: 2.2.1 + resolution: "ansi-styles@npm:2.2.1" + checksum: 10/ebc0e00381f2a29000d1dac8466a640ce11943cef3bda3cd0020dc042e31e1058ab59bf6169cd794a54c3a7338a61ebc404b7c91e004092dd20e028c432c9c2c + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10/d85ade01c10e5dd77b6c89f34ed7531da5830d2cb5882c645f330079975b716438cd7ebb81d0d6e6b4f9c577f19ae41ab55f07f19786b02f9dfd9e0377395665 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0, ansi-styles@npm:^4.2.0, ansi-styles@npm:^4.2.1, ansi-styles@npm:^4.3.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10/b4494dfbfc7e4591b4711a396bd27e540f8153914123dccb4cdbbcb514015ada63a3809f362b9d8d4f6b17a706f1d7bea3c6f974b15fa5ae76b5b502070889ff + languageName: node + linkType: hard + +"ansicolors@npm:~0.3.2": + version: 0.3.2 + resolution: "ansicolors@npm:0.3.2" + checksum: 10/0704d1485d84d65a47aacd3d2d26f501f21aeeb509922c8f2496d0ec5d346dc948efa64f3151aef0571d73e5c44eb10fd02f27f59762e9292fe123bb1ea9ff7d + languageName: node + linkType: hard + +"any-observable@npm:^0.3.0": + version: 0.3.0 + resolution: "any-observable@npm:0.3.0" + checksum: 10/21f27ed714c54aac6db4c1200674933f93416b832433cd14e5071db53f7d480de66a4c529181655dee52371be7f73ebeb0880b02a95571d70152fd6b226c11e9 + languageName: node + linkType: hard + +"apollo-codegen-core@npm:0.40.9, apollo-codegen-core@npm:^0.40.9": + version: 0.40.9 + resolution: "apollo-codegen-core@npm:0.40.9" + dependencies: + "@babel/generator": "npm:7.17.10" + "@babel/parser": "npm:^7.1.3" + "@babel/types": "npm:7.17.10" + apollo-env: "npm:^0.10.2" + apollo-language-server: "npm:^1.26.9" + ast-types: "npm:^0.14.0" + common-tags: "npm:^1.5.1" + recast: "npm:^0.21.0" + checksum: 10/52328d7d7e684d409243737cd200f98b759eb039498aa750518cd10ed0baa82d11d9ee4639b74b5c5c29a3c613eb21ab3e5447c47c460aba349fd0a5f2793608 + languageName: node + linkType: hard + +"apollo-codegen-flow@npm:0.38.9": + version: 0.38.9 + resolution: "apollo-codegen-flow@npm:0.38.9" + dependencies: + "@babel/generator": "npm:7.17.10" + "@babel/types": "npm:7.17.10" + apollo-codegen-core: "npm:^0.40.9" + change-case: "npm:^4.0.0" + common-tags: "npm:^1.5.1" + inflected: "npm:^2.0.3" + checksum: 10/f4bee85c4ccda639ca595e4733480f558a21970e18c36c3c213475084751dbc5f2a8b9c1d0b8174686338831b925b24bdf2d8d67318ab97155ed0855f386bbe6 + languageName: node + linkType: hard + +"apollo-codegen-scala@npm:0.39.9": + version: 0.39.9 + resolution: "apollo-codegen-scala@npm:0.39.9" + dependencies: + apollo-codegen-core: "npm:^0.40.9" + change-case: "npm:^4.0.0" + common-tags: "npm:^1.5.1" + inflected: "npm:^2.0.3" + checksum: 10/8a970a665bf58f3bcbf5e9124d8015ce8ebb056549a6c3f23bf0e8e915b39e08451fc92169f55b202fe358c3692bed518fa3048547cf0c386dc0f4ed7bc66f32 + languageName: node + linkType: hard + +"apollo-codegen-swift@npm:0.40.9": + version: 0.40.9 + resolution: "apollo-codegen-swift@npm:0.40.9" + dependencies: + apollo-codegen-core: "npm:^0.40.9" + change-case: "npm:^4.0.0" + common-tags: "npm:^1.5.1" + inflected: "npm:^2.0.3" + checksum: 10/ee7ca516776c97e6fbafa0949e84fa75ba40e299f2454931da729dcd70e547d64528bb5b166554ece9f0204b36d3654996ccfb2c06314213936e9cd30751322b + languageName: node + linkType: hard + +"apollo-codegen-typescript@npm:0.40.9": + version: 0.40.9 + resolution: "apollo-codegen-typescript@npm:0.40.9" + dependencies: + "@babel/generator": "npm:7.17.10" + "@babel/types": "npm:7.17.10" + apollo-codegen-core: "npm:^0.40.9" + change-case: "npm:^4.0.0" + common-tags: "npm:^1.5.1" + inflected: "npm:^2.0.3" + checksum: 10/5dfef8fdd3c7312b8c08c3ed9da317dce0e38f8fee51dc96a9e20d5daf29fd9aff3e981b127258926dc10e0c37995a8a2bc63726e4584b6ffbd130713be33280 + languageName: node + linkType: hard + +"apollo-datasource@npm:^3.0.0": + version: 3.3.2 + resolution: "apollo-datasource@npm:3.3.2" + dependencies: + "@apollo/utils.keyvaluecache": "npm:^1.0.1" + apollo-server-env: "npm:^4.2.1" + checksum: 10/70244e792655b357213b92e9dd0e8ca553724857847c9bedb53a1dbf7a92fc0d8b05a60d77203d6c30331599b44c5d7cc5f4d94c934465fa05b146b681ed2293 + languageName: node + linkType: hard + +"apollo-env@npm:0.10.2, apollo-env@npm:^0.10.2": + version: 0.10.2 + resolution: "apollo-env@npm:0.10.2" + dependencies: + "@types/node-fetch": "npm:^2.5.10" + core-js: "npm:^3.0.1" + node-fetch: "npm:^2.6.1" + sha.js: "npm:^2.4.11" + checksum: 10/142a440fa1a27e51055e3dcd4e6617a00eb97ddcb75c142d00c68bbff4adb011a94b77fd8aa1d75f00c3f3991c1ce40ce3caa37e2aaebdff6beec02beb9aaeae + languageName: node + linkType: hard + +"apollo-graphql@npm:0.9.7, apollo-graphql@npm:^0.9.3, apollo-graphql@npm:^0.9.7": + version: 0.9.7 + resolution: "apollo-graphql@npm:0.9.7" + dependencies: + core-js-pure: "npm:^3.10.2" + lodash.sortby: "npm:^4.7.0" + sha.js: "npm:^2.4.11" + peerDependencies: + graphql: ^14.2.1 || ^15.0.0 + checksum: 10/7236488b75f5cb3b9297e59779d54aab0010eece6a5424632a3bf150a88941c3d15be3c2f2aa9110099829df0be689294ef7820529419814a425f05ef5e105d0 + languageName: node + linkType: hard + +"apollo-language-server@npm:1.26.9, apollo-language-server@npm:^1.26.9": + version: 1.26.9 + resolution: "apollo-language-server@npm:1.26.9" + dependencies: + "@apollo/federation": "npm:0.27.0" + "@apollographql/apollo-tools": "npm:^0.5.4" + "@apollographql/graphql-language-service-interface": "npm:^2.0.2" + "@endemolshinegroup/cosmiconfig-typescript-loader": "npm:^3.0.2" + apollo-datasource: "npm:^3.0.0" + apollo-env: "npm:^0.10.2" + apollo-graphql: "npm:^0.9.7" + apollo-link: "npm:^1.2.3" + apollo-link-context: "npm:^1.0.9" + apollo-link-error: "npm:^1.1.1" + apollo-link-http: "npm:^1.5.5" + apollo-server-errors: "npm:^2.0.2" + await-to-js: "npm:^3.0.0" + core-js: "npm:^3.0.1" + cosmiconfig: "npm:^7.0.1" + dotenv: "npm:^16.0.0" + glob: "npm:^8.0.0" + graphql: "npm:14.0.2 - 14.2.0 || ^14.3.1 || ^15.0.0" + graphql-tag: "npm:^2.10.1" + lodash.debounce: "npm:^4.0.8" + lodash.merge: "npm:^4.6.1" + minimatch: "npm:^5.0.0" + vscode-languageserver: "npm:^7.0.0" + vscode-languageserver-textdocument: "npm:^1.0.4" + vscode-uri: "npm:1.0.6" + checksum: 10/c4e64e0f138531d18b69048d03f8396d68162e6ed65e1585c1d4e27b911538480b6e27b2a3b58c4a8003f167be3aa97e82731a701bf9347358677823de383e59 + languageName: node + linkType: hard + +"apollo-link-context@npm:^1.0.9": + version: 1.0.20 + resolution: "apollo-link-context@npm:1.0.20" + dependencies: + apollo-link: "npm:^1.2.14" + tslib: "npm:^1.9.3" + checksum: 10/5e905e3dd364dafb8683beb07d60cc84378a96247f21b735194bd2aa70c8f0f8b7b066177a9a7e5549ca08f60901c58243e56fa61a1969436a61fdb9d0cb582b + languageName: node + linkType: hard + +"apollo-link-error@npm:^1.1.1": + version: 1.1.13 + resolution: "apollo-link-error@npm:1.1.13" + dependencies: + apollo-link: "npm:^1.2.14" + apollo-link-http-common: "npm:^0.2.16" + tslib: "npm:^1.9.3" + checksum: 10/b417b77acbf464d8246eb79312c6d755f9d398c6c9f560c9e2270260519df639c3f0a8b9454ccc55c7f54eb57490706275ede120ecd7f475ec4627012feaf4b2 + languageName: node + linkType: hard + +"apollo-link-http-common@npm:^0.2.16": + version: 0.2.16 + resolution: "apollo-link-http-common@npm:0.2.16" + dependencies: + apollo-link: "npm:^1.2.14" + ts-invariant: "npm:^0.4.0" + tslib: "npm:^1.9.3" + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 + checksum: 10/8cf50fc8a10a0f1c6c6ddf4a3f76c4652e07e9dede52da020d3f430199dccf813fb9b953e8725b8621007405d626c270e67b994c60a974e5598021cd4d84e549 + languageName: node + linkType: hard + +"apollo-link-http@npm:^1.5.5": + version: 1.5.17 + resolution: "apollo-link-http@npm:1.5.17" + dependencies: + apollo-link: "npm:^1.2.14" + apollo-link-http-common: "npm:^0.2.16" + tslib: "npm:^1.9.3" + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 + checksum: 10/b66cc1ce3307a86b96b633b06ada015df094f796d289f1c29ff004075bb9505ac5d0cfb98ae8155daea0ccd0e181f0cbc08f8277fd211bfd0f572d5a1b64b579 + languageName: node + linkType: hard + +"apollo-link@npm:^1.2.14, apollo-link@npm:^1.2.3": + version: 1.2.14 + resolution: "apollo-link@npm:1.2.14" + dependencies: + apollo-utilities: "npm:^1.3.0" + ts-invariant: "npm:^0.4.0" + tslib: "npm:^1.9.3" + zen-observable-ts: "npm:^0.8.21" + peerDependencies: + graphql: ^0.11.3 || ^0.12.3 || ^0.13.0 || ^14.0.0 || ^15.0.0 + checksum: 10/92e6764038761bef8526b87b21b0a820067ad0f462c2a007d686e9a6f35bfc26b5ed232cc58301c660d28f9fdb2765edcd99e8e4bdf0b32226ec28f95ffcd212 + languageName: node + linkType: hard + +"apollo-server-env@npm:^4.2.1": + version: 4.2.1 + resolution: "apollo-server-env@npm:4.2.1" + dependencies: + node-fetch: "npm:^2.6.7" + checksum: 10/9172288c89c2ebb2a02d58542f807896de1ca0ba80c430f09242f2fa9ece40d7ecb8f9527357ba5e1d9997c64c364e7a9716e4f5485c5fb4938f69627bf1cea4 + languageName: node + linkType: hard + +"apollo-server-errors@npm:^2.0.2": + version: 2.5.0 + resolution: "apollo-server-errors@npm:2.5.0" + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 + checksum: 10/a378d7fa8a318c293910665d0c5a9b6ec86de387f0cd5f3ab9082eba4228e9846291532c82c599dc14913cb68869e8f8428c855d5486fd05a79886e103f16a40 + languageName: node + linkType: hard + +"apollo-utilities@npm:^1.3.0": + version: 1.3.4 + resolution: "apollo-utilities@npm:1.3.4" + dependencies: + "@wry/equality": "npm:^0.1.2" + fast-json-stable-stringify: "npm:^2.0.0" + ts-invariant: "npm:^0.4.0" + tslib: "npm:^1.10.0" + peerDependencies: + graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 + checksum: 10/6243ef74167996a7ec4ce2f4bd63c126de531a828ae0a2f07e0b47ca2308f6fed3a521603ed85ee3c08e8eb86ce48e9a3d7a205403112113966653fae4401e8b + languageName: node + linkType: hard + +"apollo@npm:^2.34.0": + version: 2.34.0 + resolution: "apollo@npm:2.34.0" + dependencies: + "@apollographql/apollo-tools": "npm:0.5.4" + "@oclif/command": "npm:1.8.16" + "@oclif/config": "npm:1.18.3" + "@oclif/errors": "npm:1.3.5" + "@oclif/plugin-autocomplete": "npm:1.3.0" + "@oclif/plugin-help": "npm:5.1.12" + "@oclif/plugin-not-found": "npm:2.3.1" + "@oclif/plugin-plugins": "npm:2.1.0" + "@oclif/plugin-warn-if-update-available": "npm:2.0.4" + apollo-codegen-core: "npm:0.40.9" + apollo-codegen-flow: "npm:0.38.9" + apollo-codegen-scala: "npm:0.39.9" + apollo-codegen-swift: "npm:0.40.9" + apollo-codegen-typescript: "npm:0.40.9" + apollo-env: "npm:0.10.2" + apollo-graphql: "npm:0.9.7" + apollo-language-server: "npm:1.26.9" + chalk: "npm:4.1.2" + cli-ux: "npm:6.0.9" + env-ci: "npm:7.1.0" + gaze: "npm:1.1.3" + git-parse: "npm:2.1.1" + git-rev-sync: "npm:3.0.2" + git-url-parse: "npm:11.6.0" + glob: "npm:8.0.1" + global-agent: "npm:3.0.0" + graphql: "npm:14.0.2 - 14.2.0 || ^14.3.1 || ^15.0.0" + graphql-tag: "npm:2.12.6" + listr: "npm:0.14.3" + lodash.identity: "npm:3.0.0" + lodash.pickby: "npm:4.6.0" + mkdirp: "npm:1.0.4" + moment: "npm:2.29.3" + strip-ansi: "npm:6.0.1" + table: "npm:6.8.0" + tty: "npm:1.0.1" + vscode-uri: "npm:1.0.6" + bin: + apollo: bin/run + checksum: 10/a9dd28e4e1212c5cfa9cc6615473ef6ff13611cfb1ade83305b79607556aca06fc31e982fed7063ad4156ca1177db009484adc260049111fc7364b491cb7d870 + languageName: node + linkType: hard + +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 10/969b491082f20cad166649fa4d2073ea9e974a4e5ac36247ca23d2e5a8b3cb12d60e9ff70a8acfe26d76566c71fd351ee5e6a9a6595157eb36f92b1fd64e1599 + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10/c6a621343a553ff3779390bb5ee9c2263d6643ebcd7843227bdde6cc7adbed796eb5540ca98db19e3fd7b4714e1faa51551f8849b268bb62df27ddb15cbcd91e + languageName: node + linkType: hard + +"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + is-array-buffer: "npm:^3.0.5" + checksum: 10/0ae3786195c3211b423e5be8dd93357870e6fb66357d81da968c2c39ef43583ef6eece1f9cb1caccdae4806739c65dea832b44b8593414313cd76a89795fca63 + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 10/5bee12395cba82da674931df6d0fea23c4aa4660cb3b338ced9f828782a65caa232573e6bf3968f23e0c5eb301764a382cef2f128b170a9dc59de0e36c39f98d + languageName: node + linkType: hard + +"array.prototype.reduce@npm:^1.0.6": + version: 1.0.7 + resolution: "array.prototype.reduce@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-array-method-boxes-properly: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + is-string: "npm:^1.0.7" + checksum: 10/3a4fa56cf5843d821e97680861c8edfdfe6684a7f7cd1145ed611b5fa611fd62d1b149a438ae24ae884c843876a6539b67fbcacdd3276f89731eee9415dc9012 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + is-array-buffer: "npm:^3.0.4" + checksum: 10/4821ebdfe7d699f910c7f09bc9fa996f09b96b80bccb4f5dd4b59deae582f6ad6e505ecef6376f8beac1eda06df2dbc89b70e82835d104d6fcabd33c1aed1ae9 + languageName: node + linkType: hard + +"ast-types@npm:0.15.2": + version: 0.15.2 + resolution: "ast-types@npm:0.15.2" + dependencies: + tslib: "npm:^2.0.1" + checksum: 10/81680bd5829cdec33524e9aa3434e23f3919c0c388927068a0ff2e8466f55b0f34eae53e0007b3668742910c289481ab4e1d486a5318f618ae2fc93b5e7e863b + languageName: node + linkType: hard + +"ast-types@npm:^0.14.0": + version: 0.14.2 + resolution: "ast-types@npm:0.14.2" + dependencies: + tslib: "npm:^2.0.1" + checksum: 10/7c74b3090c90aa600b49a7a8cecc99e329f190600bcaa75ad087472a1a5a7ef23795a17ea00a74c2a8e822b336cd4f874e2e1b815a9877b4dba5e401566b0433 + languageName: node + linkType: hard + +"astral-regex@npm:^2.0.0": + version: 2.0.0 + resolution: "astral-regex@npm:2.0.0" + checksum: 10/876231688c66400473ba505731df37ea436e574dd524520294cc3bbc54ea40334865e01fa0d074d74d036ee874ee7e62f486ea38bc421ee8e6a871c06f011766 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd + languageName: node + linkType: hard + +"async@npm:^3.2.3": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10/3ce727cbc78f69d6a4722517a58ee926c8c21083633b1d3fdf66fd688f6c127a53a592141bd4866f9b63240a86e9d8e974b13919450bd17fa33c2d22c4558ad8 + languageName: node + linkType: hard + +"at-least-node@npm:^1.0.0": + version: 1.0.0 + resolution: "at-least-node@npm:1.0.0" + checksum: 10/463e2f8e43384f1afb54bc68485c436d7622acec08b6fad269b421cb1d29cebb5af751426793d0961ed243146fe4dc983402f6d5a51b720b277818dbf6f2e49e + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab + languageName: node + linkType: hard + +"await-to-js@npm:^3.0.0": + version: 3.0.0 + resolution: "await-to-js@npm:3.0.0" + checksum: 10/b0445e4cbf9cf98482537f09b0a708be01b1e4d85465465545a7718b79cbefe2409a8cd0d4441d95503b1fabf29303fd9b540a8c71ed2a4b899446e6b93f9075 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10/9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 + languageName: node + linkType: hard + +"boolean@npm:^3.0.1": + version: 3.2.0 + resolution: "boolean@npm:3.2.0" + checksum: 10/d28a49dcaeef7fe10cf9fdf488214d3859f07350be8f5caa0c73ec621baf20650e5da6523262e5ce9221909519d4261c16d8430a5bf307fee9ef0e170cdb29f3 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10/a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 + languageName: node + linkType: hard + +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10/fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10/0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb + languageName: node + linkType: hard + +"byline@npm:5.0.0": + version: 5.0.0 + resolution: "byline@npm:5.0.0" + checksum: 10/737ca83e8eda2976728dae62e68bc733aea095fab08db4c6f12d3cee3cf45b6f97dce45d1f6b6ff9c2c947736d10074985b4425b31ce04afa1985a4ef3d334a7 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10/00482c1f6aa7cfb30fb1dbeb13873edf81cfac7c29ed67a5957d60635a56b2a4a480f1016ddbdb3395cc37900d46037fb965043a51c5c789ffeab4fc535d18b5 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.0, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" + dependencies: + call-bind-apply-helpers: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.2" + checksum: 10/659b03c79bbfccf0cde3a79e7d52570724d7290209823e1ca5088f94b52192dc1836b82a324d0144612f816abb2f1734447438e38d9dafe0b3f82c2a1b9e3bce + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10/ef2b96e126ec0e58a7ff694db43f4d0d44f80e641370c21549ed911fecbdbc2df3ebc9bddad918d6bbdefeafb60bb3337902006d5176d72bcd2da74820991af7 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10/072d17b6abb459c2ba96598918b55868af677154bec7e73d222ef95a8fdb9bbf7dae96a8421085cdad8cd190d86653b5b6dc55a4484f2e5b2e27d5e0c3fc15b3 + languageName: node + linkType: hard + +"camel-case@npm:^4.1.2": + version: 4.1.2 + resolution: "camel-case@npm:4.1.2" + dependencies: + pascal-case: "npm:^3.1.2" + tslib: "npm:^2.0.3" + checksum: 10/bcbd25cd253b3cbc69be3f535750137dbf2beb70f093bdc575f73f800acc8443d34fd52ab8f0a2413c34f1e8203139ffc88428d8863e4dfe530cfb257a379ad6 + languageName: node + linkType: hard + +"capital-case@npm:^1.0.4": + version: 1.0.4 + resolution: "capital-case@npm:1.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10/41fa8fa87f6d24d0835a2b4a9341a3eaecb64ac29cd7c5391f35d6175a0fa98ab044e7f2602e1ec3afc886231462ed71b5b80c590b8b41af903ec2c15e5c5931 + languageName: node + linkType: hard + +"cardinal@npm:^2.1.1": + version: 2.1.1 + resolution: "cardinal@npm:2.1.1" + dependencies: + ansicolors: "npm:~0.3.2" + redeyed: "npm:~2.1.0" + bin: + cdl: ./bin/cdl.js + checksum: 10/caf0d34739ef7b1d80e1753311f889997b62c4490906819eb5da5bd46e7f5e5caba7a8a96ca401190c7d9c18443a7749e5338630f7f9a1ae98d60cac49b9008e + languageName: node + linkType: hard + +"chalk@npm:4.1.2, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10/cb3f3e594913d63b1814d7ca7c9bafbf895f75fbf93b92991980610dfd7b48500af4e3a5d4e3a8f337990a96b168d7eb84ee55efdce965e2ee8efc20f8c8f139 + languageName: node + linkType: hard + +"chalk@npm:^1.0.0, chalk@npm:^1.1.3": + version: 1.1.3 + resolution: "chalk@npm:1.1.3" + dependencies: + ansi-styles: "npm:^2.2.1" + escape-string-regexp: "npm:^1.0.2" + has-ansi: "npm:^2.0.0" + strip-ansi: "npm:^3.0.0" + supports-color: "npm:^2.0.0" + checksum: 10/abcf10da02afde04cc615f06c4bdb3ffc70d2bfbf37e0df03bb88b7459a9411dab4d01210745b773abc936031530a20355f1facc4bee1bbf08613d8fdcfb3aeb + languageName: node + linkType: hard + +"chalk@npm:^2.4.1": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10/3d1d103433166f6bfe82ac75724951b33769675252d8417317363ef9d54699b7c3b2d46671b772b893a8e50c3ece70c4b933c73c01e81bc60ea4df9b55afa303 + languageName: node + linkType: hard + +"change-case@npm:^4.0.0": + version: 4.1.2 + resolution: "change-case@npm:4.1.2" + dependencies: + camel-case: "npm:^4.1.2" + capital-case: "npm:^1.0.4" + constant-case: "npm:^3.0.4" + dot-case: "npm:^3.0.4" + header-case: "npm:^2.0.4" + no-case: "npm:^3.0.4" + param-case: "npm:^3.0.4" + pascal-case: "npm:^3.1.2" + path-case: "npm:^3.0.4" + sentence-case: "npm:^3.0.4" + snake-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/e4bc4a093a1f7cce8b33896665cf9e456e3bc3cc0def2ad7691b1994cfca99b3188d0a513b16855b01a6bd20692fcde12a7d4d87a5615c4c515bbbf0e651f116 + languageName: node + linkType: hard + +"clean-stack@npm:^3.0.0, clean-stack@npm:^3.0.1": + version: 3.0.1 + resolution: "clean-stack@npm:3.0.1" + dependencies: + escape-string-regexp: "npm:4.0.0" + checksum: 10/dc18c842d7792dd72d463936b1b0a5b2621f0fc11588ee48b602e1a29b6c010c606d89f3de1f95d15d72de74aea93c0fbac8246593a31d95f8462cac36148e05 + languageName: node + linkType: hard + +"cli-cursor@npm:^2.0.0, cli-cursor@npm:^2.1.0": + version: 2.1.0 + resolution: "cli-cursor@npm:2.1.0" + dependencies: + restore-cursor: "npm:^2.0.0" + checksum: 10/d88e97bfdac01046a3ffe7d49f06757b3126559d7e44aa2122637eb179284dc6cd49fca2fac4f67c19faaf7e6dab716b6fe1dfcd309977407d8c7578ec2d044d + languageName: node + linkType: hard + +"cli-progress@npm:^3.10.0": + version: 3.12.0 + resolution: "cli-progress@npm:3.12.0" + dependencies: + string-width: "npm:^4.2.3" + checksum: 10/a6a549919a7461f5e798b18a4a19f83154bab145d3ec73d7f3463a8db8e311388c545ace1105557760a058cc4999b7f28c9d8d24d9783ee2912befb32544d4b8 + languageName: node + linkType: hard + +"cli-truncate@npm:^0.2.1": + version: 0.2.1 + resolution: "cli-truncate@npm:0.2.1" + dependencies: + slice-ansi: "npm:0.0.4" + string-width: "npm:^1.0.1" + checksum: 10/c2b0de7c08915eab1e660884251411ad31691c5036a876f98e1bf747f1c165dc8345afdba92b7efb3678478c9fc17c9c9c47c76d181e35478aaa1047459f98aa + languageName: node + linkType: hard + +"cli-ux@npm:6.0.9": + version: 6.0.9 + resolution: "cli-ux@npm:6.0.9" + dependencies: + "@oclif/core": "npm:^1.1.1" + "@oclif/linewrap": "npm:^1.0.0" + "@oclif/screen": "npm:^1.0.4 " + ansi-escapes: "npm:^4.3.0" + ansi-styles: "npm:^4.2.0" + cardinal: "npm:^2.1.1" + chalk: "npm:^4.1.0" + clean-stack: "npm:^3.0.0" + cli-progress: "npm:^3.10.0" + extract-stack: "npm:^2.0.0" + fs-extra: "npm:^8.1" + hyperlinker: "npm:^1.0.0" + indent-string: "npm:^4.0.0" + is-wsl: "npm:^2.2.0" + js-yaml: "npm:^3.13.1" + lodash: "npm:^4.17.21" + natural-orderby: "npm:^2.0.1" + object-treeify: "npm:^1.1.4" + password-prompt: "npm:^1.1.2" + semver: "npm:^7.3.2" + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + supports-color: "npm:^8.1.0" + supports-hyperlinks: "npm:^2.1.0" + tslib: "npm:^2.0.0" + checksum: 10/87e44a4f268b59e1c23a7b8962cdc8172161925fc627c7068881fc98eb636fffc1a5ad885b4673378e428b224b0144d5123d5c4e01e34720fff06c47992358bf + languageName: node + linkType: hard + +"code-point-at@npm:^1.0.0": + version: 1.1.0 + resolution: "code-point-at@npm:1.1.0" + checksum: 10/17d5666611f9b16d64fdf48176d9b7fb1c7d1c1607a189f7e600040a11a6616982876af148230336adb7d8fe728a559f743a4e29db3747e3b1a32fa7f4529681 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10/ffa319025045f2973919d155f25e7c00d08836b6b33ea2d205418c59bd63a665d713c52d9737a9e0fe467fb194b40fbef1d849bae80d674568ee220a31ef3d10 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10/fa00c91b4332b294de06b443923246bccebe9fab1b253f7fe1772d37b06a2269b4039a85e309abe1fe11b267b11c08d1d0473fda3badd6167f57313af2887a64 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10/09c5d3e33d2105850153b14466501f2bfb30324a2f76568a408763a3b7433b0e50e5b4ab1947868e65cb101bb7cb75029553f2c333b6d4b8138a73fcc133d69d + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10/2e969e637d05d09fa50b02d74c83a1186f6914aae89e6653b62595cc75a221464f884f55f231b8f4df7a49537fba60bdc0427acd2bf324c09a1dbb84837e36e4 + languageName: node + linkType: hard + +"common-tags@npm:^1.5.1": + version: 1.8.2 + resolution: "common-tags@npm:1.8.2" + checksum: 10/c665d0f463ee79dda801471ad8da6cb33ff7332ba45609916a508ad3d77ba07ca9deeb452e83f81f24c2b081e2c1315347f23d239210e63d1c5e1a0c7c019fe2 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10/9680699c8e2b3af0ae22592cb764acaf973f292a7b71b8a06720233011853a58e256c89216a10cbe889727532fd77f8bcd49a760cedfde271b8e006c20e079f2 + languageName: node + linkType: hard + +"constant-case@npm:^3.0.4": + version: 3.0.4 + resolution: "constant-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case: "npm:^2.0.2" + checksum: 10/6c3346d51afc28d9fae922e966c68eb77a19d94858dba230dd92d7b918b37d36db50f0311e9ecf6847e43e934b1c01406a0936973376ab17ec2c471fbcfb2cf3 + languageName: node + linkType: hard + +"content-type@npm:^1.0.4": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 + languageName: node + linkType: hard + +"core-js-pure@npm:^3.10.2": + version: 3.41.0 + resolution: "core-js-pure@npm:3.41.0" + checksum: 10/69cc1d966d8a177be3d8ddbb4460c778dbfa5a458f74069b55322428524a54544a787fc15fe905aa84e93e0eab0d6a6501fb7026a885b7a8553c8542b01e79fb + languageName: node + linkType: hard + +"core-js@npm:^3.0.1": + version: 3.41.0 + resolution: "core-js@npm:3.41.0" + checksum: 10/a06ebae2264dd24c8e4b331a68412f7d0730557c41901f80fa910a9398dbef4670482d9ef5a41fef7efd41307c612d3d4051df7640ac4c01ff6feda45f8b92be + languageName: node + linkType: hard + +"cosmiconfig@npm:^7.0.1": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10/03600bb3870c80ed151b7b706b99a1f6d78df8f4bdad9c95485072ea13358ef294b13dd99f9e7bf4cc0b43bcd3599d40df7e648750d21c2f6817ca2cd687e071 + languageName: node + linkType: hard + +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 10/a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.3": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10/0d52657d7ae36eb130999dffff1168ec348687b48dd38e2ff59992ed916c88d328cf1d07ff4a4a10bc78de5e1c23f04b306d569e42f7a2293915c081e4dfee86 + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10/c10b155a4e93999d3a215d08c23eea95f865e1f510b2e7748fcae1882b776df1afe8c99f483ace7fc0e5a3193ab08da138abebc9829d12003746c5a338c4d644 + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10/2a47055fcf1ab3ec41b00b6f738c6461a841391a643c9ed9befec1117c1765b4d492661d97fb7cc899200c328949dca6ff189d2c6537d96d60e8a02dfe3c95f7 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10/fa3bdfa0968bea6711ee50375094b39f561bce3f15f9e558df59de9c25f0bdd4cddc002d9c1d70ac7772ebd36854a7e22d1761e7302a934e6f1c2263bcf44aa2 + languageName: node + linkType: hard + +"date-fns@npm:^1.27.2": + version: 1.30.1 + resolution: "date-fns@npm:1.30.1" + checksum: 10/24c0937f4e5704f25627c9d1e92e1fe03cd6165d9f32334b7f923a737a57ef992c287cad0694356071e617fbbfa6bd10dec9192ea9035a3e6d0745b9d1594883 + languageName: node + linkType: hard + +"debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.4": + version: 4.4.0 + resolution: "debug@npm:4.4.0" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 + languageName: node + linkType: hard + +"decode-uri-component@npm:^0.2.0": + version: 0.2.2 + resolution: "decode-uri-component@npm:0.2.2" + checksum: 10/17a0e5fa400bf9ea84432226e252aa7b5e72793e16bf80b907c99b46a799aeacc139ec20ea57121e50c7bd875a1a4365928f884e92abf02e21a5a13790a0f33e + languageName: node + linkType: hard + +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10/abdcb2505d80a53524ba871273e5da75e77e52af9e15b3aa65d8aad82b8a3a424dad7aee2cc0b71470ac7acf501e08defac362e8b6a73cdb4309f028061df4ae + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 10/b4ccd00597dd46cb2d4a379398f5b19fca84a16f3374e2249201992f36b30f6835949a9429669ee6b41b6e837205a163eadd745e472069e70dfc10f03e5fcc12 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10/46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020 + languageName: node + linkType: hard + +"detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: 10/832184ec458353e41533ac9c622f16c19f7c02d8b10c303dfd3a756f56be93e903616c0bb2d4226183c9351c15fc0b3dba41a17a2308262afabcfa3776e6ae6e + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: 10/ec09ec2101934ca5966355a229d77afcad5911c92e2a77413efda5455636c4cf2ce84057e2d7715227a2eeeda04255b849bd3ae3a4dd22eb22e86e76456df069 + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: "npm:^4.0.0" + checksum: 10/fa05e18324510d7283f55862f3161c6759a3f2f8dbce491a2fc14c8324c498286c54282c1f0e933cb930da8419b30679389499b919122952a4f8592362ef4615 + languageName: node + linkType: hard + +"dot-case@npm:^3.0.4": + version: 3.0.4 + resolution: "dot-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/a65e3519414856df0228b9f645332f974f2bf5433370f544a681122eab59e66038fc3349b4be1cdc47152779dac71a5864f1ccda2f745e767c46e9c6543b1169 + languageName: node + linkType: hard + +"dotenv@npm:^16.0.0": + version: 16.4.7 + resolution: "dotenv@npm:16.4.7" + checksum: 10/f13bfe97db88f0df4ec505eeffb8925ec51f2d56a3d0b6d916964d8b4af494e6fb1633ba5d09089b552e77ab2a25de58d70259b2c5ed45ec148221835fc99a0c + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 + languageName: node + linkType: hard + +"ejs@npm:^3.1.6": + version: 3.1.10 + resolution: "ejs@npm:3.1.10" + dependencies: + jake: "npm:^10.8.5" + bin: + ejs: bin/cli.js + checksum: 10/a9cb7d7cd13b7b1cd0be5c4788e44dd10d92f7285d2f65b942f33e127230c054f99a42db4d99f766d8dbc6c57e94799593ee66a14efd7c8dd70c4812bf6aa384 + languageName: node + linkType: hard + +"elegant-spinner@npm:^1.0.1": + version: 1.0.1 + resolution: "elegant-spinner@npm:1.0.1" + checksum: 10/d6a773d950c5d403b5f0fa402787e37dde99989ab6c943558fe8491cf7cd0df0e2747a9ff4d391d5a5f20a447cc9e9a63bdc956354ba47bea462f1603a5b04fe + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10/c72d67a6821be15ec11997877c437491c313d924306b8da5d87d2a2bcc2cec9903cb5b04ee1a088460501d8e5b44f10df82fdc93c444101a7610b80c8b6938e1 + languageName: node + linkType: hard + +"env-ci@npm:7.1.0": + version: 7.1.0 + resolution: "env-ci@npm:7.1.0" + dependencies: + execa: "npm:^5.0.0" + fromentries: "npm:^1.3.2" + java-properties: "npm:^1.0.0" + checksum: 10/841e99064161165f180088e36ce2074961c2d095c2d81b1b1c2935d434481ede972bd883fcba843213d216ec4963dc5399ea59f24eb829e3c9602d742e26cc1e + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10/d547740aa29c34e753fb6fed2c5de81802438529c12b3673bd37b6bb1fe49b9b7abdc3c11e6062fe625d8a296b3cf769a80f878865e25e685f787763eede3ffb + languageName: node + linkType: hard + +"es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9": + version: 1.23.9 + resolution: "es-abstract@npm:1.23.9" + dependencies: + array-buffer-byte-length: "npm:^1.0.2" + arraybuffer.prototype.slice: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + data-view-buffer: "npm:^1.0.2" + data-view-byte-length: "npm:^1.0.2" + data-view-byte-offset: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-set-tostringtag: "npm:^2.1.0" + es-to-primitive: "npm:^1.3.0" + function.prototype.name: "npm:^1.1.8" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.0" + get-symbol-description: "npm:^1.1.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.1.0" + is-array-buffer: "npm:^3.0.5" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.2" + is-regex: "npm:^1.2.1" + is-shared-array-buffer: "npm:^1.0.4" + is-string: "npm:^1.1.1" + is-typed-array: "npm:^1.1.15" + is-weakref: "npm:^1.1.0" + math-intrinsics: "npm:^1.1.0" + object-inspect: "npm:^1.13.3" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.7" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.3" + safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" + safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + string.prototype.trim: "npm:^1.2.10" + string.prototype.trimend: "npm:^1.0.9" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.3" + typed-array-byte-length: "npm:^1.0.3" + typed-array-byte-offset: "npm:^1.0.4" + typed-array-length: "npm:^1.0.7" + unbox-primitive: "npm:^1.1.0" + which-typed-array: "npm:^1.1.18" + checksum: 10/31a321966d760d88fc2ed984104841b42f4f24fc322b246002b9be0af162e03803ee41fcc3cf8be89e07a27ba3033168f877dd983703cb81422ffe5322a27582 + languageName: node + linkType: hard + +"es-array-method-boxes-properly@npm:^1.0.0": + version: 1.0.0 + resolution: "es-array-method-boxes-properly@npm:1.0.0" + checksum: 10/27a8a21acf20f3f51f69dce8e643f151e380bffe569e95dc933b9ded9fcd89a765ee21b5229c93f9206c93f87395c6b75f80be8ac8c08a7ceb8771e1822ff1fb + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/86814bf8afbcd8966653f731415888019d4bc4aca6b6c354132a7a75bb87566751e320369654a101d23a91c87a85c79b178bcf40332839bd347aff437c4fb65f + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.3.0": + version: 1.3.0 + resolution: "es-to-primitive@npm:1.3.0" + dependencies: + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.0.5" + is-symbol: "npm:^1.0.4" + checksum: 10/17faf35c221aad59a16286cbf58ef6f080bf3c485dff202c490d074d8e74da07884e29b852c245d894eac84f73c58330ec956dfd6d02c0b449d75eb1012a3f9b + languageName: node + linkType: hard + +"es6-error@npm:^4.1.1": + version: 4.1.1 + resolution: "es6-error@npm:4.1.1" + checksum: 10/48483c25701dc5a6376f39bbe2eaf5da0b505607ec5a98cd3ade472c1939242156660636e2e508b33211e48e88b132d245341595c067bd4a95ac79fa7134da06 + languageName: node + linkType: hard + +"escape-string-regexp@npm:1.0.5, escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10/6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410 + languageName: node + linkType: hard + +"escape-string-regexp@npm:4.0.0, escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10/98b48897d93060f2322108bf29db0feba7dd774be96cd069458d1453347b25ce8682ecc39859d4bca2203cc0ab19c237bcc71755eff49a0f8d90beadeeba5cc5 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0, esprima@npm:~4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10/f1d3c622ad992421362294f7acf866aa9409fbad4eb2e8fa230bd33944ce371d32279667b242d8b8907ec2b6ad7353a717f3c0e60e748873a34a7905174bc0eb + languageName: node + linkType: hard + +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10/8ada91f2d70f7dff702c861c2c64f21dfdc1525628f3c0454fd6f02fce65f7b958616cbd2b99ca7fa4d474e461a3d363824e91b3eb881705231abbf387470597 + languageName: node + linkType: hard + +"extract-stack@npm:^2.0.0": + version: 2.0.0 + resolution: "extract-stack@npm:2.0.0" + checksum: 10/dfe47560b2f47735e6c8ff5e61aa82b98a267b020d373bb3175146257d088b20460332cb95fbcda62380706774c5b864b743532f5392f40e03162bfef133fe38 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10/e21a9d8d84f53493b6aa15efc9cfd53dd5b714a1f23f67fb5dc8f574af80df889b3bce25dc081887c6d25457cce704e636395333abad896ccdec03abaf1f3f9d + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.9": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10/dcc6432b269762dd47381d8b8358bf964d8f4f60286ac6aa41c01ade70bda459ff2001b516690b96d5365f68a49242966112b5d5cc9cd82395fa8f9d017c90ad + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10/2c20055c1fa43c922428f16ca8bb29f2807de63e5c851f665f7ac9790176c01c3b40335257736b299764a8d383388dabc73c8083b8e1bc3d99f0a941444ec60e + languageName: node + linkType: hard + +"fast-levenshtein@npm:^3.0.0": + version: 3.0.0 + resolution: "fast-levenshtein@npm:3.0.0" + dependencies: + fastest-levenshtein: "npm:^1.0.7" + checksum: 10/df98841b262eb345335043ae42f0219f1acf1a88f2e0959ca94c4a46df44e40455d9ee11a3f1c730dee2b1b87dc8b20d4184e71712b30b229df5b40c944ea649 + languageName: node + linkType: hard + +"fast-uri@npm:^3.0.1": + version: 3.0.6 + resolution: "fast-uri@npm:3.0.6" + checksum: 10/43c87cd03926b072a241590e49eca0e2dfe1d347ddffd4b15307613b42b8eacce00a315cf3c7374736b5f343f27e27ec88726260eb03a758336d507d6fbaba0a + languageName: node + linkType: hard + +"fastest-levenshtein@npm:^1.0.7": + version: 1.0.16 + resolution: "fastest-levenshtein@npm:1.0.16" + checksum: 10/ee85d33b5cef592033f70e1c13ae8624055950b4eb832435099cd56aa313d7f251b873bedbc06a517adfaff7b31756d139535991e2406967438e03a1bf1b008e + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.19.1 + resolution: "fastq@npm:1.19.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10/75679dc226316341c4f2a6b618571f51eac96779906faecd8921b984e844d6ae42fabb2df69b1071327d398d5716693ea9c9c8941f64ac9e89ec2032ce59d730 + languageName: node + linkType: hard + +"figures@npm:^1.7.0": + version: 1.7.0 + resolution: "figures@npm:1.7.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + object-assign: "npm:^4.1.0" + checksum: 10/3a815f8a3b488f818e661694112b4546ddff799aa6a07c864c46dadff923af74021f84d42ded402432a98c3208acebf2d096f3a7cc3d1a7b19a2cdc9cbcaea2e + languageName: node + linkType: hard + +"figures@npm:^2.0.0": + version: 2.0.0 + resolution: "figures@npm:2.0.0" + dependencies: + escape-string-regexp: "npm:^1.0.5" + checksum: 10/0e5bba8d2b8847c6844a476113d8d283af8757143d7760cc1a5422cceec5e8dd68c15ba50e0847597bc2c4e3865711657aeef394478c6ddce8aed7e0cd18beca + languageName: node + linkType: hard + +"filelist@npm:^1.0.4": + version: 1.0.4 + resolution: "filelist@npm:1.0.4" + dependencies: + minimatch: "npm:^5.0.1" + checksum: 10/4b436fa944b1508b95cffdfc8176ae6947b92825483639ef1b9a89b27d82f3f8aa22b21eed471993f92709b431670d4e015b39c087d435a61e1bb04564cf51de + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10/a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea + languageName: node + linkType: hard + +"filter-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "filter-obj@npm:1.1.0" + checksum: 10/9d681939eec2b4b129cb4f307b7e93d954a0657421d4e5357d86093b26d3f4f570909ed43717dcfd62428b3cf8cddd9841b35f9d40d12ac62cfabaa677942593 + languageName: node + linkType: hard + +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10/330cc2439f85c94f4609de3ee1d32c5693ae15cdd7fe3d112c4fd9efd4ce7143f2c64ef6c2c9e0cfdb0058437f33ef05b5bdae5b98fcc903fb2143fbaf0fea0f + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.2 + resolution: "form-data@npm:4.0.2" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + mime-types: "npm:^2.1.12" + checksum: 10/82c65b426af4a40090e517a1bc9057f76970b4c6043e37aa49859c447d88553e77d4cc5626395079a53d2b0889ba5f2a49f3900db3ad3f3f1bf76613532572fb + languageName: node + linkType: hard + +"fromentries@npm:^1.3.2": + version: 1.3.2 + resolution: "fromentries@npm:1.3.2" + checksum: 10/10d6e07d289db102c0c1eaf5c3e3fa55ddd6b50033d7de16d99a7cd89f1e1a302dfadb26457031f9bb5d2ed95a179aaf0396092dde5abcae06e8a2f0476826be + languageName: node + linkType: hard + +"fs-extra@npm:^8.1": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10/6fb12449f5349be724a138b4a7b45fe6a317d2972054517f5971959c26fbd17c0e145731a11c7324460262baa33e0a799b183ceace98f7a372c95fbb6f20f5de + languageName: node + linkType: hard + +"fs-extra@npm:^9.0, fs-extra@npm:^9.0.1, fs-extra@npm:^9.1.0": + version: 9.1.0 + resolution: "fs-extra@npm:9.1.0" + dependencies: + at-least-node: "npm:^1.0.0" + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10/08600da1b49552ed23dfac598c8fc909c66776dd130fea54fbcad22e330f7fcc13488bb995f6bc9ce5651aa35b65702faf616fe76370ee56f1aade55da982dca + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10/e703107c28e362d8d7b910bbcbfd371e640a3bb45ae157a362b5952c0030c0b6d4981140ec319b347bce7adc025dd7813da1ff908a945ac214d64f5402a51b96 + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": + version: 1.1.8 + resolution: "function.prototype.name@npm:1.1.8" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + functions-have-names: "npm:^1.2.3" + hasown: "npm:^2.0.2" + is-callable: "npm:^1.2.7" + checksum: 10/25b9e5bea936732a6f0c0c08db58cc0d609ac1ed458c6a07ead46b32e7b9bf3fe5887796c3f83d35994efbc4fdde81c08ac64135b2c399b8f2113968d44082bc + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 10/0ddfd3ed1066a55984aaecebf5419fbd9344a5c38dd120ffb0739fac4496758dcf371297440528b115e4367fc46e3abc86a2cc0ff44612181b175ae967a11a05 + languageName: node + linkType: hard + +"gaze@npm:1.1.3": + version: 1.1.3 + resolution: "gaze@npm:1.1.3" + dependencies: + globule: "npm:^1.0.0" + checksum: 10/9ff1110aae5c7d96cbd49812883558971c2f9eba00bdd20e326b5644e262956464fa67edcad03f2cb2ae6ca4f26c80cb1fb5b4a610280a77fca51046acc7749c + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10/6e9dd920ff054147b6f44cb98104330e87caafae051b6d37b13384a45ba15e71af33c3baeac7cb630a0aaa23142718dcf25b45cfdd86c184c5dcb4e56d953a10 + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10/bba0811116d11e56d702682ddef7c73ba3481f114590e705fc549f4d868972263896af313c57a25c076e3c0d567e11d919a64ba1b30c879be985fc9d44f96148 + languageName: node + linkType: hard + +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10/781266d29725f35c59f1d214aedc92b0ae855800a980800e2923b3fbc4e56b3cb6e462c42e09a1cf1a00c64e056a78fa407cbe06c7c92b7e5cd49b4b85c2a497 + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + checksum: 10/a353e3a9595a74720b40fb5bae3ba4a4f826e186e83814d93375182384265676f59e49998b9cdfac4a2225ce95a3d32a68f502a2c5619303987f1c183ab80494 + languageName: node + linkType: hard + +"git-parse@npm:2.1.1": + version: 2.1.1 + resolution: "git-parse@npm:2.1.1" + dependencies: + byline: "npm:5.0.0" + util.promisify: "npm:1.1.1" + checksum: 10/2d17577d7eca0bea897cd45c274182c0a32ecfde0e6cf77bdea85b7667c9cefefc829c1df61ed56a79311b6bf22586540a697f69f6bab8628404b471a881d8a6 + languageName: node + linkType: hard + +"git-rev-sync@npm:3.0.2": + version: 3.0.2 + resolution: "git-rev-sync@npm:3.0.2" + dependencies: + escape-string-regexp: "npm:1.0.5" + graceful-fs: "npm:4.1.15" + shelljs: "npm:0.8.5" + checksum: 10/a6c1b8d9417643f40db0e005df9f05b0f6440756ef5ed379f1565cb90ad58ac1192da193838b243ccf8979ca5109a43c9ca7eb016c612af06912868dab307dd3 + languageName: node + linkType: hard + +"git-up@npm:^4.0.0": + version: 4.0.5 + resolution: "git-up@npm:4.0.5" + dependencies: + is-ssh: "npm:^1.3.0" + parse-url: "npm:^6.0.0" + checksum: 10/8c47757dfbe7a7e6ada6464eac031812e051e0a87eb475cf6be1957d41299032fa498af8104c77668566dd5d1a5c7677a42273f4381f59ad23e39020c7566365 + languageName: node + linkType: hard + +"git-url-parse@npm:11.6.0": + version: 11.6.0 + resolution: "git-url-parse@npm:11.6.0" + dependencies: + git-up: "npm:^4.0.0" + checksum: 10/8fac688231fc86273e0a3f33ddeef9f463834af805ec2e40ccbd72b38b52a1def3d2dafcb32b1395095a93e70f115b30030c5fdb55c97fddd3ec35f26d8b6fb2 + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10/32cd106ce8c0d83731966d31517adb766d02c3812de49c30cfe0675c7c0ae6630c11214c54a5ae67aca882cf738d27fd7768f21aa19118b9245950554be07247 + languageName: node + linkType: hard + +"glob@npm:8.0.1": + version: 8.0.1 + resolution: "glob@npm:8.0.1" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^5.0.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10/b69c95a2019ef186ba4dfa7a8c5382b901fd81caf66ab71a474a94f34d46f2b9ce81ba3099d4f3a3689a9a0b2fa74757d0793e110f27f62a35356209c5b65107 + languageName: node + linkType: hard + +"glob@npm:^7.0.0": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10/59452a9202c81d4508a43b8af7082ca5c76452b9fcc4a9ab17655822e6ce9b21d4f8fbadabe4fe3faef448294cec249af305e2cd824b7e9aaf689240e5e96a7b + languageName: node + linkType: hard + +"glob@npm:^8.0.0": + version: 8.1.0 + resolution: "glob@npm:8.1.0" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^5.0.1" + once: "npm:^1.3.0" + checksum: 10/9aab1c75eb087c35dbc41d1f742e51d0507aa2b14c910d96fb8287107a10a22f4bbdce26fc0a3da4c69a20f7b26d62f1640b346a4f6e6becfff47f335bb1dc5e + languageName: node + linkType: hard + +"glob@npm:~7.1.1": + version: 7.1.7 + resolution: "glob@npm:7.1.7" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.0.4" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10/ff5aab0386e9cace92b0550d42085b71013c5ea382982dd7fdded998a559635f61413b8ba6fb7294eef289c83b52f4e64136f888300ac8afc4f3e5623182d6c8 + languageName: node + linkType: hard + +"global-agent@npm:3.0.0": + version: 3.0.0 + resolution: "global-agent@npm:3.0.0" + dependencies: + boolean: "npm:^3.0.1" + es6-error: "npm:^4.1.1" + matcher: "npm:^3.0.0" + roarr: "npm:^2.15.3" + semver: "npm:^7.3.2" + serialize-error: "npm:^7.0.1" + checksum: 10/a26d96d1d79af57a8ef957f66cef6f3889a8fa55131f0bbd72b8e1bc340a9b7ed7b627b96eaf5eb14aee08a8b4ad44395090e2cf77146e993f1d2df7abaa0a0d + languageName: node + linkType: hard + +"globalthis@npm:^1.0.1, globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10/1f1fd078fb2f7296306ef9dd51019491044ccf17a59ed49d375b576ca108ff37e47f3d29aead7add40763574a992f16a5367dd1e2173b8634ef18556ab719ac4 + languageName: node + linkType: hard + +"globby@npm:^11.0.1, globby@npm:^11.1.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: "npm:^2.1.0" + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.2.9" + ignore: "npm:^5.2.0" + merge2: "npm:^1.4.1" + slash: "npm:^3.0.0" + checksum: 10/288e95e310227bbe037076ea81b7c2598ccbc3122d87abc6dab39e1eec309aa14f0e366a98cdc45237ffcfcbad3db597778c0068217dcb1950fef6249104e1b1 + languageName: node + linkType: hard + +"globule@npm:^1.0.0": + version: 1.3.4 + resolution: "globule@npm:1.3.4" + dependencies: + glob: "npm:~7.1.1" + lodash: "npm:^4.17.21" + minimatch: "npm:~3.0.2" + checksum: 10/04ac30656f9fc34e7e30a700ef39bfc357629a9214e2e228ee714bc0f1be60c5e4e2a78facafa5588889b02d25f02012d9e8c057704040e19e86b920effe54d5 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 + languageName: node + linkType: hard + +"graceful-fs@npm:4.1.15": + version: 4.1.15 + resolution: "graceful-fs@npm:4.1.15" + checksum: 10/eecc88fc447c0d92257ac2583e1177574de6995a8627778bff56e8d14991275eb26a9d248e04c3c128fd17522cfbce17d2ce1bd03ec15efd42d2b3a7bab577c2 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 + languageName: node + linkType: hard + +"graphql-tag@npm:2.12.6, graphql-tag@npm:^2.10.1": + version: 2.12.6 + resolution: "graphql-tag@npm:2.12.6" + dependencies: + tslib: "npm:^2.1.0" + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 10/23a2bc1d3fbeae86444204e0ac08522e09dc369559ba75768e47421a7321b59f352fb5b2c9a5c37d3cf6de890dca4e5ac47e740c7cc622e728572ecaa649089e + languageName: node + linkType: hard + +"graphql@npm:14.0.2 - 14.2.0 || ^14.3.1 || ^15.0.0": + version: 15.10.1 + resolution: "graphql@npm:15.10.1" + checksum: 10/49177f52c1fbac022866d39bb15040122da4c3ccd12644233b20edfcb1f76aa92aa92f2a82af4668101d8f726112fda111cb11a12feb05635cd689443a7aafb8 + languageName: node + linkType: hard + +"has-ansi@npm:^2.0.0": + version: 2.0.0 + resolution: "has-ansi@npm:2.0.0" + dependencies: + ansi-regex: "npm:^2.0.0" + checksum: 10/1b51daa0214440db171ff359d0a2d17bc20061164c57e76234f614c91dbd2a79ddd68dfc8ee73629366f7be45a6df5f2ea9de83f52e1ca24433f2cc78c35d8ec + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.2": + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 10/90fb1b24d40d2472bcd1c8bd9dd479037ec240215869bdbff97b2be83acef57d28f7e96bdd003a21bed218d058b49097f4acc8821c05b1629cc5d48dd7bfcccd + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10/4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10/261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10/2d8c9ab8cebb572e3362f7d06139a4592105983d4317e68f7adba320fe6ddfc8874581e0971e899e633fd5f72e262830edce36d5a0bc863dad17ad20572484b2 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10/7eaed07728eaa28b77fadccabce53f30de467ff186a766872669a833ac2e87d8922b76a22cc58339d7e0277aefe98d6d00762113b27a97cdf65adcf958970935 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10/7898a9c1788b2862cf0f9c345a6bec77ba4a0c0983c7f19d610c382343d4f98fa260686b225dfb1f88393a66679d2ec58ee310c1d6868c081eda7918f32cc70a + languageName: node + linkType: hard + +"header-case@npm:^2.0.4": + version: 2.0.4 + resolution: "header-case@npm:2.0.4" + dependencies: + capital-case: "npm:^1.0.4" + tslib: "npm:^2.0.3" + checksum: 10/571c83eeb25e8130d172218712f807c0b96d62b020981400bccc1503a7cf14b09b8b10498a962d2739eccf231d950e3848ba7d420b58a6acd2f9283439546cd9 + languageName: node + linkType: hard + +"http-call@npm:^5.2.2": + version: 5.3.0 + resolution: "http-call@npm:5.3.0" + dependencies: + content-type: "npm:^1.0.4" + debug: "npm:^4.1.1" + is-retry-allowed: "npm:^1.1.0" + is-stream: "npm:^2.0.0" + parse-json: "npm:^4.0.0" + tunnel-agent: "npm:^0.6.0" + checksum: 10/458c890c95573db831daa2346ff98b1630543c9b2fc3cfc432e1fb6968d6eeb6a5abe87e551f0fc3bce1972215a69fd133b8d25ff8cff2276c2c153d405b3d1f + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10/df59be9e0af479036798a881d1f136c4a29e0b518d4abb863afbd11bf30efa3eeb1d0425fc65942dcc05ab3bf40205ea436b0ff389f2cd20b75b8643d539bf86 + languageName: node + linkType: hard + +"hyperlinker@npm:^1.0.0": + version: 1.0.0 + resolution: "hyperlinker@npm:1.0.0" + checksum: 10/fdcf08c72dde534e127cfc40e4c28de5106c58b58f0191d117a8a78802aeeff98dd870a2ee1ac7ee877861b9d0bd7b515a8d0759f1e319ea3162d3c210dbea7c + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10/a06b19461b4879cc654d46f8a6244eb55eb053437afd4cbb6613cad6be203811849ed3e4ea038783092879487299fda24af932b86bdfff67c9055ba3612b8c87 + languageName: node + linkType: hard + +"indent-string@npm:^3.0.0": + version: 3.2.0 + resolution: "indent-string@npm:3.2.0" + checksum: 10/a0b72603bba6c985d367fda3a25aad16423d2056b22a7e83ee2dd9ce0ce3d03d1e078644b679087aa7edf1cfb457f0d96d9eeadc0b12f38582088cc00e995d2f + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 10/cd3f5cbc9ca2d624c6a1f53f12e6b341659aba0e2d3254ae2b4464aaea8b4294cdb09616abbc59458f980531f2429784ed6a420d48d245bcad0811980c9efae9 + languageName: node + linkType: hard + +"inflected@npm:^2.0.3": + version: 2.1.0 + resolution: "inflected@npm:2.1.0" + checksum: 10/1a4d88cf12803663e25214df1f66f027b572c961f014e958a5607c1e95476f42c27e641db0b8779ef7b05c6d4d1ea4744c61d04dfb7cddf6c8fc26acdca1199d + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10/d2ebd65441a38c8336c223d1b80b921b9fa737e37ea466fd7e253cb000c64ae1f17fa59e68130ef5bda92cfd8d36b83d37dab0eb0a4558bcfec8e8cdfd2dcb67 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:^2.0.1": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10/1d5219273a3dab61b165eddf358815eefc463207db33c20fcfca54717da02e3f492003757721f972fd0bf21e4b426cab389c5427b99ceea4b8b670dc88ee6d4a + languageName: node + linkType: hard + +"interpret@npm:^1.0.0": + version: 1.4.0 + resolution: "interpret@npm:1.4.0" + checksum: 10/5beec568d3f60543d0f61f2c5969d44dffcb1a372fe5abcdb8013968114d4e4aaac06bc971a4c9f5bd52d150881d8ebad72a8c60686b1361f5f0522f39c0e1a3 + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10/ef1095c55b963cd0dcf6f88a113e44a0aeca91e30d767c475e7d746d28d1195b10c5076b94491a7a0cd85020ca6a4923070021d74651d093dc909e9932cf689b + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10/73ced84fa35e59e2c57da2d01e12cd01479f381d7f122ce41dcbb713f09dbfc651315832cd2bf8accba7681a69e4d6f1e03941d94dd10040d415086360e7005e + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" + dependencies: + async-function: "npm:^1.0.0" + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10/7c2ac7efdf671e03265e74a043bcb1c0a32e226bc2a42dfc5ec8644667df668bbe14b91c08e6c1414f392f8cf86cd1d489b3af97756e2c7a49dd1ba63fd40ca6 + languageName: node + linkType: hard + +"is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: "npm:^1.0.2" + checksum: 10/10cf327310d712fe227cfaa32d8b11814c214392b6ac18c827f157e1e85363cf9c8e2a22df526689bd5d25e53b58cc110894787afb54e138e7c504174dba15fd + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10/051fa95fdb99d7fbf653165a7e6b2cba5d2eb62f7ffa81e793a790f3fb5366c91c1b7b6af6820aa2937dd86c73aa3ca9d9ca98f500988457b1c59692c52ba911 + languageName: node + linkType: hard + +"is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.0": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10/452b2c2fb7f889cbbf7e54609ef92cf6c24637c568acc7e63d166812a0fb365ae8a504c333a29add8bdb1686704068caa7f4e4b639b650dde4f00a038b8941fb + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": + version: 1.0.2 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + is-typed-array: "npm:^1.1.13" + checksum: 10/357e9a48fa38f369fd6c4c3b632a3ab2b8adca14997db2e4b3fe94c4cd0a709af48e0fb61b02c64a90c0dd542fd489d49c2d03157b05ae6c07f5e4dec9e730a8 + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10/3a811b2c3176fb31abee1d23d3dc78b6c65fd9c07d591fcb67553cab9e7f272728c3dd077d2d738b53f9a2103255b0a6e8dfc9568a7805c56a78b2563e8d1dec + languageName: node + linkType: hard + +"is-docker@npm:^2.0.0": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 10/3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10/df033653d06d0eb567461e58a7a8c9f940bd8c22274b94bf7671ab36df5719791aae15eef6d83bbb5e23283967f2f984b8914559d4449efda578c775c4be6f85 + languageName: node + linkType: hard + +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10/0bfb145e9a1ba852ddde423b0926d2169ae5fe9e37882cde9e8f69031281a986308df4d982283e152396e88b86562ed2256cbaa5e6390fb840a4c25ab54b8a80 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^1.0.0": + version: 1.0.0 + resolution: "is-fullwidth-code-point@npm:1.0.0" + dependencies: + number-is-nan: "npm:^1.0.0" + checksum: 10/4d46a7465a66a8aebcc5340d3b63a56602133874af576a9ca42c6f0f4bd787a743605771c5f246db77da96605fefeffb65fc1dbe862dcc7328f4b4d03edf5a57 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^2.0.0": + version: 2.0.0 + resolution: "is-fullwidth-code-point@npm:2.0.0" + checksum: 10/eef9c6e15f68085fec19ff6a978a6f1b8f48018fd1265035552078ee945573594933b09bbd6f562553e2a241561439f1ef5339276eba68d272001343084cfab8 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10/44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.10": + version: 1.1.0 + resolution: "is-generator-function@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.0" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10/5906ff51a856a5fbc6b90a90fce32040b0a6870da905f98818f1350f9acadfc9884f7c3dec833fce04b83dd883937b86a190b6593ede82e8b1af8b6c4ecf7cbd + languageName: node + linkType: hard + +"is-glob@npm:^4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10/3ed74f2b0cdf4f401f38edb0442ddfde3092d79d7d35c9919c86641efdbcbb32e45aa3c0f70ce5eecc946896cd5a0f26e4188b9f2b881876f7cb6c505b82da11 + languageName: node + linkType: hard + +"is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10/8de7b41715b08bcb0e5edb0fb9384b80d2d5bcd10e142188f33247d19ff078abaf8e9b6f858e2302d8d05376a26a55cd23a3c9f8ab93292b02fcd2cc9e4e92bb + languageName: node + linkType: hard + +"is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10/a5922fb8779ab1ea3b8a9c144522b3d0bea5d9f8f23f7a72470e61e1e4df47714e28e0154ac011998b709cce260c3c9447ad3cd24a96c2f2a0abfdb2cbdc76c8 + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10/6a6c3383f68afa1e05b286af866017c78f1226d43ac8cb064e115ff9ed85eb33f5c4f7216c96a71e4dfea289ef52c5da3aef5bbfade8ffe47a0465d70c0c8e86 + languageName: node + linkType: hard + +"is-observable@npm:^1.1.0": + version: 1.1.0 + resolution: "is-observable@npm:1.1.0" + dependencies: + symbol-observable: "npm:^1.1.0" + checksum: 10/ab3d7e740915e6b53a81d96ce7d581f4dd26dacceb95278b74e7bf3123221073ea02cde810f864cff94ed5c394f18248deefd6a8f2d40137d868130eb5be6f85 + languageName: node + linkType: hard + +"is-promise@npm:^2.1.0": + version: 2.2.2 + resolution: "is-promise@npm:2.2.2" + checksum: 10/18bf7d1c59953e0ad82a1ed963fb3dc0d135c8f299a14f89a17af312fc918373136e56028e8831700e1933519630cc2fd4179a777030330fde20d34e96f40c78 + languageName: node + linkType: hard + +"is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/c42b7efc5868a5c9a4d8e6d3e9816e8815c611b09535c00fead18a1138455c5cb5e1887f0023a467ad3f9c419d62ba4dc3d9ba8bafe55053914d6d6454a945d2 + languageName: node + linkType: hard + +"is-retry-allowed@npm:^1.1.0": + version: 1.2.0 + resolution: "is-retry-allowed@npm:1.2.0" + checksum: 10/50d700a89ae31926b1c91b3eb0104dbceeac8790d8b80d02f5c76d9a75c2056f1bb24b5268a8a018dead606bddf116b2262e5ac07401eb8b8783b266ed22558d + languageName: node + linkType: hard + +"is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10/5685df33f0a4a6098a98c72d94d67cad81b2bc72f1fb2091f3d9283c4a1c582123cd709145b02a9745f0ce6b41e3e43f1c944496d1d74d4ea43358be61308669 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10/0380d7c60cc692856871526ffcd38a8133818a2ee42d47bb8008248a0cd2121d8c8b5f66b6da3cac24bc5784553cacb6faaf678f66bc88c6615b42af2825230e + languageName: node + linkType: hard + +"is-ssh@npm:^1.3.0": + version: 1.4.1 + resolution: "is-ssh@npm:1.4.1" + dependencies: + protocols: "npm:^2.0.1" + checksum: 10/f60910cd83fa94e9874655a672c3849312c12af83c0fe3dbff9945755fe838a73985d8f94e32ebf5626ba4148ee10eef51b7240b0218dbb6e9a43a06899b0529 + languageName: node + linkType: hard + +"is-stream@npm:^1.1.0": + version: 1.1.0 + resolution: "is-stream@npm:1.1.0" + checksum: 10/351aa77c543323c4e111204482808cfad68d2e940515949e31ccd0b010fc13d5fba4b9c230e4887fd24284713040f43e542332fbf172f6b9944b7d62e389c0ec + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10/b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 + languageName: node + linkType: hard + +"is-string@npm:^1.0.7, is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10/5277cb9e225a7cc8a368a72623b44a99f2cfa139659c6b203553540681ad4276bfc078420767aad0e73eef5f0bd07d4abf39a35d37ec216917879d11cebc1f8b + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + safe-regex-test: "npm:^1.1.0" + checksum: 10/db495c0d8cd0a7a66b4f4ef7fccee3ab5bd954cb63396e8ac4d32efe0e9b12fdfceb851d6c501216a71f4f21e5ff20fc2ee845a3d52d455e021c466ac5eb2db2 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10/e8cf60b9ea85667097a6ad68c209c9722cfe8c8edf04d6218366469e51944c5cc25bae45ffb845c23f811d262e4314d3b0168748eb16711aa34d12724cdf0735 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: 10/a7b7e23206c542dcf2fa0abc483142731788771527e90e7e24f658c0833a0d91948a4f7b30d78f7a65255a48512e41a0288b778ba7fc396137515c12e201fd11 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10/543506fd8259038b371bb083aac25b16cb4fd8b12fc58053aa3d45ac28dfd001cd5c6dffbba7aeea4213c74732d46b6cb2cfb5b412eed11f2db524f3f97d09a0 + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.4 + resolution: "is-weakset@npm:2.0.4" + dependencies: + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10/1d5e1d0179beeed3661125a6faa2e59bfb48afda06fc70db807f178aa0ebebc3758fb6358d76b3d528090d5ef85148c345dcfbf90839592fe293e3e5e82f2134 + languageName: node + linkType: hard + +"is-wsl@npm:^2.1.1, is-wsl@npm:^2.2.0": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: "npm:^2.0.0" + checksum: 10/20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10/1d8bc7911e13bb9f105b1b3e0b396c787a9e63046af0b8fe0ab1414488ab06b2b099b87a2d8a9e31d21c9a6fad773c7fc8b257c4880f2d957274479d28ca3414 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10/7c9f715c03aff08f35e98b1fadae1b9267b38f0615d501824f9743f3aab99ef10e303ce7db3f186763a0b70a19de5791ebfc854ff884d5a8c4d92211f642ec92 + languageName: node + linkType: hard + +"jake@npm:^10.8.5": + version: 10.9.2 + resolution: "jake@npm:10.9.2" + dependencies: + async: "npm:^3.2.3" + chalk: "npm:^4.0.2" + filelist: "npm:^1.0.4" + minimatch: "npm:^3.1.2" + bin: + jake: bin/cli.js + checksum: 10/3be324708f99f031e0aec49ef8fd872eb4583cbe8a29a0c875f554f6ac638ee4ea5aa759bb63723fd54f77ca6d7db851eaa78353301734ed3700db9cb109a0cd + languageName: node + linkType: hard + +"java-properties@npm:^1.0.0": + version: 1.0.2 + resolution: "java-properties@npm:1.0.2" + checksum: 10/d6e8bf8a28a8782afadbcebf2504ab8ea2c75d3675d7eec470920f6c056fd90c8a35a2705cd492a07ec3b2309d3d848ff4cfae098a2cda33a922153eed4bef6a + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10/af37d0d913fb56aec6dc0074c163cc71cd23c0b8aad5c2350747b6721d37ba118af35abdd8b33c47ec2800de07dedb16a527ca9c530ee004093e04958bd0cbf2 + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1, js-yaml@npm:^3.14.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10/9e22d80b4d0105b9899135365f746d47466ed53ef4223c529b3c0f7a39907743fdbd3c4379f94f1106f02755b5e90b2faaf84801a891135544e1ea475d1a1379 + languageName: node + linkType: hard + +"jsesc@npm:^2.5.1": + version: 2.5.2 + resolution: "jsesc@npm:2.5.2" + bin: + jsesc: bin/jsesc + checksum: 10/d2096abdcdec56969764b40ffc91d4a23408aa2f351b4d1c13f736f25476643238c43fdbaf38a191c26b1b78fd856d965f5d4d0dde7b89459cd94025190cdf13 + languageName: node + linkType: hard + +"json-parse-better-errors@npm:^1.0.1": + version: 1.0.2 + resolution: "json-parse-better-errors@npm:1.0.2" + checksum: 10/5553232045359b767b0f2039a6777fede1a8d7dca1a0ffb1f9ef73a7519489ae7f566b2e040f2b4c38edb8e35e37ae07af7f0a52420902f869ee0dbf5dc6c784 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10/5f3a99009ed5f2a5a67d06e2f298cc97bc86d462034173308156f15b43a6e850be8511dc204b9b94566305da2947f7d90289657237d210351a39059ff9d666cf + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10/02f2f466cdb0362558b2f1fd5e15cce82ef55d60cd7f8fa828cf35ba74330f8d767fcae5c5c2adb7851fa811766c694b9405810879bc4e1ddd78a7c0e03658ad + languageName: node + linkType: hard + +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 10/59169a081e4eeb6f9559ae1f938f656191c000e0512aa6df9f3c8b2437a4ab1823819c6b9fd1818a4e39593ccfd72e9a051fdd3e2d1e340ed913679e888ded8c + languageName: node + linkType: hard + +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.6" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10/17796f0ab1be8479827d3683433f97ebe0a1c6932c3360fa40348eac36904d69269aab26f8b16da311882d94b42e9208e8b28e490bf926364f3ac9bff134c226 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.1.0 + resolution: "jsonfile@npm:6.1.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10/03014769e7dc77d4cf05fa0b534907270b60890085dd5e4d60a382ff09328580651da0b8b4cdf44d91e4c8ae64d91791d965f05707beff000ed494a38b6fec85 + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10/0c37f9f7fa212b38912b7145e1cd16a5f3cd34d782441c3e6ca653485d326f58b3caccda66efce1c5812bde4961bbde3374fae4b0d11bf1226152337f3894aa5 + languageName: node + linkType: hard + +"listr-silent-renderer@npm:^1.1.1": + version: 1.1.1 + resolution: "listr-silent-renderer@npm:1.1.1" + checksum: 10/81982612e4d207be2e69c4dcf2a6e0aaa6080e41bfe0b73e8d0b040dcdb79874248b1040558793a2f0fcc9c2252ec8af47379650f59bf2a7656c11cd5a48c948 + languageName: node + linkType: hard + +"listr-update-renderer@npm:^0.5.0": + version: 0.5.0 + resolution: "listr-update-renderer@npm:0.5.0" + dependencies: + chalk: "npm:^1.1.3" + cli-truncate: "npm:^0.2.1" + elegant-spinner: "npm:^1.0.1" + figures: "npm:^1.7.0" + indent-string: "npm:^3.0.0" + log-symbols: "npm:^1.0.2" + log-update: "npm:^2.3.0" + strip-ansi: "npm:^3.0.1" + peerDependencies: + listr: ^0.14.2 + checksum: 10/2dddc763837a9086a684545ee9049fcb102d423b0c840ad929471ab461075ed78d5c79f1e8334cd7a76aa9076e7631c04a38733bb4d88c23ca6082c087335864 + languageName: node + linkType: hard + +"listr-verbose-renderer@npm:^0.5.0": + version: 0.5.0 + resolution: "listr-verbose-renderer@npm:0.5.0" + dependencies: + chalk: "npm:^2.4.1" + cli-cursor: "npm:^2.1.0" + date-fns: "npm:^1.27.2" + figures: "npm:^2.0.0" + checksum: 10/3e504be729f9dd15b40db743e403673b76331774411dbc29d6f48136f6ba8bc1dee645a4e621c1cb781e6e69a58b78cb9aa8c153c7ceccfe4e4ea74d563bca3a + languageName: node + linkType: hard + +"listr@npm:0.14.3": + version: 0.14.3 + resolution: "listr@npm:0.14.3" + dependencies: + "@samverschueren/stream-to-observable": "npm:^0.3.0" + is-observable: "npm:^1.1.0" + is-promise: "npm:^2.1.0" + is-stream: "npm:^1.1.0" + listr-silent-renderer: "npm:^1.1.1" + listr-update-renderer: "npm:^0.5.0" + listr-verbose-renderer: "npm:^0.5.0" + p-map: "npm:^2.0.0" + rxjs: "npm:^6.3.3" + checksum: 10/6d5dc899c62b240bd28a22c26e88cf005696786a28e7239adbe044fd9ebcb5261b1503a555c8ba7f45b10d0eabb5d159c91791daee83d803b4caf64fd8adbdf9 + languageName: node + linkType: hard + +"load-json-file@npm:^5.3.0": + version: 5.3.0 + resolution: "load-json-file@npm:5.3.0" + dependencies: + graceful-fs: "npm:^4.1.15" + parse-json: "npm:^4.0.0" + pify: "npm:^4.0.1" + strip-bom: "npm:^3.0.0" + type-fest: "npm:^0.3.0" + checksum: 10/8bf15599db9471e264d916f98f1f51eb5d1e6a26d0ec3711d17df54d5983ccba1a0a4db2a6490bb27171f1261b72bf237d557f34e87d26e724472b92bdbdd4f7 + languageName: node + linkType: hard + +"lodash.debounce@npm:^4.0.8": + version: 4.0.8 + resolution: "lodash.debounce@npm:4.0.8" + checksum: 10/cd0b2819786e6e80cb9f5cda26b1a8fc073daaf04e48d4cb462fa4663ec9adb3a5387aa22d7129e48eed1afa05b482e2a6b79bfc99b86886364449500cbb00fd + languageName: node + linkType: hard + +"lodash.get@npm:^4": + version: 4.4.2 + resolution: "lodash.get@npm:4.4.2" + checksum: 10/2a4925f6e89bc2c010a77a802d1ba357e17ed1ea03c2ddf6a146429f2856a216663e694a6aa3549a318cbbba3fd8b7decb392db457e6ac0b83dc745ed0a17380 + languageName: node + linkType: hard + +"lodash.identity@npm:3.0.0": + version: 3.0.0 + resolution: "lodash.identity@npm:3.0.0" + checksum: 10/cd0365f99cafba23237e7c7d33123ac670ebbaacb4e9213fdddb7a68f317a261bedea93c9b57500942bbccb3fa2dc4e42a388c6024cc00a18937a922dacd68a1 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.1": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10/d0ea2dd0097e6201be083865d50c3fb54fbfbdb247d9cc5950e086c991f448b7ab0cdab0d57eacccb43473d3f2acd21e134db39f22dac2d6c9ba6bf26978e3d6 + languageName: node + linkType: hard + +"lodash.pickby@npm:4.6.0": + version: 4.6.0 + resolution: "lodash.pickby@npm:4.6.0" + checksum: 10/694ec55e0df6bb683e84606eae87f69302b49c28b50988264b2f2f333bd9f008e91c2092f1c14e770f0398b2fa2a2e8d19efb6c4c54b40a8e7951ed2929fd1ba + languageName: node + linkType: hard + +"lodash.sortby@npm:^4.7.0": + version: 4.7.0 + resolution: "lodash.sortby@npm:4.7.0" + checksum: 10/38df19ae28608af2c50ac342fc1f414508309d53e1d58ed9adfb2c3cd17c3af290058c0a0478028d932c5404df3d53349d19fa364ef6bed6145a6bc21320399e + languageName: node + linkType: hard + +"lodash.truncate@npm:^4.4.2": + version: 4.4.2 + resolution: "lodash.truncate@npm:4.4.2" + checksum: 10/7a495616121449e5d2288c606b1025d42ab9979e8c93ba885e5c5802ffd4f1ebad4428c793ccc12f73e73237e85a9f5b67dd6415757546fbd5a4653ba83e25ac + languageName: node + linkType: hard + +"lodash.xorby@npm:^4.7.0": + version: 4.7.0 + resolution: "lodash.xorby@npm:4.7.0" + checksum: 10/6005e6ee52166d7ff17c00c68fd247729e768400a57b04bd8eb01f8e25c73399c35ee6ac6857438fe0f3697385f975284c62d79a540f838b7bb745c58ac830e0 + languageName: node + linkType: hard + +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 + languageName: node + linkType: hard + +"log-symbols@npm:^1.0.2": + version: 1.0.2 + resolution: "log-symbols@npm:1.0.2" + dependencies: + chalk: "npm:^1.0.0" + checksum: 10/5214ade9381db5d40528c171fdfd459b75cad7040eb6a347294ae47fa80cfebba4adbc3aa73a1c9da744cbfa240dd93b38f80df8615717affeea6c4bb6b8dfe7 + languageName: node + linkType: hard + +"log-update@npm:^2.3.0": + version: 2.3.0 + resolution: "log-update@npm:2.3.0" + dependencies: + ansi-escapes: "npm:^3.0.0" + cli-cursor: "npm:^2.0.0" + wrap-ansi: "npm:^3.0.1" + checksum: 10/84fd8e93bfc316eb6ca479a37743f2edcb7563fe5b9161205ce2980f0b3c822717b8f8f1871369697fcb0208521d7b8d00750c594edc3f8a8273dd8b48dd14a3 + languageName: node + linkType: hard + +"lower-case@npm:^2.0.2": + version: 2.0.2 + resolution: "lower-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/83a0a5f159ad7614bee8bf976b96275f3954335a84fad2696927f609ddae902802c4f3312d86668722e668bef41400254807e1d3a7f2e8c3eede79691aa1f010 + languageName: node + linkType: hard + +"lru-cache@npm:7.10.1 - 7.13.1": + version: 7.13.1 + resolution: "lru-cache@npm:7.13.1" + checksum: 10/81ebb3f1fd3e1d3c32762a58c5964364220fc4b413f5180588b74473bd9a075cdafee32421f8ee6105148c8986d183bf40539017dea03abed32f4e1e59bf2654 + languageName: node + linkType: hard + +"make-error@npm:^1, make-error@npm:^1.1.1": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 10/b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 + languageName: node + linkType: hard + +"matcher@npm:^3.0.0": + version: 3.0.0 + resolution: "matcher@npm:3.0.0" + dependencies: + escape-string-regexp: "npm:^4.0.0" + checksum: 10/8bee1a7ab7609c2c21d9c9254b6785fa708eadf289032b556d57a34e98fcd4c537659a004dafee6ce80ab157099e645c199dc52678dff1e7fb0a6684e0da4dbe + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10/6fa4dcc8d86629705cea944a4b88ef4cb0e07656ebf223fa287443256414283dd25d91c1cd84c77987f2aec5927af1a9db6085757cb43d90eb170ebf4b47f4f4 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10/7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10/6bf2a01672e7965eb9941d1f02044fad2bd12486b5553dc1116ff24c09a8723157601dc992e74c911d896175918448762df3b3fd0a6b61037dd1a9766ddfbf58 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10/54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10/89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a + languageName: node + linkType: hard + +"mimic-fn@npm:^1.0.0": + version: 1.2.0 + resolution: "mimic-fn@npm:1.2.0" + checksum: 10/69c08205156a1f4906d9c46f9b4dc08d18a50176352e77fdeb645cedfe9f20c0b19865d465bd2dec27a5c432347f24dc07fc3695e11159d193f892834233e939 + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10/d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634 + languageName: node + linkType: hard + +"minimatch@npm:^5.0.0, minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/126b36485b821daf96d33b5c821dac600cc1ab36c87e7a532594f9b1652b1fa89a1eebcaad4dff17c764dce1a7ac1531327f190fed5f97d8f6e5f889c116c429 + languageName: node + linkType: hard + +"minimatch@npm:~3.0.2": + version: 3.0.8 + resolution: "minimatch@npm:3.0.8" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10/6df5373cb1ea79020beb6887ff5576c58cfabcfd32c5a65c2cf58f326e4ee8eae84f129e5fa50b8a4347fa1d1e583f931285c9fb3040d984bdfb5109ef6607ec + languageName: node + linkType: hard + +"mkdirp@npm:1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: 10/d71b8dcd4b5af2fe13ecf3bd24070263489404fe216488c5ba7e38ece1f54daf219e72a833a3a2dc404331e870e9f44963a33399589490956bff003a3404d3b2 + languageName: node + linkType: hard + +"moment@npm:2.29.3": + version: 2.29.3 + resolution: "moment@npm:2.29.3" + checksum: 10/311a26f7e476fb502f902a45b1c91aabc1b866ff8398bc90909135e6f312562eedd4a2287c28d2f7c3dda63e848882a1b505596dd76929b49b9d0176a5e141eb + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + +"natural-orderby@npm:^2.0.1, natural-orderby@npm:^2.0.3": + version: 2.0.3 + resolution: "natural-orderby@npm:2.0.3" + checksum: 10/b0c982709cab2133e65ab2ca9c44cdbca972b5d72886a75fa3afac159b736a586e582e5de46bd04d0f3d5ab6f9d0447e6a5a8c4392de017ac67e6638b317e4aa + languageName: node + linkType: hard + +"no-case@npm:^3.0.4": + version: 3.0.4 + resolution: "no-case@npm:3.0.4" + dependencies: + lower-case: "npm:^2.0.2" + tslib: "npm:^2.0.3" + checksum: 10/0b2ebc113dfcf737d48dde49cfebf3ad2d82a8c3188e7100c6f375e30eafbef9e9124aadc3becef237b042fd5eb0aad2fd78669c20972d045bbe7fea8ba0be5c + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10/b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676 + languageName: node + linkType: hard + +"normalize-url@npm:^6.1.0": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 10/5ae699402c9d5ffa330adc348fcd6fc6e6a155ab7c811b96e30b7ecab60ceef821d8f86443869671dda71bbc47f4b9625739c82ad247e883e9aefe875bfb8659 + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10/5374c0cea4b0bbfdfae62da7bbdf1e1558d338335f4cacf2515c282ff358ff27b2ecb91ffa5330a8b14390ac66a1e146e10700440c1ab868208430f56b5f4d23 + languageName: node + linkType: hard + +"number-is-nan@npm:^1.0.0": + version: 1.0.1 + resolution: "number-is-nan@npm:1.0.1" + checksum: 10/13656bc9aa771b96cef209ffca31c31a03b507ca6862ba7c3f638a283560620d723d52e626d57892c7fff475f4c36ac07f0600f14544692ff595abff214b9ffb + languageName: node + linkType: hard + +"object-assign@npm:^4.1.0": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 10/3d81d02674115973df0b7117628ea4110d56042e5326413e4b4313f0bcdf7dd78d4a3acef2c831463fa3796a66762c49daef306f4a0ea1af44877d7086d73bde + languageName: node + linkType: hard + +"object-treeify@npm:^1.1.33, object-treeify@npm:^1.1.4": + version: 1.1.33 + resolution: "object-treeify@npm:1.1.33" + checksum: 10/1c7865240037d7c2d39e28b96598538af59b545dc49cfc45d8c0a96baa343fc3335cbef26ede8c6dc48073368ec16bf194c276ffdedf32b41f3c3c8ef4d27fef + languageName: node + linkType: hard + +"object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + object-keys: "npm:^1.1.1" + checksum: 10/3fe28cdd779f2a728a9a66bd688679ba231a2b16646cd1e46b528fe7c947494387dda4bc189eff3417f3717ef4f0a8f2439347cf9a9aa3cef722fbfd9f615587 + languageName: node + linkType: hard + +"object.getownpropertydescriptors@npm:^2.1.1": + version: 2.1.8 + resolution: "object.getownpropertydescriptors@npm:2.1.8" + dependencies: + array.prototype.reduce: "npm:^1.0.6" + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + gopd: "npm:^1.0.1" + safe-array-concat: "npm:^1.1.2" + checksum: 10/8c50f52e0d702d30836f3d2772ba02807ca25a5381be6f9470c6d143ee0bad01bce3fff0fedea2bdbc0c9297e4eb7785ffee5739f6a3a7c60fcd622b42f8a9fb + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10/cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 + languageName: node + linkType: hard + +"onetime@npm:^2.0.0": + version: 2.0.1 + resolution: "onetime@npm:2.0.1" + dependencies: + mimic-fn: "npm:^1.0.0" + checksum: 10/5b4f6079e6b4973244017e157833ab5a7a3de4bd2612d69411e3ee46f61fe8bb57b7c2e243b0b23dbaa5bad7641a15f9100a5c80295ff64c0d87aab5d1576ef9 + languageName: node + linkType: hard + +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10/e9fd0695a01cf226652f0385bf16b7a24153dbbb2039f764c8ba6d2306a8506b0e4ce570de6ad99c7a6eb49520743afdb66edd95ee979c1a342554ed49a9aadd + languageName: node + linkType: hard + +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10/ab4bb3b8636908554fc19bf899e225444195092864cb61503a0d048fdaf662b04be2605b636a4ffeaf6e8811f6fcfa8cbb210ec964c0eb1a41eb853e1d5d2f41 + languageName: node + linkType: hard + +"p-map@npm:^2.0.0": + version: 2.1.0 + resolution: "p-map@npm:2.1.0" + checksum: 10/9e3ad3c9f6d75a5b5661bcad78c91f3a63849189737cd75e4f1225bf9ac205194e5c44aac2ef6f09562b1facdb9bd1425584d7ac375bfaa17b3f1a142dab936d + languageName: node + linkType: hard + +"param-case@npm:^3.0.4": + version: 3.0.4 + resolution: "param-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/b34227fd0f794e078776eb3aa6247442056cb47761e9cd2c4c881c86d84c64205f6a56ef0d70b41ee7d77da02c3f4ed2f88e3896a8fefe08bdfb4deca037c687 + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10/6ba8b255145cae9470cf5551eb74be2d22281587af787a2626683a6c20fbb464978784661478dd2a3f1dad74d1e802d403e1b03c1a31fab310259eec8ac560ff + languageName: node + linkType: hard + +"parse-json@npm:^4.0.0": + version: 4.0.0 + resolution: "parse-json@npm:4.0.0" + dependencies: + error-ex: "npm:^1.3.1" + json-parse-better-errors: "npm:^1.0.1" + checksum: 10/0fe227d410a61090c247e34fa210552b834613c006c2c64d9a05cfe9e89cf8b4246d1246b1a99524b53b313e9ac024438d0680f67e33eaed7e6f38db64cfe7b5 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10/62085b17d64da57f40f6afc2ac1f4d95def18c4323577e1eced571db75d9ab59b297d1d10582920f84b15985cbfc6b6d450ccbf317644cfa176f3ed982ad87e2 + languageName: node + linkType: hard + +"parse-path@npm:^4.0.0": + version: 4.0.4 + resolution: "parse-path@npm:4.0.4" + dependencies: + is-ssh: "npm:^1.3.0" + protocols: "npm:^1.4.0" + qs: "npm:^6.9.4" + query-string: "npm:^6.13.8" + checksum: 10/fe07fe22fc0a19ce40168fc1cd5fd4f74bff7065c805a57c20d8ff96640dc91dc126d6a7e37ec4fd043edda797a72b1a494a574cc35704e92e9d0007cdd8628d + languageName: node + linkType: hard + +"parse-url@npm:^6.0.0": + version: 6.0.5 + resolution: "parse-url@npm:6.0.5" + dependencies: + is-ssh: "npm:^1.3.0" + normalize-url: "npm:^6.1.0" + parse-path: "npm:^4.0.0" + protocols: "npm:^1.4.0" + checksum: 10/f79a6da2d7044b6723fee0db823436266c4ccc28da501987fa00c552b654a126faf5f32c2e69453cab2ae5557f5d44afffaf2eaebef45e6863c14a9c2d3e703b + languageName: node + linkType: hard + +"pascal-case@npm:^3.1.2": + version: 3.1.2 + resolution: "pascal-case@npm:3.1.2" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/ba98bfd595fc91ef3d30f4243b1aee2f6ec41c53b4546bfa3039487c367abaa182471dcfc830a1f9e1a0df00c14a370514fa2b3a1aacc68b15a460c31116873e + languageName: node + linkType: hard + +"password-prompt@npm:^1.1.2": + version: 1.1.3 + resolution: "password-prompt@npm:1.1.3" + dependencies: + ansi-escapes: "npm:^4.3.2" + cross-spawn: "npm:^7.0.3" + checksum: 10/1cf7001e66868b2ed7a03e036bc2f1dd45eb6dc8fee7e3e2056370057c484be25e7468fee00a1378e1ee8eca77ba79f48bee5ce15dcb464413987ace63c68b35 + languageName: node + linkType: hard + +"path-case@npm:^3.0.4": + version: 3.0.4 + resolution: "path-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/61de0526222629f65038a66f63330dd22d5b54014ded6636283e1d15364da38b3cf29e4433aa3f9d8b0dba407ae2b059c23b0104a34ee789944b1bc1c5c7e06d + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10/060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8 + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10/55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10/49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10/5b1e2daa247062061325b8fdbfd1fb56dde0a448fb1455453276ea18c60685bdad23a445dc148cf87bc216be1573357509b7d4060494a6fd768c7efad833ee45 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 + languageName: node + linkType: hard + +"picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc + languageName: node + linkType: hard + +"pify@npm:^4.0.1": + version: 4.0.1 + resolution: "pify@npm:4.0.1" + checksum: 10/8b97cbf9dc6d4c1320cc238a2db0fc67547f9dc77011729ff353faf34f1936ea1a4d7f3c63b2f4980b253be77bcc72ea1e9e76ee3fd53cce2aafb6a8854d07ec + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10/2f44137b8d3dd35f4a7ba7469eec1cd9cfbb46ec164b93a5bc1f4c3d68599c9910ee3b91da1d28b4560e9cc8414c3cd56fedc07259c67e52cc774476270d3302 + languageName: node + linkType: hard + +"protocols@npm:^1.4.0": + version: 1.4.8 + resolution: "protocols@npm:1.4.8" + checksum: 10/2d555c013df0b05402970f67f7207c9955a92b1d13ffa503c814b5fe2f6dde7ac6a03320e0975c1f5832b0113327865e0b3b28bfcad023c25ddb54b53fab8684 + languageName: node + linkType: hard + +"protocols@npm:^2.0.1": + version: 2.0.2 + resolution: "protocols@npm:2.0.2" + checksum: 10/031cc068eb800468a50eb7c1e1c528bf142fb8314f5df9b9ea3c3f9df1697a19f97b9915b1229cef694d156812393172d9c3051ef7878d26eaa8c6faa5cccec4 + languageName: node + linkType: hard + +"qs@npm:^6.9.4": + version: 6.14.0 + resolution: "qs@npm:6.14.0" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 + languageName: node + linkType: hard + +"query-string@npm:^6.13.8": + version: 6.14.1 + resolution: "query-string@npm:6.14.1" + dependencies: + decode-uri-component: "npm:^0.2.0" + filter-obj: "npm:^1.1.0" + split-on-first: "npm:^1.0.0" + strict-uri-encode: "npm:^2.0.0" + checksum: 10/95f5a372f777b4fb5bdae5a2d85961cf3894d466cfc3a0cc799320d5ed633af935c0d96ee5d2b1652c02888e749831409ca5dd5eb388ce1014a9074024a22840 + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10/72900df0616e473e824202113c3df6abae59150dfb73ed13273503127235320e9c8ca4aaaaccfd58cf417c6ca92a6e68ee9a5c3182886ae949a768639b388a7b + languageName: node + linkType: hard + +"recast@npm:^0.21.0": + version: 0.21.5 + resolution: "recast@npm:0.21.5" + dependencies: + ast-types: "npm:0.15.2" + esprima: "npm:~4.0.0" + source-map: "npm:~0.6.1" + tslib: "npm:^2.0.1" + checksum: 10/b41da2bcf7e705511db2f27d17420ace027de8dd167de9f19190d4988a1f80d112f60c095101ac2f145c8657ddde0c5133eb71df20504efaf3fd9d76ad07e15d + languageName: node + linkType: hard + +"rechoir@npm:^0.6.2": + version: 0.6.2 + resolution: "rechoir@npm:0.6.2" + dependencies: + resolve: "npm:^1.1.6" + checksum: 10/fe76bf9c21875ac16e235defedd7cbd34f333c02a92546142b7911a0f7c7059d2e16f441fe6fb9ae203f459c05a31b2bcf26202896d89e390eda7514d5d2702b + languageName: node + linkType: hard + +"redeyed@npm:~2.1.0": + version: 2.1.1 + resolution: "redeyed@npm:2.1.1" + dependencies: + esprima: "npm:~4.0.0" + checksum: 10/86880f97d54bb55bbf1c338e27fe28f18f52afc2f5afa808354a09a3777aa79b4f04e04844350d7fec80aa2d299196bde256b21f586e7e5d9b63494bd4a9db27 + languageName: node + linkType: hard + +"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.1" + which-builtin-type: "npm:^1.2.1" + checksum: 10/80a4e2be716f4fe46a89a08ccad0863b47e8ce0f49616cab2d65dab0fbd53c6fdba0f52935fd41d37a2e4e22355c272004f920d63070de849f66eea7aeb4a081 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.3": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + set-function-name: "npm:^2.0.2" + checksum: 10/8ab897ca445968e0b96f6237641510f3243e59c180ee2ee8d83889c52ff735dd1bf3657fcd36db053e35e1d823dd53f2565d0b8021ea282c9fe62401c6c3bd6d + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10/839a3a890102a658f4cb3e7b2aa13a1f80a3a976b512020c3d1efc418491c48a886b6e481ea56afc6c4cb5eef678f23b2a4e70575e7534eccadf5e30ed2e56eb + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10/91eb76ce83621eea7bbdd9b55121a5c1c4a39e54a9ce04a9ad4517f102f8b5131c2cf07622c738a6683991bf54f2ce178f5a42803ecbd527ddc5105f362cc9e3 + languageName: node + linkType: hard + +"resolve@npm:^1.1.6": + version: 1.22.10 + resolution: "resolve@npm:1.22.10" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/0a398b44da5c05e6e421d70108822c327675febb880eebe905587628de401854c61d5df02866ff34fc4cb1173a51c9f0e84a94702738df3611a62e2acdc68181 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin": + version: 1.22.10 + resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/d4d878bfe3702d215ea23e75e0e9caf99468e3db76f5ca100d27ebdc527366fee3877e54bce7d47cc72ca8952fc2782a070d238bfa79a550eeb0082384c3b81a + languageName: node + linkType: hard + +"restore-cursor@npm:^2.0.0": + version: 2.0.0 + resolution: "restore-cursor@npm:2.0.0" + dependencies: + onetime: "npm:^2.0.0" + signal-exit: "npm:^3.0.2" + checksum: 10/482e13d02d834b6e5e3aa90304a8b5e840775d6f06916cc92a50038adf9f098dcc72405b567da8a37e137ae40ad3e31896fa3136ae62f7a426c2fbf53d036536 + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10/af47851b547e8a8dc89af144fceee17b80d5beaf5e6f57ed086432d79943434ff67ca526e92275be6f54b6189f6920a24eace75c2657eed32d02c400312b21ec + languageName: node + linkType: hard + +"roarr@npm:^2.15.3": + version: 2.15.4 + resolution: "roarr@npm:2.15.4" + dependencies: + boolean: "npm:^3.0.1" + detect-node: "npm:^2.0.4" + globalthis: "npm:^1.0.1" + json-stringify-safe: "npm:^5.0.1" + semver-compare: "npm:^1.0.0" + sprintf-js: "npm:^1.1.2" + checksum: 10/baaa5ad91468bf1b7f0263c4132a40865c8638a3d0916b44dd0d42980a77fb53085a3792e3edf16fc4eea9e31c719793c88bd45b1623b760763c4dc59df97619 + languageName: node + linkType: hard + +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + dependencies: + apollo: "npm:^2.34.0" + languageName: unknown + linkType: soft + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10/cb4f97ad25a75ebc11a8ef4e33bb962f8af8516bb2001082ceabd8902e15b98f4b84b4f8a9b222e5d57fc3bd1379c483886ed4619367a7680dad65316993021d + languageName: node + linkType: hard + +"rxjs@npm:^6.3.3": + version: 6.6.7 + resolution: "rxjs@npm:6.6.7" + dependencies: + tslib: "npm:^1.9.0" + checksum: 10/c8263ebb20da80dd7a91c452b9e96a178331f402344bbb40bc772b56340fcd48d13d1f545a1e3d8e464893008c5e306cc42a1552afe0d562b1a6d4e1e6262b03 + languageName: node + linkType: hard + +"safe-array-concat@npm:^1.1.2, safe-array-concat@npm:^1.1.3": + version: 1.1.3 + resolution: "safe-array-concat@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + has-symbols: "npm:^1.1.0" + isarray: "npm:^2.0.5" + checksum: 10/fac4f40f20a3f7da024b54792fcc61059e814566dcbb04586bfefef4d3b942b2408933f25b7b3dd024affd3f2a6bbc916bef04807855e4f192413941369db864 + languageName: node + linkType: hard + +"safe-buffer@npm:^5.0.1": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 + languageName: node + linkType: hard + +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10/2bd4e53b6694f7134b9cf93631480e7fafc8637165f0ee91d5a4af5e7f33d37de9562d1af5021178dd4217d0230cde8d6530fa28cfa1ebff9a431bf8fff124b4 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10/ebdb61f305bf4756a5b023ad86067df5a11b26898573afe9e52a548a63c3bd594825d9b0e2dde2eb3c94e57e0e04ac9929d4107c394f7b8e56a4613bed46c69a + languageName: node + linkType: hard + +"semver-compare@npm:^1.0.0": + version: 1.0.0 + resolution: "semver-compare@npm:1.0.0" + checksum: 10/75f9c7a7786d1756f64b1429017746721e07bd7691bdad6368f7643885d3a98a27586777e9699456564f4844b407e9f186cc1d588a3f9c0be71310e517e942c3 + languageName: node + linkType: hard + +"semver@npm:^7.3.2, semver@npm:^7.3.7": + version: 7.7.1 + resolution: "semver@npm:7.7.1" + bin: + semver: bin/semver.js + checksum: 10/4cfa1eb91ef3751e20fc52e47a935a0118d56d6f15a837ab814da0c150778ba2ca4f1a4d9068b33070ea4273629e615066664c2cfcd7c272caf7a8a0f6518b2c + languageName: node + linkType: hard + +"sentence-case@npm:^3.0.4": + version: 3.0.4 + resolution: "sentence-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10/3cfe6c0143e649132365695706702d7f729f484fa7b25f43435876efe7af2478243eefb052bacbcce10babf9319fd6b5b6bc59b94c80a1c819bcbb40651465d5 + languageName: node + linkType: hard + +"serialize-error@npm:^7.0.1": + version: 7.0.1 + resolution: "serialize-error@npm:7.0.1" + dependencies: + type-fest: "npm:^0.13.1" + checksum: 10/e0aba4dca2fc9fe74ae1baf38dbd99190e1945445a241ba646290f2176cdb2032281a76443b02ccf0caf30da5657d510746506368889a593b9835a497fc0732e + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/505d62b8e088468917ca4e3f8f39d0e29f9a563b97dbebf92f4bd2c3172ccfb3c5b8e4566d5fcd00784a00433900e7cb8fbc404e2dbd8c3818ba05bb9d4a8a6d + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/c7614154a53ebf8c0428a6c40a3b0b47dac30587c1a19703d1b75f003803f73cdfa6a93474a9ba678fa565ef5fbddc2fae79bca03b7d22ab5fd5163dbe571a74 + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10/b87f8187bca595ddc3c0721ece4635015fd9d7cb294e6dd2e394ce5186a71bbfa4dc8a35010958c65e43ad83cde09642660e61a952883c24fd6b45ead15f045c + languageName: node + linkType: hard + +"sha.js@npm:^2.4.11": + version: 2.4.11 + resolution: "sha.js@npm:2.4.11" + dependencies: + inherits: "npm:^2.0.1" + safe-buffer: "npm:^5.0.1" + bin: + sha.js: ./bin.js + checksum: 10/d833bfa3e0a67579a6ce6e1bc95571f05246e0a441dd8c76e3057972f2a3e098465687a4369b07e83a0375a88703577f71b5b2e966809e67ebc340dbedb478c7 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10/6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10/1a2bcae50de99034fcd92ad4212d8e01eedf52c7ec7830eedcf886622804fe36884278f2be8be0ea5fde3fd1c23911643a4e0f726c8685b61871c8908af01222 + languageName: node + linkType: hard + +"shelljs@npm:0.8.5": + version: 0.8.5 + resolution: "shelljs@npm:0.8.5" + dependencies: + glob: "npm:^7.0.0" + interpret: "npm:^1.0.0" + rechoir: "npm:^0.6.2" + bin: + shjs: bin/shjs + checksum: 10/f2178274b97b44332bbe9ddb78161137054f55ecf701c7a99db9552cb5478fe279ad5f5131d8a7c2f0730e01ccf0c629d01094143f0541962ce1a3d0243d23f7 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10/603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10/5771861f77feefe44f6195ed077a9e4f389acc188f895f570d56445e251b861754b547ea9ef73ecee4e01fdada6568bfe9020d2ec2dfc5571e9fa1bbc4a10615 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10/a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10/7d53b9db292c6262f326b6ff3bc1611db84ece36c2c7dc0e937954c13c73185b0406c56589e2bb8d071d6fee468e14c39fb5d203ee39be66b7b8174f179afaba + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10/94a93fff615f25a999ad4b83c9d5e257a7280c90a32a7cb8b4a87996e4babf322e469c42b7f649fd5796edd8687652f3fb452a86dc97a816f01113183393f11c + languageName: node + linkType: hard + +"slice-ansi@npm:0.0.4": + version: 0.0.4 + resolution: "slice-ansi@npm:0.0.4" + checksum: 10/481d969c6aa771b27d7baacd6fe321751a0b9eb410274bda10ca81ea641bbfe747e428025d6d8f15bd635fdcfd57e8b2d54681ee6b0ce0c40f78644b144759e3 + languageName: node + linkType: hard + +"slice-ansi@npm:^4.0.0": + version: 4.0.0 + resolution: "slice-ansi@npm:4.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + astral-regex: "npm:^2.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + checksum: 10/4a82d7f085b0e1b070e004941ada3c40d3818563ac44766cca4ceadd2080427d337554f9f99a13aaeb3b4a94d9964d9466c807b3d7b7541d1ec37ee32d308756 + languageName: node + linkType: hard + +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/0a7a79900bbb36f8aaa922cf111702a3647ac6165736d5dc96d3ef367efc50465cac70c53cd172c382b022dac72ec91710608e5393de71f76d7142e6fd80e8a3 + languageName: node + linkType: hard + +"source-map-support@npm:^0.5.17": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10/8317e12d84019b31e34b86d483dd41d6f832f389f7417faf8fc5c75a66a12d9686e47f589a0554a868b8482f037e23df9d040d29387eb16fa14cb85f091ba207 + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:~0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff + languageName: node + linkType: hard + +"split-on-first@npm:^1.0.0": + version: 1.1.0 + resolution: "split-on-first@npm:1.1.0" + checksum: 10/16ff85b54ddcf17f9147210a4022529b343edbcbea4ce977c8f30e38408b8d6e0f25f92cd35b86a524d4797f455e29ab89eb8db787f3c10708e0b47ebf528d30 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.2": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10/c34828732ab8509c2741e5fd1af6b767c3daf2c642f267788f933a65b1614943c282e74c4284f4fa749c264b18ee016a0d37a3e5b73aee446da46277d3a85daa + languageName: node + linkType: hard + +"strict-uri-encode@npm:^2.0.0": + version: 2.0.0 + resolution: "strict-uri-encode@npm:2.0.0" + checksum: 10/eaac4cf978b6fbd480f1092cab8b233c9b949bcabfc9b598dd79a758f7243c28765ef7639c876fa72940dac687181b35486ea01ff7df3e65ce3848c64822c581 + languageName: node + linkType: hard + +"string-width@npm:^1.0.1": + version: 1.0.2 + resolution: "string-width@npm:1.0.2" + dependencies: + code-point-at: "npm:^1.0.0" + is-fullwidth-code-point: "npm:^1.0.0" + strip-ansi: "npm:^3.0.0" + checksum: 10/5c79439e95bc3bd7233a332c5f5926ab2ee90b23816ed4faa380ce3b2576d7800b0a5bb15ae88ed28737acc7ea06a518c2eef39142dd727adad0e45c776cd37e + languageName: node + linkType: hard + +"string-width@npm:^2.1.1": + version: 2.1.1 + resolution: "string-width@npm:2.1.1" + dependencies: + is-fullwidth-code-point: "npm:^2.0.0" + strip-ansi: "npm:^4.0.0" + checksum: 10/d6173abe088c615c8dffaf3861dc5d5906ed3dc2d6fd67ff2bd2e2b5dce7fd683c5240699cf0b1b8aa679a3b3bd6b28b5053c824cb89b813d7f6541d8f89064a + languageName: node + linkType: hard + +"string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10/e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.10": + version: 1.2.10 + resolution: "string.prototype.trim@npm:1.2.10" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-object-atoms: "npm:^1.0.0" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/47bb63cd2470a64bc5e2da1e570d369c016ccaa85c918c3a8bb4ab5965120f35e66d1f85ea544496fac84b9207a6b722adf007e6c548acd0813e5f8a82f9712a + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.9": + version: 1.0.9 + resolution: "string.prototype.trimend@npm:1.0.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/140c73899b6747de9e499c7c2e7a83d549c47a26fa06045b69492be9cfb9e2a95187499a373983a08a115ecff8bc3bd7b0fb09b8ff72fb2172abe766849272ef + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/160167dfbd68e6f7cb9f51a16074eebfce1571656fc31d40c3738ca9e30e35496f2c046fe57b6ad49f65f238a152be8c86fd9a2dd58682b5eba39dad995b3674 + languageName: node + linkType: hard + +"strip-ansi@npm:6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10/ae3b5436d34fadeb6096367626ce987057713c566e1e7768818797e00ac5d62023d0f198c4e681eae9e20701721980b26a64a8f5b91238869592a9c6800719a2 + languageName: node + linkType: hard + +"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": + version: 3.0.1 + resolution: "strip-ansi@npm:3.0.1" + dependencies: + ansi-regex: "npm:^2.0.0" + checksum: 10/9b974de611ce5075c70629c00fa98c46144043db92ae17748fb780f706f7a789e9989fd10597b7c2053ae8d1513fd707816a91f1879b2f71e6ac0b6a863db465 + languageName: node + linkType: hard + +"strip-ansi@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-ansi@npm:4.0.0" + dependencies: + ansi-regex: "npm:^3.0.0" + checksum: 10/d9186e6c0cf78f25274f6750ee5e4a5725fb91b70fdd79aa5fe648eab092a0ec5b9621b22d69d4534a56319f75d8944efbd84e3afa8d4ad1b9a9491f12c84eca + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 10/8d50ff27b7ebe5ecc78f1fe1e00fcdff7af014e73cf724b46fb81ef889eeb1015fc5184b64e81a2efe002180f3ba431bdd77e300da5c6685d702780fbf0c8d5b + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10/69412b5e25731e1938184b5d489c32e340605bb611d6140344abc3421b7f3c6f9984b21dff296dfcf056681b82caa3bb4cc996a965ce37bcfad663e92eae9c64 + languageName: node + linkType: hard + +"supports-color@npm:^2.0.0": + version: 2.0.0 + resolution: "supports-color@npm:2.0.0" + checksum: 10/d2957d19e782a806abc3e8616b6648cc1e70c3ebe94fb1c2d43160686f6d79cd7c9f22c4853bc4a362d89d1c249ab6d429788c5f6c83b3086e6d763024bf4581 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10/5f505c6fa3c6e05873b43af096ddeb22159831597649881aeb8572d6fe3b81e798cc10840d0c9735e0026b250368851b7f77b65e84f4e4daa820a4f69947f55b + languageName: node + linkType: hard + +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10/c8bb7afd564e3b26b50ca6ee47572c217526a1389fe018d00345856d4a9b08ffbd61fadaf283a87368d94c3dcdb8f5ffe2650a5a65863e21ad2730ca0f05210a + languageName: node + linkType: hard + +"supports-color@npm:^8.1.0, supports-color@npm:^8.1.1": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10/157b534df88e39c5518c5e78c35580c1eca848d7dbaf31bbe06cdfc048e22c7ff1a9d046ae17b25691128f631a51d9ec373c1b740c12ae4f0de6e292037e4282 + languageName: node + linkType: hard + +"supports-hyperlinks@npm:^2.1.0, supports-hyperlinks@npm:^2.2.0": + version: 2.3.0 + resolution: "supports-hyperlinks@npm:2.3.0" + dependencies: + has-flag: "npm:^4.0.0" + supports-color: "npm:^7.0.0" + checksum: 10/3e7df6e9eaa177d7bfbbe065c91325e9b482f48de0f7c9133603e3ffa8af31cbceac104a0941cd0266a57f8e691de6eb58b79fec237852dc84ed7ad152b116b0 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10/a9dc19ae2220c952bd2231d08ddeecb1b0328b61e72071ff4000c8384e145cc07c1c0bdb3b5a1cb06e186a7b2790f1dee793418b332f6ddf320de25d9125be7e + languageName: node + linkType: hard + +"symbol-observable@npm:^1.1.0": + version: 1.2.0 + resolution: "symbol-observable@npm:1.2.0" + checksum: 10/4684327a2fef2453dcd4238b5bd8f69c460a4708fb8c024a824c6a707ca644b2b2a586e36e5197d0d1162ff48e288299a48844a8c46274ffcfd9260e03df7692 + languageName: node + linkType: hard + +"table@npm:6.8.0": + version: 6.8.0 + resolution: "table@npm:6.8.0" + dependencies: + ajv: "npm:^8.0.1" + lodash.truncate: "npm:^4.4.2" + slice-ansi: "npm:^4.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + checksum: 10/4c2b8ebd75f36db236529680c70f41951c9c7fda3e65cb5b987164244f23f98670ded99983fdc5d62aa02405a212e90f7446efcf87e3435e472dda26d6581645 + languageName: node + linkType: hard + +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: 10/be2de62fe58ead94e3e592680052683b1ec986c72d589e7b21e5697f8744cdbf48c266fa72f6c15932894c10187b5f54573a3bcf7da0bfd964d5caf23d436168 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10/10dda13571e1f5ad37546827e9b6d4252d2e0bc176c24a101252153ef435d83696e2557fe128c4678e4e78f5f01e83711c703eef9814eb12dab028580d45980a + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10/8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695 + languageName: node + linkType: hard + +"ts-invariant@npm:^0.4.0": + version: 0.4.4 + resolution: "ts-invariant@npm:0.4.4" + dependencies: + tslib: "npm:^1.9.3" + checksum: 10/dd6f268aa358f9d28dec0b8539667cc9829eaac588438a78a422079a1b3b9a98a96306f862a745b0581173528d11a6aeff861c6e3a988d65a113a82e55a5f409 + languageName: node + linkType: hard + +"ts-node@npm:^9": + version: 9.1.1 + resolution: "ts-node@npm:9.1.1" + dependencies: + arg: "npm:^4.1.0" + create-require: "npm:^1.1.0" + diff: "npm:^4.0.1" + make-error: "npm:^1.1.1" + source-map-support: "npm:^0.5.17" + yn: "npm:3.1.1" + peerDependencies: + typescript: ">=2.7" + bin: + ts-node: dist/bin.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10/7fd8da71dd528f0490daf771a185572b485cb7f6be354c4d675011aee63bdb291f919e68c340cc92863c450e685b7f7300bd81ec158c3881a8c3f0e2f1cc10fe + languageName: node + linkType: hard + +"tslib@npm:^1.10.0, tslib@npm:^1.9.0, tslib@npm:^1.9.3": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10/7dbf34e6f55c6492637adb81b555af5e3b4f9cc6b998fb440dac82d3b42bdc91560a35a5fb75e20e24a076c651438234da6743d139e4feabf0783f3cdfe1dddb + languageName: node + linkType: hard + +"tslib@npm:^2, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.1, tslib@npm:^2.6.1, tslib@npm:^2.6.2": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 + languageName: node + linkType: hard + +"tty@npm:1.0.1": + version: 1.0.1 + resolution: "tty@npm:1.0.1" + checksum: 10/6fa781841fa99b9ae407c713fc08ae71e15a73d9702952cddda3e6a7aa204cdfdbf85f5b4a54bb414bfd63eb8d4ba027973c4026b193081c762d4dc002d3e0a9 + languageName: node + linkType: hard + +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10/7f0d9ed5c22404072b2ae8edc45c071772affd2ed14a74f03b4e71b4dd1a14c3714d85aed64abcaaee5fec2efc79002ba81155c708f4df65821b444abb0cfade + languageName: node + linkType: hard + +"type-fest@npm:^0.13.1": + version: 0.13.1 + resolution: "type-fest@npm:0.13.1" + checksum: 10/11e9476dc85bf97a71f6844fb67ba8e64a4c7e445724c0f3bd37eb2ddf4bc97c1dc9337bd880b28bce158de1c0cb275c2d03259815a5bf64986727197126ab56 + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10/f4254070d9c3d83a6e573bcb95173008d73474ceadbbf620dd32d273940ca18734dff39c2b2480282df9afe5d1675ebed5499a00d791758748ea81f61a38961f + languageName: node + linkType: hard + +"type-fest@npm:^0.3.0": + version: 0.3.1 + resolution: "type-fest@npm:0.3.1" + checksum: 10/a969e953d87889e089ea8b370b12a0c90410e198287aeba1a5618a325492967be338ebaf85aecfb542d312dedbcf5e12be9291e5e5d3b0b6c990992a224d07ae + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10/3fb91f0735fb413b2bbaaca9fabe7b8fc14a3fa5a5a7546bab8a57e755be0e3788d893195ad9c2b842620592de0e68d4c077d4c2c41f04ec25b8b5bb82fa9a80 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.14" + checksum: 10/269dad101dda73e3110117a9b84db86f0b5c07dad3a9418116fd38d580cab7fc628a4fc167e29b6d7c39da2f53374b78e7cb578b3c5ec7a556689d985d193519 + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-byte-offset@npm:1.0.4" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + reflect.getprototypeof: "npm:^1.0.9" + checksum: 10/c2869aa584cdae24ecfd282f20a0f556b13a49a9d5bca1713370bb3c89dff0ccbc5ceb45cb5b784c98f4579e5e3e2a07e438c3a5b8294583e2bd4abbd5104fb5 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.7 + resolution: "typed-array-length@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + is-typed-array: "npm:^1.1.13" + possible-typed-array-names: "npm:^1.0.0" + reflect.getprototypeof: "npm:^1.0.6" + checksum: 10/d6b2f0e81161682d2726eb92b1dc2b0890890f9930f33f9bcf6fc7272895ce66bc368066d273e6677776de167608adc53fcf81f1be39a146d64b630edbf2081c + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + which-boxed-primitive: "npm:^1.1.1" + checksum: 10/fadb347020f66b2c8aeacf8b9a79826fa34cc5e5457af4eb0bbc4e79bd87fed0fa795949825df534320f7c13f199259516ad30abc55a6e7b91d8d996ca069e50 + languageName: node + linkType: hard + +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: 10/583ac7bbf4ff69931d3985f4762cde2690bb607844c16a5e2fbb92ed312fe4fa1b365e953032d469fa28ba8b224e88a595f0b10a449332f83fa77c695e567dbe + languageName: node + linkType: hard + +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: 10/40cdc60f6e61070fe658ca36016a8f4ec216b29bf04a55dce14e3710cc84c7448538ef4dad3728d0bfe29975ccd7bfb5f414c45e7b78883567fb31b246f02dff + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10/ecd8469fe0db28e7de9e5289d32bd1b6ba8f7183db34f3bfc4ca53c49891c2d6aa05f3fb3936a81285a905cc509fb641a0c3fc131ec786167eff41236ae32e60 + languageName: node + linkType: hard + +"upper-case-first@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case-first@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/4487db4701effe3b54ced4b3e4aa4d9ab06c548f97244d04aafb642eedf96a76d5a03cf5f38f10f415531d5792d1ac6e1b50f2a76984dc6964ad530f12876409 + languageName: node + linkType: hard + +"upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/508723a2b03ab90cf1d6b7e0397513980fab821cbe79c87341d0e96cedefadf0d85f9d71eac24ab23f526a041d585a575cfca120a9f920e44eb4f8a7cf89121c + languageName: node + linkType: hard + +"util.promisify@npm:1.1.1": + version: 1.1.1 + resolution: "util.promisify@npm:1.1.1" + dependencies: + call-bind: "npm:^1.0.0" + define-properties: "npm:^1.1.3" + for-each: "npm:^0.3.3" + has-symbols: "npm:^1.0.1" + object.getownpropertydescriptors: "npm:^2.1.1" + checksum: 10/fa1e4e6ae2636b3ab5feaa78f1324603e1f8fc71c27f755df10df83ccf24e48ec12c0d8f42081658487cd09c43ce9c0e7a1c16c8ae0888d582a74b338a5266d2 + languageName: node + linkType: hard + +"vscode-jsonrpc@npm:6.0.0": + version: 6.0.0 + resolution: "vscode-jsonrpc@npm:6.0.0" + checksum: 10/c9af7ed831912b5df0d046260ff24f99582f1f75aa49f49a39a67da1dc595dd00ddae756ae3ef64c73bc2c0e4d79b37aa13e56e24a30319f8053a229d6589b19 + languageName: node + linkType: hard + +"vscode-languageserver-protocol@npm:3.16.0": + version: 3.16.0 + resolution: "vscode-languageserver-protocol@npm:3.16.0" + dependencies: + vscode-jsonrpc: "npm:6.0.0" + vscode-languageserver-types: "npm:3.16.0" + checksum: 10/6cc184e7bc7e9334361080662a1927cc9fa4b42acf19f1e7b92f9cafa5cb0897cc1586ef1c8eb932bf46404f6eed00283540ed2e4c8247506f9bec3a4aee7131 + languageName: node + linkType: hard + +"vscode-languageserver-textdocument@npm:^1.0.4": + version: 1.0.12 + resolution: "vscode-languageserver-textdocument@npm:1.0.12" + checksum: 10/2bc0fde952d40f35a31179623d1491b0fafdee156aaf58557f40f5d394a25fc84826763cdde55fa6ce2ed9cd35a931355ad6dd7fe5db82e7f21e5d865f0af8c6 + languageName: node + linkType: hard + +"vscode-languageserver-types@npm:3.16.0": + version: 3.16.0 + resolution: "vscode-languageserver-types@npm:3.16.0" + checksum: 10/a276ad08bcf6b7eabff50073a927e3053243d558f6d7a9cba7475de2f2623ec42279e4e03544cce4cfdd5a91f3ed517074f688b42395b3e91d21809dbad43019 + languageName: node + linkType: hard + +"vscode-languageserver@npm:^7.0.0": + version: 7.0.0 + resolution: "vscode-languageserver@npm:7.0.0" + dependencies: + vscode-languageserver-protocol: "npm:3.16.0" + bin: + installServerIntoExtension: bin/installServerIntoExtension + checksum: 10/4ea1536e83ee392d0f0d4971828095a4efcd6b5b1310e7fc95f1d5e0e91328e52b27294718dcd5957d029d9301e8bb7cb181f1127937737a09a9a2ae413997e4 + languageName: node + linkType: hard + +"vscode-uri@npm:1.0.6": + version: 1.0.6 + resolution: "vscode-uri@npm:1.0.6" + checksum: 10/9ee7aa1c0198fafe22b8c7788676ff4f0111d76366e2eec1fdc1caa36b464f5d0fd988a447f6729fe5c460e1c40b5915bd6fbf614500688dc4b5b83f3584e454 + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10/b65b9f8d6854572a84a5c69615152b63371395f0c5dcd6729c45789052296df54314db2bc3e977df41705eacb8bc79c247cee139a63fa695192f95816ed528ad + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10/f95adbc1e80820828b45cc671d97da7cd5e4ef9deb426c31bcd5ab00dc7103042291613b3ef3caec0a2335ed09e0d5ed026c940755dbb6d404e2b27f940fdf07 + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: "npm:^1.1.0" + is-boolean-object: "npm:^1.2.1" + is-number-object: "npm:^1.1.1" + is-string: "npm:^1.1.1" + is-symbol: "npm:^1.1.1" + checksum: 10/a877c0667bc089518c83ad4d845cf8296b03efe3565c1de1940c646e00a2a1ae9ed8a185bcfa27cbf352de7906f0616d83b9d2f19ca500ee02a551fb5cf40740 + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + function.prototype.name: "npm:^1.1.6" + has-tostringtag: "npm:^1.0.2" + is-async-function: "npm:^2.0.0" + is-date-object: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.1.0" + is-generator-function: "npm:^1.0.10" + is-regex: "npm:^1.2.1" + is-weakref: "npm:^1.0.2" + isarray: "npm:^2.0.5" + which-boxed-primitive: "npm:^1.1.0" + which-collection: "npm:^1.0.2" + which-typed-array: "npm:^1.1.16" + checksum: 10/22c81c5cb7a896c5171742cd30c90d992ff13fb1ea7693e6cf80af077791613fb3f89aa9b4b7f890bd47b6ce09c6322c409932359580a2a2a54057f7b52d1cbe + languageName: node + linkType: hard + +"which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: "npm:^2.0.3" + is-set: "npm:^2.0.3" + is-weakmap: "npm:^2.0.2" + is-weakset: "npm:^2.0.3" + checksum: 10/674bf659b9bcfe4055f08634b48a8588e879161b9fefed57e9ec4ff5601e4d50a05ccd76cf10f698ef5873784e5df3223336d56c7ce88e13bcf52ebe582fc8d7 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10/12be30fb88567f9863186bee1777f11bea09dd59ed8b3ce4afa7dd5cade75e2f4cc56191a2da165113cc7cf79987ba021dac1e22b5b62aa7e5c56949f2469a68 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10/4782f8a1d6b8fc12c65e968fea49f59752bf6302dc43036c3bf87da718a80710f61a062516e9764c70008b487929a73546125570acea95c5b5dcc8ac3052c70f + languageName: node + linkType: hard + +"widest-line@npm:^3.1.0": + version: 3.1.0 + resolution: "widest-line@npm:3.1.0" + dependencies: + string-width: "npm:^4.0.0" + checksum: 10/03db6c9d0af9329c37d74378ff1d91972b12553c7d72a6f4e8525fe61563fa7adb0b9d6e8d546b7e059688712ea874edd5ded475999abdeedf708de9849310e0 + languageName: node + linkType: hard + +"wrap-ansi@npm:^3.0.1": + version: 3.0.1 + resolution: "wrap-ansi@npm:3.0.1" + dependencies: + string-width: "npm:^2.1.1" + strip-ansi: "npm:^4.0.0" + checksum: 10/bdd4248faa2142051ed5802c216076b25ada29778100483bb6f16a52a115bf7cb7e595bdbe9f1ed551dcd4822f3e2ece80c9febedc2b65acb2cc649705d47bc2 + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10/0d64f2d438e0b555e693b95aee7b2689a12c3be5ac458192a1ce28f542a6e9e59ddfecc37520910c2c88eb1f82a5411260566dba5064e8f9895e76e169e76187 + languageName: node + linkType: hard + +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10/cebdaeca3a6880da410f75209e68cd05428580de5ad24535f22696d7d9cab134d1f8498599f344c3cf0fb37c1715807a183778d8c648d6cc0cb5ff2bb4236540 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10/159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 + languageName: node + linkType: hard + +"yaml@npm:^1.10.0": + version: 1.10.2 + resolution: "yaml@npm:1.10.2" + checksum: 10/e088b37b4d4885b70b50c9fa1b7e54bd2e27f5c87205f9deaffd1fb293ab263d9c964feadb9817a7b129a5bf30a06582cb08750f810568ecc14f3cdbabb79cb3 + languageName: node + linkType: hard + +"yarn@npm:^1.22.17": + version: 1.22.22 + resolution: "yarn@npm:1.22.22" + bin: + yarn: bin/yarn.js + yarnpkg: bin/yarn.js + checksum: 10/98d80230beaa81f186b2256dff5ef9dce2dd6073c94299209f8e562da9018cff4275c95c27c788aaa4a9c3c186ea8a9aee9a5b83570696a4c8a9d1fff2d4da3a + languageName: node + linkType: hard + +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10/2c487b0e149e746ef48cda9f8bad10fc83693cd69d7f9dcd8be4214e985de33a29c9e24f3c0d6bcf2288427040a8947406ab27f7af67ee9456e6b84854f02dd6 + languageName: node + linkType: hard + +"zen-observable-ts@npm:^0.8.21": + version: 0.8.21 + resolution: "zen-observable-ts@npm:0.8.21" + dependencies: + tslib: "npm:^1.9.3" + zen-observable: "npm:^0.8.0" + checksum: 10/557b614e8c7394ec9fe5658c67a3883c3aa329360659a3f9be5b5288142c8cdcbc3a02c10416414ebdde1607a9e052ace58e3cf19f276391b93b24063c8a649f + languageName: node + linkType: hard + +"zen-observable@npm:^0.8.0": + version: 0.8.15 + resolution: "zen-observable@npm:0.8.15" + checksum: 10/30eac3f4055d33f446b4cd075d3543da347c2c8e68fbc35c3f5a19fb43be67c6ed27ee136bc8f8933efa547be7ce04957809ad00ee7f1b00a964f199ae6fb514 + languageName: node + linkType: hard diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index 5fc7f30602..ff887246d9 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Tgstation.Server.Host/.config/dotnet-tools.json b/src/Tgstation.Server.Host/.config/dotnet-tools.json index eda2e98c23..bc2098a53b 100644 --- a/src/Tgstation.Server.Host/.config/dotnet-tools.json +++ b/src/Tgstation.Server.Host/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "8.0.13", + "version": "9.0.3", "commands": [ "dotnet-ef" ] diff --git a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs index 9e2a37da59..f60275aceb 100644 --- a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs +++ b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs @@ -43,6 +43,14 @@ namespace Tgstation.Server.Host.Authority /// A resulting in an of . ValueTask> OAuthConnections(long userId, CancellationToken cancellationToken); + /// + /// Gets the s for the with a given . + /// + /// The of the . + /// The for the operation. + /// A resulting in an of . + ValueTask> OidcConnections(long userId, CancellationToken cancellationToken); + /// /// Gets all registered s. /// @@ -70,7 +78,7 @@ namespace Tgstation.Server.Host.Authority /// The . /// The for the operation. /// A resulting in am for the created . - [TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnOAuthConnections)] + [TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnServiceConnections)] ValueTask> Update(UserUpdateRequest updateRequest, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Authority/LoginAuthority.cs b/src/Tgstation.Server.Host/Authority/LoginAuthority.cs index c98863d01c..ab8af50f91 100644 --- a/src/Tgstation.Server.Host/Authority/LoginAuthority.cs +++ b/src/Tgstation.Server.Host/Authority/LoginAuthority.cs @@ -5,11 +5,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Authority.Core; +using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.GraphQL.Mutations.Payloads; using Tgstation.Server.Host.Models; @@ -58,6 +60,11 @@ namespace Tgstation.Server.Host.Authority /// readonly ISessionInvalidationTracker sessionInvalidationTracker; + /// + /// The for the . + /// + readonly SecurityConfiguration securityConfiguration; + /// /// Generate an for a given . /// @@ -106,6 +113,7 @@ namespace Tgstation.Server.Host.Authority /// The value of . /// The value of . /// The value of . + /// The containing the value of . public LoginAuthority( IAuthenticationContext authenticationContext, IDatabaseContext databaseContext, @@ -116,7 +124,8 @@ namespace Tgstation.Server.Host.Authority ITokenFactory tokenFactory, ICryptographySuite cryptographySuite, IIdentityCache identityCache, - ISessionInvalidationTracker sessionInvalidationTracker) + ISessionInvalidationTracker sessionInvalidationTracker, + IOptions securityConfigurationOptions) : base( authenticationContext, databaseContext, @@ -129,11 +138,16 @@ namespace Tgstation.Server.Host.Authority this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache)); this.sessionInvalidationTracker = sessionInvalidationTracker ?? throw new ArgumentNullException(nameof(sessionInvalidationTracker)); + securityConfiguration = securityConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(securityConfigurationOptions)); } /// public async ValueTask> AttemptLogin(CancellationToken cancellationToken) { + // password and oauth logins disabled + if (securityConfiguration.OidcStrictMode) + return Unauthorized(); + var headers = apiHeadersProvider.ApiHeaders; if (headers == null) return GenerateHeadersExceptionResponse(apiHeadersProvider.HeadersException!); @@ -318,6 +332,12 @@ namespace Tgstation.Server.Host.Authority (string? UserID, string AccessCode)? oauthResult; try { + // minor special case here until its removal +#pragma warning disable CS0618 // Type or member is obsolete + if (oAuthProvider == OAuthProvider.TGForums) +#pragma warning restore CS0618 // Type or member is obsolete + return (Unauthorized(), null); + var validator = oAuthProviders .GetValidator(oAuthProvider, forLogin); diff --git a/src/Tgstation.Server.Host/Authority/UserAuthority.cs b/src/Tgstation.Server.Host/Authority/UserAuthority.cs index 686eb10f47..0cb8310718 100644 --- a/src/Tgstation.Server.Host/Authority/UserAuthority.cs +++ b/src/Tgstation.Server.Host/Authority/UserAuthority.cs @@ -41,6 +41,11 @@ namespace Tgstation.Server.Host.Authority /// readonly IOAuthConnectionsDataLoader oAuthConnectionsDataLoader; + /// + /// The for the . + /// + readonly IOidcConnectionsDataLoader oidcConnectionsDataLoader; + /// /// The for the . /// @@ -71,6 +76,11 @@ namespace Tgstation.Server.Host.Authority /// readonly IOptionsSnapshot generalConfigurationOptions; + /// + /// The of for the . + /// + readonly IOptions securityConfigurationOptions; + /// /// Implements the . /// @@ -95,7 +105,7 @@ namespace Tgstation.Server.Host.Authority } /// - /// Implements the . + /// Implements the . /// /// The of s to load the OAuthConnections for. /// The to load from. @@ -117,10 +127,37 @@ namespace Tgstation.Server.Host.Authority .ToListAsync(cancellationToken); return list.ToLookup( - oauthConnection => oauthConnection.UserId, + oAuthConnection => oAuthConnection.UserId, x => new GraphQL.Types.OAuth.OAuthConnection(x.ExternalUserId!, x.Provider)); } + /// + /// Implements the . + /// + /// The of s to load the OidcConnections for. + /// The to load from. + /// The for the operation. + /// A resulting in a of the requested s. + [DataLoader] + public static async ValueTask> GetOidcConnections( + IReadOnlyList userIds, + IDatabaseContext databaseContext, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(userIds); + ArgumentNullException.ThrowIfNull(databaseContext); + + var list = await databaseContext + .OidcConnections + .AsQueryable() + .Where(x => userIds.Contains(x.User!.Id!.Value)) + .ToListAsync(cancellationToken); + + return list.ToLookup( + oidcConnection => oidcConnection.UserId, + x => new GraphQL.Types.OAuth.OidcConnection(x.ExternalUserId!, x.SchemeKey!)); + } + /// /// Check if a given has a valid specified. /// @@ -147,24 +184,28 @@ namespace Tgstation.Server.Host.Authority /// The to use. /// The value of . /// The value of . + /// The value of . /// The value of . /// The value of . /// The value of . /// The value of . /// The value of . /// The value of . + /// The value of . public UserAuthority( IAuthenticationContext authenticationContext, IDatabaseContext databaseContext, ILogger logger, IUsersDataLoader usersDataLoader, IOAuthConnectionsDataLoader oAuthConnectionsDataLoader, + IOidcConnectionsDataLoader oidcConnectionsDataLoader, ISystemIdentityFactory systemIdentityFactory, IPermissionsUpdateNotifyee permissionsUpdateNotifyee, ICryptographySuite cryptographySuite, ISessionInvalidationTracker sessionInvalidationTracker, ITopicEventSender topicEventSender, - IOptionsSnapshot generalConfigurationOptions) + IOptionsSnapshot generalConfigurationOptions, + IOptions securityConfigurationOptions) : base( authenticationContext, databaseContext, @@ -172,12 +213,14 @@ namespace Tgstation.Server.Host.Authority { this.usersDataLoader = usersDataLoader ?? throw new ArgumentNullException(nameof(usersDataLoader)); this.oAuthConnectionsDataLoader = oAuthConnectionsDataLoader ?? throw new ArgumentNullException(nameof(oAuthConnectionsDataLoader)); + this.oidcConnectionsDataLoader = oidcConnectionsDataLoader ?? throw new ArgumentNullException(nameof(oidcConnectionsDataLoader)); this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory)); this.permissionsUpdateNotifyee = permissionsUpdateNotifyee ?? throw new ArgumentNullException(nameof(permissionsUpdateNotifyee)); this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); this.sessionInvalidationTracker = sessionInvalidationTracker ?? throw new ArgumentNullException(nameof(sessionInvalidationTracker)); this.topicEventSender = topicEventSender ?? throw new ArgumentNullException(nameof(topicEventSender)); this.generalConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); + this.securityConfigurationOptions = securityConfigurationOptions ?? throw new ArgumentNullException(nameof(securityConfigurationOptions)); } /// @@ -289,6 +332,11 @@ namespace Tgstation.Server.Host.Authority => new AuthorityResponse( await oAuthConnectionsDataLoader.LoadRequiredAsync(userId, cancellationToken)); + /// + public async ValueTask> OidcConnections(long userId, CancellationToken cancellationToken) + => new AuthorityResponse( + await oidcConnectionsDataLoader.LoadRequiredAsync(userId, cancellationToken)); + /// public async ValueTask> Create( UserCreateRequest createRequest, @@ -372,7 +420,7 @@ namespace Tgstation.Server.Host.Authority var callerAdministrationRights = (AdministrationRights)AuthenticationContext.GetRight(RightsType.Administration); var canEditAllUsers = callerAdministrationRights.HasFlag(AdministrationRights.WriteUsers); var passwordEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnPassword); - var oAuthEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnOAuthConnections); + var oAuthEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnServiceConnections); var originalUser = !canEditAllUsers ? AuthenticationContext.User @@ -382,6 +430,7 @@ namespace Tgstation.Server.Host.Authority .Where(x => x.Id == model.Id) .Include(x => x.CreatedBy) .Include(x => x.OAuthConnections) + .Include(x => x.OidcConnections) .Include(x => x.Group!) .ThenInclude(x => x.PermissionSet) .Include(x => x.PermissionSet) @@ -437,8 +486,11 @@ namespace Tgstation.Server.Host.Authority && (model.OAuthConnections.Count != originalUser.OAuthConnections!.Count || !model.OAuthConnections.All(x => originalUser.OAuthConnections.Any(y => y.Provider == x.Provider && y.ExternalUserId == x.ExternalUserId)))) { + if (securityConfigurationOptions.Value.OidcStrictMode) + return BadRequest(ErrorCode.BadUserEditDueToOidcStrictMode); + if (originalUser.CanonicalName == User.CanonicalizeName(DefaultCredentials.AdminUserName)) - return BadRequest(ErrorCode.AdminUserCannotOAuth); + return BadRequest(ErrorCode.AdminUserCannotHaveServiceConnection); if (model.OAuthConnections.Count == 0 && originalUser.PasswordHash == null && originalUser.SystemIdentifier == null) return BadRequest(ErrorCode.CannotRemoveLastAuthenticationOption); @@ -452,8 +504,33 @@ namespace Tgstation.Server.Host.Authority }); } + if (model.OidcConnections != null + && (model.OidcConnections.Count != originalUser.OidcConnections!.Count + || !model.OidcConnections.All(x => originalUser.OidcConnections.Any(y => y.SchemeKey == x.SchemeKey && y.ExternalUserId == x.ExternalUserId)))) + { + if (securityConfigurationOptions.Value.OidcStrictMode) + return BadRequest(ErrorCode.BadUserEditDueToOidcStrictMode); + + if (originalUser.CanonicalName == User.CanonicalizeName(DefaultCredentials.AdminUserName)) + return BadRequest(ErrorCode.AdminUserCannotHaveServiceConnection); + + if (model.OidcConnections.Count == 0 && originalUser.PasswordHash == null && originalUser.SystemIdentifier == null) + return BadRequest(ErrorCode.CannotRemoveLastAuthenticationOption); + + originalUser.OidcConnections.Clear(); + foreach (var updatedConnection in model.OidcConnections) + originalUser.OidcConnections.Add(new Models.OidcConnection + { + SchemeKey = updatedConnection.SchemeKey, + ExternalUserId = updatedConnection.ExternalUserId, + }); + } + if (model.Group != null) { + if (securityConfigurationOptions.Value.OidcStrictMode) + return BadRequest(ErrorCode.BadUserEditDueToOidcStrictMode); + originalUser.Group = await DatabaseContext .Groups .AsQueryable() @@ -474,6 +551,9 @@ namespace Tgstation.Server.Host.Authority } else if (model.PermissionSet != null) { + if (securityConfigurationOptions.Value.OidcStrictMode) + return BadRequest(ErrorCode.BadUserEditDueToOidcStrictMode); + if (originalUser.PermissionSet == null) { Logger.LogTrace("Creating new permission set..."); @@ -495,6 +575,9 @@ namespace Tgstation.Server.Host.Authority if (model.Enabled.HasValue) { + if (securityConfigurationOptions.Value.OidcStrictMode) + return BadRequest(ErrorCode.BadUserEditDueToOidcStrictMode); + invalidateSessions = originalUser.Require(x => x.Enabled) && !model.Enabled.Value; originalUser.Enabled = model.Enabled.Value; } @@ -554,6 +637,7 @@ namespace Tgstation.Server.Host.Authority queryable = queryable .Include(x => x.CreatedBy) .Include(x => x.OAuthConnections) + .Include(x => x.OidcConnections) .Include(x => x.Group!) .ThenInclude(x => x.PermissionSet) .Include(x => x.PermissionSet); @@ -603,6 +687,15 @@ namespace Tgstation.Server.Host.Authority }) .ToList() ?? new List(), + OidcConnections = model + .OidcConnections + ?.Select(x => new Models.OidcConnection + { + SchemeKey = x.SchemeKey, + ExternalUserId = x.ExternalUserId, + }) + .ToList() + ?? new List(), }; } diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index 049c217efb..5faa30dbd2 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -377,6 +377,7 @@ namespace Tgstation.Server.Host.Components.Chat /// public Func> QueueDeploymentMessage( Models.RevisionInformation revisionInformation, + Models.RevisionInformation? previousRevisionInformation, EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string? gitHubOwner, @@ -407,6 +408,7 @@ namespace Tgstation.Server.Host.Components.Chat { var callback = await provider.SendUpdateMessage( revisionInformation, + previousRevisionInformation, engineVersion, estimatedCompletionTime, gitHubOwner, diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs index 8a0aa33fe4..669f21b305 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs @@ -61,6 +61,7 @@ namespace Tgstation.Server.Host.Components.Chat /// Send the message for a deployment to configured deployment channels. /// /// The of the deployment. + /// The optional of the previous deployment. /// The of the deployment. /// The optional the deployment is expected to be completed at. /// The repository GitHub owner, if any. @@ -69,6 +70,7 @@ namespace Tgstation.Server.Host.Components.Chat /// A to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. Returns an to call to mark the deployment as active/inactive. Parameter: If the deployment is being activated or inactivated. Func> QueueDeploymentMessage( Models.RevisionInformation revisionInformation, + Models.RevisionInformation? previousRevisionInformation, Api.Models.EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string? gitHubOwner, diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 7012a94baf..f76ee34517 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -303,6 +303,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public override async ValueTask>>> SendUpdateMessage( Models.RevisionInformation revisionInformation, + Models.RevisionInformation? previousRevisionInformation, EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string? gitHubOwner, @@ -316,7 +317,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha; - var fields = BuildUpdateEmbedFields(revisionInformation, engineVersion, gitHubOwner, gitHubRepo, localCommitPushed); + var fields = BuildUpdateEmbedFields(revisionInformation, previousRevisionInformation, engineVersion, gitHubOwner, gitHubRepo, localCommitPushed); Optional author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix) { Url = "https://github.com/tgstation/tgstation-server", @@ -900,6 +901,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// Create a of s for a discord update embed. /// /// The of the deployment. + /// The optional of the previous deployment. /// The of the deployment. /// The repository GitHub owner, if any. /// The repository GitHub name, if any. @@ -907,6 +909,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// A new of s to use. List BuildUpdateEmbedFields( Models.RevisionInformation revisionInformation, + Models.RevisionInformation? previousRevisionInformation, EngineVersion engineVersion, string? gitHubOwner, string? gitHubRepo, @@ -936,6 +939,31 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (gitHubOwner == null || gitHubRepo == null) return fields; + var previousTestMerges = (IEnumerable?)previousRevisionInformation?.ActiveTestMerges ?? Enumerable.Empty(); + var currentTestMerges = (IEnumerable?)revisionInformation.ActiveTestMerges ?? Enumerable.Empty(); + + // determine what TMs were changed and how + var addedTestMerges = currentTestMerges + .Select(x => x.TestMerge) + .Where(x => !previousTestMerges + .Any(y => y.TestMerge.Number == x.Number)) + .ToList(); + var removedTestMerges = previousTestMerges + .Select(x => x.TestMerge) + .Where(x => !currentTestMerges + .Any(y => y.TestMerge.Number == x.Number)) + .ToList(); + var updatedTestMerges = currentTestMerges + .Select(x => x.TestMerge) + .Where(x => previousTestMerges + .Any(y => y.TestMerge.Number == x.Number && y.TestMerge.TargetCommitSha != x.TargetCommitSha)) + .ToList(); + var unchangedTestMerges = currentTestMerges + .Select(x => x.TestMerge) + .Where(x => previousTestMerges + .Any(y => y.TestMerge.Number == x.Number && y.TestMerge.TargetCommitSha == x.TargetCommitSha)) + .ToList(); + fields.Add( new EmbedField( "Local Commit", @@ -952,13 +980,33 @@ namespace Tgstation.Server.Host.Components.Chat.Providers : revisionOriginSha[..7], true)); - fields.AddRange((revisionInformation.ActiveTestMerges ?? Enumerable.Empty()) - .Select(x => x.TestMerge) + fields.AddRange(addedTestMerges + .Select(x => new EmbedField( + $"#{x.Number} (Added)", + $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha![..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}", + false))); + + fields.AddRange(updatedTestMerges + .Select(x => new EmbedField( + $"#{x.Number} (Updated)", + $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha![..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}", + false))); + + fields.AddRange(unchangedTestMerges .Select(x => new EmbedField( $"#{x.Number}", $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.TargetCommitSha![..7]}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.TargetCommitSha}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}", false))); + if (removedTestMerges.Count != 0) + fields.Add( + new EmbedField( + "Removed:", + String.Join( + Environment.NewLine, + removedTestMerges + .Select(x => $"- #{x.Number} [{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_")))); + return fields; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs index 845892d9e0..0e32e3dbf6 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -84,6 +84,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// Send the message for a deployment. /// /// The of the deployment. + /// The of the previous deployment if any. /// The of the deployment. /// The optional the deployment is expected to be completed at. /// The repository GitHub owner, if any. @@ -94,6 +95,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// A resulting in a to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. Returns another callback which should be called to mark the deployment as active. ValueTask>>> SendUpdateMessage( Models.RevisionInformation revisionInformation, + Models.RevisionInformation? previousRevisionInformation, Api.Models.EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string? gitHubOwner, diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index d449320bc7..560390201d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -17,6 +17,7 @@ using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; +using Tgstation.Server.Host.Models; using Tgstation.Server.Host.System; using Tgstation.Server.Host.Utils; @@ -217,6 +218,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public override async ValueTask>>> SendUpdateMessage( Models.RevisionInformation revisionInformation, + Models.RevisionInformation? previousRevisionInformation, EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string? gitHubOwner, @@ -228,6 +230,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers ArgumentNullException.ThrowIfNull(revisionInformation); ArgumentNullException.ThrowIfNull(engineVersion); + var previousTestMerges = (IEnumerable?)previousRevisionInformation?.ActiveTestMerges ?? Enumerable.Empty(); + var currentTestMerges = (IEnumerable?)revisionInformation.ActiveTestMerges ?? Enumerable.Empty(); + var commitInsert = revisionInformation.CommitSha![..7]; string remoteCommitInsert; if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha) @@ -238,21 +243,35 @@ namespace Tgstation.Server.Host.Components.Chat.Providers else remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha![..7]); - var testmergeInsert = (revisionInformation.ActiveTestMerges?.Count ?? 0) == 0 + var testmergeInsert = !currentTestMerges.Any() ? String.Empty : String.Format( CultureInfo.InvariantCulture, " (Test Merges: {0})", String.Join( ", ", - revisionInformation - .ActiveTestMerges! + currentTestMerges .Select(x => x.TestMerge) .Select(x => { - var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.TargetCommitSha![..7]); - if (x.Comment != null) - result += String.Format(CultureInfo.InvariantCulture, " ({0})", x.Comment); + var status = string.Empty; + if (!previousTestMerges.Any(y => y.TestMerge.Number == x.Number)) + status = "Added"; + else if (previousTestMerges.Any(y => y.TestMerge.Number == x.Number && y.TestMerge.TargetCommitSha != x.TargetCommitSha)) + status = "Updated"; + + var result = $"#{x.Number} at {x.TargetCommitSha![..7]}"; + + if (!string.IsNullOrEmpty(x.Comment)) + { + if (!string.IsNullOrEmpty(status)) + result += $" ({status} - {x.Comment})"; + else + result += $" ({x.Comment})"; + } + else if (!string.IsNullOrEmpty(status)) + result += $" ({status})"; + return result; }))); diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs index 6be6b71521..e1f58b8288 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs @@ -199,6 +199,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public abstract ValueTask>>> SendUpdateMessage( RevisionInformation revisionInformation, + RevisionInformation? previousRevisionInformation, Api.Models.EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string? gitHubOwner, diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 9ffab92802..6f8b05c9d2 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -336,13 +336,17 @@ namespace Tgstation.Server.Host.Components.Deployment } }); - var likelyPushedTestMergeCommit = - repositorySettings!.PushTestMergeCommits!.Value - && repositorySettings.AccessToken != null - && repositorySettings.AccessUser != null; + Models.CompileJob? oldCompileJob; using (repo) + { + var likelyPushedTestMergeCommit = + repositorySettings!.PushTestMergeCommits!.Value + && repositorySettings.AccessToken != null + && repositorySettings.AccessUser != null; + oldCompileJob = await compileJobConsumer.LatestCompileJob(); compileJob = await Compile( job, + oldCompileJob, revInfo!, dreamMakerSettings!, ddSettings!, @@ -352,8 +356,8 @@ namespace Tgstation.Server.Host.Components.Deployment averageSpan, likelyPushedTestMergeCommit, cancellationToken); + } - var activeCompileJob = await compileJobConsumer.LatestCompileJob(); try { await databaseContextFactory.UseContext( @@ -402,7 +406,7 @@ namespace Tgstation.Server.Host.Components.Deployment var commentsTask = remoteDeploymentManager!.PostDeploymentComments( compileJob, - activeCompileJob?.RevisionInformation, + oldCompileJob?.RevisionInformation, repositorySettings, repoOwner, repoName, @@ -479,6 +483,7 @@ namespace Tgstation.Server.Host.Components.Deployment /// Run the compile implementation. /// /// The currently running . + /// The optional of the previous deployment. /// The . /// The . /// The . @@ -491,6 +496,7 @@ namespace Tgstation.Server.Host.Components.Deployment /// A resulting in the completed . async ValueTask Compile( Models.Job job, + Models.CompileJob? oldCompileJob, Models.RevisionInformation revisionInformation, Api.Models.Internal.DreamMakerSettings dreamMakerSettings, DreamDaemonLaunchParameters launchParameters, @@ -512,6 +518,7 @@ namespace Tgstation.Server.Host.Components.Deployment using var engineLock = await engineManager.UseExecutables(null, null, cancellationToken); currentChatCallback = chatManager.QueueDeploymentMessage( revisionInformation, + oldCompileJob?.RevisionInformation, engineLock.Version, DateTimeOffset.UtcNow + estimatedDuration, repository.RemoteRepositoryOwner, diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs index 0f8c86732f..13cddb3517 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs @@ -18,6 +18,11 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote /// abstract class BaseRemoteDeploymentManager : IRemoteDeploymentManager { + /// + /// The header comment that begins every deployment message comment/note. + /// + public const string DeploymentMsgHeaderStart = ""; + /// /// The for the . /// @@ -66,36 +71,28 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote if (repositorySettings.AccessToken == null) return; - var deployedRevisionInformation = compileJob.RevisionInformation; - if ((previousRevisionInformation != null && previousRevisionInformation.CommitSha == deployedRevisionInformation.CommitSha) + var revisionInformation = compileJob.RevisionInformation; + if ((previousRevisionInformation != null && previousRevisionInformation.CommitSha == revisionInformation.CommitSha) || !repositorySettings.PostTestMergeComment!.Value) return; - previousRevisionInformation ??= new RevisionInformation(); - previousRevisionInformation.ActiveTestMerges ??= new List(); + var previousTestMerges = (IEnumerable?)previousRevisionInformation?.ActiveTestMerges ?? Enumerable.Empty(); + var currentTestMerges = (IEnumerable?)revisionInformation.ActiveTestMerges ?? Enumerable.Empty(); - deployedRevisionInformation.ActiveTestMerges ??= new List(); - - // added prs - var addedTestMerges = deployedRevisionInformation - .ActiveTestMerges + // determine what TMs were changed and how + var addedTestMerges = currentTestMerges .Select(x => x.TestMerge) - .Where(x => !previousRevisionInformation - .ActiveTestMerges + .Where(x => !previousTestMerges .Any(y => y.TestMerge.Number == x.Number)) .ToList(); - var removedTestMerges = previousRevisionInformation - .ActiveTestMerges + var removedTestMerges = previousTestMerges .Select(x => x.TestMerge) - .Where(x => !deployedRevisionInformation - .ActiveTestMerges + .Where(x => !currentTestMerges .Any(y => y.TestMerge.Number == x.Number)) .ToList(); - var updatedTestMerges = deployedRevisionInformation - .ActiveTestMerges + var updatedTestMerges = currentTestMerges .Select(x => x.TestMerge) - .Where(x => previousRevisionInformation - .ActiveTestMerges + .Where(x => previousTestMerges .Any(y => y.TestMerge.Number == x.Number)) .ToList(); @@ -133,7 +130,12 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote repositorySettings, repoOwner, repoName, - "#### Test Merge Removed", + FormatTestMergeRemoval( + repositorySettings, + compileJob, + removedTestMerge, + repoOwner, + repoName), removedTestMerge.Number, cancellationToken); tasks.Add(removeCommentTask); @@ -253,6 +255,22 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote string remoteRepositoryName, bool updated); + /// + /// Formats a comment for a given removal. + /// + /// The to use. + /// The test merge's . + /// The . + /// The . + /// The . + /// A formatted for posting a informative comment about the removal. + protected abstract string FormatTestMergeRemoval( + RepositorySettings repositorySettings, + CompileJob compileJob, + TestMerge testMerge, + string remoteRepositoryOwner, + string remoteRepositoryName); + /// /// Create a comment of a given 's source. /// diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs index a5283ed469..790b9ea1d3 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs @@ -282,7 +282,17 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote try { - await gitHubService.CommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, comment, testMergeNumber, cancellationToken); + string header = String.Format(CultureInfo.InvariantCulture, "{1}{0}## Test merge deployment history:{0}{0}", Environment.NewLine, DeploymentMsgHeaderStart); + + var existingComment = await gitHubService.GetExistingCommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, DeploymentMsgHeaderStart, testMergeNumber, cancellationToken); + if (existingComment != null) + { + await gitHubService.AppendCommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, comment, existingComment, cancellationToken); + } + else + { + await gitHubService.CommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, header + comment, testMergeNumber, cancellationToken); + } } catch (Exception ex) when (ex is not OperationCanceledException) { @@ -299,30 +309,43 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote string remoteRepositoryName, bool updated) => String.Format( CultureInfo.InvariantCulture, - "#### Test Merge {4}{0}{0}
Details{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}{8}{0}
", - Environment.NewLine, + "
Test Merge {4} @ {8}:{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}{0}
{0}", + Environment.NewLine, // 0 repositorySettings.ShowTestMergeCommitters!.Value ? String.Format( CultureInfo.InvariantCulture, "{0}{0}##### Merged By{0}{1}", Environment.NewLine, testMerge.MergedBy!.Name) - : String.Empty, - testMerge.TargetCommitSha, - testMerge.Comment != null - ? String.Format( + : String.Empty, // 1 + testMerge.TargetCommitSha, // 2 + String.IsNullOrEmpty(testMerge.Comment) + ? String.Empty + : String.Format( CultureInfo.InvariantCulture, "{0}{0}##### Comment{0}{1}", Environment.NewLine, - testMerge.Comment) - : String.Empty, - updated ? "Updated" : "Deployed", - Metadata.Name, - compileJob.RevisionInformation.OriginCommitSha, - compileJob.RevisionInformation.CommitSha, + testMerge.Comment), // 3 + updated ? "Updated" : "Deployed", // 4 compileJob.GitHubDeploymentId.HasValue - ? $"{Environment.NewLine}[GitHub Deployments](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A+{Metadata.Name!.Replace(" ", "+", StringComparison.Ordinal)})" - : String.Empty); + ? $"{Environment.NewLine}[{Metadata.Name}](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A+{Metadata.Name!.Replace(" ", "+", StringComparison.Ordinal)})" + : Metadata.Name, // 5 + compileJob.RevisionInformation.OriginCommitSha, // 6 + compileJob.RevisionInformation.CommitSha, // 7 + compileJob.Job.StartedAt); // 8 + + /// + protected override string FormatTestMergeRemoval( + RepositorySettings repositorySettings, + CompileJob compileJob, + TestMerge testMerge, + string remoteRepositoryOwner, + string remoteRepositoryName) => String.Format( + CultureInfo.InvariantCulture, + "
Test Merge Removed @ {2}:{0}{0}##### Server Instance{0}{1}{0}
{0}", + Environment.NewLine, // 0 + Metadata.Name, // 1 + compileJob.Job.StartedAt); // 2 /// /// Update the deployment for a given . diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs index 9d0adeaf37..51b9e8e891 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs @@ -6,13 +6,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using GitLabApiClient; -using GitLabApiClient.Models.MergeRequests.Responses; -using GitLabApiClient.Models.Notes.Requests; using Microsoft.Extensions.Logging; +using StrawberryShake; + using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Models; +using Tgstation.Server.Host.Utils.GitLab.GraphQL; namespace Tgstation.Server.Host.Components.Deployment.Remote { @@ -52,49 +52,78 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote return Array.Empty(); } - var client = repositorySettings.AccessToken != null - ? new GitLabClient(GitLabRemoteFeatures.GitLabUrl, repositorySettings.AccessToken) - : new GitLabClient(GitLabRemoteFeatures.GitLabUrl); + var newList = revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList(); - var tasks = revisionInformation - .ActiveTestMerges - .Select(x => client - .MergeRequests - .GetAsync( - $"{repository.RemoteRepositoryOwner}/{repository.RemoteRepositoryName}", - x.TestMerge.Number) - .WaitAsync(cancellationToken)); + await using var client = await GraphQLGitLabClientFactory.CreateClient(repositorySettings.AccessToken); + IOperationResult operationResult; try { - await Task.WhenAll(tasks); + operationResult = await client.GraphQL.GetMergeRequests.ExecuteAsync( + $"{repository.RemoteRepositoryOwner}/{repository.RemoteRepositoryName}", + revisionInformation.ActiveTestMerges.Select(revInfoTestMerge => revInfoTestMerge.TestMerge.Number.ToString(CultureInfo.InvariantCulture)).ToList(), + cancellationToken); + + operationResult.EnsureNoErrors(); } catch (Exception ex) when (ex is not OperationCanceledException) { Logger.LogWarning(ex, "Merge requests update check failed!"); + return newList; } - var newList = revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList(); - - MergeRequest? lastMerged = null; - async ValueTask CheckRemoveMR(Task task) + var data = operationResult.Data?.Project?.MergeRequests?.Nodes; + if (data == null) { - var mergeRequest = await task; + Logger.LogWarning("GitLab MergeRequests check returned null!"); + return newList; + } + + async ValueTask CheckRemoveMR(IGetMergeRequests_Project_MergeRequests_Nodes? mergeRequest) + { + if (mergeRequest == null) + { + Logger.LogWarning("GitLab MergeRequest node was null!"); + return; + } + if (mergeRequest.State != MergeRequestState.Merged) return; - // We don't just assume, actually check the repo contains the merge commit. - if (await repository.CommittishIsParent(mergeRequest.MergeCommitSha, cancellationToken)) + var mergeCommitSha = mergeRequest.MergeCommitSha; + if (mergeCommitSha == null) { - if (lastMerged == null || lastMerged.ClosedAt < mergeRequest.ClosedAt) - lastMerged = mergeRequest; + Logger.LogWarning("MergeRequest #{id} had no MergeCommitSha!", mergeRequest.Iid); + return; + } + + var closedAtStr = mergeRequest.ClosedAt; + if (closedAtStr == null) + { + Logger.LogWarning("MergeRequest #{id} had no ClosedAt!", mergeRequest.Iid); + return; + } + + if (!DateTimeOffset.TryParseExact(closedAtStr, "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset closedAt)) + { + Logger.LogWarning("MergeRequest #{id} had invalid ClosedAt: {closedAt}", mergeRequest.Iid, closedAtStr); + return; + } + + if (!Int64.TryParse(mergeRequest.Iid, out long number)) + { + Logger.LogWarning("MergeRequest #{id} is non-numeric!", mergeRequest.Iid); + return; + } + + // We don't just assume, actually check the repo contains the merge commit. + if (await repository.CommittishIsParent(mergeCommitSha, cancellationToken)) newList.Remove( newList.First( - potential => potential.Number == mergeRequest.Id)); - } + potential => potential.Number == number)); } - foreach (var prTask in tasks) - await CheckRemoveMR(prTask); + foreach (var mergeRequest in data) + await CheckRemoveMR(mergeRequest); return newList; } @@ -131,23 +160,69 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote int testMergeNumber, CancellationToken cancellationToken) { - var client = repositorySettings.AccessToken != null - ? new GitLabClient(GitLabRemoteFeatures.GitLabUrl, repositorySettings.AccessToken) - : new GitLabClient(GitLabRemoteFeatures.GitLabUrl); - + await using var client = await GraphQLGitLabClientFactory.CreateClient(repositorySettings.AccessToken); try { - await client - .MergeRequests - .CreateNoteAsync( - $"{remoteRepositoryOwner}/{remoteRepositoryName}", - testMergeNumber, - new CreateMergeRequestNoteRequest(comment)) - .WaitAsync(cancellationToken); + string header = String.Format(CultureInfo.InvariantCulture, "{1}{0}## Test merge deployment history:{0}{0}", Environment.NewLine, DeploymentMsgHeaderStart); + + // Try to find an existing note + var notesQueryResult = await client.GraphQL.GetMergeRequestNotes.ExecuteAsync( + $"{remoteRepositoryOwner}/{remoteRepositoryName}", + testMergeNumber.ToString(CultureInfo.InvariantCulture), + cancellationToken); + + notesQueryResult.EnsureNoErrors(); + + var mergeRequest = notesQueryResult.Data?.Project?.MergeRequest; + if (mergeRequest == null) + { + Logger.LogWarning("GitLab GetMergeRequestNotes mergeRequest returned null!"); + return; + } + + var comments = mergeRequest.Notes?.Nodes; + IGetMergeRequestNotes_Project_MergeRequest_Notes_Nodes? existingComment = null; + if (comments != null) + { + for (int i = comments.Count - 1; i > -1; i--) + { + var currentComment = comments[i]; + if (currentComment?.Author?.Username == repositorySettings.AccessUser && (currentComment?.Body?.StartsWith(DeploymentMsgHeaderStart) ?? false)) + { + if (currentComment.Body.Length > 987856) + { // Limit should be 999,999 so we'll leave a 12,143 buffer + break; + } + + existingComment = currentComment; + break; + } + } + } + + // Either amend or create the note + if (existingComment != null) + { + var noteModificationResult = await client.GraphQL.ModifyNote.ExecuteAsync( + existingComment.Id, + existingComment.Body + comment, + cancellationToken); + + notesQueryResult.EnsureNoErrors(); + } + else + { + var noteCreationResult = await client.GraphQL.CreateNote.ExecuteAsync( + mergeRequest.Id, + header + comment, + cancellationToken); + + noteCreationResult.EnsureNoErrors(); + } } catch (Exception ex) when (ex is not OperationCanceledException) { - Logger.LogWarning(ex, "Error posting GitHub comment!"); + Logger.LogWarning(ex, "Error posting GitLab comment!"); } } @@ -160,26 +235,40 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote string remoteRepositoryName, bool updated) => String.Format( CultureInfo.InvariantCulture, - "#### Test Merge {4}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Merge Request: {2}{0}Server: {7}{3}", - Environment.NewLine, + "
Test Merge {4} @ {8}{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Merge Request: {2}{0}Server: {7}{3}
{0}", + Environment.NewLine, // 0 repositorySettings.ShowTestMergeCommitters!.Value ? String.Format( CultureInfo.InvariantCulture, "{0}{0}##### Merged By{0}{1}", Environment.NewLine, testMerge.MergedBy!.Name) - : String.Empty, - testMerge.TargetCommitSha, - testMerge.Comment != null - ? String.Format( + : String.Empty, // 1 + testMerge.TargetCommitSha, // 2 + String.IsNullOrEmpty(testMerge.Comment) + ? String.Empty + : String.Format( CultureInfo.InvariantCulture, "{0}{0}##### Comment{0}{1}", Environment.NewLine, - testMerge.Comment) - : String.Empty, - updated ? "Updated" : "Deployed", - Metadata.Name, - compileJob.RevisionInformation.OriginCommitSha, - compileJob.RevisionInformation.CommitSha); + testMerge.Comment), // 3 + updated ? "Updated" : "Deployed", // 4 + Metadata.Name, // 5 + compileJob.RevisionInformation.OriginCommitSha, // 6 + compileJob.RevisionInformation.CommitSha, // 7 + compileJob.Job.StartedAt); // 8 + + /// + protected override string FormatTestMergeRemoval( + RepositorySettings repositorySettings, + CompileJob compileJob, + TestMerge testMerge, + string remoteRepositoryOwner, + string remoteRepositoryName) => String.Format( + CultureInfo.InvariantCulture, + "
Test Merge Removed @ {2}:{0}{0}##### Server Instance{0}{1}{0}
{0}", + Environment.NewLine, // 0 + Metadata.Name, // 1 + compileJob.Job.StartedAt); // 2 } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs index 6751f18184..71766bf53c 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs @@ -65,6 +65,15 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote bool updated) => String.Empty; + /// + protected override string FormatTestMergeRemoval( + Models.RepositorySettings repositorySettings, + Models.CompileJob compileJob, + TestMerge testMerge, + string remoteRepositoryOwner, + string remoteRepositoryName) + => String.Empty; + /// protected override ValueTask MarkInactiveImpl(Models.CompileJob compileJob, CancellationToken cancellationToken) => ValueTask.CompletedTask; diff --git a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs index ffbd577ac2..58e2ebf274 100644 --- a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs @@ -1,11 +1,14 @@ using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; -using GitLabApiClient; using Microsoft.Extensions.Logging; +using StrawberryShake; + using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Utils.GitLab.GraphQL; namespace Tgstation.Server.Host.Components.Repository { @@ -45,30 +48,25 @@ namespace Tgstation.Server.Host.Components.Repository RepositorySettings repositorySettings, CancellationToken cancellationToken) { - var client = repositorySettings.AccessToken != null - ? new GitLabClient(GitLabUrl, repositorySettings.AccessToken) - : new GitLabClient(GitLabUrl); - + await using var client = await GraphQLGitLabClientFactory.CreateClient(repositorySettings.AccessToken); try { - var mr = await client - .MergeRequests - .GetAsync($"{RemoteRepositoryOwner}/{RemoteRepositoryName}", parameters.Number) - .WaitAsync(cancellationToken); + var operationResult = await client.GraphQL.GetMergeRequest.ExecuteAsync( + $"{RemoteRepositoryOwner}/{RemoteRepositoryName}", + parameters.Number.ToString(CultureInfo.InvariantCulture), + cancellationToken); - var revisionToUse = parameters.TargetCommitSha == null - || mr.Sha.StartsWith(parameters.TargetCommitSha, StringComparison.OrdinalIgnoreCase) - ? mr.Sha - : parameters.TargetCommitSha; + operationResult.EnsureNoErrors(); + var mr = operationResult.Data?.Project?.MergeRequest ?? throw new InvalidOperationException("GitLab MergeRequest check returned null!"); return new Models.TestMerge { - Author = mr.Author.Username, + Author = mr.Author?.Username, BodyAtMerge = mr.Description, TitleAtMerge = mr.Title, Comment = parameters.Comment, Number = parameters.Number, - TargetCommitSha = mr.Sha, + TargetCommitSha = mr.DiffHeadSha, Url = mr.WebUrl, }; } diff --git a/src/Tgstation.Server.Host/Configuration/OidcConfiguration.cs b/src/Tgstation.Server.Host/Configuration/OidcConfiguration.cs new file mode 100644 index 0000000000..3a7062bb44 --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/OidcConfiguration.cs @@ -0,0 +1,52 @@ +using System; + +using Microsoft.IdentityModel.JsonWebTokens; + +namespace Tgstation.Server.Host.Configuration +{ + /// + /// Configuration for an OpenID Connect provider. + /// + public sealed class OidcConfiguration + { + /// + /// The containing the .well-known endpoint for the provider. + /// + public Uri? Authority { get; set; } + + /// + /// The OIDC client ID. + /// + public string? ClientId { get; set; } + + /// + /// The OIDC client secret. + /// + public string? ClientSecret { get; set; } + + /// + /// The provider's name as it should be displayed to the user. + /// + public string? FriendlyName { get; set; } + + /// + /// The path to return to once OIDC authentication is complete. On success the "code" and "state" query strings will be set, containing a TGS token and "oidc.(scheme key)" strings respectively. On failure the "error" query string will be set with a user readable error message. + /// + public string? ReturnPath { get; set; } = "/app"; + + /// + /// Colour that should be used to theme this OIDC provider. + /// + public string? ThemeColour { get; set; } + + /// + /// Image URL that should be used to theme this OIDC provider. + /// + public Uri? ThemeIconUrl { get; set; } + + /// + /// The name of the claim used to set the user's name. + /// + public string UsernameClaim { get; set; } = JwtRegisteredClaimNames.PreferredUsername; + } +} diff --git a/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs b/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs index 35585612c0..14bffaf2bd 100644 --- a/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Swashbuckle.AspNetCore.SwaggerGen; @@ -62,6 +63,11 @@ namespace Tgstation.Server.Host.Configuration ///
public string? CustomTokenSigningKeyBase64 { get; set; } + /// + /// If OIDC strict mode should be enabled. This mode enforces the existence of at least one ion and allows users to register using them. Users must have the tgstation-server.group_id role set to a valid TGS group ID to login. This mode disables regular and OAuth login methods. + /// + public bool OidcStrictMode { get; set; } + /// /// OAuth provider settings. /// @@ -87,5 +93,23 @@ namespace Tgstation.Server.Host.Configuration /// Backing field for . /// IDictionary? oAuth; + + /// + /// OIDC provider settings keyed by scheme name. + /// + public IDictionary? OpenIDConnect { get; set; } + + /// + /// Get the s from the . + /// + /// An of s. + public IEnumerable OidcProviderInfos() + => OpenIDConnect?.Select(oidcConfig => new OidcProviderInfo + { + SchemeKey = oidcConfig.Key, + FriendlyName = oidcConfig.Value.FriendlyName ?? oidcConfig.Key, + ThemeColour = oidcConfig.Value.ThemeColour, + ThemeIconUrl = oidcConfig.Value.ThemeIconUrl, + }) ?? Enumerable.Empty(); } } diff --git a/src/Tgstation.Server.Host/Controllers/ApiRootController.cs b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs index 49f3e89c2b..ab7000e9e1 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiRootController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs @@ -70,6 +70,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly GeneralConfiguration generalConfiguration; + /// + /// The for the . + /// + readonly SecurityConfiguration securityConfiguration; + /// /// Initializes a new instance of the class. /// @@ -81,6 +86,7 @@ namespace Tgstation.Server.Host.Controllers /// The value of . /// The value of . /// The containing the value of . + /// The containing the value of . /// The for the . /// The for the . /// The value of . @@ -93,6 +99,7 @@ namespace Tgstation.Server.Host.Controllers ISwarmService swarmService, IServerControl serverControl, IOptions generalConfigurationOptions, + IOptionsSnapshot securityConfigurationOptions, ILogger logger, IApiHeadersProvider apiHeadersProvider, IRestAuthorityInvoker loginAuthority) @@ -109,6 +116,7 @@ namespace Tgstation.Server.Host.Controllers this.swarmService = swarmService ?? throw new ArgumentNullException(nameof(swarmService)); this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl)); generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); + securityConfiguration = securityConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(securityConfigurationOptions)); this.loginAuthority = loginAuthority ?? throw new ArgumentNullException(nameof(loginAuthority)); } @@ -162,7 +170,9 @@ namespace Tgstation.Server.Host.Controllers ?.Select(swarmServerInfo => new SwarmServerResponse(swarmServerInfo)) .ToList(), OAuthProviderInfos = oAuthProviders.ProviderInfos(), + OidcProviderInfos = securityConfiguration.OidcProviderInfos().ToList(), UpdateInProgress = serverControl.UpdateInProgress, + OidcStrictMode = securityConfiguration.OidcStrictMode, }); } diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 533aec872d..e99a801607 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -7,12 +7,12 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using GitLabApiClient; - using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using StrawberryShake; + using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; @@ -27,6 +27,7 @@ using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; using Tgstation.Server.Host.Utils; using Tgstation.Server.Host.Utils.GitHub; +using Tgstation.Server.Host.Utils.GitLab.GraphQL; namespace Tgstation.Server.Host.Controllers { @@ -585,70 +586,84 @@ namespace Tgstation.Server.Host.Controllers switch (remoteFeatures.RemoteGitProvider!.Value) { case RemoteGitProvider.GitHub: - var gitHubClient = await gitHubClientFactory.CreateClientForRepository( + { + var gitHubClient = await gitHubClientFactory.CreateClientForRepository( model.AccessToken, new RepositoryIdentifier( remoteFeatures.RemoteRepositoryOwner!, remoteFeatures.RemoteRepositoryName!), cancellationToken); - if (gitHubClient == null) - { - return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) + if (gitHubClient == null) { - AdditionalData = "GitHub authentication failed!", - }); - } - - try - { - string username; - if (!model.AccessToken.StartsWith(Api.Models.RepositorySettings.TgsAppPrivateKeyPrefix)) - { - var user = await gitHubClient.User.Current(); - username = user.Login; - } - else - { - // we literally need to app auth again to get the damn bot username - var appClient = gitHubClientFactory.CreateAppClient(model.AccessToken)!; - var app = await appClient.GitHubApps.GetCurrent(); - username = app.Name; + return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) + { + AdditionalData = "GitHub authentication failed!", + }); } - if (username != model.AccessUser) - return Conflict(new ErrorMessageResponse(ErrorCode.RepoTokenUsernameMismatch)); - } - catch (Exception ex) - { - return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) + try { - AdditionalData = $"GitHub Authentication Failure: {ex.Message}", - }); + string username; + if (!model.AccessToken.StartsWith(Api.Models.RepositorySettings.TgsAppPrivateKeyPrefix)) + { + var user = await gitHubClient.User.Current(); + username = user.Login; + } + else + { + // we literally need to app auth again to get the damn bot username + var appClient = gitHubClientFactory.CreateAppClient(model.AccessToken)!; + var app = await appClient.GitHubApps.GetCurrent(); + username = app.Name; + } + + if (username != model.AccessUser) + return Conflict(new ErrorMessageResponse(ErrorCode.RepoTokenUsernameMismatch)); + } + catch (Exception ex) + { + return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) + { + AdditionalData = $"GitHub Authentication Failure: {ex.Message}", + }); + } + + break; } - break; case RemoteGitProvider.GitLab: - // need to abstract this eventually - var gitLabClient = new GitLabClient(GitLabRemoteFeatures.GitLabUrl, model.AccessToken); - try { - var user = await gitLabClient.Users.GetCurrentSessionAsync(); - if (user.Username != model.AccessUser) - return Conflict(new ErrorMessageResponse(ErrorCode.RepoTokenUsernameMismatch)); - } - catch (Exception ex) - { - return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) + // need to abstract this eventually + await using var gitLabClient = await GraphQLGitLabClientFactory.CreateClient(model.AccessToken); + try { - AdditionalData = $"GitLab Authentication Failure: {ex.Message}", - }); + var operationResult = await gitLabClient.GraphQL.GetCurrentUser.ExecuteAsync(cancellationToken); + + operationResult.EnsureNoErrors(); + + var user = operationResult.Data?.CurrentUser; + if (user == null || user.Username != model.AccessUser) + { + return Conflict(new ErrorMessageResponse(ErrorCode.RepoTokenUsernameMismatch)); + } + } + catch (Exception ex) + { + return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) + { + AdditionalData = $"GitLab Authentication Failure: {ex.Message}", + }); + } + + break; } - break; case RemoteGitProvider.Unknown: default: - Logger.LogWarning("RemoteGitProvider is {provider}, no auth check implemented!", remoteFeatures.RemoteGitProvider.Value); - break; + { + Logger.LogWarning("RemoteGitProvider is {provider}, no auth check implemented!", remoteFeatures.RemoteGitProvider.Value); + break; + } } return null; diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 08f39565a6..ba07babec1 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -3,6 +3,7 @@ using System.Collections.Frozen; using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; +using System.Web; using Cyberboss.AspNetCore.AsyncInitializer; @@ -13,7 +14,9 @@ using HotChocolate.Subscriptions; using HotChocolate.Types; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Hosting; @@ -27,6 +30,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Newtonsoft.Json; @@ -121,6 +125,14 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); } + /// + /// Get the OIDC authentication scheme name for a given . + /// + /// The scheme key for the . + /// The authentication scheme name. + static string GetOidcScheme(string schemeKey) + => AuthenticationContextFactory.OpenIDConnectAuthenticationSchemePrefix + schemeKey; + /// /// Initializes a new instance of the class. /// @@ -239,7 +251,7 @@ namespace Tgstation.Server.Host.Core postSetupServices.FileLoggingConfiguration); // configure authentication pipeline - ConfigureAuthenticationPipeline(services); + ConfigureAuthenticationPipeline(services, postSetupServices.SecurityConfiguration); // add mvc, configure the json serializer settings var jsonVersionConverterList = new List @@ -316,9 +328,21 @@ namespace Tgstation.Server.Host.Core .AddScoped() .AddGraphQLServer() .AddAuthorization( - options => options.AddPolicy( - TgsAuthorizeAttribute.PolicyName, - builder => builder.RequireRole(TgsAuthorizeAttribute.UserEnabledRole))) + options => + { + options.AddPolicy( + TgsAuthorizeAttribute.PolicyName, + builder => builder + .RequireAuthenticatedUser() + .RequireRole(TgsAuthorizeAttribute.UserEnabledRole)); + options.AddPolicy( + "testingasdf", + builder => + { + builder.RequireAuthenticatedUser(); + builder.AuthenticationSchemes.Add(CookieAuthenticationDefaults.AuthenticationScheme); + }); + }) .ModifyOptions(options => { options.EnsureAllNodesCanBeResolved = true; @@ -548,6 +572,7 @@ namespace Tgstation.Server.Host.Core /// The containing the to use. /// The containing the to use. /// The containing the to use. + /// The containing the to use. /// The containing the to use. /// The containing the to use. /// The for the . @@ -560,6 +585,7 @@ namespace Tgstation.Server.Host.Core IOptions controlPanelConfigurationOptions, IOptions generalConfigurationOptions, IOptions databaseConfigurationOptions, + IOptions securityConfigurationOptions, IOptions swarmConfigurationOptions, IOptions internalConfigurationOptions, ILogger logger) @@ -728,6 +754,20 @@ namespace Tgstation.Server.Host.Core logger.LogTrace("Prometheus disabled"); endpoints.MapHealthChecks("/health"); + + var oidcConfig = securityConfigurationOptions.Value.OpenIDConnect; + if (oidcConfig == null) + return; + + foreach (var kvp in oidcConfig) + endpoints.MapGet( + $"/oidc/{kvp.Key}/signin", + context => context.ChallengeAsync( + GetOidcScheme(kvp.Key), + new AuthenticationProperties + { + RedirectUri = $"/oidc/{kvp.Key}/landing", + })); }); // 404 anything that gets this far @@ -748,7 +788,8 @@ namespace Tgstation.Server.Host.Core /// Configure the for the authentication pipeline. /// /// The to configure. - void ConfigureAuthenticationPipeline(IServiceCollection services) + /// The . + void ConfigureAuthenticationPipeline(IServiceCollection services, SecurityConfiguration securityConfiguration) { services.AddHttpContextAccessor(); services.AddScoped(); @@ -768,8 +809,11 @@ namespace Tgstation.Server.Host.Core .CurrentAuthenticationContext); services.AddScoped(); - services - .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + var authBuilder = services + .AddAuthentication(options => + { + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }) .AddJwtBearer(jwtBearerOptions => { // this line isn't actually run until the first request is made @@ -798,13 +842,104 @@ namespace Tgstation.Server.Host.Core .HttpContext .RequestServices .GetRequiredService() - .ValidateToken( + .ValidateTgsToken( context, context .HttpContext .RequestAborted), }; }); + + var oidcConfig = securityConfiguration.OpenIDConnect; + if (oidcConfig == null || oidcConfig.Count == 0) + return; + + authBuilder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme); + + foreach (var kvp in oidcConfig) + { + var configName = kvp.Key; + authBuilder + .AddOpenIdConnect( + GetOidcScheme(configName), + options => + { + var config = kvp.Value; + + options.Authority = config.Authority?.ToString(); + options.ClientId = config.ClientId; + options.ClientSecret = config.ClientSecret; + + options.Events = new OpenIdConnectEvents + { + OnRemoteFailure = context => + { + context.HandleResponse(); + context.HttpContext.Response.Redirect($"{config.ReturnPath}?error={HttpUtility.UrlEncode(context.Failure?.Message ?? $"{options.Events.OnRemoteFailure} was called without an {nameof(Exception)}!")}&state=oidc.{HttpUtility.UrlEncode(configName)}"); + return Task.CompletedTask; + }, + OnTicketReceived = context => + { + var services = context + .HttpContext + .RequestServices; + var tokenFactory = services + .GetRequiredService(); + var authenticationContext = services + .GetRequiredService(); + context.HandleResponse(); + context.HttpContext.Response.Redirect($"{config.ReturnPath}?code={HttpUtility.UrlEncode(tokenFactory.CreateToken(authenticationContext.User, true))}&state=oidc.{HttpUtility.UrlEncode(configName)}"); + return Task.CompletedTask; + }, + }; + + Task CompleteAuth(RemoteAuthenticationContext context) + => context + .HttpContext + .RequestServices + .GetRequiredService() + .ValidateOidcToken( + context, + configName, + context + .HttpContext + .RequestAborted); + + if (securityConfiguration.OidcStrictMode) + { + options.GetClaimsFromUserInfoEndpoint = true; + options.ClaimActions.MapUniqueJsonKey(AuthenticationContextFactory.TgsGroupIdClaimName, AuthenticationContextFactory.TgsGroupIdClaimName); + options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters + { + NameClaimType = config.UsernameClaim, + RoleClaimType = "roles", + }; + + options.Scope.Add(OpenIdConnectScope.Profile); + options.Events.OnUserInformationReceived = CompleteAuth; + } + else + options.Events.OnTokenValidated = CompleteAuth; + + options.Scope.Add(OpenIdConnectScope.OpenId); + options.Scope.Add(OpenIdConnectScope.OfflineAccess); + +#if DEBUG + options.RequireHttpsMetadata = false; +#endif + + options.SaveTokens = true; + options.ResponseType = OpenIdConnectResponseType.Code; + options.MapInboundClaims = false; + + options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + + var basePath = $"/oidc/{configName}/"; + options.CallbackPath = new PathString(basePath + "signin-callback"); + options.SignedOutCallbackPath = new PathString(basePath + "signout-callback"); + options.RemoteSignOutPath = new PathString(basePath + "signout"); + }); + } } } } diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index 6b8013f8c6..193a7d62dd 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -98,6 +98,11 @@ namespace Tgstation.Server.Host.Database /// public DbSet OAuthConnections { get; set; } + /// + /// The s in the . + /// + public DbSet OidcConnections { get; set; } + /// /// The s in the . /// @@ -153,6 +158,9 @@ namespace Tgstation.Server.Host.Database /// IDatabaseCollection IDatabaseContext.OAuthConnections => oAuthConnections; + /// + IDatabaseCollection IDatabaseContext.OidcConnections => oidcConnections; + /// IDatabaseCollection IDatabaseContext.Groups => groups; @@ -239,6 +247,11 @@ namespace Tgstation.Server.Host.Database /// readonly IDatabaseCollection oAuthConnections; + /// + /// Backing field for . + /// + readonly IDatabaseCollection oidcConnections; + /// /// Backing field for . /// @@ -288,6 +301,7 @@ namespace Tgstation.Server.Host.Database jobsCollection = new DatabaseCollection(Jobs!); reattachInformationsCollection = new DatabaseCollection(ReattachInformations!); oAuthConnections = new DatabaseCollection(OAuthConnections!); + oidcConnections = new DatabaseCollection(OidcConnections!); groups = new DatabaseCollection(Groups!); permissionSets = new DatabaseCollection(PermissionSets!); } @@ -387,8 +401,10 @@ namespace Tgstation.Server.Host.Database userModel.HasIndex(x => x.SystemIdentifier).IsUnique(); userModel.HasMany(x => x.TestMerges).WithOne(x => x.MergedBy).OnDelete(DeleteBehavior.Restrict); userModel.HasMany(x => x.OAuthConnections).WithOne(x => x.User).OnDelete(DeleteBehavior.Cascade); + userModel.HasMany(x => x.OidcConnections).WithOne(x => x.User).OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasIndex(x => new { x.Provider, x.ExternalUserId }).IsUnique(); + modelBuilder.Entity().HasIndex(x => new { x.SchemeKey, x.ExternalUserId }).IsUnique(); var groupsModel = modelBuilder.Entity(); groupsModel.HasIndex(x => x.Name).IsUnique(); @@ -451,22 +467,22 @@ namespace Tgstation.Server.Host.Database /// /// Used by unit tests to remind us to setup the correct MSSQL migration downgrades. /// - internal static readonly Type MSLatestMigration = typeof(MSAddAutoStartAndStop); + internal static readonly Type MSLatestMigration = typeof(MSAddOidcConnections); /// /// Used by unit tests to remind us to setup the correct MYSQL migration downgrades. /// - internal static readonly Type MYLatestMigration = typeof(MYAddAutoStartAndStop); + internal static readonly Type MYLatestMigration = typeof(MYAddOidcConnections); /// /// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades. /// - internal static readonly Type PGLatestMigration = typeof(PGAddAutoStartAndStop); + internal static readonly Type PGLatestMigration = typeof(PGAddOidcConnections); /// /// Used by unit tests to remind us to setup the correct SQLite migration downgrades. /// - internal static readonly Type SLLatestMigration = typeof(SLAddAutoStartAndStop); + internal static readonly Type SLLatestMigration = typeof(SLAddOidcConnections); /// /// Gets the name of the migration to run for migrating down to a given for the . @@ -482,6 +498,16 @@ namespace Tgstation.Server.Host.Database string BadDatabaseType() => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)); // !!! DON'T FORGET TO UPDATE THE SWARM PROTOCOL MAJOR VERSION !!! + if (targetVersion < new Version(6, 15, 0)) + targetMigration = currentDatabaseType switch + { + DatabaseType.MySql => nameof(MYAddAutoStartAndStop), + DatabaseType.PostgresSql => nameof(PGAddAutoStartAndStop), + DatabaseType.SqlServer => nameof(MSAddAutoStartAndStop), + DatabaseType.Sqlite => nameof(SLAddAutoStartAndStop), + _ => BadDatabaseType(), + }; + if (targetVersion < new Version(6, 12, 0)) targetMigration = currentDatabaseType switch { diff --git a/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs index f67028efff..04fa4eb139 100644 --- a/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs +++ b/src/Tgstation.Server.Host/Database/Design/MySqlDesignTimeDbContextFactory.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.Database.Design { /// public MySqlDatabaseContext CreateDbContext(string[] args) - => new MySqlDatabaseContext( + => new( DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions( DatabaseType.MariaDB, "Server=127.0.0.1;User Id=root;Password=zdxfOOTlQFnklwzytzCj;Database=TGS_Design")); diff --git a/src/Tgstation.Server.Host/Database/IDatabaseContext.cs b/src/Tgstation.Server.Host/Database/IDatabaseContext.cs index 2ca3f748f6..b947de2dbd 100644 --- a/src/Tgstation.Server.Host/Database/IDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/IDatabaseContext.cs @@ -90,6 +90,11 @@ namespace Tgstation.Server.Host.Database /// IDatabaseCollection OAuthConnections { get; } + /// + /// The for s. + /// + IDatabaseCollection OidcConnections { get; } + /// /// The for s. /// diff --git a/src/Tgstation.Server.Host/Database/Migrations/20250302232550_MSAddOidcConnections.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20250302232550_MSAddOidcConnections.Designer.cs new file mode 100644 index 0000000000..ad2d731905 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20250302232550_MSAddOidcConnections.Designer.cs @@ -0,0 +1,1146 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20250302232550_MSAddOidcConnections")] + partial class MSAddOidcConnections + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .HasColumnType("int"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique() + .HasFilter("[DiscordChannelId] IS NOT NULL"); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique() + .HasFilter("[IrcChannel] IS NOT NULL"); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("uniqueidentifier"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("GitHubDeploymentId") + .HasColumnType("bigint"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RepositoryOrigin") + .HasColumnType("nvarchar(max)"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("HealthCheckSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("bit"); + + b.Property("MapThreads") + .HasColumnType("bigint"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("bit"); + + b.Property("OpenDreamTopicPort") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("bit"); + + b.Property("StartupTimeout") + .HasColumnType("bigint"); + + b.Property("TopicRequestTimeout") + .HasColumnType("bigint"); + + b.Property("Visibility") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .HasColumnType("int"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("DMApiValidationMode") + .HasColumnType("int"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("time"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AutoStartCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("AutoStopCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("AutoUpdateCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("AutoUpdateInterval") + .HasColumnType("bigint"); + + b.Property("ChatBotLimit") + .HasColumnType("int"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Online") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("SwarmIdentifer") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique() + .HasFilter("[SwarmIdentifer] IS NOT NULL"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("decimal(20,0)"); + + b.Property("ConfigurationRights") + .HasColumnType("decimal(20,0)"); + + b.Property("DreamDaemonRights") + .HasColumnType("decimal(20,0)"); + + b.Property("DreamMakerRights") + .HasColumnType("decimal(20,0)"); + + b.Property("EngineRights") + .HasColumnType("decimal(20,0)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("decimal(20,0)"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("decimal(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("decimal(20,0)"); + + b.Property("CancelRightsType") + .HasColumnType("decimal(20,0)"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("bit"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ErrorCode") + .HasColumnType("bigint"); + + b.Property("ExceptionDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("tinyint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetimeoffset"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SchemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemeKey", "ExternalUserId") + .IsUnique(); + + b.ToTable("OidcConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("decimal(20,0)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("decimal(20,0)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique() + .HasFilter("[GroupId] IS NOT NULL"); + + b.HasIndex("UserId") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("LaunchVisibility") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.Property("TopicPort") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("bit"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("bit"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("bit"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("bit"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("bit"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("nvarchar(max)"); + + b.Property("MergedAt") + .HasColumnType("datetimeoffset"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetimeoffset"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("bit"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique() + .HasFilter("[SystemIdentifier] IS NOT NULL"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OidcConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("OidcConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20250302232550_MSAddOidcConnections.cs b/src/Tgstation.Server.Host/Database/Migrations/20250302232550_MSAddOidcConnections.cs new file mode 100644 index 0000000000..d176cbea59 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20250302232550_MSAddOidcConnections.cs @@ -0,0 +1,57 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class MSAddOidcConnections : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.CreateTable( + name: "OidcConnections", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "bigint", nullable: false), + SchemeKey = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + ExternalUserId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_OidcConnections", x => x.Id); + table.ForeignKey( + name: "FK_OidcConnections_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OidcConnections_SchemeKey_ExternalUserId", + table: "OidcConnections", + columns: new[] { "SchemeKey", "ExternalUserId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_OidcConnections_UserId", + table: "OidcConnections", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropTable( + name: "OidcConnections"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20250302232601_MYAddOidcConnections.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20250302232601_MYAddOidcConnections.Designer.cs new file mode 100644 index 0000000000..ce403613c5 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20250302232601_MYAddOidcConnections.Designer.cs @@ -0,0 +1,1217 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20250302232601_MYAddOidcConnections")] + partial class MYAddOidcConnections + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ConnectionString"), "utf8mb4"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("bigint unsigned"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("IrcChannel"), "utf8mb4"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Tag"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("char(36)"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("DmeName"), "utf8mb4"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("EngineVersion"), "utf8mb4"); + + b.Property("GitHubDeploymentId") + .HasColumnType("bigint"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Output"), "utf8mb4"); + + b.Property("RepositoryOrigin") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("RepositoryOrigin"), "utf8mb4"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AdditionalParameters"), "utf8mb4"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("HealthCheckSeconds") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("MapThreads") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("OpenDreamTopicPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("Port") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("Visibility") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("varchar(10000)"); + + b.Property("DMApiValidationMode") + .HasColumnType("int"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ProjectName"), "utf8mb4"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("time(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AutoStartCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("AutoStopCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("AutoUpdateCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.Property("Online") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("varchar(255)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Path"), "utf8mb4"); + + b.Property("SwarmIdentifer") + .HasColumnType("varchar(255)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("SwarmIdentifer"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("bigint unsigned"); + + b.Property("ConfigurationRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamDaemonRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamMakerRights") + .HasColumnType("bigint unsigned"); + + b.Property("EngineRights") + .HasColumnType("bigint unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("bigint unsigned"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("bigint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("bigint unsigned"); + + b.Property("CancelRightsType") + .HasColumnType("bigint unsigned"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Description"), "utf8mb4"); + + b.Property("ErrorCode") + .HasColumnType("int unsigned"); + + b.Property("ExceptionDetails") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ExceptionDetails"), "utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("tinyint unsigned"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("ExternalUserId"), "utf8mb4"); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SchemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemeKey", "ExternalUserId") + .IsUnique(); + + b.ToTable("OidcConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("bigint unsigned"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("bigint unsigned"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessIdentifier"), "utf8mb4"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("LaunchVisibility") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("smallint unsigned"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.Property("TopicPort") + .HasColumnType("smallint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessToken"), "utf8mb4"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("AccessUser"), "utf8mb4"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitterEmail"), "utf8mb4"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitterName"), "utf8mb4"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CommitSha"), "utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("OriginCommitSha"), "utf8mb4"); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Author"), "utf8mb4"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("BodyAtMerge"), "utf8mb4"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Comment"), "utf8mb4"); + + b.Property("MergedAt") + .HasColumnType("datetime(6)"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("TargetCommitSha"), "utf8mb4"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("TitleAtMerge"), "utf8mb4"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Url"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("CanonicalName"), "utf8mb4"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("PasswordHash"), "utf8mb4"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("SystemIdentifier"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + MySqlPropertyBuilderExtensions.HasCharSet(b.Property("Name"), "utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OidcConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("OidcConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20250302232601_MYAddOidcConnections.cs b/src/Tgstation.Server.Host/Database/Migrations/20250302232601_MYAddOidcConnections.cs new file mode 100644 index 0000000000..44f5008d52 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20250302232601_MYAddOidcConnections.cs @@ -0,0 +1,61 @@ +using System; + +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class MYAddOidcConnections : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.CreateTable( + name: "OidcConnections", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "bigint", nullable: false), + SchemeKey = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ExternalUserId = table.Column(type: "varchar(100)", maxLength: 100, nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + }, + constraints: table => + { + table.PrimaryKey("PK_OidcConnections", x => x.Id); + table.ForeignKey( + name: "FK_OidcConnections_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_OidcConnections_SchemeKey_ExternalUserId", + table: "OidcConnections", + columns: new[] { "SchemeKey", "ExternalUserId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_OidcConnections_UserId", + table: "OidcConnections", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropTable( + name: "OidcConnections"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20250302232611_PGAddOidcConnections.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20250302232611_PGAddOidcConnections.Designer.cs new file mode 100644 index 0000000000..9d96e0bdea --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20250302232611_PGAddOidcConnections.Designer.cs @@ -0,0 +1,1140 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(PostgresSqlDatabaseContext))] + [Migration("20250302232611_PGAddOidcConnections")] + partial class PGAddOidcConnections + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChannelLimit") + .HasColumnType("integer"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("ReconnectionInterval") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DMApiMajorVersion") + .HasColumnType("integer"); + + b.Property("DMApiMinorVersion") + .HasColumnType("integer"); + + b.Property("DMApiPatchVersion") + .HasColumnType("integer"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("GitHubDeploymentId") + .HasColumnType("bigint"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("integer"); + + b.Property("Output") + .IsRequired() + .HasColumnType("text"); + + b.Property("RepositoryOrigin") + .HasColumnType("text"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("HealthCheckSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("MapThreads") + .HasColumnType("bigint"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("OpenDreamTopicPort") + .HasColumnType("integer"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("SecurityLevel") + .HasColumnType("integer"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("StartupTimeout") + .HasColumnType("bigint"); + + b.Property("TopicRequestTimeout") + .HasColumnType("bigint"); + + b.Property("Visibility") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApiValidationPort") + .HasColumnType("integer"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("integer"); + + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("DMApiValidationMode") + .HasColumnType("integer"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("interval"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoStartCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("AutoStopCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("AutoUpdateCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("AutoUpdateInterval") + .HasColumnType("bigint"); + + b.Property("ChatBotLimit") + .HasColumnType("integer"); + + b.Property("ConfigurationType") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Online") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SwarmIdentifer") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChatBotRights") + .HasColumnType("numeric(20,0)"); + + b.Property("ConfigurationRights") + .HasColumnType("numeric(20,0)"); + + b.Property("DreamDaemonRights") + .HasColumnType("numeric(20,0)"); + + b.Property("DreamMakerRights") + .HasColumnType("numeric(20,0)"); + + b.Property("EngineRights") + .HasColumnType("numeric(20,0)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("numeric(20,0)"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CancelRight") + .HasColumnType("numeric(20,0)"); + + b.Property("CancelRightsType") + .HasColumnType("numeric(20,0)"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ErrorCode") + .HasColumnType("bigint"); + + b.Property("ExceptionDetails") + .HasColumnType("text"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("JobCode") + .HasColumnType("smallint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SchemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemeKey", "ExternalUserId") + .IsUnique(); + + b.ToTable("OidcConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdministrationRights") + .HasColumnType("numeric(20,0)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("numeric(20,0)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("InitialCompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("integer"); + + b.Property("LaunchVisibility") + .HasColumnType("integer"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ProcessId") + .HasColumnType("integer"); + + b.Property("RebootState") + .HasColumnType("integer"); + + b.Property("TopicPort") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("character varying(10000)"); + + b.Property("MergedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OidcConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("OidcConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20250302232611_PGAddOidcConnections.cs b/src/Tgstation.Server.Host/Database/Migrations/20250302232611_PGAddOidcConnections.cs new file mode 100644 index 0000000000..f5727efa85 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20250302232611_PGAddOidcConnections.cs @@ -0,0 +1,59 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class PGAddOidcConnections : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.CreateTable( + name: "OidcConnections", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "bigint", nullable: false), + SchemeKey = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + ExternalUserId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_OidcConnections", x => x.Id); + table.ForeignKey( + name: "FK_OidcConnections_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OidcConnections_SchemeKey_ExternalUserId", + table: "OidcConnections", + columns: new[] { "SchemeKey", "ExternalUserId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_OidcConnections_UserId", + table: "OidcConnections", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropTable( + name: "OidcConnections"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20250302232622_SLAddOidcConnections.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20250302232622_SLAddOidcConnections.Designer.cs new file mode 100644 index 0000000000..9b72276b7d --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20250302232622_SLAddOidcConnections.Designer.cs @@ -0,0 +1,1111 @@ +// +using System; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20250302232622_SLAddOidcConnections")] + partial class SLAddOidcConnections + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.13"); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ConnectionString") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChatSettingsId") + .HasColumnType("INTEGER"); + + b.Property("DiscordChannelId") + .HasColumnType("INTEGER"); + + b.Property("IrcChannel") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsSystemChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Tag") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DMApiMajorVersion") + .HasColumnType("INTEGER"); + + b.Property("DMApiMinorVersion") + .HasColumnType("INTEGER"); + + b.Property("DMApiPatchVersion") + .HasColumnType("INTEGER"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EngineVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GitHubDeploymentId") + .HasColumnType("INTEGER"); + + b.Property("GitHubRepoId") + .HasColumnType("INTEGER"); + + b.Property("JobId") + .HasColumnType("INTEGER"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("Output") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RepositoryOrigin") + .HasColumnType("TEXT"); + + b.Property("RevisionInformationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalParameters") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("DumpOnHealthCheckRestart") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("HealthCheckSeconds") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("LogOutput") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("MapThreads") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Minidumps") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("OpenDreamTopicPort") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Port") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("SecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("StartProfiler") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Visibility") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("CompilerAdditionalArguments") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("DMApiValidationMode") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("ProjectName") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("Timeout") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoStartCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("AutoStopCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("AutoUpdateCron") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ConfigurationType") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Online") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SwarmIdentifer") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChatBotRights") + .HasColumnType("INTEGER"); + + b.Property("ConfigurationRights") + .HasColumnType("INTEGER"); + + b.Property("DreamDaemonRights") + .HasColumnType("INTEGER"); + + b.Property("DreamMakerRights") + .HasColumnType("INTEGER"); + + b.Property("EngineRights") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("INTEGER"); + + b.Property("PermissionSetId") + .HasColumnType("INTEGER"); + + b.Property("RepositoryRights") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CancelRight") + .HasColumnType("INTEGER"); + + b.Property("CancelRightsType") + .HasColumnType("INTEGER"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("CancelledById") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ErrorCode") + .HasColumnType("INTEGER"); + + b.Property("ExceptionDetails") + .HasColumnType("TEXT"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("JobCode") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartedById") + .HasColumnType("INTEGER"); + + b.Property("StoppedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SchemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemeKey", "ExternalUserId") + .IsUnique(); + + b.ToTable("OidcConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdministrationRights") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("InstanceManagerRights") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CompileJobId") + .HasColumnType("INTEGER"); + + b.Property("InitialCompileJobId") + .HasColumnType("INTEGER"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("LaunchVisibility") + .HasColumnType("INTEGER"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ProcessId") + .HasColumnType("INTEGER"); + + b.Property("RebootState") + .HasColumnType("INTEGER"); + + b.Property("TopicPort") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.HasIndex("InitialCompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AccessUser") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("CommitterEmail") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("CommitterName") + .IsRequired() + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("UpdateSubmodules") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RevisionInformationId") + .HasColumnType("INTEGER"); + + b.Property("TestMergeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Comment") + .HasMaxLength(10000) + .HasColumnType("TEXT"); + + b.Property("MergedAt") + .HasColumnType("TEXT"); + + b.Property("MergedById") + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CanonicalName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("LastPasswordUpdate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("SystemIdentifier") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChatSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("RevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + + b.Navigation("PermissionSet"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CancelledBy"); + + b.Navigation("Instance"); + + b.Navigation("StartedBy"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OidcConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "InitialCompileJob") + .WithMany() + .HasForeignKey("InitialCompileJobId"); + + b.Navigation("CompileJob"); + + b.Navigation("InitialCompileJob"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("RevisionInformation"); + + b.Navigation("TestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Instance"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MergedBy"); + + b.Navigation("PrimaryRevisionInformation"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + + b.Navigation("CreatedBy"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Navigation("Channels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Navigation("ChatSettings"); + + b.Navigation("DreamDaemonSettings"); + + b.Navigation("DreamMakerSettings"); + + b.Navigation("InstancePermissionSets"); + + b.Navigation("Jobs"); + + b.Navigation("RepositorySettings"); + + b.Navigation("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Navigation("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Navigation("ActiveTestMerges"); + + b.Navigation("CompileJobs"); + + b.Navigation("PrimaryTestMerge"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Navigation("RevisonInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Navigation("CreatedUsers"); + + b.Navigation("OAuthConnections"); + + b.Navigation("OidcConnections"); + + b.Navigation("PermissionSet"); + + b.Navigation("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Navigation("PermissionSet") + .IsRequired(); + + b.Navigation("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20250302232622_SLAddOidcConnections.cs b/src/Tgstation.Server.Host/Database/Migrations/20250302232622_SLAddOidcConnections.cs new file mode 100644 index 0000000000..e65dd77083 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20250302232622_SLAddOidcConnections.cs @@ -0,0 +1,57 @@ +using System; + +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + public partial class SLAddOidcConnections : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.CreateTable( + name: "OidcConnections", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "INTEGER", nullable: false), + SchemeKey = table.Column(type: "TEXT", maxLength: 100, nullable: false), + ExternalUserId = table.Column(type: "TEXT", maxLength: 100, nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_OidcConnections", x => x.Id); + table.ForeignKey( + name: "FK_OidcConnections_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OidcConnections_SchemeKey_ExternalUserId", + table: "OidcConnections", + columns: new[] { "SchemeKey", "ExternalUserId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_OidcConnections_UserId", + table: "OidcConnections", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropTable( + name: "OidcConnections"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs index 82823ebae9..6d7c085311 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("ProductVersion", "8.0.13") .HasAnnotation("Relational:MaxIdentifierLength", 64); MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); @@ -512,6 +512,37 @@ namespace Tgstation.Server.Host.Database b.ToTable("OAuthConnections"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SchemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemeKey", "ExternalUserId") + .IsUnique(); + + b.ToTable("OidcConnections"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => { b.Property("Id") @@ -996,6 +1027,17 @@ namespace Tgstation.Server.Host.Database b.Navigation("User"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OidcConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => { b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") @@ -1152,6 +1194,8 @@ namespace Tgstation.Server.Host.Database b.Navigation("OAuthConnections"); + b.Navigation("OidcConnections"); + b.Navigation("PermissionSet"); b.Navigation("TestMerges"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs index 281085cf05..bf16153eb6 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("ProductVersion", "8.0.13") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -471,6 +471,37 @@ namespace Tgstation.Server.Host.Database b.ToTable("OAuthConnections"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SchemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemeKey", "ExternalUserId") + .IsUnique(); + + b.ToTable("OidcConnections"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => { b.Property("Id") @@ -919,6 +950,17 @@ namespace Tgstation.Server.Host.Database b.Navigation("User"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OidcConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => { b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") @@ -1075,6 +1117,8 @@ namespace Tgstation.Server.Host.Database b.Navigation("OAuthConnections"); + b.Navigation("OidcConnections"); + b.Navigation("PermissionSet"); b.Navigation("TestMerges"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs index 28821afc19..f7d224b1f0 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("ProductVersion", "8.0.13") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -474,6 +474,37 @@ namespace Tgstation.Server.Host.Database b.ToTable("OAuthConnections"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SchemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemeKey", "ExternalUserId") + .IsUnique(); + + b.ToTable("OidcConnections"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => { b.Property("Id") @@ -925,6 +956,17 @@ namespace Tgstation.Server.Host.Database b.Navigation("User"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OidcConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => { b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") @@ -1081,6 +1123,8 @@ namespace Tgstation.Server.Host.Database b.Navigation("OAuthConnections"); + b.Navigation("OidcConnections"); + b.Navigation("PermissionSet"); b.Navigation("TestMerges"); diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs index 3de5ce1013..69519eadf3 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs @@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Database protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.13"); modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => { @@ -460,6 +460,35 @@ namespace Tgstation.Server.Host.Database b.ToTable("OAuthConnections"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ExternalUserId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SchemeKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("SchemeKey", "ExternalUserId") + .IsUnique(); + + b.ToTable("OidcConnections"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => { b.Property("Id") @@ -892,6 +921,17 @@ namespace Tgstation.Server.Host.Database b.Navigation("User"); }); + modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OidcConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => { b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") @@ -1048,6 +1088,8 @@ namespace Tgstation.Server.Host.Database b.Navigation("OAuthConnections"); + b.Navigation("OidcConnections"); + b.Navigation("PermissionSet"); b.Navigation("TestMerges"); diff --git a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs index 4bdcf9a066..6a94adf80b 100644 --- a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs @@ -57,6 +57,7 @@ namespace Tgstation.Server.Host.Database serverVersion, mySqlOptions => { + mySqlOptions.TranslateParameterizedCollectionsToConstants(); mySqlOptions.EnableRetryOnFailure(); mySqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery); }); diff --git a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs index d9157ab41a..0633fd66cd 100644 --- a/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/PostgresSqlDatabaseContext.cs @@ -41,6 +41,7 @@ namespace Tgstation.Server.Host.Database AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); options.UseNpgsql(databaseConfiguration.ConnectionString, options => { + options.TranslateParameterizedCollectionsToConstants(); options.EnableRetryOnFailure(); options.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery); diff --git a/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs index 3997e0a46b..432a77863b 100644 --- a/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/SqlServerDatabaseContext.cs @@ -37,6 +37,7 @@ namespace Tgstation.Server.Host.Database databaseConfiguration.ConnectionString, sqlServerOptions => { + sqlServerOptions.TranslateParameterizedCollectionsToConstants(); sqlServerOptions.EnableRetryOnFailure(); sqlServerOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery); }); diff --git a/src/Tgstation.Server.Host/Database/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Database/SqliteDatabaseContext.cs index 98a58190d9..090be3e341 100644 --- a/src/Tgstation.Server.Host/Database/SqliteDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/SqliteDatabaseContext.cs @@ -56,7 +56,11 @@ namespace Tgstation.Server.Host.Database if (databaseConfiguration.DatabaseType != DatabaseType.Sqlite) throw new InvalidOperationException($"Invalid DatabaseType for {nameof(SqliteDatabaseContext)}!"); - options.UseSqlite(databaseConfiguration.ConnectionString, sqliteOptions => sqliteOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)); + options.UseSqlite(databaseConfiguration.ConnectionString, sqliteOptions => + { + sqliteOptions.TranslateParameterizedCollectionsToConstants(); + sqliteOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery); + }); } /// @@ -72,19 +76,19 @@ namespace Tgstation.Server.Host.Database // use the DateTimeOffsetToBinaryConverter // Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754 // This only supports millisecond precision, but should be sufficient for most use cases. - if (!designTime) - foreach (var entityType in modelBuilder.Model.GetEntityTypes()) - { - var properties = entityType - .ClrType - .GetProperties() - .Where(p => p.PropertyType == typeof(DateTimeOffset) || p.PropertyType == typeof(DateTimeOffset?)); - foreach (var property in properties) - modelBuilder - .Entity(entityType.Name) - .Property(property.Name) - .HasConversion(new DateTimeOffsetToBinaryConverter()); - } + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + var properties = entityType + .ClrType + .GetProperties() + .Where(p => p.PropertyType == typeof(DateTimeOffset) || p.PropertyType == typeof(DateTimeOffset?)); + foreach (var property in properties) + modelBuilder + .Entity(entityType.Name) + .Property(property.Name) + .HasConversion(new DateTimeOffsetToBinaryConverter()) + .HasColumnType("TEXT"); // Dominion: This is for purely legacy reasons + } } } } diff --git a/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs b/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs index 73add394bd..3179c684f2 100644 --- a/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/WebHostBuilderExtensions.cs @@ -66,6 +66,7 @@ namespace Tgstation.Server.Host.Extensions applicationBuilder.ApplicationServices.GetRequiredService>(), applicationBuilder.ApplicationServices.GetRequiredService>(), applicationBuilder.ApplicationServices.GetRequiredService>(), + applicationBuilder.ApplicationServices.GetRequiredService>(), applicationBuilder.ApplicationServices.GetRequiredService>(), applicationBuilder.ApplicationServices.GetRequiredService>(), applicationBuilder.ApplicationServices.GetRequiredService>()); diff --git a/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs b/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs index 77fc7e3445..1653ef0165 100644 --- a/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs +++ b/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs @@ -33,6 +33,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// The password of the . /// If the is . /// The s for the user. + /// The s for the user. /// The owned of the user. /// The for the . /// The for the operation. @@ -44,6 +45,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations string password, bool? enabled, IEnumerable? oAuthConnections, + IEnumerable? oidcConnections, PermissionSetInput? permissionSet, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) @@ -73,6 +75,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations Provider = oAuthConnection.Provider, }) .ToList(), + OidcConnections = oidcConnections + ?.Select(oidcConnection => new Api.Models.OidcConnection + { + ExternalUserId = oidcConnection.ExternalUserId, + SchemeKey = oidcConnection.SchemeKey, + }) + .ToList(), }, false, cancellationToken)); @@ -85,6 +94,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// The password of the . /// If the is . /// The s for the user. + /// The s for the user. /// The of the the will belong to. /// The for the . /// The for the operation. @@ -96,6 +106,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations string password, bool? enabled, IEnumerable? oAuthConnections, + IEnumerable? oidcConnections, [ID(nameof(UserGroup))] long groupId, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) @@ -122,16 +133,24 @@ namespace Tgstation.Server.Host.GraphQL.Mutations Provider = oAuthConnection.Provider, }) .ToList(), + OidcConnections = oidcConnections + ?.Select(oidcConnection => new Api.Models.OidcConnection + { + ExternalUserId = oidcConnection.ExternalUserId, + SchemeKey = oidcConnection.SchemeKey, + }) + .ToList(), }, false, cancellationToken)); } /// - /// Creates a TGS user authenticated with one or mor s specifying a personal . + /// Creates a TGS user authenticated with one or more s or s specifying a personal . /// /// The of the . /// The s for the user. + /// The s for the user. /// If the is . /// The owned of the user. /// The for the . @@ -139,9 +158,10 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// The created . [TgsGraphQLAuthorize(nameof(IUserAuthority.Create))] [Error(typeof(ErrorMessageException))] - public ValueTask CreateUserByOAuthAndPermissionSet( + public ValueTask CreateUserByServiceConnectionAndPermissionSet( string name, - IEnumerable oAuthConnections, + IEnumerable? oAuthConnections, + IEnumerable? oidcConnections, bool? enabled, PermissionSetInput? permissionSet, [Service] IGraphQLAuthorityInvoker userAuthority, @@ -172,16 +192,24 @@ namespace Tgstation.Server.Host.GraphQL.Mutations Provider = oAuthConnection.Provider, }) .ToList(), + OidcConnections = oidcConnections + ?.Select(oidcConnection => new Api.Models.OidcConnection + { + ExternalUserId = oidcConnection.ExternalUserId, + SchemeKey = oidcConnection.SchemeKey, + }) + .ToList(), }, true, cancellationToken)); } /// - /// Creates a TGS user specifying the they will belong to. + /// Creates a TGS user using s and/or s specifying the they will belong to. /// /// The of the . /// The s for the user. + /// The s for the user. /// The of the the will belong to. /// If the is . /// The for the . @@ -189,9 +217,10 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// The created . [TgsGraphQLAuthorize(nameof(IUserAuthority.Create))] [Error(typeof(ErrorMessageException))] - public ValueTask CreateUserByOAuthAndGroup( + public ValueTask CreateUserByServiceConnectionAndGroup( string name, IEnumerable oAuthConnections, + IEnumerable oidcConnections, [ID(nameof(UserGroup))] long groupId, bool? enabled, [Service] IGraphQLAuthorityInvoker userAuthority, @@ -219,6 +248,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations Provider = oAuthConnection.Provider, }) .ToList(), + OidcConnections = oidcConnections + ?.Select(oidcConnection => new Api.Models.OidcConnection + { + ExternalUserId = oidcConnection.ExternalUserId, + SchemeKey = oidcConnection.SchemeKey, + }) + .ToList(), }, true, cancellationToken)); @@ -230,6 +266,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// The of the . /// If the is . /// The s for the user. + /// The s for the user. /// The owned of the user. /// The for the . /// The for the operation. @@ -240,6 +277,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations string systemIdentifier, bool? enabled, IEnumerable? oAuthConnections, + IEnumerable? oidcConnections, PermissionSetInput permissionSet, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) @@ -267,6 +305,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations Provider = oAuthConnection.Provider, }) .ToList(), + OidcConnections = oidcConnections + ?.Select(oidcConnection => new Api.Models.OidcConnection + { + ExternalUserId = oidcConnection.ExternalUserId, + SchemeKey = oidcConnection.SchemeKey, + }) + .ToList(), }, false, cancellationToken)); @@ -279,6 +324,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// If the is . /// The of the the will belong to. /// The s for the user. + /// The s for the user. /// The for the . /// The for the operation. /// The created . @@ -289,6 +335,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations bool? enabled, [ID(nameof(UserGroup))] long groupId, IEnumerable? oAuthConnections, + IEnumerable? oidcConnections, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) { @@ -312,6 +359,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations Provider = oAuthConnection.Provider, }) .ToList(), + OidcConnections = oidcConnections + ?.Select(oidcConnection => new Api.Models.OidcConnection + { + ExternalUserId = oidcConnection.ExternalUserId, + SchemeKey = oidcConnection.SchemeKey, + }) + .ToList(), }, false, cancellationToken)); @@ -346,17 +400,19 @@ namespace Tgstation.Server.Host.GraphQL.Mutations } /// - /// Sets the current user's s. + /// Sets the current user's s and s. /// - /// The new s for the current user. + /// Optional new s for the current . + /// Optional new s for the current . /// The to get the of the user. /// The for the . /// The for the operation. /// The updated current . - [TgsGraphQLAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnOAuthConnections)] + [TgsGraphQLAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnServiceConnections)] [Error(typeof(ErrorMessageException))] - public ValueTask SetCurrentOAuthConnections( - IEnumerable newOAuthConnections, + public ValueTask SetCurrentServiceConnections( + IEnumerable? newOAuthConnections, + IEnumerable? newOidcConnections, [Service] IAuthenticationContext authenticationContext, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) @@ -375,6 +431,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations Provider = oAuthConnection.Provider, }) .ToList(), + OidcConnections = newOidcConnections + ?.Select(oidcConnection => new Api.Models.OidcConnection + { + ExternalUserId = oidcConnection.ExternalUserId, + SchemeKey = oidcConnection.SchemeKey, + }) + .ToList(), }, cancellationToken)); } @@ -387,6 +450,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// Optional new password for the . Only applicable to TGS users. /// Optional new status for the . /// Optional new s for the . + /// Optional new s for the . /// The for the . /// The for the operation. /// The updated . @@ -398,6 +462,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations string? newPassword, bool? enabled, IEnumerable? newOAuthConnections, + IEnumerable? newOidcConnections, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) { @@ -410,6 +475,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations null, null, newOAuthConnections, + newOidcConnections, userAuthority, cancellationToken); } @@ -422,7 +488,8 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// Optional new password for the . Only applicable to TGS users. /// Optional new status for the . /// Updated owned for the user. Note that setting this on a in a will remove them from that group. - /// The new s for the . + /// Optional new s for the . + /// Optional new s for the . /// The for the . /// The for the operation. /// The updated . @@ -435,6 +502,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations bool? enabled, PermissionSetInput newPermissionSet, IEnumerable? newOAuthConnections, + IEnumerable? newOidcConnections, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) { @@ -447,6 +515,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations newPermissionSet, null, newOAuthConnections, + newOidcConnections, userAuthority, cancellationToken); } @@ -459,7 +528,8 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// Optional new password for the . Only applicable to TGS users. /// Optional new status for the . /// of the to move the to. - /// The new s for the . + /// Optional new s for the . + /// Optional new s for the . /// The for the . /// The for the operation. /// The updated . @@ -472,6 +542,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations bool? enabled, [ID(nameof(UserGroup))] long newGroupId, IEnumerable? newOAuthConnections, + IEnumerable? newOidcConnections, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) { @@ -484,6 +555,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations null, newGroupId, newOAuthConnections, + newOidcConnections, userAuthority, cancellationToken); } @@ -498,6 +570,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// Optional updated new owned for the user. Note that setting this on a in a will remove them from that group. /// Optional of the to move the to. /// Optional new s for the . + /// Optional new s for the . /// The for the . /// The for the operation. /// The updated . @@ -509,6 +582,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations PermissionSetInput? newPermissionSet, long? newGroupId, IEnumerable? newOAuthConnections, + IEnumerable? newOidcConnections, IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) => userAuthority.InvokeTransformable( @@ -539,6 +613,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations Provider = oAuthConnection.Provider, }) .ToList(), + OidcConnections = newOidcConnections + ?.Select(oidcConnection => new Api.Models.OidcConnection + { + ExternalUserId = oidcConnection.ExternalUserId, + SchemeKey = oidcConnection.SchemeKey, + }) + .ToList(), }, cancellationToken)); } diff --git a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthConnection.cs b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthConnection.cs index b073446c66..64374b2bb6 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthConnection.cs +++ b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthConnection.cs @@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.GraphQL.Types.OAuth { /// /// The of the . - /// ] + /// public OAuthProvider Provider { get; } /// diff --git a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthProviderInfos.cs b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthProviderInfos.cs index da9bffe4b3..65506d9968 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthProviderInfos.cs +++ b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthProviderInfos.cs @@ -20,21 +20,6 @@ namespace Tgstation.Server.Host.GraphQL.Types.OAuth /// public RedirectOAuthProviderInfo? GitHub { get; } - /// - /// https://tgstation13.org. - /// - public RedirectOAuthProviderInfo? TGForums { get; } - - /// - /// https://www.keycloak.org. - /// - public FullOAuthProviderInfo? Keycloak { get; } - - /// - /// https://invisioncommunity.com. - /// - public FullOAuthProviderInfo? InvisionCommunity { get; } - /// /// Initializes a new instance of the class. /// @@ -58,9 +43,6 @@ namespace Tgstation.Server.Host.GraphQL.Types.OAuth 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)); } } } diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Oidc/OidcConnection.cs b/src/Tgstation.Server.Host/GraphQL/Types/Oidc/OidcConnection.cs new file mode 100644 index 0000000000..cf2157ed8f --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Types/Oidc/OidcConnection.cs @@ -0,0 +1,31 @@ +using System; + +namespace Tgstation.Server.Host.GraphQL.Types.OAuth +{ + /// + /// Represents a valid OAuth connection. + /// + public sealed class OidcConnection + { + /// + /// The scheme key of the . + /// ] + public string SchemeKey { get; } + + /// + /// The ID of the user in the OIDC provider ("sub" claim). + /// + public string ExternalUserId { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + public OidcConnection(string externalUserId, string schemeKey) + { + ExternalUserId = externalUserId ?? throw new ArgumentNullException(nameof(externalUserId)); + SchemeKey = schemeKey ?? throw new ArgumentNullException(nameof(schemeKey)); + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Types/User.cs b/src/Tgstation.Server.Host/GraphQL/Types/User.cs index 721fbe9eef..7de3e7ca2a 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/User.cs +++ b/src/Tgstation.Server.Host/GraphQL/Types/User.cs @@ -105,6 +105,21 @@ namespace Tgstation.Server.Host.GraphQL.Types authority => authority.OAuthConnections(Id, cancellationToken)); } + /// + /// List of s associated with the user if OIDC is configured. + /// + /// The for the . + /// The for the operation. + /// A resulting in a new of s for the if OAuth is configured. + public ValueTask OidcConnections( + [Service] IGraphQLAuthorityInvoker userAuthority, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(userAuthority); + return userAuthority.Invoke( + authority => authority.OidcConnections(Id, cancellationToken)); + } + /// /// The associated with the . /// diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Users.cs b/src/Tgstation.Server.Host/GraphQL/Types/Users.cs index 04ac751b25..8ec3667fe3 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/Users.cs +++ b/src/Tgstation.Server.Host/GraphQL/Types/Users.cs @@ -8,7 +8,10 @@ using HotChocolate.Data; using HotChocolate.Types; using HotChocolate.Types.Relay; +using Microsoft.Extensions.Options; + using Tgstation.Server.Host.Authority; +using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Models.Transformers; using Tgstation.Server.Host.Security; @@ -27,6 +30,18 @@ namespace Tgstation.Server.Host.GraphQL.Types /// A new . public UserGroups Groups() => new(); + /// + /// If only OIDC logins and registration is allowed. + /// + /// The containing the . + /// if OIDC strict mode is enabled, otherwise. + public bool OidcStrictMode( + [Service] IOptions securityConfigurationOptions) + { + ArgumentNullException.ThrowIfNull(securityConfigurationOptions); + return securityConfigurationOptions.Value.OidcStrictMode; + } + /// /// Gets the current . /// diff --git a/src/Tgstation.Server.Host/Models/OidcConnection.cs b/src/Tgstation.Server.Host/Models/OidcConnection.cs new file mode 100644 index 0000000000..d3b2cb1b6d --- /dev/null +++ b/src/Tgstation.Server.Host/Models/OidcConnection.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + /// + public sealed class OidcConnection : Api.Models.OidcConnection, ILegacyApiTransformable + { + /// + /// The row Id. + /// + public long Id { get; set; } + + /// + /// The of . + /// + public long UserId { get; set; } + + /// + /// The owning . + /// + [Required] + public User? User { get; set; } + + /// + public Api.Models.OidcConnection ToApi() => new() + { + SchemeKey = SchemeKey, + ExternalUserId = ExternalUserId, + }; + } +} diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index c573368deb..e8bc06f6ba 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -73,10 +73,15 @@ namespace Tgstation.Server.Host.Models public ICollection? TestMerges { get; set; } /// - /// The s made by the . + /// The s for the . /// public ICollection? OAuthConnections { get; set; } + /// + /// The s for the . + /// + public ICollection? OidcConnections { get; set; } + /// /// Change a into a . /// @@ -104,6 +109,9 @@ namespace Tgstation.Server.Host.Models result.OAuthConnections = OAuthConnections ?.Select(x => x.ToApi()) .ToList(); + result.OidcConnections = OidcConnections + ?.Select(x => x.ToApi()) + .ToList(); result.Group = Group?.ToApi(false); result.PermissionSet = PermissionSet?.ToApi(); return result; diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index ba47b47a44..855c325c5b 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -1,11 +1,13 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -13,8 +15,10 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using Tgstation.Server.Api; +using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Utils; @@ -23,6 +27,16 @@ namespace Tgstation.Server.Host.Security /// sealed class AuthenticationContextFactory : ITokenValidator, IDisposable { + /// + /// Internal scheme prefix for OIDC schemes. + /// + public const string OpenIDConnectAuthenticationSchemePrefix = $"{OpenIdConnectDefaults.AuthenticationScheme}."; + + /// + /// Claim name used to set user groups in OIDC strict mode. + /// + public const string TgsGroupIdClaimName = "tgstation-server-group-id"; + /// /// The the created. /// @@ -48,6 +62,11 @@ namespace Tgstation.Server.Host.Security /// readonly SwarmConfiguration swarmConfiguration; + /// + /// The for the . + /// + readonly SecurityConfiguration securityConfiguration; + /// /// Backing field for . /// @@ -63,6 +82,30 @@ namespace Tgstation.Server.Host.Security /// int initialized; + /// + /// Parse a out of a in a given . + /// + /// The containing claims. + /// The name to parse from. + /// The parsed . + static DateTimeOffset ParseTime(ClaimsPrincipal principal, string key) + { + var claim = principal.FindFirst(key); + if (claim == default) + throw new InvalidOperationException($"Missing '{key}' claim!"); + + try + { + return new DateTimeOffset( + EpochTime.DateTime( + Int64.Parse(claim.Value, CultureInfo.InvariantCulture))); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to parse '{key}'!", ex); + } + } + /// /// Initializes a new instance of the class. /// @@ -70,12 +113,14 @@ namespace Tgstation.Server.Host.Security /// The value of . /// The containing the value of . /// The containing the value of . + /// The containing the value of . /// The value of . public AuthenticationContextFactory( IDatabaseContext databaseContext, IIdentityCache identityCache, IApiHeadersProvider apiHeadersProvider, IOptions swarmConfigurationOptions, + IOptions securityConfigurationOptions, ILogger logger) { this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); @@ -84,6 +129,8 @@ namespace Tgstation.Server.Host.Security apiHeaders = apiHeadersProvider.ApiHeaders; swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions)); + securityConfiguration = securityConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(securityConfigurationOptions)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); currentAuthenticationContext = new AuthenticationContext(); @@ -94,7 +141,7 @@ namespace Tgstation.Server.Host.Security /// #pragma warning disable CA1506 // TODO: Decomplexify - public async Task ValidateToken(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken) + public async Task ValidateTgsToken(Microsoft.AspNetCore.Authentication.JwtBearer.TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken) #pragma warning restore CA1506 { ArgumentNullException.ThrowIfNull(tokenValidatedContext); @@ -121,26 +168,8 @@ namespace Tgstation.Server.Host.Security throw new InvalidOperationException("Failed to parse user ID!", e); } - DateTimeOffset ParseTime(string key) - { - var claim = principal.FindFirst(key); - if (claim == default) - throw new InvalidOperationException($"Missing '{key}' claim!"); - - try - { - return new DateTimeOffset( - EpochTime.DateTime( - Int64.Parse(claim.Value, CultureInfo.InvariantCulture))); - } - catch (Exception ex) - { - throw new InvalidOperationException($"Failed to parse '{key}'!", ex); - } - } - - var notBefore = ParseTime(JwtRegisteredClaimNames.Nbf); - var expires = ParseTime(JwtRegisteredClaimNames.Exp); + var notBefore = ParseTime(principal, JwtRegisteredClaimNames.Nbf); + var expires = ParseTime(principal, JwtRegisteredClaimNames.Exp); var user = await databaseContext .Users @@ -202,5 +231,158 @@ namespace Tgstation.Server.Host.Security throw; } } + + /// + public async Task ValidateOidcToken(RemoteAuthenticationContext tokenValidatedContext, string schemeKey, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(tokenValidatedContext); + + var principal = tokenValidatedContext.Principal; + if (principal == null) + throw new InvalidOperationException("Expected a valid principal here!"); + + var userIdClaim = principal.FindFirst(JwtRegisteredClaimNames.Sub); + if (userIdClaim == default) + throw new InvalidOperationException($"Missing '{JwtRegisteredClaimNames.Sub}' claim!"); + + var userId = userIdClaim.Value; + var scheme = tokenValidatedContext.Scheme.Name; + var deprefixedScheme = scheme.Substring(OpenIDConnectAuthenticationSchemePrefix.Length); + var connection = await databaseContext + .OidcConnections + .AsQueryable() + .Where(oidcConnection => oidcConnection.ExternalUserId == userId && oidcConnection.SchemeKey == deprefixedScheme) + .Include(oidcConnection => oidcConnection.User) + .ThenInclude(user => user!.Group) + .ThenInclude(group => group!.PermissionSet) + .FirstOrDefaultAsync(cancellationToken); + + User user; + if (!securityConfiguration.OidcStrictMode) + { + if (connection == default) + { + tokenValidatedContext.Fail($"Unable to find user with OidcConnection for {deprefixedScheme}/{userId}!"); + return; + } + + user = connection.User!; + } + else + { + var groupClaim = principal.FindFirst(TgsGroupIdClaimName); + long? groupId; + if (groupClaim == default) + groupId = null; + else if (Int64.TryParse(groupClaim.Value, out long groupIdParsed)) + groupId = groupIdParsed; + else + { + tokenValidatedContext.Fail($"User has non-numeric '{TgsGroupIdClaimName}' claim!"); + return; + } + + UserGroup? group = groupId.HasValue + ? await databaseContext + .Groups + .AsQueryable() + .Where(group => group.Id == groupId.Value) + .Include(group => group.PermissionSet) + .FirstOrDefaultAsync(cancellationToken) + : null; + + var missingClaimError = $"User missing '{TgsGroupIdClaimName}' claim!"; + if (connection == default) + { + var username = principal.Identity?.Name; + if (username == null) + { + tokenValidatedContext.Fail("Failed to retrieve user's name from retrieved claims!"); + return; + } + + if (username.Contains(':', StringComparison.Ordinal)) + { + tokenValidatedContext.Fail("Cannot create users with the ':' in their name!"); + return; + } + + if (group == null) + { + tokenValidatedContext.Fail( + groupId.HasValue + ? $"'{TgsGroupIdClaimName}' does not point to a valid group!" + : missingClaimError); + return; + } + + var tgsUser = await databaseContext + .Users + .GetTgsUser( + dbUser => new User + { + Id = dbUser.Id!.Value, + }, + cancellationToken); + + user = new User + { + CreatedAt = DateTimeOffset.UtcNow, + CanonicalName = User.CanonicalizeName(username), + Name = username, + CreatedById = tgsUser.Id, + Enabled = true, + GroupId = group.Id, + OidcConnections = new List + { + new OidcConnection + { + SchemeKey = schemeKey, + ExternalUserId = userId, + }, + }, + PasswordHash = "_", // This can't be hashed + }; + + databaseContext.Users.Add(user); + } + else + { + user = connection.User!; + + // group update + if (group == null) + { + user.PermissionSet = new PermissionSet + { + AdministrationRights = AdministrationRights.None, + InstanceManagerRights = InstanceManagerRights.None, + }; + user.GroupId = null; + user.Enabled = false; + + tokenValidatedContext.Fail(missingClaimError); + return; + } + + user.Group = group; + if (user.PermissionSet != null) + databaseContext.PermissionSets.Remove(user.PermissionSet); + + user.Enabled = true; + } + + await databaseContext.Save(cancellationToken); + } + + var expires = ParseTime(principal, JwtRegisteredClaimNames.Exp); + + currentAuthenticationContext.Initialize( + user, + expires, + Guid.NewGuid().ToString(), + null, + null); + } } } diff --git a/src/Tgstation.Server.Host/Security/ITokenFactory.cs b/src/Tgstation.Server.Host/Security/ITokenFactory.cs index 777122a2ed..bfa7bb360f 100644 --- a/src/Tgstation.Server.Host/Security/ITokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/ITokenFactory.cs @@ -25,8 +25,8 @@ namespace Tgstation.Server.Host.Security /// Create a for a given . /// /// The to create the token for. Must have the field available. - /// Whether or not this is an OAuth login. + /// Whether or not this is an external service login. /// A new token . - string CreateToken(Models.User user, bool oAuth); + string CreateToken(Models.User user, bool serviceLogin); } } diff --git a/src/Tgstation.Server.Host/Security/ITokenValidator.cs b/src/Tgstation.Server.Host/Security/ITokenValidator.cs index c4fc4f2e4f..e7d17f14aa 100644 --- a/src/Tgstation.Server.Host/Security/ITokenValidator.cs +++ b/src/Tgstation.Server.Host/Security/ITokenValidator.cs @@ -1,21 +1,31 @@ using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; namespace Tgstation.Server.Host.Security { /// - /// Handles s. + /// Handles validating authentication tokens. /// public interface ITokenValidator { /// - /// Handles . + /// Handles TGS s. /// - /// The . + /// The . /// The for the operation. /// A representing the running operation. - Task ValidateToken(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken); + Task ValidateTgsToken(Microsoft.AspNetCore.Authentication.JwtBearer.TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken); + + /// + /// Handles OIDC s. + /// + /// The for . + /// The scheme key being used to login. + /// The for the operation. + /// A representing the running operation. + Task ValidateOidcToken(RemoteAuthenticationContext tokenValidatedContext, string schemeKey, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs index 0e912bd368..20206e86d8 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/InvisionCommunityOAuthValidator.cs @@ -14,7 +14,9 @@ namespace Tgstation.Server.Host.Security.OAuth sealed class InvisionCommunityOAuthValidator : GenericOAuthValidator { /// +#pragma warning disable CS0618 // Type or member is obsolete public override OAuthProvider Provider => OAuthProvider.InvisionCommunity; +#pragma warning restore CS0618 // Type or member is obsolete /// protected override Uri TokenUrl => new($"{OAuthConfiguration.ServerUrl}/oauth/token/"); // This needs the trailing slash or it doesnt get the token. Do not remove. diff --git a/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs index f4e61b4b5c..795812b40d 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/KeycloakOAuthValidator.cs @@ -14,7 +14,9 @@ namespace Tgstation.Server.Host.Security.OAuth sealed class KeycloakOAuthValidator : GenericOAuthValidator { /// +#pragma warning disable CS0618 // Type or member is obsolete public override OAuthProvider Provider => OAuthProvider.Keycloak; +#pragma warning restore CS0618 // Type or member is obsolete /// protected override Uri TokenUrl => new($"{BaseProtocolPath}/token"); diff --git a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs index 5b5af32ba9..1f67b1999f 100644 --- a/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs +++ b/src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs @@ -57,21 +57,18 @@ namespace Tgstation.Server.Host.Security.OAuth loggerFactory.CreateLogger(), discordConfig)); - if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.TGForums, out var tgConfig)) - validatorsBuilder.Add( - new TGForumsOAuthValidator( - httpClientFactory, - loggerFactory.CreateLogger(), - tgConfig)); - +#pragma warning disable CS0618 // Type or member is obsolete if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.Keycloak, out var keyCloakConfig)) +#pragma warning restore CS0618 // Type or member is obsolete validatorsBuilder.Add( new KeycloakOAuthValidator( httpClientFactory, loggerFactory.CreateLogger(), keyCloakConfig)); +#pragma warning disable CS0618 // Type or member is obsolete if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.InvisionCommunity, out var invisionConfig)) +#pragma warning restore CS0618 // Type or member is obsolete validatorsBuilder.Add( new InvisionCommunityOAuthValidator( httpClientFactory, diff --git a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs b/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs deleted file mode 100644 index c81a54ec73..0000000000 --- a/src/Tgstation.Server.Host/Security/OAuth/TGForumsOAuthValidator.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; - -using Microsoft.Extensions.Logging; - -using Tgstation.Server.Api.Models; -using Tgstation.Server.Common.Http; -using Tgstation.Server.Host.Configuration; - -namespace Tgstation.Server.Host.Security.OAuth -{ - /// - /// for /tg/ forums. - /// - sealed class TGForumsOAuthValidator : GenericOAuthValidator - { - /// - public override OAuthProvider Provider => OAuthProvider.TGForums; - - /// - protected override Uri TokenUrl => new("https://tgstation13.org/phpBB/app.php/tgapi/oauth/token"); - - /// - protected override Uri UserInformationUrl => new("https://tgstation13.org/phpBB/app.php/tgapi/user/me"); - - /// - /// Initializes a new instance of the class. - /// - /// The for the . - /// The for the . - /// The for the . - public TGForumsOAuthValidator( - IAbstractHttpClientFactory httpClientFactory, - ILogger logger, - OAuthConfiguration oAuthConfiguration) - : base( - httpClientFactory, - logger, - oAuthConfiguration) - { - } - - /// - protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token; - - /// - protected override string DecodeUserInformationPayload(dynamic responseJson) => responseJson.phpbb_username; - - /// - protected override OAuthTokenRequest CreateTokenRequest(string code) => new(OAuthConfiguration, code, "user"); - } -} diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 9269472c87..6081aeb42f 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -101,7 +101,7 @@ namespace Tgstation.Server.Host.Security } /// - public string CreateToken(User user, bool oAuth) + public string CreateToken(User user, bool serviceLogin) { ArgumentNullException.ThrowIfNull(user); @@ -121,7 +121,7 @@ namespace Tgstation.Server.Host.Security else notBefore = now; - var expiry = now.AddMinutes(oAuth + var expiry = now.AddMinutes(serviceLogin ? securityConfiguration.OAuthTokenExpiryMinutes : securityConfiguration.TokenExpiryMinutes); diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index c3c1291b69..96c7dbcee1 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -97,8 +97,6 @@ - - @@ -111,24 +109,28 @@ + + - + - + - + - + runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + + + @@ -136,9 +138,9 @@ - + - + @@ -154,20 +156,21 @@ - + - + - + - + - + + diff --git a/src/Tgstation.Server.Host/Utils/GitHub/GitHubService.cs b/src/Tgstation.Server.Host/Utils/GitHub/GitHubService.cs index 84e793ff12..7a47fe2a9f 100644 --- a/src/Tgstation.Server.Host/Utils/GitHub/GitHubService.cs +++ b/src/Tgstation.Server.Host/Utils/GitHub/GitHubService.cs @@ -163,6 +163,73 @@ namespace Tgstation.Server.Host.Utils.GitHub .WaitAsync(cancellationToken); } + /// + public Task AppendCommentOnIssue(string repoOwner, string repoName, string comment, IssueComment issueComment, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(repoOwner); + + ArgumentNullException.ThrowIfNull(repoName); + + ArgumentNullException.ThrowIfNull(comment); + + ArgumentNullException.ThrowIfNull(issueComment); + + logger.LogTrace("AppendCommentOnIssue"); + + return gitHubClient + .Issue + .Comment + .Update( + repoOwner, + repoName, + issueComment.Id, + issueComment.Body + comment) + .WaitAsync(cancellationToken); + } + + /// + public async ValueTask GetExistingCommentOnIssue(string repoOwner, string repoName, string header, int issueNumber, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(repoOwner); + + ArgumentNullException.ThrowIfNull(repoName); + + ArgumentNullException.ThrowIfNull(header); + + logger.LogTrace("GetExistingCommentOnIssue"); + + var comments = await gitHubClient + .Issue + .Comment + .GetAllForIssue( + repoOwner, + repoName, + issueNumber) + .WaitAsync(cancellationToken); + if (comments == null) + { + return null; + } + + long userId = await GetCurrentUserId(cancellationToken); + + for (int i = comments.Count - 1; i > -1; i--) + { + var currentComment = comments[i]; + if (currentComment.User?.Id == userId && (currentComment.Body?.StartsWith(header) ?? false)) + { + if (currentComment.Body.Length > 250000) + { // Limit should be 262,143 so we'll leave a 12,143 buffer + return null; + } + + return currentComment; + } + } + + return null; + } + /// public async ValueTask GetRepositoryId(string repoOwner, string repoName, CancellationToken cancellationToken) { diff --git a/src/Tgstation.Server.Host/Utils/GitHub/IAuthenticatedGitHubService.cs b/src/Tgstation.Server.Host/Utils/GitHub/IAuthenticatedGitHubService.cs index e2cba94a2c..b802e4731b 100644 --- a/src/Tgstation.Server.Host/Utils/GitHub/IAuthenticatedGitHubService.cs +++ b/src/Tgstation.Server.Host/Utils/GitHub/IAuthenticatedGitHubService.cs @@ -21,6 +21,28 @@ namespace Tgstation.Server.Host.Utils.GitHub /// A representing the running operation. Task CommentOnIssue(string repoOwner, string repoName, string comment, int issueNumber, CancellationToken cancellationToken); + /// + /// Append a comment on an existing . + /// + /// The owner of the target repository. + /// The name of the target repository. + /// The text of the comment. + /// The to amend. + /// The for the operation. + /// A representing the running operation. + Task AppendCommentOnIssue(string repoOwner, string repoName, string comment, IssueComment issueComment, CancellationToken cancellationToken); + + /// + /// Gets an for a particular with the provided if it exists and is not too large. + /// + /// The owner of the target repository. + /// The name of the target repository. + /// The starting text of the comment to search for. + /// The number of the issue to comment on. + /// The for the operation. + /// A of an existing comment if its currently under 250k characters, otherwise the will be null. + ValueTask GetExistingCommentOnIssue(string repoOwner, string repoName, string header, int issueNumber, CancellationToken cancellationToken); + /// /// Create a on a target repostiory. /// diff --git a/src/Tgstation.Server.Shared/Tgstation.Server.Shared.csproj b/src/Tgstation.Server.Shared/Tgstation.Server.Shared.csproj index 29e3b26c77..be0deff57a 100644 --- a/src/Tgstation.Server.Shared/Tgstation.Server.Shared.csproj +++ b/src/Tgstation.Server.Shared/Tgstation.Server.Shared.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj index 886899a85b..c36c1a6dcf 100644 --- a/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj +++ b/tests/Tgstation.Server.Client.Tests/Tgstation.Server.Client.Tests.csproj @@ -6,7 +6,7 @@
- + diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index ba5469714b..b6edec53b0 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -10,7 +10,7 @@ using Moq; using System; using System.Threading; -using Tgstation.Server.Host.Components; +using Tgstation.Server.Api; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Security; using Tgstation.Server.Host.System; @@ -40,43 +40,47 @@ namespace Tgstation.Server.Host.Core.Tests Assert.ThrowsException(() => app.ConfigureServices(mockServiceCollection, null, null)); - Assert.ThrowsException(() => app.Configure(null, null, null, null, null, null, null, null, null, null, null)); + Assert.ThrowsException(() => app.Configure(null, null, null, null, null, null, null, null, null, null, null, null)); var mockAppBuilder = new Mock(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, null, null, null, null, null, null, null, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, null, null, null, null, null, null, null, null, null, null, null)); var mockServerControl = new Mock(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null, null, null, null, null, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null, null, null, null, null, null, null, null, null)); var mockTokenFactory = new Mock(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null, null, null, null, null, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null, null, null, null, null, null, null, null, null)); var mockServerPortProvider = new Mock(); mockServerPortProvider.SetupGet(x => x.HttpApiPort).Returns(5345); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, null, null, null, null, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, null, null, null, null, null, null, null, null)); var mockAssemblyInformationProvider = Mock.Of(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, null, null, null, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, null, null, null, null, null, null, null)); var mockControlPanelOptions = new Mock>(); mockControlPanelOptions.SetupGet(x => x.Value).Returns(new ControlPanelConfiguration()).Verifiable(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, null, null, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, null, null, null, null, null, null)); var mockGeneralOptions = new Mock>(); mockGeneralOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration()).Verifiable(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, null, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, null, null, null, null, null)); var mockDatabaseOptions = new Mock>(); mockDatabaseOptions.SetupGet(x => x.Value).Returns(new DatabaseConfiguration()).Verifiable(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, null, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, null, null, null, null)); + + var mockSecurityOptions = new Mock>(); + mockDatabaseOptions.SetupGet(x => x.Value).Returns(new DatabaseConfiguration()).Verifiable(); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSecurityOptions.Object, null, null, null)); var mockSwarmOptions = new Mock>(); mockSwarmOptions.SetupGet(x => x.Value).Returns(new SwarmConfiguration()).Verifiable(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSwarmOptions.Object, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSecurityOptions.Object, mockSwarmOptions.Object, null, null)); var mockInternalOptions = new Mock>(); mockInternalOptions.SetupGet(x => x.Value).Returns(new InternalConfiguration()).Verifiable(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSwarmOptions.Object, mockInternalOptions.Object, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSecurityOptions.Object, mockSwarmOptions.Object, mockInternalOptions.Object, null)); mockControlPanelOptions.VerifyAll(); mockInternalOptions.VerifyAll(); diff --git a/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs b/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs index 9c130af80e..9b3e925536 100644 --- a/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs +++ b/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs @@ -89,7 +89,7 @@ namespace Tgstation.Server.Host.Swarm.Tests public TokenValidationParameters ValidationParameters => throw new NotSupportedException(); - public string CreateToken(User user, bool oAuth) + public string CreateToken(User user, bool serviceLogin) { throw new NotSupportedException(); } diff --git a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj index 0849c3118b..976f644e52 100644 --- a/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj +++ b/tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs b/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs index 04fe1b542c..20328014d6 100644 --- a/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs +++ b/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs @@ -105,6 +105,7 @@ namespace Tgstation.Server.Tests.Live public override ValueTask>>> SendUpdateMessage( RevisionInformation revisionInformation, + RevisionInformation previousRevisionInformation, Api.Models.EngineVersion engineVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, diff --git a/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs b/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs index 2e26ff9bc7..492bc59b65 100644 --- a/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs +++ b/tests/Tgstation.Server.Tests/Live/RawRequestTests.cs @@ -19,7 +19,6 @@ using Microsoft.Extensions.Logging; using Tgstation.Server.Api; using Tgstation.Server.Api.Hubs; -using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Request; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Client; @@ -27,8 +26,6 @@ using Tgstation.Server.Client.Extensions; using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host; using Tgstation.Server.Client.GraphQL; -using System.Linq; -using StrawberryShake; namespace Tgstation.Server.Tests.Live { diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index d8042b4492..ceb1f4bc6d 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -294,7 +294,7 @@ namespace Tgstation.Server.Tests.Live } } - [ClassCleanup] + [ClassCleanup(ClassCleanupBehavior.EndOfClass)] public static void Cleanup() { CachingFileDownloader.Cleanup(); @@ -1751,7 +1751,7 @@ namespace Tgstation.Server.Tests.Live } catch (SocketException ex) { - Assert.AreEqual(ex.SocketErrorCode, SocketError.AddressAlreadyInUse); + Assert.AreEqual(SocketError.AddressAlreadyInUse, ex.SocketErrorCode); } finally { @@ -1897,7 +1897,7 @@ namespace Tgstation.Server.Tests.Live expectedCompileJobId = compileJob.Id.Value; dd = await wdt.TellWorldToReboot(true, cancellationToken); - Assert.AreEqual(dd.ActiveCompileJob.Job.Id, expectedCompileJobId); + Assert.AreEqual(expectedCompileJobId, dd.ActiveCompileJob.Job.Id); Assert.AreEqual(WatchdogStatus.Online, dd.Status.Value); expectedCompileJobId = dd.ActiveCompileJob.Id.Value; diff --git a/tests/Tgstation.Server.Tests/Live/TestingGitHubService.cs b/tests/Tgstation.Server.Tests/Live/TestingGitHubService.cs index b37749af9f..731e653a4c 100644 --- a/tests/Tgstation.Server.Tests/Live/TestingGitHubService.cs +++ b/tests/Tgstation.Server.Tests/Live/TestingGitHubService.cs @@ -91,6 +91,18 @@ namespace Tgstation.Server.Tests.Live return Task.CompletedTask; } + public Task AppendCommentOnIssue(string repoOwner, string repoName, string comment, IssueComment issueComment, CancellationToken cancellationToken) + { + logger.LogTrace("AppendCommentOnIssue"); + return Task.CompletedTask; + } + + public ValueTask GetExistingCommentOnIssue(string repoOwner, string repoName, string header, int issueNumber, CancellationToken cancellationToken) + { + logger.LogTrace("GetExistingCommentOnIssue"); + return ValueTask.FromResult(null); + } + public ValueTask CreateDeployment(NewDeployment newDeployment, string repoOwner, string repoName, CancellationToken cancellationToken) { logger.LogTrace("CreateDeployment"); diff --git a/tests/Tgstation.Server.Tests/Live/UsersTest.cs b/tests/Tgstation.Server.Tests/Live/UsersTest.cs index 8d918804fe..37653adc46 100644 --- a/tests/Tgstation.Server.Tests/Live/UsersTest.cs +++ b/tests/Tgstation.Server.Tests/Live/UsersTest.cs @@ -127,7 +127,7 @@ namespace Tgstation.Server.Tests.Live { Id = restUser.Id, OAuthConnections = sampleOAuthConnections - }, cancellationToken), Api.Models.ErrorCode.AdminUserCannotOAuth); + }, cancellationToken), Api.Models.ErrorCode.AdminUserCannotHaveServiceConnection); var testUser = await client.Users.Create( new UserCreateRequest @@ -288,7 +288,7 @@ namespace Tgstation.Server.Tests.Live sampleOAuthConnections, cancellationToken), data => data.UpdateUser, - Client.GraphQL.ErrorCode.AdminUserCannotOAuth, + Client.GraphQL.ErrorCode.AdminUserCannotHaveServiceConnection, cancellationToken); var testUserResult = await client.RunMutationEnsureNoErrors( @@ -334,7 +334,7 @@ namespace Tgstation.Server.Tests.Live Assert.IsFalse(group.PermissionSet.AdministrationRights.CanChangeVersion); Assert.IsFalse(group.PermissionSet.AdministrationRights.CanDownloadLogs); - Assert.IsFalse(group.PermissionSet.AdministrationRights.CanEditOwnOAuthConnections); + Assert.IsFalse(group.PermissionSet.AdministrationRights.CanEditOwnServiceConnections); Assert.IsFalse(group.PermissionSet.AdministrationRights.CanEditOwnPassword); Assert.IsFalse(group.PermissionSet.AdministrationRights.CanReadUsers); Assert.IsFalse(group.PermissionSet.AdministrationRights.CanRestartHost); @@ -363,7 +363,7 @@ namespace Tgstation.Server.Tests.Live Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanChangeVersion); Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanDownloadLogs); - Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanEditOwnOAuthConnections); + Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanEditOwnServiceConnections); Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanEditOwnPassword); Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanReadUsers); Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanRestartHost); @@ -416,7 +416,7 @@ namespace Tgstation.Server.Tests.Live Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanChangeVersion); Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanDownloadLogs); - Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanEditOwnOAuthConnections); + Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanEditOwnServiceConnections); Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanEditOwnPassword); Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanReadUsers); Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanRestartHost); @@ -448,7 +448,7 @@ namespace Tgstation.Server.Tests.Live } ], cancellationToken), - data => data.CreateUserByOAuthAndPermissionSet, + data => data.CreateUserByServiceConnectionAndPermissionSet, cancellationToken); var testUser2 = oAuthCreateResult.User; @@ -483,7 +483,7 @@ namespace Tgstation.Server.Tests.Live { CanChangeVersion = true, CanDownloadLogs = true, - CanEditOwnOAuthConnections = true, + CanEditOwnServiceConnections = true, CanEditOwnPassword = true, CanReadUsers = true, CanRestartHost = true, diff --git a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj index e4b0a8244b..02b4501cd5 100644 --- a/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj +++ b/tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/tgstation-server.sln b/tgstation-server.sln index 8815c70e78..165ed3cac8 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -275,6 +275,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nix", "nix", "{5130526C-A55 build\package\nix\tgstation-server.nix = build\package\nix\tgstation-server.nix EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Host.Utils.GitLab.GraphQL", "src\Tgstation.Server.Host.Utils.GitLab.GraphQL\Tgstation.Server.Host.Utils.GitLab.GraphQL.csproj", "{BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -557,6 +559,18 @@ Global {8BF95E2D-FD27-470C-82B7-C21AC01BFBD7}.ReleaseNoWindows|Any CPU.Build.0 = Release|Any CPU {8BF95E2D-FD27-470C-82B7-C21AC01BFBD7}.ReleaseNoWix|Any CPU.ActiveCfg = Release|Any CPU {8BF95E2D-FD27-470C-82B7-C21AC01BFBD7}.ReleaseNoWix|Any CPU.Build.0 = Release|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.DebugNoWindows|Any CPU.ActiveCfg = Debug|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.DebugNoWindows|Any CPU.Build.0 = Debug|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.DebugNoWix|Any CPU.ActiveCfg = Debug|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.DebugNoWix|Any CPU.Build.0 = Debug|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.Release|Any CPU.Build.0 = Release|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.ReleaseNoWindows|Any CPU.ActiveCfg = Release|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.ReleaseNoWindows|Any CPU.Build.0 = Release|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.ReleaseNoWix|Any CPU.ActiveCfg = Release|Any CPU + {BF02BCC5-735C-4FF1-8EEF-FF78EA42FC85}.ReleaseNoWix|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE