password recovery flow
This commit is contained in:
13
README.md
13
README.md
@@ -20,7 +20,7 @@ Demo site [here](https://nuxt3-saas-boilerplate.netlify.app/)
|
|||||||
### User Management
|
### User Management
|
||||||
- [x] Social Signon (e.g. google) via Supabase, Full list of available [providers](https://supabase.com/docs/guides/auth#providers)
|
- [x] Social Signon (e.g. google) via Supabase, Full list of available [providers](https://supabase.com/docs/guides/auth#providers)
|
||||||
- [x] Email/Password Signon via Supabase
|
- [x] Email/Password Signon via Supabase
|
||||||
- [ ] Password recovery
|
- [x] Password recovery
|
||||||
- [x] User roles and permissions (admin, regular user, etc. roles defined in the [Prisma Schema](/prisma/schema.prisma))
|
- [x] User roles and permissions (admin, regular user, etc. roles defined in the [Prisma Schema](/prisma/schema.prisma))
|
||||||
- [x] User Email captured on initial login
|
- [x] User Email captured on initial login
|
||||||
- [x] Initial plan and plan period controled via config to allow either a trial plan or a 'No Plan' for initial users
|
- [x] Initial plan and plan period controled via config to allow either a trial plan or a 'No Plan' for initial users
|
||||||
@@ -69,6 +69,8 @@ Demo site [here](https://nuxt3-saas-boilerplate.netlify.app/)
|
|||||||
### Look and Feel, Design System and Customisation
|
### Look and Feel, Design System and Customisation
|
||||||
- [x] Default UI isn't too crap
|
- [x] Default UI isn't too crap
|
||||||
- [x] Integrated Design system including theming (Tailwind + daisyUI)
|
- [x] Integrated Design system including theming (Tailwind + daisyUI)
|
||||||
|
- [ ] Toasts for things like reset email sent
|
||||||
|
- [ ] Modals, just because people like modals
|
||||||
|
|
||||||
### Demo Software (Notes)
|
### Demo Software (Notes)
|
||||||
- [x] Simple Text based Notes functionality
|
- [x] Simple Text based Notes functionality
|
||||||
@@ -99,7 +101,8 @@ This solution uses Supabase for Auth and to provide a DB. In addition to Magic
|
|||||||
|
|
||||||
1) Go to [Supabase](https://supabase.com/) and 'Start your Project'
|
1) Go to [Supabase](https://supabase.com/) and 'Start your Project'
|
||||||
2) Setup your org and project (Free tier is fine to start)
|
2) Setup your org and project (Free tier is fine to start)
|
||||||
3) Update the project's email template
|
3) Update the project's email template (Supabase -> Authentication -> Email Templates)
|
||||||
|
Note that the default Supabase email templates are very generic and for some reason this can lead to your emails being sent to spam folders. e.g. I to get my password reset emails to go to inbox, I needed to change the subject to "Password Reset for Nuxt 3 SAAS Boilerplate" and also the email body text.
|
||||||
4) Choose an OAuth provider. I have chosen Google using these [Instructions](https://supabase.com/docs/guides/auth/social-login/auth-google) for the purposes of demonstration but they all should work.
|
4) Choose an OAuth provider. I have chosen Google using these [Instructions](https://supabase.com/docs/guides/auth/social-login/auth-google) for the purposes of demonstration but they all should work.
|
||||||
5) Go to Project Settings -> API and copy Project URL and Project API Key to SUPABASE_URL and SUPABASE_KEY settings respectively in your [.env](/.env) file
|
5) Go to Project Settings -> API and copy Project URL and Project API Key to SUPABASE_URL and SUPABASE_KEY settings respectively in your [.env](/.env) file
|
||||||
6) Go to Project Settings -> Database -> Connection String -> URI and copy the uri value into the DATABASE_URL setting in your [.env](/.env) file, remembering to replace ```[YOUR-PASSWORD]``` with the password you provided when you setup the project.
|
6) Go to Project Settings -> Database -> Connection String -> URI and copy the uri value into the DATABASE_URL setting in your [.env](/.env) file, remembering to replace ```[YOUR-PASSWORD]``` with the password you provided when you setup the project.
|
||||||
@@ -192,9 +195,13 @@ Steps (Assumes your repo is in github)
|
|||||||
5) Setup environment variables per the .env_example file (SUPABASE_URL, SUPABASE_KEY....etc)
|
5) Setup environment variables per the .env_example file (SUPABASE_URL, SUPABASE_KEY....etc)
|
||||||
6) Optionally change site name (e.g. mycoolsaas) or apply a domain name
|
6) Optionally change site name (e.g. mycoolsaas) or apply a domain name
|
||||||
|
|
||||||
7) Go to (Supabase)[https://app.supabase.com/]
|
7) Go to [Supabase](https://app.supabase.com/)
|
||||||
8) Choose your project
|
8) Choose your project
|
||||||
9) Go to URL Authentication -> URL Configuration -> Site URL
|
9) Go to URL Authentication -> URL Configuration -> Site URL
|
||||||
10) enter your new netlify URL e.g. https://mycoolsaas.netlify.app/ and click 'save'
|
10) enter your new netlify URL e.g. https://mycoolsaas.netlify.app/ and click 'save'
|
||||||
|
11) Add the following additional redirect URLs for local development and deployment previews:
|
||||||
|
http://localhost:3000/**
|
||||||
|
https://**--mycoolsaas.netlify.app/**
|
||||||
|
12) If you haven't already done so, edit your Supabase Email templates as the generic ones tend to get blocked by GMail.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
38
pages/forgotpassword.vue
Normal file
38
pages/forgotpassword.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const supabase = useSupabaseAuthClient();
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const email = ref('')
|
||||||
|
|
||||||
|
const sendResetPasswordLink = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const { data, error } = await supabase.auth.resetPasswordForEmail(email.value, {
|
||||||
|
redirectTo: `${config.public.siteRootUrl}/resetpassword`,
|
||||||
|
})
|
||||||
|
if (error) throw error
|
||||||
|
else alert('Password Reset link sent, check your email.');
|
||||||
|
} catch (error) {
|
||||||
|
alert(error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
|
||||||
|
<div class="w-full max-w-md p-6 space-y-6 bg-white rounded-lg shadow-lg">
|
||||||
|
<h1 class="text-3xl font-bold text-center">Forgot Pasword</h1>
|
||||||
|
<form @submit.prevent="sendResetPasswordLink" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label for="email" class="block mb-2 font-bold">Email</label>
|
||||||
|
<input v-model="email" id="email" type="email" class="w-full p-2 border border-gray-400 rounded-md"
|
||||||
|
placeholder="Enter your email" required>
|
||||||
|
</div>
|
||||||
|
<button :disabled="loading || email === ''" type="submit"
|
||||||
|
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">Send Reset Password Link</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
46
pages/resetpassword.vue
Normal file
46
pages/resetpassword.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const supabase = useSupabaseAuthClient();
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const password = ref('')
|
||||||
|
const confirmPassword = ref('')
|
||||||
|
|
||||||
|
const changePassword = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const { data, error } = await supabase.auth.updateUser({
|
||||||
|
password: password.value
|
||||||
|
});
|
||||||
|
if (error) throw error
|
||||||
|
else {
|
||||||
|
alert('password changed');
|
||||||
|
navigateTo('/signin', {replace: true}); // navigate to signin because it is best practice although the auth session seems to be valid so it immediately redirects to dashboard
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center justify-center h-screen bg-gray-100">
|
||||||
|
<div class="w-full max-w-md p-6 space-y-6 bg-white rounded-lg shadow-lg">
|
||||||
|
<h1 class="text-3xl font-bold text-center">Forgot Pasword</h1>
|
||||||
|
<form @submit.prevent="changePassword" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label for="password" class="block mb-2 font-bold">New Password</label>
|
||||||
|
<input v-model="password" id="password" type="password" class="w-full p-2 border border-gray-400 rounded-md"
|
||||||
|
placeholder="Enter your new password" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="confirmPassword" class="block mb-2 font-bold">Confirm New Password</label>
|
||||||
|
<input v-model="confirmPassword" id="confirmPassword" type="password" class="w-full p-2 border border-gray-400 rounded-md"
|
||||||
|
placeholder="Confirm new password" required>
|
||||||
|
</div>
|
||||||
|
<button :disabled="loading || password === '' || (confirmPassword !== password)" type="submit"
|
||||||
|
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">Change Password</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
<input v-model="password" id="password" type="password" class="w-full p-2 border border-gray-400 rounded-md"
|
<input v-model="password" id="password" type="password" class="w-full p-2 border border-gray-400 rounded-md"
|
||||||
placeholder="Enter your password" required>
|
placeholder="Enter your password" required>
|
||||||
</div>
|
</div>
|
||||||
|
<NuxtLink id="forgotPasswordLink" to="/forgotpassword" class="text-right block">Forgot your password?</NuxtLink>
|
||||||
<button :disabled="loading || password === ''" type="submit"
|
<button :disabled="loading || password === ''" type="submit"
|
||||||
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">Sign in</button>
|
class="w-full py-2 text-white bg-indigo-600 rounded-md hover:bg-indigo-700">Sign in</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user