first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:09:32 +03:00
commit 71eff2d979
78 changed files with 10173 additions and 0 deletions

170
app/lib/api-auth.ts Normal file
View File

@@ -0,0 +1,170 @@
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/db";
import { apiKeys, user } from "@/db/schema";
import { eq, and } from "drizzle-orm";
import { verifyJWT, isValidAPIKeyFormat } from "./jwt";
import { UserRole } from "./permissions";
export interface AuthenticatedRequest extends NextRequest {
userId?: string;
email?: string;
role?: UserRole;
}
export interface AuthResult {
authenticated: boolean;
userId?: string;
email?: string;
role?: UserRole;
error?: string;
}
/**
* API isteklerini doğrula (JWT token veya API key ile)
*
* Kullanım:
* const authResult = await authenticateAPIRequest(request);
* if (!authResult.authenticated) {
* return NextResponse.json({ error: authResult.error }, { status: 401 });
* }
* const userId = authResult.userId;
*/
export async function authenticateAPIRequest(request: NextRequest): Promise<AuthResult> {
const authHeader = request.headers.get("authorization");
if (!authHeader) {
return {
authenticated: false,
error: "Authorization header eksik. Bearer token veya API key gerekli.",
};
}
// Bearer token kontrolü
if (authHeader.startsWith("Bearer ")) {
const token = authHeader.substring(7);
// JWT token mu yoksa API key mi?
if (isValidAPIKeyFormat(token)) {
// API Key doğrulama
return await validateAPIKey(token);
} else {
// JWT token doğrulama
return await validateJWTToken(token);
}
}
return {
authenticated: false,
error: "Geçersiz authorization formatı. 'Bearer <token>' formatında olmalı.",
};
}
/**
* JWT token doğrula ve kullanıcı bilgilerini getir
*/
async function validateJWTToken(token: string): Promise<AuthResult> {
const payload = verifyJWT(token);
if (!payload) {
return {
authenticated: false,
error: "Geçersiz veya süresi dolmuş token.",
};
}
// Kullanıcı bilgilerini DB'den al (role için)
try {
const users = await db
.select()
.from(user)
.where(eq(user.id, payload.userId))
.limit(1);
if (users.length === 0) {
return {
authenticated: false,
error: "Kullanıcı bulunamadı.",
};
}
const userData = users[0];
return {
authenticated: true,
userId: payload.userId,
email: payload.email,
role: (userData.role as UserRole) || "user",
};
} catch (error) {
console.error("User lookup error:", error);
return {
authenticated: false,
error: "Kimlik doğrulama sırasında bir hata oluştu.",
};
}
}
/**
* API key doğrula (veritabanından kontrol)
*/
async function validateAPIKey(key: string): Promise<AuthResult> {
try {
const apiKey = await db
.select()
.from(apiKeys)
.where(and(eq(apiKeys.key, key), eq(apiKeys.isActive, true)))
.limit(1);
if (apiKey.length === 0) {
return {
authenticated: false,
error: "Geçersiz API key.",
};
}
const keyData = apiKey[0];
// Süre kontrolü
if (keyData.expiresAt && keyData.expiresAt < new Date()) {
return {
authenticated: false,
error: "API key süresi dolmuş.",
};
}
// Kullanıcı bilgilerini al
const users = await db
.select()
.from(user)
.where(eq(user.id, keyData.userId))
.limit(1);
if (users.length === 0) {
return {
authenticated: false,
error: "Kullanıcı bulunamadı.",
};
}
const userData = users[0];
// Son kullanım tarihini güncelle (opsiyonel)
await db
.update(apiKeys)
.set({ lastUsedAt: new Date() })
.where(eq(apiKeys.id, keyData.id));
return {
authenticated: true,
userId: keyData.userId,
email: userData.email,
role: (userData.role as UserRole) || "user",
};
} catch (error) {
console.error("API key doğrulama hatası:", error);
return {
authenticated: false,
error: "Kimlik doğrulama sırasında bir hata oluştu.",
};
}
}

37
app/lib/auth.ts Normal file
View File

@@ -0,0 +1,37 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
import * as schema from "@/db/schema";
// Validate BETTER_AUTH_SECRET at runtime (not during build)
const secret = process.env.BETTER_AUTH_SECRET;
if (!secret && process.env.NODE_ENV === "production") {
console.warn("WARNING: BETTER_AUTH_SECRET is not set. Authentication will not work properly.");
}
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
schema: {
user: schema.user,
session: schema.session,
account: schema.account,
verification: schema.verification,
},
}),
emailAndPassword: {
enabled: true,
},
secret: secret || "build-time-secret-key-minimum-32-characters-long-temp",
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
user: {
additionalFields: {
role: {
type: "string",
defaultValue: "user",
required: false,
input: false, // Don't allow setting role on signup
},
},
},
});

48
app/lib/jwt.ts Normal file
View File

