Skip to main content

Disable inactive external contractors based on audit login events

Script loads external contractors (placeholder), checks their last login via audit login filter for the last N days, and disables users who have no login events or are inactive for INACTIVITY_DAYS.

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.

// If the user has not logged in for INACTIVITY_DAYS days, disable them
const INACTIVITY_DAYS = 30;

// How many days back we search login audit events
const LOGIN_LOOKBACK_DAYS = 180;

// People status value/name. Replace if your portal expects a numeric status code.
const DISABLED_STATUS = 'Terminated';

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

// Basic request helper
async function docspaceRequest(method: string, path: string, body: any = null) {
const url = `${API_HOST}${path}`;

try {
const res = await fetch(url, {
method,
headers: HEADERS,
body: body ? JSON.stringify(body) : undefined,
});

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

return res.json();
} catch (err: any) {
console.log(`Request error: ${err?.message || err}`);
return null;
}
}

function isoDaysAgo(days: number) {
const ms = days * 24 * 60 * 60 * 1000;
return new Date(Date.now() - ms).toISOString();
}

function parseIsoDate(value: any): Date | null {
if (!value || typeof value !== 'string') return null;
const d = new Date(value);
return Number.isNaN(d.getTime()) ? null : d;
}

// Step 1: Load external contractors (placeholder)
function fetchExternalContractors() {
// Replace this with:
// - your HR/CRM source, or
// - DocSpace People list filtered by your own logic.
return [
{ id: 'user-id-1', email: 'contractor1@vendor.com', displayName: 'Contractor One' },
{ id: 'user-id-2', email: 'contractor2@vendor.com', displayName: 'Contractor Two' },
];
}

// Step 2: Get the last login date from audit logs
async function getLastLoginDate(userId: string): Promise<Date | null> {
const now = new Date();
const from = isoDaysAgo(LOGIN_LOOKBACK_DAYS);

const params = new URLSearchParams({
userId: String(userId),
action: '0', // 0 = login
from,
to: now.toISOString(),
});

const data = await docspaceRequest(
'GET',
`/api/2.0/security/audit/login/filter?${params.toString()}`,
);

const events = Array.isArray(data) ? data : Array.isArray(data?.response) ? data.response : [];
if (!Array.isArray(events) || events.length === 0) return null;

let last: Date | null = null;

for (const e of events) {
const dt = parseIsoDate(e?.date || e?.created || e?.time);
if (!dt) continue;
if (!last || dt > last) last = dt;
}

return last;
}

function isInactive(lastLogin: Date | null, now: Date) {
// If there are no login events, treat the contractor as inactive
if (!lastLogin) return true;

const diffMs = now.getTime() - lastLogin.getTime();
const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));

return days >= INACTIVITY_DAYS;
}

// Step 3: Disable inactive users
async function disableUsers(userIds: string[]) {
if (!userIds.length) return true;

const body = {
userIds,
resendAll: false,
};

// Replace DISABLED_STATUS if your portal expects numeric status codes:
// PUT /api/2.0/people/status/{status}
const data = await docspaceRequest('PUT', `/api/2.0/people/status/${DISABLED_STATUS}`, body);
return Boolean(data);
}

async function cleanupInactiveExternalContractors() {
const now = new Date();

console.log('Loading external contractors...');
const contractors = fetchExternalContractors();

if (!contractors.length) {
console.log('No external contractors found.');
return;
}

const toDisable: string[] = [];

for (const u of contractors) {
const userId = String(u.id);
const label = u.displayName || u.email || userId;

console.log(`\nChecking: ${label} (${userId})`);

const lastLogin = await getLastLoginDate(userId);

if (lastLogin) {
console.log(`Last login: ${lastLogin.toISOString()}`);
} else {
console.log('No login events found.');
}

if (isInactive(lastLogin, now)) {
console.log(`Inactive >= ${INACTIVITY_DAYS} days. Marking for disable.`);
toDisable.push(userId);
} else {
console.log('Still active. Skipping.');
}
}

console.log('\nSummary:');
if (!toDisable.length) {
console.log('No inactive users found. Nothing to disable.');
return;
}

const ok = await disableUsers(toDisable);
console.log(ok ? `Disabled ${toDisable.length} user(s).` : 'Failed to disable users.');
}

cleanupInactiveExternalContractors().catch((err: any) => console.error(err?.message || err));

Step 1: Load external contractors

The script prepares a list of external contractors (placeholder). Replace this part with your HR/CRM source or your own People API filtering logic to return external user IDs that should be checked.

function fetchExternalContractors() {
// Replace this with:
// - your HR/CRM source, or
// - DocSpace People list filtered by your own logic.
return [
{ id: 'user-id-1', email: 'contractor1@vendor.com', displayName: 'Contractor One' },
{ id: 'user-id-2', email: 'contractor2@vendor.com', displayName: 'Contractor Two' },
];
}

Step 2: Check last login via audit logs

Then it sends a GET request to /api/2.0/security/audit/login/filter with:

  • userId: contractor user ID in DocSpace
  • action=0: login events only
  • from / to: date range for audit search (for example, the last 180 days)

The script parses the returned events and picks the most recent login timestamp.

async function getLastLoginDate(userId: string): Promise<Date | null> {
const now = new Date();
const from = isoDaysAgo(LOGIN_LOOKBACK_DAYS);

const params = new URLSearchParams({
userId: String(userId),
action: '0', // 0 = login
from,
to: now.toISOString(),
});

const data = await docspaceRequest(
'GET',
`/api/2.0/security/audit/login/filter?${params.toString()}`,
);

const events = Array.isArray(data) ? data : Array.isArray(data?.response) ? data.response : [];
if (!Array.isArray(events) || events.length === 0) return null;

let last: Date | null = null;

for (const e of events) {
const dt = parseIsoDate(e?.date || e?.created || e?.time);
if (!dt) continue;
if (!last || dt > last) last = dt;
}

return last;
}

Step 3: Disable inactive users

If the last login is missing or older than INACTIVITY_DAYS, the script adds the user to the disable list. Then it sends a PUT request to /api/2.0/people/status/:status with the collected userIds. This updates the user status and disables inactive contractors. If your portal uses numeric status codes instead of string statuses, replace Terminated with the required status value.

async function disableUsers(userIds: string[]) {
if (!userIds.length) return true;

const body = {
userIds,
resendAll: false,
};

// Replace DISABLED_STATUS if your portal expects numeric status codes:
// PUT /api/2.0/people/status/{status}
const data = await docspaceRequest('PUT', `/api/2.0/people/status/${DISABLED_STATUS}`, body);
return Boolean(data);
}