180 lines
6.4 KiB
TypeScript
180 lines
6.4 KiB
TypeScript
import {
|
|
createUser,
|
|
findUserByUsername,
|
|
validatePassword,
|
|
createSession,
|
|
createGuestSession,
|
|
deleteSession,
|
|
getUserPermissions,
|
|
getAllUsers,
|
|
grantPermission,
|
|
revokePermission,
|
|
} from "../db";
|
|
import {
|
|
getUser,
|
|
requirePermission,
|
|
setSessionCookie,
|
|
clearSessionCookie,
|
|
getClientInfo,
|
|
} from "../auth";
|
|
import { config } from "../config";
|
|
import { state } from "../state";
|
|
import { getOrCreateUser, userHasPermission } from "./helpers";
|
|
|
|
// Auth: signup
|
|
export async function handleSignup(req: Request, server: any): Promise<Response> {
|
|
try {
|
|
const { username, password } = await req.json();
|
|
if (!username || !password) {
|
|
return Response.json({ error: "Username and password required" }, { status: 400 });
|
|
}
|
|
if (username.length < 3 || password.length < 6) {
|
|
return Response.json({ error: "Username min 3 chars, password min 6 chars" }, { status: 400 });
|
|
}
|
|
const existing = findUserByUsername(username);
|
|
if (existing) {
|
|
return Response.json({ error: "Username already taken" }, { status: 400 });
|
|
}
|
|
const user = await createUser(username, password);
|
|
const userAgent = req.headers.get("user-agent") ?? undefined;
|
|
const ipAddress = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim()
|
|
?? req.headers.get("x-real-ip")
|
|
?? server.requestIP(req)?.address
|
|
?? undefined;
|
|
const token = createSession(user.id, userAgent, ipAddress);
|
|
console.log(`[AUTH] Signup: user="${username}" id=${user.id} admin=${user.is_admin} session=${token} ip=${ipAddress} ua="${userAgent?.slice(0, 50)}..."`);
|
|
state.library.logActivity("account_created", { title: user.is_admin ? "admin" : "user" }, { id: user.id, username: user.username });
|
|
return Response.json(
|
|
{ user: { id: user.id, username: user.username, isAdmin: user.is_admin } },
|
|
{ headers: { "Set-Cookie": setSessionCookie(token) } }
|
|
);
|
|
} catch (e) {
|
|
return Response.json({ error: "Signup failed" }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// Auth: login
|
|
export async function handleLogin(req: Request, server: any): Promise<Response> {
|
|
try {
|
|
const { username, password } = await req.json();
|
|
const user = findUserByUsername(username);
|
|
if (!user || !(await validatePassword(user, password))) {
|
|
console.log(`[AUTH] Login failed: user="${username}"`);
|
|
return Response.json({ error: "Invalid username or password" }, { status: 401 });
|
|
}
|
|
const userAgent = req.headers.get("user-agent") ?? undefined;
|
|
const ipAddress = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim()
|
|
?? req.headers.get("x-real-ip")
|
|
?? server.requestIP(req)?.address
|
|
?? undefined;
|
|
const token = createSession(user.id, userAgent, ipAddress);
|
|
console.log(`[AUTH] Login: user="${username}" id=${user.id} session=${token} ip=${ipAddress} ua="${userAgent?.slice(0, 50)}..."`);
|
|
return Response.json(
|
|
{ user: { id: user.id, username: user.username, isAdmin: user.is_admin } },
|
|
{ headers: { "Set-Cookie": setSessionCookie(token) } }
|
|
);
|
|
} catch (e) {
|
|
return Response.json({ error: "Login failed" }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// Auth: logout
|
|
export function handleLogout(req: Request): Response {
|
|
const token = req.headers.get("cookie")?.match(/blastoise_session=([^;]+)/)?.[1];
|
|
if (token) {
|
|
const { validateSession } = require("../db");
|
|
const user = validateSession(token);
|
|
console.log(`[AUTH] Logout: user="${user?.username ?? "unknown"}" session=${token}`);
|
|
deleteSession(token);
|
|
}
|
|
return Response.json(
|
|
{ success: true },
|
|
{ headers: { "Set-Cookie": clearSessionCookie() } }
|
|
);
|
|
}
|
|
|
|
// Auth: get current user
|
|
export function handleGetMe(req: Request, server: any): Response {
|
|
const { user, headers } = getOrCreateUser(req, server);
|
|
if (!user) {
|
|
return Response.json({ user: null });
|
|
}
|
|
const permissions = getUserPermissions(user.id);
|
|
const effectivePermissions = [...permissions];
|
|
if (config.defaultPermissions) {
|
|
for (const perm of config.defaultPermissions) {
|
|
if (user.is_guest && perm === "control") continue;
|
|
effectivePermissions.push({
|
|
id: 0,
|
|
user_id: user.id,
|
|
resource_type: "channel",
|
|
resource_id: null,
|
|
permission: perm,
|
|
});
|
|
}
|
|
}
|
|
return Response.json({
|
|
user: { id: user.id, username: user.username, isAdmin: user.is_admin, isGuest: user.is_guest },
|
|
permissions: effectivePermissions,
|
|
}, { headers });
|
|
}
|
|
|
|
// Kick all other clients for current user
|
|
export function handleKickOthers(req: Request, server: any): Response {
|
|
const { user } = getOrCreateUser(req, server);
|
|
if (!user) {
|
|
return Response.json({ error: "Not authenticated" }, { status: 401 });
|
|
}
|
|
|
|
const connections = state.userConnections.get(user.id);
|
|
if (!connections || connections.size === 0) {
|
|
return Response.json({ kicked: 0 });
|
|
}
|
|
|
|
let kickedCount = 0;
|
|
for (const ws of connections) {
|
|
ws.send(JSON.stringify({ type: "kick", reason: "Kicked by another session" }));
|
|
kickedCount++;
|
|
}
|
|
|
|
console.log(`[Kick] User ${user.username} kicked ${kickedCount} other clients`);
|
|
return Response.json({ kicked: kickedCount });
|
|
}
|
|
|
|
// Admin: list users
|
|
export function handleListUsers(req: Request, server: any): Response {
|
|
try {
|
|
requirePermission(req, "global", null, "admin", server);
|
|
return Response.json(getAllUsers());
|
|
} catch (e) {
|
|
if (e instanceof Response) return e;
|
|
return Response.json({ error: "Failed" }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// Admin: grant permission
|
|
export async function handleGrantPermission(req: Request, server: any, userId: number): Promise<Response> {
|
|
try {
|
|
requirePermission(req, "global", null, "admin", server);
|
|
const { resourceType, resourceId, permission } = await req.json();
|
|
grantPermission(userId, resourceType, resourceId, permission);
|
|
return Response.json({ success: true });
|
|
} catch (e) {
|
|
if (e instanceof Response) return e;
|
|
return Response.json({ error: "Failed" }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
// Admin: revoke permission
|
|
export async function handleRevokePermission(req: Request, server: any, userId: number): Promise<Response> {
|
|
try {
|
|
requirePermission(req, "global", null, "admin", server);
|
|
const { resourceType, resourceId, permission } = await req.json();
|
|
revokePermission(userId, resourceType, resourceId, permission);
|
|
return Response.json({ success: true });
|
|
} catch (e) {
|
|
if (e instanceof Response) return e;
|
|
return Response.json({ error: "Failed" }, { status: 500 });
|
|
}
|
|
}
|