Add complete CKE Project implementation with news management system

This commit is contained in:
albivkt
2025-07-13 01:34:11 +03:00
parent c9317555ca
commit a84810c6b9
32 changed files with 8901 additions and 811 deletions

250
lib/auth.ts Normal file
View File

@ -0,0 +1,250 @@
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import { NextRequest } from 'next/server';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export interface User {
id: string;
email: string;
username: string;
role: 'USER' | 'ADMIN' | 'EDITOR';
name?: string;
avatar?: string;
}
export interface AuthContext {
user: User | null;
isAuthenticated: boolean;
}
export const JWT_SECRET = process.env.NEXTAUTH_SECRET || 'your-secret-key';
export async function hashPassword(password: string): Promise<string> {
return await bcrypt.hash(password, 12);
}
export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
return await bcrypt.compare(password, hashedPassword);
}
export function generateToken(userId: string): string {
return jwt.sign({ userId }, JWT_SECRET, { expiresIn: '7d' });
}
export function verifyToken(token: string): { userId: string } | null {
try {
const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };
return decoded;
} catch (error) {
return null;
}
}
export async function getUserFromToken(token: string): Promise<User | null> {
const decoded = verifyToken(token);
if (!decoded) return null;
try {
const user = await prisma.user.findUnique({
where: { id: decoded.userId },
select: {
id: true,
email: true,
username: true,
role: true,
name: true,
avatar: true
}
});
return user;
} catch (error) {
return null;
}
}
export async function getAuthContext(request: NextRequest): Promise<AuthContext> {
const token = extractTokenFromRequest(request);
if (!token) {
return { user: null, isAuthenticated: false };
}
const user = await getUserFromToken(token);
return {
user,
isAuthenticated: !!user
};
}
export function extractTokenFromRequest(request: NextRequest): string | null {
// Проверяем заголовок Authorization
const authHeader = request.headers.get('authorization');
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
// Проверяем cookie
const tokenCookie = request.cookies.get('auth-token');
if (tokenCookie) {
return tokenCookie.value;
}
return null;
}
export function requireAuth(context: AuthContext): User {
if (!context.isAuthenticated || !context.user) {
throw new Error('Authentication required');
}
return context.user;
}
export function requireRole(context: AuthContext, allowedRoles: string[]): User {
const user = requireAuth(context);
if (!allowedRoles.includes(user.role)) {
throw new Error('Insufficient permissions');
}
return user;
}
export function requireAdmin(context: AuthContext): User {
return requireRole(context, ['ADMIN']);
}
export function requireEditorOrAdmin(context: AuthContext): User {
return requireRole(context, ['EDITOR', 'ADMIN']);
}
export async function authenticateUser(email: string, password: string): Promise<{ user: User; token: string } | null> {
try {
const user = await prisma.user.findUnique({
where: { email },
select: {
id: true,
email: true,
username: true,
role: true,
name: true,
avatar: true,
password: true
}
});
if (!user) {
return null;
}
const isValidPassword = await verifyPassword(password, user.password);
if (!isValidPassword) {
return null;
}
const token = generateToken(user.id);
// Убираем пароль из возвращаемых данных
const { password: _, ...userWithoutPassword } = user;
return {
user: userWithoutPassword,
token
};
} catch (error) {
console.error('Authentication error:', error);
return null;
}
}
export async function registerUser(userData: {
email: string;
username: string;
password: string;
name?: string;
role?: 'USER' | 'ADMIN' | 'EDITOR';
}): Promise<{ user: User; token: string } | null> {
try {
// Проверяем, что пользователь с таким email или username не существует
const existingUser = await prisma.user.findFirst({
where: {
OR: [
{ email: userData.email },
{ username: userData.username }
]
}
});
if (existingUser) {
throw new Error('User already exists');
}
const hashedPassword = await hashPassword(userData.password);
const user = await prisma.user.create({
data: {
email: userData.email,
username: userData.username,
password: hashedPassword,
name: userData.name,
role: userData.role || 'USER'
},
select: {
id: true,
email: true,
username: true,
role: true,
name: true,
avatar: true
}
});
const token = generateToken(user.id);
return {
user,
token
};
} catch (error) {
console.error('Registration error:', error);
return null;
}
}
// Middleware для проверки аутентификации в API routes
export async function withAuth(
handler: (request: NextRequest, context: AuthContext) => Promise<Response>
) {
return async (request: NextRequest) => {
const context = await getAuthContext(request);
return handler(request, context);
};
}
// Middleware для проверки роли в API routes
export async function withRole(
handler: (request: NextRequest, context: AuthContext) => Promise<Response>,
allowedRoles: string[]
) {
return withAuth(async (request: NextRequest, context: AuthContext) => {
try {
requireRole(context, allowedRoles);
return handler(request, context);
} catch (error) {
return new Response(
JSON.stringify({ error: 'Insufficient permissions' }),
{ status: 403, headers: { 'Content-Type': 'application/json' } }
);
}
});
}
// Middleware для проверки админских прав
export async function withAdmin(
handler: (request: NextRequest, context: AuthContext) => Promise<Response>
) {
return withRole(handler, ['ADMIN']);
}