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 .