@@ -0,0 +1,48 @@
import jwt, { SignOptions } from "jsonwebtoken";
import { nanoid } from "nanoid";
const JWT_SECRET = process.env.JWT_SECRET || process.env.BETTER_AUTH_SECRET || "fallback-secret-key";
const API_KEY_PREFIX = "img_";
export interface JWTPayload {
userId: string;
email: string;
type: "access" | "refresh";
}
/**
* JWT token oluştur
* @param payload - Token içeriği
* @param expiresIn - Geçerlilik süresi (örn: "7d", "1h")
*/
export function signJWT(payload: JWTPayload, expiresIn: string | number = "7d"): string {
return jwt.sign(payload, JWT_SECRET, { expiresIn } as SignOptions);
}
/**
* JWT token doğrula
* @param token - Doğrulanacak token
*/
export function verifyJWT(token: string): JWTPayload | null {
try {
const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload;
return decoded;
} catch (error) {
return null;
}
}
/**
* API key oluştur
* Formad: img_xxxxxxxxxxxxxxxxxxxxxxxx
*/
export function generateAPIKey(): string {
return `${API_KEY_PREFIX}${nanoid(32)}`;
}
/**
* API key validasyonu
*/
export function isValidAPIKeyFormat(key: string): boolean {
return key.startsWith(API_KEY_PREFIX) && key.length === 36; // img_ + 32 chars
}

View File

@@ -0,0 +1,15 @@
next js beter auth yuklu ve drizze orm yuklu posgrsql veritabanı ile entegre edilecek.
drizzle orm ile veritabanına bağlanılacak.
beter auth ile register yapılacak.
beter auth ile giriş yapılacak.
giriş yapıldıktan sonra kullanıcının bilgileri veritabanından alınacak.
kullanıcının bilgileri veritabanından alındıktan sonra kullanıcının bilgileri sayfada görüntülenecek.
birkaç yapilandirma eklendi ama duzgun olmayabilir sen kotrol et ve duzelt
sadece login olmus userlerin giris yapabilecegi bir sayfa olacak. ve sayfada resim dosyalri yuklenecek en boy kalite format vs kullnacini verdigi bilgilere gore
resim manipule edilecek ve database ye drizzle orm ile kaydedilecek.
resim url si çıkartilarak download edilebilir ve bir buton ile resmin url si kopyalanabilir. olacak ve bu url ile resim indirilebilir.

93
app/lib/permissions.ts Normal file
View File

@@ -0,0 +1,93 @@
import { db } from "@/db";
import { user } from "@/db/schema";
import { eq } from "drizzle-orm";
export type UserRole = "user" | "admin" | "moderator";
// Permission tanımları
export const PERMISSIONS = {
// Image permissions
IMAGE_UPLOAD: "image:upload",
IMAGE_DELETE_OWN: "image:delete:own",
IMAGE_DELETE_ANY: "image:delete:any",
IMAGE_VIEW_OWN: "image:view:own",
IMAGE_VIEW_ANY: "image:view:any",
// User permissions
USER_VIEW: "user:view",
USER_EDIT: "user:edit",
USER_DELETE: "user:delete",
USER_MANAGE_ROLES: "user:manage:roles",
} as const;
// Role'lere göre izinler
export const ROLE_PERMISSIONS: Record<UserRole, string[]> = {
user: [
PERMISSIONS.IMAGE_UPLOAD,
PERMISSIONS.IMAGE_DELETE_OWN,
PERMISSIONS.IMAGE_VIEW_OWN,
],
moderator: [
PERMISSIONS.IMAGE_UPLOAD,
PERMISSIONS.IMAGE_DELETE_OWN,
PERMISSIONS.IMAGE_VIEW_OWN,
PERMISSIONS.IMAGE_VIEW_ANY,
PERMISSIONS.USER_VIEW,
],
admin: Object.values(PERMISSIONS), // Tüm izinler
};
/**
* Kullanıcının belirli bir role sahip olup olmadığını kontrol eder
*/
export function hasRole(userRole: UserRole, requiredRole: UserRole | UserRole[]): boolean {
const roles = Array.isArray(requiredRole) ? requiredRole : [requiredRole];
return roles.includes(userRole);
}
/**
* Kullanıcının belirli bir izne sahip olup olmadığını kontrol eder
*/
export function hasPermission(userRole: UserRole, permission: string): boolean {
const rolePermissions = ROLE_PERMISSIONS[userRole] || [];
return rolePermissions.includes(permission);
}
/**
* Kullanıcının birden fazla izne sahip olup olmadığını kontrol eder
*/
export function hasPermissions(userRole: UserRole, permissions: string[]): boolean {
return permissions.every(permission => hasPermission(userRole, permission));
}
/**
* Kullanıcının en az bir izne sahip olup olmadığını kontrol eder
*/
export function hasAnyPermission(userRole: UserRole, permissions: string[]): boolean {
return permissions.some(permission => hasPermission(userRole, permission));
}
/**
* Kullanıcının admin olup olmadığını kontrol eder
*/
export function isAdmin(userRole: UserRole): boolean {
return userRole === "admin";
}
/**
* Kullanıcı bilgilerini userId'den alır
*/
export async function getUserById(userId: string) {
const users = await db.select().from(user).where(eq(user.id, userId)).limit(1);
return users[0] || null;
}
/**
* Kullanıcının rolünü günceller (sadece admin yapabilir)
*/
export async function updateUserRole(userId: string, newRole: UserRole) {
await db.update(user).set({
role: newRole,
updatedAt: new Date()
}).where(eq(user.id, userId));
}