diff --git a/README.md b/README.md
index 4db3360d44..d3dfca1c65 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ Twenty development stack is composed of 3 different layers
## Setup env variables and npmrc variables
-1. `cp ./front/.env.example ./front/.env` and fill with values
+1. `cp ./infra/dev/.env.example ./infra/dev/.env` and fill with values
2. `cp ./front/.npmrc.example ./front/.npmrc` and fill with values
## Development environment setup with docker-compose (Recommended)
diff --git a/front/.env.example b/front/.env.example
deleted file mode 100644
index deb105c8c6..0000000000
--- a/front/.env.example
+++ /dev/null
@@ -1 +0,0 @@
-REACT_APP_API_URL=http://localhost:8080
\ No newline at end of file
diff --git a/front/src/App.tsx b/front/src/App.tsx
index a33e58148e..999a3be734 100644
--- a/front/src/App.tsx
+++ b/front/src/App.tsx
@@ -1,6 +1,8 @@
import React from 'react';
import People from './pages/people/People';
import Companies from './pages/companies/Companies';
+import AuthCallback from './pages/auth/Callback';
+import Login from './pages/auth/Login';
import AppLayout from './layout/AppLayout';
import { Routes, Route, Navigate } from 'react-router-dom';
@@ -26,6 +28,8 @@ function App() {
} />
} />
} />
+ } />
+ } />
}
diff --git a/front/src/pages/auth/Callback.tsx b/front/src/pages/auth/Callback.tsx
new file mode 100644
index 0000000000..f30c8e4e81
--- /dev/null
+++ b/front/src/pages/auth/Callback.tsx
@@ -0,0 +1,17 @@
+import { useEffect } from 'react';
+import { useSearchParams, useNavigate } from 'react-router-dom';
+
+function Callback() {
+ const [searchParams] = useSearchParams();
+ const refreshToken = searchParams.get('refreshToken');
+ localStorage.setItem('refreshToken', refreshToken || '');
+
+ const navigate = useNavigate();
+ useEffect(() => {
+ navigate('/');
+ }, [navigate]);
+
+ return <>>;
+}
+
+export default Callback;
diff --git a/front/src/pages/auth/Login.tsx b/front/src/pages/auth/Login.tsx
new file mode 100644
index 0000000000..b57c12efb8
--- /dev/null
+++ b/front/src/pages/auth/Login.tsx
@@ -0,0 +1,17 @@
+import { useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+function Login() {
+ const refreshToken = localStorage.getItem('refreshToken');
+ const navigate = useNavigate();
+ useEffect(() => {
+ if (!refreshToken) {
+ window.location.href = process.env.REACT_APP_LOGIN_PROVIDER_URL || '';
+ }
+ navigate('/');
+ }, [refreshToken, navigate]);
+
+ return <>>;
+}
+
+export default Login;
diff --git a/front/src/pages/auth/__stories__/Callback.stories.tsx b/front/src/pages/auth/__stories__/Callback.stories.tsx
new file mode 100644
index 0000000000..74f3100f25
--- /dev/null
+++ b/front/src/pages/auth/__stories__/Callback.stories.tsx
@@ -0,0 +1,19 @@
+import { MemoryRouter } from 'react-router-dom';
+import Callback from '../Callback';
+import { ThemeProvider } from '@emotion/react';
+import { lightTheme } from '../../../layout/styles/themes';
+
+const component = {
+ title: 'Callback',
+ component: Callback,
+};
+
+export default component;
+
+export const CallbackDefault = () => (
+
+
+
+
+
+);
diff --git a/front/src/pages/auth/__stories__/Login.stories.tsx b/front/src/pages/auth/__stories__/Login.stories.tsx
new file mode 100644
index 0000000000..5631d9e8bf
--- /dev/null
+++ b/front/src/pages/auth/__stories__/Login.stories.tsx
@@ -0,0 +1,19 @@
+import { MemoryRouter } from 'react-router-dom';
+import Login from '../Login';
+import { ThemeProvider } from '@emotion/react';
+import { lightTheme } from '../../../layout/styles/themes';
+
+const component = {
+ title: 'Login',
+ component: Login,
+};
+
+export default component;
+
+export const LoginDefault = () => (
+
+
+
+
+
+);
diff --git a/front/src/pages/auth/__tests__/Callback.test.tsx b/front/src/pages/auth/__tests__/Callback.test.tsx
new file mode 100644
index 0000000000..3b22994e18
--- /dev/null
+++ b/front/src/pages/auth/__tests__/Callback.test.tsx
@@ -0,0 +1,7 @@
+import { render } from '@testing-library/react';
+
+import { CallbackDefault } from '../__stories__/Callback.stories';
+
+it('Checks the Callback page render', () => {
+ render();
+});
diff --git a/front/src/pages/auth/__tests__/Login.test.tsx b/front/src/pages/auth/__tests__/Login.test.tsx
new file mode 100644
index 0000000000..d66ae59ade
--- /dev/null
+++ b/front/src/pages/auth/__tests__/Login.test.tsx
@@ -0,0 +1,15 @@
+import { render } from '@testing-library/react';
+import { LoginDefault } from '../__stories__/Login.stories';
+
+const assignMock = jest.fn();
+
+delete window.location;
+window.location = { assign: assignMock };
+
+afterEach(() => {
+ assignMock.mockClear();
+});
+
+it('Checks the Login page render', () => {
+ render();
+});
diff --git a/hasura/metadata/databases/default/tables/auth_provider_requests.yaml b/hasura/metadata/databases/default/tables/auth_provider_requests.yaml
new file mode 100644
index 0000000000..91ba1967d3
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/auth_provider_requests.yaml
@@ -0,0 +1,23 @@
+table:
+ name: provider_requests
+ schema: auth
+configuration:
+ column_config:
+ id:
+ custom_name: id
+ options:
+ custom_name: options
+ custom_column_names:
+ id: id
+ options: options
+ custom_name: authProviderRequests
+ custom_root_fields:
+ delete: deleteAuthProviderRequests
+ delete_by_pk: deleteAuthProviderRequest
+ insert: insertAuthProviderRequests
+ insert_one: insertAuthProviderRequest
+ select: authProviderRequests
+ select_aggregate: authProviderRequestsAggregate
+ select_by_pk: authProviderRequest
+ update: updateAuthProviderRequests
+ update_by_pk: updateAuthProviderRequest
diff --git a/hasura/metadata/databases/default/tables/auth_providers.yaml b/hasura/metadata/databases/default/tables/auth_providers.yaml
new file mode 100644
index 0000000000..3de528dc92
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/auth_providers.yaml
@@ -0,0 +1,28 @@
+table:
+ name: providers
+ schema: auth
+configuration:
+ column_config:
+ id:
+ custom_name: id
+ custom_column_names:
+ id: id
+ custom_name: authProviders
+ custom_root_fields:
+ delete: deleteAuthProviders
+ delete_by_pk: deleteAuthProvider
+ insert: insertAuthProviders
+ insert_one: insertAuthProvider
+ select: authProviders
+ select_aggregate: authProvidersAggregate
+ select_by_pk: authProvider
+ update: updateAuthProviders
+ update_by_pk: updateAuthProvider
+array_relationships:
+ - name: userProviders
+ using:
+ foreign_key_constraint_on:
+ column: provider_id
+ table:
+ name: user_providers
+ schema: auth
diff --git a/hasura/metadata/databases/default/tables/auth_refresh_tokens.yaml b/hasura/metadata/databases/default/tables/auth_refresh_tokens.yaml
new file mode 100644
index 0000000000..e9152dfb35
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/auth_refresh_tokens.yaml
@@ -0,0 +1,36 @@
+table:
+ name: refresh_tokens
+ schema: auth
+configuration:
+ column_config:
+ created_at:
+ custom_name: createdAt
+ expires_at:
+ custom_name: expiresAt
+ refresh_token:
+ custom_name: refreshToken
+ refresh_token_hash:
+ custom_name: refreshTokenHash
+ user_id:
+ custom_name: userId
+ custom_column_names:
+ created_at: createdAt
+ expires_at: expiresAt
+ refresh_token: refreshToken
+ refresh_token_hash: refreshTokenHash
+ user_id: userId
+ custom_name: authRefreshTokens
+ custom_root_fields:
+ delete: deleteAuthRefreshTokens
+ delete_by_pk: deleteAuthRefreshToken
+ insert: insertAuthRefreshTokens
+ insert_one: insertAuthRefreshToken
+ select: authRefreshTokens
+ select_aggregate: authRefreshTokensAggregate
+ select_by_pk: authRefreshToken
+ update: updateAuthRefreshTokens
+ update_by_pk: updateAuthRefreshToken
+object_relationships:
+ - name: user
+ using:
+ foreign_key_constraint_on: user_id
diff --git a/hasura/metadata/databases/default/tables/auth_roles.yaml b/hasura/metadata/databases/default/tables/auth_roles.yaml
new file mode 100644
index 0000000000..bc6d5a2cfb
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/auth_roles.yaml
@@ -0,0 +1,35 @@
+table:
+ name: roles
+ schema: auth
+configuration:
+ column_config:
+ role:
+ custom_name: role
+ custom_column_names:
+ role: role
+ custom_name: authRoles
+ custom_root_fields:
+ delete: deleteAuthRoles
+ delete_by_pk: deleteAuthRole
+ insert: insertAuthRoles
+ insert_one: insertAuthRole
+ select: authRoles
+ select_aggregate: authRolesAggregate
+ select_by_pk: authRole
+ update: updateAuthRoles
+ update_by_pk: updateAuthRole
+array_relationships:
+ - name: userRoles
+ using:
+ foreign_key_constraint_on:
+ column: role
+ table:
+ name: user_roles
+ schema: auth
+ - name: usersByDefaultRole
+ using:
+ foreign_key_constraint_on:
+ column: default_role
+ table:
+ name: users
+ schema: auth
diff --git a/hasura/metadata/databases/default/tables/auth_user_providers.yaml b/hasura/metadata/databases/default/tables/auth_user_providers.yaml
new file mode 100644
index 0000000000..02b8eb2da7
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/auth_user_providers.yaml
@@ -0,0 +1,48 @@
+table:
+ name: user_providers
+ schema: auth
+configuration:
+ column_config:
+ access_token:
+ custom_name: accessToken
+ created_at:
+ custom_name: createdAt
+ id:
+ custom_name: id
+ provider_id:
+ custom_name: providerId
+ provider_user_id:
+ custom_name: providerUserId
+ refresh_token:
+ custom_name: refreshToken
+ updated_at:
+ custom_name: updatedAt
+ user_id:
+ custom_name: userId
+ custom_column_names:
+ access_token: accessToken
+ created_at: createdAt
+ id: id
+ provider_id: providerId
+ provider_user_id: providerUserId
+ refresh_token: refreshToken
+ updated_at: updatedAt
+ user_id: userId
+ custom_name: authUserProviders
+ custom_root_fields:
+ delete: deleteAuthUserProviders
+ delete_by_pk: deleteAuthUserProvider
+ insert: insertAuthUserProviders
+ insert_one: insertAuthUserProvider
+ select: authUserProviders
+ select_aggregate: authUserProvidersAggregate
+ select_by_pk: authUserProvider
+ update: updateAuthUserProviders
+ update_by_pk: updateAuthUserProvider
+object_relationships:
+ - name: provider
+ using:
+ foreign_key_constraint_on: provider_id
+ - name: user
+ using:
+ foreign_key_constraint_on: user_id
diff --git a/hasura/metadata/databases/default/tables/auth_user_roles.yaml b/hasura/metadata/databases/default/tables/auth_user_roles.yaml
new file mode 100644
index 0000000000..f90553941e
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/auth_user_roles.yaml
@@ -0,0 +1,36 @@
+table:
+ name: user_roles
+ schema: auth
+configuration:
+ column_config:
+ created_at:
+ custom_name: createdAt
+ id:
+ custom_name: id
+ role:
+ custom_name: role
+ user_id:
+ custom_name: userId
+ custom_column_names:
+ created_at: createdAt
+ id: id
+ role: role
+ user_id: userId
+ custom_name: authUserRoles
+ custom_root_fields:
+ delete: deleteAuthUserRoles
+ delete_by_pk: deleteAuthUserRole
+ insert: insertAuthUserRoles
+ insert_one: insertAuthUserRole
+ select: authUserRoles
+ select_aggregate: authUserRolesAggregate
+ select_by_pk: authUserRole
+ update: updateAuthUserRoles
+ update_by_pk: updateAuthUserRole
+object_relationships:
+ - name: roleByRole
+ using:
+ foreign_key_constraint_on: role
+ - name: user
+ using:
+ foreign_key_constraint_on: user_id
diff --git a/hasura/metadata/databases/default/tables/auth_user_security_keys.yaml b/hasura/metadata/databases/default/tables/auth_user_security_keys.yaml
new file mode 100644
index 0000000000..9963b62342
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/auth_user_security_keys.yaml
@@ -0,0 +1,33 @@
+table:
+ name: user_security_keys
+ schema: auth
+configuration:
+ column_config:
+ credential_id:
+ custom_name: credentialId
+ credential_public_key:
+ custom_name: credentialPublicKey
+ id:
+ custom_name: id
+ user_id:
+ custom_name: userId
+ custom_column_names:
+ credential_id: credentialId
+ credential_public_key: credentialPublicKey
+ id: id
+ user_id: userId
+ custom_name: authUserSecurityKeys
+ custom_root_fields:
+ delete: deleteAuthUserSecurityKeys
+ delete_by_pk: deleteAuthUserSecurityKey
+ insert: insertAuthUserSecurityKeys
+ insert_one: insertAuthUserSecurityKey
+ select: authUserSecurityKeys
+ select_aggregate: authUserSecurityKeysAggregate
+ select_by_pk: authUserSecurityKey
+ update: updateAuthUserSecurityKeys
+ update_by_pk: updateAuthUserSecurityKey
+object_relationships:
+ - name: user
+ using:
+ foreign_key_constraint_on: user_id
diff --git a/hasura/metadata/databases/default/tables/auth_users.yaml b/hasura/metadata/databases/default/tables/auth_users.yaml
new file mode 100644
index 0000000000..730001730a
--- /dev/null
+++ b/hasura/metadata/databases/default/tables/auth_users.yaml
@@ -0,0 +1,122 @@
+table:
+ name: users
+ schema: auth
+configuration:
+ column_config:
+ active_mfa_type:
+ custom_name: activeMfaType
+ avatar_url:
+ custom_name: avatarUrl
+ created_at:
+ custom_name: createdAt
+ default_role:
+ custom_name: defaultRole
+ disabled:
+ custom_name: disabled
+ display_name:
+ custom_name: displayName
+ email:
+ custom_name: email
+ email_verified:
+ custom_name: emailVerified
+ id:
+ custom_name: id
+ is_anonymous:
+ custom_name: isAnonymous
+ last_seen:
+ custom_name: lastSeen
+ locale:
+ custom_name: locale
+ new_email:
+ custom_name: newEmail
+ otp_hash:
+ custom_name: otpHash
+ otp_hash_expires_at:
+ custom_name: otpHashExpiresAt
+ otp_method_last_used:
+ custom_name: otpMethodLastUsed
+ password_hash:
+ custom_name: passwordHash
+ phone_number:
+ custom_name: phoneNumber
+ phone_number_verified:
+ custom_name: phoneNumberVerified
+ ticket:
+ custom_name: ticket
+ ticket_expires_at:
+ custom_name: ticketExpiresAt
+ totp_secret:
+ custom_name: totpSecret
+ updated_at:
+ custom_name: updatedAt
+ webauthn_current_challenge:
+ custom_name: currentChallenge
+ custom_column_names:
+ active_mfa_type: activeMfaType
+ avatar_url: avatarUrl
+ created_at: createdAt
+ default_role: defaultRole
+ disabled: disabled
+ display_name: displayName
+ email: email
+ email_verified: emailVerified
+ id: id
+ is_anonymous: isAnonymous
+ last_seen: lastSeen
+ locale: locale
+ new_email: newEmail
+ otp_hash: otpHash
+ otp_hash_expires_at: otpHashExpiresAt
+ otp_method_last_used: otpMethodLastUsed
+ password_hash: passwordHash
+ phone_number: phoneNumber
+ phone_number_verified: phoneNumberVerified
+ ticket: ticket
+ ticket_expires_at: ticketExpiresAt
+ totp_secret: totpSecret
+ updated_at: updatedAt
+ webauthn_current_challenge: currentChallenge
+ custom_name: users
+ custom_root_fields:
+ delete: deleteUsers
+ delete_by_pk: deleteUser
+ insert: insertUsers
+ insert_one: insertUser
+ select: users
+ select_aggregate: usersAggregate
+ select_by_pk: user
+ update: updateUsers
+ update_by_pk: updateUser
+object_relationships:
+ - name: defaultRoleByRole
+ using:
+ foreign_key_constraint_on: default_role
+array_relationships:
+ - name: refreshTokens
+ using:
+ foreign_key_constraint_on:
+ column: user_id
+ table:
+ name: refresh_tokens
+ schema: auth
+ - name: roles
+ using:
+ foreign_key_constraint_on:
+ column: user_id
+ table:
+ name: user_roles
+ schema: auth
+ - name: securityKeys
+ using:
+ foreign_key_constraint_on:
+ column: user_id
+ table:
+ name: user_security_keys
+ schema: auth
+ - name: userProviders
+ using:
+ foreign_key_constraint_on:
+ column: user_id
+ table:
+ name: user_providers
+ schema: auth
diff --git a/hasura/metadata/databases/default/tables/tables.yaml b/hasura/metadata/databases/default/tables/tables.yaml
index 7a5556d642..eb72915903 100644
--- a/hasura/metadata/databases/default/tables/tables.yaml
+++ b/hasura/metadata/databases/default/tables/tables.yaml
@@ -1,3 +1,11 @@
+- "!include auth_provider_requests.yaml"
+- "!include auth_providers.yaml"
+- "!include auth_refresh_tokens.yaml"
+- "!include auth_roles.yaml"
+- "!include auth_user_providers.yaml"
+- "!include auth_user_roles.yaml"
+- "!include auth_user_security_keys.yaml"
+- "!include auth_users.yaml"
- "!include public_company.yaml"
- "!include public_person.yaml"
- "!include public_workspaces.yaml"
diff --git a/infra/dev/.env.example b/infra/dev/.env.example
new file mode 100644
index 0000000000..a162afdc8c
--- /dev/null
+++ b/infra/dev/.env.example
@@ -0,0 +1,6 @@
+HASURA_AUTH_SERVER_URL: http://localhost:4000
+HASURA_AUTH_CLIENT_URL: http://localhost:3001/auth/callback
+HASURA_AUTH_PROVIDER_GOOGLE_CLIENT_ID: REPLACE_ME
+HASURA_AUTH_PROVIDER_GOOGLE_CLIENT_SECRET: REPLACE_ME
+FRONT_REACT_APP_API_URL=http://localhost:8080
+FRONT_REACT_APP_LOGIN_PROVIDER_URL=http://localhost:4000/signin/provider/google
\ No newline at end of file
diff --git a/infra/dev/docker-compose.yml b/infra/dev/docker-compose.yml
index bd49883d47..9155f074fd 100644
--- a/infra/dev/docker-compose.yml
+++ b/infra/dev/docker-compose.yml
@@ -7,6 +7,9 @@ services:
ports:
- "3001:3001"
- "6006:6006"
+ environment:
+ - REACT_APP_API_URL=${FRONT_REACT_APP_API_URL}
+ - REACT_APP_LOGIN_PROVIDER_URL=${FRONT_REACT_APP_LOGIN_PROVIDER_URL}
volumes:
- ../../front:/app/front
- twenty_node_modules_front:/app/front/node_modules
@@ -44,6 +47,12 @@ services:
npm_package_version: '0'
AUTH_SMTP_HOST: mailhog
AUTH_SMTP_PORT: 1025
+ AUTH_SERVER_URL: ${HASURA_AUTH_SERVER_URL}
+ AUTH_CLIENT_URL: ${HASURA_AUTH_CLIENT_URL}
+ AUTH_PROVIDER_GOOGLE_ENABLED: true
+ AUTH_PROVIDER_GOOGLE_CLIENT_ID: ${HASURA_AUTH_PROVIDER_GOOGLE_CLIENT_ID}
+ AUTH_PROVIDER_GOOGLE_CLIENT_SECRET: ${HASURA_AUTH_PROVIDER_GOOGLE_CLIENT_SECRET}
+ AUTH_PROVIDER_GOOGLE_SCOPE: email,profile
depends_on:
- "twenty-hasura"
- "postgres"
diff --git a/infra/prod/front/Dockerfile b/infra/prod/front/Dockerfile
index 526f876394..6624a0342c 100644
--- a/infra/prod/front/Dockerfile
+++ b/infra/prod/front/Dockerfile
@@ -2,6 +2,7 @@ FROM node:18.16.0-alpine as front
ARG FONTAWESOME_NPM_AUTH_TOKEN
ARG REACT_APP_API_URL
+ARG REACT_APP_LOGIN_PROVIDER_URL
WORKDIR /app/front
COPY ./front .