feat(auth): Appwrite-backed sign-in / sign-up / forgot-password + middleware guard
- Server actions in lib/appwrite/auth-actions.ts: signInAction, signUpAction, forgotPasswordAction, signOutAction All use node-appwrite admin client; session secret stored as httpOnly cookie (isletmem-session). Errors localized to Turkish. - Redesigned /sign-in and /sign-up using sign-in-3 split-card layout, branded as 'İşletmem' with gradient brand panel (no external image). Removed social login buttons (email/password only for now). - /forgot-password localized; success state shows email-sent confirmation. - Auth pages redirect to /dashboard if user already has a session. - middleware.ts: * Protects /dashboard, /onboarding, /settings — redirects to /sign-in?redirect=... * Auth pages redirect logged-in users to /dashboard * Keeps legacy /login and /register redirects
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"use server";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { AppwriteException, ID } from "node-appwrite";
|
||||
|
||||
import { APPWRITE_SESSION_COOKIE, createAdminClient, createSessionClient } from "./server";
|
||||
|
||||
export type AuthState = {
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
const initial: AuthState = { ok: false };
|
||||
|
||||
function appwriteError(e: unknown): string {
|
||||
if (e instanceof AppwriteException) {
|
||||
switch (e.type) {
|
||||
case "user_invalid_credentials":
|
||||
return "Email veya şifre hatalı.";
|
||||
case "user_blocked":
|
||||
return "Hesabınız engellenmiş.";
|
||||
case "user_already_exists":
|
||||
case "user_email_already_exists":
|
||||
return "Bu email ile zaten bir hesap var.";
|
||||
case "user_password_mismatch":
|
||||
return "Şifreler eşleşmiyor.";
|
||||
case "general_rate_limit_exceeded":
|
||||
return "Çok fazla deneme. Birkaç dakika sonra tekrar deneyin.";
|
||||
default:
|
||||
return e.message || "Beklenmeyen bir hata oluştu.";
|
||||
}
|
||||
}
|
||||
return "Bağlantı hatası. Tekrar deneyin.";
|
||||
}
|
||||
|
||||
async function setSessionCookie(secret: string, expire: string) {
|
||||
(await cookies()).set(APPWRITE_SESSION_COOKIE, secret, {
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
expires: new Date(expire),
|
||||
});
|
||||
}
|
||||
|
||||
export async function signInAction(_prev: AuthState, formData: FormData): Promise<AuthState> {
|
||||
const email = String(formData.get("email") ?? "").trim();
|
||||
const password = String(formData.get("password") ?? "");
|
||||
|
||||
if (!email || !password) {
|
||||
return { ok: false, error: "Email ve şifre zorunlu." };
|
||||
}
|
||||
|
||||
try {
|
||||
const { account } = createAdminClient();
|
||||
const session = await account.createEmailPasswordSession(email, password);
|
||||
await setSessionCookie(session.secret, session.expire);
|
||||
} catch (e) {
|
||||
return { ok: false, error: appwriteError(e) };
|
||||
}
|
||||
|
||||
redirect("/dashboard");
|
||||
}
|
||||
|
||||
export async function signUpAction(_prev: AuthState, formData: FormData): Promise<AuthState> {
|
||||
const name = String(formData.get("name") ?? "").trim();
|
||||
const email = String(formData.get("email") ?? "").trim();
|
||||
const password = String(formData.get("password") ?? "");
|
||||
|
||||
if (!name || !email || !password) {
|
||||
return { ok: false, error: "Tüm alanlar zorunlu." };
|
||||
}
|
||||
if (password.length < 8) {
|
||||
return { ok: false, error: "Şifre en az 8 karakter olmalı." };
|
||||
}
|
||||
|
||||
try {
|
||||
const { account } = createAdminClient();
|
||||
await account.create(ID.unique(), email, password, name);
|
||||
const session = await account.createEmailPasswordSession(email, password);
|
||||
await setSessionCookie(session.secret, session.expire);
|
||||
} catch (e) {
|
||||
return { ok: false, error: appwriteError(e) };
|
||||
}
|
||||
|
||||
redirect("/onboarding");
|
||||
}
|
||||
|
||||
export async function forgotPasswordAction(
|
||||
_prev: AuthState,
|
||||
formData: FormData,
|
||||
): Promise<AuthState> {
|
||||
const email = String(formData.get("email") ?? "").trim();
|
||||
if (!email) return { ok: false, error: "Email zorunlu." };
|
||||
|
||||
try {
|
||||
const { account } = createAdminClient();
|
||||
const recoveryUrl = `${process.env.APP_URL ?? "http://localhost:3000"}/reset-password`;
|
||||
await account.createRecovery(email, recoveryUrl);
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
return { ok: false, error: appwriteError(e) };
|
||||
}
|
||||
}
|
||||
|
||||
export async function signOutAction() {
|
||||
try {
|
||||
const { account } = await createSessionClient();
|
||||
await account.deleteSession("current");
|
||||
} catch {
|
||||
// ignore — cookie will be cleared anyway
|
||||
}
|
||||
(await cookies()).delete(APPWRITE_SESSION_COOKIE);
|
||||
redirect("/sign-in");
|
||||
}
|
||||
|
||||
export const initialAuthState = initial;
|
||||
Reference in New Issue
Block a user