Skip to content

Commit

Permalink
Merge pull request #43 from marmelab/feat/remove-browserrouter
Browse files Browse the repository at this point in the history
Feat(auth): Handle resetPassword and Invite user
  • Loading branch information
arimet authored Aug 9, 2024
2 parents 3284c1c + 7b7c13a commit fb5d76f
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 73 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,9 @@ Create a new migration using the following command:
npx supabase db diff | npx supabase migration new <migration_name>
```

### Password Reset
### Password Reset And Invitations

If users forget their password, they can request for a reset. Atomic CRM handles it for you, using Supabase.
An user can be invited to the CRM by an administrator. The user will receive an email with a link to set their password. The password reset feature is also available. You don't have to worry about these processes, Atomic CRM handles them for you, using Supabase.

Please make sure to read and apply the [Login Callback](./doc/supabase-configuration.md#login-callback) and [Customizing Mail Template](./doc/supabase-configuration.md#customizing-mail-template) sections to properly configure the password reset feature.

Expand Down
34 changes: 17 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.1.0",
"react": "^18.2.0",
"react-admin": "^5.1.0",
"react-cropper": "^2.3.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/providers/fakerest/dataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,12 @@ const dataProviderWithCustomMethod: CrmDataProvider = {
password,
};
},
async salesCreate({
password: _password,
...data
}: SalesFormData): Promise<Sale> {
async salesCreate({ ...data }: SalesFormData): Promise<Sale> {
const user = await dataProvider.create('sales', {
data,
data: {
...data,
password: 'new_password',
},
});

return {
Expand Down
27 changes: 0 additions & 27 deletions src/providers/supabase/authProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,35 +68,8 @@ export const authProvider: AuthProvider = {

return baseAuthProvider.checkAuth(params);
},
// We need to ovveride the getPermissions here to avoid the `ra-supabase` retries
async getPermissions(params) {
const isInitialized = await getIsInitialized();
return isInitialized ? baseAuthProvider.getPermissions(params) : null;
},
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 };
};
10 changes: 1 addition & 9 deletions src/sales/SalesCreate.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Card, Container, Typography } from '@mui/material';
import { useMutation } from '@tanstack/react-query';
import {
PasswordInput,
required,
SimpleForm,
useDataProvider,
useNotify,
Expand Down Expand Up @@ -50,13 +48,7 @@ export function SalesCreate() {
<Card>
<SimpleForm onSubmit={onSubmit as SubmitHandler<any>}>
<Typography variant="h6">Create sale person</Typography>
<SalesInputs>
<PasswordInput
source="password"
validate={required()}
helperText={false}
/>
</SalesInputs>
<SalesInputs />
</SimpleForm>
</Card>
</Container>
Expand Down
4 changes: 1 addition & 3 deletions src/sales/SalesInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Sale>();
return (
Expand All @@ -29,7 +28,6 @@ export function SalesInputs({ children }: { children?: ReactNode }) {
validate={required()}
helperText={false}
/>
{children}
<BooleanInput
source="administrator"
readOnly={record?.id === identity?.id}
Expand Down
9 changes: 4 additions & 5 deletions supabase/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -91,10 +91,9 @@ double_confirm_changes = true
# If enabled, users need to confirm their email address before signing in.
enable_confirmations = false

# Uncomment to customize email template
# [auth.email.template.invite]
# subject = "You have been invited"
# content_path = "./supabase/templates/invite.html"
[auth.email.template.invite]
subject = "You've been invited to use Atomic CRM"
content_path = "./supabase/templates/invite.html"

[auth.email.template.recovery]
subject = "Reset Password"
Expand Down
6 changes: 4 additions & 2 deletions supabase/functions/users/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ async function inviteUser(req: Request) {
await supabaseAdmin.auth.admin.createUser({
email,
password,
email_confirm: true,
user_metadata: { first_name, last_name },
});

if (!data?.user || userError) {
const { error: emailError } =
await supabaseAdmin.auth.admin.inviteUserByEmail(email);

if (!data?.user || userError || emailError) {
console.error('Error inviting user:', userError);
return createErrorResponse(500, 'Internal Server Error');
}
Expand Down
90 changes: 90 additions & 0 deletions supabase/templates/invite.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!doctype html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.container {
width: 100%;
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
text-align: center;
padding: 10px 0;
}
.header h2 {
margin: 0;
color: #333333;
}
.content {
padding: 20px;
line-height: 1.6;
color: #555555;
}
.content p {
margin: 0 0 10px;
}
.button {
display: block;
width: 200px;
margin: 20px auto;
padding: 10px;
text-align: center;
background-color: #007bff;
color: #ffffff;
text-decoration: none;
border-radius: 5px;
}
.footer {
text-align: center;
padding: 10px 0;
font-size: 12px;
color: #777777;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>Confirm your account</h2>
</div>
<div class="content">
<p>Welcome to Atomic CRM.</p>
<p>
You've been invited to join the (Name) organization. Confirm
your address below:
</p>
<a
href="{{ .ConfirmationURL }}auth-callback.html"
class="button"
>Confirm my account</a
>
<p>
<strong>Warning:</strong> If the password reset request did
not originate from you, please contact us immediately as
this may be a fraudulent attempt.
</p>
<p>See you soon!</p>
<p>The Atomic CRM team</p>
</div>
<div class="footer">
<p>
If the activation link doesn’t work,
<a
href="mailto:support@react-admin-enterprise-edition.zendesk.com"
>contact us</a
>.
</p>
</div>
</div>
</body>
</html>
2 changes: 1 addition & 1 deletion supabase/templates/recovery.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ <h2>Reset Password</h2>
<p>Hello,</p>
<p>To reset your password, follow the link below:</p>
<a
href="{{ .ConfirmationURL }}auth-callback/index.html"
href="{{ .ConfirmationURL }}auth-callback.html"
class="button"
>Reset my password</a
>
Expand Down

0 comments on commit fb5d76f

Please sign in to comment.