From 2e20d7b2140120f2c82a491d73aeb783ea917153 Mon Sep 17 00:00:00 2001 From: Anthony Rimet Date: Thu, 8 Aug 2024 13:46:35 +0200 Subject: [PATCH 1/5] Feat(auth): Handle redirection from supabase with HashRouter --- package-lock.json | 34 +++++++++---------- package.json | 2 +- .../index.html => auth-callback.html} | 2 +- src/providers/supabase/authProvider.ts | 26 -------------- supabase/config.toml | 2 +- supabase/templates/recovery.html | 2 +- 6 files changed, 21 insertions(+), 47 deletions(-) rename public/{auth-callback/index.html => auth-callback.html} (97%) diff --git a/package-lock.json b/package-lock.json index 2d882c6..3eb1fa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "papaparse": "^5.4.1", "prop-types": "^15.8.1", "ra-data-fakerest": "^5.1.1", - "ra-supabase": "^3.0.0", + "ra-supabase": "^3.0.1", "react": "^18.2.0", "react-admin": "^5.1.0", "react-cropper": "^2.3.3", @@ -10570,19 +10570,19 @@ } }, "node_modules/ra-supabase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ra-supabase/-/ra-supabase-3.0.0.tgz", - "integrity": "sha512-vBVPyPkPhlfuAJp5CnQseNioBcpqGjfz9MOuOekXsRRXA+5dv8nJfp9XnX7wytGU4oax+gRNqlXJMZJEtZDJXQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ra-supabase/-/ra-supabase-3.1.0.tgz", + "integrity": "sha512-T+OtQ4FTYcJqWUdgZ5rcLamHbU2Ae0BqMEuBLry9sXPUFOtsh26+zShFRe6mtkbJA2ydjfiYW01/n5SQrWP2eg==", "dependencies": { - "ra-supabase-core": "^3.0.0", - "ra-supabase-language-english": "^3.0.0", - "ra-supabase-ui-materialui": "^3.0.0" + "ra-supabase-core": "^3.1.0", + "ra-supabase-language-english": "^3.1.0", + "ra-supabase-ui-materialui": "^3.1.0" } }, "node_modules/ra-supabase-core": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ra-supabase-core/-/ra-supabase-core-3.0.0.tgz", - "integrity": "sha512-V6OHsgOE7icqH6ZstwOjny55mxPeSh2mmAdsUnuiYORk6mWAq8g7d5ba8pZHhowXKiUK3G0Pw9VEqMVIZ55y2g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ra-supabase-core/-/ra-supabase-core-3.1.0.tgz", + "integrity": "sha512-KagSKm+fJLkQTtNc7OtejktXMIysv80ZgOzJAM5nz+xyHNVhFb2GEbW+AvYI3uXejay/cQL8WvfQkczl/NeZ7Q==", "peerDependencies": { "@raphiniert/ra-data-postgrest": "^2.3.0", "@supabase/supabase-js": "^2.0.0", @@ -10590,16 +10590,16 @@ } }, "node_modules/ra-supabase-language-english": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ra-supabase-language-english/-/ra-supabase-language-english-3.0.0.tgz", - "integrity": "sha512-V1VJrUnAZWTys1s7Qv7xJx55mxDrgui7E22NtNPe+ET+TROsLs7xtDZmsWcmMDTOpqnzuqyPS5x/zmJDJ7/uxw==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ra-supabase-language-english/-/ra-supabase-language-english-3.1.0.tgz", + "integrity": "sha512-9/S8n6quJeJiOcFVLDZQNqXOMcGnlwmdZ+Xy4UFGcfdsGCuPr68CYxdG68WnP1jxyo7Hy7b/zvfr+Zu5rW8f+g==" }, "node_modules/ra-supabase-ui-materialui": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ra-supabase-ui-materialui/-/ra-supabase-ui-materialui-3.0.0.tgz", - "integrity": "sha512-BcZr4AeqCfSLpc+fUWaaKzw+F5xI3bp9nBmSuk+eoUW7c67uDqY0eSi7kO5HmiM6WoCnerfhrDrljAs4JKMavg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ra-supabase-ui-materialui/-/ra-supabase-ui-materialui-3.1.0.tgz", + "integrity": "sha512-F/6JfoBEwW4wAZ/0b8ccJ/EqjGR460+ermU1HuGxXXj5YCd9Gg6Oc+YDcKlSZ7F0bH16DI9Q8z08EXns6cvyQw==", "dependencies": { - "ra-supabase-core": "^3.0.0" + "ra-supabase-core": "^3.1.0" }, "peerDependencies": { "@mui/icons-material": "^5.0.0", diff --git a/package.json b/package.json index 0661a8c..5880b07 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "papaparse": "^5.4.1", "prop-types": "^15.8.1", "ra-data-fakerest": "^5.1.1", - "ra-supabase": "^3.0.0", + "ra-supabase": "^3.0.1", "react": "^18.2.0", "react-admin": "^5.1.0", "react-cropper": "^2.3.3", diff --git a/public/auth-callback/index.html b/public/auth-callback.html similarity index 97% rename from public/auth-callback/index.html rename to public/auth-callback.html index ef7d292..ab2d5f0 100644 --- a/public/auth-callback/index.html +++ b/public/auth-callback.html @@ -131,7 +131,7 @@ function interceptAuthCallback() { const hash = window.location.hash; const {access_token, refresh_token, type} = getUrlParams(); - window.location.href = `../?access_token=${access_token}&refresh_token=${refresh_token}&type=${type}#/auth-callback`; + window.location.href = `../#/auth-callback?access_token=${access_token}&refresh_token=${refresh_token}&type=${type}`; } // Call the function to intercept the auth callback diff --git a/src/providers/supabase/authProvider.ts b/src/providers/supabase/authProvider.ts index 2a26477..997365c 100644 --- a/src/providers/supabase/authProvider.ts +++ b/src/providers/supabase/authProvider.ts @@ -56,30 +56,4 @@ export const authProvider: AuthProvider = { return baseAuthProvider.checkAuth(params); }, - handleCallback: async () => { - const { access_token, refresh_token, type } = getUrlParams(); - // Users have reset their password or have just been invited and must set a new password - if (type === 'recovery' || type === 'invite') { - if (access_token && refresh_token) { - return { - redirectTo: `/set-password?access_token=${access_token}&refresh_token=${refresh_token}&type=${type}`, - }; - } - - if (process.env.NODE_ENV === 'development') { - console.error( - 'Missing access_token or refresh_token for an invite or recovery' - ); - } - } - }, -}; - -const getUrlParams = () => { - const urlSearchParams = new URLSearchParams(window.location.search); - const access_token = urlSearchParams.get('access_token'); - const refresh_token = urlSearchParams.get('refresh_token'); - const type = urlSearchParams.get('type'); - - return { access_token, refresh_token, type }; }; diff --git a/supabase/config.toml b/supabase/config.toml index df13d88..f061d99 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -71,7 +71,7 @@ enabled = true # in emails. site_url = "http://localhost:5173/" # A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://localhost:5173/auth-callback/index.html"] +additional_redirect_urls = ["https://localhost:5173/auth-callback.html"] # How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). jwt_expiry = 3600 # If disabled, the refresh token will never expire. diff --git a/supabase/templates/recovery.html b/supabase/templates/recovery.html index e021e56..e0ec103 100644 --- a/supabase/templates/recovery.html +++ b/supabase/templates/recovery.html @@ -61,7 +61,7 @@

