Skip to main content

Monitor VDR activity and alert on suspicious user behavior

This example shows how to monitor user activity in a Virtual Data Room (VDR) in ONLYOFFICE DocSpace using audit logs.

The workflow analyzes audit events for a selected period and identifies users with unusually high activity inside the VDR. For those users, it additionally retrieves login history to provide context for further investigation.

The result is a structured alert payload that can be sent to a security or compliance channel.

Before you start

  1. Replace https://yourportal.onlyoffice.com and YOUR_API_KEY with your actual DocSpace portal URL and API key. Ensure you have the necessary data and permissions to perform these operations.
  2. Before you can make requests to the API, you need to authenticate. Check out the Personal access tokens page to learn how to obtain and use access tokens.
Full example
// Set API base URL
const API_HOST = process.env.DOCSPACE_API_HOST; // Set DOCSPACE_API_HOST in env (recommended). For quick tests you can temporarily paste your portal URL here.
const API_KEY = process.env.DOCSPACE_API_KEY; // Set DOCSPACE_API_KEY in env (recommended). For quick tests you can temporarily paste token here.

// Headers with API key for authentication
const HEADERS = {
Accept: 'application/json',
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};

// Simple configuration for monitoring
const MONITORED_ROOM_TITLE = 'Sensitive VDR - Finance';
const MAX_EVENTS_PER_USER = 20; // Example threshold
const AUDIT_PERIOD_FROM = '2025-01-01';
const AUDIT_PERIOD_TO = '2025-12-31';

