dev #58
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
||||
import { generateReferralCode } from "@sase/shared";
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { captcha } from "better-auth/plugins";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
@@ -165,6 +166,9 @@ export function createAuth(
|
||||
},
|
||||
},
|
||||
session: {
|
||||
// "Beni hatırla" işaretliyse oturum 30 gün korunur; işaretli değilse
|
||||
// better-auth çerezi oturum çerezi yapar (tarayıcı kapanınca silinir).
|
||||
expiresIn: 60 * 60 * 24 * 30, // 30 gün
|
||||
cookieCache: {
|
||||
enabled: true,
|
||||
maxAge: 60 * 5, // 5 minutes
|
||||
@@ -198,6 +202,19 @@ export function createAuth(
|
||||
...(process.env.CORS_ORIGIN || "http://localhost:3000").split(","),
|
||||
"http://localhost:4000",
|
||||
],
|
||||
plugins: [
|
||||
// Cloudflare Turnstile: yalnızca secret tanımlıysa aktif. sign-in/sign-up
|
||||
// uçları "x-captcha-response" header'ındaki token ile doğrulanır.
|
||||
...(process.env.TURNSTILE_SECRET_KEY
|
||||
? [
|
||||
captcha({
|
||||
provider: "cloudflare-turnstile",
|
||||
secretKey: process.env.TURNSTILE_SECRET_KEY,
|
||||
endpoints: ["/sign-in/email", "/sign-up/email"],
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
});
|
||||
|
||||
return authInstance;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Turnstile, type TurnstileHandle } from "@/components/turnstile";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { signIn } from "@/lib/auth-client";
|
||||
import { startAction } from "@/lib/faro";
|
||||
@@ -7,7 +8,7 @@ import { Button } from "@sase/ui";
|
||||
import { Input } from "@sase/ui";
|
||||
import { Label } from "@sase/ui";
|
||||
import { Link, createFileRoute } from "@tanstack/react-router";
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export const Route = createFileRoute("/_auth/login")({
|
||||
component: LoginPage,
|
||||
@@ -16,16 +17,27 @@ export const Route = createFileRoute("/_auth/login")({
|
||||
function LoginPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [rememberMe, setRememberMe] = useState(true);
|
||||
const [captchaToken, setCaptchaToken] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const turnstileRef = useRef<TurnstileHandle>(null);
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
startAction("login", { method: "email" });
|
||||
setLoading(true);
|
||||
|
||||
const { error } = await signIn.email({ email, password });
|
||||
const { error } = await signIn.email({
|
||||
email,
|
||||
password,
|
||||
rememberMe,
|
||||
fetchOptions: captchaToken ? { headers: { "x-captcha-response": captchaToken } } : undefined,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
// Token tek kullanımlık — başarısız denemeden sonra widget'ı sıfırla
|
||||
turnstileRef.current?.reset();
|
||||
setCaptchaToken("");
|
||||
try {
|
||||
const { exists } = await api.post<{ exists: boolean }>("/users/check-email", { email });
|
||||
if (!exists) {
|
||||
@@ -123,15 +135,27 @@ function LoginPage() {
|
||||
|
||||
{/* Remember me + Forgot password */}
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" className="size-4 rounded border-input accent-primary" />
|
||||
Beni hatırla
|
||||
<label htmlFor="rememberMe" className="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
id="rememberMe"
|
||||
type="checkbox"
|
||||
checked={rememberMe}
|
||||
onChange={(e) => setRememberMe(e.target.checked)}
|
||||
className="size-4 rounded border-input accent-primary"
|
||||
/>
|
||||
Beni 30 gün hatırla
|
||||
</label>
|
||||
<Link to="/forgot-password" className="text-sm text-muted-foreground hover:underline">
|
||||
Şifremi Unuttum
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Turnstile
|
||||
ref={turnstileRef}
|
||||
onVerify={setCaptchaToken}
|
||||
onExpire={() => setCaptchaToken("")}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? "Giriş yapılıyor..." : "Giriş Yap"}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user