{ "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" }] } ] }