Separate auth0 users depending on tenants
This commit is contained in:
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -0,0 +1 @@
|
||||
alter table "public"."tenants" drop column "auth0_client_id";
|
||||
@ -0,0 +1 @@
|
||||
alter table "public"."tenants" add column "auth0_client_id" text null;
|
||||
@ -0,0 +1 @@
|
||||
alter table "public"."tenants" drop column "domain";
|
||||
@ -0,0 +1,2 @@
|
||||
alter table "public"."tenants" add column "domain" text
|
||||
null default 'pilot.twenty.com';
|
||||
@ -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 (
|
||||
<AppLayout user={user}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Inbox />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/contacts"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Contacts />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/insights"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Insights />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||
</Routes>
|
||||
</AppLayout>
|
||||
<div>
|
||||
{tenant && (
|
||||
<Auth0Provider
|
||||
domain={process.env.REACT_APP_AUTH0_DOMAIN || ''}
|
||||
clientId={tenant?.auth0_client_id || ''}
|
||||
authorizationParams={{
|
||||
redirect_uri:
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
process.env.REACT_APP_AUTH0_CALLBACK_URL || '',
|
||||
audience: process.env.REACT_APP_AUTH0_AUDIENCE || '',
|
||||
}}
|
||||
>
|
||||
<AppLayout user={user}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Inbox />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/contacts"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Contacts />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/insights"
|
||||
element={
|
||||
<RequireAuth>
|
||||
<Insights />
|
||||
</RequireAuth>
|
||||
}
|
||||
/>
|
||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||
</Routes>
|
||||
</AppLayout>
|
||||
</Auth0Provider>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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<QueryResult<any>> = {
|
||||
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);
|
||||
});
|
||||
});
|
||||
31
front/src/hooks/tenant/useGetTenantByDomain.tsx
Normal file
31
front/src/hooks/tenant/useGetTenantByDomain.tsx
Normal file
@ -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] };
|
||||
};
|
||||
@ -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(
|
||||
<Auth0Provider
|
||||
domain={process.env.REACT_APP_AUTH0_DOMAIN || ''}
|
||||
clientId={process.env.REACT_APP_AUTH0_CLIENT_ID || ''}
|
||||
authorizationParams={{
|
||||
redirect_uri:
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
process.env.REACT_APP_AUTH0_CALLBACK_URL || '',
|
||||
audience: process.env.REACT_APP_AUTH0_AUDIENCE || '',
|
||||
}}
|
||||
>
|
||||
<ApolloProvider client={client}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ApolloProvider>
|
||||
</Auth0Provider>,
|
||||
<ApolloProvider client={client}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ApolloProvider>,
|
||||
);
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export interface Tenant {
|
||||
id: number;
|
||||
name: string;
|
||||
domain: string;
|
||||
auth0_client_id: string;
|
||||
}
|
||||
|
||||
@ -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',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user