feat: add admin panel, Replicate AI translation, and document translation
- Admin panel (/admin) with JWT auth: configure Replicate API token, JigsawStack API key, model version, enable/disable AI translation, change admin password. Settings persisted in data/settings.json. - Replicate AI translation: POST /api/translate/replicate uses JigsawStack text-translate model via Replicate API. Main page switches to client-side AI translation when enabled. - Document translation tab: supports PDF, DOCX, XLSX, XLS, CSV. Excel/Word formatting fully preserved (SheetJS + JSZip XML manipulation). PDF uses pdf-parse extraction + pdf-lib reconstruction. Column selector UI for tabular data (per-sheet, All/None toggles). - Updated README with full implementation documentation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
55
utils/admin-auth.ts
Normal file
55
utils/admin-auth.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { SignJWT, jwtVerify } from "jose";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { readSettings } from "./settings-store";
|
||||
|
||||
const JWT_COOKIE = "lingva_admin";
|
||||
const JWT_EXPIRY = "8h";
|
||||
|
||||
function getSecret(): Uint8Array {
|
||||
const secret = process.env["ADMIN_JWT_SECRET"] ?? "lingva-admin-secret-change-me";
|
||||
return new TextEncoder().encode(secret);
|
||||
}
|
||||
|
||||
export async function signAdminToken(): Promise<string> {
|
||||
return new SignJWT({ role: "admin" })
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(JWT_EXPIRY)
|
||||
.sign(getSecret());
|
||||
}
|
||||
|
||||
export async function verifyAdminToken(token: string): Promise<boolean> {
|
||||
try {
|
||||
await jwtVerify(token, getSecret());
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function getTokenFromRequest(req: NextApiRequest): string | null {
|
||||
const cookie = req.cookies[JWT_COOKIE];
|
||||
if (cookie) return cookie;
|
||||
const auth = req.headers.authorization;
|
||||
if (auth?.startsWith("Bearer ")) return auth.slice(7);
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function requireAdmin(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
): Promise<boolean> {
|
||||
const token = getTokenFromRequest(req);
|
||||
if (!token || !(await verifyAdminToken(token))) {
|
||||
res.status(401).json({ error: "Unauthorized" });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function checkPassword(password: string): boolean {
|
||||
const settings = readSettings();
|
||||
return password === settings.adminPasswordHash;
|
||||
}
|
||||
|
||||
export const COOKIE_NAME = JWT_COOKIE;
|
||||
Reference in New Issue
Block a user