Store refresh token on login

This commit is contained in:
Charles Bochet 2023-04-20 18:46:27 +02:00
parent fe10c0ba7d
commit cd18e952b9
21 changed files with 484 additions and 2 deletions

View File

@ -13,7 +13,7 @@ Twenty development stack is composed of 3 different layers
## Setup env variables and npmrc variables ## 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 2. `cp ./front/.npmrc.example ./front/.npmrc` and fill with values
## Development environment setup with docker-compose (Recommended) ## Development environment setup with docker-compose (Recommended)

View File

@ -1 +0,0 @@
REACT_APP_API_URL=http://localhost:8080

View File

@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import People from './pages/people/People'; import People from './pages/people/People';
import Companies from './pages/companies/Companies'; import Companies from './pages/companies/Companies';
import AuthCallback from './pages/auth/Callback';
import Login from './pages/auth/Login';
import AppLayout from './layout/AppLayout'; import AppLayout from './layout/AppLayout';
import { Routes, Route, Navigate } from 'react-router-dom'; import { Routes, Route, Navigate } from 'react-router-dom';
@ -26,6 +28,8 @@ function App() {
<Route path="/" element={<Navigate to="/people" replace />} /> <Route path="/" element={<Navigate to="/people" replace />} />
<Route path="/people" element={<People />} /> <Route path="/people" element={<People />} />
<Route path="/companies" element={<Companies />} /> <Route path="/companies" element={<Companies />} />
<Route path="/auth/callback" element={<AuthCallback />} />
<Route path="/auth/login" element={<Login />} />
</Routes> </Routes>
</AppLayout> </AppLayout>
} }

View File

@ -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;

View File

@ -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;

View File

@ -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 = () => (
<ThemeProvider theme={lightTheme}>
<MemoryRouter>
<Callback />
</MemoryRouter>
</ThemeProvider>
);

View File

@ -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 = () => (
<ThemeProvider theme={lightTheme}>
<MemoryRouter>
<Login />
</MemoryRouter>
</ThemeProvider>
);

View File

@ -0,0 +1,7 @@
import { render } from '@testing-library/react';
import { CallbackDefault } from '../__stories__/Callback.stories';
it('Checks the Callback page render', () => {
render(<CallbackDefault />);
});

View File

@ -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(<LoginDefault />);
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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_company.yaml"
- "!include public_person.yaml" - "!include public_person.yaml"
- "!include public_workspaces.yaml" - "!include public_workspaces.yaml"

6
infra/dev/.env.example Normal file
View File

@ -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

View File

@ -7,6 +7,9 @@ services:
ports: ports:
- "3001:3001" - "3001:3001"
- "6006:6006" - "6006:6006"
environment:
- REACT_APP_API_URL=${FRONT_REACT_APP_API_URL}
- REACT_APP_LOGIN_PROVIDER_URL=${FRONT_REACT_APP_LOGIN_PROVIDER_URL}
volumes: volumes:
- ../../front:/app/front - ../../front:/app/front
- twenty_node_modules_front:/app/front/node_modules - twenty_node_modules_front:/app/front/node_modules
@ -44,6 +47,12 @@ services:
npm_package_version: '0' npm_package_version: '0'
AUTH_SMTP_HOST: mailhog AUTH_SMTP_HOST: mailhog
AUTH_SMTP_PORT: 1025 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: depends_on:
- "twenty-hasura" - "twenty-hasura"
- "postgres" - "postgres"

View File

@ -2,6 +2,7 @@ FROM node:18.16.0-alpine as front
ARG FONTAWESOME_NPM_AUTH_TOKEN ARG FONTAWESOME_NPM_AUTH_TOKEN
ARG REACT_APP_API_URL ARG REACT_APP_API_URL
ARG REACT_APP_LOGIN_PROVIDER_URL
WORKDIR /app/front WORKDIR /app/front
COPY ./front . COPY ./front .