Skip to main content

Monitor logins and enforce IP allowlist

This example demonstrates how to monitor login activity in ONLYOFFICE DocSpace and detect logins that violate an IP allowlist policy.

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: Record<string, string> = {
Accept: 'application/json',
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};

// In a real project, store this data in a database.
// Here we keep allowed IPs per user in memory for demonstration.
const ALLOWED_IPS_BY_USER: Record<string, string[]> = {};

// Example: new user data coming from an external system (JSON)
const NEW_USER = {
id: 'user_id_here',
email: 'user@example.com',
allowedIps: ['203.0.113.10', '198.51.100.0-198.51.100.255'], // range is for illustration
};

// Helper: basic request wrapper (beginner-friendly)
async function docspaceRequest(method: string, url: string, body?: any) {
const res = await fetch(url, {
method,
headers: HEADERS,
body: body !== undefined ? JSON.stringify(body) : undefined,
});

if (!res.ok) {
const t = await res.text();
console.log(`Request failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

try {
return await res.json();
} catch {
return null;
}
}

// Step 1: Retrieve current IP restrictions
async function getRestrictions() {
const url = `${API_HOST}/api/2.0/settings/iprestrictions`;
const settings = await docspaceRequest('GET', url);
if (settings) {
console.log('Current IP restrictions settings retrieved.');
}
return settings;
}

// Step 2: Update IP restrictions (portal-wide allowlist)
async function saveRestrictions(ipRestrictions: any[], enable = true) {
const url = `${API_HOST}/api/2.0/settings/iprestrictions`;
const body = { enable, ipRestrictions };

const settings = await docspaceRequest('PUT', url, body);
if (settings) {
console.log('IP restrictions updated successfully.');
}
return settings;
}

// Helper: normalize different response shapes into a list
function extractIpRestrictionsList(currentSettings: any): any[] {
// currentSettings may be:
// - list: [ { "ip": "...", "forAdmin": false }, ... ]
// - dict: { "response": { "ipRestrictions": [...] }, ... }
// - dict: { "ipRestrictions": [...] }
if (Array.isArray(currentSettings)) {
return currentSettings;
}

if (currentSettings && typeof currentSettings === 'object') {
const resp = currentSettings.response ?? currentSettings;
if (Array.isArray(resp)) return resp;
if (resp && typeof resp === 'object' && Array.isArray(resp.ipRestrictions)) return resp.ipRestrictions;
}

return [];
}

// Helper: ensure that allowed IPs for a user exist in the portal IP restrictions
async function ensureUserIpsInPortalRestrictions(userId: string, allowedIps: string[]) {
// Save per-user allowed IPs in memory (for demo)
ALLOWED_IPS_BY_USER[userId] = allowedIps;

const currentSettings = await getRestrictions();
if (!currentSettings) return;

const currentList = extractIpRestrictionsList(currentSettings);
const updatedList = [...currentList];

for (const ip of allowedIps) {
const alreadyExists = currentList.some((entry) => entry && typeof entry === 'object' && entry.ip === ip);
if (!alreadyExists) {
updatedList.push({
ip,
forAdmin: false, // adjust if you want to restrict admins separately
});
}
}

await saveRestrictions(updatedList, true);
}

// Step 3: Retrieve login events for a given period
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 data = await docspaceRequest('GET', url.toString());
if (data) {
console.log('Login events retrieved successfully.');
}
return data;
}

// Helper: simple check if IP is allowed for a user (exact match demo)
function isIpAllowedForUser(userId: string, ip: string) {
const allowedIps = ALLOWED_IPS_BY_USER[userId] || [];
return allowedIps.includes(ip);
}

// Step 4: Placeholder to notify administrators about unauthorized logins
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] Unauthorized login detected:');
console.log(JSON.stringify(payload, null, 2));
}

// Step 5: Check login events and notify if IP is not allowed for the user
async function checkLoginsForUnauthorizedIps(fromDate: string, toDate: string) {
const loginData = await getLoginEventsByFilter({ from: fromDate, to: toDate });
if (!loginData) return;

// loginData may be:
// - { "response": [ ... ], ... }
// - directly [ ... ]
const events = Array.isArray(loginData) ? loginData : loginData.response;

if (!Array.isArray(events)) {
console.log('Login events data is not a list.');
return;
}

const suspiciousEvents: any[] = [];

for (const event of events) {
if (!event || typeof event !== 'object') continue;

const userId = event.userId || event.account || event.user;
const ip = event.ip || event.ipAddress;

if (!userId || !ip) continue;

if (!isIpAllowedForUser(String(userId), String(ip))) {
const rawDate = event.date ?? event.time;
const date =
typeof rawDate === 'string' && rawDate.trim()
? rawDate
: rawDate != null
? String(rawDate)
: 'unknown';

suspiciousEvents.push({
userId,
ip,
date,
raw: event,
});
}
}

if (suspiciousEvents.length === 0) {
console.log('\nNo unauthorized IP logins detected for the selected period.');
return;
}

sendSecurityAlert({
from: fromDate,
to: toDate,
events: suspiciousEvents,
});
}

// Main workflow: add IP restrictions for a new user and monitor logins
(async () => {
try {
console.log('\nStep 1: Registering allowed IP range(s) for a new user...');
await ensureUserIpsInPortalRestrictions(NEW_USER.id, NEW_USER.allowedIps);

console.log('\nStep 2: Checking login history for unauthorized IP addresses (example period)...');
const startDate = '2025-01-01';
const endDate = '2025-12-31';

await checkLoginsForUnauthorizedIps(startDate, endDate);

console.log('\nIP-based access check completed.');
} catch (err: any) {
console.error(err?.message || err);
}
})();

Step 1: Retrieve current IP restrictions

A GET request is sent to /api/2.0/settings/iprestrictions to retrieve the current portal IP restriction settings.

This step helps you understand what IP rules are already configured before adding new IP entries.

async function getRestrictions() {
const url = `${API_HOST}/api/2.0/settings/iprestrictions`;
const settings = await docspaceRequest('GET', url);
if (settings) {
console.log('Current IP restrictions settings retrieved.');
}
return settings;
}

Step 2: Update portal IP restrictions (allowlist)

A PUT request is sent to /api/2.0/settings/iprestrictions to enable IP restrictions and store the updated allowlist.

The request body includes:

  • enable: Enables or disables IP restrictions for the portal.
  • ipRestrictions: A list of IP entries (each entry typically contains an ip value and additional flags like forAdmin).

In this example, we add missing user IPs to the portal-wide allowlist.

async function saveRestrictions(ipRestrictions: any[], enable = true) {
const url = `${API_HOST}/api/2.0/settings/iprestrictions`;
const body = { enable, ipRestrictions };

const settings = await docspaceRequest('PUT', url, body);
if (settings) {
console.log('IP restrictions updated successfully.');
}
return settings;
}

Step 3: Retrieve login events for a selected period

A GET request is sent to /api/2.0/security/audit/login/filter to fetch login events for a selected date range.

This step is used to monitor authentication activity and detect logins that may violate your IP rules.

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 data = await docspaceRequest('GET', url.toString());
if (data) {
console.log('Login events retrieved successfully.');
}
return data;
}

Step 4: Send a security alert (placeholder)

This is a placeholder step showing where you would notify administrators or security tools.

In a real system, this can be replaced with:

  • Email notification
  • Slack / Teams webhook
  • Ticket or incident creation
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] Unauthorized login detected:');
console.log(JSON.stringify(payload, null, 2));
}

Step 5: Check logins and alert on unauthorized IP addresses

This step loops through login events, reads the user ID and IP address from each event, and checks if the IP is allowed for that user.

If an event uses a non-allowed IP, the script collects it into a list and sends one combined alert payload.

If you want, next we can make the demo even more “junior-proof” without changing logic:

  • add one small function that prints a clean “Step X completed” message consistently,
  • rename a couple of variables to be 100% consistent between Node.js and Python (still same flow).
async function checkLoginsForUnauthorizedIps(fromDate: string, toDate: string) {
const loginData = await getLoginEventsByFilter({ from: fromDate, to: toDate });
if (!loginData) return;

// loginData may be:
// - { "response": [ ... ], ... }
// - directly [ ... ]
const events = Array.isArray(loginData) ? loginData : loginData.response;

if (!Array.isArray(events)) {
console.log('Login events data is not a list.');
return;
}

const suspiciousEvents: any[] = [];

for (const event of events) {
if (!event || typeof event !== 'object') continue;

const userId = event.userId || event.account || event.user;
const ip = event.ip || event.ipAddress;

if (!userId || !ip) continue;

if (!isIpAllowedForUser(String(userId), String(ip))) {
const rawDate = event.date ?? event.time;
const date =
typeof rawDate === 'string' && rawDate.trim()
? rawDate
: rawDate != null
? String(rawDate)
: 'unknown';

suspiciousEvents.push({
userId,
ip,
date,
raw: event,
});
}
}

if (suspiciousEvents.length === 0) {
console.log('\nNo unauthorized IP logins detected for the selected period.');
return;
}

sendSecurityAlert({
from: fromDate,
to: toDate,
events: suspiciousEvents,
});
}