Skip to content

Commit c62eece

Browse files
fix(auth): use createServerActionClient for proper PKCE cookie handling
- Change createClient() to createServerActionClient() in all auth server actions - createClient() silently ignores cookie set failures (designed for Server Components) - createServerActionClient() properly sets PKCE code_verifier cookie - Add fallback error handling when OAuth redirect URL is not returned - Fix infinite re-render in AddRepositoryCombobox by filtering organizations Fixes production login failure where clicking login button did nothing
1 parent 3a6d0a9 commit c62eece

File tree

2 files changed

+42
-13
lines changed

2 files changed

+42
-13
lines changed

components/Board/AddRepositoryCombobox.tsx

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,31 @@ export const AddRepositoryCombobox = memo(function AddRepositoryCombobox({
6666
const [organizations, setOrganizations] = useState<GitHubOrganization[]>([])
6767
const [isLoadingOrgs, setIsLoadingOrgs] = useState(false)
6868

69+
/**
70+
* Filter organizations to exclude currentUser (prevents duplicate SelectItem values)
71+
* This avoids Radix Select reconciliation issues when the same value appears multiple times
72+
*/
73+
const filteredOrganizations = useMemo(
74+
() =>
75+
organizations.filter(
76+
(org) => org.login.toLowerCase() !== currentUser?.login?.toLowerCase(),
77+
),
78+
[organizations, currentUser?.login],
79+
)
80+
81+
/**
82+
* Guarded organization filter change handler
83+
* Prevents potential infinite re-render by checking if value actually changed
84+
*/
85+
const handleOrganizationFilterChange = useCallback(
86+
(value: string) => {
87+
if (value !== organizationFilter) {
88+
setOrganizationFilter(value)
89+
}
90+
},
91+
[organizationFilter],
92+
)
93+
6994
// Refs
7095
const searchInputRef = useRef<HTMLInputElement>(null)
7196
const comboboxRef = useRef<HTMLDivElement>(null)
@@ -328,8 +353,7 @@ export const AddRepositoryCombobox = memo(function AddRepositoryCombobox({
328353
{/* Organization Filter */}
329354
<Select
330355
value={organizationFilter}
331-
// eslint-disable-next-line @laststance/react-next/no-set-state-prop-drilling
332-
onValueChange={setOrganizationFilter}
356+
onValueChange={handleOrganizationFilterChange}
333357
>
334358
<SelectTrigger
335359
className="flex-1 h-9 text-sm"
@@ -344,18 +368,19 @@ export const AddRepositoryCombobox = memo(function AddRepositoryCombobox({
344368
{currentUser.login} (Personal)
345369
</SelectItem>
346370
)}
347-
{organizations.map((org) => (
371+
{filteredOrganizations.map((org) => (
348372
<SelectItem key={org.id} value={org.login}>
349373
{org.login}
350374
</SelectItem>
351375
))}
352-
{isLoadingOrgs && (
353-
<SelectItem value="loading" disabled>
354-
Loading...
355-
</SelectItem>
356-
)}
357376
</SelectContent>
358377
</Select>
378+
{/* Loading indicator for organizations */}
379+
{isLoadingOrgs && (
380+
<span className="text-xs text-gray-500 self-center">
381+
Loading orgs...
382+
</span>
383+
)}
359384

360385
{/* Visibility filter */}
361386
<select

lib/actions/auth.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
import { headers } from 'next/headers'
1313
import { redirect } from 'next/navigation'
1414

15-
import { createClient } from '@/lib/supabase/server'
15+
import { createServerActionClient } from '@/lib/supabase/server'
1616

1717
/**
1818
* Sign in with GitHub OAuth
1919
*
2020
* @returns Redirect URL to GitHub authentication screen
2121
*/
2222
export async function signInWithGitHub() {
23-
const supabase = await createClient()
23+
const supabase = await createServerActionClient()
2424
const origin =
2525
(await headers()).get('origin') ?? process.env.NEXT_PUBLIC_SITE_URL
2626

@@ -42,6 +42,10 @@ export async function signInWithGitHub() {
4242
if (data.url) {
4343
redirect(data.url)
4444
}
45+
46+
// Handle case where no URL is returned (unexpected state)
47+
console.error('GitHub OAuth: No redirect URL returned')
48+
redirect('/login?error=oauth_failed&message=No%20redirect%20URL%20returned')
4549
}
4650

4751
/**
@@ -51,7 +55,7 @@ export async function signInWithGitHub() {
5155
* - Redirect to login page
5256
*/
5357
export async function signOut() {
54-
const supabase = await createClient()
58+
const supabase = await createServerActionClient()
5559

5660
const { error } = await supabase.auth.signOut()
5761

@@ -69,7 +73,7 @@ export async function signOut() {
6973
* @returns Session information (null if not authenticated)
7074
*/
7175
export async function getSession() {
72-
const supabase = await createClient()
76+
const supabase = await createServerActionClient()
7377

7478
const {
7579
data: { session },
@@ -90,7 +94,7 @@ export async function getSession() {
9094
* @returns User information (null if not authenticated)
9195
*/
9296
export async function getUser() {
93-
const supabase = await createClient()
97+
const supabase = await createServerActionClient()
9498

9599
const {
96100
data: { user },

0 commit comments

Comments
 (0)