first commit
This commit is contained in:
176
lib/auth-api.ts
Normal file
176
lib/auth-api.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Auth API client for login, register, me, refresh.
|
||||
* Uses NEXT_PUBLIC_BASE_API_URL on client (defaults to same as BASE_API_URL for server).
|
||||
*/
|
||||
|
||||
const getBaseUrl = () =>
|
||||
typeof window !== "undefined"
|
||||
? process.env.NEXT_PUBLIC_BASE_API_URL ?? "http://127.0.0.1:8080"
|
||||
: process.env.BASE_API_URL ?? process.env.NEXT_PUBLIC_BASE_API_URL ?? "http://127.0.0.1:8080";
|
||||
|
||||
const API_PREFIX = "/api/v1/auth";
|
||||
|
||||
export interface AuthUser {
|
||||
id: number;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username?: string;
|
||||
is_admin: boolean;
|
||||
email_verified?: boolean;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
user: AuthUser;
|
||||
}
|
||||
|
||||
export interface RegisterResponse {
|
||||
message: string;
|
||||
user: AuthUser;
|
||||
}
|
||||
|
||||
export interface RefreshResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
/** POST /api/v1/auth/login – doğrudan backend (token client’ta; cookie için loginViaCookie kullanın) */
|
||||
export async function login(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<LoginResponse> {
|
||||
const base = getBaseUrl();
|
||||
const res = await fetch(`${base}${API_PREFIX}/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", accept: "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error((err as { detail?: string }).detail ?? "Giriş başarısız");
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/** Cookie tabanlı giriş – token’lar HTTP-only secure cookie’de saklanır */
|
||||
export async function loginViaCookie(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<{ user: AuthUser }> {
|
||||
const res = await fetch("/api/auth/cookie-login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: email.trim(), password }),
|
||||
credentials: "include",
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
throw new Error((data as { error?: string }).error ?? "Giriş başarısız");
|
||||
}
|
||||
return data as { user: AuthUser };
|
||||
}
|
||||
|
||||
/** Cookie tabanlı çıkış */
|
||||
export async function logoutViaCookie(): Promise<void> {
|
||||
await fetch("/api/auth/cookie-logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
}
|
||||
|
||||
/** Cookie oturumunu kontrol et (client tarafında oturum bilgisi için) */
|
||||
export async function getCookieSession(): Promise<{
|
||||
loggedIn: boolean;
|
||||
user?: AuthUser;
|
||||
}> {
|
||||
const res = await fetch("/api/auth/cookie-session", { credentials: "include" });
|
||||
const data = await res.json().catch(() => ({ loggedIn: false }));
|
||||
return data as { loggedIn: boolean; user?: AuthUser };
|
||||
}
|
||||
|
||||
/** POST /api/v1/auth/register */
|
||||
export async function register(body: {
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
password: string;
|
||||
username: string;
|
||||
}): Promise<RegisterResponse> {
|
||||
const base = getBaseUrl();
|
||||
const res = await fetch(`${base}${API_PREFIX}/register`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", accept: "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
const detail = (err as { detail?: string | string[] }).detail;
|
||||
const message = Array.isArray(detail) ? detail.join(" ") : detail ?? "Kayıt başarısız";
|
||||
throw new Error(message);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/** GET /api/v1/auth/me */
|
||||
export async function me(accessToken: string): Promise<{ user: AuthUser }> {
|
||||
const base = getBaseUrl();
|
||||
const res = await fetch(`${base}${API_PREFIX}/me`, {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
if (!res.ok) throw new Error("Oturum bilgisi alınamadı");
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/** POST /api/v1/auth/refresh */
|
||||
export async function refresh(refreshToken: string): Promise<RefreshResponse> {
|
||||
const base = getBaseUrl();
|
||||
const res = await fetch(`${base}${API_PREFIX}/refresh`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", accept: "application/json" },
|
||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||
});
|
||||
if (!res.ok) throw new Error("Token yenilenemedi");
|
||||
return res.json();
|
||||
}
|
||||
|
||||
const ACCESS_KEY = "auth_access_token";
|
||||
const REFRESH_KEY = "auth_refresh_token";
|
||||
|
||||
const AUTH_CHANGE_EVENT = "auth-change";
|
||||
|
||||
/** Header vb. bileşenlerin oturum değişikliğini algılaması için tetiklenir. */
|
||||
export function notifyAuthChange(): void {
|
||||
if (typeof window === "undefined") return;
|
||||
window.dispatchEvent(new Event(AUTH_CHANGE_EVENT));
|
||||
}
|
||||
|
||||
export function setTokens(access: string, refreshToken: string): void {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.setItem(ACCESS_KEY, access);
|
||||
localStorage.setItem(REFRESH_KEY, refreshToken);
|
||||
notifyAuthChange();
|
||||
}
|
||||
|
||||
export function getAccessToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
return localStorage.getItem(ACCESS_KEY);
|
||||
}
|
||||
|
||||
export function getRefreshToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
return localStorage.getItem(REFRESH_KEY);
|
||||
}
|
||||
|
||||
export function clearTokens(): void {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.removeItem(ACCESS_KEY);
|
||||
localStorage.removeItem(REFRESH_KEY);
|
||||
notifyAuthChange();
|
||||
}
|
||||
|
||||
export { AUTH_CHANGE_EVENT };
|
||||
25
lib/auth-cookies.ts
Normal file
25
lib/auth-cookies.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Cookie adları ve ayarları – JWT için HTTP-only secure cookie.
|
||||
* Sadece sunucu tarafında (API route) kullanılır.
|
||||
*/
|
||||
|
||||
const COOKIE_ACCESS = "auth_access_token";
|
||||
const COOKIE_REFRESH = "auth_refresh_token";
|
||||
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
const COOKIE_OPTS = {
|
||||
httpOnly: true,
|
||||
secure: isProd,
|
||||
sameSite: "lax" as const,
|
||||
path: "/",
|
||||
};
|
||||
const ACCESS_MAX_AGE = 24 * 60 * 60; // 1 gün
|
||||
const REFRESH_MAX_AGE = 30 * 24 * 60 * 60; // 30 gün
|
||||
|
||||
export {
|
||||
COOKIE_ACCESS,
|
||||
COOKIE_REFRESH,
|
||||
COOKIE_OPTS,
|
||||
ACCESS_MAX_AGE,
|
||||
REFRESH_MAX_AGE,
|
||||
};
|
||||
53
lib/auth-options.ts
Normal file
53
lib/auth-options.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { NextAuthOptions } from "next-auth";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
import GitHubProvider from "next-auth/providers/github";
|
||||
|
||||
/**
|
||||
* NextAuth options: Google & GitHub OAuth.
|
||||
* Env: NEXTAUTH_SECRET veya NEXT_AUTH_SECRET, NEXTAUTH_URL,
|
||||
* GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
|
||||
* GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET
|
||||
*/
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
secret: process.env.NEXTAUTH_SECRET ?? process.env.NEXT_AUTH_SECRET,
|
||||
useSecureCookies: isProd,
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
name: `${isProd ? "__Secure-" : ""}next-auth.session-token`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
secure: isProd,
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 gün
|
||||
},
|
||||
},
|
||||
},
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
|
||||
}),
|
||||
GitHubProvider({
|
||||
clientId: process.env.GITHUB_CLIENT_ID ?? "",
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET ?? "",
|
||||
authorization: {
|
||||
params: {
|
||||
scope: "user:email",
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
pages: {
|
||||
signIn: "/auth/login",
|
||||
},
|
||||
callbacks: {
|
||||
redirect({ url, baseUrl }) {
|
||||
if (url.startsWith("/")) return `${baseUrl}${url}`;
|
||||
if (new URL(url).origin === baseUrl) return url;
|
||||
return baseUrl;
|
||||
},
|
||||
},
|
||||
};
|
||||
6
lib/utils.ts
Normal file
6
lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user