From 7535c84e3db69a4b6065d03dcfffa5b495d15947 Mon Sep 17 00:00:00 2001 From: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Date: Fri, 8 Dec 2023 13:13:56 +0100 Subject: [PATCH] 2814 timebox create a poc to test the gmail api (#2868) * create gmail strategy and controller * gmail button connect * wip * trying to fix error { error: 'invalid_grant', error_description: 'Bad Request' } * access token working * refresh token working * Getting the short term token from the front is working * working * rename token * remove comment * rename env var * move file * Fix * Fix --------- Co-authored-by: Charles Bochet --- .../self-hosting/enviroment-variables.mdx | 2 + front/src/generated/graphql.tsx | 57 ++++++- .../mutations/generateTransientToken.ts | 11 ++ .../SettingsAccountsEmptyStateCard.tsx | 42 +++-- .../constants/settingsFieldMetadataTypes.ts | 2 +- .../__stories__/NavigationDrawer.stories.tsx | 4 +- .../graphql/fragments/userQueryFragment.ts | 6 + .../settings/accounts/SettingsAccounts.tsx | 20 +-- .../SettingsObjectNewFieldStep2.tsx | 2 +- server/.env.example | 5 + server/.env.test | 1 + server/package.json | 2 + server/src/core/auth/auth.module.ts | 23 ++- server/src/core/auth/auth.resolver.ts | 17 ++ .../google-gmail-auth.controller.ts | 48 ++++++ .../core/auth/dto/save-connected-account.ts | 31 ++++ .../core/auth/dto/transient-token.entity.ts | 9 ++ .../auth/guards/google-gmail-oauth.guard.ts | 27 ++++ .../google-gmail-provider-enabled.guard.ts | 21 +++ .../auth/services/google-gmail.service.ts | 35 +++++ .../src/core/auth/services/token.service.ts | 37 +++++ .../strategies/google-gmail.auth.strategy.ts | 80 ++++++++++ .../core/auth/strategies/jwt.auth.strategy.ts | 5 +- .../environment/environment.service.ts | 19 +++ .../standard-objects/connected-account.ts | 96 ++++++------ server/yarn.lock | 147 +++++++++++++++++- 26 files changed, 660 insertions(+), 89 deletions(-) create mode 100644 front/src/modules/auth/graphql/mutations/generateTransientToken.ts create mode 100644 server/src/core/auth/controllers/google-gmail-auth.controller.ts create mode 100644 server/src/core/auth/dto/save-connected-account.ts create mode 100644 server/src/core/auth/dto/transient-token.entity.ts create mode 100644 server/src/core/auth/guards/google-gmail-oauth.guard.ts create mode 100644 server/src/core/auth/guards/google-gmail-provider-enabled.guard.ts create mode 100644 server/src/core/auth/services/google-gmail.service.ts create mode 100644 server/src/core/auth/strategies/google-gmail.auth.strategy.ts diff --git a/docs/docs/start/self-hosting/enviroment-variables.mdx b/docs/docs/start/self-hosting/enviroment-variables.mdx index 3e96b2096..a443fdf6a 100644 --- a/docs/docs/start/self-hosting/enviroment-variables.mdx +++ b/docs/docs/start/self-hosting/enviroment-variables.mdx @@ -47,6 +47,8 @@ import OptionTable from '@site/src/theme/OptionTable' ### Auth ; displayName?: InputMaybe; @@ -644,12 +650,17 @@ export type GenerateApiKeyTokenMutationVariables = Exact<{ export type GenerateApiKeyTokenMutation = { __typename?: 'Mutation', generateApiKeyToken: { __typename?: 'ApiKeyToken', token: string } }; +export type GenerateTransientTokenMutationVariables = Exact<{ [key: string]: never; }>; + + +export type GenerateTransientTokenMutation = { __typename?: 'Mutation', generateTransientToken: { __typename?: 'TransientToken', transientToken: { __typename?: 'AuthToken', token: string } } }; + export type ImpersonateMutationVariables = Exact<{ userId: Scalars['String']; }>; -export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; +export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type RenewTokenMutationVariables = Exact<{ refreshToken: Scalars['String']; @@ -672,7 +683,7 @@ export type VerifyMutationVariables = Exact<{ }>; -export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; +export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type CheckUserExistsQueryVariables = Exact<{ email: Scalars['String']; @@ -702,7 +713,7 @@ export type UploadImageMutationVariables = Exact<{ export type UploadImageMutation = { __typename?: 'Mutation', uploadImage: string }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean } }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null } }; export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>; @@ -788,6 +799,12 @@ export const UserQueryFragmentFragmentDoc = gql` domainName inviteHash allowImpersonation + featureFlags { + id + key + value + workspaceId + } } } `; @@ -895,6 +912,40 @@ export function useGenerateApiKeyTokenMutation(baseOptions?: Apollo.MutationHook export type GenerateApiKeyTokenMutationHookResult = ReturnType; export type GenerateApiKeyTokenMutationResult = Apollo.MutationResult; export type GenerateApiKeyTokenMutationOptions = Apollo.BaseMutationOptions; +export const GenerateTransientTokenDocument = gql` + mutation generateTransientToken { + generateTransientToken { + transientToken { + token + } + } +} + `; +export type GenerateTransientTokenMutationFn = Apollo.MutationFunction; + +/** + * __useGenerateTransientTokenMutation__ + * + * To run a mutation, you first call `useGenerateTransientTokenMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useGenerateTransientTokenMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [generateTransientTokenMutation, { data, loading, error }] = useGenerateTransientTokenMutation({ + * variables: { + * }, + * }); + */ +export function useGenerateTransientTokenMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(GenerateTransientTokenDocument, options); + } +export type GenerateTransientTokenMutationHookResult = ReturnType; +export type GenerateTransientTokenMutationResult = Apollo.MutationResult; +export type GenerateTransientTokenMutationOptions = Apollo.BaseMutationOptions; export const ImpersonateDocument = gql` mutation Impersonate($userId: String!) { impersonate(userId: $userId) { diff --git a/front/src/modules/auth/graphql/mutations/generateTransientToken.ts b/front/src/modules/auth/graphql/mutations/generateTransientToken.ts new file mode 100644 index 000000000..07a94b39f --- /dev/null +++ b/front/src/modules/auth/graphql/mutations/generateTransientToken.ts @@ -0,0 +1,11 @@ +import { gql } from '@apollo/client'; + +export const GENERATE_ONE_TRANSIENT_TOKEN = gql` + mutation generateTransientToken { + generateTransientToken { + transientToken { + token + } + } + } +`; diff --git a/front/src/modules/settings/accounts/components/SettingsAccountsEmptyStateCard.tsx b/front/src/modules/settings/accounts/components/SettingsAccountsEmptyStateCard.tsx index 38f8937a4..dff1d555c 100644 --- a/front/src/modules/settings/accounts/components/SettingsAccountsEmptyStateCard.tsx +++ b/front/src/modules/settings/accounts/components/SettingsAccountsEmptyStateCard.tsx @@ -1,8 +1,11 @@ +import { useCallback } from 'react'; import styled from '@emotion/styled'; import { IconGoogle } from '@/ui/display/icon/components/IconGoogle'; import { Button } from '@/ui/input/button/components/Button'; import { Card } from '@/ui/layout/card/components/Card'; +import { REACT_APP_SERVER_AUTH_URL } from '~/config'; +import { useGenerateTransientTokenMutation } from '~/generated/graphql'; const StyledCard = styled(Card)` border-radius: ${({ theme }) => theme.border.radius.md}; @@ -27,15 +30,30 @@ const StyledBody = styled.div` padding: ${({ theme }) => theme.spacing(4)}; `; -export const SettingsAccountsEmptyStateCard = () => ( - - No connected account - -