{
  "id": "1FWYGAsYJHcbfASX",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Automated Failed Login Detection with Jira Security Tasks and Slack Notifications",
  "tags": [],
  "nodes": [
    {
      "id": "9b061f7c-0295-46d4-98dd-1eb3ca265c91",
      "name": "Faield Login Trigger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        768,
        832
      ],
      "parameters": {
        "path": "failed-login",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 1
    },
    {
      "id": "1fb1f478-6384-4f89-b062-4f4df12899f0",
      "name": "Normalize Login Event",
      "type": "n8n-nodes-base.function",
      "position": [
        992,
        832
      ],
      "parameters": {
        "functionCode": "// Normalize input (single object or array) and return one item per attempt\nconst body = $json.body;\nlet items = [];\nif (Array.isArray(body)) items = body; else if (body && Object.keys(body).length) items = [body];\n\nreturn items.map(it => {\n  const username = (it.username || '').toString();\n  const ip = (it.ip || '').toString();\n  const timestamp = it.timestamp || new Date().toISOString();\n  const error = it.error || 'Login failed';\n  return {\n    json: {\n      username,\n      ip,\n      timestamp,\n      error,\n      logMessage: `Failed login attempt detected\\nUser: ${username || 'Unknown'}\\nIP: ${ip || 'Unknown'}\\nTime: ${timestamp}\\nError: ${error}`\n    }\n  };\n});"
      },
      "typeVersion": 1
    },
    {
      "id": "cf1b5d07-e61c-44c6-9f67-8926d3f56443",
      "name": "Check Username & IP present",
      "type": "n8n-nodes-base.if",
      "position": [
        1216,
        832
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json[\"username\"] !== '' && $json[\"ip\"] !== '' }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "665bdc22-2db1-4ac5-914c-cc8546889108",
      "name": "IF - Multiple Attempts",
      "type": "n8n-nodes-base.if",
      "position": [
        1664,
        736
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json[\"multiple\"] === true }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "bac1c2ad-91bc-4c7c-8485-f1e667ce0394",
      "name": "Build Multi-Attempt Summary",
      "type": "n8n-nodes-base.function",
      "position": [
        1888,
        640
      ],
      "parameters": {
        "functionCode": "// Build the grouped summary message for Slack & Jira\nconst attempts = $json.attempts || [];\nlet summary = `User: ${attempts[0].username}\\nIP: ${attempts[0].ip}\\nTotal Attempts: ${attempts.length}\\n\\nAttempts:\\n`;\nattempts.forEach((a, idx) => {\n  const time = a.timestamp || new Date(a.ts).toISOString();\n  summary += `${idx + 1}. ${time} \u2014 ${a.error}\\n`;\n});\n\nreturn [{ json: { username: attempts[0].username, ip: attempts[0].ip, count: attempts.length, summaryMessage: summary, attempts } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "d5b3292c-795a-49a9-906f-5e095cd98bb4",
      "name": "Create Ticket- Multiple Attempts",
      "type": "n8n-nodes-base.jira",
      "position": [
        2048,
        640
      ],
      "parameters": {
        "project": {
          "__rl": true,
          "mode": "list",
          "value": "10000",
          "cachedResultName": "n8n sample project"
        },
        "summary": "=[Security Alert] Multiple Failed Login Attempts: {{$json.username}}",
        "issueType": {
          "__rl": true,
          "mode": "list",
          "value": "10003",
          "cachedResultName": "Task"
        },
        "additionalFields": {
          "description": "={{ $json.summaryMessage }}"
        }
      },
      "credentials": {
        "jiraSoftwareCloudApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4dbfb710-0e51-4e15-8874-d2ba741eec40",
      "name": "Create Ticket - Single Attempt",
      "type": "n8n-nodes-base.jira",
      "position": [
        1888,
        832
      ],
      "parameters": {
        "project": {
          "__rl": true,
          "mode": "list",
          "value": "10000",
          "cachedResultName": "n8n sample project"
        },
        "summary": "=[Security Alert] Failed Login: {{$json.username}}",
        "issueType": {
          "__rl": true,
          "mode": "list",
          "value": "10003",
          "cachedResultName": "Task"
        },
        "additionalFields": {
          "description": "={{ $json.attempts[0].logMessage || $json.logMessage }}"
        }
      },
      "credentials": {
        "jiraSoftwareCloudApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "791dda77-40e1-45e6-8df3-793d59a74f2c",
      "name": "Slack Alert- Multiple Attempts",
      "type": "n8n-nodes-base.slack",
      "position": [
        2368,
        640
      ],
      "parameters": {
        "text": "=*Multiple Failed Login Attempts Detected*\nUserName: {{ $json.username }}\nIp: {{ $json.ip }}\nTotalAttempts: {{ $json.totalAttempts }}\nAttemptType: {{ $json.attemptType }}\nAttempt List With Time & Error: {{ $json.attemptList }}\n*Jira:* https://yourjira.atlassian.net/browse/{{$node[\"Create Ticket- Multiple Attempts\"].json.key}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09S57E2JQ2",
          "cachedResultName": "n8n"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "426c59e1-b546-4ea1-811b-934b9e3e50fb",
      "name": "Slack Alert - Single Attempt",
      "type": "n8n-nodes-base.slack",
      "position": [
        2272,
        832
      ],
      "parameters": {
        "text": "=*Failed Login Attempt Detected*\nUserName: {{ $json.username }}\nIp: {{ $json.ip }}\nTotalAttempts: {{ $json.totalAttempts }}\nAttemptType: {{ $json.attemptType }}\nAttempt List With Time & Error: {{ $json.attemptList }}\n*Jira:* https://yourjira.atlassian.net/browse/{{$json.key}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09S57E2JQ2",
          "cachedResultName": "n8n"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "e5853630-2424-457d-901c-f0d089b61390",
      "name": "Slack Alert - Missing Username/IP",
      "type": "n8n-nodes-base.slack",
      "position": [
        1440,
        928
      ],
      "parameters": {
        "text": "=*Failed Login Attempt with Missing Information*\n*User:* {{$json.username || 'Unknown'}}\n*IP:* {{$json.ip || 'Unknown'}}\n*Time:* {{$json.timestamp}}\n*Error:* {{$json.error}}                  ",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09S57E2JQ2",
          "cachedResultName": "n8n"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "d8be8ed8-654e-4d6a-9497-ae1e99bfb91f",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        752
      ],
      "parameters": {
        "color": 7,
        "width": 224,
        "height": 240,
        "content": "\nReceives the failed-login data from your application and starts the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "d15fed38-11f5-41ce-a229-ec071c715edc",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        944,
        752
      ],
      "parameters": {
        "color": 7,
        "width": 208,
        "height": 240,
        "content": "This node cleans and format the login data"
      },
      "typeVersion": 1
    },
    {
      "id": "8b7d74b0-0355-42a6-a61c-f11d1d366bf8",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1168,
        544
      ],
      "parameters": {
        "color": 7,
        "width": 1456,
        "height": 560,
        "content": "## Security Login Alert Process\nThis process for checks failed login event. If the username or IP address is missing, it immediately sends a Slack alert. If both are present, it checks whether the same user and IP tried to log in multiple times. If it\u2019s only one attempt, it creates a Jira task and sends a Slack message. If there are multiple attempts, it prepares a summary, creates a detailed Jira task, and sends a Slack alert with the summary."
      },
      "typeVersion": 1
    },
    {
      "id": "2a0808e7-5b82-4354-971c-aab886745d35",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 534,
        "height": 864,
        "content": "## How It Works\n\nThis workflow automatically monitors failed login attempts sent from your application. When a failed login event arrives, the workflow first cleans the data and checks whether the username and IP address are present. If either one is missing, it immediately sends a Slack alert to notify your security team.\n\nIf the username and IP are available, the workflow checks whether the same user and IP have attempted to log in multiple times. For a single failed attempt, it creates a simple Jira security task and sends a Slack notification with the details. If there are multiple failed attempts, the workflow groups them together, prepares a clear summary (including count, timestamps, and errors), creates a detailed Jira ticket, and notifies Slack with the complete summary.\n\nOverall, the workflow helps your team quickly detect suspicious login behavior, respond faster to security risks, and keep all incidents documented in Jira.\n\n## Setup Steps\n\n**1. Add the Webhook URL** to your application so it can send failed login events to n8n.\n\n**2. Review the \u201cNormalize Login Event\u201d node** to make sure the incoming fields match your app\u2019s output (username, IP, timestamp, error).\n\n**3. Check the IF nodes** to ensure logic matches your needs (e.g., when to treat attempts as multiple).\n\n**4. Connect your Jira account** in the two Jira nodes and update the project/issue type if needed.\n\n**5. Connect your Slack account** and choose the channel where alerts should be sent.\n\n**6. Connect your Notion** and create database for store login attempt failed data.\n\n**7. Test the workflow** by sending sample failed login events\u2014test with missing fields, single attempts, and multiple attempts.\n\n**8. Enable the workflow** once everything works as expected."
      },
      "typeVersion": 1
    },
    {
      "id": "1496a256-b92f-46fc-8941-b72fe5b84520",
      "name": "Detect Multiple Attempts",
      "type": "n8n-nodes-base.function",
      "position": [
        1440,
        736
      ],
      "parameters": {
        "functionCode": "// Detect duplicates within a 5-minute window grouped by username+ip\nconst all = $input.all().map(i => i.json);\nconst windowMs = 5 * 60 * 1000; // 5 minutes\n\n// Group by username|ip\nconst groups = {};\nall.forEach(a => {\n  const key = `${a.username}||${a.ip}`;\n  if (!groups[key]) groups[key] = [];\n  groups[key].push(a);\n});\n\nconst output = [];\nfor (const key of Object.keys(groups)) {\n  const attempts = groups[key]\n    .map(a => ({ ...a, ts: new Date(a.timestamp).getTime() }))\n    .sort((x, y) => x.ts - y.ts);\n\n  // sliding window to find max attempts within windowMs\n  let maxCount = 0;\n  let bestSlice = [attempts[0]];\n  for (let i = 0; i < attempts.length; i++) {\n    let j = i;\n    while (j < attempts.length && (attempts[j].ts - attempts[i].ts) <= windowMs) j++;\n    const count = j - i;\n    if (count > maxCount) {\n      maxCount = count;\n      bestSlice = attempts.slice(i, j);\n    }\n  }\n\n  if (maxCount >= 2) {\n    // multiple attempts within window -> grouped output\n    output.push({ json: { username: attempts[0].username, ip: attempts[0].ip, multiple: true, attempts: bestSlice } });\n  } else {\n    // single attempt (no duplicates in window) -> output each attempt as single\n    attempts.forEach(a => output.push({ json: { username: a.username, ip: a.ip, multiple: false, attempts: [a] } }));\n  }\n}\n\nreturn output;"
      },
      "typeVersion": 1
    },
    {
      "id": "c5510875-1d73-493c-ba3f-eeba321fe5e3",
      "name": "Format Login Attempt",
      "type": "n8n-nodes-base.code",
      "position": [
        2784,
        752
      ],
      "parameters": {
        "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\nconst textBlock =\n  $json.message.blocks[0].elements[0].elements[1].text || \"\";\n\nconst jiraUrl =\n  $json.message.blocks[0].elements[0].elements[4].url || \"\";\n\nfunction extractValue(key) {\n  const regex = new RegExp(key + \":\\\\s*(.*)\");\n  const match = textBlock.match(regex);\n  return match ? match[1].trim() : null;\n}\n\nreturn [\n  {\n    json: {\n      username: extractValue(\"UserName\"),\n      ip: extractValue(\"Ip\"),\n      totalAttempts: Number(extractValue(\"TotalAttempts\")),\n      attemptType: extractValue(\"AttemptType\"),\n      attemptList: extractValue(\"Attempt List With Time & Error\"),\n      jira: jiraUrl,\n      rawText: textBlock,\n    },\n  },\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "1281fbe9-53aa-4c62-bc10-35c3a29b3700",
      "name": "Login Attempts Data Store in DB",
      "type": "n8n-nodes-base.notion",
      "position": [
        3040,
        752
      ],
      "parameters": {
        "title": "n8n",
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "2b54b520-96ce-80c0-8d36-ffcd10b283fe",
          "cachedResultUrl": "https://www.notion.so/2b54b52096ce80c08d36ffcd10b283fe",
          "cachedResultName": "n8n LoginFaield"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "UserName|title",
              "title": "={{ $json.username }}"
            },
            {
              "key": "IP|rich_text",
              "textContent": "={{ $json.ip }}"
            },
            {
              "key": "TotalAttempts|number",
              "numberValue": "={{ $json.totalAttempts }}"
            },
            {
              "key": "AttemptList|rich_text",
              "textContent": "={{ $json.attemptList }}"
            },
            {
              "key": "AttemptType|rich_text",
              "textContent": "={{ $json.attemptType }}"
            }
          ]
        }
      },
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6a8ee686-0610-4e42-8cd0-baf191539c17",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2672,
        640
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 352,
        "content": "## Format data & Store to Database\nIn this Process format the login attempts data in proper manner and add to the Database"
      },
      "typeVersion": 1
    },
    {
      "id": "fbdd80bc-abb8-4882-8d4d-dc869009772a",
      "name": "Format Fields for Multiple-attempt",
      "type": "n8n-nodes-base.set",
      "position": [
        2208,
        640
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "8b7005d1-3d92-4e69-990a-989f7b2e3ecd",
              "name": "ip",
              "type": "string",
              "value": "={{ $('Build Multi-Attempt Summary').item.json.ip }}"
            },
            {
              "id": "606fb5b7-d09f-4f0c-8731-015a6d6edbe2",
              "name": "username",
              "type": "string",
              "value": "={{ $('Build Multi-Attempt Summary').item.json.username }}"
            },
            {
              "id": "e30113ae-2523-490a-ac67-a26a97961b20",
              "name": "totalAttempts",
              "type": "number",
              "value": "={{ $('Build Multi-Attempt Summary').item.json.count }}"
            },
            {
              "id": "6c58a22f-0bba-4ac2-b4a6-a31e1b270a4b",
              "name": "attemptList",
              "type": "string",
              "value": "=[\"Time\":{{ $('Build Multi-Attempt Summary').item.json.attempts[0].timestamp }},\"Error\": {{ $('Build Multi-Attempt Summary').item.json.attempts[0].error }}]"
            },
            {
              "id": "a2b7a4a5-7c96-4733-b709-877671f2ab47",
              "name": "attemptType",
              "type": "string",
              "value": "multiple"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "36c3ec51-168c-4dbb-b726-2e87ee85eb04",
      "name": "Format Fields For Single Attempt",
      "type": "n8n-nodes-base.set",
      "position": [
        2080,
        832
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "8b7005d1-3d92-4e69-990a-989f7b2e3ecd",
              "name": "ip",
              "type": "string",
              "value": "={{ $('IF - Multiple Attempts').item.json.ip }}"
            },
            {
              "id": "606fb5b7-d09f-4f0c-8731-015a6d6edbe2",
              "name": "username",
              "type": "string",
              "value": "={{ $('IF - Multiple Attempts').item.json.username }}"
            },
            {
              "id": "e30113ae-2523-490a-ac67-a26a97961b20",
              "name": "totalAttempts",
              "type": "number",
              "value": "=1"
            },
            {
              "id": "6c58a22f-0bba-4ac2-b4a6-a31e1b270a4b",
              "name": "attemptList",
              "type": "string",
              "value": "=[\"Time\": {{ $('IF - Multiple Attempts').item.json.attempts[0].timestamp }},\"Error\": {{ $('IF - Multiple Attempts').item.json.attempts[0].error }}]"
            },
            {
              "id": "a2b7a4a5-7c96-4733-b709-877671f2ab47",
              "name": "attemptType",
              "type": "string",
              "value": "single"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "82ca943f-a5ba-49d0-9cad-14d07d2bf4e3",
  "connections": {
    "Faield Login Trigger": {
      "main": [
        [
          {
            "node": "Normalize Login Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Login Attempt": {
      "main": [
        [
          {
            "node": "Login Attempts Data Store in DB",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Login Event": {
      "main": [
        [
          {
            "node": "Check Username & IP present",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF - Multiple Attempts": {
      "main": [
        [
          {
            "node": "Build Multi-Attempt Summary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Ticket - Single Attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect Multiple Attempts": {
      "main": [
        [
          {
            "node": "IF - Multiple Attempts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Multi-Attempt Summary": {
      "main": [
        [
          {
            "node": "Create Ticket- Multiple Attempts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Username & IP present": {
      "main": [
        [
          {
            "node": "Detect Multiple Attempts",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack Alert - Missing Username/IP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Alert - Single Attempt": {
      "main": [
        [
          {
            "node": "Format Login Attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Ticket - Single Attempt": {
      "main": [
        [
          {
            "node": "Format Fields For Single Attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Alert- Multiple Attempts": {
      "main": [
        [
          {
            "node": "Format Login Attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Login Attempts Data Store in DB": {
      "main": [
        []
      ]
    },
    "Create Ticket- Multiple Attempts": {
      "main": [
        [
          {
            "node": "Format Fields for Multiple-attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Fields For Single Attempt": {
      "main": [
        [
          {
            "node": "Slack Alert - Single Attempt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Fields for Multiple-attempt": {
      "main": [
        [
          {
            "node": "Slack Alert- Multiple Attempts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}