n8n-nodes-duo 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/LICENSE.md +21 -0
- package/README.md +81 -0
- package/dist/credentials/DuoSecurityApi.credentials.js +140 -0
- package/dist/credentials/DuoSecurityApi.credentials.js.map +6 -0
- package/dist/nodes/DuoSecurity/DuoSecurity.node.js +610 -0
- package/dist/nodes/DuoSecurity/DuoSecurity.node.js.map +6 -0
- package/dist/nodes/DuoSecurity/V1/duo-dark.svg +37 -0
- package/dist/nodes/DuoSecurity/V1/duo.svg +37 -0
- package/dist/nodes/DuoSecurity/duo-dark.svg +37 -0
- package/dist/nodes/DuoSecurity/duo.svg +37 -0
- package/examples/workflows/check-api-health.json +81 -0
- package/examples/workflows/duo-human-in-the-loop-approval.json +154 -0
- package/examples/workflows/duo-human-in-the-loop-approval.md +49 -0
- package/package.json +73 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1"
|
|
3
|
+
viewBox="0 0 211.1 211.1"><!--Generator: Adobe Illustrator 29.3.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 151)-->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>.st0 {
|
|
6
|
+
fill: #4d4d4f;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.st1 {
|
|
10
|
+
opacity: .8;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.st1, .st2 {
|
|
14
|
+
fill: #74bf4b;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</defs>
|
|
18
|
+
<g transform="translate(0 54.35)">
|
|
19
|
+
<path class="st1" d="M.3,102.4h34c18.2,0,33.1-14.3,34-32.3H.3v32.3h0Z"/>
|
|
20
|
+
<path class="st2" d="M34.3,34.3H.3v32.3h68c-.9-18-15.8-32.3-34-32.3"/>
|
|
21
|
+
<path class="st2" d="M177.2,34.3c-18.2,0-33.1,14.3-34,32.3h67.9c-.9-18-15.8-32.3-34-32.3"/>
|
|
22
|
+
<path class="st1" d="M177.2,102.4c18.2,0,33.1-14.3,34-32.3h-67.9c.9,18,15.8,32.3,34,32.3"/>
|
|
23
|
+
<path class="st2" d="M71.7,34.3v34c0,18.2,14.3,33.1,32.3,34V34.3h-32.3Z"/>
|
|
24
|
+
<polygon class="st1" points="107.4 34.3 107.4 102.4 139.8 102.4 139.8 68.3 139.8 34.3 107.4 34.3"/>
|
|
25
|
+
</g>
|
|
26
|
+
<g>
|
|
27
|
+
<rect class="st0" x="18.7" y=".3" width="4.3" height="17"/>
|
|
28
|
+
<path class="st0"
|
|
29
|
+
d="M44.4,8.8c0,5.5,4.2,8.8,9,8.8s3.3-.5,3.9-.6v-4.6c-.2,0-1.6.9-3.6.9-2.8,0-4.7-2-4.7-4.5s1.9-4.5,4.7-4.5,3.4.8,3.6.9V.6c-.4-.1-1.9-.6-3.9-.6-5.2,0-9,3.7-9,8.8Z"/>
|
|
30
|
+
<path class="st0"
|
|
31
|
+
d="M0,8.8c0,5.5,4.2,8.8,9,8.8s3.3-.5,3.9-.6v-4.6c-.2,0-1.6.9-3.6.9-2.8,0-4.7-2-4.7-4.5s1.9-4.5,4.7-4.5,3.4.8,3.6.9V.6c-.4-.1-1.9-.6-3.9-.6C3.8,0,0,3.7,0,8.8Z"/>
|
|
32
|
+
<path class="st0"
|
|
33
|
+
d="M70.6,0c-5.2,0-8.9,3.9-8.9,8.8s3.7,8.8,8.9,8.8,8.9-3.9,8.9-8.8S75.8,0,70.6,0ZM70.6,13.3c-2.5,0-4.4-2-4.4-4.5s1.9-4.5,4.4-4.5,4.4,2,4.4,4.5-1.9,4.5-4.4,4.5Z"/>
|
|
34
|
+
<path class="st0"
|
|
35
|
+
d="M36.2,7l-1.2-.4c-.7-.2-1.9-.6-1.9-1.6s.9-1.4,2.6-1.4,3.3.5,3.3.5V.5c-.1,0-2.1-.5-4.1-.5-3.9,0-6.3,2.1-6.3,5.3s2,4.2,4.3,5c.3,0,.7.2.9.3,1.1.3,1.9.8,1.9,1.7s-1,1.6-3.1,1.6-3.6-.5-4-.6v3.9c.2,0,2.3.5,4.6.5,3.3,0,7-1.4,7-5.7s-1.3-4-4-4.9Z"/>
|
|
36
|
+
</g>
|
|
37
|
+
</svg>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1"
|
|
3
|
+
viewBox="0 0 211.1 211.1"><!--Generator: Adobe Illustrator 29.3.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 151)-->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>.st0 {
|
|
6
|
+
fill: #4d4d4f;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.st1 {
|
|
10
|
+
opacity: .8;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.st1, .st2 {
|
|
14
|
+
fill: #74bf4b;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</defs>
|
|
18
|
+
<g transform="translate(0 54.35)">
|
|
19
|
+
<path class="st1" d="M.3,102.4h34c18.2,0,33.1-14.3,34-32.3H.3v32.3h0Z"/>
|
|
20
|
+
<path class="st2" d="M34.3,34.3H.3v32.3h68c-.9-18-15.8-32.3-34-32.3"/>
|
|
21
|
+
<path class="st2" d="M177.2,34.3c-18.2,0-33.1,14.3-34,32.3h67.9c-.9-18-15.8-32.3-34-32.3"/>
|
|
22
|
+
<path class="st1" d="M177.2,102.4c18.2,0,33.1-14.3,34-32.3h-67.9c.9,18,15.8,32.3,34,32.3"/>
|
|
23
|
+
<path class="st2" d="M71.7,34.3v34c0,18.2,14.3,33.1,32.3,34V34.3h-32.3Z"/>
|
|
24
|
+
<polygon class="st1" points="107.4 34.3 107.4 102.4 139.8 102.4 139.8 68.3 139.8 34.3 107.4 34.3"/>
|
|
25
|
+
</g>
|
|
26
|
+
<g>
|
|
27
|
+
<rect class="st0" x="18.7" y=".3" width="4.3" height="17"/>
|
|
28
|
+
<path class="st0"
|
|
29
|
+
d="M44.4,8.8c0,5.5,4.2,8.8,9,8.8s3.3-.5,3.9-.6v-4.6c-.2,0-1.6.9-3.6.9-2.8,0-4.7-2-4.7-4.5s1.9-4.5,4.7-4.5,3.4.8,3.6.9V.6c-.4-.1-1.9-.6-3.9-.6-5.2,0-9,3.7-9,8.8Z"/>
|
|
30
|
+
<path class="st0"
|
|
31
|
+
d="M0,8.8c0,5.5,4.2,8.8,9,8.8s3.3-.5,3.9-.6v-4.6c-.2,0-1.6.9-3.6.9-2.8,0-4.7-2-4.7-4.5s1.9-4.5,4.7-4.5,3.4.8,3.6.9V.6c-.4-.1-1.9-.6-3.9-.6C3.8,0,0,3.7,0,8.8Z"/>
|
|
32
|
+
<path class="st0"
|
|
33
|
+
d="M70.6,0c-5.2,0-8.9,3.9-8.9,8.8s3.7,8.8,8.9,8.8,8.9-3.9,8.9-8.8S75.8,0,70.6,0ZM70.6,13.3c-2.5,0-4.4-2-4.4-4.5s1.9-4.5,4.4-4.5,4.4,2,4.4,4.5-1.9,4.5-4.4,4.5Z"/>
|
|
34
|
+
<path class="st0"
|
|
35
|
+
d="M36.2,7l-1.2-.4c-.7-.2-1.9-.6-1.9-1.6s.9-1.4,2.6-1.4,3.3.5,3.3.5V.5c-.1,0-2.1-.5-4.1-.5-3.9,0-6.3,2.1-6.3,5.3s2,4.2,4.3,5c.3,0,.7.2.9.3,1.1.3,1.9.8,1.9,1.7s-1,1.6-3.1,1.6-3.6-.5-4-.6v3.9c.2,0,2.3.5,4.6.5,3.3,0,7-1.4,7-5.7s-1.3-4-4-4.9Z"/>
|
|
36
|
+
</g>
|
|
37
|
+
</svg>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1"
|
|
3
|
+
viewBox="0 0 211.1 211.1"><!--Generator: Adobe Illustrator 29.3.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 151)-->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>.st0 {
|
|
6
|
+
fill: #4d4d4f;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.st1 {
|
|
10
|
+
opacity: .8;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.st1, .st2 {
|
|
14
|
+
fill: #74bf4b;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</defs>
|
|
18
|
+
<g transform="translate(0 54.35)">
|
|
19
|
+
<path class="st1" d="M.3,102.4h34c18.2,0,33.1-14.3,34-32.3H.3v32.3h0Z"/>
|
|
20
|
+
<path class="st2" d="M34.3,34.3H.3v32.3h68c-.9-18-15.8-32.3-34-32.3"/>
|
|
21
|
+
<path class="st2" d="M177.2,34.3c-18.2,0-33.1,14.3-34,32.3h67.9c-.9-18-15.8-32.3-34-32.3"/>
|
|
22
|
+
<path class="st1" d="M177.2,102.4c18.2,0,33.1-14.3,34-32.3h-67.9c.9,18,15.8,32.3,34,32.3"/>
|
|
23
|
+
<path class="st2" d="M71.7,34.3v34c0,18.2,14.3,33.1,32.3,34V34.3h-32.3Z"/>
|
|
24
|
+
<polygon class="st1" points="107.4 34.3 107.4 102.4 139.8 102.4 139.8 68.3 139.8 34.3 107.4 34.3"/>
|
|
25
|
+
</g>
|
|
26
|
+
<g>
|
|
27
|
+
<rect class="st0" x="18.7" y=".3" width="4.3" height="17"/>
|
|
28
|
+
<path class="st0"
|
|
29
|
+
d="M44.4,8.8c0,5.5,4.2,8.8,9,8.8s3.3-.5,3.9-.6v-4.6c-.2,0-1.6.9-3.6.9-2.8,0-4.7-2-4.7-4.5s1.9-4.5,4.7-4.5,3.4.8,3.6.9V.6c-.4-.1-1.9-.6-3.9-.6-5.2,0-9,3.7-9,8.8Z"/>
|
|
30
|
+
<path class="st0"
|
|
31
|
+
d="M0,8.8c0,5.5,4.2,8.8,9,8.8s3.3-.5,3.9-.6v-4.6c-.2,0-1.6.9-3.6.9-2.8,0-4.7-2-4.7-4.5s1.9-4.5,4.7-4.5,3.4.8,3.6.9V.6c-.4-.1-1.9-.6-3.9-.6C3.8,0,0,3.7,0,8.8Z"/>
|
|
32
|
+
<path class="st0"
|
|
33
|
+
d="M70.6,0c-5.2,0-8.9,3.9-8.9,8.8s3.7,8.8,8.9,8.8,8.9-3.9,8.9-8.8S75.8,0,70.6,0ZM70.6,13.3c-2.5,0-4.4-2-4.4-4.5s1.9-4.5,4.4-4.5,4.4,2,4.4,4.5-1.9,4.5-4.4,4.5Z"/>
|
|
34
|
+
<path class="st0"
|
|
35
|
+
d="M36.2,7l-1.2-.4c-.7-.2-1.9-.6-1.9-1.6s.9-1.4,2.6-1.4,3.3.5,3.3.5V.5c-.1,0-2.1-.5-4.1-.5-3.9,0-6.3,2.1-6.3,5.3s2,4.2,4.3,5c.3,0,.7.2.9.3,1.1.3,1.9.8,1.9,1.7s-1,1.6-3.1,1.6-3.6-.5-4-.6v3.9c.2,0,2.3.5,4.6.5,3.3,0,7-1.4,7-5.7s-1.3-4-4-4.9Z"/>
|
|
36
|
+
</g>
|
|
37
|
+
</svg>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "wetomate-example-duo-authentication-flow",
|
|
3
|
+
"name": "[Wetomate Example] Duo Authentication Flow",
|
|
4
|
+
"nodes": [
|
|
5
|
+
{
|
|
6
|
+
"parameters": {},
|
|
7
|
+
"id": "wetomate-example-duo-manual-trigger",
|
|
8
|
+
"name": "Start Duo Check",
|
|
9
|
+
"type": "n8n-nodes-base.manualTrigger",
|
|
10
|
+
"typeVersion": 1,
|
|
11
|
+
"position": [0, 0]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"parameters": { "endpoint": "ping" },
|
|
15
|
+
"id": "wetomate-example-duo-ping",
|
|
16
|
+
"name": "1. Check Duo Availability",
|
|
17
|
+
"type": "n8n-nodes-duo.duoSecurity",
|
|
18
|
+
"typeVersion": 1,
|
|
19
|
+
"position": [240, 0]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"parameters": {
|
|
23
|
+
"endpoint": "preauth",
|
|
24
|
+
"userIdentifier": { "mode": "username", "value": "alice@example.com" },
|
|
25
|
+
"preAuthOptionalFields": {
|
|
26
|
+
"clientSupportsVerifiedPush": true,
|
|
27
|
+
"ipaddr": "192.0.2.10"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"id": "wetomate-example-duo-preauth",
|
|
31
|
+
"name": "2. Check User Access",
|
|
32
|
+
"type": "n8n-nodes-duo.duoSecurity",
|
|
33
|
+
"typeVersion": 1,
|
|
34
|
+
"position": [480, 0]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"parameters": {
|
|
38
|
+
"endpoint": "auth",
|
|
39
|
+
"userIdentifier": { "mode": "username", "value": "alice@example.com" },
|
|
40
|
+
"factor": "push",
|
|
41
|
+
"device": "auto",
|
|
42
|
+
"pushinfo": {
|
|
43
|
+
"pairs": [{ "key": "source", "value": "Wetomate n8n workflow" }]
|
|
44
|
+
},
|
|
45
|
+
"authOptionalFields": { "async": true, "type": "Workflow sign-in" }
|
|
46
|
+
},
|
|
47
|
+
"id": "wetomate-example-duo-auth",
|
|
48
|
+
"name": "3. Send Duo Push",
|
|
49
|
+
"type": "n8n-nodes-duo.duoSecurity",
|
|
50
|
+
"typeVersion": 1,
|
|
51
|
+
"position": [720, 0]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"parameters": {
|
|
55
|
+
"endpoint": "auth_status",
|
|
56
|
+
"txid": "={{ $json.result.response.txid }}"
|
|
57
|
+
},
|
|
58
|
+
"id": "wetomate-example-duo-auth-status",
|
|
59
|
+
"name": "4. Read Authentication Status",
|
|
60
|
+
"type": "n8n-nodes-duo.duoSecurity",
|
|
61
|
+
"typeVersion": 1,
|
|
62
|
+
"position": [960, 0]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"connections": {
|
|
66
|
+
"Start Duo Check": {
|
|
67
|
+
"main": [[{ "node": "1. Check Duo Availability", "type": "main", "index": 0 }]]
|
|
68
|
+
},
|
|
69
|
+
"1. Check Duo Availability": {
|
|
70
|
+
"main": [[{ "node": "2. Check User Access", "type": "main", "index": 0 }]]
|
|
71
|
+
},
|
|
72
|
+
"2. Check User Access": {
|
|
73
|
+
"main": [[{ "node": "3. Send Duo Push", "type": "main", "index": 0 }]]
|
|
74
|
+
},
|
|
75
|
+
"3. Send Duo Push": {
|
|
76
|
+
"main": [[{ "node": "4. Read Authentication Status", "type": "main", "index": 0 }]]
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"active": false,
|
|
80
|
+
"settings": { "executionOrder": "v1" }
|
|
81
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "wetomate-example-duo-human-in-the-loop-approval",
|
|
3
|
+
"name": "[Wetomate Example] Duo Human-in-the-Loop Approval",
|
|
4
|
+
"nodes": [
|
|
5
|
+
{
|
|
6
|
+
"parameters": {
|
|
7
|
+
"httpMethod": "POST",
|
|
8
|
+
"path": "wetomate/duo/approval",
|
|
9
|
+
"responseMode": "onReceived",
|
|
10
|
+
"options": {}
|
|
11
|
+
},
|
|
12
|
+
"id": "wetomate-example-duo-approval-webhook",
|
|
13
|
+
"name": "Event Trigger",
|
|
14
|
+
"type": "n8n-nodes-base.webhook",
|
|
15
|
+
"typeVersion": 2,
|
|
16
|
+
"position": [0, 0]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"parameters": {
|
|
20
|
+
"endpoint": "preauth",
|
|
21
|
+
"userIdentifier": {
|
|
22
|
+
"mode": "username",
|
|
23
|
+
"value": "={{ $json.body.adminUsername }}"
|
|
24
|
+
},
|
|
25
|
+
"preAuthOptionalFields": {
|
|
26
|
+
"clientSupportsVerifiedPush": true,
|
|
27
|
+
"ipaddr": "={{ $json.body.sourceIp }}"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"id": "wetomate-example-duo-approval-preauth",
|
|
31
|
+
"name": "Check Administrator Access",
|
|
32
|
+
"type": "n8n-nodes-duo.duoSecurity",
|
|
33
|
+
"typeVersion": 1,
|
|
34
|
+
"position": [240, 0]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"parameters": {
|
|
38
|
+
"endpoint": "auth",
|
|
39
|
+
"userIdentifier": {
|
|
40
|
+
"mode": "username",
|
|
41
|
+
"value": "={{ $('Event Trigger').item.json.body.adminUsername }}"
|
|
42
|
+
},
|
|
43
|
+
"factor": "push",
|
|
44
|
+
"device": "auto",
|
|
45
|
+
"pushinfo": {
|
|
46
|
+
"pairs": [
|
|
47
|
+
{
|
|
48
|
+
"key": "event",
|
|
49
|
+
"value": "={{ $('Event Trigger').item.json.body.eventId }}"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"key": "action",
|
|
53
|
+
"value": "={{ $('Event Trigger').item.json.body.action }}"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"key": "target",
|
|
57
|
+
"value": "={{ $('Event Trigger').item.json.body.target }}"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"authOptionalFields": {
|
|
62
|
+
"async": false,
|
|
63
|
+
"display_username": "={{ $('Event Trigger').item.json.body.requestedBy }}",
|
|
64
|
+
"type": "Approval required"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"id": "wetomate-example-duo-approval-auth",
|
|
68
|
+
"name": "Request Administrator Approval",
|
|
69
|
+
"type": "n8n-nodes-duo.duoSecurity",
|
|
70
|
+
"typeVersion": 1,
|
|
71
|
+
"position": [480, 0]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"parameters": {
|
|
75
|
+
"conditions": {
|
|
76
|
+
"options": {
|
|
77
|
+
"caseSensitive": true,
|
|
78
|
+
"leftValue": "",
|
|
79
|
+
"typeValidation": "strict",
|
|
80
|
+
"version": 2
|
|
81
|
+
},
|
|
82
|
+
"conditions": [
|
|
83
|
+
{
|
|
84
|
+
"id": "wetomate-example-duo-approval-allow",
|
|
85
|
+
"leftValue": "={{ $json.result.response.result }}",
|
|
86
|
+
"rightValue": "allow",
|
|
87
|
+
"operator": { "type": "string", "operation": "equals" }
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"combinator": "and"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"id": "wetomate-example-duo-approval-if",
|
|
94
|
+
"name": "Approved?",
|
|
95
|
+
"type": "n8n-nodes-base.if",
|
|
96
|
+
"typeVersion": 2.2,
|
|
97
|
+
"position": [720, 0]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"parameters": {
|
|
101
|
+
"method": "POST",
|
|
102
|
+
"url": "={{ $('Event Trigger').item.json.body.actionUrl }}",
|
|
103
|
+
"sendBody": true,
|
|
104
|
+
"specifyBody": "json",
|
|
105
|
+
"jsonBody": "={{ JSON.stringify({ eventId: $('Event Trigger').item.json.body.eventId, action: $('Event Trigger').item.json.body.action, target: $('Event Trigger').item.json.body.target, approvedBy: $('Event Trigger').item.json.body.adminUsername }) }}",
|
|
106
|
+
"options": {}
|
|
107
|
+
},
|
|
108
|
+
"id": "wetomate-example-duo-approval-action",
|
|
109
|
+
"name": "Execute Approved Action",
|
|
110
|
+
"type": "n8n-nodes-base.httpRequest",
|
|
111
|
+
"typeVersion": 4.2,
|
|
112
|
+
"position": [960, -80]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"parameters": {
|
|
116
|
+
"assignments": {
|
|
117
|
+
"assignments": [
|
|
118
|
+
{
|
|
119
|
+
"id": "wetomate-example-duo-approval-denied-message",
|
|
120
|
+
"name": "status",
|
|
121
|
+
"type": "string",
|
|
122
|
+
"value": "Approval denied; action was not executed."
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
"options": {}
|
|
127
|
+
},
|
|
128
|
+
"id": "wetomate-example-duo-approval-denied",
|
|
129
|
+
"name": "Record Denied Approval",
|
|
130
|
+
"type": "n8n-nodes-base.set",
|
|
131
|
+
"typeVersion": 3.4,
|
|
132
|
+
"position": [960, 120]
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"connections": {
|
|
136
|
+
"Event Trigger": {
|
|
137
|
+
"main": [[{ "node": "Check Administrator Access", "type": "main", "index": 0 }]]
|
|
138
|
+
},
|
|
139
|
+
"Check Administrator Access": {
|
|
140
|
+
"main": [[{ "node": "Request Administrator Approval", "type": "main", "index": 0 }]]
|
|
141
|
+
},
|
|
142
|
+
"Request Administrator Approval": {
|
|
143
|
+
"main": [[{ "node": "Approved?", "type": "main", "index": 0 }]]
|
|
144
|
+
},
|
|
145
|
+
"Approved?": {
|
|
146
|
+
"main": [
|
|
147
|
+
[{ "node": "Execute Approved Action", "type": "main", "index": 0 }],
|
|
148
|
+
[{ "node": "Record Denied Approval", "type": "main", "index": 0 }]
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"active": false,
|
|
153
|
+
"settings": { "executionOrder": "v1" }
|
|
154
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Duo human-in-the-loop approval board
|
|
2
|
+
|
|
3
|
+
## Core idea
|
|
4
|
+
|
|
5
|
+
An event asks for a sensitive action. n8n sends the administrator a synchronous Duo Push with enough context to make a decision. The action endpoint is called only after Duo immediately reports `allow`.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Event Trigger
|
|
9
|
+
|
|
|
10
|
+
v
|
|
11
|
+
Check administrator access
|
|
12
|
+
|
|
|
13
|
+
v
|
|
14
|
+
Request synchronous Duo Push
|
|
15
|
+
(event, action, target in pushinfo)
|
|
16
|
+
|
|
|
17
|
+
v
|
|
18
|
+
Approved?
|
|
19
|
+
/ \
|
|
20
|
+
allow deny / timeout
|
|
21
|
+
/ \
|
|
22
|
+
Execute action Record rejection
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Event contract
|
|
26
|
+
|
|
27
|
+
The webhook expects a JSON body containing:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"eventId": "change-123",
|
|
32
|
+
"action": "restart-service",
|
|
33
|
+
"target": "payments-api",
|
|
34
|
+
"actionUrl": "https://automation.example.test/actions/restart",
|
|
35
|
+
"requestedBy": "release-bot",
|
|
36
|
+
"adminUsername": "admin@example.com",
|
|
37
|
+
"sourceIp": "192.0.2.20"
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Questions for the next iteration
|
|
42
|
+
|
|
43
|
+
- Should a denied or expired request notify the requester?
|
|
44
|
+
- Should the workflow persist the approval decision and Duo transaction ID for audit?
|
|
45
|
+
- Should the action URL be selected from an allowlisted action map instead of accepted from the event?
|
|
46
|
+
- Should approvals require two administrators for high impact actions?
|
|
47
|
+
- Should high impact actions use async authentication and poll `auth_status` instead?
|
|
48
|
+
|
|
49
|
+
The imported workflow is inactive and intentionally has no credential references. Create a Duo credential, replace the administrator username, and point `actionUrl` at a safe test endpoint before enabling it. The approved branch performs a real HTTP POST.
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-duo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Duo Security multi-factor authentication community node for n8n",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"n8n",
|
|
8
|
+
"duo",
|
|
9
|
+
"duo-security",
|
|
10
|
+
"mfa",
|
|
11
|
+
"authentication"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"homepage": "https://github.com/wetomate/nodes/tree/main/n8n-nodes-duo#readme",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Wetomate",
|
|
17
|
+
"email": "dev@wetomate.de"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/wetomate/nodes/issues"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/wetomate/nodes.git",
|
|
25
|
+
"directory": "n8n-nodes-duo"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "n8n-node build && node ../configs/build-community-node.mjs",
|
|
32
|
+
"dev": "tsc --watch",
|
|
33
|
+
"test": "jest",
|
|
34
|
+
"format": "prettier --write .",
|
|
35
|
+
"lint": "n8n-node lint",
|
|
36
|
+
"lintfix": "n8n-node lint --fix",
|
|
37
|
+
"prepack": "node ../scripts/validate-community-node-packages.js",
|
|
38
|
+
"prepublishOnly": "npm run build && npm run lint && npm test"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"examples"
|
|
43
|
+
],
|
|
44
|
+
"n8n": {
|
|
45
|
+
"strict": true,
|
|
46
|
+
"n8nNodesApiVersion": 1,
|
|
47
|
+
"credentials": [
|
|
48
|
+
"dist/credentials/DuoSecurityApi.credentials.js"
|
|
49
|
+
],
|
|
50
|
+
"nodes": [
|
|
51
|
+
"dist/nodes/DuoSecurity/DuoSecurity.node.js"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@n8n/node-cli": "^0.46.4",
|
|
56
|
+
"@types/jest": "^30.0.0",
|
|
57
|
+
"@types/node": "^24.0.12",
|
|
58
|
+
"@wetomate/n8n-node-toolkit": "^1.0.2",
|
|
59
|
+
"@n8n/di": "^0.10.0",
|
|
60
|
+
"eslint": "^9.32.0",
|
|
61
|
+
"jest": "^30.0.5",
|
|
62
|
+
"jest-mock-extended": "^4.0.0",
|
|
63
|
+
"n8n-core": "^2.16.0",
|
|
64
|
+
"n8n-workflow": "^2.16.0",
|
|
65
|
+
"nock": "^14.0.10",
|
|
66
|
+
"prettier": "^3.6.2",
|
|
67
|
+
"ts-jest": "^29.4.1",
|
|
68
|
+
"typescript": "^5.9.3"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"n8n-workflow": "*"
|
|
72
|
+
}
|
|
73
|
+
}
|