// Step 1: Create a Virtual Data Room (roomType: 8)
async function createVdrRoom(title: string) {
const url = `${API_HOST}/api/2.0/files/rooms`;

const body = {
title,
roomType: 8, // 8 = Virtual Data Room (VDR)
};

const res = await fetch(url, { method: 'POST', headers: HEADERS, body: JSON.stringify(body) });
if (!res.ok) {
const t = await res.text();
console.log(`VDR creation failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('VDR created successfully.');
return json;
}

// Step 2: Get filtered audit trail data
async function getAuditEventsByFilter(params: { userId?: string; from?: string; to?: string }) {
const url = new URL(`${API_HOST}/api/2.0/security/audit/events/filter`);
if (params.userId) url.searchParams.set('userId', params.userId);
if (params.from) url.searchParams.set('from', params.from);
if (params.to) url.searchParams.set('to', params.to);

const res = await fetch(url.toString(), { method: 'GET', headers: HEADERS });
if (!res.ok) {
const t = await res.text();
console.log(`Audit events retrieval failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('Filtered audit events retrieved successfully.');
return json;
}

// Step 3: Get filtered login events for a user
async function getLoginEventsByFilter(params: { userId?: string; from?: string; to?: string }) {
const url = new URL(`${API_HOST}/api/2.0/security/audit/login/filter`);
if (params.userId) url.searchParams.set('userId', params.userId);
if (params.from) url.searchParams.set('from', params.from);
if (params.to) url.searchParams.set('to', params.to);

const res = await fetch(url.toString(), { method: 'GET', headers: HEADERS });
if (!res.ok) {
const t = await res.text();
console.log(`Login events retrieval failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('Filtered login events retrieved successfully.');
return json;
}

// Helper: Detect suspicious activity per user based on a simple threshold
function detectSuspiciousUsers(auditEvents: any[], maxEvents: number) {
const counters: Record<string, number> = {};

for (const event of auditEvents) {
const userId = event?.userId || event?.createBy || event?.createdBy;
if (!userId) continue;

const key = String(userId);
counters[key] = (counters[key] || 0) + 1;
}

return Object.entries(counters)
.filter(([, count]) => count > maxEvents)
.map(([userId]) => userId);
}

// Step 4: Placeholder for sending an alert to security
function sendSecurityAlert(payload: any) {
// In a real implementation, send this data to:
// - your email server
// - Slack / Teams webhook
// - incident/ticket system
console.log('\n[SECURITY ALERT] Suspicious activity detected:');
console.log(JSON.stringify(payload, null, 2));
}

// Main monitoring workflow
(async () => {
try {
console.log('\nStep 1: Creating or locating the VDR...');
const vdr = await createVdrRoom(MONITORED_ROOM_TITLE);
if (!vdr) {
console.log('VDR creation failed. Monitoring cannot continue.');
return;
}

const roomId = vdr?.response?.id ?? vdr?.id ?? null;
console.log('VDR room ID:', roomId);

console.log('\nStep 2: Retrieving audit trail data for the selected period...');
const auditData = await getAuditEventsByFilter({
from: AUDIT_PERIOD_FROM,
to: AUDIT_PERIOD_TO,
});
if (!auditData) return;

let auditEvents = Array.isArray(auditData) ? auditData : auditData.response;
if (!Array.isArray(auditEvents)) auditEvents = [];

const suspiciousUsers = detectSuspiciousUsers(auditEvents, MAX_EVENTS_PER_USER);
if (suspiciousUsers.length === 0) {
console.log('\nNo suspicious activity detected for the selected period.');
return;
}

console.log('\nSuspicious users detected:', suspiciousUsers);

console.log('\nStep 3: Retrieving login history for suspicious users...');
const loginDetails: Record<string, any> = {};

for (const userId of suspiciousUsers) {
const loginData = await getLoginEventsByFilter({
userId,
from: AUDIT_PERIOD_FROM,
to: AUDIT_PERIOD_TO,
});
loginDetails[userId] = loginData;
}

console.log('\nStep 4: Sending security alert with audit and login details...');
const payload = {
roomId,
period: { from: AUDIT_PERIOD_FROM, to: AUDIT_PERIOD_TO },
maxEventsPerUser: MAX_EVENTS_PER_USER,
suspiciousUsers,
loginDetails,
};

sendSecurityAlert(payload);

console.log('\nMonitoring completed.');
} catch (err: any) {
console.error(err?.message || err);
}
})();

Step 1: Create a Virtual Data Room (VDR)

A POST request is sent to /api/2.0/files/rooms to create a new room.

The request body includes:

  • title: room title.
  • roomType: room type. Use 8 to create a Virtual Data Room (VDR).

The API returns a room object. The room ID is used in the monitoring payload.

async function createVdrRoom(title: string) {
const url = `${API_HOST}/api/2.0/files/rooms`;

const body = {
title,
roomType: 8, // 8 = Virtual Data Room (VDR)
};

const res = await fetch(url, { method: 'POST', headers: HEADERS, body: JSON.stringify(body) });
if (!res.ok) {
const t = await res.text();
console.log(`VDR creation failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('VDR created successfully.');
return json;
}

Step 2: Retrieve audit events for the selected period

A GET request is sent to /api/2.0/security/audit/events/filter to retrieve audit trail events.

The request uses query parameters:

  • from: Period start date (YYYY-MM-DD)
  • to: Period end date (YYYY-MM-DD)
  • userId: Optional filter (used when needed)

The API returns a list of events (typically under response).

async function getAuditEventsByFilter(params: { userId?: string; from?: string; to?: string }) {
const url = new URL(`${API_HOST}/api/2.0/security/audit/events/filter`);
if (params.userId) url.searchParams.set('userId', params.userId);
if (params.from) url.searchParams.set('from', params.from);
if (params.to) url.searchParams.set('to', params.to);

const res = await fetch(url.toString(), { method: 'GET', headers: HEADERS });
if (!res.ok) {
const t = await res.text();
console.log(`Audit events retrieval failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('Filtered audit events retrieved successfully.');
return json;
}

Step 3: Retrieve login events for suspicious users

A GET request is sent to /api/2.0/security/audit/login/filter to retrieve login history. This step is run for each suspicious user ID detected by the threshold rule.

async function getLoginEventsByFilter(params: { userId?: string; from?: string; to?: string }) {
const url = new URL(`${API_HOST}/api/2.0/security/audit/login/filter`);
if (params.userId) url.searchParams.set('userId', params.userId);
if (params.from) url.searchParams.set('from', params.from);
if (params.to) url.searchParams.set('to', params.to);

const res = await fetch(url.toString(), { method: 'GET', headers: HEADERS });
if (!res.ok) {
const t = await res.text();
console.log(`Login events retrieval failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('Filtered login events retrieved successfully.');
return json;
}

Step 4: Send a security alert

This is a placeholder step that shows where to send the monitoring output.

The payload includes:

  • roomId: The VDR room ID
  • period: Reporting period
  • maxEventsPerUser: Threshold used for detection
  • suspiciousUsers: List of user IDs that exceeded the threshold
  • loginDetails: Login audit data for each suspicious user

You can replace this placeholder with Slack, email, or your incident management system.

function sendSecurityAlert(payload: any) {
// In a real implementation, send this data to:
// - your email server
// - Slack / Teams webhook
// - incident/ticket system
console.log('\n[SECURITY ALERT] Suspicious activity detected:');
console.log(JSON.stringify(payload, null, 2));
}