n8n-nodes-thirdfactor-obsidian 0.1.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/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/credentials/ThirdFactorObsidianApi.credentials.d.ts +9 -0
- package/dist/credentials/ThirdFactorObsidianApi.credentials.js +46 -0
- package/dist/nodes/ThirdFactorObsidian/GenericFunctions.d.ts +15 -0
- package/dist/nodes/ThirdFactorObsidian/GenericFunctions.js +54 -0
- package/dist/nodes/ThirdFactorObsidian/ThirdFactorObsidian.node.d.ts +11 -0
- package/dist/nodes/ThirdFactorObsidian/ThirdFactorObsidian.node.js +413 -0
- package/dist/nodes/ThirdFactorObsidian/ThirdFactorObsidianTrigger.node.d.ts +20 -0
- package/dist/nodes/ThirdFactorObsidian/ThirdFactorObsidianTrigger.node.js +192 -0
- package/dist/nodes/ThirdFactorObsidian/thirdfactor.png +0 -0
- package/index.js +3 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2026 ThirdFactor / Prixa. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Commercial License
|
|
4
|
+
|
|
5
|
+
This SDK is proprietary software, provided as part of the ThirdFactor /
|
|
6
|
+
Obsidian KYC platform. Use, reproduction, and distribution are permitted
|
|
7
|
+
only to ThirdFactor customers and partners, solely to integrate applications
|
|
8
|
+
with the ThirdFactor platform, under the terms of your ThirdFactor service
|
|
9
|
+
agreement (or another written agreement with ThirdFactor).
|
|
10
|
+
|
|
11
|
+
You may not modify, reverse engineer, sublicense, rent, or redistribute this
|
|
12
|
+
SDK except as your agreement expressly allows or applicable law permits
|
|
13
|
+
notwithstanding this limitation.
|
|
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
|
|
18
|
+
THIRDFACTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM
|
|
19
|
+
THE USE OF THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
Licensing questions: support@thirdfactor.ai
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# n8n-nodes-thirdfactor-obsidian
|
|
2
|
+
|
|
3
|
+
[n8n](https://n8n.io) community nodes for **ThirdFactor Obsidian** — the
|
|
4
|
+
identity-verification (KYC/KYB) platform. A webhook trigger for platform
|
|
5
|
+
events plus an action node for sessions, tags, and standalone AML checks.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
In n8n: **Settings → Community Nodes → Install** and enter
|
|
10
|
+
`n8n-nodes-thirdfactor-obsidian`, or on a self-hosted instance:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install n8n-nodes-thirdfactor-obsidian
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Credentials
|
|
17
|
+
|
|
18
|
+
Create a **ThirdFactor Obsidian API** credential:
|
|
19
|
+
|
|
20
|
+
| Field | Description |
|
|
21
|
+
|---|---|
|
|
22
|
+
| **Base URL** | Your deployment's API base URL, e.g. `https://api.thirdfactor.example`. |
|
|
23
|
+
| **API Key** | A tenant API key (`sk_…`) from the console (Settings → API Keys). Sent as the `X-API-Key` header. |
|
|
24
|
+
|
|
25
|
+
The credential test calls `GET /v3/automation/me/` on your deployment.
|
|
26
|
+
|
|
27
|
+
## ThirdFactor Obsidian Trigger
|
|
28
|
+
|
|
29
|
+
Webhook trigger backed by the platform's REST-hooks API. When the workflow is
|
|
30
|
+
activated, the node subscribes via `POST /v3/automation/subscriptions/`
|
|
31
|
+
(`source: "n8n"`); deactivating unsubscribes.
|
|
32
|
+
|
|
33
|
+
- **Events** — multi-select, fetched live from `/v3/automation/events/`
|
|
34
|
+
(session lifecycle, module results, AML/blocklist/risk/duplicate signals,
|
|
35
|
+
review cases, contact/document progress, credits).
|
|
36
|
+
- **Workflows** — optional filter; only sessions of the selected verification
|
|
37
|
+
workflows fire the trigger.
|
|
38
|
+
- **Modules** — optional filter for module-scoped events
|
|
39
|
+
(e.g. `module.completed` for `AML_SCREENING` only).
|
|
40
|
+
- **Verify Signature** (default on) — validates the `X-Webhook-Signature`
|
|
41
|
+
header (`t=<unix>,v1=<hex>`, HMAC-SHA256 of `"<t>.<raw_body>"` with the
|
|
42
|
+
subscription secret) and rejects mismatched or stale (>5 min) deliveries
|
|
43
|
+
with HTTP 401.
|
|
44
|
+
|
|
45
|
+
Each delivery arrives as one item:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"payload_version": 1,
|
|
50
|
+
"event": "session.approved",
|
|
51
|
+
"id": "<delivery uuid>",
|
|
52
|
+
"created_at": "<iso timestamp>",
|
|
53
|
+
"data": { "session": { "id": "…", "status": "approved", "vendor_data": "…" } }
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
To test without waiting for a real event, use **Listen for test event** and
|
|
58
|
+
complete a verification, or fetch a recent payload yourself from
|
|
59
|
+
`GET /v3/automation/samples/<event>/`.
|
|
60
|
+
|
|
61
|
+
## ThirdFactor Obsidian (action node)
|
|
62
|
+
|
|
63
|
+
| Resource | Operation | Endpoint |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Session | Create | `POST /v3/session/` — returns the hosted verification `url` |
|
|
66
|
+
| Session | Get | `GET /v3/session/<id>/` |
|
|
67
|
+
| Session | Get Decision | `GET /v3/session/<id>/decision/` |
|
|
68
|
+
| Session | Get Many | `GET /v3/session/` (status / page / page size filters) |
|
|
69
|
+
| Session | Add Tag | `POST /v3/session/<id>/tags/` |
|
|
70
|
+
| Tools | AML Check | `POST /v3/tools/aml-check/` (billed per use) |
|
|
71
|
+
|
|
72
|
+
**Session → Create** offers a dynamic Workflow dropdown (from
|
|
73
|
+
`/v3/workflows/`) plus vendor data, contact email/phone, expiry, metadata
|
|
74
|
+
(JSON), callback URL, and locale.
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
cd sdks/n8n
|
|
80
|
+
npm install
|
|
81
|
+
npm run build # tsc + copies the node icon (thirdfactor.png — the product favicon) into dist/
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Link into a local n8n for manual testing:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm link
|
|
88
|
+
cd ~/.n8n/custom # or your n8n custom-extensions dir
|
|
89
|
+
npm link n8n-nodes-thirdfactor-obsidian
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Compatibility
|
|
93
|
+
|
|
94
|
+
Built against `n8n-workflow` ^1.70 with the community-node API version 1;
|
|
95
|
+
requires Node.js ≥ 18.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class ThirdFactorObsidianApi implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
documentationUrl: string;
|
|
6
|
+
properties: INodeProperties[];
|
|
7
|
+
authenticate: IAuthenticateGeneric;
|
|
8
|
+
test: ICredentialTestRequest;
|
|
9
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ThirdFactorObsidianApi = void 0;
|
|
4
|
+
class ThirdFactorObsidianApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'thirdFactorObsidianApi';
|
|
7
|
+
this.displayName = 'ThirdFactor Obsidian API';
|
|
8
|
+
this.documentationUrl = 'https://github.com/thirdfactor/obsidian';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Base URL',
|
|
12
|
+
name: 'baseUrl',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '',
|
|
15
|
+
placeholder: 'https://api.thirdfactor.example',
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Your ThirdFactor Obsidian API base URL (each deployment has its own domain)',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'API Key',
|
|
21
|
+
name: 'apiKey',
|
|
22
|
+
type: 'string',
|
|
23
|
+
typeOptions: { password: true },
|
|
24
|
+
default: '',
|
|
25
|
+
required: true,
|
|
26
|
+
description: 'A tenant API key from the ThirdFactor Obsidian console (starts with "sk_"). Sent as the X-API-Key header.',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
this.authenticate = {
|
|
30
|
+
type: 'generic',
|
|
31
|
+
properties: {
|
|
32
|
+
headers: {
|
|
33
|
+
'X-API-Key': '={{$credentials.apiKey}}',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
this.test = {
|
|
38
|
+
request: {
|
|
39
|
+
baseURL: '={{$credentials.baseUrl.replace(new RegExp("/+$"), "")}}',
|
|
40
|
+
url: '/v3/automation/me/',
|
|
41
|
+
method: 'GET',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.ThirdFactorObsidianApi = ThirdFactorObsidianApi;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IDataObject, IExecuteFunctions, IHookFunctions, IHttpRequestMethods, ILoadOptionsFunctions, INodePropertyOptions, IWebhookFunctions } from 'n8n-workflow';
|
|
2
|
+
type ObsidianContext = IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions;
|
|
3
|
+
/**
|
|
4
|
+
* Authenticated request against the tenant's ThirdFactor Obsidian deployment.
|
|
5
|
+
* The credential injects the X-API-Key header; the base URL comes from the
|
|
6
|
+
* same credential so every endpoint path here is deployment-relative.
|
|
7
|
+
*/
|
|
8
|
+
export declare function apiRequest(this: ObsidianContext, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: IDataObject): Promise<any>;
|
|
9
|
+
/** GET /v3/automation/events/ → options for the events multi-select. */
|
|
10
|
+
export declare function loadEventOptions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
11
|
+
/** GET /v3/automation/events/ → the module/feature filter vocabulary. */
|
|
12
|
+
export declare function loadFeatureOptions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
13
|
+
/** GET /v3/workflows/ → workflow dropdown options (bare array response). */
|
|
14
|
+
export declare function loadWorkflowOptions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apiRequest = apiRequest;
|
|
4
|
+
exports.loadEventOptions = loadEventOptions;
|
|
5
|
+
exports.loadFeatureOptions = loadFeatureOptions;
|
|
6
|
+
exports.loadWorkflowOptions = loadWorkflowOptions;
|
|
7
|
+
/**
|
|
8
|
+
* Authenticated request against the tenant's ThirdFactor Obsidian deployment.
|
|
9
|
+
* The credential injects the X-API-Key header; the base URL comes from the
|
|
10
|
+
* same credential so every endpoint path here is deployment-relative.
|
|
11
|
+
*/
|
|
12
|
+
async function apiRequest(method, endpoint, body, qs) {
|
|
13
|
+
const credentials = await this.getCredentials('thirdFactorObsidianApi');
|
|
14
|
+
const baseUrl = String(credentials.baseUrl || '').replace(/\/+$/, '');
|
|
15
|
+
const options = {
|
|
16
|
+
method,
|
|
17
|
+
url: `${baseUrl}${endpoint}`,
|
|
18
|
+
json: true,
|
|
19
|
+
};
|
|
20
|
+
if (body && Object.keys(body).length) {
|
|
21
|
+
options.body = body;
|
|
22
|
+
}
|
|
23
|
+
if (qs && Object.keys(qs).length) {
|
|
24
|
+
options.qs = qs;
|
|
25
|
+
}
|
|
26
|
+
return await this.helpers.httpRequestWithAuthentication.call(this, 'thirdFactorObsidianApi', options);
|
|
27
|
+
}
|
|
28
|
+
/** GET /v3/automation/events/ → options for the events multi-select. */
|
|
29
|
+
async function loadEventOptions() {
|
|
30
|
+
const response = await apiRequest.call(this, 'GET', '/v3/automation/events/');
|
|
31
|
+
const events = (response === null || response === void 0 ? void 0 : response.events) || [];
|
|
32
|
+
return events.map((event) => ({
|
|
33
|
+
name: `${event.group}: ${event.label}`,
|
|
34
|
+
value: String(event.key),
|
|
35
|
+
description: String(event.description || ''),
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
/** GET /v3/automation/events/ → the module/feature filter vocabulary. */
|
|
39
|
+
async function loadFeatureOptions() {
|
|
40
|
+
const response = await apiRequest.call(this, 'GET', '/v3/automation/events/');
|
|
41
|
+
const features = (response === null || response === void 0 ? void 0 : response.features) || [];
|
|
42
|
+
return features.map((feature) => ({ name: feature, value: feature }));
|
|
43
|
+
}
|
|
44
|
+
/** GET /v3/workflows/ → workflow dropdown options (bare array response). */
|
|
45
|
+
async function loadWorkflowOptions() {
|
|
46
|
+
const response = await apiRequest.call(this, 'GET', '/v3/workflows/');
|
|
47
|
+
const workflows = (Array.isArray(response) ? response : []);
|
|
48
|
+
return workflows.map((workflow) => ({
|
|
49
|
+
name: workflow.verification_type
|
|
50
|
+
? `${workflow.name} (${workflow.verification_type})`
|
|
51
|
+
: String(workflow.name),
|
|
52
|
+
value: String(workflow.id),
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
import { loadWorkflowOptions } from './GenericFunctions';
|
|
3
|
+
export declare class ThirdFactorObsidian implements INodeType {
|
|
4
|
+
description: INodeTypeDescription;
|
|
5
|
+
methods: {
|
|
6
|
+
loadOptions: {
|
|
7
|
+
getWorkflows: typeof loadWorkflowOptions;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ThirdFactorObsidian = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const GenericFunctions_1 = require("./GenericFunctions");
|
|
6
|
+
class ThirdFactorObsidian {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.description = {
|
|
9
|
+
displayName: 'ThirdFactor Obsidian',
|
|
10
|
+
name: 'thirdFactorObsidian',
|
|
11
|
+
icon: 'file:thirdfactor.png',
|
|
12
|
+
group: ['transform'],
|
|
13
|
+
version: 1,
|
|
14
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
15
|
+
description: 'Interact with the ThirdFactor Obsidian identity-verification platform (sessions, tags, AML checks)',
|
|
16
|
+
defaults: {
|
|
17
|
+
name: 'ThirdFactor Obsidian',
|
|
18
|
+
},
|
|
19
|
+
inputs: ["main" /* NodeConnectionType.Main */],
|
|
20
|
+
outputs: ["main" /* NodeConnectionType.Main */],
|
|
21
|
+
credentials: [
|
|
22
|
+
{
|
|
23
|
+
name: 'thirdFactorObsidianApi',
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
properties: [
|
|
28
|
+
{
|
|
29
|
+
displayName: 'Resource',
|
|
30
|
+
name: 'resource',
|
|
31
|
+
type: 'options',
|
|
32
|
+
noDataExpression: true,
|
|
33
|
+
options: [
|
|
34
|
+
{ name: 'Session', value: 'session' },
|
|
35
|
+
{ name: 'Tools', value: 'tools' },
|
|
36
|
+
],
|
|
37
|
+
default: 'session',
|
|
38
|
+
},
|
|
39
|
+
// ── Session operations ────────────────────────────────────────
|
|
40
|
+
{
|
|
41
|
+
displayName: 'Operation',
|
|
42
|
+
name: 'operation',
|
|
43
|
+
type: 'options',
|
|
44
|
+
noDataExpression: true,
|
|
45
|
+
displayOptions: { show: { resource: ['session'] } },
|
|
46
|
+
options: [
|
|
47
|
+
{
|
|
48
|
+
name: 'Add Tag',
|
|
49
|
+
value: 'addTag',
|
|
50
|
+
description: 'Apply a tag to a verification session',
|
|
51
|
+
action: 'Add a tag to a session',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Create',
|
|
55
|
+
value: 'create',
|
|
56
|
+
description: 'Create a hosted verification session and get the verification link',
|
|
57
|
+
action: 'Create a verification session',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Get',
|
|
61
|
+
value: 'get',
|
|
62
|
+
description: 'Retrieve a verification session',
|
|
63
|
+
action: 'Get a verification session',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'Get Decision',
|
|
67
|
+
value: 'getDecision',
|
|
68
|
+
description: 'Retrieve the decision detail of a session',
|
|
69
|
+
action: 'Get a session decision',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Get Many',
|
|
73
|
+
value: 'getMany',
|
|
74
|
+
description: 'List recent verification sessions',
|
|
75
|
+
action: 'Get many verification sessions',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
default: 'create',
|
|
79
|
+
},
|
|
80
|
+
// Session → Create
|
|
81
|
+
{
|
|
82
|
+
displayName: 'Workflow Name or ID',
|
|
83
|
+
name: 'workflowId',
|
|
84
|
+
type: 'options',
|
|
85
|
+
typeOptions: {
|
|
86
|
+
loadOptionsMethod: 'getWorkflows',
|
|
87
|
+
},
|
|
88
|
+
default: '',
|
|
89
|
+
displayOptions: { show: { resource: ['session'], operation: ['create'] } },
|
|
90
|
+
description: 'The verification workflow to run. Leave empty for the tenant default. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
displayName: 'Additional Fields',
|
|
94
|
+
name: 'additionalFields',
|
|
95
|
+
type: 'collection',
|
|
96
|
+
placeholder: 'Add Field',
|
|
97
|
+
default: {},
|
|
98
|
+
displayOptions: { show: { resource: ['session'], operation: ['create'] } },
|
|
99
|
+
options: [
|
|
100
|
+
{
|
|
101
|
+
displayName: 'Callback URL',
|
|
102
|
+
name: 'callbackUrl',
|
|
103
|
+
type: 'string',
|
|
104
|
+
default: '',
|
|
105
|
+
description: 'Where the hosted flow redirects the applicant after completion (your app, not a webhook)',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
displayName: 'Contact Email',
|
|
109
|
+
name: 'contactEmail',
|
|
110
|
+
type: 'string',
|
|
111
|
+
placeholder: 'name@email.com',
|
|
112
|
+
default: '',
|
|
113
|
+
description: 'Applicant email address',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
displayName: 'Contact Phone',
|
|
117
|
+
name: 'contactPhone',
|
|
118
|
+
type: 'string',
|
|
119
|
+
default: '',
|
|
120
|
+
description: 'Applicant phone in international format, e.g. +9779800000000',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
displayName: 'Expires in (Hours)',
|
|
124
|
+
name: 'expiresInHours',
|
|
125
|
+
type: 'number',
|
|
126
|
+
typeOptions: { minValue: 1, maxValue: 720 },
|
|
127
|
+
default: 48,
|
|
128
|
+
description: 'How long the verification link stays valid (1-720 hours)',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
displayName: 'Locale',
|
|
132
|
+
name: 'locale',
|
|
133
|
+
type: 'string',
|
|
134
|
+
default: '',
|
|
135
|
+
description: 'UI language for the hosted flow (BCP-47-ish, e.g. "en", "ne"). Empty = workflow default.',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
displayName: 'Metadata',
|
|
139
|
+
name: 'metadata',
|
|
140
|
+
type: 'json',
|
|
141
|
+
default: '{}',
|
|
142
|
+
description: 'Free-form JSON object stored on the session',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
displayName: 'Vendor Data',
|
|
146
|
+
name: 'vendorData',
|
|
147
|
+
type: 'string',
|
|
148
|
+
default: '',
|
|
149
|
+
description: 'Your own reference for this applicant (user ID, application number, …) — echoed back on every event',
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
// Session → Get / Get Decision / Add Tag
|
|
154
|
+
{
|
|
155
|
+
displayName: 'Session ID',
|
|
156
|
+
name: 'sessionId',
|
|
157
|
+
type: 'string',
|
|
158
|
+
required: true,
|
|
159
|
+
default: '',
|
|
160
|
+
displayOptions: {
|
|
161
|
+
show: { resource: ['session'], operation: ['get', 'getDecision', 'addTag'] },
|
|
162
|
+
},
|
|
163
|
+
description: 'The verification session UUID',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
displayName: 'Tag',
|
|
167
|
+
name: 'tag',
|
|
168
|
+
type: 'string',
|
|
169
|
+
required: true,
|
|
170
|
+
default: '',
|
|
171
|
+
displayOptions: { show: { resource: ['session'], operation: ['addTag'] } },
|
|
172
|
+
description: 'The tag to apply (normalized to lowercase_with_underscores), e.g. "vip"',
|
|
173
|
+
},
|
|
174
|
+
// Session → Get Many
|
|
175
|
+
{
|
|
176
|
+
displayName: 'Filters',
|
|
177
|
+
name: 'filters',
|
|
178
|
+
type: 'collection',
|
|
179
|
+
placeholder: 'Add Filter',
|
|
180
|
+
default: {},
|
|
181
|
+
displayOptions: { show: { resource: ['session'], operation: ['getMany'] } },
|
|
182
|
+
options: [
|
|
183
|
+
{
|
|
184
|
+
displayName: 'Status',
|
|
185
|
+
name: 'status',
|
|
186
|
+
type: 'options',
|
|
187
|
+
options: [
|
|
188
|
+
{ name: 'Abandoned', value: 'abandoned' },
|
|
189
|
+
{ name: 'Approved', value: 'approved' },
|
|
190
|
+
{ name: 'Awaiting User', value: 'awaiting_user' },
|
|
191
|
+
{ name: 'Declined', value: 'declined' },
|
|
192
|
+
{ name: 'Expired', value: 'expired' },
|
|
193
|
+
{ name: 'In Progress', value: 'in_progress' },
|
|
194
|
+
{ name: 'In Review', value: 'in_review' },
|
|
195
|
+
{ name: 'KYC Expired', value: 'kyc_expired' },
|
|
196
|
+
{ name: 'Not Started', value: 'not_started' },
|
|
197
|
+
{ name: 'Resubmitted', value: 'resubmitted' },
|
|
198
|
+
],
|
|
199
|
+
default: 'approved',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
displayName: 'Page',
|
|
203
|
+
name: 'page',
|
|
204
|
+
type: 'number',
|
|
205
|
+
typeOptions: { minValue: 1 },
|
|
206
|
+
default: 1,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
displayName: 'Page Size',
|
|
210
|
+
name: 'pageSize',
|
|
211
|
+
type: 'number',
|
|
212
|
+
typeOptions: { minValue: 1, maxValue: 200 },
|
|
213
|
+
default: 50,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
// ── Tools operations ──────────────────────────────────────────
|
|
218
|
+
{
|
|
219
|
+
displayName: 'Operation',
|
|
220
|
+
name: 'operation',
|
|
221
|
+
type: 'options',
|
|
222
|
+
noDataExpression: true,
|
|
223
|
+
displayOptions: { show: { resource: ['tools'] } },
|
|
224
|
+
options: [
|
|
225
|
+
{
|
|
226
|
+
name: 'AML Check',
|
|
227
|
+
value: 'amlCheck',
|
|
228
|
+
description: 'Screen a person against sanctions / PEP / watchlists (billed per use)',
|
|
229
|
+
action: 'Run an AML check',
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
default: 'amlCheck',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
displayName: 'Full Name',
|
|
236
|
+
name: 'fullName',
|
|
237
|
+
type: 'string',
|
|
238
|
+
default: '',
|
|
239
|
+
displayOptions: { show: { resource: ['tools'], operation: ['amlCheck'] } },
|
|
240
|
+
description: "The person's full name (or set First + Last Name below)",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
displayName: 'Additional Fields',
|
|
244
|
+
name: 'amlAdditionalFields',
|
|
245
|
+
type: 'collection',
|
|
246
|
+
placeholder: 'Add Field',
|
|
247
|
+
default: {},
|
|
248
|
+
displayOptions: { show: { resource: ['tools'], operation: ['amlCheck'] } },
|
|
249
|
+
options: [
|
|
250
|
+
{
|
|
251
|
+
displayName: 'Country',
|
|
252
|
+
name: 'country',
|
|
253
|
+
type: 'string',
|
|
254
|
+
default: '',
|
|
255
|
+
description: 'Country of residence code, if different from nationality',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
displayName: 'Date of Birth',
|
|
259
|
+
name: 'dateOfBirth',
|
|
260
|
+
type: 'string',
|
|
261
|
+
default: '',
|
|
262
|
+
placeholder: '1990-01-31',
|
|
263
|
+
description: 'ISO date — improves match precision',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
displayName: 'Document Number',
|
|
267
|
+
name: 'documentNumber',
|
|
268
|
+
type: 'string',
|
|
269
|
+
default: '',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
displayName: 'First Name',
|
|
273
|
+
name: 'firstName',
|
|
274
|
+
type: 'string',
|
|
275
|
+
default: '',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
displayName: 'Last Name',
|
|
279
|
+
name: 'lastName',
|
|
280
|
+
type: 'string',
|
|
281
|
+
default: '',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
displayName: 'Match Threshold',
|
|
285
|
+
name: 'matchThreshold',
|
|
286
|
+
type: 'number',
|
|
287
|
+
typeOptions: { minValue: 0, maxValue: 1, numberPrecision: 2 },
|
|
288
|
+
default: 0.7,
|
|
289
|
+
description: 'Override the confirmed-match score threshold (0-1)',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
displayName: 'Nationality',
|
|
293
|
+
name: 'nationality',
|
|
294
|
+
type: 'string',
|
|
295
|
+
default: '',
|
|
296
|
+
description: 'Country code (e.g. NP, US)',
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
this.methods = {
|
|
303
|
+
loadOptions: {
|
|
304
|
+
getWorkflows: GenericFunctions_1.loadWorkflowOptions,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
async execute() {
|
|
309
|
+
const items = this.getInputData();
|
|
310
|
+
const returnData = [];
|
|
311
|
+
const resource = this.getNodeParameter('resource', 0);
|
|
312
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
313
|
+
for (let i = 0; i < items.length; i++) {
|
|
314
|
+
try {
|
|
315
|
+
let responseData;
|
|
316
|
+
if (resource === 'session' && operation === 'create') {
|
|
317
|
+
const body = {};
|
|
318
|
+
const workflowId = this.getNodeParameter('workflowId', i, '');
|
|
319
|
+
if (workflowId)
|
|
320
|
+
body.workflow_id = workflowId;
|
|
321
|
+
const extra = this.getNodeParameter('additionalFields', i, {});
|
|
322
|
+
if (extra.vendorData)
|
|
323
|
+
body.vendor_data = extra.vendorData;
|
|
324
|
+
if (extra.contactEmail)
|
|
325
|
+
body.contact_email = extra.contactEmail;
|
|
326
|
+
if (extra.contactPhone)
|
|
327
|
+
body.contact_phone = extra.contactPhone;
|
|
328
|
+
if (extra.expiresInHours)
|
|
329
|
+
body.expires_in_hours = extra.expiresInHours;
|
|
330
|
+
if (extra.callbackUrl)
|
|
331
|
+
body.callback_url = extra.callbackUrl;
|
|
332
|
+
if (extra.locale)
|
|
333
|
+
body.locale = extra.locale;
|
|
334
|
+
if (extra.metadata) {
|
|
335
|
+
const metadata = typeof extra.metadata === 'string'
|
|
336
|
+
? JSON.parse(extra.metadata)
|
|
337
|
+
: extra.metadata;
|
|
338
|
+
if (Object.keys(metadata).length)
|
|
339
|
+
body.metadata = metadata;
|
|
340
|
+
}
|
|
341
|
+
responseData = await GenericFunctions_1.apiRequest.call(this, 'POST', '/v3/session/', body);
|
|
342
|
+
}
|
|
343
|
+
else if (resource === 'session' && operation === 'get') {
|
|
344
|
+
const sessionId = this.getNodeParameter('sessionId', i);
|
|
345
|
+
responseData = await GenericFunctions_1.apiRequest.call(this, 'GET', `/v3/session/${sessionId}/`);
|
|
346
|
+
}
|
|
347
|
+
else if (resource === 'session' && operation === 'getDecision') {
|
|
348
|
+
const sessionId = this.getNodeParameter('sessionId', i);
|
|
349
|
+
responseData = await GenericFunctions_1.apiRequest.call(this, 'GET', `/v3/session/${sessionId}/decision/`);
|
|
350
|
+
}
|
|
351
|
+
else if (resource === 'session' && operation === 'getMany') {
|
|
352
|
+
const filters = this.getNodeParameter('filters', i, {});
|
|
353
|
+
const qs = {};
|
|
354
|
+
if (filters.status)
|
|
355
|
+
qs.status = filters.status;
|
|
356
|
+
if (filters.page)
|
|
357
|
+
qs.page = filters.page;
|
|
358
|
+
if (filters.pageSize)
|
|
359
|
+
qs.page_size = filters.pageSize;
|
|
360
|
+
responseData = (await GenericFunctions_1.apiRequest.call(this, 'GET', '/v3/session/', undefined, qs));
|
|
361
|
+
}
|
|
362
|
+
else if (resource === 'session' && operation === 'addTag') {
|
|
363
|
+
const sessionId = this.getNodeParameter('sessionId', i);
|
|
364
|
+
const tag = this.getNodeParameter('tag', i);
|
|
365
|
+
const response = await GenericFunctions_1.apiRequest.call(this, 'POST', `/v3/session/${sessionId}/tags/`, { tag });
|
|
366
|
+
responseData = { ...response, session_id: sessionId };
|
|
367
|
+
}
|
|
368
|
+
else if (resource === 'tools' && operation === 'amlCheck') {
|
|
369
|
+
const body = {};
|
|
370
|
+
const fullName = this.getNodeParameter('fullName', i, '');
|
|
371
|
+
if (fullName)
|
|
372
|
+
body.full_name = fullName;
|
|
373
|
+
const extra = this.getNodeParameter('amlAdditionalFields', i, {});
|
|
374
|
+
if (extra.firstName)
|
|
375
|
+
body.first_name = extra.firstName;
|
|
376
|
+
if (extra.lastName)
|
|
377
|
+
body.last_name = extra.lastName;
|
|
378
|
+
if (extra.dateOfBirth)
|
|
379
|
+
body.date_of_birth = extra.dateOfBirth;
|
|
380
|
+
if (extra.nationality)
|
|
381
|
+
body.nationality = extra.nationality;
|
|
382
|
+
if (extra.country)
|
|
383
|
+
body.country = extra.country;
|
|
384
|
+
if (extra.documentNumber)
|
|
385
|
+
body.document_number = extra.documentNumber;
|
|
386
|
+
if (extra.matchThreshold)
|
|
387
|
+
body.match_threshold = extra.matchThreshold;
|
|
388
|
+
if (!body.full_name && !(body.first_name || body.last_name)) {
|
|
389
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Provide a Full Name, or a First and/or Last Name', { itemIndex: i });
|
|
390
|
+
}
|
|
391
|
+
responseData = await GenericFunctions_1.apiRequest.call(this, 'POST', '/v3/tools/aml-check/', body);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported operation "${resource}.${operation}"`, { itemIndex: i });
|
|
395
|
+
}
|
|
396
|
+
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
|
|
397
|
+
returnData.push(...executionData);
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
if (this.continueOnFail()) {
|
|
401
|
+
returnData.push({
|
|
402
|
+
json: { error: error.message },
|
|
403
|
+
pairedItem: { item: i },
|
|
404
|
+
});
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return [returnData];
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
exports.ThirdFactorObsidian = ThirdFactorObsidian;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IHookFunctions, INodeType, INodeTypeDescription, IWebhookFunctions, IWebhookResponseData } from 'n8n-workflow';
|
|
2
|
+
import { loadEventOptions, loadFeatureOptions, loadWorkflowOptions } from './GenericFunctions';
|
|
3
|
+
export declare class ThirdFactorObsidianTrigger implements INodeType {
|
|
4
|
+
description: INodeTypeDescription;
|
|
5
|
+
methods: {
|
|
6
|
+
loadOptions: {
|
|
7
|
+
getEvents: typeof loadEventOptions;
|
|
8
|
+
getWorkflows: typeof loadWorkflowOptions;
|
|
9
|
+
getFeatures: typeof loadFeatureOptions;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
webhookMethods: {
|
|
13
|
+
default: {
|
|
14
|
+
checkExists(this: IHookFunctions): Promise<boolean>;
|
|
15
|
+
create(this: IHookFunctions): Promise<boolean>;
|
|
16
|
+
delete(this: IHookFunctions): Promise<boolean>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ThirdFactorObsidianTrigger = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const GenericFunctions_1 = require("./GenericFunctions");
|
|
6
|
+
/**
|
|
7
|
+
* Verify the platform's webhook signature:
|
|
8
|
+
* X-Webhook-Signature: t=<unix>,v1=<hex hmac-sha256 of "<t>.<raw_body>">
|
|
9
|
+
* signed with the subscription secret returned on subscribe.
|
|
10
|
+
*/
|
|
11
|
+
function verifySignature(header, rawBody, secret) {
|
|
12
|
+
const parts = {};
|
|
13
|
+
for (const piece of header.split(',')) {
|
|
14
|
+
const idx = piece.indexOf('=');
|
|
15
|
+
if (idx > 0) {
|
|
16
|
+
parts[piece.slice(0, idx).trim()] = piece.slice(idx + 1).trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const timestamp = parts.t;
|
|
20
|
+
const signature = parts.v1;
|
|
21
|
+
if (!timestamp || !signature)
|
|
22
|
+
return false;
|
|
23
|
+
// Reject stale deliveries (replay guard) — 5 minutes of clock drift allowed.
|
|
24
|
+
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
|
|
25
|
+
if (!Number.isFinite(age) || age > 300)
|
|
26
|
+
return false;
|
|
27
|
+
const expected = (0, crypto_1.createHmac)('sha256', secret)
|
|
28
|
+
.update(`${timestamp}.${rawBody}`)
|
|
29
|
+
.digest('hex');
|
|
30
|
+
if (expected.length !== signature.length)
|
|
31
|
+
return false;
|
|
32
|
+
try {
|
|
33
|
+
return (0, crypto_1.timingSafeEqual)(new Uint8Array(Buffer.from(expected, 'hex')), new Uint8Array(Buffer.from(signature, 'hex')));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
class ThirdFactorObsidianTrigger {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.description = {
|
|
42
|
+
displayName: 'ThirdFactor Obsidian Trigger',
|
|
43
|
+
name: 'thirdFactorObsidianTrigger',
|
|
44
|
+
icon: 'file:thirdfactor.png',
|
|
45
|
+
group: ['trigger'],
|
|
46
|
+
version: 1,
|
|
47
|
+
subtitle: '={{$parameter["events"].join(", ")}}',
|
|
48
|
+
description: 'Starts the workflow when ThirdFactor Obsidian events occur (verifications, review cases, AML hits, credits, …)',
|
|
49
|
+
defaults: {
|
|
50
|
+
name: 'ThirdFactor Obsidian Trigger',
|
|
51
|
+
},
|
|
52
|
+
inputs: [],
|
|
53
|
+
outputs: ["main" /* NodeConnectionType.Main */],
|
|
54
|
+
credentials: [
|
|
55
|
+
{
|
|
56
|
+
name: 'thirdFactorObsidianApi',
|
|
57
|
+
required: true,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
webhooks: [
|
|
61
|
+
{
|
|
62
|
+
name: 'default',
|
|
63
|
+
httpMethod: 'POST',
|
|
64
|
+
responseMode: 'onReceived',
|
|
65
|
+
path: 'webhook',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
properties: [
|
|
69
|
+
{
|
|
70
|
+
displayName: 'Events',
|
|
71
|
+
name: 'events',
|
|
72
|
+
type: 'multiOptions',
|
|
73
|
+
typeOptions: {
|
|
74
|
+
loadOptionsMethod: 'getEvents',
|
|
75
|
+
},
|
|
76
|
+
default: [],
|
|
77
|
+
required: true,
|
|
78
|
+
description: 'The platform events to subscribe to. The list is fetched live from your deployment. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
displayName: 'Workflows',
|
|
82
|
+
name: 'workflowIds',
|
|
83
|
+
type: 'multiOptions',
|
|
84
|
+
typeOptions: {
|
|
85
|
+
loadOptionsMethod: 'getWorkflows',
|
|
86
|
+
},
|
|
87
|
+
default: [],
|
|
88
|
+
description: 'Only receive events for sessions of these verification workflows. Leave empty for all workflows. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
displayName: 'Modules',
|
|
92
|
+
name: 'features',
|
|
93
|
+
type: 'multiOptions',
|
|
94
|
+
typeOptions: {
|
|
95
|
+
loadOptionsMethod: 'getFeatures',
|
|
96
|
+
},
|
|
97
|
+
default: [],
|
|
98
|
+
description: 'Only receive module-scoped events for these verification modules (applies to events like module.completed). Leave empty for all modules. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
displayName: 'Verify Signature',
|
|
102
|
+
name: 'verifySignature',
|
|
103
|
+
type: 'boolean',
|
|
104
|
+
default: true,
|
|
105
|
+
description: 'Whether to verify the X-Webhook-Signature header (HMAC-SHA256 with the subscription secret) and reject deliveries that do not match',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
this.methods = {
|
|
110
|
+
loadOptions: {
|
|
111
|
+
getEvents: GenericFunctions_1.loadEventOptions,
|
|
112
|
+
getWorkflows: GenericFunctions_1.loadWorkflowOptions,
|
|
113
|
+
getFeatures: GenericFunctions_1.loadFeatureOptions,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
this.webhookMethods = {
|
|
117
|
+
default: {
|
|
118
|
+
async checkExists() {
|
|
119
|
+
const staticData = this.getWorkflowStaticData('node');
|
|
120
|
+
if (!staticData.subscriptionId) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const response = await GenericFunctions_1.apiRequest.call(this, 'GET', '/v3/automation/subscriptions/');
|
|
124
|
+
const subscriptions = (response === null || response === void 0 ? void 0 : response.subscriptions) || [];
|
|
125
|
+
const exists = subscriptions.some((subscription) => subscription.id === staticData.subscriptionId);
|
|
126
|
+
if (!exists) {
|
|
127
|
+
delete staticData.subscriptionId;
|
|
128
|
+
delete staticData.subscriptionSecret;
|
|
129
|
+
}
|
|
130
|
+
return exists;
|
|
131
|
+
},
|
|
132
|
+
async create() {
|
|
133
|
+
var _a;
|
|
134
|
+
const webhookUrl = this.getNodeWebhookUrl('default');
|
|
135
|
+
const events = this.getNodeParameter('events', []);
|
|
136
|
+
const workflowIds = this.getNodeParameter('workflowIds', []);
|
|
137
|
+
const features = this.getNodeParameter('features', []);
|
|
138
|
+
const body = {
|
|
139
|
+
url: webhookUrl,
|
|
140
|
+
events,
|
|
141
|
+
source: 'n8n',
|
|
142
|
+
description: `n8n workflow ${(_a = this.getWorkflow().id) !== null && _a !== void 0 ? _a : ''}`.trim(),
|
|
143
|
+
};
|
|
144
|
+
if (workflowIds.length)
|
|
145
|
+
body.workflow_ids = workflowIds;
|
|
146
|
+
if (features.length)
|
|
147
|
+
body.features = features;
|
|
148
|
+
const response = await GenericFunctions_1.apiRequest.call(this, 'POST', '/v3/automation/subscriptions/', body);
|
|
149
|
+
if (!(response === null || response === void 0 ? void 0 : response.id)) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
const staticData = this.getWorkflowStaticData('node');
|
|
153
|
+
staticData.subscriptionId = response.id;
|
|
154
|
+
// The signing secret is returned exactly once, on create.
|
|
155
|
+
staticData.subscriptionSecret = response.secret;
|
|
156
|
+
return true;
|
|
157
|
+
},
|
|
158
|
+
async delete() {
|
|
159
|
+
const staticData = this.getWorkflowStaticData('node');
|
|
160
|
+
if (staticData.subscriptionId) {
|
|
161
|
+
// Idempotent server-side: deleting an already-gone hook succeeds.
|
|
162
|
+
await GenericFunctions_1.apiRequest.call(this, 'DELETE', `/v3/automation/subscriptions/${staticData.subscriptionId}/`);
|
|
163
|
+
delete staticData.subscriptionId;
|
|
164
|
+
delete staticData.subscriptionSecret;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async webhook() {
|
|
172
|
+
var _a, _b, _c;
|
|
173
|
+
const req = this.getRequestObject();
|
|
174
|
+
const body = this.getBodyData();
|
|
175
|
+
const shouldVerify = this.getNodeParameter('verifySignature', true);
|
|
176
|
+
if (shouldVerify) {
|
|
177
|
+
const staticData = this.getWorkflowStaticData('node');
|
|
178
|
+
const secret = staticData.subscriptionSecret;
|
|
179
|
+
const header = String((_a = this.getHeaderData()['x-webhook-signature']) !== null && _a !== void 0 ? _a : '');
|
|
180
|
+
const rawBody = (_c = (_b = req.rawBody) === null || _b === void 0 ? void 0 : _b.toString('utf8')) !== null && _c !== void 0 ? _c : JSON.stringify(body);
|
|
181
|
+
if (!secret || !header || !verifySignature(header, rawBody, secret)) {
|
|
182
|
+
const response = this.getResponseObject();
|
|
183
|
+
response.status(401).json({ ok: false, error: 'invalid_signature' });
|
|
184
|
+
return { noWebhookResponse: true };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
workflowData: [this.helpers.returnJsonArray([body])],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
exports.ThirdFactorObsidianTrigger = ThirdFactorObsidianTrigger;
|
|
Binary file
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-thirdfactor-obsidian",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "n8n community nodes for ThirdFactor Obsidian — identity verification triggers and actions",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"thirdfactor",
|
|
8
|
+
"kyc",
|
|
9
|
+
"identity-verification",
|
|
10
|
+
"aml"
|
|
11
|
+
],
|
|
12
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "ThirdFactor"
|
|
15
|
+
},
|
|
16
|
+
"main": "index.js",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc && node scripts/copy-assets.js",
|
|
26
|
+
"dev": "tsc --watch",
|
|
27
|
+
"lint": "tsc --noEmit"
|
|
28
|
+
},
|
|
29
|
+
"n8n": {
|
|
30
|
+
"n8nNodesApiVersion": 1,
|
|
31
|
+
"credentials": [
|
|
32
|
+
"dist/credentials/ThirdFactorObsidianApi.credentials.js"
|
|
33
|
+
],
|
|
34
|
+
"nodes": [
|
|
35
|
+
"dist/nodes/ThirdFactorObsidian/ThirdFactorObsidian.node.js",
|
|
36
|
+
"dist/nodes/ThirdFactorObsidian/ThirdFactorObsidianTrigger.node.js"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"n8n-workflow": "*"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"n8n-workflow": "~1.70.0",
|
|
44
|
+
"typescript": "^5.5.0"
|
|
45
|
+
}
|
|
46
|
+
}
|