Skip to content

Commit f5bae68

Browse files
authored
Merge pull request #466 from CodebilityDev/alyssa-jecomo/supabase-auth-delete-account
Fixed Supabase Auth User List Issue
2 parents ec511d1 + fd13f4a commit f5bae68

2 files changed

Lines changed: 183 additions & 76 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// app/api/delete-auth-user/route.ts
2+
import { createClient } from '@supabase/supabase-js';
3+
import { NextRequest, NextResponse } from 'next/server';
4+
5+
// Define request body type for type safety
6+
interface DeleteUserRequest {
7+
userId: string;
8+
}
9+
10+
export async function POST(request: NextRequest) {
11+
try {
12+
// Parse the request body with type assertion
13+
const body = await request.json() as DeleteUserRequest;
14+
const { userId } = body;
15+
16+
if (!userId) {
17+
return NextResponse.json(
18+
{ error: 'User ID is required' },
19+
{ status: 400 }
20+
);
21+
}
22+
23+
// Create Supabase client with service role key
24+
// IMPORTANT: Service role key should ONLY be used server-side
25+
const supabaseAdmin = createClient(
26+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
27+
process.env.DB_SERVICE_ROLE!, // Using your environment variable name
28+
{
29+
auth: {
30+
autoRefreshToken: false,
31+
persistSession: false
32+
}
33+
}
34+
);
35+
36+
// Verify the user making the request
37+
const authHeader = request.headers.get('authorization');
38+
if (!authHeader) {
39+
return NextResponse.json(
40+
{ error: 'No authorization header' },
41+
{ status: 401 }
42+
);
43+
}
44+
45+
const token = authHeader.replace('Bearer ', '');
46+
const { data: { user }, error: verifyError } = await supabaseAdmin.auth.getUser(token);
47+
48+
if (verifyError || !user) {
49+
return NextResponse.json(
50+
{ error: 'Unauthorized' },
51+
{ status: 401 }
52+
);
53+
}
54+
55+
// Ensure user can only delete their own account
56+
if (user.id !== userId) {
57+
return NextResponse.json(
58+
{ error: 'Forbidden: You can only delete your own account' },
59+
{ status: 403 }
60+
);
61+
}
62+
63+
// Delete the user from Supabase Auth
64+
const { error: deleteError } = await supabaseAdmin.auth.admin.deleteUser(userId);
65+
66+
if (deleteError) {
67+
console.error('Error deleting user:', deleteError);
68+
return NextResponse.json(
69+
{ error: 'Failed to delete user authentication' },
70+
{ status: 500 }
71+
);
72+
}
73+
74+
return NextResponse.json(
75+
{ success: true, message: 'User authentication deleted successfully' },
76+
{ status: 200 }
77+
);
78+
79+
} catch (error) {
80+
console.error('Unexpected error:', error);
81+
return NextResponse.json(
82+
{ error: 'An unexpected error occurred' },
83+
{ status: 500 }
84+
);
85+
}
86+
}

‎apps/codebility/app/home/account-settings/_components/AccountSettingsDelete.tsx‎

Lines changed: 97 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ export default function AccountSettingsDelete() {
8787
return;
8888
}
8989

