This commit is contained in:
2026-09-12 21:55:40 +02:00
parent 53055ea5c6
commit 90860b9279
24 changed files with 1958 additions and 45 deletions
+74 -6
View File
@@ -86,14 +86,57 @@ export function verifySignature(sshPubkey, message, signature) {
}
}
import security from './security.mjs';
import { getHashUserIps } from './settings.mjs';
/**
* Get IP for audit/logging, hashed if hash_user_ips is enabled in config.
* @param {object} req
* @returns {string}
*/
export function resolveAuditIP(req) {
if (!req) return 'unknown';
const rawIp = security.getRealIP(req);
return getHashUserIps() ? security.hashIP(rawIp) : rawIp;
}
/**
* Log activity for an anonymous user (or session).
* @param {object} req
* @param {{ action: string, targetId?: number|string, details?: object, hwFingerprint?: string }} params
*/
export async function logAnonActivity(req, { action, targetId = null, details = null, hwFingerprint = null } = {}) {
try {
const rawIp = security.getRealIP(req);
const ip = getHashUserIps() ? security.hashIP(rawIp) : rawIp;
const userId = req?.session?.id || null;
if (!userId) return;
const fingerprint = req?.session?.fingerprint || req?.session?.anon_fingerprint || null;
const hwFp = hwFingerprint || req?.session?.hw_fingerprint || null;
const numTargetId = targetId ? parseInt(targetId, 10) : null;
await db`
INSERT INTO anon_activity_log (user_id, fingerprint, hw_fingerprint, ip, action, target_id, details)
VALUES (${userId}, ${fingerprint}, ${hwFp}, ${ip}, ${action}, ${!isNaN(numTargetId) ? numTargetId : null}, ${details ? JSON.stringify(details) : null})
`;
await security.logUserIP(userId, rawIp);
} catch (err) {
console.error('[ANON_ACTIVITY_LOG] Failed to log activity:', err);
}
}
/**
* Find or create a shadow user in the database for an anonymous SSH identity.
* @param {string} pubkey
* @param {string} fingerprint
* @param {object} [req]
* @param {string} [hwFingerprint]
* @returns {Promise<{ userId: number, isNew: boolean }>}
*/
export async function getOrCreateAnonUser(pubkey, fingerprint) {
export async function getOrCreateAnonUser(pubkey, fingerprint, req = null, hwFingerprint = null) {
const normPubkey = pubkey.trim();
const auditIp = req ? resolveAuditIP(req) : null;
const existing = await db`
SELECT user_id FROM anon_identities
WHERE pubkey = ${normPubkey}
@@ -101,7 +144,13 @@ export async function getOrCreateAnonUser(pubkey, fingerprint) {
`;
if (existing.length > 0) {
await db`UPDATE anon_identities SET last_seen = NOW() WHERE pubkey = ${normPubkey}`;
await db`
UPDATE anon_identities
SET last_seen = NOW()
${auditIp ? db`, last_ip = ${auditIp}` : db``}
${hwFingerprint ? db`, hw_fingerprint = ${hwFingerprint}` : db``}
WHERE pubkey = ${normPubkey}
`;
return { userId: existing[0].user_id, isNew: false };
}
@@ -131,9 +180,12 @@ export async function getOrCreateAnonUser(pubkey, fingerprint) {
`;
await db`
INSERT INTO anon_identities (user_id, pubkey, fingerprint)
VALUES (${userId}, ${normPubkey}, ${fingerprint})
ON CONFLICT (pubkey) DO NOTHING
INSERT INTO anon_identities (user_id, pubkey, fingerprint, created_ip, last_ip, hw_fingerprint)
VALUES (${userId}, ${normPubkey}, ${fingerprint}, ${auditIp}, ${auditIp}, ${hwFingerprint})
ON CONFLICT (pubkey) DO UPDATE
SET last_seen = NOW()
${auditIp ? db`, last_ip = ${auditIp}` : db``}
${hwFingerprint ? db`, hw_fingerprint = ${hwFingerprint}` : db``}
`;
return { userId, isNew: true };
@@ -143,11 +195,24 @@ export async function getOrCreateAnonUser(pubkey, fingerprint) {
* Create a valid session in user_sessions for this anonymous user.
* @param {number} userId
* @param {object} req
* @param {string} [hwFingerprint]
* @returns {Promise<{ session: string, csrf_token: string }>}
*/
export async function createAnonSession(userId, req) {
export async function createAnonSession(userId, req, hwFingerprint = null) {
const auditIp = resolveAuditIP(req);
// Update anon_identities last_ip, created_ip, and hw_fingerprint
await db`
UPDATE anon_identities
SET last_ip = ${auditIp},
created_ip = COALESCE(created_ip, ${auditIp})
${hwFingerprint ? db`, hw_fingerprint = COALESCE(${hwFingerprint}, hw_fingerprint)` : db``}
WHERE user_id = ${userId}
`.catch(() => {});
// 1. If req.session is already active for this exact userId, reuse it!
if (req?.session && req.session.id === userId && req.session.csrf_token && req.cookies?.session) {
await logAnonActivity(req, { action: 'handshake', hwFingerprint });
return { session: req.cookies.session, csrf_token: req.session.csrf_token };
}
@@ -161,6 +226,7 @@ export async function createAnonSession(userId, req) {
`;
if (existing.length > 0) {
await db`UPDATE user_sessions SET last_used = ${~~(Date.now() / 1e3)} WHERE session = ${existingHash}`;
await logAnonActivity({ ...req, session: { id: userId, is_anon: true } }, { action: 'handshake', hwFingerprint });
return { session: req.cookies.session, csrf_token: existing[0].csrf_token };
}
}
@@ -188,5 +254,7 @@ export async function createAnonSession(userId, req) {
INSERT INTO "user_sessions" ${db(sessRecord, 'user_id', 'session', 'csrf_token', 'browser', 'created_at', 'last_used', 'last_action', 'kmsi', 'ip')}
`;
await logAnonActivity({ ...req, session: { id: userId, is_anon: true } }, { action: 'handshake', hwFingerprint });
return { session, csrf_token: csrfToken };
}