@hustleops/n8n-nodes-hustleops 0.1.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HustleOps
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # HustleOps n8n Community Node
2
+
3
+ This package provides an n8n community node for HustleOps incident response workflows.
4
+
5
+ The current package supports live HustleOps API requests for core alert, incident, observable, knowledge, comment, tag, and custom field operations.
6
+
7
+ ## Supported Resources And Operations
8
+
9
+ | Resource | Operations |
10
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | Alert | Search, Count, Get, Create, Update, Set Tags, Add Tags, Remove Tag |
12
+ | Incident | Search, Count, Get, Create, Update, Set Tags, Add Tags, Remove Tag |
13
+ | Observable | Search, Count, Get, Create, Update, Set Tags, Add Tags, Remove Tag |
14
+ | Knowledge | Search, Count, Get, Create, Update, Set Tags, Add Tags, Remove Tag |
15
+ | Comment | List, Search, Get Unread Count, Create, Mark Read, Update, Delete, Toggle Reaction, Toggle Pin |
16
+ | Tag | List, Search, Create, Update Color, Bulk Update Color, Delete, Bulk Delete |
17
+ | Custom Field | List Groups, Create Group, Update Group, Delete Group, List Definitions, Search Definitions, Create Definition, Update Definition, Bulk Update Definitions, Delete Definition, Bulk Delete Definitions, Get Values, Get Available, Batch Get Values, Replace Values, Update Selected Values Safely |
18
+
19
+ Payload operations expose an **Input Mode** selector.
20
+
21
+ - **Individual Fields** is the default. The node builds the request body from visible fields and validates the body before it reads credentials or calls HustleOps.
22
+ - **JSON Object** submits the JSON object as the complete request body. JSON Object mode is full replacement: values left in hidden Individual Fields controls are ignored.
23
+
24
+ Structured enum fields render as dropdowns. Picklist-backed fields such as alert type/status, incident status/category, observable type/threat level/criticality, and knowledge type load their dropdown options from HustleOps `/picklists/:domain` endpoints and send the selected API value in the create or update payload.
25
+
26
+ Use `Additional JSON` only for advanced supported fields or intentional overrides. `Additional JSON` is merged after structured fields, so duplicate keys in `Additional JSON` win. Unsupported fields still fail before the API request is sent.
27
+
28
+ Core search paths are `/alerts/search`, `/incidents/search`, `/observables/search`, and `/knowledge/search`. Enable `Return All` to fetch pages until the API response reaches `totalPages`, `Max Items`, or `Max Pages`.
29
+
30
+ Required create fields appear as normal n8n fields in Individual Fields mode. Optional create fields are available under `Additional Fields`, and update payload fields are available under `Fields to Update`. Unsupported fields still fail before the API request is sent.
31
+
32
+ ## Tag Operations
33
+
34
+ Core resources expose `Set Tags`, `Add Tags`, and `Remove Tag` operations directly under Alert, Incident, Observable, and Knowledge.
35
+
36
+ - `Set Tags`: calls `PUT /<resource>/:id/tags` with `{ "values": [...] }`. An empty array clears all tags.
37
+ - `Add Tags`: calls `POST /<resource>/:id/tags` and requires at least one value.
38
+ - `Remove Tag`: calls `DELETE /<resource>/:id/tags/:tagId`.
39
+
40
+ Tag values are validated before the API request is sent: at most 20 tags per entity, at most 30 characters per tag, and only letters, numbers, spaces, and `* ! @ # $ : . - _ =`.
41
+
42
+ The `Tag` resource covers admin tag management:
43
+
44
+ - `List`: calls `GET /tags`, with optional `withCounts=true` for admin-only counts.
45
+ - `Search`: calls `POST /tags/search` with a SearchRequest body.
46
+ - `Create`: calls `POST /tags` with `{ "value": "vip", "color": "#0EA5E9" }`.
47
+ - `Update Color`: calls `PATCH /tags/:id` with `{ "color": "#A855F7" }`. Tag values are immutable.
48
+ - `Bulk Update Color`: calls `PATCH /tags/bulk` with `{ "ids": [...], "color": "#22C55E" }`.
49
+ - `Delete`: calls `DELETE /tags/:id?force=true` when Force is enabled.
50
+ - `Bulk Delete`: calls `POST /tags/bulk-delete` with `{ "ids": [...], "force": true }`.
51
+
52
+ Tag payload operations support **Input Mode**. In **Individual Fields** mode, use fields such as `Tag Value`, `Tag Color`, `Tag IDs`, and `Force`. In **JSON Object** mode, submit the complete tag payload, for example:
53
+
54
+ ```json
55
+ {
56
+ "ids": ["11111111-1111-4111-8111-111111111111"],
57
+ "force": true
58
+ }
59
+ ```
60
+
61
+ ## Custom Field Operations
62
+
63
+ The `Custom Field` resource covers custom field groups, definitions, and values.
64
+
65
+ Group operations call `/custom-fields/groups`. Definition operations call `/custom-fields/definitions`, `/custom-fields/definitions/search`, `/custom-fields/definitions/bulk`, and `/custom-fields/definitions/bulk-delete`. Definition update rejects `fieldType` because field type is immutable.
66
+
67
+ Value operations use uppercase entity types: `ALERT`, `INCIDENT`, `OBSERVABLE`, and `KNOWLEDGE`.
68
+
69
+ - `Get Values`: calls `GET /custom-fields/values/:entityType/:entityId`.
70
+ - `Get Available`: calls `GET /custom-fields/available/:entityType/:entityId`.
71
+ - `Batch Get Values`: calls `POST /custom-fields/values/batch` with `{ "entityType": "ALERT", "entityIds": [...] }` and allows up to 100 IDs.
72
+ - `Replace Values`: calls `PATCH /custom-fields/values/:entityType/:entityId` with the exact attached field set to keep.
73
+ - `Update Selected Values Safely`: first reads existing values, merges selected field changes, then patches the complete attached field set so omitted attached fields are preserved.
74
+
75
+ Custom field write operations support **Input Mode**. In **Individual Fields** mode, use the visible group, definition, definition ID, entity ID list, or attached field row controls for the selected operation. In **JSON Object** mode, submit the complete payload, for example:
76
+
77
+ ```json
78
+ {
79
+ "entityType": "INCIDENT",
80
+ "entityIds": ["22222222-2222-4222-8222-222222222222"]
81
+ }
82
+ ```
83
+
84
+ Custom field values sent to the API are strings or `null`. `MULTI_SELECT` array inputs are serialized with `JSON.stringify`, so `["a", "b"]` is sent as `"[\"a\",\"b\"]"`. BOOLEAN values must be `"true"` or `"false"`; NUMBER, DATE, and URL values are validated before sending.
85
+
86
+ ## Getting an API Key
87
+
88
+ API keys must be created outside n8n by a HustleOps administrator or another user with access to API-key management.
89
+
90
+ The n8n node does not create, rotate, or revoke HustleOps API keys.
91
+
92
+ The API key owner needs the HustleOps permissions required by the operation being run, such as view permission for Search/Get and create or update permission for write operations.
93
+
94
+ ## Authentication
95
+
96
+ Create a `HustleOps API` credential in n8n with:
97
+
98
+ - `Base URL`: the full HTTPS URL of your HustleOps instance. Use HTTP only for local development on `localhost`, `127.0.0.1`, or `::1`.
99
+ - `API Key`: your HustleOps API key.
100
+ - `Ignore SSL Issues`: disabled by default. Enable only for local or private test instances that use a self-signed certificate. For production, install a trusted certificate or add the issuing CA to the n8n/Node trust store.
101
+
102
+ Requests send:
103
+
104
+ ```text
105
+ x-api-key: ho_sk_...
106
+ Accept: application/json
107
+ Content-Type: application/json
108
+ ```
109
+
110
+ API keys act as the user who owns the key, so role and permission checks still apply.
111
+
112
+ ## Create Examples
113
+
114
+ With **Input Mode** set to **Individual Fields**, set these values through node fields. The JSON below shows the API payload produced from the structured fields.
115
+
116
+ Alert field values:
117
+
118
+ | n8n field | Value |
119
+ | ------------- | -------------------------- |
120
+ | `Name` | `Suspicious login` |
121
+ | `Description` | `Okta anomaly` |
122
+ | `Severity` | `HIGH` |
123
+ | `TLP` | `AMBER` |
124
+ | `Source` | `okta` |
125
+ | `Type` | `identity` |
126
+ | `Source Ref` | `evt_12345` |
127
+ | `Detected At` | `2026-06-28T12:00:00.000Z` |
128
+
129
+ ```json
130
+ {
131
+ "name": "Suspicious login",
132
+ "description": "Okta anomaly",
133
+ "severity": "HIGH",
134
+ "tlp": "AMBER",
135
+ "source": "okta",
136
+ "type": "identity",
137
+ "sourceRef": "evt_12345",
138
+ "detectedAt": "2026-06-28T12:00:00.000Z"
139
+ }
140
+ ```
141
+
142
+ Incident field values:
143
+
144
+ | n8n field | Value |
145
+ | ------------- | --------------------------------------------------- |
146
+ | `Name` | `Credential theft investigation` |
147
+ | `Description` | `Coordinated response for suspicious Okta activity` |
148
+ | `Severity` | `HIGH` |
149
+ | `TLP` | `AMBER` |
150
+ | `Category` | `identity` |
151
+
152
+ ```json
153
+ {
154
+ "name": "Credential theft investigation",
155
+ "description": "Coordinated response for suspicious Okta activity",
156
+ "severity": "HIGH",
157
+ "tlp": "AMBER",
158
+ "category": "identity"
159
+ }
160
+ ```
161
+
162
+ Observable field values:
163
+
164
+ | n8n field | Value |
165
+ | -------------- | -------------------------- |
166
+ | `Value` | `198.51.100.10` |
167
+ | `Type` | `ip` |
168
+ | `Threat Level` | `SUSPICIOUS` |
169
+ | `TLP` | `AMBER` |
170
+ | `First Seen` | `2026-06-28T11:30:00.000Z` |
171
+ | `Last Seen` | `2026-06-28T12:00:00.000Z` |
172
+
173
+ ```json
174
+ {
175
+ "value": "198.51.100.10",
176
+ "type": "ip",
177
+ "threatLevel": "SUSPICIOUS",
178
+ "tlp": "AMBER",
179
+ "firstSeen": "2026-06-28T11:30:00.000Z",
180
+ "lastSeen": "2026-06-28T12:00:00.000Z"
181
+ }
182
+ ```
183
+
184
+ Knowledge field values:
185
+
186
+ | n8n field | Value |
187
+ | ------------- | ------------------------------------------ |
188
+ | `Value` | `Containment runbook` |
189
+ | `Type` | `runbook` |
190
+ | `TLP` | `AMBER` |
191
+ | `Description` | `Steps for disabling compromised accounts` |
192
+
193
+ ```json
194
+ {
195
+ "value": "Containment runbook",
196
+ "type": "runbook",
197
+ "tlp": "AMBER",
198
+ "description": "Steps for disabling compromised accounts"
199
+ }
200
+ ```
201
+
202
+ ## Update Fields
203
+
204
+ Update operations keep `ID` as a required field and put editable payload values under `Fields to Update` in **Individual Fields** mode.
205
+
206
+ For example, updating an Observable can set `Threat Level`, `Criticality`, and `Version` under `Fields to Update`.
207
+
208
+ To submit a complete update body or clear a supported nullable field, use **JSON Object** mode, such as `{ "summary": null }`. Blank Individual Fields update values are omitted.
209
+
210
+ ## Search Pagination
211
+
212
+ By default, Search returns one output item per row in the API response `data` array.
213
+
214
+ Enable `Include Pagination Metadata` to return the raw page object with `data`, `total`, `page`, `pageSize`, and `totalPages`.
215
+
216
+ Enable `Return All` to fetch multiple pages. `Max Items` and `Max Pages` bound the request so large result sets do not run indefinitely.
217
+
218
+ ## Comment Operations
219
+
220
+ Comment operations work against comment threads attached to core entities. Choose `Comment` as the resource, then choose one of:
221
+
222
+ - `List`: calls `GET /comments` with `entityType`, `entityId`, optional `cursor`, and `take`.
223
+ - `Search`: calls `GET /comments/search` with `entityType`, `entityId`, and `q`.
224
+ - `Get Unread Count`: calls `GET /comments/unread-count` and returns `{ "unreadCount": number }`.
225
+ - `Create`: calls `POST /comments` with content, parent ID, attachment IDs, or a complete JSON Object payload.
226
+ - `Mark Read`: calls `POST /comments/read` with `entityType` and `entityId`.
227
+ - `Update`: calls `PATCH /comments/:id` with `{ "content": "Updated containment note" }`.
228
+ - `Delete`: calls `DELETE /comments/:id`.
229
+ - `Toggle Reaction`: calls `POST /comments/:id/reactions` with `{ "emoji": "\u2705" }`.
230
+ - `Toggle Pin`: calls `PATCH /comments/:id/pin`.
231
+
232
+ Comment `entityType` must be one of `ALERT`, `INCIDENT`, `OBSERVABLE`, or `KNOWLEDGE`.
233
+
234
+ List uses cursor pagination. `Take` defaults to `50` and must be between `1` and `100`. Enable `Include Cursor Metadata` to return the raw response containing `items` and `nextCursor` instead of one output item per comment.
235
+
236
+ Search emits one item per comment and caps emitted rows with `Max Results`, which defaults to `100`.
237
+
238
+ ### n8n Fields and Outputs
239
+
240
+ | Operation | Required n8n fields | Optional n8n fields | Output |
241
+ | ---------------- | ------------------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
242
+ | List | `Entity Type`, `Entity ID` | `Take`, `Cursor`, `Include Cursor Metadata` | One item per comment, or one raw response item with `items` and `nextCursor` when cursor metadata is enabled |
243
+ | Search | `Entity Type`, `Entity ID`, `Search Query` | `Max Results` | One item per matching comment, up to `Max Results` |
244
+ | Get Unread Count | `Entity Type`, `Entity ID` | none | `{ "unreadCount": number }` |
245
+ | Create | `Entity Type`, `Entity ID`, `Input Mode` | `Content`, `Parent ID`, `Attachment IDs`, or JSON Object | Created comment plus `autoTransitioned` |
246
+ | Mark Read | `Entity Type`, `Entity ID` | none | `{ "success": true, "entityType": "INCIDENT", "entityId": "11111111-1111-4111-8111-111111111111" }` |
247
+ | Update | `Comment ID`, `Input Mode` | `Content` or JSON Object | Updated comment |
248
+ | Delete | `Comment ID` | none | `{ "id": "22222222-2222-4222-8222-222222222222", "entityType": "ALERT", "entityId": "11111111-1111-4111-8111-111111111111" }` |
249
+ | Toggle Reaction | `Comment ID`, `Input Mode` | `Emoji` or JSON Object | Updated comment |
250
+ | Toggle Pin | `Comment ID` | none | Updated comment |
251
+
252
+ ### Comment Payload Examples
253
+
254
+ Create comment in **Individual Fields** mode. Set `Entity Type`, `Entity ID`, and `Content` in the node fields. The request body is:
255
+
256
+ ```json
257
+ {
258
+ "content": "Containment started"
259
+ }
260
+ ```
261
+
262
+ Create reply in **JSON Object** mode. Set `Entity Type` and `Entity ID` in the node fields, then submit this complete JSON Object:
263
+
264
+ ```json
265
+ {
266
+ "content": "Adding timeline details",
267
+ "parentId": "22222222-2222-4222-8222-222222222222"
268
+ }
269
+ ```
270
+
271
+ Create comment with staged attachments in **JSON Object** mode. Set `Entity Type` and `Entity ID` in the node fields, then submit this complete JSON Object:
272
+
273
+ ```json
274
+ {
275
+ "attachmentIds": ["33333333-3333-4333-8333-333333333333"]
276
+ }
277
+ ```
278
+
279
+ Update comment in **Individual Fields** mode by setting `Content`, or submit this complete JSON Object in **JSON Object** mode:
280
+
281
+ ```json
282
+ {
283
+ "content": "Updated containment note"
284
+ }
285
+ ```
286
+
287
+ Toggle reaction in **Individual Fields** mode by setting `Emoji`, or submit this complete JSON Object in **JSON Object** mode:
288
+
289
+ ```json
290
+ {
291
+ "emoji": "\u2705"
292
+ }
293
+ ```
@@ -0,0 +1,12 @@
1
+ import type { IAuthenticateGeneric, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class HustleOpsApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ icon: {
6
+ readonly light: "file:../nodes/HustleOps/hustleops.svg";
7
+ readonly dark: "file:../nodes/HustleOps/hustleops.dark.svg";
8
+ };
9
+ documentationUrl: string;
10
+ properties: INodeProperties[];
11
+ authenticate: IAuthenticateGeneric;
12
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HustleOpsApi = void 0;
4
+ const constants_1 = require("../nodes/HustleOps/constants");
5
+ class HustleOpsApi {
6
+ constructor() {
7
+ this.name = 'hustleOpsApi';
8
+ this.displayName = 'HustleOps API';
9
+ this.icon = {
10
+ light: 'file:../nodes/HustleOps/hustleops.svg',
11
+ dark: 'file:../nodes/HustleOps/hustleops.dark.svg',
12
+ };
13
+ this.documentationUrl = 'https://hustleops.io/docs';
14
+ this.properties = [
15
+ {
16
+ displayName: 'Base URL',
17
+ name: 'baseUrl',
18
+ type: 'string',
19
+ default: '',
20
+ required: true,
21
+ placeholder: 'https://hustleops.example.com',
22
+ description: 'Full HTTPS HustleOps instance URL. Use HTTP only for local development.',
23
+ },
24
+ {
25
+ displayName: 'API Key',
26
+ name: 'apiKey',
27
+ type: 'string',
28
+ typeOptions: {
29
+ password: true,
30
+ },
31
+ default: '',
32
+ required: true,
33
+ description: 'API key used to authenticate with HustleOps.',
34
+ },
35
+ {
36
+ displayName: 'Ignore SSL Issues',
37
+ name: 'ignoreSslIssues',
38
+ type: 'boolean',
39
+ default: false,
40
+ description: 'Whether to connect even if SSL certificate validation is not possible',
41
+ },
42
+ ];
43
+ this.authenticate = {
44
+ type: 'generic',
45
+ properties: {
46
+ headers: {
47
+ [constants_1.HUSTLEOPS_API_KEY_HEADER]: '={{$credentials.apiKey}}',
48
+ },
49
+ },
50
+ };
51
+ }
52
+ }
53
+ exports.HustleOpsApi = HustleOpsApi;
@@ -0,0 +1,38 @@
1
+ import type { ICredentialDataDecryptedObject, ICredentialTestFunctions, IDataObject, IExecuteFunctions } from 'n8n-workflow';
2
+ export type HustleOpsRequestContext = Pick<IExecuteFunctions, 'getCredentials' | 'getNode'> & {
3
+ helpers: Pick<IExecuteFunctions['helpers'], 'httpRequest'>;
4
+ };
5
+ export type HustleOpsHttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
6
+ export type HustleOpsQueryParams = Record<string, string | number | boolean | null | undefined>;
7
+ type PaginationOptions = {
8
+ maxItems?: number;
9
+ maxPages?: number;
10
+ };
11
+ type HustleOpsApiClient = {
12
+ request: <T = IDataObject>(method: HustleOpsHttpMethod, path: string, body?: IDataObject, query?: HustleOpsQueryParams) => Promise<T>;
13
+ requestEachPage: (path: string, initialBody: IDataObject, options: PaginationOptions, onRow: (row: IDataObject) => void) => Promise<void>;
14
+ };
15
+ export type PaginatedResponse = {
16
+ data: IDataObject[];
17
+ total: number;
18
+ page: number;
19
+ pageSize: number;
20
+ totalPages: number;
21
+ };
22
+ export declare const MAX_JSON_PARAMETER_CHARS = 100000;
23
+ export declare function normalizeBaseUrl(input: string): string;
24
+ export declare function isUuid(value: unknown): value is string;
25
+ export declare function assertUuid(value: unknown, label: string): string;
26
+ export declare function parseIntegerInRange(value: unknown, label: string, minimum: number, maximum: number, defaultValue?: number): number;
27
+ export declare function safePathSegment(value: string, label: string): string;
28
+ export declare function compactObject<T>(value: T): T;
29
+ export declare function redactSensitiveText(value: string): string;
30
+ export declare function assertPaginatedResponse(value: unknown, label: string): PaginatedResponse;
31
+ export declare function parsePositiveInteger(context: IExecuteFunctions, value: unknown, label: string, itemIndex: number): number;
32
+ export declare function parseJsonObject(context: IExecuteFunctions, value: unknown, fieldName: string, itemIndex: number): IDataObject;
33
+ export declare function parseJsonArray(context: Pick<IExecuteFunctions, 'getNode'>, value: unknown, fieldName: string, itemIndex: number): unknown[];
34
+ export declare function testHustleOpsApiCredentials(context: ICredentialTestFunctions, credentialData: ICredentialDataDecryptedObject): Promise<void>;
35
+ export declare function createHustleOpsApiClient(context: HustleOpsRequestContext, itemIndex: number): Promise<HustleOpsApiClient>;
36
+ export declare function hustleOpsApiRequest<T = IDataObject>(context: HustleOpsRequestContext, method: HustleOpsHttpMethod, path: string, body: IDataObject | undefined, itemIndex: number, query?: HustleOpsQueryParams): Promise<T>;
37
+ export declare function hustleOpsApiRequestEachPage(context: HustleOpsRequestContext, path: string, initialBody: IDataObject, itemIndex: number, options: PaginationOptions, onRow: (row: IDataObject) => void): Promise<void>;
38
+ export {};