From 2aff30dce56d1111589777881d6c628e070070c9 Mon Sep 17 00:00:00 2001 From: Holger Benl Date: Fri, 5 Apr 2024 16:56:39 +0200 Subject: [PATCH] Add support for SSO login --- src/graphql/generated/gql.ts | 21 +-- src/graphql/generated/graphql.ts | 34 +++-- src/graphql/graphQLClient.ts | 19 ++- src/graphql/queries/fulfillAuthRequest.ts | 2 +- src/graphql/queries/getAuthConnection.ts | 28 ++++ src/pages/api/auth/[auth0].ts | 41 ++++-- src/pages/api/browser/auth.ts | 7 +- src/pages/auth/cli.tsx | 18 --- src/pages/login.tsx | 158 +++++++++++++++++----- src/utils/getValueFromArrayOrString.ts | 1 + 10 files changed, 228 insertions(+), 101 deletions(-) create mode 100644 src/graphql/queries/getAuthConnection.ts delete mode 100644 src/pages/auth/cli.tsx create mode 100644 src/utils/getValueFromArrayOrString.ts diff --git a/src/graphql/generated/gql.ts b/src/graphql/generated/gql.ts index 8283802a..d21823d4 100644 --- a/src/graphql/generated/gql.ts +++ b/src/graphql/generated/gql.ts @@ -21,9 +21,8 @@ const documents = { "\n mutation DeleteUserAPIKey($id: ID!) {\n deleteUserAPIKey(input: { id: $id }) {\n success\n }\n }\n ": types.DeleteUserApiKeyDocument, "\n mutation DeleteWorkspace(\n $workspaceId: ID!\n $shouldDeleteRecordings: Boolean!\n ) {\n deleteWorkspace(\n input: {\n workspaceId: $workspaceId\n shouldDeleteRecordings: $shouldDeleteRecordings\n }\n ) {\n success\n }\n }\n ": types.DeleteWorkspaceDocument, "\n mutation DeleteWorkspaceAPIKey($id: ID!) {\n deleteWorkspaceAPIKey(input: { id: $id }) {\n success\n }\n }\n ": types.DeleteWorkspaceApiKeyDocument, + "\n query GetAuthConnection($email: String!) {\n auth {\n connection(email: $email)\n }\n }\n": types.GetAuthConnectionDocument, "\n query GetRecordingPhoto($recordingId: UUID!) {\n recording(uuid: $recordingId) {\n thumbnail\n uuid\n }\n }\n": types.GetRecordingPhotoDocument, - "\n query GetUserSettings {\n viewer {\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n ": types.GetUserSettingsDocument, - "\n query GetWorkspaceApiKeys($workspaceId: ID!) {\n node(id: $workspaceId) {\n ... on Workspace {\n id\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n }\n ": types.GetWorkspaceApiKeysDocument, "\n query GetWorkspaceMembers($workspaceId: ID!) {\n node(id: $workspaceId) {\n ... on Workspace {\n id\n members {\n edges {\n node {\n ... on WorkspacePendingEmailMember {\n __typename\n id\n roles\n email\n createdAt\n }\n ... on WorkspacePendingUserMember {\n __typename\n id\n roles\n user {\n id\n name\n picture\n }\n }\n ... on WorkspaceUserMember {\n __typename\n id\n roles\n user {\n id\n name\n picture\n }\n }\n }\n }\n }\n }\n }\n }\n ": types.GetWorkspaceMembersDocument, "\n query GetWorkspace($workspaceId: ID!) {\n node(id: $workspaceId) {\n ... on Workspace {\n id\n isOrganization\n isTest\n }\n }\n }\n": types.GetWorkspaceDocument, "\n mutation InviteWorkspaceMember(\n $email: String!\n $workspaceId: ID!\n $roles: [String!]\n ) {\n addWorkspaceMember(\n input: { email: $email, workspaceId: $workspaceId, roles: $roles }\n ) {\n success\n }\n }\n ": types.InviteWorkspaceMemberDocument, @@ -33,6 +32,8 @@ const documents = { "\n query GetUser {\n viewer {\n email\n internal\n nags\n user {\n name\n picture\n id\n }\n }\n }\n ": types.GetUserDocument, "\n mutation DeleteRecording($recordingId: ID!) {\n deleteRecording(input: { id: $recordingId }) {\n success\n }\n }\n ": types.DeleteRecordingDocument, "\n mutation DeleteCollaborator($collaborationId: ID!) {\n removeRecordingCollaborator(input: { id: $collaborationId }) {\n success\n }\n }\n ": types.DeleteCollaboratorDocument, + "\n query GetUserSettings {\n viewer {\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n ": types.GetUserSettingsDocument, + "\n query GetWorkspaceApiKeys($workspaceId: ID!) {\n node(id: $workspaceId) {\n ... on Workspace {\n id\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n }\n ": types.GetWorkspaceApiKeysDocument, "\n query GetNonPendingWorkspaces {\n viewer {\n workspaces {\n edges {\n node {\n hasPaymentMethod\n id\n invitationCode\n isOrganization\n isTest\n name\n settings {\n features\n }\n subscription {\n id\n plan {\n id\n key\n }\n }\n }\n }\n }\n }\n }\n ": types.GetNonPendingWorkspacesDocument, "\n query GetPendingWorkspaces {\n viewer {\n workspaceInvitations {\n edges {\n node {\n workspace {\n id\n name\n recordingCount\n isOrganization\n isTest\n }\n inviterEmail\n }\n }\n }\n }\n }\n ": types.GetPendingWorkspacesDocument, "\n query GetPersonalRecordings($filter: String) {\n viewer {\n recordings(filter: $filter) {\n edges {\n node {\n buildId\n duration\n comments {\n id\n }\n createdAt\n owner {\n id\n name\n picture\n }\n private\n title\n url\n uuid\n workspace {\n id\n }\n }\n }\n }\n }\n }\n ": types.GetPersonalRecordingsDocument, @@ -98,15 +99,11 @@ export function graphql(source: "\n mutation DeleteWorkspaceAPIKey($id: ID! /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query GetRecordingPhoto($recordingId: UUID!) {\n recording(uuid: $recordingId) {\n thumbnail\n uuid\n }\n }\n"): (typeof documents)["\n query GetRecordingPhoto($recordingId: UUID!) {\n recording(uuid: $recordingId) {\n thumbnail\n uuid\n }\n }\n"]; -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql(source: "\n query GetUserSettings {\n viewer {\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n "): (typeof documents)["\n query GetUserSettings {\n viewer {\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n "]; +export function graphql(source: "\n query GetAuthConnection($email: String!) {\n auth {\n connection(email: $email)\n }\n }\n"): (typeof documents)["\n query GetAuthConnection($email: String!) {\n auth {\n connection(email: $email)\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query GetWorkspaceApiKeys($workspaceId: ID!) {\n node(id: $workspaceId) {\n ... on Workspace {\n id\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n }\n "): (typeof documents)["\n query GetWorkspaceApiKeys($workspaceId: ID!) {\n node(id: $workspaceId) {\n ... on Workspace {\n id\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n }\n "]; +export function graphql(source: "\n query GetRecordingPhoto($recordingId: UUID!) {\n recording(uuid: $recordingId) {\n thumbnail\n uuid\n }\n }\n"): (typeof documents)["\n query GetRecordingPhoto($recordingId: UUID!) {\n recording(uuid: $recordingId) {\n thumbnail\n uuid\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -143,6 +140,14 @@ export function graphql(source: "\n mutation DeleteRecording($recordingId: * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n mutation DeleteCollaborator($collaborationId: ID!) {\n removeRecordingCollaborator(input: { id: $collaborationId }) {\n success\n }\n }\n "): (typeof documents)["\n mutation DeleteCollaborator($collaborationId: ID!) {\n removeRecordingCollaborator(input: { id: $collaborationId }) {\n success\n }\n }\n "]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query GetUserSettings {\n viewer {\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n "): (typeof documents)["\n query GetUserSettings {\n viewer {\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n "]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query GetWorkspaceApiKeys($workspaceId: ID!) {\n node(id: $workspaceId) {\n ... on Workspace {\n id\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n }\n "): (typeof documents)["\n query GetWorkspaceApiKeys($workspaceId: ID!) {\n node(id: $workspaceId) {\n ... on Workspace {\n id\n apiKeys {\n id\n createdAt\n label\n scopes\n recordingCount\n maxRecordings\n }\n }\n }\n }\n "]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/src/graphql/generated/graphql.ts b/src/graphql/generated/graphql.ts index ba7f666b..0d449348 100644 --- a/src/graphql/generated/graphql.ts +++ b/src/graphql/generated/graphql.ts @@ -24065,24 +24065,19 @@ export type DeleteWorkspaceApiKeyMutationVariables = Exact<{ export type DeleteWorkspaceApiKeyMutation = { __typename?: 'mutation_root', deleteWorkspaceAPIKey: { __typename?: 'DeleteWorkspaceAPIKey', success?: boolean | null } }; -export type GetRecordingPhotoQueryVariables = Exact<{ - recordingId: Scalars['UUID']['input']; +export type GetAuthConnectionQueryVariables = Exact<{ + email: Scalars['String']['input']; }>; -export type GetRecordingPhotoQuery = { __typename?: 'query_root', recording?: { __typename?: 'Recording', thumbnail?: string | null, uuid: any } | null }; - -export type GetUserSettingsQueryVariables = Exact<{ [key: string]: never; }>; - - -export type GetUserSettingsQuery = { __typename?: 'query_root', viewer?: { __typename?: 'AuthenticatedUser', apiKeys: Array<{ __typename?: 'AuthenticatedUserAPIKey', id: string, createdAt: any, label: string, scopes: Array, recordingCount: number, maxRecordings?: number | null }> } | null }; +export type GetAuthConnectionQuery = { __typename?: 'query_root', auth: { __typename?: 'Authentication', connection?: string | null } }; -export type GetWorkspaceApiKeysQueryVariables = Exact<{ - workspaceId: Scalars['ID']['input']; +export type GetRecordingPhotoQueryVariables = Exact<{ + recordingId: Scalars['UUID']['input']; }>; -export type GetWorkspaceApiKeysQuery = { __typename?: 'query_root', node?: { __typename?: 'Recording' } | { __typename?: 'RootCauseAnalysis' } | { __typename?: 'Workspace', id: string, apiKeys?: Array<{ __typename?: 'WorkspaceAPIKey', id: string, createdAt: any, label: string, scopes: Array, recordingCount: number, maxRecordings?: number | null }> | null } | null }; +export type GetRecordingPhotoQuery = { __typename?: 'query_root', recording?: { __typename?: 'Recording', thumbnail?: string | null, uuid: any } | null }; export type GetWorkspaceMembersQueryVariables = Exact<{ workspaceId: Scalars['ID']['input']; @@ -24150,6 +24145,18 @@ export type DeleteCollaboratorMutationVariables = Exact<{ export type DeleteCollaboratorMutation = { __typename?: 'mutation_root', removeRecordingCollaborator: { __typename?: 'RemoveRecordingCollaborator', success?: boolean | null } }; +export type GetUserSettingsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetUserSettingsQuery = { __typename?: 'query_root', viewer?: { __typename?: 'AuthenticatedUser', apiKeys: Array<{ __typename?: 'AuthenticatedUserAPIKey', id: string, createdAt: any, label: string, scopes: Array, recordingCount: number, maxRecordings?: number | null }> } | null }; + +export type GetWorkspaceApiKeysQueryVariables = Exact<{ + workspaceId: Scalars['ID']['input']; +}>; + + +export type GetWorkspaceApiKeysQuery = { __typename?: 'query_root', node?: { __typename?: 'Recording' } | { __typename?: 'RootCauseAnalysis' } | { __typename?: 'Workspace', id: string, apiKeys?: Array<{ __typename?: 'WorkspaceAPIKey', id: string, createdAt: any, label: string, scopes: Array, recordingCount: number, maxRecordings?: number | null }> | null } | null }; + export type GetNonPendingWorkspacesQueryVariables = Exact<{ [key: string]: never; }>; @@ -24265,9 +24272,8 @@ export const DeclinePendingWorkspaceInvitationDocument = {"kind":"Document","def export const DeleteUserApiKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteUserAPIKey"},"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":"deleteUserAPIKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; export const DeleteWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"shouldDeleteRecordings"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteWorkspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"shouldDeleteRecordings"},"value":{"kind":"Variable","name":{"kind":"Name","value":"shouldDeleteRecordings"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; export const DeleteWorkspaceApiKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteWorkspaceAPIKey"},"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":"deleteWorkspaceAPIKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; +export const GetAuthConnectionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAuthConnection"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"auth"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connection"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}}]}]}}]}}]} as unknown as DocumentNode; export const GetRecordingPhotoDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetRecordingPhoto"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"recordingId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"recording"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"recordingId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thumbnail"}},{"kind":"Field","name":{"kind":"Name","value":"uuid"}}]}}]}}]} as unknown as DocumentNode; -export const GetUserSettingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"recordingCount"}},{"kind":"Field","name":{"kind":"Name","value":"maxRecordings"}}]}}]}}]}}]} as unknown as DocumentNode; -export const GetWorkspaceApiKeysDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceApiKeys"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"apiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"recordingCount"}},{"kind":"Field","name":{"kind":"Name","value":"maxRecordings"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetWorkspaceMembersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceMembers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"members"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspacePendingEmailMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"roles"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspacePendingUserMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"roles"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"picture"}}]}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceUserMember"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"roles"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"picture"}}]}}]}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isOrganization"}},{"kind":"Field","name":{"kind":"Name","value":"isTest"}}]}}]}}]}}]} as unknown as DocumentNode; export const InviteWorkspaceMemberDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InviteWorkspaceMember"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"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":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"roles"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addWorkspaceMember"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"roles"},"value":{"kind":"Variable","name":{"kind":"Name","value":"roles"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; @@ -24277,6 +24283,8 @@ export const ClaimTeamInvitationCodeDocument = {"kind":"Document","definitions": export const GetUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"internal"}},{"kind":"Field","name":{"kind":"Name","value":"nags"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"picture"}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const DeleteRecordingDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteRecording"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"recordingId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteRecording"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"recordingId"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; export const DeleteCollaboratorDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteCollaborator"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"collaborationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeRecordingCollaborator"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"collaborationId"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; +export const GetUserSettingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUserSettings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"recordingCount"}},{"kind":"Field","name":{"kind":"Name","value":"maxRecordings"}}]}}]}}]}}]} as unknown as DocumentNode; +export const GetWorkspaceApiKeysDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWorkspaceApiKeys"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"apiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"recordingCount"}},{"kind":"Field","name":{"kind":"Name","value":"maxRecordings"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetNonPendingWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNonPendingWorkspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasPaymentMethod"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"invitationCode"}},{"kind":"Field","name":{"kind":"Name","value":"isOrganization"}},{"kind":"Field","name":{"kind":"Name","value":"isTest"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"settings"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"features"}}]}},{"kind":"Field","name":{"kind":"Name","value":"subscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"plan"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}}]}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetPendingWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetPendingWorkspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceInvitations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"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"}},{"kind":"Field","name":{"kind":"Name","value":"recordingCount"}},{"kind":"Field","name":{"kind":"Name","value":"isOrganization"}},{"kind":"Field","name":{"kind":"Name","value":"isTest"}}]}},{"kind":"Field","name":{"kind":"Name","value":"inviterEmail"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetPersonalRecordingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetPersonalRecordings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"viewer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"recordings"},"arguments":[{"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":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"buildId"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"picture"}}]}},{"kind":"Field","name":{"kind":"Name","value":"private"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/src/graphql/graphQLClient.ts b/src/graphql/graphQLClient.ts index 1c39a9d5..7fbd188d 100644 --- a/src/graphql/graphQLClient.ts +++ b/src/graphql/graphQLClient.ts @@ -14,16 +14,20 @@ if (process.env.NODE_ENV === "development") { loadErrorMessageHandler(); } +let graphQLClientAccessToken: string | undefined; let graphQLClient: ApolloClient; -export function getGraphQLClient(accessToken: string) { - if (graphQLClient == null) { +export function getGraphQLClient(accessToken?: string) { + if (graphQLClient == null || graphQLClientAccessToken !== accessToken) { + const headers: Record = { + "Content-Type": "application/json", + "Replay-Client-Id": "196a9e7b-dba5-46ee-8b81-fac66991f431", + }; + if (accessToken) { + headers["Authorization"] = `Bearer ${accessToken}`; + } const httpLink = createHttpLink({ - headers: { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", - "Replay-Client-Id": "196a9e7b-dba5-46ee-8b81-fac66991f431", - }, + headers, uri: `${URLS.api}/v1/graphql`, }); const retryLink = new RetryLink({ @@ -36,6 +40,7 @@ export function getGraphQLClient(accessToken: string) { }, }); + graphQLClientAccessToken = accessToken; graphQLClient = new ApolloClient({ cache: new InMemoryCache({ typePolicies: { diff --git a/src/graphql/queries/fulfillAuthRequest.ts b/src/graphql/queries/fulfillAuthRequest.ts index 111db026..d0f6fe50 100644 --- a/src/graphql/queries/fulfillAuthRequest.ts +++ b/src/graphql/queries/fulfillAuthRequest.ts @@ -16,7 +16,7 @@ export async function fulfillAuthRequest(id: string, token: string) { } `, variables: { - secret: "omNN-4K*GiHhqUH8-7mUB6Ecz8ZPBtcqH68V", + secret: process.env.FRONTEND_API_SECRET!, id, token, }, diff --git a/src/graphql/queries/getAuthConnection.ts b/src/graphql/queries/getAuthConnection.ts new file mode 100644 index 00000000..b934b9e3 --- /dev/null +++ b/src/graphql/queries/getAuthConnection.ts @@ -0,0 +1,28 @@ +import { + GetAuthConnectionQuery, + GetAuthConnectionQueryVariables, +} from "@/graphql/generated/graphql"; +import { getGraphQLClient } from "@/graphql/graphQLClient"; +import { gql } from "@apollo/client"; + +const QUERY = gql` + query GetAuthConnection($email: String!) { + auth { + connection(email: $email) + } + } +`; + +export async function getAuthConnection(email: string): Promise { + const graphQLClient = getGraphQLClient(); + + const response = await graphQLClient.query< + GetAuthConnectionQuery, + GetAuthConnectionQueryVariables + >({ + query: QUERY, + variables: { email }, + }); + + return response.data?.auth?.connection ?? null; +} diff --git a/src/pages/api/auth/[auth0].ts b/src/pages/api/auth/[auth0].ts index 931f9fb8..046d9b07 100644 --- a/src/pages/api/auth/[auth0].ts +++ b/src/pages/api/auth/[auth0].ts @@ -1,18 +1,29 @@ -import { handleAuth, handleLogin } from "@auth0/nextjs-auth0"; - -const authorizationParams = { - audience: "https://api.replay.io", - code_challenge_method: "S256", - response_type: "code" as "code", - scope: "openid profile offline_access", -}; +import { getValueFromArrayOrString } from "@/utils/getValueFromArrayOrString"; +import { handleAuth, handleCallback, handleLogin } from "@auth0/nextjs-auth0"; +import { NextApiRequest, NextApiResponse } from "next"; export default handleAuth({ - login: handleLogin({ authorizationParams }), - switchAccount: handleLogin({ - authorizationParams: { - ...authorizationParams, - prompt: "login", - }, - }), + login: (req: NextApiRequest, res: NextApiResponse) => { + handleLogin(req, res, { + authorizationParams: { + audience: "https://api.replay.io", + code_challenge_method: "S256", + response_type: "code" as "code", + scope: "openid profile offline_access", + prompt: getValueFromArrayOrString(req.query.prompt), + connection: getValueFromArrayOrString(req.query.connection), + } + }); + }, + callback: (req: NextApiRequest, res: NextApiResponse) => { + if (req.query.error_description) { + const searchParams = new URLSearchParams({ + type: "auth", + message: getValueFromArrayOrString(req.query.error_description)!, + }); + res.redirect(`/browser/error?${searchParams.toString()}`); + } else { + handleCallback(req, res); + } + }, }); diff --git a/src/pages/api/browser/auth.ts b/src/pages/api/browser/auth.ts index 8d02a36a..11b8a7b5 100644 --- a/src/pages/api/browser/auth.ts +++ b/src/pages/api/browser/auth.ts @@ -4,12 +4,11 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { getSession } from "@auth0/nextjs-auth0"; import { initAuthRequest } from "@/graphql/queries/initAuthRequest"; import { fulfillAuthRequest } from "@/graphql/queries/fulfillAuthRequest"; - -const getQueryValue = (query: string | string[] | undefined) => (Array.isArray(query) ? query[0] : query); +import { getValueFromArrayOrString } from "@/utils/getValueFromArrayOrString"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const key = getQueryValue(req.query.key); - const source = getQueryValue(req.query.source) || "browser"; + const key = getValueFromArrayOrString(req.query.key); + const source = getValueFromArrayOrString(req.query.source) || "browser"; try { if (key) { diff --git a/src/pages/auth/cli.tsx b/src/pages/auth/cli.tsx deleted file mode 100644 index f753ce54..00000000 --- a/src/pages/auth/cli.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { EmptyLayout } from "@/components/EmptyLayout"; -import { SessionContext } from "@/components/SessionContext"; -import { useContext } from "react"; - -export default function Page() { - const { accessToken } = useContext(SessionContext); - - // TODO: Implement the CLI authentication page - if (accessToken) { - return ( -
Would you like to authorize the CLI?
- ); - } else { - return
You need to sign in
; - } -} - -Page.Layout = EmptyLayout; diff --git a/src/pages/login.tsx b/src/pages/login.tsx index b6e057c1..eccefac1 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -1,11 +1,111 @@ import { Button } from "@/components/Button"; import { EmptyLayout } from "@/components/EmptyLayout"; import { ExternalLink } from "@/components/ExternalLink"; +import { Input } from "@/components/Input"; import { Message } from "@/components/Message"; import { ReplayLogo } from "@/components/ReplayLogo"; +import { getAuthConnection } from "@/graphql/queries/getAuthConnection"; import { getSession } from "@auth0/nextjs-auth0"; import { GetServerSidePropsContext, InferGetServerSidePropsType } from "next"; import { useRouter, useSearchParams } from "next/navigation"; +import { useState } from "react"; + +const defaultConnection = "google-oauth2"; + +function DefaultLogin({ onLogin, onSSOLogin }: { onLogin: () => void; onSSOLogin: () => void }) { + return ( + + +
+ Replay captures everything you need for the perfect bug report, all in + one link. +
+ + Learn more + +
+
+ + +
+
+ ); +} + +function SSOLogin({ onLogin }: { onLogin: (connection: string) => void }) { + const [email, setEmail] = useState(""); + const [error, setError] = useState(false); + + const onSSOLogin = async () => { + const authConnection = await getAuthConnection(email); + + if (authConnection) { + onLogin(authConnection); + } else { + setError(true); + } + }; + + return ( + + +
+ Enter your email to be redirected to your SSO provider. +
+ {error && ( +
+ We couldn't find an SSO provider for your email. +
+ )} +
+ setEmail(value)} + /> + +
+ +
+ ); +} + +function SwitchAccountMessage({ name, label, onContinue, onSwitch }: { + name: string; + label: string; + onContinue: () => void; + onSwitch: () => void; +}) { + return ( + + +
+ You are already logged in as {name}. +
+ + { + globalThis.__IS_RECORD_REPLAY_RUNTIME__ || ( + + ) + } +
+ ); +} export default function Page({ user, @@ -13,46 +113,34 @@ export default function Page({ const router = useRouter(); const searchParams = useSearchParams(); const returnTo = searchParams?.get("returnTo") || "/"; + const [switchAccount, setSwitchAccount] = useState(false); + const [ssoLogin, setSSOLogin] = useState(false); + + function onLogin(connection: string) { + let authUrl = `/api/auth/login?connection=${connection}&returnTo=${returnTo}`; + if (switchAccount) { + authUrl += "&prompt=login"; + } + router.push(authUrl); + } - if (user) { + if (user && !switchAccount) { return ( - - -
- You are already logged in as {user.name}. -
- - { - globalThis.__IS_RECORD_REPLAY_RUNTIME__ || ( - - ) - } -
+ router.push(returnTo)} + onSwitch={() => setSwitchAccount(true)} + /> ); + } else if (ssoLogin) { + return ; } else { return ( - - -
- Replay captures everything you need for the perfect bug report, all in - one link. -
- - Learn more - -
- -
+ onLogin(defaultConnection)} + onSSOLogin={() => setSSOLogin(true)} + /> ); } } diff --git a/src/utils/getValueFromArrayOrString.ts b/src/utils/getValueFromArrayOrString.ts new file mode 100644 index 00000000..c42e4f48 --- /dev/null +++ b/src/utils/getValueFromArrayOrString.ts @@ -0,0 +1 @@ +export const getValueFromArrayOrString = (query: string | string[] | undefined) => (Array.isArray(query) ? query[0] : query);