Panel şu ana kadar herkese açıktı — URL'yi bilen herkes kanal ekleyip/silebilir, cookie güncelleyebilir, kayıt indirebilirdi. - users tablosu (bcrypt şifre hash'i) - /login: hiç kullanıcı yoksa "ilk admin hesabı oluştur" formu, varsa normal giriş formu - jose ile imzalanmış, httpOnly çerezde tutulan 30 günlük oturum - middleware.ts: /login hariç tüm rotaları korur (video stream route'u dahil — aynı origin istekleri çerezi otomatik taşır) - layout: oturum yoksa nav hiç gösterilmiyor, varsa kullanıcı adı + çıkış butonu ekleniyor SESSION_SECRET production'da zorunlu (docker-compose derleme zamanında kontrol ediyor); Coolify'a rastgele bir değer eklendi. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
61 lines
1.8 KiB
TypeScript
61 lines
1.8 KiB
TypeScript
"use server";
|
||
|
||
import { cookies } from "next/headers";
|
||
import { redirect } from "next/navigation";
|
||
import bcrypt from "bcryptjs";
|
||
import { prisma } from "@streamclipper/db";
|
||
import { createSessionToken, SESSION_COOKIE_NAME } from "../../lib/auth";
|
||
|
||
const SESSION_MAX_AGE_SEC = 60 * 60 * 24 * 30;
|
||
|
||
async function setSessionCookie(userId: string, username: string) {
|
||
const token = await createSessionToken({ sub: userId, username });
|
||
const jar = await cookies();
|
||
jar.set(SESSION_COOKIE_NAME, token, {
|
||
httpOnly: true,
|
||
secure: process.env.NODE_ENV === "production",
|
||
sameSite: "lax",
|
||
path: "/",
|
||
maxAge: SESSION_MAX_AGE_SEC,
|
||
});
|
||
}
|
||
|
||
export async function bootstrapAdmin(formData: FormData) {
|
||
const username = String(formData.get("username") ?? "").trim();
|
||
const password = String(formData.get("password") ?? "");
|
||
|
||
if (!username || password.length < 8) {
|
||
throw new Error("Kullanıcı adı gerekli, şifre en az 8 karakter olmalı.");
|
||
}
|
||
|
||
const existing = await prisma.user.count();
|
||
if (existing > 0) {
|
||
redirect("/login");
|
||
}
|
||
|
||
const passwordHash = await bcrypt.hash(password, 12);
|
||
const user = await prisma.user.create({ data: { username, passwordHash } });
|
||
|
||
await setSessionCookie(user.id, user.username);
|
||
redirect("/");
|
||
}
|
||
|
||
export async function login(formData: FormData) {
|
||
const username = String(formData.get("username") ?? "").trim();
|
||
const password = String(formData.get("password") ?? "");
|
||
|
||
const user = await prisma.user.findUnique({ where: { username } });
|
||
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
|
||
redirect("/login?error=1");
|
||
}
|
||
|
||
await setSessionCookie(user.id, user.username);
|
||
redirect("/");
|
||
}
|
||
|
||
export async function logout() {
|
||
const jar = await cookies();
|
||
jar.delete(SESSION_COOKIE_NAME);
|
||
redirect("/login");
|
||
}
|