network/gaiia/pppoe_full_sync_workflow.json
2026-07-19 14:42:02 -05:00

198 lines
12 KiB
JSON

{
"name": "pppoe auth",
"description": "WEBHOOK: PPPoE connect/disconnect → account lookup → Preseem sync. Connect (bound=1): BYOD if needed, assign IP, add MAC to Preseem with all inventoried equipment. Disconnect (bound=0): remove MAC from Preseem.",
"trigger": {
"type": "WEBHOOK",
"inputJsonSchema": {
"type": "object",
"required": ["bound", "callingstationid", "framedip", "username"],
"properties": {
"bound": { "type": "string" },
"framedip": { "type": "string" },
"username": { "type": "string" },
"callingstationid": { "type": "string" }
}
},
"nextSteps": [{ "slug": "get-account-id" }]
},
"steps": [
{
"name": "Get Account ID from RADIUS Username",
"slug": "get-account-id",
"type": "CUSTOM_WORKFLOW",
"nodeSlug": "get-account-id-from-radius-username",
"allowSkipExecution": false,
"inputs": [
{ "key": "username", "type": "STATE", "value": "input.username" }
],
"nextSteps": [{ "slug": "is-connect" }],
"errorSteps": [{ "slug": "end", "errorCodes": ["USERNAME_NOT_FOUND"] }]
},
{
"name": "Is this a connect?",
"slug": "is-connect",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n return state.input.bound === '1';\n}",
"elseSteps": [{ "slug": "expire-ip-assignment" }],
"thenSteps": [{ "slug": "check-inventory" }]
},
{
"name": "Expire IP assignment on disconnect",
"slug": "expire-ip-assignment",
"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.callingstationid;\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: {\n macAddress: formattedMac,\n ipAddress: state.input.framedip,\n expired: true\n }\n }\n };\n}"
}
],
"nextSteps": [{ "slug": "get-account-macs-and-plan" }]
},
{
"name": "Check if MAC is already inventoried",
"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 const assigned = items.filter(\n (item) => item.inventoryItem?.assignation?.assignee?.__typename === 'Account'\n );\n return assigned;\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.callingstationid;\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": "if-byod-needed" }]
},
{
"name": "Is BYOD needed?",
"slug": "if-byod-needed",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const found = state.steps['check-inventory'].output;\n return !found || found.length === 0;\n}",
"elseSteps": [{ "slug": "assign-ip-address" }],
"thenSteps": [{ "slug": "find-byod-model" }]
},
{
"name": "Find Customer Provided Router model",
"slug": "find-byod-model",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output }) => {\n const model = output.response.inventoryModels.nodes[0];\n if (!model) {\n workflow.fail('Customer Provided Router model not found', 'MODEL_NOT_FOUND');\n }\n const macField = model.fields.nodes.find(\n (field) => field.name === 'MAC Address'\n );\n if (!macField) {\n workflow.fail('MAC Address field not found on Customer Provided Router model', 'MAC_FIELD_NOT_FOUND');\n }\n return { id: model.id, macFieldId: macField.id };\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query GetModel($filters: InventoryModelQueryFilters) {\n inventoryModels(filters: $filters) {\n nodes {\n id\n name\n fields { nodes { id name } }\n }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ secrets, state, utils, variables }) => {\n return { filters: { name: { equals: 'Customer Provided Router' } } };\n}"
}
],
"nextSteps": [{ "slug": "create-byod" }]
},
{
"name": "Create BYOD inventory item",
"slug": "create-byod",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation AddInventoryItems($input: CreateInventoryItemsInput!) {\n addInventoryItems(input: $input) {\n inventoryItemsCreationResult { addedItems { id } }\n errors { code message }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.callingstationid;\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 const accountId = state.steps['get-account-id'].output.accounts[0].accountId;\n const { id: modelId, macFieldId } = state.steps['find-byod-model'].output;\n return {\n input: {\n items: [{\n fields: [{ modelFieldId: macFieldId, data: formattedMac }],\n modelId: modelId,\n equipmentConditionType: 'NEW',\n purchasePriceInCents: 0,\n status: 'FUNCTIONAL',\n assignation: { assigneeId: accountId, assigneeType: 'ACCOUNT' }\n }]\n }\n };\n}"
}
],
"nextSteps": [{ "slug": "assign-ip-address" }]
},
{
"name": "Assign IP address to MAC",
"slug": "assign-ip-address",
"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.callingstationid;\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: {\n macAddress: formattedMac,\n ipAddress: state.input.framedip,\n expired: false\n }\n }\n };\n}"
}
],
"nextSteps": [{ "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['get-account-id'].output.accounts[0].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.callingstationid;\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.framedip;\n // On connect (bound=1): include connecting MAC with its IP. On disconnect (bound=0): exclude it.\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" }]
}
]
}