90+
// Get the current session token for API authentication
91+
const {
92+
data: { session },
93+
} = await supabase.auth.getSession();
94+
95+
if (!session) {
96+
toast.error("No active session");
97+
return;
98+
}
99+
90100
// Delete user data from your codev table first
91101
const { error: dbError } = await supabase
92102
.from("codev")
@@ -95,16 +105,37 @@ export default function AccountSettingsDelete() {
95105

96106
if (dbError) {
97107
toast.error("Failed to delete user data");
108+
console.error("Database deletion error:", dbError);
98109
return;
99110
}
100111

101-
// Delete the authentication user
102-
const { error: authError } = await supabase.auth.admin.deleteUser(
103-
user.id,
104-
);
112+
// Call the API route to delete the authentication user
113+
const response = await fetch("/api/delete-auth-user", {
114+
method: "POST",
115+
headers: {
116+
"Content-Type": "application/json",
117+
"Authorization": `Bearer ${session.access_token}`,
118+
},
119+
body: JSON.stringify({
120+
userId: user.id,
121+
}),
122+
});
123+
124+
// Parse response with proper type handling
125+
const rawData: unknown = await response.json();
126+
127+
// Type guard for API response
128+
interface ApiResponse {
129+
success?: boolean;
130+
message?: string;
131+
error?: string;
132+
}
133+
134+
const data = rawData as ApiResponse;
105135

106-
if (authError) {
107-
toast.error("Failed to delete account");
136+
if (!response.ok) {
137+
toast.error(data.error || "Failed to delete account");
138+
console.error("API error:", data);
108139
return;
109140
}
110141

@@ -163,78 +194,68 @@ export default function AccountSettingsDelete() {
163194
</CardDescription>
164195
</CardHeader>
165196
<CardContent>
166-
{/* <Alert className="background-box mb-4 border border-red-600 text-red-600">
167-
<AlertCircle className="h-4 w-4" color="red" />
168-
<AlertTitle>Warning</AlertTitle>
169-
<AlertDescription className="text-xs sm:text-sm">
170-
This action is irreversible. All your data will be permanently
171-
deleted.
172-
</AlertDescription>
173-
</Alert> */}
174-
175-
<Dialog
176-
open={isOpen}
177-
onOpenChange={(open) => {
178-
setIsOpen(open);
179-
if (!open) {
180-
form.reset();
181-
}
182-
}}
183-
>
184-
<DialogTrigger asChild>
185-
<Button variant="destructive">Delete Account</Button>
186-
</DialogTrigger>
187-
<DialogContent className="text-dark100_light900 background-box w-[90%] sm:w-full">
188-
<DialogHeader>
189-
<DialogTitle>Are you absolutely sure?</DialogTitle>
190-
<DialogDescription>
191-
This action cannot be undone. This will permanently delete
192-
your account and remove your data from our servers.
193-
</DialogDescription>
194-
</DialogHeader>
195-
<Form {...form}>
196-
<form onSubmit={form.handleSubmit(onSubmit)} noValidate>
197-
<FormField
198-
control={form.control}
199-
name="confirmation"
200-
render={({ field }) => (
201-
<FormItem>
202-
<FormControl>
203-
<Input
204-
placeholder="Type DELETE to confirm"
205-
labelClassName="sm:text-base text-sm"
206-
label="To confirm, type DELETE in the box below:"
207-
parentClassName="flex flex-col gap-2"
208-
variant="lightgray"
209-
disabled={isLoading}
210-
{...field}
211-
/>
212-
</FormControl>
213-
<FormMessage className="text-red-600" />
214-
</FormItem>
215-
)}
216-
/>
217-
<DialogFooter className="mt-4 gap-2">
218-
<DialogClose asChild>
219-
<Button variant="outline" disabled={isLoading}>
220-
Cancel
221-
</Button>
222-
</DialogClose>
223-
<Button
224-
variant="destructive"
225-
type="submit"
226-
disabled={!form.formState.isValid || isLoading}
227-
>
228-
{isLoading ? "Deleting..." : "Delete Account"}
197+
<Dialog
198+
open={isOpen}
199+
onOpenChange={(open) => {
200+
setIsOpen(open);
201+
if (!open) {
202+
form.reset();
203+
}
204+
}}
205+
>
206+
<DialogTrigger asChild>
207+
<Button variant="destructive">Delete Account</Button>
208+
</DialogTrigger>
209+
<DialogContent className="text-dark100_light900 background-box w-[90%] sm:w-full">
210+
<DialogHeader>
211+
<DialogTitle>Are you absolutely sure?</DialogTitle>
212+
<DialogDescription>
213+
This action cannot be undone. This will permanently delete
214+
your account and remove your data from our servers.
215+
</DialogDescription>
216+
</DialogHeader>
217+
<Form {...form}>
218+
<form onSubmit={form.handleSubmit(onSubmit)} noValidate>
219+
<FormField
220+
control={form.control}
221+
name="confirmation"
222+
render={({ field }) => (
223+
<FormItem>
224+
<FormControl>
225+
<Input
226+
placeholder="Type DELETE to confirm"
227+
labelClassName="sm:text-base text-sm"
228+
label="To confirm, type DELETE in the box below:"
229+
parentClassName="flex flex-col gap-2"
230+
variant="lightgray"
231+
disabled={isLoading}
232+
{...field}
233+
/>
234+
</FormControl>
235+
<FormMessage className="text-red-600" />
236+
</FormItem>
237+
)}
238+
/>
239+
<DialogFooter className="mt-4 gap-2">
240+
<DialogClose asChild>
241+
<Button variant="outline" disabled={isLoading}>
242+
Cancel
229243
</Button>
230-
</DialogFooter>
231-
</form>
232-
</Form>
233-
</DialogContent>
234-
</Dialog>
235-
244+
</DialogClose>
245+
<Button
246+
variant="destructive"
247+
type="submit"
248+
disabled={!form.formState.isValid || isLoading}
249+
>
250+
{isLoading ? "Deleting..." : "Delete Account"}
251+
</Button>
252+
</DialogFooter>
253+
</form>
254+
</Form>
255+
</DialogContent>
256+
</Dialog>
236257
</CardContent>
237258
</Card>
238259
</>
239260
);
240-
}
261+
}

0 commit comments

Comments
 (0)