From efd1c93b2e5ab7816de0267c8d7a1bc3e5698159 Mon Sep 17 00:00:00 2001 From: Parsa Asgari Date: Tue, 5 Mar 2024 10:43:11 +0000 Subject: [PATCH 1/2] auth: changed auth library from auth-helpers to supabase ssr --- app/sign-in/page.tsx | 3 +- auth.ts | 20 ++++++ components/login-form.tsx | 42 ++++++----- middleware.ts | 133 +++++++++++++++++++++++++---------- utils/login.ts | 27 +++++++ utils/supabase/client.ts | 8 +++ utils/supabase/middleware.ts | 60 ++++++++++++++++ utils/supabase/server.ts | 35 +++++++++ 8 files changed, 273 insertions(+), 55 deletions(-) create mode 100644 utils/login.ts create mode 100644 utils/supabase/client.ts create mode 100644 utils/supabase/middleware.ts create mode 100644 utils/supabase/server.ts diff --git a/app/sign-in/page.tsx b/app/sign-in/page.tsx index 1db04694f..8e534a3f5 100644 --- a/app/sign-in/page.tsx +++ b/app/sign-in/page.tsx @@ -13,7 +13,8 @@ export default async function SignInPage({ searchParams: { [key: string]: string | string[] | undefined } }) { const cookieStore = cookies() - let session = await auth({ cookieStore }) + // let session = await auth({ cookieStore }) + let session = await auth({cookieStore}) // legacy code // // // const user_id = searchParams?.user_id diff --git a/auth.ts b/auth.ts index dc1ea5683..9674b49c5 100644 --- a/auth.ts +++ b/auth.ts @@ -1,5 +1,6 @@ import 'server-only' import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' +import { createClient } from '@/utils/supabase/server' import { cookies } from 'next/headers' export const auth = async ({ @@ -15,3 +16,22 @@ export const auth = async ({ if (error) throw error return data.session } + +export const authUser = async ({ + cookieStore +}: { + cookieStore: ReturnType +}) => { + // Create a Supabase client configured to use cookies + // const supabase = createServerComponentClient({ + // cookies: () => cookieStore + // }) + const supabase = createClient() + const { data, error } = await supabase.auth.getSession() + + // const supabase = createClient() + // const { data:data, error:error } = await supabase.auth.getUser() + + if (error) throw error + return data.session +} diff --git a/components/login-form.tsx b/components/login-form.tsx index 59ccf354d..4acda4491 100644 --- a/components/login-form.tsx +++ b/components/login-form.tsx @@ -13,6 +13,10 @@ import { toast } from 'react-hot-toast' import { useRouter } from 'next/navigation' import GlobalConfig from '@/app/app.config.js' +import { createBrowserClient } from '@/utils/supabase/client' + +import {signInServer} from '@/utils/login' + interface LoginFormProps extends React.ComponentPropsWithoutRef<'div'> { action: 'sign-in' | 'sign-up' @@ -27,7 +31,7 @@ export function LoginForm({ const [isLoading, setIsLoading] = React.useState(false) const router = useRouter() // Create a Supabase client configured to use cookies - const supabase = createClientComponentClient() + const [formState, setFormState] = React.useState<{ email: string @@ -38,26 +42,28 @@ export function LoginForm({ }) const signIn = async () => { - const { email, password } = formState - const { error } = await supabase.auth.signInWithPassword({ - email, - password - }) + // const { email, password } = formState + // const { error } = await supabase.auth.signInWithPassword({ + // email, + // password + // }) + // return error + const error = await signInServer(formState) return error } - const signUp = async () => { - const { email, password } = formState - const { error, data } = await supabase.auth.signUp({ - email, - password, - options: { emailRedirectTo: `${location.origin}/api/auth/callback` } - }) - - if (!error && !data.session) - toast.success('Check your inbox to confirm your email address!') - return error - } + // const signUp = async () => { + // const { email, password } = formState + // const { error, data } = await supabase.auth.signUp({ + // email, + // password, + // options: { emailRedirectTo: `${location.origin}/api/auth/callback` } + // }) + + // if (!error && !data.session) + // toast.success('Check your inbox to confirm your email address!') + // return error + // } const handleOnSubmit: React.FormEventHandler = async e => { e.preventDefault() diff --git a/middleware.ts b/middleware.ts index 6a1673296..44ce6fdb9 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,31 +1,39 @@ import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' import { NextResponse, NextRequest } from 'next/server' -export async function middleware(req: NextRequest) { - const res = NextResponse.next() +import { createServerClient, type CookieOptions } from '@supabase/ssr' +import { cookies } from 'next/headers' + +export async function middleware(request: NextRequest) { + // const res = NextResponse.next() + let response = NextResponse.next({ + request: { + headers: request.headers, + }, + }) // Added CSP code // directly from https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy const nonce = Buffer.from(crypto.randomUUID()).toString('base64') - const cspHeader = ` - default-src 'self'; - script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; - style-src 'self' 'nonce-${nonce}'; - img-src 'self' blob: data:; - font-src 'self'; - object-src 'none'; - base-uri 'self'; - form-action 'self'; - frame-ancestors 'none'; - block-all-mixed-content; - upgrade-insecure-requests; + const cspHeader = process.env.DEBUG_MODE ? `` : ` + default-src 'self' 'unsafe-inline'; + script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline'; + style-src 'self' 'nonce-${nonce}' 'unsafe-inline'; + img-src 'self' blob: data:; + font-src 'self'; + object-src 'none'; + base-uri 'self'; + form-action 'self'; + frame-ancestors 'none'; + block-all-mixed-content; + upgrade-insecure-requests; ` - // Replace newline characters and spaces +// // Replace newline characters and spaces const contentSecurityPolicyHeaderValue = cspHeader .replace(/\s{2,}/g, ' ') .trim() - const requestHeaders = new Headers(req.headers) + const requestHeaders = new Headers(request.headers) requestHeaders.set('x-nonce', nonce) requestHeaders.set( @@ -33,48 +41,94 @@ export async function middleware(req: NextRequest) { contentSecurityPolicyHeaderValue ) - const response = NextResponse.next({ - request: { - headers: requestHeaders, - }, - }) response.headers.set( 'Content-Security-Policy', contentSecurityPolicyHeaderValue ) // Create a Supabase client configured to use cookies - const supabase = createMiddlewareClient({ req, res }) + // const supabase = createMiddlewareClient({ req, res }) // Refresh session if expired - required for Server Components // https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-session-with-middleware - const { - data: { session } - } = await supabase.auth.getSession() + // const { + // data: { session } + // } = await supabase.auth.getSession() + + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookieOptions: {domain: '', secure: 'false', maxAge: 604800, path: '', sameSite: 'None'}, + cookies: { + get(name: string) { + return request.cookies.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + request.cookies.set({ + name, + value, + ...options, + }) + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }) + response.cookies.set({ + name, + value, + ...options, + }) + }, + remove(name: string, options: CookieOptions) { + request.cookies.set({ + name, + value: '', + ...options, + }) + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }) + response.cookies.set({ + name, + value: '', + ...options, + }) + }, + }, + } + ) + // console.log(await supabase.auth.getUser()) + let {data: session} = await supabase.auth.getSession() + await supabase.auth.getUser() + const cookieStore = cookies() // OPTIONAL: this forces users to be logged in to use the chatbot. // If you want to allow anonymous users, simply remove the check below. - const user_id = req.nextUrl.searchParams.get('user_id') + const user_id = request.nextUrl.searchParams.get('user_id') if ( - !session && - !req.url.includes('/sign-in') && - !req.url.includes('/sign-up') && - !req.url.includes('/reset-password') + !session.session && + !request.url.includes('/sign-in') && + !request.url.includes('/sign-up') && + !request.url.includes('/reset-password') ) { - const redirectUrl = req.nextUrl.clone() + const redirectUrl = request.nextUrl.clone() redirectUrl.pathname = '/sign-in' - redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname) + redirectUrl.searchParams.set(`redirectedFrom`, request.nextUrl.pathname) user_id ? redirectUrl.searchParams.set(`user_id`, user_id) : undefined return NextResponse.redirect(redirectUrl) } - else if (session && req.url.includes('/reset-password') && req.url.includes('/sign-up') && req.url.includes('/sign-in')){ - const redirectUrl = req.nextUrl.clone() + else if (session.session && request.url.includes('/reset-password') && request.url.includes('/sign-up') && request.url.includes('/sign-in')){ + const redirectUrl = request.nextUrl.clone() redirectUrl.pathname = '/' user_id ? redirectUrl.searchParams.set(`user_id`, user_id) : undefined return NextResponse.redirect(redirectUrl) } - return res + return response } export const config = { @@ -87,6 +141,13 @@ export const config = { * - _next/image (image optimization files) * - favicon.ico (favicon file) */ - '/((?!share|api|_next/static|_next/image|favicon.ico).*)' + // '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', + { + source: '/((?!api|_next/static|_next/image|favicon.ico).*)', + missing: [ + { type: 'header', key: 'next-router-prefetch' }, + { type: 'header', key: 'purpose', value: 'prefetch' }, + ], + }, ] } diff --git a/utils/login.ts b/utils/login.ts new file mode 100644 index 000000000..e662a66a5 --- /dev/null +++ b/utils/login.ts @@ -0,0 +1,27 @@ +import { createClient } from '@/utils/supabase/client' + + +const supabase = createClient() + +type formStateType = { + email: string, + password: string +} + +export const signInServer = async (formState: formStateType) => { + const { email, password } = formState + const { error } = await supabase.auth.signInWithPassword({ + email, + password + }) + return error + } + +export const signUpServer = async (formState: formStateType) => { + const { email, password } = formState + const { error, data } = await supabase.auth.signUp({ + email, + password, + options: { emailRedirectTo: `${location.origin}/api/auth/callback` } + }) +} diff --git a/utils/supabase/client.ts b/utils/supabase/client.ts new file mode 100644 index 000000000..78ff395da --- /dev/null +++ b/utils/supabase/client.ts @@ -0,0 +1,8 @@ +import { createBrowserClient } from '@supabase/ssr' + +export function createClient() { + return createBrowserClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) +} \ No newline at end of file diff --git a/utils/supabase/middleware.ts b/utils/supabase/middleware.ts new file mode 100644 index 000000000..0f5d9c976 --- /dev/null +++ b/utils/supabase/middleware.ts @@ -0,0 +1,60 @@ +import { createServerClient, type CookieOptions } from '@supabase/ssr' +import { NextResponse, type NextRequest } from 'next/server' + +export async function updateSession(request: NextRequest) { + let response = NextResponse.next({ + request: { + headers: request.headers, + }, + }) + + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return request.cookies.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + request.cookies.set({ + name, + value, + ...options, + }) + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }) + response.cookies.set({ + name, + value, + ...options, + }) + }, + remove(name: string, options: CookieOptions) { + request.cookies.set({ + name, + value: '', + ...options, + }) + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }) + response.cookies.set({ + name, + value: '', + ...options, + }) + }, + }, + } + ) + + await supabase.auth.getUser() + + return response +} \ No newline at end of file diff --git a/utils/supabase/server.ts b/utils/supabase/server.ts new file mode 100644 index 000000000..be58d6a68 --- /dev/null +++ b/utils/supabase/server.ts @@ -0,0 +1,35 @@ +import { createServerClient, type CookieOptions } from '@supabase/ssr' +import { cookies } from 'next/headers' + +export function createClient() { + const cookieStore = cookies() + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + try { + cookieStore.set({ name, value, ...options }) + } catch (error) { + // The `set` method was called from a Server Component. + // This can be ignored if you have middleware refreshing + // user sessions. + } + }, + remove(name: string, options: CookieOptions) { + try { + cookieStore.set({ name, value: '', ...options }) + } catch (error) { + // The `delete` method was called from a Server Component. + // This can be ignored if you have middleware refreshing + // user sessions. + } + }, + }, + } + ) +} \ No newline at end of file From 77d22d2ef408e592210bef5b09fa44f91d8c14ec Mon Sep 17 00:00:00 2001 From: Parsa Asgari Date: Tue, 5 Mar 2024 10:48:23 +0000 Subject: [PATCH 2/2] minor fix for pnpm build --- package.json | 1 + pnpm-lock.yaml | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/package.json b/package.json index b0f701343..711453b18 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.6", "@supabase/auth-helpers-nextjs": "^0.7.2", + "@supabase/ssr": "^0.1.0", "@supabase/supabase-js": "^2.26.0", "@tanstack/react-table": "^8.10.7", "@vercel/analytics": "^1.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b87f57cf3..faaa1ebbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ dependencies: '@supabase/auth-helpers-nextjs': specifier: ^0.7.2 version: 0.7.2(@supabase/supabase-js@2.26.0) + '@supabase/ssr': + specifier: ^0.1.0 + version: 0.1.0(@supabase/supabase-js@2.26.0) '@supabase/supabase-js': specifier: ^2.26.0 version: 2.26.0(encoding@0.1.13) @@ -2134,6 +2137,16 @@ packages: - supports-color dev: false + /@supabase/ssr@0.1.0(@supabase/supabase-js@2.26.0): + resolution: {integrity: sha512-bIVrkqjAK5G3KjkIMKYKtAOlCgRRplEWjrlyRyXSOYtgDieiOhk2ZyNAPsEOa1By9OZVxuX5eAW1fitdnuxayw==} + peerDependencies: + '@supabase/supabase-js': ^2.33.1 + dependencies: + '@supabase/supabase-js': 2.26.0(encoding@0.1.13) + cookie: 0.5.0 + ramda: 0.29.1 + dev: false + /@supabase/storage-js@2.5.1(encoding@0.1.13): resolution: {integrity: sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==} dependencies: @@ -2918,6 +2931,11 @@ packages: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: false + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + /cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -5561,6 +5579,10 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /ramda@0.29.1: + resolution: {integrity: sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==} + dev: false + /react-day-picker@8.9.1(date-fns@2.30.0)(react@18.2.0): resolution: {integrity: sha512-W0SPApKIsYq+XCtfGeMYDoU0KbsG3wfkYtlw8l+vZp6KoBXGOlhzBUp4tNx1XiwiOZwhfdGOlj7NGSCKGSlg5Q==} peerDependencies: