Working on Auth flows. Struggling with getting client and server to both update when signing in or out.
This commit is contained in:
@ -1,24 +1,77 @@
|
||||
'use server';
|
||||
|
||||
import 'server-only';
|
||||
import { createServerClient } from '@/utils/supabase';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
// The `/auth/callback` route is required for the server-side auth flow implemented
|
||||
// by the SSR package. It exchanges an auth code for the user's session.
|
||||
// https://supabase.com/docs/guides/auth/server-side/nextjs
|
||||
export const GET = async (request: Request) => {
|
||||
const requestUrl = new URL(request.url);
|
||||
const code = requestUrl.searchParams.get('code');
|
||||
const token = requestUrl.searchParams.get('token');
|
||||
const type = requestUrl.searchParams.get('type');
|
||||
const origin = requestUrl.origin;
|
||||
const redirectTo = requestUrl.searchParams.get('redirect_to')?.toString();
|
||||
|
||||
const supabase = await createServerClient();
|
||||
|
||||
if (token && type) {
|
||||
try {
|
||||
if (type === 'signup') {
|
||||
// Confirm email signup
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
token_hash: token,
|
||||
type: 'signup',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Email confirmation error:', error);
|
||||
return NextResponse.redirect(`${origin}/sign-in?error=Invalid or expired confirmation link`);
|
||||
}
|
||||
} else if (type === 'recovery') {
|
||||
// Handle password recovery
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
token_hash: token,
|
||||
type: 'recovery',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Password recovery error:', error);
|
||||
return NextResponse.redirect(`${origin}/sign-in?error=Invalid or expired reset link`);
|
||||
} else {
|
||||
return NextResponse.redirect(`${origin}/reset-password`);
|
||||
}
|
||||
} else if (type === 'email_change') {
|
||||
// Handle email change
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
token_hash: token,
|
||||
type: 'email_change',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Email change error:', error);
|
||||
return NextResponse.redirect(`${origin}/profile?error=Invalid or expired email change link`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Verification error:', error);
|
||||
return NextResponse.redirect(`${origin}/sign-in?error=Verification failed`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle code-based flow (OAuth, etc.)
|
||||
if (code) {
|
||||
const supabase = await createServerClient();
|
||||
await supabase.auth.exchangeCodeForSession(code);
|
||||
}
|
||||
|
||||
// Handle redirect
|
||||
if (redirectTo) {
|
||||
return NextResponse.redirect(`${origin}${redirectTo}`);
|
||||
try {
|
||||
new URL(redirectTo);
|
||||
return NextResponse.redirect(redirectTo);
|
||||
} catch {
|
||||
return NextResponse.redirect(`${origin}${redirectTo}`);
|
||||
}
|
||||
}
|
||||
|
||||
// URL to redirect to after sign up process completes
|
||||
return NextResponse.redirect(origin);
|
||||
}
|
||||
|
24
src/app/(auth-pages)/auth/callback/route.ts.bak
Normal file
24
src/app/(auth-pages)/auth/callback/route.ts.bak
Normal file
@ -0,0 +1,24 @@
|
||||
import { createServerClient } from '@/utils/supabase';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const GET = async (request: Request) => {
|
||||
// The `/auth/callback` route is required for the server-side auth flow implemented
|
||||
// by the SSR package. It exchanges an auth code for the user's session.
|
||||
// https://supabase.com/docs/guides/auth/server-side/nextjs
|
||||
const requestUrl = new URL(request.url);
|
||||
const code = requestUrl.searchParams.get('code');
|
||||
const origin = requestUrl.origin;
|
||||
const redirectTo = requestUrl.searchParams.get('redirect_to')?.toString();
|
||||
|
||||
if (code) {
|
||||
const supabase = await createServerClient();
|
||||
await supabase.auth.exchangeCodeForSession(code);
|
||||
}
|
||||
|
||||
if (redirectTo) {
|
||||
return NextResponse.redirect(`${origin}${redirectTo}`);
|
||||
}
|
||||
|
||||
// URL to redirect to after sign up process completes
|
||||
return NextResponse.redirect(origin);
|
||||
}
|
@ -3,7 +3,6 @@ import { getUser, signIn } from '@/lib/actions';
|
||||
import { FormMessage, type Message, SubmitButton } from '@/components/default';
|
||||
import { Input, Label } from '@/components/ui';
|
||||
import { redirect } from 'next/navigation';
|
||||
import type { User } from '@/utils/supabase';
|
||||
|
||||
const Login = async (props: { searchParams: Promise<Message> }) => {
|
||||
const searchParams = await props.searchParams;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import React, { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
|
||||
import { getUser, getProfile, updateProfile as updateProfileAction, getSignedUrl } from '@/lib/actions';
|
||||
import type { User, Profile } from '@/utils/supabase';
|
||||
import { type User, type Profile, createClient } from '@/utils/supabase';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type AuthContextType = {
|
||||
@ -72,15 +72,32 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const supabase = createClient();
|
||||
fetchUserData().catch((error) => {
|
||||
console.error('Error fetching user data:', error);
|
||||
});
|
||||
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange(async (event, session) => {
|
||||
if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
|
||||
await fetchUserData();
|
||||
} else if (event === 'SIGNED_OUT') {
|
||||
setUser(null);
|
||||
setProfile(null);
|
||||
setAvatarUrl(null);
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
void fetchUserData();
|
||||
}, 30 * 60 * 1000);
|
||||
}, 1 * 60 * 1000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const updateProfile = async (data: {
|
||||
|
@ -3,17 +3,18 @@
|
||||
import 'server-only';
|
||||
import { encodedRedirect } from '@/utils/utils';
|
||||
import { createServerClient } from '@/utils/supabase';
|
||||
import type { User } from '@supabase/supabase-js';
|
||||
import { headers } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
import type { User } from '@/utils/supabase';
|
||||
import type { Result } from './index';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
|
||||
export const signUp = async (formData: FormData) => {
|
||||
const name = formData.get('name') as string;
|
||||
const email = formData.get('email') as string;
|
||||
const password = formData.get('password') as string;
|
||||
const supabase = await createServerClient();
|
||||
//const origin = (await headers()).get('origin');
|
||||
const origin = (await headers()).get('origin');
|
||||
|
||||
if (!email || !password) {
|
||||
return encodedRedirect(
|
||||
@ -23,36 +24,26 @@ export const signUp = async (formData: FormData) => {
|
||||
);
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
const { error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
//options: {
|
||||
//emailRedirectTo: `${origin}/auth/callback`,
|
||||
//},
|
||||
options: {
|
||||
emailRedirectTo: `${origin}/auth/callback`,
|
||||
data: {
|
||||
full_name: name,
|
||||
email,
|
||||
provider: 'email',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return redirect('/');
|
||||
//return encodedRedirect('error', '/sign-up',
|
||||
//'Thanks for signing up! Please check your email for a verification link.');
|
||||
return encodedRedirect('error', '/sign-up', error.message);
|
||||
} else {
|
||||
try {
|
||||
if (!data.user) throw new Error('Could not sign up');
|
||||
const { error } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
full_name: name,
|
||||
provider: 'email',
|
||||
})
|
||||
.eq('id', data.user.id);
|
||||
if (error) throw new Error('Could not update profile');
|
||||
} catch (error) {
|
||||
console.error('Error updating profile: ', error);
|
||||
} finally {
|
||||
return redirect('/');
|
||||
//return encodedRedirect('success', '/',
|
||||
//'Thanks for signing up! Please check your email for a verification link.);
|
||||
}
|
||||
return encodedRedirect(
|
||||
'success',
|
||||
'/sign-up',
|
||||
'Thanks for signing up! Please check your email for a verification link.',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -68,8 +59,9 @@ export const signIn = async (formData: FormData) => {
|
||||
|
||||
if (error) {
|
||||
return encodedRedirect('error', '/sign-in', error.message);
|
||||
} else {
|
||||
return redirect('/');
|
||||
}
|
||||
return redirect('/');
|
||||
};
|
||||
|
||||
export const forgotPassword = async (formData: FormData) => {
|
||||
@ -147,10 +139,8 @@ export const resetPassword = async (
|
||||
|
||||
|
||||
export const resetPasswordFromEmail = async (formData: FormData) => {
|
||||
|
||||
const password = formData.get('password') as string;
|
||||
const confirmPassword = formData.get('confirmPassword') as string;
|
||||
|
||||
if (!password || !confirmPassword) {
|
||||
encodedRedirect(
|
||||
'error',
|
||||
@ -158,7 +148,6 @@ export const resetPasswordFromEmail = async (formData: FormData) => {
|
||||
'Password and confirm password are required',
|
||||
);
|
||||
}
|
||||
|
||||
const supabase = await createServerClient();
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
|
@ -45,7 +45,7 @@ export const resizeImage = async ({
|
||||
(blob) => {
|
||||
if (!blob) return;
|
||||
const resizedFile = new File([blob], file.name, {
|
||||
type: 'imgage/jpeg',
|
||||
type: 'image/jpeg',
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
resolve(resizedFile);
|
||||
|
@ -76,8 +76,24 @@ create policy "Anyone can upload an avatar." on storage.objects
|
||||
-- CREATE POLICY "Public statuses are viewable by everyone." ON statuses
|
||||
-- FOR SELECT USING (true);
|
||||
|
||||
-- CREATE POLICY "Users can insert statuses for any user." ON statuses
|
||||
-- FOR INSERT WITH CHECK (auth.role() = 'authenticated');
|
||||
-- -- RECREATE it using the recommended sub-select form
|
||||
-- CREATE POLICY "Authenticated users can insert statuses for any user."
|
||||
-- ON public.statuses
|
||||
-- FOR INSERT
|
||||
-- WITH CHECK (
|
||||
-- (SELECT auth.role()) = 'authenticated'
|
||||
-- );
|
||||
|
||||
-- -- ADD an UPDATE policy so anyone signed-in can update *any* status
|
||||
-- CREATE POLICY "Authenticated users can update statuses for any user."
|
||||
-- ON public.statuses
|
||||
-- FOR UPDATE
|
||||
-- USING (
|
||||
-- (SELECT auth.role()) = 'authenticated'
|
||||
-- )
|
||||
-- WITH CHECK (
|
||||
-- (SELECT auth.role()) = 'authenticated'
|
||||
-- );
|
||||
|
||||
-- -- Function to add first status
|
||||
-- CREATE FUNCTION public.handle_first_status()
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { createBrowserClient } from '@supabase/ssr';
|
||||
import type { Database } from '@/utils/supabase/types';
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
'use server'
|
||||
|
||||
import 'server-only';
|
||||
import { createServerClient as CreateServerClient } from '@supabase/ssr';
|
||||
import type { Database } from '@/utils/supabase/types';
|
||||
import { cookies } from 'next/headers';
|
||||
|
Reference in New Issue
Block a user