Skip to content

Commit

Permalink
Merge pull request #194 from NSWC-Crane/CHRIS_DEV
Browse files Browse the repository at this point in the history
  • Loading branch information
crodriguez6497 authored Feb 19, 2025
2 parents b502206 + 9a5623e commit 27f8cb7
Show file tree
Hide file tree
Showing 57 changed files with 3,988 additions and 1,922 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
## What is a POAM?
[NIST](https://csrc.nist.gov/glossary/term/POAM) defines a POAM as "a document for a system that identifies tasks needing to be accomplished. It details resources required to accomplish the elements of the plan, any milestones in meetings the tasks, and scheduled completion dates for the milestones".

The POAM document is an output of the "Assess" step of the Risk Management Framework.
The POAM document is an output of the "Assess" step of the Risk Management Framework.
35 changes: 35 additions & 0 deletions api/Controllers/AI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
!##########################################################################
! CRANE PLAN OF ACTION AND MILESTONE AUTOMATION TOOL (C-PAT) SOFTWARE
! Use is governed by the Open Source Academic Research License Agreement
! contained in the LICENSE.MD file, which is part of this software package.
! BY USING OR MODIFYING THIS SOFTWARE, YOU ARE AGREEING TO THE TERMS AND
! CONDITIONS OF THE LICENSE.
!##########################################################################
*/

const aiService = require('../Services/aiService');
const logger = require('../utils/logger');

module.exports.generateMitigation = async function generateMitigation(req, res, next) {
try {
const response = await aiService.generateMitigation(req, res, next);

if (!res.headersSent && response) {
res.status(200).json(response);
}
} catch (error) {
logger.writeError('AI', 'generateMitigation', {
error: error.message,
prompt: req.body
});

if (!res.headersSent) {
if (error.status === 400) {
res.status(400).json({ error: 'Validation Error', detail: error.errors });
} else {
res.status(500).json({ error: 'Internal Server Error', detail: error.message });
}
}
}
};
27 changes: 27 additions & 0 deletions api/Controllers/Import.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,31 @@ module.exports.importVRAMExcel = async (req, res, next) => {
}
}
});
};

