Add files via upload
This commit is contained in:
1 parent
1bff4c6eea
commit
c3ac407e2e
10 files changed
+697
-1
No files matched your search
@@ -0,0 +1,86 @@
|
||||
// Gemeinsame Helfer für alle vitaAPI-Endpunkte (Vercel Serverless + Neon Postgres)
|
||||
import { neon } from '@neondatabase/serverless';
|
||||
import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
|
||||
|
||||
export const sql = neon(process.env.DATABASE_URL);
|
||||
|
||||
export const ONLINE_SECS = 60;
|
||||
export const MAX_TEXT_LEN = 500;
|
||||
export const MAX_POLL = 50;
|
||||
export const PREVIEW_LEN = 38;
|
||||
|
||||
/* ---------------- Antwort-Protokoll (zeilenbasiert, Pipe-getrennt) ---------------- */
|
||||
|
||||
export function textHead(res) {
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
}
|
||||
|
||||
export function ok(res, body = '') {
|
||||
textHead(res);
|
||||
res.status(200).send(body);
|
||||
}
|
||||
|
||||
export function fail(res, msg) {
|
||||
textHead(res);
|
||||
res.status(200).send(`ERR|${msg}\n`); // 200, damit der Vita-Client die Zeile sicher liest
|
||||
}
|
||||
|
||||
/** Pipes und Zeilenumbrüche aus Nutzdaten entfernen, damit das Protokoll heil bleibt. */
|
||||
export function clean(s) {
|
||||
return String(s).replace(/\|/g, '¦').replace(/[\r\n]+/g, ' ');
|
||||
}
|
||||
|
||||
/* ---------------- Parameter (GET-Query oder POST-Formulardaten) ---------------- */
|
||||
|
||||
export function param(req, key) {
|
||||
let v = req.query?.[key];
|
||||
if (v === undefined && req.body) {
|
||||
if (typeof req.body === 'string') {
|
||||
// Fallback, falls Vercel den Body nicht geparst hat
|
||||
v = new URLSearchParams(req.body).get(key);
|
||||
} else {
|
||||
v = req.body[key];
|
||||
}
|
||||
}
|
||||
return (v ?? '').toString().trim();
|
||||
}
|
||||
|
||||
/* ---------------- Passwörter (scrypt aus Node, keine Extra-Abhängigkeit) ---------------- */
|
||||
|
||||
export function hashPassword(pass) {
|
||||
const salt = randomBytes(16);
|
||||
const hash = scryptSync(pass, salt, 64);
|
||||
return `${salt.toString('hex')}:${hash.toString('hex')}`;
|
||||
}
|
||||
|
||||
export function verifyPassword(pass, stored) {
|
||||
try {
|
||||
const [saltHex, hashHex] = String(stored).split(':');
|
||||
if (!saltHex || !hashHex) return false;
|
||||
const expected = Buffer.from(hashHex, 'hex');
|
||||
const actual = scryptSync(pass, Buffer.from(saltHex, 'hex'), expected.length);
|
||||
return timingSafeEqual(expected, actual);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function newToken() {
|
||||
return randomBytes(16).toString('hex');
|
||||
}
|
||||
|
||||
/* ---------------- Authentifizierung ---------------- */
|
||||
|
||||
/** Gibt den Nutzer zurück oder null (dann hat der Aufrufer bereits geantwortet). */
|
||||
export async function auth(req, res) {
|
||||
const token = param(req, 'token');
|
||||
if (!token) { fail(res, 'Kein Token'); return null; }
|
||||
|
||||
const rows = await sql`SELECT id, name FROM users WHERE token = ${token}`;
|
||||
if (!rows.length) { fail(res, 'Ungueltiges Token'); return null; }
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
await sql`UPDATE users SET last_seen = ${now} WHERE id = ${rows[0].id}`;
|
||||
return rows[0];
|
||||
}
|
||||
Reference in new issue
Block a user