227 lines
13 KiB
JSON
227 lines
13 KiB
JSON
{
|
|
"name": "DHCP Option 82 Auth",
|
|
"description": "WEBHOOK: Always upserts IP. If Option 82 present AND MAC linked to an account, syncs all inventoried MACs to Preseem with plan details.",
|
|
"trigger": {
|
|
"type": "WEBHOOK",
|
|
"inputJsonSchema": {
|
|
"type": "object",
|
|
"required": ["bound", "mac", "ip", "server", "remoteId"],
|
|
"properties": {
|
|
"bound": { "type": "string" },
|
|
"mac": { "type": "string" },
|
|
"ip": { "type": "string" },
|
|
"server": { "type": "string" },
|
|
"remoteId": { "type": "string" }
|
|
}
|
|
},
|
|
"nextSteps": [{ "slug": "check-inventory" }]
|
|
},
|
|
"steps": [
|
|
{
|
|
"name": "Find inventory item by MAC",
|
|
"slug": "check-inventory",
|
|
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
|
|
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
|
|
"allowSkipExecution": false,
|
|
"outputMapper": "({ output, state }) => {\n const items = output.response.findInventoryItemsFromDeviceIdentifiers;\n // Accept items assigned to Accounts OR NetworkSites\n const assigned = items.filter(\n (item) => {\n const type = item.inventoryItem?.assignation?.assignee?.__typename;\n return type === 'Account' || type === 'NetworkSite';\n }\n );\n if (assigned.length === 0) return null;\n const inv = assigned[0].inventoryItem;\n const assignee = inv.assignation.assignee;\n return {\n assigneeId: assignee.id,\n assigneeType: assignee.__typename,\n assigneeName: assignee.name || ''\n };\n}",
|
|
"inputs": [
|
|
{
|
|
"key": "query",
|
|
"type": "LITERAL",
|
|
"value": "string:query FindItem($input: FindInventoryItemsFromDeviceIdentifiersQueryInput!) {\n findInventoryItemsFromDeviceIdentifiers(input: $input) {\n identifier\n inventoryItem {\n id\n model { name }\n assignation {\n assignee {\n __typename\n ... on Account { id name }\n }\n }\n }\n }\n}"
|
|
},
|
|
{
|
|
"key": "variables",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return { input: { identifiers: [formattedMac] } };\n}"
|
|
}
|
|
],
|
|
"nextSteps": [{ "slug": "is-connect" }]
|
|
},
|
|
{
|
|
"name": "Connect or disconnect?",
|
|
"slug": "is-connect",
|
|
"type": "CONDITION",
|
|
"nodeSlug": "condition",
|
|
"condition": "({ secrets, state, utils, variables }) => {\n return state.input.bound === '1';\n}",
|
|
"elseSteps": [{ "slug": "expire-ip" }],
|
|
"thenSteps": [{ "slug": "has-account-connect" }]
|
|
},
|
|
{
|
|
"name": "Account found? (connect)",
|
|
"slug": "has-account-connect",
|
|
"type": "CONDITION",
|
|
"nodeSlug": "condition",
|
|
"condition": "({ secrets, state, utils, variables }) => {\n return state.steps['check-inventory'].output != null;\n}",
|
|
"elseSteps": [{ "slug": "upsert-ip-no-account" }],
|
|
"thenSteps": [{ "slug": "upsert-ip-with-account" }]
|
|
},
|
|
{
|
|
"name": "Upsert IP (no account)",
|
|
"slug": "upsert-ip-no-account",
|
|
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
|
|
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
|
|
"allowSkipExecution": false,
|
|
"inputs": [
|
|
{
|
|
"key": "query",
|
|
"type": "LITERAL",
|
|
"value": "string:mutation UpsertIp($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
|
|
},
|
|
{
|
|
"key": "variables",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return {\n input: {\n assignations: { macAddress: formattedMac, ipAddress: state.input.ip, expired: false }\n }\n };\n}"
|
|
}
|
|
],
|
|
"nextSteps": [{ "slug": "end" }]
|
|
},
|
|
{
|
|
"name": "Upsert IP (with account)",
|
|
"slug": "upsert-ip-with-account",
|
|
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
|
|
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
|
|
"allowSkipExecution": false,
|
|
"inputs": [
|
|
{
|
|
"key": "query",
|
|
"type": "LITERAL",
|
|
"value": "string:mutation UpsertIp($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
|
|
},
|
|
{
|
|
"key": "variables",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return {\n input: {\n assignations: { macAddress: formattedMac, ipAddress: state.input.ip, expired: false }\n }\n };\n}"
|
|
}
|
|
],
|
|
"nextSteps": [{ "slug": "should-sync-preseem" }]
|
|
},
|
|
{
|
|
"name": "Has Option 82 and account?",
|
|
"slug": "should-sync-preseem",
|
|
"type": "CONDITION",
|
|
"nodeSlug": "condition",
|
|
"condition": "({ secrets, state, utils, variables }) => {\n const inv = state.steps['check-inventory'].output;\n if (inv == null) return false;\n if (!state.input.remoteId || state.input.remoteId === '') return false;\n return inv.assigneeType === 'Account';\n}",
|
|
"elseSteps": [{ "slug": "should-sync-site" }],
|
|
"thenSteps": [{ "slug": "get-account-macs-and-plan" }]
|
|
},
|
|
{
|
|
"name": "Get all inventoried MACs and plan",
|
|
"slug": "get-account-macs-and-plan",
|
|
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
|
|
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
|
|
"allowSkipExecution": false,
|
|
"outputMapper": "({ output, state }) => {\n const account = output.response.account;\n if (!account) {\n workflow.fail('Account not found', 'ACCOUNT_NOT_FOUND');\n }\n const macs = [];\n for (const assign of (account.assignedInventoryItems?.nodes || [])) {\n const inv = assign.inventoryItem || {};\n for (const f of (inv.fields?.nodes || [])) {\n const fieldName = (f.modelField?.name || '').toLowerCase();\n const data = (f.data || '').trim();\n if (fieldName.includes('mac') && data && data !== '00:00:00:00:00:00') {\n const cleaned = data.startsWith('1:') ? data.slice(2) : data;\n const parts = cleaned.split(':');\n if (parts.length === 6) {\n macs.push(parts.map((p) => p.padStart(2, '0').toUpperCase()).join(':'));\n }\n }\n }\n for (const a of (inv.ipAddressAssignments?.nodes || [])) {\n if (a.macAddress) macs.push(a.macAddress.toUpperCase());\n }\n }\n const plan = (account.billingSubscriptions?.nodes || [])\n .map((s) => s.productVersion?.product?.slug)\n .find(Boolean) || '';\n return { macs: [...new Set(macs)], plan, gaiiaId: account.readableId };\n}",
|
|
"inputs": [
|
|
{
|
|
"key": "query",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const accountId = state.steps['check-inventory'].output.accountId;\n return `query { account(id: \"${accountId}\") { readableId assignedInventoryItems { nodes { inventoryItem { fields { nodes { data modelField { name } } } ipAddressAssignments { nodes { macAddress } } } } } billingSubscriptions { nodes { productVersion { product { slug } } } } } }`;\n}"
|
|
},
|
|
{
|
|
"key": "variables",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => ({})"
|
|
}
|
|
],
|
|
"nextSteps": [{ "slug": "preseem-update" }]
|
|
},
|
|
{
|
|
"name": "Update Preseem",
|
|
"slug": "preseem-update",
|
|
"type": "PRESEEM_REST_REQUEST",
|
|
"nodeSlug": "preseem-rest-request",
|
|
"allowSkipExecution": false,
|
|
"inputs": [
|
|
{
|
|
"key": "method",
|
|
"type": "LITERAL",
|
|
"value": "string:PUT"
|
|
},
|
|
{
|
|
"key": "path",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const { gaiiaId } = state.steps['get-account-macs-and-plan'].output;\n return `/services/${gaiiaId}`;\n}"
|
|
},
|
|
{
|
|
"key": "body",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const { gaiiaId, macs, plan } = state.steps['get-account-macs-and-plan'].output;\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const connectingMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toLowerCase();\n const framedIp = state.input.ip;\n const inventoriedMacs = macs.map((m) => m.toLowerCase());\n const attachments = inventoriedMacs\n .filter((m) => state.input.bound === '0' || m !== connectingMac)\n .map((m) => ({ cpe_mac: m }));\n if (state.input.bound === '1') {\n attachments.push({ cpe_mac: connectingMac, network_prefixes: [framedIp] });\n }\n return {\n id: String(gaiiaId),\n attachments,\n account: String(gaiiaId),\n package: plan\n };\n}"
|
|
}
|
|
],
|
|
"nextSteps": [{ "slug": "end" }]
|
|
},
|
|
{
|
|
"name": "Expire IP",
|
|
"slug": "expire-ip",
|
|
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
|
|
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
|
|
"allowSkipExecution": false,
|
|
"inputs": [
|
|
{
|
|
"key": "query",
|
|
"type": "LITERAL",
|
|
"value": "string:mutation ExpireIp($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
|
|
},
|
|
{
|
|
"key": "variables",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return {\n input: {\n assignations: { macAddress: formattedMac, ipAddress: state.input.ip, expired: true }\n }\n };\n}"
|
|
}
|
|
],
|
|
"nextSteps": [{ "slug": "should-sync-preseem-disconnect" }]
|
|
},
|
|
{
|
|
"name": "Network site with Option 82?",
|
|
"slug": "should-sync-site",
|
|
"type": "CONDITION",
|
|
"nodeSlug": "condition",
|
|
"condition": "({ secrets, state, utils, variables }) => {\n const inv = state.steps['check-inventory'].output;\n if (inv == null) return false;\n if (!state.input.remoteId || state.input.remoteId === '') return false;\n return inv.assigneeType === 'NetworkSite';\n}",
|
|
"elseSteps": [{ "slug": "end" }],
|
|
"thenSteps": [{ "slug": "preseem-site-update" }]
|
|
},
|
|
{
|
|
"name": "Update Preseem (site)",
|
|
"slug": "preseem-site-update",
|
|
"type": "PRESEEM_REST_REQUEST",
|
|
"nodeSlug": "preseem-rest-request",
|
|
"allowSkipExecution": false,
|
|
"inputs": [
|
|
{
|
|
"key": "method",
|
|
"type": "LITERAL",
|
|
"value": "string:PUT"
|
|
},
|
|
{
|
|
"key": "path",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const inv = state.steps['check-inventory'].output;\n return `/access_points/${inv.assigneeId}`;\n}"
|
|
},
|
|
{
|
|
"key": "body",
|
|
"type": "FUNCTION",
|
|
"value": "({ state }) => {\n const inv = state.steps['check-inventory'].output;\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const mac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toLowerCase();\n return {\n id: inv.assigneeId,\n name: inv.assigneeName || inv.assigneeId,\n tower: inv.assigneeName || '',\n ip_address: state.input.ip\n };\n}"
|
|
}
|
|
],
|
|
"nextSteps": [{ "slug": "end" }]
|
|
},
|
|
{
|
|
"name": "Has Option 82 and account? (disconnect)",
|
|
"slug": "should-sync-preseem-disconnect",
|
|
"type": "CONDITION",
|
|
"nodeSlug": "condition",
|
|
"condition": "({ secrets, state, utils, variables }) => {\n const inv = state.steps['check-inventory'].output;\n if (inv == null) return false;\n if (!state.input.remoteId || state.input.remoteId === '') return false;\n return inv.assigneeType === 'Account';\n}",
|
|
"elseSteps": [{ "slug": "should-sync-site-disconnect" }],
|
|
"thenSteps": [{ "slug": "get-account-macs-and-plan" }]
|
|
},
|
|
{
|
|
"name": "Network site? (disconnect)",
|
|
"slug": "should-sync-site-disconnect",
|
|
"type": "CONDITION",
|
|
"nodeSlug": "condition",
|
|
"condition": "({ secrets, state, utils, variables }) => {\n const inv = state.steps['check-inventory'].output;\n if (inv == null) return false;\n if (!state.input.remoteId || state.input.remoteId === '') return false;\n return inv.assigneeType === 'NetworkSite';\n}",
|
|
"elseSteps": [{ "slug": "end" }],
|
|
"thenSteps": [{ "slug": "preseem-site-update" }]
|
|
}
|
|
]
|
|
}
|