n8n-nodes-onedrive-business 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,452 @@
1
+ # OneDrive Business Custom Node for n8n
2
+
3
+ Production-ready OneDrive Business integration for n8n with robust deduplication and webhook-based triggers.
4
+
5
+ ## Features
6
+
7
+ ### ✅ OneDrive Business ONLY
8
+ - **Tenant-based authentication** via Azure Entra ID (OAuth2)
9
+ - Supports **User Drives** (`/users/{userId}/drive`)
10
+ - Supports **SharePoint Site Drives** (`/sites/{siteId}/drive`)
11
+ - ❌ **NOT for OneDrive Personal/Consumer**
12
+
13
+ ### 📁 File Operations
14
+ - **Upload**: Binary file upload with automatic MIME type detection
15
+ - **Download**: Binary file download
16
+ - **Get**: Retrieve file metadata
17
+ - **Delete**: Remove files
18
+ - **Rename**: Rename files
19
+ - **Search**: Search for files
20
+ - **Share**: Create sharing links (view/edit, anonymous/organization)
21
+
22
+ ### 📂 Folder Operations
23
+ - **Create**: Create folders
24
+ - **Delete**: Remove folders
25
+ - **Get Items**: List folder contents
26
+ - **Rename**: Rename folders
27
+ - **Search**: Search for folders
28
+ - **Share**: Create sharing links
29
+
30
+ ### 🔔 Trigger Node (Webhook + Delta Query)
31
+ - **File Created**: Trigger when new files are uploaded
32
+ - **File Updated**: Trigger when files are modified
33
+ - **Folder Created**: Trigger when new folders are created
34
+ - **Folder Updated**: Trigger when folders are modified
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ cd ~/.n8n/custom
40
+ git clone <your-repo> n8n-nodes-onedrive-business
41
+ cd n8n-nodes-onedrive-business
42
+ npm install
43
+ npm run build
44
+ ```
45
+
46
+ Restart n8n to load the custom node.
47
+
48
+ ## Azure App Registration Setup
49
+
50
+ ### 1. Create App Registration
51
+
52
+ 1. Go to [Azure Portal](https://portal.azure.com)
53
+ 2. Navigate to **Azure Active Directory** → **App registrations**
54
+ 3. Click **New registration**
55
+ 4. Enter name: `n8n OneDrive Business`
56
+ 5. Select **Accounts in this organizational directory only** (or multi-tenant if needed)
57
+ 6. Set Redirect URI: `https://your-n8n-instance.com/rest/oauth2-credential/callback`
58
+ 7. Click **Register**
59
+
60
+ ### 2. Configure API Permissions
61
+
62
+ 1. Go to **API permissions**
63
+ 2. Click **Add a permission** → **Microsoft Graph** → **Delegated permissions**
64
+ 3. Add these permissions:
65
+ - `Files.ReadWrite.All`
66
+ - `Sites.ReadWrite.All`
67
+ - `offline_access`
68
+ 4. Click **Add permissions**
69
+ 5. Click **Grant admin consent** (requires admin)
70
+
71
+ ### 3. Create Client Secret
72
+
73
+ 1. Go to **Certificates & secrets**
74
+ 2. Click **New client secret**
75
+ 3. Enter description: `n8n integration`
76
+ 4. Select expiration (recommend 24 months)
77
+ 5. Click **Add**
78
+ 6. **Copy the secret value immediately** (you won't see it again)
79
+
80
+ ### 4. Get Required IDs
81
+
82
+ - **Application (client) ID**: Copy from Overview page
83
+ - **Directory (tenant) ID**: Copy from Overview page
84
+
85
+ ## Configuration in n8n
86
+
87
+ ### 1. Create Credential
88
+
89
+ 1. In n8n, go to **Credentials** → **New**
90
+ 2. Search for **OneDrive Business OAuth2 API**
91
+ 3. Fill in:
92
+ - **Tenant ID**: Your Azure AD tenant ID (or "common" for multi-tenant)
93
+ - **Client ID**: Application ID from Azure
94
+ - **Client Secret**: Secret value from Azure
95
+ 4. Click **Connect my account**
96
+ 5. Sign in with a Microsoft 365 account
97
+ 6. Grant permissions
98
+
99
+ ### 2. Use in Workflows
100
+
101
+ #### File Upload Example
102
+ ```
103
+ 1. Add "OneDrive Business" node
104
+ 2. Select Resource: File
105
+ 3. Select Operation: Upload
106
+ 4. Drive Type: User Drive
107
+ 5. User ID: user@contoso.com
108
+ 6. File Path: /Documents/report.pdf
109
+ 7. Binary Property: data
110
+ ```
111
+
112
+ #### Trigger Example
113
+ ```
114
+ 1. Add "OneDrive Business Trigger" node
115
+ 2. Drive Type: User Drive
116
+ 3. User ID: user@contoso.com
117
+ 4. Trigger On: File Created, File Updated
118
+ 5. Activate workflow
119
+ ```
120
+
121
+ ## Deduplication Architecture
122
+
123
+ ### The Problem
124
+
125
+ SharePoint-backed OneDrive generates **multiple updates per file upload**:
126
+
127
+ 1. File created (size=0, no hash)
128
+ 2. Metadata updated
129
+ 3. Content uploaded (partial hash)
130
+ 4. Final metadata sync (complete hash)
131
+
132
+ Without deduplication, **ONE upload = FOUR workflow executions**.
133
+
134
+ ### The Solution
135
+
136
+ This node implements a **4-layer deduplication strategy**:
137
+
138
+ #### 1. **File Stability Checking**
139
+ ```typescript
140
+ function isStable(item): boolean {
141
+ return (
142
+ item.size > 0 &&
143
+ item.file?.hashes?.quickXorHash &&
144
+ item.lastModifiedDateTime === item.fileSystemInfo?.lastModifiedDateTime
145
+ );
146
+ }
147
+ ```
148
+
149
+ Ensures files have completed uploading before processing.
150
+
151
+ #### 2. **Version-based Tracking**
152
+ ```typescript
153
+ const versionKey = `${item.id}_${item.eTag}`;
154
+ ```
155
+
156
+ Each file version (identified by eTag) is processed **exactly once**.
157
+
158
+ #### 3. **Stability Window**
159
+ ```typescript
160
+ if (Date.now() - lastModified < 15000) {
161
+ deferProcessing(); // Wait 15 seconds
162
+ }
163
+ ```
164
+
165
+ Prevents processing files still being modified.
166
+
167
+ #### 4. **Event Classification**
168
+ ```typescript
169
+ if (!previousState[item.id]) {
170
+ event = 'file.created';
171
+ } else if (item.eTag !== previousState[item.id].eTag) {
172
+ event = 'file.updated';
173
+ }
174
+ ```
175
+
176
+ Accurately distinguishes between created and updated events.
177
+
178
+ ### State Persistence
179
+
180
+ State is stored in:
181
+ ```
182
+ ~/.n8n-state/onedrive-business/{nodeId}.json
183
+ ```
184
+
185
+ Contains:
186
+ - **deltaLink**: Microsoft Graph delta query continuation token
187
+ - **processedVersions**: Map of `${itemId}_${eTag}` → `true`
188
+ - **lastKnownItems**: Map of `itemId` → `{ eTag, lastModifiedDateTime }`
189
+
190
+ **Survives n8n restarts** to prevent duplicate re-processing.
191
+
192
+ ## Webhook Architecture
193
+
194
+ ### Why Webhook + Delta Query?
195
+
196
+ Microsoft Graph webhooks **do NOT contain file data**. They only notify that "something changed."
197
+
198
+ The **Delta Query** is the single source of truth.
199
+
200
+ ### Flow
201
+
202
+ ```
203
+ 1. File uploaded to OneDrive
204
+
205
+ 2. Microsoft Graph sends webhook notification
206
+
207
+ 3. Webhook endpoint returns 200 OK (does NOT trigger workflow)
208
+
209
+ 4. n8n polls trigger node (every 60 seconds)
210
+
211
+ 5. Trigger fetches Delta Query
212
+
213
+ 6. DeltaProcessor applies deduplication
214
+
215
+ 7. Workflow triggered with deduplicated events
216
+ ```
217
+
218
+ ### Why This Works
219
+
220
+ - **Webhook**: Low-latency notification (near real-time)
221
+ - **Delta Query**: Complete file metadata + pagination
222
+ - **Deduplication**: Collapses multiple updates into one event
223
+ - **State Persistence**: Prevents re-processing after restarts
224
+
225
+ ## API Rate Limits
226
+
227
+ Microsoft Graph has rate limits:
228
+
229
+ - **Per app per tenant**: 10,000 requests per 10 minutes
230
+ - **Per user**: 2,000 requests per second
231
+
232
+ This node implements:
233
+ - **Exponential backoff** for 429 responses
234
+ - **Retry-After header parsing**
235
+ - **Automatic retry** (up to 3 attempts)
236
+
237
+ ## Troubleshooting
238
+
239
+ ### No Webhook Notifications Received
240
+
241
+ 1. Verify n8n is **publicly accessible** (webhook URL must be reachable)
242
+ 2. Check firewall rules
243
+ 3. Verify SSL certificate (Microsoft Graph requires HTTPS)
244
+ 4. Check n8n logs for webhook validation errors
245
+
246
+ ### Duplicate Workflow Executions
247
+
248
+ 1. Check if deduplication is working:
249
+ - View `~/.n8n-state/onedrive-business/{nodeId}.json`
250
+ - Verify `processedVersions` is being populated
251
+ 2. Increase stability window if large files are being processed
252
+ 3. Check that `eTag` is changing between updates
253
+
254
+ ### Subscription Expired
255
+
256
+ Subscriptions auto-renew every 2 days. If renewal fails:
257
+
258
+ 1. Check OAuth2 token validity
259
+ 2. Verify API permissions are still granted
260
+ 3. Manually deactivate and reactivate the trigger node
261
+
262
+ ### Delta Query Not Working
263
+
264
+ 1. Verify drive path is correct:
265
+ - User drive: `/users/{userId}/drive`
266
+ - Site drive: `/sites/{siteId}/drive`
267
+ 2. Check that user/site ID is valid
268
+ 3. Ensure credentials have `Files.ReadWrite.All` permission
269
+
270
+ ## Development
271
+
272
+ ### Build
273
+
274
+ ```bash
275
+ npm run build
276
+ ```
277
+
278
+ ### Watch Mode
279
+
280
+ ```bash
281
+ npm run dev
282
+ ```
283
+
284
+ ### Lint
285
+
286
+ ```bash
287
+ npm run lint
288
+ npm run lintfix
289
+ ```
290
+
291
+ ## License
292
+
293
+ MIT
294
+
295
+ ## Support
296
+
297
+ For issues, feature requests, or contributions, please open an issue on GitHub.
298
+
299
+ ---
300
+
301
+ ## ✔ How this OneDrive Business node avoids duplicate executions
302
+
303
+ ### Problem Statement
304
+
305
+ OneDrive Business is backed by SharePoint, which generates **multiple Graph API notifications for a single file upload**:
306
+
307
+ 1. **File Created**: Empty placeholder (size=0)
308
+ 2. **Metadata Updated**: File properties set
309
+ 3. **Content Uploaded**: Binary data written (partial hash)
310
+ 4. **Final Sync**: Hash finalized, timestamps aligned
311
+
312
+ Each of these generates a separate webhook notification and appears in the Delta Query.
313
+
314
+ **Without deduplication**: Uploading one 10MB PDF would trigger **4 workflow executions**.
315
+
316
+ ### Solution Architecture
317
+
318
+ #### 1. **Webhook as Notification Only**
319
+
320
+ The webhook endpoint **never emits workflow data**. It only:
321
+ - Validates the request (clientState check)
322
+ - Returns `200 OK`
323
+ - Functions as a "wake-up call" for the polling mechanism
324
+
325
+ **Result**: Webhook notifications don't directly trigger workflows, eliminating one source of duplicates.
326
+
327
+ #### 2. **Delta Query as Single Source of Truth**
328
+
329
+ All workflow triggers come from the **Delta Query** (`GET /drive/root/delta`), which:
330
+ - Provides complete file metadata
331
+ - Handles pagination
332
+ - Returns a `@odata.deltaLink` for continuing from the last check
333
+
334
+ **Result**: Only one API provides workflow data, creating a single choke point for deduplication.
335
+
336
+ #### 3. **eTag-based Version Tracking**
337
+
338
+ Each file version has a unique `eTag` value. The node tracks:
339
+
340
+ ```typescript
341
+ processedVersions: {
342
+ "fileId123_etag-v1": true,
343
+ "fileId123_etag-v2": true,
344
+ "fileId123_etag-v3": true
345
+ }
346
+ ```
347
+
348
+ Before emitting a workflow event:
349
+ ```typescript
350
+ const versionKey = `${item.id}_${item.eTag}`;
351
+ if (processedVersions[versionKey]) {
352
+ return; // Already processed
353
+ }
354
+ processedVersions[versionKey] = true;
355
+ // Emit workflow event
356
+ ```
357
+
358
+ **Result**: Each file version triggers **exactly one** workflow execution, regardless of how many times it appears in Delta Query.
359
+
360
+ #### 4. **File Stability Window**
361
+
362
+ Some uploads take time to finalize (especially large files). The node waits for:
363
+
364
+ - ✅ `size > 0`
365
+ - ✅ `quickXorHash` present
366
+ - ✅ `lastModifiedDateTime` matches `fileSystemInfo.lastModifiedDateTime`
367
+ - ✅ 15 seconds elapsed since last modification
368
+
369
+ If these conditions aren't met, the file is **deferred** (not processed yet).
370
+
371
+ **Result**: Only stable, fully uploaded files trigger workflows.
372
+
373
+ #### 5. **State Persistence Across Restarts**
374
+
375
+ All deduplication state is saved to disk:
376
+
377
+ ```
378
+ ~/.n8n-state/onedrive-business/{nodeId}.json
379
+ ```
380
+
381
+ When n8n restarts:
382
+ 1. State is loaded from disk
383
+ 2. `deltaLink` is restored
384
+ 3. `processedVersions` map is restored
385
+ 4. Delta Query continues from where it left off
386
+
387
+ **Result**: Restarting n8n doesn't cause already-processed files to re-trigger workflows.
388
+
389
+ ### Real-World Example
390
+
391
+ **Scenario**: User uploads `report.pdf` (10MB) to OneDrive Business.
392
+
393
+ **What happens in SharePoint**:
394
+ ```
395
+ 0s: File created (size=0, no hash) → eTag-v1
396
+ 1s: Metadata set (name, path) → eTag-v2
397
+ 2s: Content upload starts (partial hash) → eTag-v3
398
+ 5s: Content upload completes (quickXorHash set) → eTag-v4
399
+ 6s: Final metadata sync → eTag-v5
400
+ ```
401
+
402
+ **Without this node** (naive approach):
403
+ - Delta Query returns 5 versions
404
+ - **5 workflow executions** 😱
405
+
406
+ **With this node**:
407
+ ```
408
+ Delta Query returns 5 versions:
409
+
410
+ Version 1 (eTag-v1):
411
+ ❌ size=0 → Not stable, deferred
412
+
413
+ Version 2 (eTag-v2):
414
+ ❌ size=0 → Not stable, deferred
415
+
416
+ Version 3 (eTag-v3):
417
+ ❌ No quickXorHash → Not stable, deferred
418
+
419
+ Version 4 (eTag-v4):
420
+ ✅ size=10485760
421
+ ✅ quickXorHash present
422
+ ✅ Timestamps aligned
423
+ ❌ Modified 2 seconds ago → Within stability window, deferred
424
+
425
+ Version 5 (eTag-v5):
426
+ ✅ size=10485760
427
+ ✅ quickXorHash present
428
+ ✅ Timestamps aligned
429
+ ✅ Modified 20 seconds ago → Stable!
430
+ ✅ Not in processedVersions
431
+
432
+ → Mark as processed: processedVersions["fileId123_etag-v5"] = true
433
+ → Emit workflow event: { event: 'file.created', item: {...} }
434
+ ```
435
+
436
+ **Result**: **1 workflow execution** ✅
437
+
438
+ ### Why This Matters for Production
439
+
440
+ In a real Microsoft 365 tenant with hundreds of users:
441
+
442
+ - **100 files uploaded per day**
443
+ - **Without deduplication**: 400+ workflow executions
444
+ - **With this node**: 100 workflow executions
445
+
446
+ **75% reduction in duplicate workflows**, preventing:
447
+ - Wasted compute resources
448
+ - Duplicate notifications to users
449
+ - Redundant database writes
450
+ - API quota exhaustion
451
+
452
+ This architecture ensures **enterprise-grade reliability** for OneDrive Business integrations.
@@ -0,0 +1,25 @@
1
+ import { ICredentialTestRequest, ICredentialType, INodeProperties, ICredentialDataDecryptedObject, IHttpRequestOptions } from 'n8n-workflow';
2
+ /**
3
+ * OAuth2 Credentials for OneDrive Business (Azure Entra ID)
4
+ *
5
+ * CRITICAL: This is for OneDrive Business ONLY (SharePoint-backed).
6
+ * It uses Azure Entra ID (formerly Azure AD) tenant-based authentication.
7
+ *
8
+ * DO NOT use this for OneDrive Personal/Consumer accounts.
9
+ */
10
+ export declare class OneDriveBusinessOAuth2 implements ICredentialType {
11
+ name: string;
12
+ extends: string[];
13
+ displayName: string;
14
+ documentationUrl: string;
15
+ properties: INodeProperties[];
16
+ /**
17
+ * Authenticate requests to Microsoft Graph API
18
+ * This is called automatically by n8n to add authentication headers
19
+ */
20
+ authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions>;
21
+ /**
22
+ * Test credentials by fetching the user's OneDrive information
23
+ */
24
+ test: ICredentialTestRequest;
25
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OneDriveBusinessOAuth2 = void 0;
4
+ /**
5
+ * OAuth2 Credentials for OneDrive Business (Azure Entra ID)
6
+ *
7
+ * CRITICAL: This is for OneDrive Business ONLY (SharePoint-backed).
8
+ * It uses Azure Entra ID (formerly Azure AD) tenant-based authentication.
9
+ *
10
+ * DO NOT use this for OneDrive Personal/Consumer accounts.
11
+ */
12
+ class OneDriveBusinessOAuth2 {
13
+ constructor() {
14
+ this.name = 'oneDriveBusinessOAuth2Api';
15
+ this.extends = ['oAuth2Api'];
16
+ this.displayName = 'OneDrive Business OAuth2 API';
17
+ this.documentationUrl = 'https://learn.microsoft.com/en-us/graph/auth-v2-user';
18
+ this.properties = [
19
+ {
20
+ displayName: 'Grant Type',
21
+ name: 'grantType',
22
+ type: 'hidden',
23
+ default: 'authorizationCode',
24
+ },
25
+ {
26
+ displayName: 'Tenant ID',
27
+ name: 'tenantId',
28
+ type: 'string',
29
+ default: '',
30
+ required: true,
31
+ placeholder: 'common or your-tenant-id',
32
+ description: 'Azure Entra ID Tenant ID. Use "common" for multi-tenant apps, or specify your organization\'s tenant ID for single-tenant apps.',
33
+ },
34
+ {
35
+ displayName: 'Client ID',
36
+ name: 'clientId',
37
+ type: 'string',
38
+ default: '',
39
+ required: true,
40
+ description: 'Application (client) ID from Azure App Registration',
41
+ },
42
+ {
43
+ displayName: 'Client Secret',
44
+ name: 'clientSecret',
45
+ type: 'string',
46
+ typeOptions: {
47
+ password: true,
48
+ },
49
+ default: '',
50
+ required: true,
51
+ description: 'Client secret from Azure App Registration',
52
+ },
53
+ {
54
+ displayName: 'Authorization URL',
55
+ name: 'authUrl',
56
+ type: 'hidden',
57
+ default: '={{$self["tenantId"] ? `https://login.microsoftonline.com/${$self["tenantId"]}/oauth2/v2.0/authorize` : "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"}}',
58
+ required: true,
59
+ },
60
+ {
61
+ displayName: 'Access Token URL',
62
+ name: 'accessTokenUrl',
63
+ type: 'hidden',
64
+ default: '={{$self["tenantId"] ? `https://login.microsoftonline.com/${$self["tenantId"]}/oauth2/v2.0/token` : "https://login.microsoftonline.com/common/oauth2/v2.0/token"}}',
65
+ required: true,
66
+ },
67
+ {
68
+ displayName: 'Scope',
69
+ name: 'scope',
70
+ type: 'hidden',
71
+ default: 'https://graph.microsoft.com/Files.ReadWrite.All https://graph.microsoft.com/Sites.ReadWrite.All offline_access',
72
+ description: 'Required Microsoft Graph API permissions for OneDrive Business',
73
+ },
74
+ {
75
+ displayName: 'Auth URI Query Parameters',
76
+ name: 'authQueryParameters',
77
+ type: 'hidden',
78
+ default: 'response_type=code&prompt=consent',
79
+ },
80
+ {
81
+ displayName: 'Authentication',
82
+ name: 'authentication',
83
+ type: 'hidden',
84
+ default: 'body',
85
+ },
86
+ ];
87
+ /**
88
+ * Test credentials by fetching the user's OneDrive information
89
+ */
90
+ this.test = {
91
+ request: {
92
+ baseURL: 'https://graph.microsoft.com/v1.0',
93
+ url: '/me/drive',
94
+ method: 'GET',
95
+ },
96
+ };
97
+ }
98
+ /**
99
+ * Authenticate requests to Microsoft Graph API
100
+ * This is called automatically by n8n to add authentication headers
101
+ */
102
+ async authenticate(credentials, requestOptions) {
103
+ // The OAuth2 token is stored in the credentials object
104
+ const oauthData = credentials.oauthTokenData;
105
+ if (!requestOptions.headers) {
106
+ requestOptions.headers = {};
107
+ }
108
+ if (oauthData && oauthData.access_token) {
109
+ requestOptions.headers['Authorization'] = `Bearer ${oauthData.access_token}`;
110
+ }
111
+ return requestOptions;
112
+ }
113
+ }
114
+ exports.OneDriveBusinessOAuth2 = OneDriveBusinessOAuth2;
@@ -0,0 +1,35 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ /**
3
+ * OneDrive Business Node
4
+ *
5
+ * Provides file and folder operations for OneDrive Business:
6
+ * - File: Delete, Download, Get, Rename, Search, Share, Upload
7
+ * - Folder: Create, Delete, Get Items, Rename, Search, Share
8
+ *
9
+ * Uses Microsoft Graph API v1.0 with tenant-based authentication.
10
+ */
11
+ export declare class OneDriveBusiness implements INodeType {
12
+ description: INodeTypeDescription;
13
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
14
+ /**
15
+ * Execute file operations
16
+ */
17
+ private static executeFileOperation;
18
+ /**
19
+ * Execute folder operations
20
+ */
21
+ private static executeFolderOperation;
22
+ private static fileDelete;
23
+ private static fileDownload;
24
+ private static fileGet;
25
+ private static fileRename;
26
+ private static fileSearch;
27
+ private static fileShare;
28
+ private static fileUpload;
29
+ private static folderCreate;
30
+ private static folderDelete;
31
+ private static folderGetItems;
32
+ private static folderRename;
33
+ private static folderSearch;
34
+ private static folderShare;
35
+ }