跳到主要内容

Remove guest access when a room is moved to Archive

This example shows how to automatically revoke guest (external) access when a room is moved under an Archive root in DocSpace. When a move event is received, the script verifies that the destination parent is the configured archive root, loads the room sharing entries, removes guest participants, and updates the room share settings. Optionally, it generates a cleanup report and notifies the room owner.

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.

// Archive root folder/room ID
const ARCHIVE_ROOT_ID = 539564;

// Folder where cleanup reports are stored
const GUEST_CLEANUP_FOLDER_ID = 433867;

const HEADERS = {
Accept: 'application/json',
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};

async function docspaceRequest(path: string, method: string = 'GET', 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 t = await res.text();
console.log(`Request failed. Status: ${res.status}, Message: ${t}`);
return null;
}

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

// Step 1: Get room info
async function getRoom(roomId: number) {
const data = await docspaceRequest(`/api/2.0/files/rooms/${roomId}`, 'GET');
if (!data || typeof data !== 'object') return null;

const node = (data as any).response != null ? (data as any).response : data;
return node && typeof node === 'object' ? node : null;
}

// Step 2: Get room share settings
async function getRoomShare(roomId: number) {
const data = await docspaceRequest(`/api/2.0/files/rooms/${roomId}/share`, 'GET');
if (!data) return null;

return (data as any).response != null ? (data as any).response : data;
}

// Helper: extract entries list from share response
function extractParticipants(shareData: any) {
if (Array.isArray(shareData)) return shareData;

if (shareData && typeof shareData === 'object') {
const keys = ['entries', 'items', 'participants', 'share'];
for (const k of keys) {
if (Array.isArray(shareData[k])) return shareData[k];
}
}

return [];
}

// Step 3: Save room share settings (without guests)
async function putRoomShare(roomId: number, participants: any[]) {
const payload = { entries: participants };
const data = await docspaceRequest(`/api/2.0/files/rooms/${roomId}/share`, 'PUT', payload);
return Boolean(data);
}

// Helper: detect guest entry
function isGuest(entry: any) {
const role = String(entry.access || entry.role || '').toLowerCase();
const subjectType = String(entry.subjectType || '').toLowerCase();
const visitorFlag = Boolean(entry.isVisitor || entry.visitor);

if (visitorFlag) return true;

if (role.includes('guest') || role.includes('visitor') || role.includes('external')) return true;

if (subjectType === 'guest' || subjectType === 'visitor' || subjectType === 'external') return true;

return false;
}

// Step 4: Create a text cleanup report (simple placeholder)
async function createCleanupReport(roomTitle: string, guests: any[]) {
if (!guests.length) return;

const ts = new Date().toISOString().replace(/[:.]/g, '-');
const safeTitle = roomTitle.replace(/[\/\\]/g, '_');
const fileName = `${safeTitle}-guest-cleanup-${ts}.txt`;

const lines: string[] = [];
lines.push(`Guest cleanup report for room "${roomTitle}"`);
lines.push('');
lines.push('Removed guests:');

for (const g of guests) {
const email = g.email || g.userName || g.displayName || String(g.id || 'unknown');
lines.push(`- ${email}`);
}

const content = lines.join('\n');

// Placeholder request (replace with your real upload/create-text endpoint)
const payload = { title: fileName, content };
await docspaceRequest(`/api/2.0/files/folder/${GUEST_CLEANUP_FOLDER_ID}`, 'POST', payload);

console.log(`Cleanup report created: ${fileName}`);
}

// Step 5: Notify room owner (placeholder)
function notifyOwner(ownerEmail: string, roomTitle: string) {
console.log(`[NOTIFY] ${ownerEmail}: Guest access cleaned up for room "${roomTitle}".`);
}

// Main: cleanup guests from a single room
async function cleanupRoomGuests(roomId: number) {
const room = await getRoom(roomId);
if (!room) {
console.log(`Room not found. roomId=${roomId}`);
return;
}

const title = String(room.title || `Room ${roomId}`);
const ownerEmail = String(room.ownerEmail || room.owner || 'owner@example.com');

const shareData = await getRoomShare(roomId);
if (!shareData) {
console.log(`Cannot load share data. roomId=${roomId}`);
return;
}

const participants = extractParticipants(shareData);
if (!participants.length) {
console.log(`Room "${title}" has no participants in share.`);
return;
}

const guests = participants.filter(isGuest);
if (!guests.length) {
console.log(`Room "${title}" has no guest users.`);
return;
}

const remaining = participants.filter((p: any) => !isGuest(p));

const ok = await putRoomShare(roomId, remaining);
if (!ok) {
console.log(`Failed to update share. roomId=${roomId}`);
return;
}

await createCleanupReport(title, guests);
notifyOwner(ownerEmail, title);

console.log(`Guest access cleaned: room="${title}", removed=${guests.length}`);
}

