Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/app/auth/confirm/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type EmailOtpType } from '@supabase/supabase-js'
import { type NextRequest } from 'next/server'

import { createClient } from '@/lib/client/supabase/server'
import { redirect } from 'next/navigation'
Comment on lines +1 to +5
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent quote style. This file uses single quotes while other files in the codebase use double quotes (e.g., src/lib/client/supabase/client.ts, src/lib/client/supabase/auth.ts). Consider using double quotes for consistency with the rest of the codebase.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is also the errors Vercel points out. Make sure you're up-to-date with the latest main branch! There's code in the main branch that runs formatting/linting automatically for you before every commit.


export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const next = searchParams.get('next') ?? '/'

if (token_hash && type) {
const supabase = await createClient()

const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
// redirect user to specified redirect URL or root of app
redirect(next)
Comment on lines +11 to +22
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next parameter from search params is used directly for redirection without validation. This creates an open redirect vulnerability where attackers could craft malicious links to redirect users to external sites after authentication. Validate that the redirect URL is relative or on the same domain before using it.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good practice

}
}

// redirect the user to an error page with some instructions
redirect('/auth/auth-code-error')
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The route redirects to /auth/auth-code-error but this page doesn't exist in the codebase. This will result in a 404 error when email confirmation fails. You need to create the error page at src/app/auth/auth-code-error/page.tsx.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just create a very basic page

}
101 changes: 101 additions & 0 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// temp ugly auth page for testing
"use client";

import { useState } from "react";
import { signInWithEmail, signUpWithEmail } from "@/lib/client/supabase/auth";

export default function LoginPage() {
const [signInEmail, setSignInEmail] = useState("");
const [signInPassword, setSignInPassword] = useState("");
const [signUpEmail, setSignUpEmail] = useState("");
const [signUpPassword, setSignUpPassword] = useState("");
const [message, setMessage] = useState<string | null>(null);
const [responseData, setResponseData] = useState<unknown>(null);
const [loading, setLoading] = useState(false);

const handleSignIn = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setLoading(true);
setMessage(null);
setResponseData(null);
const { data, error } = await signInWithEmail(
signInEmail,
signInPassword
);
setMessage(error ? error.message : "Signed in successfully.");
setResponseData(data);
setLoading(false);
};

const handleSignUp = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setLoading(true);
setMessage(null);
setResponseData(null);
const { data, error } = await signUpWithEmail(signUpEmail, signUpPassword);
setMessage(
error ? error.message : "Check your email to confirm your account."
);
setResponseData(data);
setLoading(false);
};

return (
<main>
<h1>Sign In</h1>
<form onSubmit={handleSignIn}>
<label htmlFor="signin-email">Email</label>
<input
id="signin-email"
type="email"
value={signInEmail}
onChange={(event) => setSignInEmail(event.target.value)}
required
/>

<label htmlFor="signin-password">Password</label>
<input
id="signin-password"
type="password"
value={signInPassword}
onChange={(event) => setSignInPassword(event.target.value)}
required
/>

<button type="submit" disabled={loading}>
Sign In
</button>
</form>

<h1>Sign Up</h1>
<form onSubmit={handleSignUp}>
<label htmlFor="signup-email">Email</label>
<input
id="signup-email"
type="email"
value={signUpEmail}
onChange={(event) => setSignUpEmail(event.target.value)}
required
/>

<label htmlFor="signup-password">Password</label>
<input
id="signup-password"
type="password"
value={signUpPassword}
onChange={(event) => setSignUpPassword(event.target.value)}
required
/>

<button type="submit" disabled={loading}>
Sign Up
</button>
</form>

{message ? <p>{message}</p> : null}
{responseData ? (
<pre>{JSON.stringify(responseData, null, 2)}</pre>
) : null}
</main>
);
}
17 changes: 16 additions & 1 deletion src/lib/client/supabase/auth.ts
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
// To be developed: Auth related functions for Supabase client
import { createClient } from "./client";

