feat(auth): "beni 30 gün hatırla" + login Turnstile koruması

- session.expiresIn = 30 gün; login'de rememberMe checkbox signIn.email'e bağlandı
  (işaretli=30 gün kalıcı çerez, değilse oturum çerezi)
- better-auth captcha plugin'i (cloudflare-turnstile, TURNSTILE_SECRET_KEY varsa)
  sign-in/sign-up'ı korur; login formuna Turnstile widget'ı + x-captcha-response

Not: auth.ts ve login.tsx her iki özelliği birlikte içerdiğinden tek commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 02:14:18 +03:00
parent a09182fb98
commit 7eb2e85bbc
2 changed files with 46 additions and 5 deletions

View File

@@ -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>