diff --git a/packages/server/modules/workspaces/errors/sso.ts b/packages/server/modules/workspaces/errors/sso.ts index 0e491f75ea..3f9efb297d 100644 --- a/packages/server/modules/workspaces/errors/sso.ts +++ b/packages/server/modules/workspaces/errors/sso.ts @@ -1,5 +1,12 @@ import { BaseError } from '@/modules/shared/errors/base' +export class SsoSessionMissingOrExpiredError extends BaseError { + static defaultMessage = + 'No valid SSO session found for the given workspace. Please sign in.' + static code = 'SSO_SESSION_MISSING_OR_EXPIRED_ERROR' + static statusCode = 401 +} + export class SsoVerificationCodeMissingError extends BaseError { static defaultMessage = 'Cannot find verification token. Restart authentication flow.' static code = 'SSO_VERIFICATION_CODE_MISSING_ERROR' diff --git a/packages/server/modules/workspaces/events/eventListener.ts b/packages/server/modules/workspaces/events/eventListener.ts index a6c1deface..5c123afe98 100644 --- a/packages/server/modules/workspaces/events/eventListener.ts +++ b/packages/server/modules/workspaces/events/eventListener.ts @@ -10,6 +10,8 @@ import { upsertProjectRoleFactory } from '@/modules/core/repositories/streams' import { + GetWorkspace, + GetWorkspaceRoleForUser, GetWorkspaceRoles, GetWorkspaceRoleToDefaultProjectRoleMapping, QueryAllWorkspaceProjects @@ -35,6 +37,7 @@ import { WorkspaceEvents } from '@/modules/workspacesCore/domain/events' import { Knex } from 'knex' import { getWorkspaceFactory, + getWorkspaceRoleForUserFactory, getWorkspaceRolesFactory, getWorkspaceWithDomainsFactory, upsertWorkspaceRoleFactory @@ -46,6 +49,17 @@ import { import { withTransaction } from '@/modules/shared/helpers/dbHelper' import { findVerifiedEmailsByUserIdFactory } from '@/modules/core/repositories/userEmails' import { GetStream } from '@/modules/core/domain/streams/operations' +import { + GetUserSsoSession, + GetWorkspaceSsoProviderRecord +} from '@/modules/workspaces/domain/sso/operations' +import { isValidSsoSession } from '@/modules/workspaces/domain/sso/logic' +import { SsoSessionMissingOrExpiredError } from '@/modules/workspaces/errors/sso' +import { + getUserSsoSessionFactory, + getWorkspaceSsoProviderRecordFactory +} from '@/modules/workspaces/repositories/sso' +import { WorkspacesNotAuthorizedError } from '@/modules/workspaces/errors/workspace' export const onProjectCreatedFactory = ({ @@ -131,6 +145,35 @@ export const onInviteFinalizedFactory = }) } +export const onWorkspaceAuthorizedFactory = + ({ + getWorkspace, + getWorkspaceRoleForUser, + getWorkspaceSsoProviderRecord, + getUserSsoSession + }: { + getWorkspace: GetWorkspace + getWorkspaceRoleForUser: GetWorkspaceRoleForUser + getWorkspaceSsoProviderRecord: GetWorkspaceSsoProviderRecord + getUserSsoSession: GetUserSsoSession + }) => + async ({ userId, workspaceId }: { userId: string | null; workspaceId: string }) => { + if (!userId) throw new WorkspacesNotAuthorizedError() + + // Guests cannot use (and are not restricted by) SSO + const workspaceRole = await getWorkspaceRoleForUser({ userId, workspaceId }) + if (workspaceRole?.role === Roles.Workspace.Guest) return + + const provider = await getWorkspaceSsoProviderRecord({ workspaceId }) + if (!provider) return + + const session = await getUserSsoSession({ userId, workspaceId }) + if (!session || !isValidSsoSession(session)) { + const workspace = await getWorkspace({ workspaceId }) + throw new SsoSessionMissingOrExpiredError(workspace?.slug) + } + } + export const onWorkspaceRoleDeletedFactory = ({ queryAllWorkspaceProjects, @@ -244,6 +287,15 @@ export const initializeEventListenersFactory = }) await onInviteFinalized(payload) }), + eventBus.listen(WorkspaceEvents.Authorized, async ({ payload }) => { + const onWorkspaceAuthorized = onWorkspaceAuthorizedFactory({ + getWorkspace: getWorkspaceFactory({ db }), + getWorkspaceRoleForUser: getWorkspaceRoleForUserFactory({ db }), + getWorkspaceSsoProviderRecord: getWorkspaceSsoProviderRecordFactory({ db }), + getUserSsoSession: getUserSsoSessionFactory({ db }) + }) + await onWorkspaceAuthorized(payload) + }), eventBus.listen(WorkspaceEvents.RoleDeleted, async ({ payload }) => { const trx = await db.transaction() const onWorkspaceRoleDeleted = onWorkspaceRoleDeletedFactory({ diff --git a/packages/server/modules/workspaces/repositories/sso.ts b/packages/server/modules/workspaces/repositories/sso.ts index dd62c5dbe9..1988f91dae 100644 --- a/packages/server/modules/workspaces/repositories/sso.ts +++ b/packages/server/modules/workspaces/repositories/sso.ts @@ -167,6 +167,7 @@ export const listWorkspaceSsoMembershipsFactory = .where((builder) => { builder.where({ userId }) builder.whereNotNull('providerId') + builder.whereNot('role', 'workspace:guest') }) return workspaces } diff --git a/packages/server/modules/workspaces/tests/integration/sso.graph.spec.ts b/packages/server/modules/workspaces/tests/integration/sso.graph.spec.ts new file mode 100644 index 0000000000..0e90db119c --- /dev/null +++ b/packages/server/modules/workspaces/tests/integration/sso.graph.spec.ts @@ -0,0 +1,251 @@ +import { + assignToWorkspaces, + BasicTestWorkspace, + createTestOidcProvider, + createTestSsoSession, + createTestWorkspaces +} from '@/modules/workspaces/tests/helpers/creation' +import { + BasicTestUser, + createAuthTokenForUser, + createTestUsers +} from '@/test/authHelper' +import { + ActiveUserExpiredSsoSessionsDocument, + GetActiveUserWorkspacesDocument, + GetProjectDocument, + GetWorkspaceDocument, + GetWorkspaceProjectsDocument, + GetWorkspaceSsoDocument +} from '@/test/graphql/generated/graphql' +import { + createTestContext, + testApolloServer, + TestApolloServer +} from '@/test/graphqlHelper' +import { truncateTables } from '@/test/hooks' +import { BasicTestStream, createTestStream } from '@/test/speckle-helpers/streamHelper' +import { AllScopes, Roles } from '@speckle/shared' +import { expect } from 'chai' +import cryptoRandomString from 'crypto-random-string' + +describe('Workspace SSO', () => { + let memberApollo: TestApolloServer + let guestApollo: TestApolloServer + + const workspaceAdmin: BasicTestUser = { + id: '', + name: 'John Admin', + email: `${cryptoRandomString({ length: 9 })}@example.org`, + role: Roles.Server.Admin + } + + const workspaceMember: BasicTestUser = { + id: '', + name: 'John Member', + email: `${cryptoRandomString({ length: 9 })}@example.org` + } + + const workspaceGuest: BasicTestUser = { + id: '', + name: 'John Guest', + email: `${cryptoRandomString({ length: 9 })}@example.org` + } + + const testWorkspaceWithSso: BasicTestWorkspace = { + id: '', + ownerId: '', + name: 'Test SSO Workspace', + slug: 'gql-sso-workspace' + } + let testWorkspaceWithSsoProviderId = '' + let testWorkspaceWithSsoProjectId = '' + + const testWorkspaceWithoutSso: BasicTestWorkspace = { + id: '', + ownerId: '', + name: 'Test Non-SSO Workspace', + slug: 'gql-no-sso-workspace' + } + + before(async () => { + await createTestUsers([workspaceAdmin, workspaceMember, workspaceGuest]) + await createTestWorkspaces([ + [testWorkspaceWithSso, workspaceAdmin], + [testWorkspaceWithoutSso, workspaceAdmin] + ]) + testWorkspaceWithSsoProviderId = await createTestOidcProvider( + testWorkspaceWithSso.id + ) + + await assignToWorkspaces([ + [testWorkspaceWithSso, workspaceMember, Roles.Workspace.Member], + [testWorkspaceWithSso, workspaceGuest, Roles.Workspace.Guest], + [testWorkspaceWithoutSso, workspaceMember, Roles.Workspace.Member], + [testWorkspaceWithoutSso, workspaceGuest, Roles.Workspace.Guest] + ]) + + memberApollo = await testApolloServer({ + context: createTestContext({ + auth: true, + userId: workspaceMember.id, + token: await createAuthTokenForUser(workspaceMember.id), + role: Roles.Server.User, + scopes: AllScopes + }) + }) + guestApollo = await testApolloServer({ + context: createTestContext({ + auth: true, + userId: workspaceGuest.id, + token: await createAuthTokenForUser(workspaceGuest.id), + role: Roles.Server.User, + scopes: AllScopes + }) + }) + + const testProject: BasicTestStream = { + id: '', + ownerId: '', + isPublic: false, + name: 'Workspace Project', + workspaceId: testWorkspaceWithSso.id + } + + await createTestStream(testProject, workspaceAdmin) + testWorkspaceWithSsoProjectId = testProject.id + }) + + afterEach(async () => { + truncateTables(['user_sso_sessions']) + }) + + describe('given a workspace with SSO configured', () => { + describe('when a workspace member requests workspace information', () => { + describe('with a valid SSO session', () => { + beforeEach(async () => { + await createTestSsoSession(workspaceMember.id, testWorkspaceWithSso.id) + }) + + it('should allow the request', async () => { + const res = await memberApollo.execute(GetWorkspaceDocument, { + workspaceId: testWorkspaceWithSso.id + }) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.workspace.slug).to.equal('gql-sso-workspace') + }) + + it('should provide active SSO session information on workspace type', async () => { + const res = await memberApollo.execute(GetWorkspaceSsoDocument, { + id: testWorkspaceWithSso.id + }) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.workspace.sso).to.not.be.undefined + expect(res.data?.workspace.sso?.provider?.id).to.equal( + testWorkspaceWithSsoProviderId + ) + expect(res.data?.workspace.sso?.session).to.not.be.undefined + }) + }) + + describe('without a valid SSO session', () => { + it('should throw and provide redirect information', async () => { + const resA = await memberApollo.execute(GetWorkspaceDocument, { + workspaceId: testWorkspaceWithSso.id + }) + const resB = await memberApollo.execute(GetWorkspaceProjectsDocument, { + id: testWorkspaceWithSso.id + }) + const resC = await memberApollo.execute(GetProjectDocument, { + id: testWorkspaceWithSsoProjectId + }) + + for (const res of [resA, resB, resC]) { + expect(res).to.haveGraphQLErrors({ message: 'gql-sso-workspace' }) + expect(res).to.haveGraphQLErrors({ + code: 'SSO_SESSION_MISSING_OR_EXPIRED_ERROR' + }) + } + }) + + it('should allow limited access to workspace memberships', async () => { + const res = await memberApollo.execute(GetActiveUserWorkspacesDocument, {}) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.activeUser?.workspaces.items.length).to.equal(2) + }) + + it('should surface expired session', async () => { + const res = await memberApollo.execute( + ActiveUserExpiredSsoSessionsDocument, + {} + ) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.activeUser?.expiredSsoSessions.length).to.equal(1) + expect(res.data?.activeUser?.expiredSsoSessions[0].slug).to.equal( + 'gql-sso-workspace' + ) + }) + }) + }) + + describe('when a workspace guest requests workspace information', () => { + describe('without a valid SSO session', () => { + it('should allow the request', async () => { + const res = await guestApollo.execute(GetWorkspaceDocument, { + workspaceId: testWorkspaceWithSso.id + }) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.workspace.slug).to.equal('gql-sso-workspace') + }) + + it('should not show the workspace as an expired SSO session', async () => { + const res = await guestApollo.execute( + ActiveUserExpiredSsoSessionsDocument, + {} + ) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.activeUser?.expiredSsoSessions.length).to.equal(0) + }) + }) + }) + }) + + describe('given a workspace without SSO configured', () => { + describe('when a workspace member requests workspace information', () => { + it('should allow the request', async () => { + const res = await memberApollo.execute(GetWorkspaceDocument, { + workspaceId: testWorkspaceWithoutSso.id + }) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.workspace.slug).to.equal('gql-no-sso-workspace') + }) + + it('should return workspace provider information as `null`', async () => { + const res = await memberApollo.execute(GetWorkspaceSsoDocument, { + id: testWorkspaceWithoutSso.id + }) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.workspace.sso).to.be.null + }) + }) + + describe('when a workspace guest requests workspace information', () => { + it('should allow the request', async () => { + const res = await guestApollo.execute(GetWorkspaceDocument, { + workspaceId: testWorkspaceWithoutSso.id + }) + + expect(res).to.not.haveGraphQLErrors() + expect(res.data?.workspace.slug).to.equal('gql-no-sso-workspace') + }) + }) + }) +}) diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index a312b40af0..cc22744d33 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -4786,6 +4786,13 @@ export type GetProjectObjectQueryVariables = Exact<{ export type GetProjectObjectQuery = { __typename?: 'Query', project: { __typename?: 'Project', object?: { __typename?: 'Object', id: string, createdAt?: string | null } | null } }; +export type GetProjectQueryVariables = Exact<{ + id: Scalars['String']['input']; +}>; + + +export type GetProjectQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, workspaceId?: string | null } }; + export type CreateProjectMutationVariables = Exact<{ input: ProjectCreateInput; }>; @@ -5110,6 +5117,13 @@ export type GetWorkspaceProjectsQueryVariables = Exact<{ export type GetWorkspaceProjectsQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', projects: { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, team: Array<{ __typename?: 'ProjectCollaborator', id: string, role: string }> }> } } }; +export type GetWorkspaceSsoQueryVariables = Exact<{ + id: Scalars['String']['input']; +}>; + + +export type GetWorkspaceSsoQuery = { __typename?: 'Query', workspace: { __typename?: 'Workspace', sso?: { __typename?: 'WorkspaceSso', provider?: { __typename?: 'WorkspaceSsoProvider', id: string, name: string } | null, session?: { __typename?: 'WorkspaceSsoSession', createdAt: string, validUntil: string } | null } | null } }; + export type GetWorkspaceTeamQueryVariables = Exact<{ workspaceId: Scalars['String']['input']; filter?: InputMaybe; @@ -5132,6 +5146,11 @@ export type ActiveUserProjectsWorkspaceQueryVariables = Exact<{ [key: string]: n export type ActiveUserProjectsWorkspaceQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', projects: { __typename?: 'ProjectCollection', items: Array<{ __typename?: 'Project', id: string, workspace?: { __typename?: 'Workspace', id: string, name: string } | null }> } } | null }; +export type ActiveUserExpiredSsoSessionsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type ActiveUserExpiredSsoSessionsQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', expiredSsoSessions: Array<{ __typename?: 'LimitedWorkspace', id: string, slug: string }> } | null }; + export type MoveProjectToWorkspaceMutationVariables = Exact<{ projectId: Scalars['String']['input']; workspaceId: Scalars['String']['input']; @@ -5210,6 +5229,7 @@ export const UseProjectAccessRequestDocument = {"kind":"Document","definitions": export const CreateProjectCommentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProjectComment"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateCommentInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"commentMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rawText"}},{"kind":"Field","name":{"kind":"Name","value":"text"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"doc"}}]}},{"kind":"Field","name":{"kind":"Name","value":"authorId"}}]}}]}}]}}]} as unknown as DocumentNode; export const AdminProjectListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminProjectList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"query"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"visibility"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},"defaultValue":{"kind":"IntValue","value":"25"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}},"defaultValue":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"admin"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"query"},"value":{"kind":"Variable","name":{"kind":"Name","value":"query"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orderBy"}}},{"kind":"Argument","name":{"kind":"Name","value":"visibility"},"value":{"kind":"Variable","name":{"kind":"Name","value":"visibility"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectFields"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const GetProjectObjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProjectObject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"objectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"object"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode; +export const GetProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}}]}}]}}]} as unknown as DocumentNode; export const CreateProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"BasicProjectFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BasicProjectFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const BatchDeleteProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BatchDeleteProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ids"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"batchDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"ids"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ids"}}}]}]}}]}}]} as unknown as DocumentNode; export const CreateServerInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateServerInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInviteCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInviteCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; @@ -5254,7 +5274,9 @@ export const GetActiveUserWorkspacesDocument = {"kind":"Document","definitions": export const UpdateWorkspaceRoleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceRole"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceRoleUpdateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateRole"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; export const CreateWorkspaceProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateWorkspaceProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceProject"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; export const GetWorkspaceProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceProjectsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceProject"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]} as unknown as DocumentNode; +export const GetWorkspaceSsoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceSso"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sso"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"provider"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"session"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"validUntil"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetWorkspaceTeamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceTeam"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceTeamFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"team"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TestWorkspaceCollaborator"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TestWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"projectRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"project"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; export const ActiveUserLeaveWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ActiveUserLeaveWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"leave"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]}}]} as unknown as DocumentNode; export const ActiveUserProjectsWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUserProjectsWorkspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const ActiveUserExpiredSsoSessionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUserExpiredSsoSessions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"expiredSsoSessions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}}]}}]}}]}}]} as unknown as DocumentNode; export const MoveProjectToWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MoveProjectToWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"moveToWorkspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}},{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/packages/server/test/graphql/projects.ts b/packages/server/test/graphql/projects.ts index 999b4e5b89..08c7a35458 100644 --- a/packages/server/test/graphql/projects.ts +++ b/packages/server/test/graphql/projects.ts @@ -52,6 +52,16 @@ export const getProjectObjectQuery = gql` } ` +export const getProjectQuery = gql` + query GetProject($id: String!) { + project(id: $id) { + id + name + workspaceId + } + } +` + export const createProjectMutation = gql` mutation CreateProject($input: ProjectCreateInput!) { projectMutations { diff --git a/packages/server/test/graphql/workspaces.ts b/packages/server/test/graphql/workspaces.ts index 61f537306d..42fd600d7b 100644 --- a/packages/server/test/graphql/workspaces.ts +++ b/packages/server/test/graphql/workspaces.ts @@ -173,6 +173,23 @@ export const getWorkspaceProjectsQuery = gql` ${workspaceProjectFragment} ` +export const getWorkspaceSsoQuery = gql` + query GetWorkspaceSso($id: String!) { + workspace(id: $id) { + sso { + provider { + id + name + } + session { + createdAt + validUntil + } + } + } + } +` + export const getWorkspaceTeamQuery = gql` query GetWorkspaceTeam( $workspaceId: String! @@ -217,6 +234,17 @@ export const getProjectWorkspaceQuery = gql` } ` +export const getActiveUserExpiredSsoSessions = gql` + query ActiveUserExpiredSsoSessions { + activeUser { + expiredSsoSessions { + id + slug + } + } + } +` + export const moveProjectToWorkspaceMutation = gql` mutation MoveProjectToWorkspace($projectId: String!, $workspaceId: String!) { workspaceMutations {