Reset Password

Hello,

To reset your password, follow the link below:

Reset my password From db7f8dd1e758114b5e775cd59d960c766496f0cc Mon Sep 17 00:00:00 2001 From: Anthony Rimet Date: Thu, 8 Aug 2024 15:01:26 +0200 Subject: [PATCH 2/5] Feat(auth): Create invite email --- src/sales/SalesCreate.tsx | 10 +--- src/sales/SalesInputs.tsx | 4 +- supabase/config.toml | 7 ++- supabase/functions/users/index.ts | 4 +- supabase/templates/invite.html | 90 +++++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 supabase/templates/invite.html diff --git a/src/sales/SalesCreate.tsx b/src/sales/SalesCreate.tsx index b4d3c70..320efe0 100644 --- a/src/sales/SalesCreate.tsx +++ b/src/sales/SalesCreate.tsx @@ -1,8 +1,6 @@ import { Card, Container, Typography } from '@mui/material'; import { useMutation } from '@tanstack/react-query'; import { - PasswordInput, - required, SimpleForm, useDataProvider, useNotify, @@ -39,13 +37,7 @@ export function SalesCreate() { }> Create sale person - - - + diff --git a/src/sales/SalesInputs.tsx b/src/sales/SalesInputs.tsx index 0f6b70b..c0440bf 100644 --- a/src/sales/SalesInputs.tsx +++ b/src/sales/SalesInputs.tsx @@ -6,10 +6,9 @@ import { useRecordContext, } from 'react-admin'; import { Sale } from '../types'; -import { ReactNode } from 'react'; import { Stack } from '@mui/material'; -export function SalesInputs({ children }: { children?: ReactNode }) { +export function SalesInputs() { const { identity } = useGetIdentity(); const record = useRecordContext(); return ( @@ -29,7 +28,6 @@ export function SalesInputs({ children }: { children?: ReactNode }) { validate={required()} helperText={false} /> - {children} + + + + + +
+
+

Confirm your account

+
+
+

Welcome to Atomic CRM.

+

+ You've been invited to join the (Name) organization. Confirm + your address below: +

+ Confirm my account +

+ Warning: If the password reset request did + not originate from you, please contact us immediately as + this may be a fraudulent attempt. +

+

See you soon!

+

The Atomic CRM team

+
+ +
+ + From dc5742280962cf14cee6edc40d9e28ab42cbf28a Mon Sep 17 00:00:00 2001 From: Anthony Rimet Date: Thu, 8 Aug 2024 16:20:59 +0200 Subject: [PATCH 3/5] Fix: lint --- supabase/functions/users/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supabase/functions/users/index.ts b/supabase/functions/users/index.ts index 34d3116..e81dd64 100644 --- a/supabase/functions/users/index.ts +++ b/supabase/functions/users/index.ts @@ -38,10 +38,10 @@ async function inviteUser(req: Request) { user_metadata: { first_name, last_name }, }); - const { data: emailData, error: emailError } = + const { _, error: emailError } = await supabaseAdmin.auth.admin.inviteUserByEmail(email); - if (!data?.user || userError) { + if (!data?.user || userError || emailError) { console.error('Error inviting user:', userError); return createErrorResponse(500, 'Internal Server Error'); } From 43a351d45bfd1a3f1cb8b516df8be6106690a5ca Mon Sep 17 00:00:00 2001 From: Anthony Date: Thu, 8 Aug 2024 16:29:37 +0200 Subject: [PATCH 4/5] Update supabase/functions/users/index.ts Co-authored-by: Jonathan <125876152+jonathan-marmelab@users.noreply.github.com> --- supabase/functions/users/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supabase/functions/users/index.ts b/supabase/functions/users/index.ts index e81dd64..05a188c 100644 --- a/supabase/functions/users/index.ts +++ b/supabase/functions/users/index.ts @@ -38,7 +38,7 @@ async function inviteUser(req: Request) { user_metadata: { first_name, last_name }, }); - const { _, error: emailError } = + const { error: emailError } = await supabaseAdmin.auth.admin.inviteUserByEmail(email); if (!data?.user || userError || emailError) { From 0833bafede720341c1d24b1490b1d7faf39061c8 Mon Sep 17 00:00:00 2001 From: Anthony Rimet Date: Thu, 8 Aug 2024 17:27:32 +0200 Subject: [PATCH 5/5] Fix: Apply reviews --- README.md | 12 ++++++++---- package.json | 2 +- src/providers/fakerest/dataProvider.ts | 10 +++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 314d905..e980c3b 100644 --- a/README.md +++ b/README.md @@ -253,12 +253,12 @@ Create a new migration using the following command: npx supabase db diff | npx supabase migration new ``` -### Password Reset When Forgotten -If users forgot their password, they can request for a reset. Atomic CRM handle it for you. All the magic is done by the custom route `/forgot-password` and `/set-password` that you can find in the `CRM` component. +### Password Reset When Forgotten or Password change +If users forgot their password or want to update it, they can request for a reset. Atomic CRM handle it for you. All the magic is done by the custom route `/forgot-password` and `/set-password` that you can find in the `CRM` component. If you want to update default configuration: -### Local development +#### Local development 1. Go to your `config.toml` file 2. In `[auth]` section set `site_url` to your application URL @@ -283,7 +283,7 @@ In Recovery email template set the `auth-callback` redirection Supabase provides an [Inbucket](https://inbucket.org/) instance that allows you to test your emails and configure your flow. -### Production +#### Production This requires you to configure your supabase instance: @@ -291,3 +291,7 @@ This requires you to configure your supabase instance: 2. In URL Configuration, set Site URL to your application URL 3. In URL Configuration, add the following URL in the Redirect URLs section: `YOUR_APPLICATION_URL/auth-callback` 4. In Email Templates, change the `"{{ .ConfirmationURL }}"` to `"{{ .ConfirmationURL }}/auth-callback"` + +### Invite Users + +You can invite users to your CRM. The invitation is sent by email. The invited user will receive an email with a link to set a password. Do the same configuration as the password reset process but with the `invite` template. \ No newline at end of file diff --git a/package.json b/package.json index 5880b07..c304b0d 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "papaparse": "^5.4.1", "prop-types": "^15.8.1", "ra-data-fakerest": "^5.1.1", - "ra-supabase": "^3.0.1", + "ra-supabase": "^3.1.0", "react": "^18.2.0", "react-admin": "^5.1.0", "react-cropper": "^2.3.3", diff --git a/src/providers/fakerest/dataProvider.ts b/src/providers/fakerest/dataProvider.ts index e78b252..e1ab574 100644 --- a/src/providers/fakerest/dataProvider.ts +++ b/src/providers/fakerest/dataProvider.ts @@ -157,12 +157,12 @@ const dataProviderWithCustomMethod: CrmDataProvider = { password, }; }, - async salesCreate({ - password: _password, - ...data - }: SalesFormData): Promise { + async salesCreate({ ...data }: SalesFormData): Promise { const user = await dataProvider.create('sales', { - data, + data: { + ...data, + password: 'new_password', + }, }); return {