fleet/website/api/controllers/create-vanta-authorization-request.js
Eric bd9867c7b6
Website: update error handling in create-vanta-authorization-request (#41483)
Changes:
- Updated the create-vanta-authorization-request action to return a
`fleetInstanceNotResponding` response if requests fail with a 404 status
code.
2026-03-11 13:53:49 -05:00

186 lines
9.2 KiB
JavaScript
Vendored

module.exports = {
friendlyName: 'Create Vanta authorization request',
description: 'Returns a URL used to authorize requests to the user\'s Vanta account from fleetdm.com on behalf of the user.',
inputs: {
emailAddress: {
type: 'string',
required: true,
isEmail: true,
},
fleetInstanceUrl: {
type: 'string',
required: true,
},
fleetApiKey: {
type: 'string',
required: true,
},
redirectToExternalPageAfterAuthorization: {
type: 'string',
description: 'If provided, the user will be sent to this URL after they complete the setup of this integration'
},
sharedSecret: {
type: 'string',
description: 'A shared secret used to verify external requests to this endpoint.',
extendedDescription: 'This input is used only when this action runs at the "/api/v1/create-external-vanta-authorization-request" endpoint'
}
},
exits: {
success: {
outputType: 'string'
},
connectionAlreadyExists: {
description: 'The Fleet instance url provided is already connected to a Vanta account.',
statusCode: 409,
},
fleetInstanceNotResponding: {
description: 'A http request to the user\'s Fleet instance failed.',
statusCode: 404,
},
invalidToken: {
description: 'The provided token for the api-only user could not be used to authorize requests from fleetdm.com',
statusCode: 401,
},
requestToFleetInstanceForbidden: {
description: 'The user\'s Fleet instance returned a "403 Forbidden" response',
statusCode: 403,
},
invalidLicense: {
description: 'The Fleet instance provided is using a Free license.',
statusCode: 400,
},
invalidResponseFromFleetInstance: {
description: 'The response body from the Fleet API was invalid.',
statusCode: 400,
},
nonApiOnlyUser: {
description: 'The provided API token for this Fleet instance is not associated with an api-only user.',
statusCode: 400,
},
insufficientPermissions:{
description: 'The api-only user associated with the provided token does not have the propper permissions to query the users endpoint.',
statusCode: 403,
},
missingOrInvalidSharedSecret: {
description: 'The request to set up a Vanta integration has an invalid shared secret',
statusCode: 401
}
},
fn: async function (inputs) {
require('assert')(sails.config.custom.sharedSecretForExternalVantaRequests);
if(this.req.url === '/api/v1/create-external-vanta-authorization-request' && inputs.sharedSecret !== sails.config.custom.sharedSecretForExternalVantaRequests) {
throw 'missingOrInvalidSharedSecret';
}
let url = require('url');
// Look for any existing VantaConnection records that use this fleet instance URL.
let existingConnectionRecord = await VantaConnection.findOne({fleetInstanceUrl: inputs.fleetInstanceUrl});
// Generate the `state` string for this request.
let generatedStateForThisRequest = await sails.helpers.strings.random.with({len: 10});
// Generate a sourceId for this user. This value will be used as the indentifier of ther user's vanta connection
let generatedSourceIdSuffix = await sails.helpers.strings.random.with({len: 20, style: 'url-friendly'});
let sourceIDForThisRequest = 'fleet_'+generatedSourceIdSuffix;
if(existingConnectionRecord) {
// If an active Vanta connection exists for the provided Fleet instance url, we'll throw a 'connectionAlreadyExists' exit, and the user will be asked to contact us to make changes to the existing vanta connection.
if(existingConnectionRecord.isConnectedToVanta) {
throw 'connectionAlreadyExists';
} else if(existingConnectionRecord.fleetApiKey !== inputs.fleetApiKey && existingConnectionRecord.emailAddress !== inputs.emailAddress) {
// If an incomplete connection exists, and the API token and email address provided do not match. The user will be asked to contact us to make changes to their connection.
throw 'connectionAlreadyExists';
} else {
// If an inactive and incomplete Vanta connection exists that uses the same API token and email address, we'll use the sourceId from that record for this request.
sourceIDForThisRequest = existingConnectionRecord.vantaSourceId;
}
}
// Check the fleet instance url and API key provided
let responseFromFleetInstance = await sails.helpers.http.get(inputs.fleetInstanceUrl+'/api/v1/fleet/me',{},{'Authorization': 'Bearer ' +inputs.fleetApiKey})
.intercept('requestFailed', 'fleetInstanceNotResponding')
.intercept({raw: {statusCode: 401}}, 'invalidToken')
.intercept({raw: {statusCode: 403}}, 'requestToFleetInstanceForbidden')
.intercept({raw: {statusCode: 404}}, 'fleetInstanceNotResponding')
.intercept((error)=>{
return new Error(`When sending a request to a user's (${inputs.emailAddress}) Fleet instance's (${inputs.fleetInstanceUrl}) /me endpoint to verify that a token meets the requirements for a Vanta connection, an error occurred: ${error}`);
});
// Throw an error if the response from the Fleet instance's /me API endpoint does not contain a user.
if(!responseFromFleetInstance.user){
throw 'invalidResponseFromFleetInstance';
}
// Throw an error if the provided API token is not an API-only user.
if(!responseFromFleetInstance.user.api_only) {
throw 'nonApiOnlyUser';
}
// If the API-only user associated with the token provided does not have the admin role, we'll throw an error.
// We require an admin token so we can send Vanta data about all of the active user accounts on the requesting user's Fleet instance
if(responseFromFleetInstance.user.global_role !== 'admin') {
throw 'insufficientPermissions';
}
// Send a request to the provided Fleet instance's /config endpoint to check their license tier.
let configResponse = await sails.helpers.http.get(inputs.fleetInstanceUrl+'/api/v1/fleet/config', {}, {'Authorization': 'Bearer ' +inputs.fleetApiKey})
.intercept('requestFailed','fleetInstanceNotResponding')
.intercept({raw: {statusCode: 401}}, 'invalidToken')
.intercept({raw: {statusCode: 403}}, 'requestToFleetInstanceForbidden')
.intercept({raw: {statusCode: 404}}, 'fleetInstanceNotResponding')
.intercept((error)=>{
return new Error(`When sending a request to a user's (email: ${inputs.emailAddress}) Fleet instance's (${inputs.fleetInstanceUrl}) /config API endpoint for a Vanta connection, an error occurred: ${error}`);
});
// Throw an error if the response from the Fleet instance's /config API endpoint does not contain a license.
if(!configResponse.license){
throw 'invalidResponseFromFleetInstance';
}
// If the user's Fleet instance has a free license, we'll throw the 'invalidLicense' exit and let the user know that this is only available for Fleet Premium subscribers.
if(configResponse.license.tier === 'free') {
throw 'invalidLicense';
}
// If we're not using an existing vantaConnection record for this request, we'll create a new one.
if(!existingConnectionRecord) {
// Create the VantaConnection record for this request.
await VantaConnection.create({
emailAddress: inputs.emailAddress,
vantaSourceId: sourceIDForThisRequest,
fleetInstanceUrl: inputs.fleetInstanceUrl,
fleetApiKey: inputs.fleetApiKey,
});
}
// Build the authorization URL for this request.
let vantaAuthorizationRequestURL = `https://app.vanta.com/oauth/authorize?client_id=${encodeURIComponent(sails.config.custom.vantaAuthorizationClientId)}&scope=connectors.self:write-resource connectors.self:read-resource&state=${encodeURIComponent(generatedStateForThisRequest)}&source_id=${encodeURIComponent(sourceIDForThisRequest)}&redirect_uri=${encodeURIComponent(url.resolve(sails.config.custom.baseUrl, '/vanta-authorization'))}&response_type=code`;
if(inputs.redirectToExternalPageAfterAuthorization){
let internalRedirectUrl = `${sails.config.custom.baseUrl}/redirect-vanta-authorization-request?vantaSourceId=${encodeURIComponent(sourceIDForThisRequest)}&state=${encodeURIComponent(generatedStateForThisRequest)}&vantaAuthorizationRequestURL=${encodeURIComponent(vantaAuthorizationRequestURL)}&redirectAfterSetup=${encodeURIComponent(inputs.redirectToExternalPageAfterAuthorization)}`;
return internalRedirectUrl;
// If the useInternalRedirect input was provided, we'll return the URL of an internal endpoiint that will set the required cookies for this request.
} else {
// Otherwise, if this request came from a user on the connect-vanta page, we'll set the cookies are redirect them directly to Vanta.
// Set a `state` cookie on the user's browser. This value will be checked against a query parameter when the user returns to fleetdm.com.
this.res.cookie('state', generatedStateForThisRequest, {signed: true});
// Set the sourceId to a cookie, we'll use this value to find the database record we created for this request when the user returns to fleetdm.com.
this.res.cookie('vantaSourceId', sourceIDForThisRequest, {signed: true});
return vantaAuthorizationRequestURL;
}
}
};