Auto-resolve bare IP to CARD asset ID with suffix lookup
The CARD API requires asset IDs in the format {IP}-{SUFFIX} (e.g.,
10.240.78.110-CTEC) but the frontend only has the bare IP. Add
resolveAssetId() helper that tries known suffixes (CTEC, NATL,
CHTR, COML, RESI, WIFI, VOIP) via owner lookup until one succeeds.
Apply resolution to confirm, decline, and redirect handlers so
they accept bare IPs from the frontend and resolve them
automatically before calling the CARD mutation APIs.
This commit is contained in:
@@ -290,6 +290,43 @@ async function redirectAsset(assetId, fromTeam, toTeam, updateToken) {
|
|||||||
return { status: res.status, body: res.body, ok: res.status >= 200 && res.status < 300 };
|
return { status: res.status, body: res.body, ok: res.status >= 200 && res.status < 300 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a bare IP address to a full CARD asset ID by trying known suffixes.
|
||||||
|
* Returns the first asset ID that returns a valid owner record, or null if none found.
|
||||||
|
*/
|
||||||
|
async function resolveAssetId(ip) {
|
||||||
|
const SUFFIXES = ['CTEC', 'NATL', 'CHTR', 'COML', 'RESI', 'WIFI', 'VOIP'];
|
||||||
|
const trimmedIp = (ip || '').trim();
|
||||||
|
if (!trimmedIp) return null;
|
||||||
|
|
||||||
|
// If it already has a suffix (contains a dash followed by letters), use as-is
|
||||||
|
if (/\d+-[A-Z]+$/i.test(trimmedIp)) {
|
||||||
|
const result = await getOwner(trimmedIp);
|
||||||
|
if (result.ok) return trimmedIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try each suffix
|
||||||
|
for (const suffix of SUFFIXES) {
|
||||||
|
const candidate = `${trimmedIp}-${suffix}`;
|
||||||
|
try {
|
||||||
|
const result = await getOwner(candidate);
|
||||||
|
if (result.ok) return candidate;
|
||||||
|
} catch (_) {
|
||||||
|
// Continue to next suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try bare IP as last resort
|
||||||
|
try {
|
||||||
|
const result = await getOwner(trimmedIp);
|
||||||
|
if (result.ok) return trimmedIp;
|
||||||
|
} catch (_) {
|
||||||
|
// Not found
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isConfigured,
|
isConfigured,
|
||||||
missingVars,
|
missingVars,
|
||||||
@@ -304,4 +341,5 @@ module.exports = {
|
|||||||
declineAsset,
|
declineAsset,
|
||||||
redirectAsset,
|
redirectAsset,
|
||||||
invalidateToken,
|
invalidateToken,
|
||||||
|
resolveAssetId,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const {
|
|||||||
confirmAsset,
|
confirmAsset,
|
||||||
declineAsset,
|
declineAsset,
|
||||||
redirectAsset,
|
redirectAsset,
|
||||||
|
resolveAssetId,
|
||||||
} = require('../helpers/cardApi');
|
} = require('../helpers/cardApi');
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -213,15 +214,25 @@ function createCardApiRouter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { queueItemId } = req.params;
|
const { queueItemId } = req.params;
|
||||||
const { teamName, assetId, comment } = req.body;
|
const { teamName, assetId: rawAssetId, comment } = req.body;
|
||||||
|
|
||||||
if (!teamName || typeof teamName !== 'string' || !teamName.trim()) {
|
if (!teamName || typeof teamName !== 'string' || !teamName.trim()) {
|
||||||
return res.status(400).json({ error: 'teamName is required.' });
|
return res.status(400).json({ error: 'teamName is required.' });
|
||||||
}
|
}
|
||||||
if (!assetId || typeof assetId !== 'string' || !assetId.trim()) {
|
if (!rawAssetId || typeof rawAssetId !== 'string' || !rawAssetId.trim()) {
|
||||||
return res.status(400).json({ error: 'assetId is required.' });
|
return res.status(400).json({ error: 'assetId is required.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve bare IP to full CARD asset ID
|
||||||
|
let assetId = rawAssetId.trim();
|
||||||
|
if (!/\d+-[A-Z]+$/i.test(assetId)) {
|
||||||
|
const resolved = await resolveAssetId(assetId);
|
||||||
|
if (!resolved) {
|
||||||
|
return res.status(404).json({ error: `Asset not found in CARD for IP: ${assetId}` });
|
||||||
|
}
|
||||||
|
assetId = resolved;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { rows } = await pool.query(
|
const { rows } = await pool.query(
|
||||||
'SELECT * FROM ivanti_todo_queue WHERE id = $1 AND user_id = $2 AND workflow_type = $3',
|
'SELECT * FROM ivanti_todo_queue WHERE id = $1 AND user_id = $2 AND workflow_type = $3',
|
||||||
@@ -305,15 +316,25 @@ function createCardApiRouter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { queueItemId } = req.params;
|
const { queueItemId } = req.params;
|
||||||
const { teamName, assetId, comment } = req.body;
|
const { teamName, assetId: rawAssetId, comment } = req.body;
|
||||||
|
|
||||||
if (!teamName || typeof teamName !== 'string' || !teamName.trim()) {
|
if (!teamName || typeof teamName !== 'string' || !teamName.trim()) {
|
||||||
return res.status(400).json({ error: 'teamName is required.' });
|
return res.status(400).json({ error: 'teamName is required.' });
|
||||||
}
|
}
|
||||||
if (!assetId || typeof assetId !== 'string' || !assetId.trim()) {
|
if (!rawAssetId || typeof rawAssetId !== 'string' || !rawAssetId.trim()) {
|
||||||
return res.status(400).json({ error: 'assetId is required.' });
|
return res.status(400).json({ error: 'assetId is required.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve bare IP to full CARD asset ID
|
||||||
|
let assetId = rawAssetId.trim();
|
||||||
|
if (!/\d+-[A-Z]+$/i.test(assetId)) {
|
||||||
|
const resolved = await resolveAssetId(assetId);
|
||||||
|
if (!resolved) {
|
||||||
|
return res.status(404).json({ error: `Asset not found in CARD for IP: ${assetId}` });
|
||||||
|
}
|
||||||
|
assetId = resolved;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { rows } = await pool.query(
|
const { rows } = await pool.query(
|
||||||
'SELECT * FROM ivanti_todo_queue WHERE id = $1 AND user_id = $2 AND workflow_type = $3',
|
'SELECT * FROM ivanti_todo_queue WHERE id = $1 AND user_id = $2 AND workflow_type = $3',
|
||||||
@@ -397,7 +418,7 @@ function createCardApiRouter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { queueItemId } = req.params;
|
const { queueItemId } = req.params;
|
||||||
const { fromTeam, toTeam, assetId } = req.body;
|
const { fromTeam, toTeam, assetId: rawAssetId } = req.body;
|
||||||
|
|
||||||
if (!fromTeam || typeof fromTeam !== 'string' || !fromTeam.trim()) {
|
if (!fromTeam || typeof fromTeam !== 'string' || !fromTeam.trim()) {
|
||||||
return res.status(400).json({ error: 'fromTeam is required.' });
|
return res.status(400).json({ error: 'fromTeam is required.' });
|
||||||
@@ -405,10 +426,20 @@ function createCardApiRouter() {
|
|||||||
if (!toTeam || typeof toTeam !== 'string' || !toTeam.trim()) {
|
if (!toTeam || typeof toTeam !== 'string' || !toTeam.trim()) {
|
||||||
return res.status(400).json({ error: 'toTeam is required.' });
|
return res.status(400).json({ error: 'toTeam is required.' });
|
||||||
}
|
}
|
||||||
if (!assetId || typeof assetId !== 'string' || !assetId.trim()) {
|
if (!rawAssetId || typeof rawAssetId !== 'string' || !rawAssetId.trim()) {
|
||||||
return res.status(400).json({ error: 'assetId is required.' });
|
return res.status(400).json({ error: 'assetId is required.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve bare IP to full CARD asset ID (e.g., 10.240.78.110 → 10.240.78.110-CTEC)
|
||||||
|
let assetId = rawAssetId.trim();
|
||||||
|
if (!/\d+-[A-Z]+$/i.test(assetId)) {
|
||||||
|
const resolved = await resolveAssetId(assetId);
|
||||||
|
if (!resolved) {
|
||||||
|
return res.status(404).json({ error: `Asset not found in CARD for IP: ${assetId}` });
|
||||||
|
}
|
||||||
|
assetId = resolved;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { rows } = await pool.query(
|
const { rows } = await pool.query(
|
||||||
'SELECT * FROM ivanti_todo_queue WHERE id = $1 AND user_id = $2 AND workflow_type = $3',
|
'SELECT * FROM ivanti_todo_queue WHERE id = $1 AND user_id = $2 AND workflow_type = $3',
|
||||||
|
|||||||
Reference in New Issue
Block a user