diff --git a/packages/twenty-chrome-extension/src/db/auth.db.ts b/packages/twenty-chrome-extension/src/db/auth.db.ts index 60af6a076..9bc4c3d77 100644 --- a/packages/twenty-chrome-extension/src/db/auth.db.ts +++ b/packages/twenty-chrome-extension/src/db/auth.db.ts @@ -18,9 +18,3 @@ export const exchangeAuthorizationCode = async ( return data.exchangeAuthorizationCode; else return null; }; - -// export const RenewToken = async (appToken: string): Promise => { -// const data = await callQuery(RENEW_TOKEN, { appToken }); -// if (isDefined(data)) return data; -// else return null; -// }; diff --git a/packages/twenty-chrome-extension/src/db/token.db.ts b/packages/twenty-chrome-extension/src/db/token.db.ts new file mode 100644 index 000000000..44fc354b3 --- /dev/null +++ b/packages/twenty-chrome-extension/src/db/token.db.ts @@ -0,0 +1,36 @@ +import { ApolloClient, InMemoryCache } from '@apollo/client'; + +import { Tokens } from '~/db/types/auth.types'; +import { RENEW_TOKEN } from '~/graphql/auth/mutations'; +import { isDefined } from '~/utils/isDefined'; + +export const renewToken = async ( + appToken: string, +): Promise<{ renewToken: { tokens: Tokens } } | null> => { + const store = await chrome.storage.local.get(); + const serverUrl = `${ + isDefined(store.serverBaseUrl) + ? store.serverBaseUrl + : import.meta.env.VITE_SERVER_BASE_URL + }/graphql`; + + // Create new client to call refresh token graphql mutation + const client = new ApolloClient({ + uri: serverUrl, + cache: new InMemoryCache({}), + }); + + const { data } = await client.mutate({ + mutation: RENEW_TOKEN, + variables: { + appToken, + }, + fetchPolicy: 'network-only', + }); + + if (isDefined(data)) { + return data; + } else { + return null; + } +}; diff --git a/packages/twenty-chrome-extension/src/graphql/auth/mutations.ts b/packages/twenty-chrome-extension/src/graphql/auth/mutations.ts index 34fb9a3f1..470d3826f 100644 --- a/packages/twenty-chrome-extension/src/graphql/auth/mutations.ts +++ b/packages/twenty-chrome-extension/src/graphql/auth/mutations.ts @@ -26,3 +26,20 @@ export const EXCHANGE_AUTHORIZATION_CODE = gql` } } `; + +export const RENEW_TOKEN = gql` + mutation RenewToken($appToken: String!) { + renewToken(appToken: $appToken) { + tokens { + accessToken { + token + expiresAt + } + refreshToken { + token + expiresAt + } + } + } + } +`; diff --git a/packages/twenty-chrome-extension/src/graphql/auth/queries.ts b/packages/twenty-chrome-extension/src/graphql/auth/queries.ts deleted file mode 100644 index fe9d1f8c1..000000000 --- a/packages/twenty-chrome-extension/src/graphql/auth/queries.ts +++ /dev/null @@ -1,20 +0,0 @@ -// import { gql } from '@apollo/client'; - -// export const RENEW_TOKEN = gql` -// query RenewToken($appToken: String!) { -// renewToken(appToken: $appToken) { -// loginToken { -// token -// expiresAt -// } -// accessToken { -// token -// expiresAt -// } -// refreshToken { -// token -// expiresAt -// } -// } -// } -// `; diff --git a/packages/twenty-chrome-extension/src/utils/apolloClient.ts b/packages/twenty-chrome-extension/src/utils/apolloClient.ts index 3514bd50c..23f54fc83 100644 --- a/packages/twenty-chrome-extension/src/utils/apolloClient.ts +++ b/packages/twenty-chrome-extension/src/utils/apolloClient.ts @@ -1,72 +1,121 @@ -import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client'; +import { + ApolloClient, + from, + fromPromise, + HttpLink, + InMemoryCache, +} from '@apollo/client'; +import { setContext } from '@apollo/client/link/context'; import { onError } from '@apollo/client/link/error'; +import { renewToken } from '~/db/token.db'; +import { Tokens } from '~/db/types/auth.types'; import { isDefined } from '~/utils/isDefined'; const clearStore = () => { - chrome.storage.local.remove('loginToken'); - - chrome.storage.local.remove('accessToken'); - - chrome.storage.local.remove('refreshToken'); - + chrome.storage.local.remove(['loginToken', 'accessToken', 'refreshToken']); chrome.storage.local.set({ isAuthenticated: false }); }; -const getApolloClient = async () => { +const setStore = (tokens: Tokens) => { + if (isDefined(tokens.loginToken)) { + chrome.storage.local.set({ + loginToken: tokens.loginToken, + }); + } + chrome.storage.local.set({ + accessToken: tokens.accessToken, + refreshToken: tokens.refreshToken, + }); +}; + +export const getServerUrl = async () => { const store = await chrome.storage.local.get(); const serverUrl = `${ isDefined(store.serverBaseUrl) ? store.serverBaseUrl : import.meta.env.VITE_SERVER_BASE_URL }/graphql`; + return serverUrl; +}; - const errorLink = onError(({ graphQLErrors, networkError }) => { - if (isDefined(graphQLErrors)) { - for (const graphQLError of graphQLErrors) { - if (graphQLError.message === 'Unauthorized') { - //TODO: replace this with renewToken mutation - clearStore(); - return; - } - switch (graphQLError?.extensions?.code) { - case 'UNAUTHENTICATED': { - //TODO: replace this with renewToken mutation - clearStore(); - break; +const getAuthToken = async () => { + const store = await chrome.storage.local.get(); + if (isDefined(store.accessToken)) return `Bearer ${store.accessToken.token}`; + else return ''; +}; + +const getApolloClient = async () => { + const store = await chrome.storage.local.get(); + + const authLink = setContext(async (_, { headers }) => { + const token = await getAuthToken(); + return { + headers: { + ...headers, + authorization: token, + }, + }; + }); + const errorLink = onError( + ({ graphQLErrors, networkError, forward, operation }) => { + if (isDefined(graphQLErrors)) { + for (const graphQLError of graphQLErrors) { + if (graphQLError.message === 'Unauthorized') { + return fromPromise( + renewToken(store.refreshToken.token) + .then((response) => { + if (isDefined(response)) { + setStore(response.renewToken.tokens); + } + }) + .catch(() => { + clearStore(); + }), + ).flatMap(() => forward(operation)); + } + switch (graphQLError?.extensions?.code) { + case 'UNAUTHENTICATED': { + return fromPromise( + renewToken(store.refreshToken.token) + .then((response) => { + if (isDefined(response)) { + setStore(response.renewToken.tokens); + } + }) + .catch(() => { + clearStore(); + }), + ).flatMap(() => forward(operation)); + } + default: + // eslint-disable-next-line no-console + console.error( + `[GraphQL error]: Message: ${graphQLError.message}, Location: ${ + graphQLError.locations + ? JSON.stringify(graphQLError.locations) + : graphQLError.locations + }, Path: ${graphQLError.path}`, + ); + break; } - default: - // eslint-disable-next-line no-console - console.error( - `[GraphQL error]: Message: ${graphQLError.message}, Location: ${ - graphQLError.locations - ? JSON.stringify(graphQLError.locations) - : graphQLError.locations - }, Path: ${graphQLError.path}`, - ); - break; } } - } - if (isDefined(networkError)) { - // eslint-disable-next-line no-console - console.error(`[Network error]: ${networkError}`); - } - }); + if (isDefined(networkError)) { + // eslint-disable-next-line no-console + console.error(`[Network error]: ${networkError}`); + } + }, + ); const httpLink = new HttpLink({ - uri: serverUrl, - headers: isDefined(store.accessToken) - ? { - Authorization: `Bearer ${store.accessToken.token}`, - } - : {}, + uri: await getServerUrl(), }); const client = new ApolloClient({ cache: new InMemoryCache(), - link: from([errorLink, httpLink]), + link: from([errorLink, authLink, httpLink]), }); return client; diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/token.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/token.service.ts index 47d0a1af4..c8a8a85df 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/token.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/token.service.ts @@ -422,8 +422,9 @@ export class TokenService { assert(token, "This refresh token doesn't exist", NotFoundException); - const user = await this.userRepository.findOneBy({ - id: jwtPayload.sub, + const user = await this.userRepository.findOne({ + where: { id: jwtPayload.sub }, + relations: ['appTokens'], }); assert(user, 'User not found', NotFoundException); diff --git a/packages/twenty-server/src/engine/middlewares/graphql-hydrate-request-from-token.middleware.ts b/packages/twenty-server/src/engine/middlewares/graphql-hydrate-request-from-token.middleware.ts index 8e435978e..4b416ea79 100644 --- a/packages/twenty-server/src/engine/middlewares/graphql-hydrate-request-from-token.middleware.ts +++ b/packages/twenty-server/src/engine/middlewares/graphql-hydrate-request-from-token.middleware.ts @@ -32,6 +32,7 @@ export class GraphQLHydrateRequestFromTokenMiddleware 'SignUp', 'RenewToken', 'IntrospectionQuery', + 'ExchangeAuthorizationCode', ]; if (