Spaces:
Sleeping
Sleeping
FauziIsyrinApridal
commited on
Commit
·
43be92e
1
Parent(s):
7ca96c1
revisi 8
Browse files- middleware.ts +20 -5
- utils/forgotPassword.ts +52 -4
middleware.ts
CHANGED
@@ -23,22 +23,36 @@ export async function middleware(request: NextRequest) {
|
|
23 |
},
|
24 |
set(name: string, value: string, options: CookieOptions) {
|
25 |
request.cookies.set({ name, value, ...options });
|
26 |
-
response = NextResponse.next({
|
|
|
|
|
27 |
response.cookies.set({ name, value, ...options });
|
28 |
},
|
29 |
remove(name: string, options: CookieOptions) {
|
30 |
request.cookies.set({ name, value: "", ...options });
|
31 |
-
response = NextResponse.next({
|
|
|
|
|
32 |
response.cookies.set({ name, value: "", ...options });
|
33 |
},
|
34 |
},
|
35 |
-
}
|
36 |
);
|
37 |
|
38 |
const { data } = await supabase.auth.getUser();
|
39 |
-
const
|
|
|
40 |
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
const url = request.nextUrl.clone();
|
43 |
url.pathname = "/login";
|
44 |
url.searchParams.set("error", "not_admin");
|
@@ -53,3 +67,4 @@ export const config = {
|
|
53 |
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
|
54 |
],
|
55 |
};
|
|
|
|
23 |
},
|
24 |
set(name: string, value: string, options: CookieOptions) {
|
25 |
request.cookies.set({ name, value, ...options });
|
26 |
+
response = NextResponse.next({
|
27 |
+
request: { headers: request.headers },
|
28 |
+
});
|
29 |
response.cookies.set({ name, value, ...options });
|
30 |
},
|
31 |
remove(name: string, options: CookieOptions) {
|
32 |
request.cookies.set({ name, value: "", ...options });
|
33 |
+
response = NextResponse.next({
|
34 |
+
request: { headers: request.headers },
|
35 |
+
});
|
36 |
response.cookies.set({ name, value: "", ...options });
|
37 |
},
|
38 |
},
|
39 |
+
},
|
40 |
);
|
41 |
|
42 |
const { data } = await supabase.auth.getUser();
|
43 |
+
const user = data?.user;
|
44 |
+
const role = user?.user_metadata?.role;
|
45 |
|
46 |
+
// If not authenticated, send to login without error flag
|
47 |
+
if (!user) {
|
48 |
+
const url = request.nextUrl.clone();
|
49 |
+
url.pathname = "/login";
|
50 |
+
url.searchParams.delete("error");
|
51 |
+
return NextResponse.redirect(url);
|
52 |
+
}
|
53 |
+
|
54 |
+
// If authenticated but not admin, show not_admin
|
55 |
+
if (role !== "admin") {
|
56 |
const url = request.nextUrl.clone();
|
57 |
url.pathname = "/login";
|
58 |
url.searchParams.set("error", "not_admin");
|
|
|
67 |
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
|
68 |
],
|
69 |
};
|
70 |
+
|
utils/forgotPassword.ts
CHANGED
@@ -1,7 +1,10 @@
|
|
1 |
"use server";
|
2 |
import { createClient } from "@/utils/supabase/server";
|
|
|
3 |
|
4 |
-
export type ForgotResult =
|
|
|
|
|
5 |
|
6 |
export async function sendResetEmail(email: string): Promise<ForgotResult> {
|
7 |
const supabase = createClient();
|
@@ -10,9 +13,51 @@ export async function sendResetEmail(email: string): Promise<ForgotResult> {
|
|
10 |
return { ok: false, message: "Email tidak valid" };
|
11 |
}
|
12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
// Determine redirect URL for the password reset flow
|
14 |
-
const siteUrl =
|
15 |
-
|
|
|
|
|
|
|
16 |
const redirectTo = `${siteUrl}/reset-password`;
|
17 |
|
18 |
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
@@ -23,5 +68,8 @@ export async function sendResetEmail(email: string): Promise<ForgotResult> {
|
|
23 |
return { ok: false, message: "Gagal mengirim email reset. Coba lagi." };
|
24 |
}
|
25 |
|
26 |
-
return {
|
|
|
|
|
|
|
27 |
}
|
|
|
1 |
"use server";
|
2 |
import { createClient } from "@/utils/supabase/server";
|
3 |
+
import { createClient as createSupabaseClient } from "@supabase/supabase-js";
|
4 |
|
5 |
+
export type ForgotResult =
|
6 |
+
| { ok: true; message: string }
|
7 |
+
| { ok: false; message: string };
|
8 |
|
9 |
export async function sendResetEmail(email: string): Promise<ForgotResult> {
|
10 |
const supabase = createClient();
|
|
|
13 |
return { ok: false, message: "Email tidak valid" };
|
14 |
}
|
15 |
|
16 |
+
// Admin-only: verify the email belongs to an admin user before sending reset link
|
17 |
+
try {
|
18 |
+
const adminClient = createSupabaseClient(
|
19 |
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
20 |
+
process.env.SUPABASE_SERVICE_KEY!,
|
21 |
+
);
|
22 |
+
// list users and find by email (no direct getUserByEmail in v2)
|
23 |
+
let page = 1;
|
24 |
+
const perPage = 200;
|
25 |
+
let foundRole: unknown = null;
|
26 |
+
while (true) {
|
27 |
+
const { data, error } = await adminClient.auth.admin.listUsers({ page, perPage });
|
28 |
+
if (error) throw error;
|
29 |
+
const users = data?.users || [];
|
30 |
+
const match = users.find((u) => u.email?.toLowerCase() === email.toLowerCase());
|
31 |
+
if (match) {
|
32 |
+
foundRole = match.user_metadata?.role;
|
33 |
+
break;
|
34 |
+
}
|
35 |
+
if (!data || users.length < perPage) break; // no more pages
|
36 |
+
page += 1;
|
37 |
+
// safety cap to avoid excessive loops
|
38 |
+
if (page > 50) break;
|
39 |
+
}
|
40 |
+
if (foundRole !== "admin") {
|
41 |
+
// Do not reveal whether the email exists; return a generic message
|
42 |
+
return {
|
43 |
+
ok: false,
|
44 |
+
message: "Akun tidak diizinkan melakukan reset di aplikasi admin.",
|
45 |
+
};
|
46 |
+
}
|
47 |
+
} catch {
|
48 |
+
// Fail closed: if role check fails, do not proceed
|
49 |
+
return {
|
50 |
+
ok: false,
|
51 |
+
message: "Gagal memverifikasi akun. Coba lagi.",
|
52 |
+
};
|
53 |
+
}
|
54 |
+
|
55 |
// Determine redirect URL for the password reset flow
|
56 |
+
const siteUrl =
|
57 |
+
process.env.NEXT_PUBLIC_SITE_URL?.replace(/\/$/, "") ||
|
58 |
+
(process.env.VERCEL_URL
|
59 |
+
? `https://${process.env.VERCEL_URL}`
|
60 |
+
: "http://localhost:3000");
|
61 |
const redirectTo = `${siteUrl}/reset-password`;
|
62 |
|
63 |
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
|
|
68 |
return { ok: false, message: "Gagal mengirim email reset. Coba lagi." };
|
69 |
}
|
70 |
|
71 |
+
return {
|
72 |
+
ok: true,
|
73 |
+
message: "Email reset telah dikirim. Periksa kotak masuk Anda.",
|
74 |
+
};
|
75 |
}
|