export async function signUpWithEmail(email: string, password: string) {
const supabase = createClient();
const { data, error } = await supabase.auth.signUp({ email, password });
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signUpWithEmail function should include an emailRedirectTo option to specify where users should be redirected after confirming their email. Without this, the confirmation link may not work correctly with the /auth/confirm route. Consider adding: options: { emailRedirectTo: ${window.location.origin}/auth/confirm }

Suggested change
const { data, error } = await supabase.auth.signUp({ email, password });
const { data, error } = await supabase.auth.signUp(
{ email, password },
{ emailRedirectTo: `${window.location.origin}/auth/confirm` }
);

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the confirmation link work properly?

return { data, error };
}

export async function signInWithEmail(email: string, password: string) {
const supabase = createClient();
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
return { data, error };
}
Comment on lines +9 to +16
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding email format validation before calling the Supabase API. While the HTML input type="email" provides basic validation, additional checks in the auth functions would provide defense-in-depth and ensure consistent validation if these functions are called from other contexts.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can ignore this for now for the same reason that we ignore the password checks. Performing these strength requirements checks on the frontend would provide faster feedback to the user.

8 changes: 8 additions & 0 deletions src/lib/client/supabase/client.ts
Original file line number Diff line number Diff line change
@@ -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_PUBLISHABLE_KEY!,
);
}
69 changes: 69 additions & 0 deletions src/lib/client/supabase/proxy.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use a different name for this file since it's the same as src/proxy.ts

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";

export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
});

// With Fluid compute, don't put this client in a global environment
// variable. Always create a new one on each request.
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value),
);
supabaseResponse = NextResponse.next({
request,
});
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options),
);
},
},
},
);

// Do not run code between createServerClient and
// supabase.auth.getClaims(). A simple mistake could make it very hard to debug
// issues with users being randomly logged out.

// IMPORTANT: If you remove getClaims() and you use server-side rendering
// with the Supabase client, your users may be randomly logged out.
const { data } = await supabase.auth.getClaims();
const user = data?.claims;

if (
request.nextUrl.pathname !== "/" &&
!user &&
!request.nextUrl.pathname.startsWith("/login") &&
!request.nextUrl.pathname.startsWith("/auth")
) {
// no user, potentially respond by redirecting the user to the login page
const url = request.nextUrl.clone();
url.pathname = "/auth/login";
return NextResponse.redirect(url);
}

// IMPORTANT: You *must* return the supabaseResponse object as it is.
// If you're creating a new response object with NextResponse.next() make sure to:
// 1. Pass the request in it, like so:
// const myNewResponse = NextResponse.next({ request })
// 2. Copy over the cookies, like so:
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
// 3. Change the myNewResponse object to fit your needs, but avoid changing
// the cookies!
// 4. Finally:
// return myNewResponse
// If this is not done, you may be causing the browser and server to go out
// of sync and terminate the user's session prematurely!

return supabaseResponse;
}
20 changes: 20 additions & 0 deletions src/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { updateSession } from "@/lib/supabase/proxy";
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import path is incorrect. The file proxy.ts is located at src/lib/client/supabase/proxy.ts, not @/lib/supabase/proxy. This should be @/lib/client/supabase/proxy to match the actual file location.

Suggested change
import { updateSession } from "@/lib/supabase/proxy";
import { updateSession } from "@/lib/client/supabase/proxy";

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double check this

import { type NextRequest } from "next/server";

export async function proxy(request: NextRequest) {
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be named middleware.ts rather than proxy.ts and placed at the root of src/ directory. Next.js expects middleware to be exported from a file named middleware.ts at the root of the src directory (or project root). The exported function should also be named middleware instead of proxy.

Suggested change
export async function proxy(request: NextRequest) {
export async function middleware(request: NextRequest) {

Copilot uses AI. Check for mistakes.
Copy link
Member

@LeandroHamaguchi LeandroHamaguchi Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the name proxy.ts for this file work? Because for the Next.js version (15.5.6) we're using it asks for the function to be called middleware. The newest version (16) of Next.js requires proxy.ts instead of middleware.ts, but in the version we're using this isn't used yet.

return await updateSession(request);
}

export const config = {
matcher: [
/*
* Match all request paths except:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - images - .svg, .png, .jpg, .jpeg, .gif, .webp
* Feel free to modify this pattern to include more paths.
*/
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};
Loading