module.exports.importAssetListExcel = async (req, res, next) => {
const file = req.files[0];

if (!file) {
return res.status(400).json({ message: "No file uploaded" });
}

importService.excelFilter(req, file, async (err) => {
if (err) {
return res.status(400).json({
message: err.message,
});
} else {
try {
const result = await importService.importAssetListExcel(file);
res.status(201).json(result);
} catch (error) {
res.status(500).json({
message: "Could not process the file",
error: error.message,
});
throw new SmError.UnprocessableError('Error processing Asset List file.');
}
}
});
};
23 changes: 12 additions & 11 deletions api/Controllers/Poam.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,18 @@ module.exports.updatePoamStatus = async function updatePoamStatus(req, res, next

module.exports.deletePoam = async function deletePoam(req, res, next) {
try {
const result = await poamService.deletePoam(req, res, next);
if (result.error) {
res.status(500).json({ error: 'Internal Server Error', detail: result.error });
} else {
res.status(204).send();
const result = await poamService.deletePoam(req);

if (result.status) {
return res.status(result.status).json({ error: result.errors });
}
} catch (error) {
if (error.status === 400) {
res.status(400).json({ error: 'Validation Error', detail: error.errors });
} else {
res.status(500).json({ error: 'Internal Server Error', detail: error.message });

if (result.success) {
return res.status(204).send();
}

return res.status(500).json({ error: 'Unknown error occurred' });
} catch (error) {
return res.status(500).json({ error: 'Internal Server Error', detail: error.message });
}
};
};
26 changes: 22 additions & 4 deletions api/Controllers/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

const userService = require('../Services/usersService');
const SmError = require('../utils/error');

module.exports.getUsers = async function getUsers(req, res, next) {
try {
Expand Down Expand Up @@ -104,12 +105,29 @@ module.exports.updateUserLastCollectionAccessed = async function updateUserLastC
}
};

module.exports.deleteUser = async function deleteUser(req, res, next) {
module.exports.disableUser = async function disableUser(req, res, next) {
try {
const elevate = req.query.elevate;
await userService.deleteUser(requestorId, elevate, req);
res.status(200).json({ message: 'User deleted' });
const userId = parseInt(req.params.userId);

const result = await userService.disableUser(elevate, userId);

res.status(200).json({
success: true,
message: result.message
});
} catch (error) {
res.status(500).json({ error: 'Internal Server Error', detail: error.message });
if (error instanceof SmError.PrivilegeError) {
res.status(400).json({
success: false,
error: error.message
});
} else {
res.status(500).json({
success: false,
error: 'Internal Server Error',
detail: error.message
});
}
}
};
182 changes: 182 additions & 0 deletions api/Services/aiService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
!##########################################################################
! CRANE PLAN OF ACTION AND MILESTONE AUTOMATION TOOL (C-PAT) SOFTWARE
! Use is governed by the Open Source Academic Research License Agreement
! contained in the LICENSE.MD file, which is part of this software package.
! BY USING OR MODIFYING THIS SOFTWARE, YOU ARE AGREEING TO THE TERMS AND
! CONDITIONS OF THE LICENSE.
!##########################################################################
*/

'use strict';

const config = require('../utils/config');
const logger = require('../utils/logger');
const { generateText } = require('ai');
const { anthropic } = require('@ai-sdk/anthropic');
const { openai } = require('@ai-sdk/openai');
const { gemini } = require('@ai-sdk/google');
const { mistral } = require('@ai-sdk/mistral');
const { groq } = require('@ai-sdk/groq');
const { xai } = require('@ai-sdk/xai');
const { togetherai } = require('@ai-sdk/togetherai');
const { cohere } = require('@ai-sdk/cohere');
const { fireworks } = require('@ai-sdk/fireworks');
const { cerebras } = require('@ai-sdk/cerebras');
const { perplexity } = require('@ai-sdk/perplexity');
const { deepinfra } = require('@ai-sdk/deepinfra');
const { ollama } = require('ollama-ai-provider');

const AI_MODELS = {
anthropic: 'claude-3-5-haiku-20241022',
openai: 'gpt-4-turbo',
gemini: 'gemini-1.5-pro-latest',
mistral: 'mistral-small-latest',
groq: 'gemma2-9b-it',
xai: 'grok-2-1212',
togetherai: 'google/gemma-2-9b-it',
cohere: 'command-r-plus',
fireworks: 'accounts/fireworks/models/llama-v3p3-70b-instruct',
cerebras: 'llama3.1-8b',
perplexity: 'sonar-pro',
deepinfra: 'google/gemma-2-9b-it',
ollama: 'llama3.2'
};

const ENV_KEYS = {
anthropic: 'ANTHROPIC_API_KEY',
openai: 'OPENAI_API_KEY',
gemini: 'GOOGLE_API_KEY',
mistral: 'MISTRAL_API_KEY',
groq: 'GROQ_API_KEY',
xai: 'XAI_API_KEY',
togetherai: 'TOGETHER_AI_API_KEY',
cohere: 'COHERE_API_KEY',
fireworks: 'FIREWORKS_API_KEY',
cerebras: 'CEREBRAS_API_KEY',
perplexity: 'PERPLEXITY_API_KEY',
deepinfra: 'DEEPINFRA_API_KEY'
};

async function getAIModel() {
if (!config.ai.provider) {
throw new Error('AI provider is not configured');
}

const provider = config.ai.provider.toLowerCase();
if (!(provider in AI_MODELS)) {
throw new Error(`Unsupported AI provider: ${config.ai.provider}`);
}

if (provider !== 'ollama' && !config.ai.apiKey) {
throw new Error('AI API key is not configured');
}

if (provider !== 'ollama' && ENV_KEYS[provider]) {
process.env[ENV_KEYS[provider]] = config.ai.apiKey;
}

const modelName = config.ai.modelName || AI_MODELS[provider];

switch (provider) {
case 'anthropic':
return anthropic(modelName);
case 'openai':
return openai(modelName);
case 'gemini':
return gemini(modelName);
case 'mistral':
return mistral(modelName);
case 'groq':
return groq(modelName);
case 'xai':
return xai(modelName);
case 'togetherai':
return togetherai(modelName);
case 'cohere':
return cohere(modelName);
case 'fireworks':
return fireworks(modelName);
case 'cerebras':
return cerebras(modelName);
case 'perplexity':
return perplexity(modelName);
case 'deepinfra':
return deepinfra(modelName);
case 'ollama':
return ollama(modelName);
default:
throw new Error('Unsupported AI provider');
}
}

exports.generateMitigation = async function generateMitigation(req, res, next) {
if (!config.ai.enabled) {
return next({
status: 400,
errors: {
message: 'AI is disabled'
}
});
}
if (!req.body) {
logger.writeError('aiService', 'generateMitigation', {
error: 'Missing prompt'
});
return next({
status: 400,
errors: {
message: 'Prompt is required'
}
});
}

if (!config.ai.provider) {
logger.writeError('aiService', 'generateMitigation', {
error: 'Missing AI provider configuration'
});
return next({
status: 500,
errors: {
message: 'AI provider is not configured'
}
});
}

try {
const model = await getAIModel();
const { text } = await generateText({
model,
prompt: req.body,
timeout: 60000
});

if (!text) {
logger.writeError('aiService', 'generateMitigation', {
error: 'Empty response from AI provider'
});
throw new Error('No response received from AI provider');
}

return {
mitigation: text,
};
} catch (error) {
logger.writeError('aiService', 'generateMitigation', {
error: error.message,
stack: error.stack,
provider: config.ai.provider
});

const isAuthError = error.message.includes('API key') ||
error.message.includes('authentication');

return next({
status: 500,
errors: {
message: isAuthError ? 'AI service configuration error' : 'AI service error',
detail: error.message
}
});
}
};
Loading

0 comments on commit 27f8cb7

Please sign in to comment.