From d58af82c51b8eb0d2e579c5ecf316d32492d2475 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 3 Feb 2023 19:59:00 +0100 Subject: [PATCH] Separate auth0 users depending on tenants --- .circleci/config.yml | 5 +- .../twenty/tables/public_tenants.yaml | 10 ++- .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 1 + .../up.sql | 2 + front/src/App.tsx | 80 ++++++++++++------- .../__tests__/useGetTenantByDomain.test.tsx | 25 ++++++ .../src/hooks/tenant/useGetTenantByDomain.tsx | 31 +++++++ front/src/index.tsx | 37 ++++----- front/src/interfaces/tenant.interface.ts | 2 + .../navbar/__stories__/Navbar.stories.tsx | 7 +- infra/dev/docker-compose.yml | 1 + 13 files changed, 147 insertions(+), 56 deletions(-) create mode 100644 api/migrations/twenty/1675425693191_alter_table_public_tenants_add_column_auth0_client_id/down.sql create mode 100644 api/migrations/twenty/1675425693191_alter_table_public_tenants_add_column_auth0_client_id/up.sql create mode 100644 api/migrations/twenty/1675428346925_alter_table_public_tenants_add_column_domain/down.sql create mode 100644 api/migrations/twenty/1675428346925_alter_table_public_tenants_add_column_domain/up.sql create mode 100644 front/src/hooks/tenant/__tests__/useGetTenantByDomain.test.tsx create mode 100644 front/src/hooks/tenant/useGetTenantByDomain.tsx diff --git a/.circleci/config.yml b/.circleci/config.yml index 31a25f1f3..0f3410535 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -110,7 +110,10 @@ workflows: deploy-api: jobs: - - deploy-api-canary + - deploy-api-canary: + filters: + branches: + only: main - slack/on-hold: name: slack-notification context: slack-secrets diff --git a/api/metadata/databases/twenty/tables/public_tenants.yaml b/api/metadata/databases/twenty/tables/public_tenants.yaml index a9c8db72d..a239e789c 100644 --- a/api/metadata/databases/twenty/tables/public_tenants.yaml +++ b/api/metadata/databases/twenty/tables/public_tenants.yaml @@ -10,13 +10,21 @@ array_relationships: name: users schema: public select_permissions: + - role: public + permission: + columns: + - auth0_client_id + - domain + filter: {} + limit: 1 - role: user permission: columns: + - auth0_client_id + - email_domain - id - name - uuid - - email_domain filter: users: email: diff --git a/api/migrations/twenty/1675425693191_alter_table_public_tenants_add_column_auth0_client_id/down.sql b/api/migrations/twenty/1675425693191_alter_table_public_tenants_add_column_auth0_client_id/down.sql new file mode 100644 index 000000000..d9cdfcc79 --- /dev/null +++ b/api/migrations/twenty/1675425693191_alter_table_public_tenants_add_column_auth0_client_id/down.sql @@ -0,0 +1 @@ +alter table "public"."tenants" drop column "auth0_client_id"; diff --git a/api/migrations/twenty/1675425693191_alter_table_public_tenants_add_column_auth0_client_id/up.sql b/api/migrations/twenty/1675425693191_alter_table_public_tenants_add_column_auth0_client_id/up.sql new file mode 100644 index 000000000..da867c001 --- /dev/null +++ b/api/migrations/twenty/1675425693191_alter_table_public_tenants_add_column_auth0_client_id/up.sql @@ -0,0 +1 @@ +alter table "public"."tenants" add column "auth0_client_id" text null; diff --git a/api/migrations/twenty/1675428346925_alter_table_public_tenants_add_column_domain/down.sql b/api/migrations/twenty/1675428346925_alter_table_public_tenants_add_column_domain/down.sql new file mode 100644 index 000000000..61f33b11a --- /dev/null +++ b/api/migrations/twenty/1675428346925_alter_table_public_tenants_add_column_domain/down.sql @@ -0,0 +1 @@ +alter table "public"."tenants" drop column "domain"; \ No newline at end of file diff --git a/api/migrations/twenty/1675428346925_alter_table_public_tenants_add_column_domain/up.sql b/api/migrations/twenty/1675428346925_alter_table_public_tenants_add_column_domain/up.sql new file mode 100644 index 000000000..f421a8a7a --- /dev/null +++ b/api/migrations/twenty/1675428346925_alter_table_public_tenants_add_column_domain/up.sql @@ -0,0 +1,2 @@ +alter table "public"."tenants" add column "domain" text + null default 'pilot.twenty.com'; diff --git a/front/src/App.tsx b/front/src/App.tsx index c94aa3b76..3db1c99b5 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import { Auth0Provider } from '@auth0/auth0-react'; +import React, { useEffect } from 'react'; import Inbox from './pages/inbox/Inbox'; import Contacts from './pages/Contacts'; import Insights from './pages/Insights'; @@ -7,40 +8,59 @@ import AppLayout from './layout/AppLayout'; import RequireAuth from './components/RequireAuth'; import { Routes, Route } from 'react-router-dom'; import { useGetProfile } from './hooks/profile/useGetProfile'; +import { useGetTenantByDomain } from './hooks/tenant/useGetTenantByDomain'; function App() { + const { tenant } = useGetTenantByDomain(); const { user } = useGetProfile(); return ( - - - - - - } - /> - - - - } - /> - - - - } - /> - } /> - - +
+ {tenant && ( + + + + + + + } + /> + + + + } + /> + + + + } + /> + } /> + + + + )} +
); } diff --git a/front/src/hooks/tenant/__tests__/useGetTenantByDomain.test.tsx b/front/src/hooks/tenant/__tests__/useGetTenantByDomain.test.tsx new file mode 100644 index 000000000..61805bc40 --- /dev/null +++ b/front/src/hooks/tenant/__tests__/useGetTenantByDomain.test.tsx @@ -0,0 +1,25 @@ +import { renderHook } from '@testing-library/react'; +import { useQuery, QueryResult } from '@apollo/client'; +import { useGetTenantByDomain } from '../useGetTenantByDomain'; + +jest.mock('@apollo/client', () => ({ + useQuery: jest.fn(), +})); + +describe('useGetTenantByDomain', () => { + beforeEach(() => { + const result: Partial> = { + data: { tenants: [{ domain: 'pilot.twenty.com' }] }, + loading: false, + error: undefined, + }; + (useQuery as jest.Mock).mockImplementation(() => result as QueryResult); + }); + + it('returns tenant by domain', () => { + const { result } = renderHook(() => useGetTenantByDomain()); + const domain = result.current.tenant?.domain; + expect(domain).toEqual(result.current.tenant?.domain); + expect(useQuery).toHaveBeenCalledTimes(1); + }); +}); diff --git a/front/src/hooks/tenant/useGetTenantByDomain.tsx b/front/src/hooks/tenant/useGetTenantByDomain.tsx new file mode 100644 index 000000000..c3132c92c --- /dev/null +++ b/front/src/hooks/tenant/useGetTenantByDomain.tsx @@ -0,0 +1,31 @@ +import { ApolloError, useQuery } from '@apollo/client'; +import { gql } from 'graphql-tag'; +import { Tenant } from '../../interfaces/tenant.interface'; + +const GET_TENANT_BY_DOMAIN = gql` + query GetTenantByDomain($domain: String!) { + tenants(where: { domain: { _eq: $domain } }, limit: 1) { + auth0_client_id + domain + } + } +`; + +type TenantResult = { + loading: boolean; + error?: ApolloError; + tenant?: Tenant; +}; + +export const useGetTenantByDomain = (): TenantResult => { + const domain = window.location.hostname; + const { loading, error, data } = useQuery(GET_TENANT_BY_DOMAIN, { + variables: { domain }, + context: { + headers: { + 'x-hasura-default-role': 'public', + }, + }, + }); + return { loading, error, tenant: data?.tenants[0] }; +}; diff --git a/front/src/index.tsx b/front/src/index.tsx index 070a78d40..61333fb9b 100644 --- a/front/src/index.tsx +++ b/front/src/index.tsx @@ -3,7 +3,6 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import { BrowserRouter } from 'react-router-dom'; -import { Auth0Provider } from '@auth0/auth0-react'; import { ApolloClient, InMemoryCache, @@ -15,12 +14,17 @@ import { setContext } from '@apollo/client/link/context'; const httpLink = createHttpLink({ uri: process.env.REACT_APP_API_URL }); const authLink = setContext((_, { headers }) => { + const requestHeaders = { ...headers }; const token = localStorage.getItem('accessToken'); + const headerContainsPublicRole = + requestHeaders.hasOwnProperty('x-hasura-default-role') && + requestHeaders['x-hasura-default-role'] === 'public'; + if (!headerContainsPublicRole && token) { + requestHeaders['authorization'] = `Bearer ${token}`; + } + return { - headers: { - ...headers, - authorization: token ? `Bearer ${token}` : '', - }, + headers: requestHeaders, }; }); @@ -33,22 +37,9 @@ const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, ); root.render( - - - - - - - , + + + + + , ); diff --git a/front/src/interfaces/tenant.interface.ts b/front/src/interfaces/tenant.interface.ts index 857c1b211..5c5c9052c 100644 --- a/front/src/interfaces/tenant.interface.ts +++ b/front/src/interfaces/tenant.interface.ts @@ -1,4 +1,6 @@ export interface Tenant { id: number; name: string; + domain: string; + auth0_client_id: string; } diff --git a/front/src/layout/navbar/__stories__/Navbar.stories.tsx b/front/src/layout/navbar/__stories__/Navbar.stories.tsx index 234860bec..e6421340f 100644 --- a/front/src/layout/navbar/__stories__/Navbar.stories.tsx +++ b/front/src/layout/navbar/__stories__/Navbar.stories.tsx @@ -15,7 +15,12 @@ export const NavbarOnInsights = () => ( email: 'charles@twenty.com', first_name: 'Charles', last_name: 'Bochet', - tenant: { id: 1, name: 'Twenty' }, + tenant: { + id: 1, + name: 'Twenty', + domain: 'pilot.twenty.com', + auth0_client_id: 'auth0_client_id', + }, }} /> diff --git a/infra/dev/docker-compose.yml b/infra/dev/docker-compose.yml index 736d868e7..c5fc3aaf0 100644 --- a/infra/dev/docker-compose.yml +++ b/infra/dev/docker-compose.yml @@ -36,6 +36,7 @@ services: HASURA_GRAPHQL_PG_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/twenty HASURA_GRAPHQL_ENABLE_CONSOLE: "true" HASURA_GRAPHQL_DEV_MODE: "true" + HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "public" HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET} HASURA_GRAPHQL_JWT_SECRET: ${HASURA_GRAPHQL_JWT_SECRET} HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log