Skip to content

Commit 727f6f3

Browse files
feat(Azure Storage Node): New node (n8n-io#12536)
1 parent d550382 commit 727f6f3

43 files changed

Lines changed: 4696 additions & 6 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2+
3+
export class AzureStorageOAuth2Api implements ICredentialType {
4+
name = 'azureStorageOAuth2Api';
5+
6+
displayName = 'Azure Storage OAuth2 API';
7+
8+
extends = ['microsoftOAuth2Api'];
9+
10+
documentationUrl = 'azurestorage';
11+
12+
properties: INodeProperties[] = [
13+
{
14+
displayName: 'Account',
15+
name: 'account',
16+
type: 'string',
17+
default: '',
18+
},
19+
{
20+
displayName: 'Base URL',
21+
name: 'baseUrl',
22+
type: 'hidden',
23+
default: '=https://{{ $self["account"] }}.blob.core.windows.net',
24+
},
25+
{
26+
displayName: 'Scope',
27+
name: 'scope',
28+
type: 'hidden',
29+
default: 'https://storage.azure.com/.default',
30+
},
31+
];
32+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import type {
2+
ICredentialDataDecryptedObject,
3+
ICredentialType,
4+
IHttpRequestOptions,
5+
INodeProperties,
6+
} from 'n8n-workflow';
7+
import { createHmac } from 'node:crypto';
8+
9+
import {
10+
getCanonicalizedHeadersString,
11+
getCanonicalizedResourceString,
12+
HeaderConstants,
13+
} from '../nodes/Microsoft/Storage/GenericFunctions';
14+
15+
export class AzureStorageSharedKeyApi implements ICredentialType {
16+
name = 'azureStorageSharedKeyApi';
17+
18+
displayName = 'Azure Storage Shared Key API';
19+
20+
documentationUrl = 'azurestorage';
21+
22+
properties: INodeProperties[] = [
23+
{
24+
displayName: 'Account',
25+
name: 'account',
26+
description: 'Account name',
27+
type: 'string',
28+
default: '',
29+
},
30+
{
31+
displayName: 'Key',
32+
name: 'key',
33+
description: 'Account key',
34+
type: 'string',
35+
typeOptions: {
36+
password: true,
37+
},
38+
default: '',
39+
},
40+
{
41+
displayName: 'Base URL',
42+
name: 'baseUrl',
43+
type: 'hidden',
44+
default: '=https://{{ $self["account"] }}.blob.core.windows.net',
45+
},
46+
];
47+
48+
async authenticate(
49+
credentials: ICredentialDataDecryptedObject,
50+
requestOptions: IHttpRequestOptions,
51+
): Promise<IHttpRequestOptions> {
52+
if (requestOptions.qs) {
53+
for (const [key, value] of Object.entries(requestOptions.qs)) {
54+
if (value === undefined) {
55+
delete requestOptions.qs[key];
56+
}
57+
}
58+
}
59+
if (requestOptions.headers) {
60+
for (const [key, value] of Object.entries(requestOptions.headers)) {
61+
if (value === undefined) {
62+
delete requestOptions.headers[key];
63+
}
64+
}
65+
}
66+
67+
requestOptions.method ??= 'GET';
68+
requestOptions.headers ??= {};
69+
70+
const stringToSign: string = [
71+
requestOptions.method.toUpperCase(),
72+
requestOptions.headers[HeaderConstants.CONTENT_LANGUAGE] ?? '',
73+
requestOptions.headers[HeaderConstants.CONTENT_ENCODING] ?? '',
74+
requestOptions.headers[HeaderConstants.CONTENT_LENGTH] ?? '',
75+
requestOptions.headers[HeaderConstants.CONTENT_MD5] ?? '',
76+
requestOptions.headers[HeaderConstants.CONTENT_TYPE] ?? '',
77+
requestOptions.headers[HeaderConstants.DATE] ?? '',
78+
requestOptions.headers[HeaderConstants.IF_MODIFIED_SINCE] ?? '',
79+
requestOptions.headers[HeaderConstants.IF_MATCH] ?? '',
80+
requestOptions.headers[HeaderConstants.IF_NONE_MATCH] ?? '',
81+
requestOptions.headers[HeaderConstants.IF_UNMODIFIED_SINCE] ?? '',
82+
requestOptions.headers[HeaderConstants.RANGE] ?? '',
83+
getCanonicalizedHeadersString(requestOptions) +
84+
getCanonicalizedResourceString(requestOptions, credentials),
85+
].join('\n');
86+
87+
const signature: string = createHmac('sha256', Buffer.from(credentials.key as string, 'base64'))
88+
.update(stringToSign, 'utf8')
89+
.digest('base64');
90+
91+
requestOptions.headers[HeaderConstants.AUTHORIZATION] =
92+
`SharedKey ${credentials.account as string}:${signature}`;
93+
94+
return requestOptions;
95+
}
96+
}

packages/nodes-base/nodes/Microsoft/Entra/test/GroupDescription.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { WorkflowTestData } from '@test/nodes/types';
77

88
import { microsoftEntraApiResponse, microsoftEntraNodeResponse } from './mocks';
99

10-
describe('Gong Node', () => {
10+
describe('Microsoft Entra Node', () => {
1111
const baseUrl = 'https://graph.microsoft.com/v1.0';
1212

1313
beforeEach(() => {

packages/nodes-base/nodes/Microsoft/Entra/test/MicrosoftEntra.node.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { microsoftEntraApiResponse, microsoftEntraNodeResponse } from './mocks';
1515
import { FAKE_CREDENTIALS_DATA } from '../../../../test/nodes/FakeCredentialsMap';
1616
import { MicrosoftEntra } from '../MicrosoftEntra.node';
1717

18-
describe('Gong Node', () => {
18+
describe('Microsoft Entra Node', () => {
1919
const baseUrl = 'https://graph.microsoft.com/v1.0';
2020

2121
beforeEach(() => {

packages/nodes-base/nodes/Microsoft/Entra/test/UserDescription.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { WorkflowTestData } from '@test/nodes/types';
77

88
import { microsoftEntraApiResponse, microsoftEntraNodeResponse } from './mocks';
99

10-
describe('Gong Node', () => {
10+
describe('Microsoft Entra Node', () => {
1111
const baseUrl = 'https://graph.microsoft.com/v1.0';
1212

1313
beforeEach(() => {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"node": "n8n-nodes-base.azureStorage",
3+
"nodeVersion": "1.0",
4+
"codexVersion": "1.0",
5+
"categories": ["Data & Storage"],
6+
"resources": {
7+
"credentialDocumentation": [
8+
{
9+
"url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/"
10+
}
11+
],
12+
"primaryDocumentation": [
13+
{
14+
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.azurestorage/"
15+
}
16+
]
17+
}
18+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
2+
import { NodeConnectionType } from 'n8n-workflow';
3+
4+
import { blobFields, blobOperations, containerFields, containerOperations } from './descriptions';
5+
import { getBlobs, getContainers } from './GenericFunctions';
6+
7+
export class AzureStorage implements INodeType {
8+
description: INodeTypeDescription = {
9+
displayName: 'Azure Storage',
10+
name: 'azureStorage',
11+
icon: {
12+
light: 'file:azureStorage.svg',
13+
dark: 'file:azureStorage.dark.svg',
14+
},
15+
group: ['transform'],
16+
version: 1,
17+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
18+
description: 'Interact with Azure Storage API',
19+
defaults: {
20+
name: 'Azure Storage',
21+
},
22+
inputs: [NodeConnectionType.Main],
23+
outputs: [NodeConnectionType.Main],
24+
credentials: [
25+
{
26+
name: 'azureStorageOAuth2Api',
27+
required: true,
28+
displayOptions: {
29+
show: {
30+
authentication: ['oAuth2'],
31+
},
32+
},
33+
},
34+
{
35+
name: 'azureStorageSharedKeyApi',
36+
required: true,
37+
displayOptions: {
38+
show: {
39+
authentication: ['sharedKey'],
40+
},
41+
},
42+
},
43+
],
44+
requestDefaults: {
45+
baseURL: '={{ $credentials.baseUrl }}',
46+
},
47+
properties: [
48+
{
49+
displayName: 'Authentication',
50+
name: 'authentication',
51+
type: 'options',
52+
options: [
53+
{
54+
name: 'OAuth2',
55+
value: 'oAuth2',
56+
},
57+
{
58+
name: 'Shared Key',
59+
value: 'sharedKey',
60+
},
61+
],
62+
default: 'sharedKey',
63+
},
64+
{
65+
displayName: 'Resource',
66+
name: 'resource',
67+
type: 'options',
68+
noDataExpression: true,
69+
options: [
70+
{
71+
name: 'Blob',
72+
value: 'blob',
73+
},
74+
{
75+
name: 'Container',
76+
value: 'container',
77+
},
78+
],
79+
default: 'container',
80+
},
81+
82+
...blobOperations,
83+
...blobFields,
84+
...containerOperations,
85+
...containerFields,
86+
],
87+
};
88+
89+
methods = {
90+
loadOptions: {},
91+
92+
listSearch: {
93+
getBlobs,
94+
getContainers,
95+
},
96+
};
97+
}

0 commit comments

Comments
 (0)