// Webhook handler: room moved into archive root
async function handleRoomMovedEvent(eventPayload: any) {
const data = eventPayload && eventPayload.data ? eventPayload.data : {};

const roomId = Number(data.roomId);
const newParentId = Number(data.newParentId);

if (!Number.isFinite(roomId) || !Number.isFinite(newParentId)) {
console.log('Invalid roomId or newParentId in payload.');
return;
}

if (newParentId !== ARCHIVE_ROOT_ID) {
console.log(`Room moved, but not into archive root. roomId=${roomId}`);
return;
}

await cleanupRoomGuests(roomId);
}

// Example usage
handleRoomMovedEvent({
event: 'room_moved',
data: { roomId: 2025512, newParentId: ARCHIVE_ROOT_ID },
});

Step 1: Handle the room moved event and validate the destination

Your integration receives a room move event (for example, room_moved) that contains:

  • roomId: moved room ID
  • newParentId: destination parent ID

The script checks whether newParentId equals ARCHIVE_ROOT_ID. If the room was moved somewhere else, it is ignored.

async function getRoom(roomId: number) {
const data = await docspaceRequest(`/api/2.0/files/rooms/${roomId}`, 'GET');
if (!data || typeof data !== 'object') return null;

const node = (data as any).response != null ? (data as any).response : data;
return node && typeof node === 'object' ? node : null;
}

Step 2: Load room details (title/owner) for reporting

A GET request is sent to /api/2.0/files/rooms/:roomId/share to retrieve the room sharing entries. The response can contain the list of entries in different fields (for example, entries), so the script extracts participants from the most common structures.

async function getRoomShare(roomId: number) {
const data = await docspaceRequest(`/api/2.0/files/rooms/${roomId}/share`, 'GET');
if (!data) return null;

return (data as any).response != null ? (data as any).response : data;
}

Step 3: Load room sharing entries

The script filters guest entries using is_guest().

Then it sends a PUT request to /api/2.0/files/rooms/:roomId/share with only the remaining participants. This updates the room share settings and removes guest access.

async function putRoomShare(roomId: number, participants: any[]) {
const payload = { entries: participants };
const data = await docspaceRequest(`/api/2.0/files/rooms/${roomId}/share`, 'PUT', payload);
return Boolean(data);
}

Step 4: Remove guest/external entries and save share settings

After guests are removed, the script builds a simple text report with:

  • room title
  • list of removed guests
  • timestamp In this example, report creation is a placeholder request. Replace it with your actual file upload or “create text file” logic.
async function createCleanupReport(roomTitle: string, guests: any[]) {
if (!guests.length) return;

const ts = new Date().toISOString().replace(/[:.]/g, '-');
const safeTitle = roomTitle.replace(/[\/\\]/g, '_');
const fileName = `${safeTitle}-guest-cleanup-${ts}.txt`;

const lines: string[] = [];
lines.push(`Guest cleanup report for room "${roomTitle}"`);
lines.push('');
lines.push('Removed guests:');

for (const g of guests) {
const email = g.email || g.userName || g.displayName || String(g.id || 'unknown');
lines.push(`- ${email}`);
}

const content = lines.join('\n');

// Placeholder request (replace with your real upload/create-text endpoint)
const payload = { title: fileName, content };
await docspaceRequest(`/api/2.0/files/folder/${GUEST_CLEANUP_FOLDER_ID}`, 'POST', payload);

console.log(`Cleanup report created: ${fileName}`);
}

Step 5: Write a cleanup report and notify the owner

The example uses a placeholder notification (console output). Replace it with your email/webhook/incident integration as needed.

function notifyOwner(ownerEmail: string, roomTitle: string) {
console.log(`[NOTIFY] ${ownerEmail}: Guest access cleaned up for room "${roomTitle}".`);
}